artirix/browsercms

View on GitHub
app/controllers/cms/content_block_controller.rb

Summary

Maintainability
D
1 day
Test Coverage
require 'cms/category_type'
# This is not called directly
# This is the base class for other content blocks
module Cms
  class ContentBlockController < Cms::BaseController
    include Cms::ContentRenderingSupport

    allow_guests_to [:show_via_slug]

    helper_method :block_form, :content_type
    helper Cms::RenderingHelper
    helper do

      # Addressable content types don't allow for Mobile Optimized templates.
      def mobile_template_exists?(block)
        false
      end
    end
    include Cms::PublishWorkflow

    def index
      load_blocks
    end

    def bulk_update
      ids    = params[:content_id] || []
      models = ids.collect do |id|
        model_class.find(id.to_i)
      end
      if params[:commit] == 'Delete'
        deleted        = models.select do |m|
          m.destroy
        end
        flash[:notice] = "Deleted #{deleted.size} records."
      else
        # Need to do these one at a time since the code logic is more complex than single UPDATE.
        published = models.select do |m|
          m.publish!
        end

        flash[:notice] = "Published #{published.size} records."
      end

      redirect_to action: :index
    end

    # Getting content by its path  (i.e. /products/:slug)
    def show_via_slug
      @block = model_class.with_slug(params[:slug])
      unless @block
        raise Cms::Errors::ContentNotFound.new("No Content at #{model_class.calculate_path(params[:slug])}")
      end
      render_block_in_main_container
    end

    # Getting content by its id (i.e. /products/:id)
    # Logged in editors will get the editing frame.
    def show
      load_block_draft
      render_editing_frame_or_block_in_main_container
    end

    # Getting the content for the editing frame.
    def inline
      load_block_draft
      render_block_in_main_container
    end

    def new
      build_block

      if readonly?
        url = [params[:_redirect_to], engine_aware_path(@block, nil)].map(&:presence).compact.first
        if url
          return redirect_to url, alert: 'Read only models cannot be created'
        else
          return render_not_implemented
        end
      end

      set_default_category
    end

    def create
      return render_not_implemented if readonly?

      if create_block
        after_create_on_success
      else
        after_create_on_failure
      end
    rescue Exception => @exception
      raise @exception if @exception.is_a?(Cms::Errors::AccessDenied)
      after_create_on_error
    end

    def edit
      load_block_draft

      if readonly?
        url = [params[:_redirect_to], engine_aware_path(@block)].map(&:presence).compact.first
        if url
          return redirect_to url, alert: 'Read only models cannot be edited'
        else
          return render_not_implemented
        end
      end
    end

    def update
      return render_not_implemented if readonly?

      if update_block
        after_update_on_success
      else
        after_update_on_failure
      end
    rescue ActiveRecord::StaleObjectError => @exception
      after_update_on_edit_conflict
    rescue Exception => @exception
      raise @exception if @exception.is_a?(Cms::Errors::AccessDenied)
      after_update_on_error
    end

    def destroy
      return render_not_implemented if readonly?

      do_command("deleted") { @block.destroy }
      respond_to do |format|
        format.html { redirect_to_first params[:_redirect_to], engine_aware_path(@block.class) }
        format.json { render :json => { :success => true } }
      end

    end

    # Additional CMS Action

    def publish
      return render_not_implemented if readonly?

      do_command("published") { @block.publish! }
      redirect_to_first params[:_redirect_to], engine_aware_path(@block, nil)
    end

    def revert_to
      return render_not_implemented if readonly?

      do_command("reverted to version #{params[:version]}") do
        revert_block(params[:version])
      end
      redirect_to_first params[:_redirect_to], engine_aware_path(@block, nil)
    end

    def version
      return render_not_implemented unless versioned?

      load_block
      if params[:version]
        @block = @block.as_of_version(params[:version])
      end
      render "show_in_isolation"
    end

    def versions
      return render_not_implemented unless versioned?

      load_block
    end

    def new_button_path
      new_engine_aware_path(content_type) unless readonly?
    end

    protected

    def render_not_implemented
      render text: 'Not Implemented', status: :not_implemented
    end

    def versioned?
      model_class.versioned?
    end

    def readonly?
      model_class.readonly?
    end

    def content_type_name
      self.class.name.sub(/Controller/, '').singularize
    end

    def content_type
      @content_type ||= ContentType.find_by_key(content_type_name)
    end

    def model_class
      content_type.model_class
    end

    def model_form_name
      content_type.param_key
    end

    alias :resource_param :model_form_name

    def resource
      @block ||= find_block
    end

    # methods for loading one or a collection of blocks

    def load_blocks
      scope                  = load_blocks_scope
      @total_number_of_items = scope.count

      scope   = add_scope_pagination(scope)
      @blocks = scope

      check_permissions
    end

    def add_scope_pagination(scope)
      scope.paginate(pagination_options)
    end

    def pagination_options
      options = {}

      options[:page]  = params[:page]
      options[:order] = model_class.default_order if model_class.respond_to?(:default_order)
      options[:order] = params[:order] unless params[:order].blank?

      options
    end

    def search_filter
      @search_filter ||= SearchFilter.build(params[:search_filter], model_class)
    end
    helper_method :search_filter

    def load_blocks_scope
      scope = model_class.respond_to?(:list) ? model_class.list : model_class
      if scope.searchable?
        scope = scope.search(search_filter.term)
      end
      if params[:section_id] && model_class.respond_to?(:with_parent_id)
        scope = scope.with_parent_id(params[:section_id])
      end
      scope
    end

    def load_block
      find_block
      check_permissions
    end

    def find_block
      @block = model_class.find(params[:id])
    end

    def load_block_draft
      find_block
      @block = @block.as_of_draft_version if model_class.versioned?
      check_permissions
    end

    # path related methods - available in the view as helpers

    # This is the partial that will be used in the form
    def block_form
      @content_type.form
    end


    def build_block
      if params[model_form_name]
        @block = model_class.new(model_params)
      else
        # Need to make sure @block exists for form helpers to correctly generate paths
        @block = model_class.new unless @block
      end
      check_permissions
    end

    def set_default_category
      if @last_block = model_class.last
        @block.category = @last_block.category if @block.respond_to?(:category=)
      end
    end

    def create_block
      build_block
      @block.save
    end

    def after_create_on_success
      block          = @block.class.versioned? ? @block.draft : @block
      flash[:notice] = "#{content_type.display_name} '#{block.name}' was created"
      if @block.class.connectable? && @block.connected_page
        redirect_to @block.connected_page.path
      else
        redirect_to_first params[:_redirect_to], engine_aware_path(@block)
      end
    end

    def after_create_on_failure
      render "new"
    end

    def after_create_on_error
      log_complete_stacktrace(@exception)
      after_create_on_failure
    end

    def after_update_on_error
      log_complete_stacktrace(@exception)
      after_update_on_failure
    end


    # update related methods
    def update_block
      load_block
      @block.update_attributes(model_params())
    end

    # Returns the parameters for the block to be saved.
    # Handles defaults as well as eventually 'strong_params'
    def model_params
      defaults     = { "publish_on_save" => false }
      model_params = params[model_form_name]
      defaults.merge(model_params)
    end

    def after_update_on_success
      flash[:notice] = "#{content_type_name.demodulize.titleize} '#{@block.name}' was updated"
      redirect_to_first params[:_redirect_to], engine_aware_path(@block)
    end

    def after_update_on_failure
      render "edit"
    end

    def after_update_on_edit_conflict
      @other_version = @block.class.find(@block.id)
      after_update_on_failure
    end


    # A "command" is when you want to perform an action on a content block
    # You pass a ruby block to this method, this calls it
    # and then sets a flash message based on success or failure
    def do_command(result)
      load_block
      if yield
        flash[:notice] = "#{content_type_name.demodulize.titleize} '#{@block.name}' was #{result}" unless request.xhr?
      else
        flash[:error] = "#{content_type_name.demodulize.titleize} '#{@block.name}' could not be #{result}" unless request.xhr?
      end

    end

    def revert_block(to_version)
      begin
        @block.revert_to(to_version)
      rescue Exception => @exception
        Cms::ErrorHandling::NotifierService.notify @exception
        logger.warn "Could not revert #{@block.inspect} to version #{to_version}"
        logger.warn "#{@exception.message}\n:#{@exception.backtrace.join("\n")}"
        false
      end
    end

    # Use a "whitelist" approach to access to avoid mistakes
    # By default everyone can create new block and view them and their properties,
    # but blocks can only be modified based on the permissions of the pages they
    # are connected to.
    def check_permissions
      case action_name
      when "index", "show", "new", "create", "version", "versions"
        # Allow
      when "edit", "update", "inline"
        raise Cms::Errors::AccessDenied unless current_user.able_to_edit?(@block)
      when "destroy", "publish", "revert_to"
        raise Cms::Errors::AccessDenied unless current_user.able_to_publish?(@block)
      else
        raise Cms::Errors::AccessDenied
      end
    end

    private

    def render_block_in_main_container
      ensure_current_user_can_view(@block)
      show_content_as_page(@block)
      render 'render_block_in_main_container', layout: @block.class.layout
    end

    def render_block_in_content_library
      render 'show_in_isolation'
    end

    def render_editing_frame_or_block_in_main_container
      if @block.class.addressable?
        if current_user.able_to_edit?(@block)
          render_toolbar_and_iframe
        else
          render_block_in_main_container
        end
      else
        render_block_in_content_library
      end
    end

    def render_toolbar_and_iframe
      @page       = @block
      @page_title = @block.page_title
      render "show", :layout => 'cms/page_editor'
    end
  end
end