sharetribe/sharetribe

View on GitHub
app/models/community.rb

Summary

Maintainability
D
2 days
Test Coverage
# == Schema Information
#
# Table name: communities
#
#  id                                         :integer          not null, primary key
#  uuid                                       :binary(16)       not null
#  ident                                      :string(255)
#  domain                                     :string(255)
#  use_domain                                 :boolean          default(FALSE), not null
#  created_at                                 :datetime
#  updated_at                                 :datetime
#  settings                                   :text(65535)
#  consent                                    :string(255)
#  transaction_agreement_in_use               :boolean          default(FALSE)
#  email_admins_about_new_members             :boolean          default(FALSE)
#  use_fb_like                                :boolean          default(FALSE)
#  real_name_required                         :boolean          default(TRUE)
#  automatic_newsletters                      :boolean          default(TRUE)
#  join_with_invite_only                      :boolean          default(FALSE)
#  allowed_emails                             :text(16777215)
#  users_can_invite_new_users                 :boolean          default(TRUE)
#  private                                    :boolean          default(FALSE)
#  label                                      :string(255)
#  show_date_in_listings_list                 :boolean          default(FALSE)
#  all_users_can_add_news                     :boolean          default(TRUE)
#  custom_frontpage_sidebar                   :boolean          default(FALSE)
#  event_feed_enabled                         :boolean          default(TRUE)
#  slogan                                     :string(255)
#  description                                :text(65535)
#  country                                    :string(255)
#  members_count                              :integer          default(0)
#  user_limit                                 :integer
#  monthly_price_in_euros                     :float(24)
#  logo_file_name                             :string(255)
#  logo_content_type                          :string(255)
#  logo_file_size                             :integer
#  logo_updated_at                            :datetime
#  cover_photo_file_name                      :string(255)
#  cover_photo_content_type                   :string(255)
#  cover_photo_file_size                      :integer
#  cover_photo_updated_at                     :datetime
#  small_cover_photo_file_name                :string(255)
#  small_cover_photo_content_type             :string(255)
#  small_cover_photo_file_size                :integer
#  small_cover_photo_updated_at               :datetime
#  custom_color1                              :string(255)
#  custom_color2                              :string(255)
#  slogan_color                               :string(6)
#  description_color                          :string(6)
#  stylesheet_url                             :string(255)
#  stylesheet_needs_recompile                 :boolean          default(FALSE)
#  service_logo_style                         :string(255)      default("full-logo")
#  currency                                   :string(3)        not null
#  facebook_connect_enabled                   :boolean          default(FALSE)
#  minimum_price_cents                        :integer
#  hide_expiration_date                       :boolean          default(TRUE)
#  facebook_connect_id                        :string(255)
#  facebook_connect_secret                    :string(255)
#  google_analytics_key                       :string(255)
#  google_maps_key                            :string(64)
#  name_display_type                          :string(255)      default("first_name_with_initial")
#  twitter_handle                             :string(255)
#  use_community_location_as_default          :boolean          default(FALSE)
#  preproduction_stylesheet_url               :string(255)
#  show_category_in_listing_list              :boolean          default(FALSE)
#  default_browse_view                        :string(255)      default("grid")
#  wide_logo_file_name                        :string(255)
#  wide_logo_content_type                     :string(255)
#  wide_logo_file_size                        :integer
#  wide_logo_updated_at                       :datetime
#  listing_comments_in_use                    :boolean          default(FALSE)
#  show_listing_publishing_date               :boolean          default(FALSE)
#  require_verification_to_post_listings      :boolean          default(FALSE)
#  show_price_filter                          :boolean          default(FALSE)
#  price_filter_min                           :integer          default(0)
#  price_filter_max                           :integer          default(100000)
#  automatic_confirmation_after_days          :integer          default(14)
#  favicon_file_name                          :string(255)
#  favicon_content_type                       :string(255)
#  favicon_file_size                          :integer
#  favicon_updated_at                         :datetime
#  default_min_days_between_community_updates :integer          default(7)
#  listing_location_required                  :boolean          default(FALSE)
#  custom_head_script                         :text(65535)
#  custom_body_script                         :text(65535)
#  custom_css_script                          :text(65535)
#  follow_in_use                              :boolean          default(TRUE), not null
#  logo_processing                            :boolean
#  wide_logo_processing                       :boolean
#  cover_photo_processing                     :boolean
#  small_cover_photo_processing               :boolean
#  favicon_processing                         :boolean
#  deleted                                    :boolean
#  end_user_analytics                         :boolean          default(FALSE)
#  show_slogan                                :boolean          default(TRUE)
#  show_description                           :boolean          default(TRUE)
#  hsts_max_age                               :integer
#  footer_theme                               :integer          default("dark")
#  footer_copyright                           :text(65535)
#  footer_enabled                             :boolean          default(FALSE)
#  logo_link                                  :string(255)
#  google_connect_enabled                     :boolean
#  google_connect_id                          :string(255)
#  google_connect_secret                      :string(255)
#  linkedin_connect_enabled                   :boolean
#  linkedin_connect_id                        :string(255)
#  linkedin_connect_secret                    :string(255)
#  pre_approved_listings                      :boolean          default(FALSE)
#  allow_free_conversations                   :boolean          default(TRUE)
#  email_admins_about_new_transactions        :boolean          default(FALSE)
#  show_location                              :boolean          default(TRUE)
#  fuzzy_location                             :boolean          default(FALSE)
#  recaptcha_site_key                         :string(255)
#  recaptcha_secret_key                       :string(255)
#  enable_social_share_buttons                :boolean          default(FALSE), not null
#
# Indexes
#
#  index_communities_on_domain  (domain)
#  index_communities_on_ident   (ident)
#  index_communities_on_uuid    (uuid) UNIQUE
#

