SpinaCMS/Spina

View on GitHub
app/models/spina/account.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
90%
module Spina
  class Account < ApplicationRecord
    include AttrJson::Record
    include AttrJson::NestedAttributes
    include Partable
    include TranslatedContent

    serialize :preferences, coder: Psych

    after_save :bootstrap_website

    validates :name, presence: true

    def to_s
      name
    end

    # Spina previously stored account preferences with symbols as keys.
    # Because of CVE-2022-32224 we're changing that to strings instead.
    # This fallback ensures backwards compatibility, but in the long run this
    # should be refactored to use a simple JSONB-column with Postgres.
    def self.serialized_attr_accessor(*args)
      args.each do |method_name|
        define_method method_name do
          if preferences.try(:[], method_name.to_sym).present?
            ActiveSupport::Deprecation.warn("#{method_name} is stored as a symbol. Please set and save it again using #{method_name}= on your Spina::Account model to store it as a string. You can do this from the UI by saving your account preferences.")
          end

          preferences.try(:[], method_name.to_s) ||
            preferences.try(:[], method_name.to_sym)
        end

        define_method "#{method_name}=" do |value|
          self.preferences ||= {}
          self.preferences.except!(method_name.to_sym)
          self.preferences[method_name.to_s] = value
        end
      end
    end

    serialized_attr_accessor :google_analytics, :google_site_verification, :facebook, :twitter, :instagram, :youtube, :linkedin, :google_plus, :theme

    private

    def bootstrap_website
      theme_config = ::Spina::Theme.find_by_name(theme)
      if theme_config

        bootstrap_navigations(theme_config)
        bootstrap_pages(theme_config)
        bootstrap_resources(theme_config)
      end
    end

    def bootstrap_navigations(theme)
      theme.navigations.each_with_index do |navigation, index|
        Navigation.where(name: navigation[:name]).first_or_create.update(navigation.merge(position: index))
      end
    end

    def bootstrap_pages(theme)
      find_or_create_custom_pages(theme)
      deactivate_unused_view_templates(theme)
      activate_used_view_templates(theme)
    end

    def bootstrap_resources(theme)
      theme.resources.each do |resource|
        Resource.where(name: resource[:name]).first_or_create.update(resource)
      end
    end

    def find_or_create_custom_pages(theme)
      theme.custom_pages.each do |page|
        ancestry = nil

        if page[:parent].present?
          parent_page = Page.find_by(name: page[:parent])
          ancestry = [parent_page&.ancestry, parent_page&.id].compact.join("/")
        end
        
        Page.where(name: page[:name])
          .first_or_create(title: page[:title])
          .update(view_template: page[:view_template], deletable: page[:deletable], ancestry: ancestry)
      end
    end

    def deactivate_unused_view_templates(theme)
      Page.active.where.not(view_template: theme.view_templates.map { |h| h[:name] }).update_all(active: false)
    end

    def activate_used_view_templates(theme)
      Page.where(active: false, view_template: theme.view_templates.map { |h| h[:name] }).update_all(active: true)
    end
  end
end