Noosfero/noosfero

View on GitHub
app/controllers/my_profile/cms_controller.rb

Summary

Maintainability
F
6 days
Test Coverage
class CmsController < MyProfileController
  protect "edit_profile", :profile, only: [:set_home_page]

  include ArticleHelper
  include CategoriesHelper
  include SearchTags
  include Captcha

  def self.protect_if(*args)
    before_action(*args) do |c|
      user, profile = c.send(:user), c.send(:profile)
      if yield(c, user, profile)
        true
      else
        access_denied = _("You do not have access to ")
        render_access_denied("#{access_denied}#{c.request.path_info}")
        false
      end
    end
  end

  before_action :login_required, except: [:suggest_an_article]
  before_action :load_recent_files, only: [:new, :edit]

  helper_method :file_types

  protect_if except: [:suggest_an_article, :set_home_page, :edit, :destroy, :publish, :publish_on_portal_community, :publish_on_communities, :search_communities_to_publish, :upload_files, :new] do |c, user, profile|
    user && user.has_permission?("post_content", profile)
  end

  protect_if only: [:new, :upload_files] do |c, user, profile|
    parent_id = c.params[:article].present? ? c.params[:article][:parent_id] : c.params[:parent_id]
    parent = profile.articles.find_by(id: parent_id)
    user && user.can_post_content?(profile, parent)
  end

  protect_if only: :destroy do |c, user, profile|
    profile.articles.find(c.params[:id]).allow_post_content?(user)
  end

  protect_if only: :edit do |c, user, profile|
    profile.articles.find(c.params[:id]).allow_edit?(user)
  end

  def boxes_holder
    profile
  end

  def view
    @article = profile.articles.find(params[:id])
    @articles = @article.children.reorder("case when type = 'Folder' then 0 when type ='Blog' then 1 else 2 end, updated_at DESC, name")
    @articles = @articles.where "type <> ?", "RssFeed" if @article.has_posts?
    @articles = @articles.paginate per_page: per_page, page: params[:npage]
  end

  def index
    @article = nil
    @articles = profile.top_level_articles
                       .order("case when type = 'Folder' then 0 when type ='Blog' then 1 else 2 end, updated_at DESC")
                       .paginate(per_page: per_page, page: params[:npage])

    render action: "view"
  end

  def edit
    params[:article] ||= {}
    @success_back_to = params[:success_back_to]
    @article = profile.articles.find(params[:id])
    version = params[:version]
    @article.revert_to(version) if version

    @parent_id = params[:parent_id]
    @type = params[:type] || @article.class.to_s
    translations if @article.translatable?
    continue = params[:continue]

    @article.article_privacy_exceptions = params[:q].split(/,/).map { |n| environment.people.find n.to_i } unless params[:q].nil?

    @tokenized_children = prepare_to_token_input(
      profile.members.includes(:articles_with_access).find_all { |m|
        m.articles_with_access.include?(@article)
      }
    )
    refuse_blocks
    record_coming
    if request.post?
      if @article.image.present? && params[:article][:image_builder] && params[:article][:image_builder][:label]
        @article.image.label = params[:article][:image_builder][:label]
        @article.image.save!
      end
      params_metadata = params[:article].try(:delete, :metadata) || {}
      custom_fields = params_metadata.try(:delete, :custom_fields) || {}
      @article.metadata = @article.metadata.merge(params_metadata)
      @article.metadata["custom_fields"] = custom_fields
      @article.last_changed_by = user
      @article.update_access_level(params[:article][:access])
      params[:article].delete(:access)

      if @article.update(params[:article])
        if !continue
          if @article.content_type.nil? || @article.image?
            success_redirect
          else
            redirect_to action: (@article.parent ? "view" : "index"), id: @article.parent
          end
        end
      end
    end
  end

  def new
    # FIXME this method should share some logic with edit !!!
    @success_back_to = params[:success_back_to]
    # user must choose an article type first

    @parent = profile.articles.find(params[:parent_id]) if params && params[:parent_id].present?
    record_coming
    @type = params[:type]
    if @type.blank?
      @article_types = []
      available_article_types.each do |type|
        @article_types.push(
          class: type,
          short_description: type.short_description,
          description: type.description
        )
      end
      @parent_id = params[:parent_id]
      render action: "select_article_type", layout: false, back_to: @back_to
      return
    else
      refuse_blocks
    end

    raise "Invalid article type #{@type}" unless valid_article_type?(@type)

    klass = @type.constantize
    article_data = environment.enabled?("articles_dont_accept_comments_by_default") ? { accept_comments: false } : {}

    article_data.merge!(params[:article]) if params[:article]
    article_data.merge!(profile: profile) if profile

    @article = if params[:clone]
                 current_article = profile.articles.find(params[:id])
                 current_article.copy_without_save
               else
                 klass.new(article_data)
    end

    @article.access = profile.access

    parent = check_parent(params[:parent_id])
    if parent
      @article.parent = parent
      @article.published = parent.published
      @article.show_to_followers = parent.show_to_followers
      @article.highlighted = parent.highlighted
      @article.access = parent.access
      @parent_id = parent.id
    end

    @article.profile = profile
    @article.author = user
    @article.editor = current_person.editor
    @article.last_changed_by = user
    @article.created_by = user
    @article.update_access_level(article_data["access"]) if article_data["access"]

    translations if @article.translatable?

    continue = params[:continue]
    if request.post?
      @article.article_privacy_exceptions = params[:q].split(/,/).map { |n| environment.people.find n.to_i } unless params[:q].nil?
      if @article.save
        if continue
          redirect_to action: "edit", id: @article
        else
          respond_to do |format|
            format.html { success_redirect }
            format.json { render plain: { id: @article.id, full_name: profile.identifier + "/" + @article.full_name }.to_json }
          end
        end
        return
      end
    end
    render action: "edit"
  end

  post_only :set_home_page
  def set_home_page
    return render_access_denied unless user.can_change_homepage?

    article = (params[:id].nil? || params[:id].blank?) ? nil : profile.articles.find(params[:id])
    profile.update_attribute(:home_page, article)

    if article.nil?
      session[:notice] = _("Homepage reseted.")
    else
      session[:notice] = _('"%s" configured as homepage.') % article.title
    end

    redirect_to (request.referer || profile.url)
  end

  def upload_files
    upload_single_file?
    @uploaded_files = []
    @parent = check_parent(params[:parent_id])
    @target = @parent ? ("/%s/%s" % [profile.identifier, @parent.full_name]) : "/%s" % profile.identifier
    record_coming
    if request.post? && params[:uploaded_files]
      params[:uploaded_files].each do |_, file|
        if file && file.has_key?("file") && file[:file] != ""
          data = {
            uploaded_data: file[:file],
            profile: profile,
            parent: @parent,
            last_changed_by: user,
            author: user,
          }
          data = data.merge(cropped_params(file)) if file.key?(:crop_x)
          @uploaded_files << UploadedFile.create(data, without_protection: true)
        end
      end
      @errors = @uploaded_files.select { |f| f.errors.any? }
      if @errors.any?
        render action: "upload_files", parent_id: @parent_id
      else
        session[:notice] = _("File(s) successfully uploaded")
        if @back_to
          redirect_to url_for(@back_to)
        elsif @parent
          redirect_to action: "view", id: @parent.id
        else
          redirect_to action: "index"
        end
      end
    end
  end

  def destroy
    @article = profile.articles.find(params[:id])
    if request.post?
      @article.destroy
      session[:notice] = _("\"%s\" was removed." % @article.title)
      referer = Rails.application.routes.recognize_path URI.parse(request.referer).path rescue nil
      if referer && (referer[:controller] == "cms") && (referer[:action] != "edit")
        redirect_to referer
      elsif @article.parent
        redirect_to @article.parent.url
      else
        redirect_to profile.url
      end
    end
  end

  def why_categorize
    render action: params[:action], layout: false
  end

  def update_categories
    @object = params[:id] ? @profile.articles.find(params[:id]) : Article.new
    render_categories "article"
  end

  def search_communities_to_publish
    scope = user.memberships.distinct(false).where.not(id: profile)
    results = find_by_contents(:profiles, environment, scope, params["q"], { page: 1 }, { fields: ["name"] })[:results]
    render plain: results.map { |community| { id: community.id, name: community.name } }
                         .uniq { |c| c[:id] }.to_json
  end

  def publish
    @article = profile.articles.find(params[:id])
    record_coming
    @failed = {}
    if request.post?
      article_name = params[:name]
      task = ApproveArticle.create!(article: @article, name: article_name, target: user, requestor: user)
      begin
        task.finish
      rescue Exception => ex
        @failed[ex.message] ? @failed[ex.message] << @article.title : @failed[ex.message] = [@article.title]
        task.cancel
      end
      if @failed.blank?
        session[:notice] = _("You published this content successfully")
        if @back_to
          redirect_to @back_to
        else
          redirect_to @article.view_url
        end
      end
    end
  end

  def publish_on_communities
    if request.post?
      @back_to = params[:back_to]
      @article = profile.articles.find(params[:id])
      @failed = {}
      article_name = params[:name]
      params_marked = (params["q"] || "").split(",").select { |marked| user.memberships.map(&:id).include? marked.to_i }
      @marked_groups = Profile.find(params_marked)
      if @marked_groups.empty?
        redirect_to @back_to
        return session[:notice] = _("Select some group to publish your article")
      end
      @marked_groups.each do |item|
        task = ApproveArticle.create!(article: @article, name: article_name, target: item, requestor: user)
        begin
          task.finish unless item.moderated_articles?
        rescue Exception => ex
          @failed[ex.message] ? @failed[ex.message] << item.name : @failed[ex.message] = [item.name]
          task.cancel
        end
      end
      if @failed.blank?
        session[:notice] = _("Your publish request was sent successfully")
        if @back_to
          redirect_to @back_to
        else
          redirect_to @article.view_url
        end
      else
        session[:notice] = _("Some of your publish requests couldn't be sent.")
        render action: "publish"
      end
    end
  end

  def publish_on_portal_community
    if request.post?
      @article = profile.articles.find(params[:id])
      if environment.portal_enabled
        task = ApproveArticle.create!(article: @article, name: params[:name], target: environment.portal_community, requestor: user)
        begin
          task.finish unless environment.portal_community.moderated_articles?
          session[:notice] = _("Your publish request was sent successfully")
        rescue
          session[:notice] = _("Your publish request couldn't be sent.")
          task.cancel
        end
      else
        session[:notice] = _("There is no portal community to publish your article.")
      end

      if @back_to
        redirect_to @back_to
      else
        redirect_to @article.view_url
      end
    end
  end

  def suggest_an_article
    @back_to = params[:back_to] || request.referer || url_for(profile.public_profile_url)
    @task = SuggestArticle.new(params[:task])
    if request.post?
      @task.target = profile
      @task.ip_address = request.remote_ip
      @task.user_agent = request.user_agent
      @task.referrer = request.referrer
      @task.requestor = current_person if logged_in?
      if verify_captcha(:suggest_article, @task, user, environment, profile) && @task.save
        session[:notice] = _("Thanks for your suggestion. The community administrators were notified.")
        redirect_to @back_to
      end
    end
  end

  def search
    query = params[:q]
    results = find_by_contents(:uploaded_files, profile, profile.files.published, query)[:results]
    render plain: article_list_to_json(results).html_safe, content_type: "application/json"
  end

  def media_upload
    parent = check_parent(params[:parent_id])
    if request.post?
      begin
        file = params[:file].present? ? params[:file] : params[:crop][:file]
        data = { uploaded_data: file,
                 profile: profile,
                 parent: parent }
        data = data.merge(cropped_params(params[:crop])) if params.include?(:crop)
        @file = UploadedFile.create!(data) unless params[:file] == ""
        @file = FilePresenter.for(@file)
        respond_to do |format|
          format.js
        end
      rescue Exception => exception
        render plain: :exception.to_s, status: :bad_request
      end
    end
  end

  def published_media_items
    load_recent_files(params[:parent_id], params[:q])
    render partial: "published_media_items"
  end

  def view_all_media
    paginate_options = { page: params[:page].blank? ? 1 : params[:page] }
    @key = params[:key].to_sym
    load_recent_files(params[:parent_id], params[:q], paginate_options)
  end

  def files
    @files = profile.files.paginate(per_page: per_page, page: params[:npage])
    @filters = {
      _("Name") => "name",
      _("Size (bigger first)") => "size DESC",
      _("Size (smaller first)") => "size ASC"
    }

    @sort_by = params[:sort_by] || "name"
    if @sort_by.in?(@filters.values)
      @files = @files.order(@sort_by)
    else
      @files = @files.order("name")
    end

    respond_to do |format|
      format.html
      format.js
    end
  end

  def invite_to_event
    @article = profile.articles.find(params[:id])
    @profiles = invite_event_to @article
    record_coming
    if request.post?
      @back_to = params[:back_to]
      @failed = {}

      people_to_invite = @profiles.select { |profile|
        params[:profile_ids].include? profile.id.to_s
      }

      if people_to_invite.empty?
        render action: "invite_to_event"
        return session[:notice] = _("Are not you going to invite anyone?")
      end

      people_to_invite.each do |person|
        begin
          task = InviteEvent.create!(metadata: { event_id: @article.id, message: params[:data][:message] },
                                     target: person, requestor: user)
        rescue Exception => ex
          @failed[ex.message] ? @failed[ex.message] << person.name : @failed[ex.message] = [person.name]
        end
      end

      if @failed.blank?
        session[:notice] = _("Your friends were invited successfully.")
        if @back_to
          redirect_to @back_to
        else
          redirect_to @article.view_url
        end
      else
        session[:notice] = _("Some of your friends could not be invited.")
        render action: "invite_to_event"
      end
    end
  end

  def sensitive_content
    current_page = params[:page] ? Article.find(params[:page]) : nil
    select_subdirectory = !params[:select_subdirectory].nil?

    @sensitive_back = params[:not_back] ? (params[:not_back] == "true") : false

    @sensitive_content = SensitiveContent.new(
      user: user,
      page: current_page,
      alternative_context: params[:alternative_context],
      profile: profile,
      select_subdirectory: select_subdirectory
    )

    if params[:select_directory]
      render action: "select_directory"
    else
      render action: "sensitive_content"
    end
  end

  def select_profile
    current_page = params[:page] ? Article.find(params[:page]) : nil

    @sensitive_back = params[:back] ? params[:back] : false

    @sensitive_content = SensitiveContent.new(user: user, page: current_page,
                                              alternative_context: params[:alternative_context],
                                              profile: profile)

    if params[:select_type].present?
      case params[:select_type]
      when "community"
        @profiles = user.communities_with_post_permisson
      when "enterprise"
        @profiles = user.enterprises_with_post_permisson
      else
        @profiles = nil
      end
    else
      @profiles = nil
    end
  end

  protected

    include CmsHelper

    def available_article_types
      articles = [
        TextArticle,
        Event
      ]
      articles += special_article_types if params && params[:cms]
      parent_id = params ? params[:parent_id] : nil
      if @parent && @parent.blog?
        articles -= Article.folder_types.map(&:constantize)
      end
      articles
    end

    def special_article_types
      [Folder, Blog, UploadedFile, Forum, Gallery, RssFeed] + @plugins.dispatch(:content_types)
    end

    def record_coming
      if request.post?
        @back_to = params[:back_to]
      else
        @back_to = params[:back_to] || request.referer
      end
    end

    def valid_article_type?(type)
      (available_article_types + special_article_types).map { |item| item.name }.include?(type)
    end

    def check_parent(id)
      if !id.blank?
        parent = profile.articles.find(id)
        if !parent.allow_children?
          raise ArgumentError.new("cannot create child of article which does not accept children")
        end

        parent
      else
        nil
      end
    end

    def refuse_blocks
      @no_design_blocks = @type.present? && valid_article_type?(@type) ? !@type.constantize.can_display_blocks? : false
    end

    def per_page
      10
    end

    def translations
      @locales = environment.locales.invert.reject { |name, lang| !@article.possible_translations.include?(lang) }
      @selected_locale = @article.language || FastGettext.locale
    end

    def article_list_to_json(list)
      list.map do |item|
        {
          "title" => item.title,
          "url" => item.image? ? item.public_filename(:uploaded) : url_for(item.url),
          :icon => icon_for_article(item),
          :content_type => item.mime_type,
          :error => item.errors.any? ? _("%s could not be uploaded") % item.title : nil,
        }
      end.to_json
    end

    def content_editor?
      true
    end

    def success_redirect
      if !@success_back_to.blank?
        redirect_to @success_back_to
      else
        redirect_to @article.view_url
      end
    end

    def file_types
      { images: _("Images"), generics: _("Files") }
    end

    def load_recent_files(parent_id = nil, q = nil, paginate_options = { page: 1, per_page: 6 })
      # TODO Since we only have special support for images, I'm limiting myself to
      #     consider generic files as non-images. In the future, with more supported
      #     file types we'll need to have a smart way to fetch from the database
      #     scopes of each supported type as well as the non-supported types as a
      #     whole.
      @recent_files = {}

      parent = parent_id.present? ? profile.articles.find(parent_id) : nil
      if parent.present?
        files = parent.children.files
      else
        files = profile.files
      end

      files = files.reorder("created_at DESC")
      images = files.images
      generics = files.no_images

      if q.present?
        @recent_files[:images] = find_by_contents(:images, profile, images, q, paginate_options)[:results]
        @recent_files[:generics] = find_by_contents(:generics, profile, generics, q, paginate_options)[:results]
      else
        @recent_files[:images] = images.paginate(paginate_options)
        @recent_files[:generics] = generics.paginate(paginate_options)
      end
    end

    def upload_single_file?
      if profile.allow_single_file?
        @article = UploadedFile.new if @article.nil?
        @type = "UploadedFile"
        redirect_to action: "new", type: "UploadedFile", back_to: params[:back_to], parent_id: params[:parent_id]
      end
    end

    def cropped_params(file_params)
      data = {}
      if file_params[:crop_x].present?
        data = {
          crop_x: file_params[:crop_x],
          crop_y: file_params[:crop_y],
          crop_w: file_params[:crop_w],
          crop_h: file_params[:crop_h]
        }
      end
      data
    end

    def invite_event_to(article)
      profiles = profile.kind_of?(Organization) ? profile.members : profile.friends
      inviteds = article.invitations if article.event?
      result = []
      profiles.each do |profile|
        result << profile unless inviteds.find_by(guest: profile)
      end
      result
    end
end