class Community < ApplicationRecord # rubocop:disable Metrics/ClassLength

  require 'compass'
  require 'sass/plugin'

  include EmailHelper
  include AttachmentDestroyer

  has_many :community_memberships, :dependent => :destroy
  has_many :members, -> { merge(CommunityMembership.accepted) }, :through => :community_memberships, :source => :person
  has_many :admins, -> { merge(CommunityMembership.admin.not_banned.not_deleted_user) }, :through => :community_memberships, :source => :person
  has_many :members_all_statuses, :through => :community_memberships, :source => :person
  has_many :invitations, :dependent => :destroy
  has_one :location, :dependent => :destroy
  has_many :community_customizations, :dependent => :destroy
  has_many :menu_links, -> { for_topbar.sorted }, :dependent => :destroy, :inverse_of => :community
  has_many :footer_menu_links, -> { for_footer.sorted }, :class_name => "MenuLink",  :dependent => :destroy, :inverse_of => :community

  has_many :categories, -> { order("sort_priority") }, :inverse_of => :community
  has_many :top_level_categories, -> { where("parent_id IS NULL").order("sort_priority") }, :class_name => "Category", :inverse_of => :community
  has_many :subcategories, -> { where("parent_id IS NOT NULL").order("sort_priority") }, :class_name => "Category", :inverse_of => :community

  has_many :conversations, :dependent => :destroy
  has_many :transactions, :dependent => :destroy

  has_many :listings, :dependent => :destroy
  has_many :listing_shapes, :dependent => :destroy
  has_many :shapes, ->{ exist_ordered }, class_name: 'ListingShape', :dependent => :destroy, :inverse_of => :community

  has_many :transaction_processes, :dependent => :destroy

  has_one :paypal_account, :dependent => :destroy # Admin paypal account

  has_many :custom_fields, -> { for_listing }, :dependent => :destroy, :inverse_of => :community
  has_many :custom_dropdown_fields, -> { for_listing.dropdown }, :class_name => "CustomField", :dependent => :destroy, :inverse_of => :community
  has_many :custom_numeric_fields, -> { for_listing.numeric }, :class_name => "NumericField", :dependent => :destroy, :inverse_of => :community
  has_many :person_custom_fields, -> { for_person.sorted }, :class_name => "CustomField",  :dependent => :destroy, :inverse_of => :community
  has_many :person_custom_dropdown_fields, -> { for_person.sorted.dropdown }, :class_name => "CustomField", :dependent => :destroy, :inverse_of => :community
  has_many :person_custom_numeric_fields, -> { for_person.sorted.numeric }, :class_name => "NumericField", :dependent => :destroy, :inverse_of => :community
  has_many :marketplace_sender_emails, :dependent => :destroy

  has_one :configuration, class_name: 'MarketplaceConfigurations', :dependent => :destroy
  has_one :social_logo, :dependent => :destroy
  has_many :social_links, -> { sorted }, :dependent => :destroy, :inverse_of => :community

  has_many_attached :landing_page_assets

  has_one :domain_setup, :dependent => :destroy

  accepts_nested_attributes_for :social_logo
  accepts_nested_attributes_for :configuration
  accepts_nested_attributes_for :footer_menu_links, allow_destroy: true
  accepts_nested_attributes_for :menu_links, allow_destroy: true
  accepts_nested_attributes_for :social_links, allow_destroy: true
  accepts_nested_attributes_for :community_customizations

  after_create :initialize_settings

  monetize :minimum_price_cents, :allow_nil => true, :with_model_currency => :currency

  # starts ends with alphanumerics can contain hyphen
  validates :ident, length: { in: 3..50 },
                    format: { with: /\A[A-Z0-9][A-Z0-9\-]*[A-Z0-9]\z/i, message: :domain_name_is_invalid },
                    uniqueness: true,
                    exclusion: { in: MarketplaceService::RESERVED_DOMAINS, message: :domain_name_is_invalid }
  # cannot contain --
  validates :ident, format: { with: /\A((?!\-\-).)*\z/, message: :domain_name_is_invalid }
  validates_length_of :slogan, :in => 2..100, :allow_nil => true
  validates_format_of :custom_color1, :with => /\A[A-F0-9_-]{6}\z/i, :allow_nil => true
  validates_format_of :custom_color2, :with => /\A[A-F0-9_-]{6}\z/i, :allow_nil => true
  validates_format_of :slogan_color, :with => /\A[A-F0-9_-]{6}\z/i, :allow_nil => true
  validates_format_of :description_color, :with => /\A[A-F0-9_-]{6}\z/i, :allow_nil => true
  validates_length_of :custom_head_script, maximum: 65535
  validates_length_of :custom_body_script, maximum: 65535
  validates_length_of :custom_css_script, maximum: 65535

  VALID_BROWSE_TYPES = %w{grid map list}
  validates_inclusion_of :default_browse_view, :in => VALID_BROWSE_TYPES

  VALID_NAME_DISPLAY_TYPES = %w{first_name_only first_name_with_initial full_name}
  validates_inclusion_of :name_display_type, :in => VALID_NAME_DISPLAY_TYPES

  # The settings hash contains some community specific settings:
  # locales: which locales are in use, the first one is the default

  serialize :settings, Hash

  has_attached_file :logo,
                    :styles => {
                      :header => "192x192#",
                      :header_icon => "40x40#",
                      :header_icon_highres => "80x80#",
                      :apple_touch => "152x152#",
                      :original => "600x600>"
                    },
                    :convert_options => {
                      # iOS makes logo background black if there's an alpha channel
                      # And the options has to be in correct order! First background, then flatten. Otherwise it will
                      # not work.
                      :apple_touch => "-background white -flatten"
                    },
                    :keep_old_files => true

  validates_attachment_content_type :logo,
                                    :content_type => IMAGE_CONTENT_TYPE

  has_attached_file :wide_logo,
                    :styles => {
                      :header => "168x40#",
                      :paypal => "190x60>", # This logo is shown in PayPal checkout page. It has to be 190x60 according to PayPal docs.
                      :header_highres => "336x80#",
                      :original => "600x600>"
                    },
                    :convert_options => {
                      # The size for paypal logo will be exactly 190x60. No cropping, instead the canvas is extended with white background
                      :paypal => "-background white -gravity center -extent 190x60"
                    },
                    :keep_old_files => true

  validates_attachment_content_type :wide_logo,
                                    :content_type => IMAGE_CONTENT_TYPE

  has_attached_file :cover_photo,
                    :styles => {
                      :header => "1600x195#",
                      :hd_header => "1920x450#",
                      :original => "3840x3840>"
                    },
                    :default_url => ->(_){ ActionController::Base.helpers.asset_path("cover_photos/header/default.jpg") },
                    :keep_old_files => true

  validates_attachment_content_type :cover_photo,
                                    :content_type => IMAGE_CONTENT_TYPE

  has_attached_file :small_cover_photo,
                    :styles => {
                      :header => "1600x195#",
                      :hd_header => "1920x96#",
                      :original => "3840x3840>"
                    },
                    :default_url => ->(_) { ActionController::Base.helpers.asset_path("cover_photos/header/default.jpg") },
                    :keep_old_files => true

  validates_attachment_content_type :small_cover_photo,
                                    :content_type => IMAGE_CONTENT_TYPE

  has_attached_file :favicon,
                    :styles => {
                      :favicon => "32x32#"
                    },
                    :default_style => :favicon,
                    :convert_options => {
                      :favicon => "-depth 32 -strip"
                    },
                    :default_url => ->(_) { ActionController::Base.helpers.asset_path("favicon.ico") }

  validates_attachment_content_type :favicon,
                                    :content_type => %w[image/jpeg image/png image/gif image/x-icon image/vnd.microsoft.icon]

  # process_in_background definitions have to be after
  # after all attachments: https://github.com/jrgifford/delayed_paperclip/issues/129
  process_in_background :logo, priority: 1
  process_in_background :wide_logo, priority: 1
  process_in_background :cover_photo, priority: 1
  process_in_background :small_cover_photo, priority: 1

  process_in_background :favicon, priority: 1

  before_save :cache_previous_image_urls

  FOOTER_THEMES = {
    FOOTER_DARK = 'dark'.freeze => 0,
    FOOTER_LIGHT = 'light'.freeze => 1,
    FOOTER_MARKETPLACE_COLOR = 'marketplace_color'.freeze => 2,
    FOOTER_LOGO = 'logo'.freeze => 3
  }.freeze
  enum footer_theme: FOOTER_THEMES

  def uuid_object
    if self[:uuid].nil?
      nil
    else
      UUIDUtils.parse_raw(self[:uuid])
    end
  end

  def uuid_object=(uuid)
    self.uuid = UUIDUtils.raw(uuid)
  end

  before_create :add_uuid
  def add_uuid
    self.uuid ||= UUIDUtils.create_raw
  end

  validates_format_of :twitter_handle, with: /\A[A-Za-z0-9_]{1,15}\z/, allow_nil: true

  validates :facebook_connect_id, numericality: { only_integer: true }, allow_nil: true
  validates :facebook_connect_id, length: {maximum: 16}, allow_nil: true

  validates_format_of :facebook_connect_secret, with: /\A[a-f0-9]{32}\z/, allow_nil: true

  attr_accessor :terms

  before_validation :check_colors, :socials_process, :check_twitter

  def check_twitter
    self.twitter_handle = twitter_handle.to_s.delete('@').presence
  end

  def check_colors
    self.slogan_color = slogan_color.to_s.delete('#').presence
    self.description_color = description_color.to_s.delete('#').presence
    self.custom_color1 = custom_color1.to_s.delete('#').presence
  end

  def socials_process
    self.facebook_connect_secret = facebook_connect_secret.presence
    self.facebook_connect_id = facebook_connect_id.presence
    self.linkedin_connect_secret = linkedin_connect_secret.presence
    self.linkedin_connect_id = linkedin_connect_id.presence
    self.google_connect_secret = google_connect_secret.presence
    self.google_connect_id = google_connect_id.presence
  end

  def description_color_string
    return unless description_color.present?

    "##{description_color}"
  end

  def custom_color1_string
    return unless custom_color1.present?

    "##{custom_color1}"
  end

  def slogan_color_string
    return unless slogan_color.present?

    "##{slogan_color}"
  end

  def social_share_buttons_disabled?
    private?
  end

  def show_listing_social_share_buttons?
    !private? && enable_social_share_buttons
  end

  def apply_main_search_keyword!
    configuration.update(main_search: :keyword)
  end

  def recaptcha_configured?
    recaptcha_site_key.present? && recaptcha_secret_key.present? && !Rails.env.test?
  end

  def google_analytics_key_ua
    return nil unless google_analytics_key.to_s.start_with?('UA-')

    google_analytics_key
  end

  def google_analytics_key_g
    return nil unless google_analytics_key.to_s.start_with?('G-')

    google_analytics_key
  end

  # Wrapper for the various attachment images url methods
  # which returns url of old image, while new one is processing.
  def stable_image_url(image_name, style = nil, options = {})
    image = send(:"#{image_name}")
    if image.processing?
      old_name = Rails.cache.read("c_att/#{id}/#{image_name}")
      return image.url(style, options) unless old_name

      # Temporarily set processing to false and the file name to the
      # old file name, so that we can call Paperclip's own url method.
      new_name = image.original_filename
      send(:"#{image_name}_processing=", false)
      send(:"#{image_name}_file_name=", old_name)

      url = image.url(style, options)

      send(:"#{image_name}_file_name=", new_name)
      send(:"#{image_name}_processing=", true)

      url
    else
      image.url(style, options)
    end
  end

  def cache_previous_image_urls
    return unless has_changes_to_save?

    changes_to_save.select { |attribute, values|
      attachment_name = attribute.chomp("_file_name")
      attribute.end_with?("_file_name") && !send(:"#{attachment_name}_processing") && values[0]
    }.each { |attribute, values|
      attachment_name = attribute.chomp("_file_name")
      # Temporarily store previous attachment file name in cache
      # so that we can still link to it, while new attachment is being processed.
      # This should probably be switched to using new columns in model, so that
      # old link doesn't break if processing fails and cache expires.
      Rails.cache.write("c_att/#{id}/#{attachment_name}", values[0], expires_in: 5.minutes)
    }
    true
  end

  def name(locale)
    customization = Maybe(community_customizations.where(locale: locale).first).or_else {
      # We should not end up in a situation where the given locale is not found.
      # However, currently that is likely to happend, because:
      # - User has one locale
      # - User can join to multiple communities, which may not have user's locale available
      fallback_customisation = community_customizations.where(locale: default_locale).first
      if !(fallback_customisation && fallback_customisation.name)
        # Corner case: switching default language to a language without localisation.
        fallback_customisation = community_customizations.where("name IS NOT NULL").order(:updated_at).last
      end
      fallback_customisation
    }

    if customization
      customization.name
    else
      raise ArgumentError.new("Cannot find translation for marketplace name community_id: #{id}, locale: #{locale}")
    end
  end

  def full_name(locale)
    name(locale)
  end

  # If community name has several words, add an extra space
  # to the end to make Finnish translation look better.
  def name_with_separator(locale)
    (name(locale).include?(" ") && locale.to_s.eql?("fi")) ? "#{name(locale)} " : name(locale)
  end

  # If community full name has several words, add an extra space
  # to the end to make Finnish translation look better.
  def full_name_with_separator(locale)
    (full_name(locale).include?(" ") && locale.to_s.eql?("fi")) ? "#{full_name(locale)} " : full_name(locale)
  end

  def address
    location ? location.address : nil
  end

  def default_locale
    if settings && !settings["locales"].blank?
      return settings["locales"].first
    else
      return APP_CONFIG.default_locale
    end
  end

  def locales
   if settings && !settings["locales"].blank?
      return settings["locales"]
    else
      # if locales not set, return the short locales from the default list
      return Sharetribe::AVAILABLE_LOCALES.map { |l| l[:ident] }
    end
  end

  # Returns the emails of admins in an array
  def admin_emails
    admins.collect { |p| p.confirmed_notification_email_addresses } .flatten
  end

  def allows_user_to_send_invitations?(user)
    (users_can_invite_new_users && user.member_of?(self)) || user.has_admin_rights?(self)
  end

  def has_customizations?
    custom_color1 || custom_color2 || slogan_color || description_color || cover_photo.present? || small_cover_photo.present? || wide_logo.present? || logo.present?
  end

  def has_custom_stylesheet?
    if APP_CONFIG.preproduction
      preproduction_stylesheet_url.present?
    else
      stylesheet_url.present?
    end
  end

  def custom_stylesheet_url
    if APP_CONFIG.preproduction
      self.preproduction_stylesheet_url
    else
      self.stylesheet_url
    end
  end

  def self.with_customizations
    customization_columns = [
      "custom_color1",
      "custom_color2",
      "cover_photo_file_name",
      "small_cover_photo_file_name",
      "wide_logo_file_name",
      "logo_file_name"
    ]

    sql = customization_columns.map { |column_name| column_name + " IS NOT NULL" }.join(" OR ")

    where(sql)
  end

  def menu_link_attributes=(attributes)
    ids = []

    attributes.each_with_index do |(id, value), i|
      if menu_link = menu_links.find_by_id(id)
        menu_link.update(value.merge(sort_priority: i))
        ids << menu_link.id
      else
        menu_links.build(value.merge(sort_priority: i))
      end
    end

    links_to_destroy = menu_links.reject { |menu_link| menu_link.id.nil? || ids.include?(menu_link.id) }
    links_to_destroy.each { |link| link.destroy }
  end

  def self.find_by_email_ending(email)
    Community.all.find_each do |community|
      return community if community.allowed_emails && community.email_allowed?(email)
    end
    return nil
  end

  def new_members_during_last(time)
    community_memberships.where(:created_at => time.ago..Time.now).collect(&:person)
  end

  # Returns the full domain with default protocol in front
  def full_url
    full_domain(:with_protocol => true)
  end

  #returns full domain without protocol
  def full_domain(options= {})
    # assume that if port is used in domain config, it should
    # be added to the end of the full domain for links to work
    # This concerns usually mostly testing and development
    default_host, default_port = APP_CONFIG.domain.split(':')
    port_string = options[:port] || default_port

    if domain.present? && use_domain? # custom domain
      dom = domain
    else # just a subdomain specified
      dom = "#{self.ident}.#{default_host}"
      dom += ":#{port_string}" unless port_string.blank?
    end

    if options[:with_protocol]
      dom = "#{(APP_CONFIG.always_use_ssl.to_s == "true" ? "https://" : "http://")}#{dom}"
    end

    return dom

  end

  # returns the community specific service name if such is in use
  # otherwise returns the global default
  def service_name
    if settings && settings["service_name"].present?
      settings["service_name"]
    else
      APP_CONFIG.global_service_name || "Sharetribe"
    end
  end

  def has_new_listings_since?(time)
    return listings.where("created_at > ?", time).present?
  end

  def get_new_listings_to_update_email(person)
    latest = person.last_community_updates_at

    selected_listings = listings
      .currently_open
      .where("updates_email_at > ? AND updates_email_at > created_at", latest)
      .order("updates_email_at DESC")
      .to_a

    additional_listings = 10 - selected_listings.length
    new_listings =
      if additional_listings > 0
        listings
          .currently_open
          .where("updates_email_at > ? AND updates_email_at = created_at", latest)
          .order("updates_email_at DESC")
          .limit(additional_listings)
          .to_a
      else
        []
      end

     selected_listings
      .concat(new_listings)
      .sort_by { |listing| listing.updates_email_at}
      .reverse
  end

  def self.find_by_allowed_email(email)
    email_ending = "@#{email.split('@')[1]}"
    where("allowed_emails LIKE ?", "%#{email_ending}%")
  end

  # Returns all the people who are admins in at least one tribe.
  def self.all_admins
    Person.joins(:community_memberships).where("community_memberships.admin = '1'").group("people.id")
  end

  def self.stylesheet_needs_recompile!
    Community.with_customizations.update_all(:stylesheet_needs_recompile => true)
  end

  # approves a membership pending email if one is found
  # if email is given, only approves if email is allowed
  # returns true if membership was now approved
  # false if it wasn't allowed or if already a member
  def approve_pending_membership(person, email_address=nil)
    membership = community_memberships.where(:person_id => person.id, :status => "pending_email_confirmation").first
    if membership && (email_address.nil? || email_allowed?(email_address))
      membership.update_attribute(:status, "accepted")
      return true
    end
    return false
  end

  def main_categories
    top_level_categories
  end

  def leaf_categories
    categories.reject { |c| !c.children.empty? }
  end

  # is it possible to pay for this listing via the payment system
  def payment_possible_for?(listing)
    listing.price && listing.price > 0 && payments_in_use?
  end

  # Deprecated
  #
  # There is a method `payment_type` is community service. Use that instead.
  def payments_in_use?
    active_payment_types.present?
  end

  def self.all_with_custom_fb_login
    begin
      where("facebook_connect_id IS NOT NULL")
    rescue Mysql2::Error
      # in some environments (e.g. Travis CI) the tables are not yet loaded when this is called
      # so return empty array, as it shouldn't matter in those cases
      return []
    end
  end

  def email_notification_types
    valid_types = Person::EMAIL_NOTIFICATION_TYPES.dup
    unless follow_in_use?
      valid_types.delete('email_about_new_listings_by_followed_people')
    end
    valid_types
  end

  def close_listings_by_author(author)
    listings.where(:author_id => author.id).update_all(:open => false)
  end

  def images_processing?
    logo.processing? ||
    wide_logo.processing? ||
    cover_photo.processing? ||
    small_cover_photo.processing? ||
    favicon.processing?
  end

  def as_json(options)
    attrs = super(options)
    uuid = UUIDUtils.parse_raw(attrs["uuid"])
    attrs.merge({"uuid" => uuid.to_s})
  end

  # FIXME-RF not the best place
  def active_payment_types
    supported = []
    supported << :paypal if PaypalHelper.paypal_active?(self.id)
    supported << :stripe if StripeHelper.stripe_active?(self.id)
    supported.size > 1 ? supported : supported.first
  end

  def is_person_only_admin(person)
    admins.count == 1 && admins.first == person
  end

  def disabled_countries
    country = []
    country << 'HU' unless currency == 'HUF'
    country
  end

  private

  def initialize_settings
    update_attribute(:settings,{"locales"=>[APP_CONFIG.default_locale]}) if self.settings.blank?
    true
  end
end