internetee/registry

View on GitHub
app/models/domain.rb

Summary

Maintainability
F
3 days
Test Coverage
A
90%
class Domain < ApplicationRecord
  include UserEvents
  include Roids
  include Versions # version/domain_version.rb
  include Domain::Expirable
  include Domain::Activatable
  include Domain::ForceDelete
  include Domain::Discardable
  include Domain::Deletable
  include Domain::Transferable
  include Domain::RegistryLockable
  include Domain::Releasable
  include Domain::Disputable
  include Domain::BulkUpdatable

  PERIODS = [
    ['3 months', '3m'],
    ['6 months', '6m'],
    ['9 months', '9m'],
    ['1 year', '1y'],
    ['2 years', '2y'],
    ['3 years', '3y'],
    ['4 years', '4y'],
    ['5 years', '5y'],
    ['6 years', '6y'],
    ['7 years', '7y'],
    ['8 years', '8y'],
    ['9 years', '9y'],
    ['10 years', '10y'],
  ].freeze

  attr_accessor :roles,
                :legal_document_id,
                :is_admin,
                :registrant_typeahead,
                :update_me,
                :epp_pending_update,
                :epp_pending_delete,
                :reserved_pw

  alias_attribute :on_hold_time, :outzone_at
  alias_attribute :outzone_time, :outzone_at
  alias_attribute :auth_info, :transfer_code # Old attribute name; for PaperTrail
  alias_attribute :registered_at, :created_at

  store_accessor :json_statuses_history,
                 :force_delete_domain_statuses_history,
                 :admin_store_statuses_history

  # TODO: whois requests ip whitelist for full info for own domains and partial info for other domains
  # TODO: most inputs should be trimmed before validation, probably some global logic?

  belongs_to :registrar, required: true
  belongs_to :registrant, required: true
  # TODO: should we user validates_associated :registrant here?

  has_many :admin_domain_contacts
  accepts_nested_attributes_for :admin_domain_contacts,
                                allow_destroy: true, reject_if: :admin_change_prohibited?
  has_many :tech_domain_contacts
  accepts_nested_attributes_for :tech_domain_contacts,
                                allow_destroy: true, reject_if: :tech_change_prohibited?

  def registrant_change_prohibited?
    statuses.include? DomainStatus::SERVER_REGISTRANT_CHANGE_PROHIBITED
  end

  # NB! contacts, admin_contacts, tech_contacts are empty for a new record
  has_many :domain_contacts, dependent: :destroy
  has_many :contacts, through: :domain_contacts, source: :contact
  has_many :admin_contacts, through: :admin_domain_contacts, source: :contact
  has_many :tech_contacts, through: :tech_domain_contacts, source: :contact
  has_many :nameservers, dependent: :destroy, inverse_of: :domain

  accepts_nested_attributes_for :nameservers, allow_destroy: true,
                                              reject_if: proc { |attrs| attrs[:hostname].blank? }

  has_many :transfers, class_name: 'DomainTransfer', dependent: :destroy

  has_many :dnskeys, dependent: :destroy

  has_one  :whois_record # destroyment will be done in after_commit

  accepts_nested_attributes_for :dnskeys, allow_destroy: true

  has_many :legal_documents, as: :documentable
  has_many :registrant_verifications, dependent: :destroy
  has_one :csync_record, dependent: :destroy

  after_initialize do
    self.pending_json = {} if pending_json.blank?
    self.statuses = [] if statuses.nil?
    self.status_notes = {} if status_notes.nil?
  end

  before_save :manage_automatic_statuses
  before_save :touch_always_version
  def touch_always_version
    self.updated_at = Time.zone.now
  end

  before_update :manage_statuses
  def manage_statuses
    return unless will_save_change_to_registrant_id? # rollback has not yet happened

    pending_update! if registrant_verification_asked?
    true
  end

  after_commit :update_whois_record

  after_create :update_reserved_domains
  def update_reserved_domains
    ReservedDomain.new_password_for(name) if in_reserved_list?
  end

  validates :name_dirty, domain_name: true, uniqueness: true
  validates :puny_label, length: { maximum: 63 }
  validates :period, presence: true, numericality: { only_integer: true }
  validates :transfer_code, presence: true
  validate :validate_reservation

  def validate_reservation
    return if persisted? || !in_reserved_list?

    if reserved_pw.blank?
      errors.add(:base, :required_parameter_missing_reserved)
      return false
    end

    return if ReservedDomain.pw_for(name) == reserved_pw

    errors.add(:base, :invalid_auth_information_reserved)
  end

  validate :status_is_consistant
  def status_is_consistant
    has_error = (hold_status? && statuses.include?(DomainStatus::SERVER_MANUAL_INZONE))
    if !has_error && statuses.include?(DomainStatus::PENDING_DELETE)
      has_error = statuses.include? DomainStatus::SERVER_DELETE_PROHIBITED
    end
    errors.add(:domains, I18n.t(:object_status_prohibits_operation)) if has_error
  end

  # Removed to comply new ForceDelete procedure
  # at https://github.com/internetee/registry/issues/1428#issuecomment-570561967
  #
  # validate :check_permissions, :unless => :is_admin
  # def check_permissions
  #   return unless force_delete_scheduled?
  #   errors.add(:base, I18n.t(:object_status_prohibits_operation))
  #   false
  # end

  validates :nameservers, domain_nameserver: {
    min: -> { Setting.ns_min_count },
    max: -> { Setting.ns_max_count },
  }

  validates :dnskeys, object_count: {
    min: -> { Setting.dnskeys_min_count },
    max: -> { Setting.dnskeys_max_count },
  }

  validates :admin_domain_contacts, object_count: {
    min: -> { Setting.admin_contacts_min_count },
    max: -> { Setting.admin_contacts_max_count },
  }

  validates :tech_domain_contacts, object_count: {
    min: -> { Setting.tech_contacts_min_count },
    max: -> { Setting.tech_contacts_max_count },
  }

  validates :nameservers, uniqueness_multi: {
    attribute: 'hostname',
  }

  validates :dnskeys, uniqueness_multi: {
    attribute: 'public_key',
  }

  validate :validate_nameserver_ips

  validate :statuses_uniqueness

  def security_level_resolver
    resolver = Dnsruby::Resolver.new(nameserver: Dnskey::RESOLVERS)
    resolver.do_validation = true
    resolver.do_caching = false
    resolver.dnssec = true
    resolver
  end

  def dnssec_security_level(stubber: nil)
    Dnsruby::Dnssec.reset
    resolver = security_level_resolver
    Dnsruby::Recursor.clear_caches(resolver)
    if Rails.env.staging?
      clear_dnssec_trusted_anchors_and_keys
    elsif stubber
      Dnsruby::Dnssec.add_trust_anchor(stubber.ds_rr)
    end
    recursor = Dnsruby::Recursor.new(resolver)
    recursor.dnssec = true
    recursor.query(name, 'A', 'IN').security_level
  end

  def clear_dnssec_trusted_anchors_and_keys
    Dnsruby::Dnssec.clear_trust_anchors
    Dnsruby::Dnssec.clear_trusted_keys
    Dnsruby::Dnssec.add_trust_anchor(Dnsruby::RR.create(ENV['trusted_dnskey']))
  end

  def statuses_uniqueness
    return if statuses.uniq == statuses

    errors.add(:statuses, :taken)
  end

  self.ignored_columns = %w[legacy_id legacy_registrar_id legacy_registrant_id]

  def subordinate_nameservers
    nameservers.select { |x| x.hostname.end_with?(name) }
  end

  def delegated_nameservers
    nameservers.reject { |x| x.hostname.end_with?(name) }
  end

  def extension_update_prohibited?
    statuses.include? DomainStatus::SERVER_EXTENSION_UPDATE_PROHIBITED
  end

  def dnskey_update_enabled?
    statuses.include? DomainStatus::SERVER_OBJ_UPDATE_PROHIBITED
  end

  def admin_change_prohibited?
    statuses.include? DomainStatus::SERVER_ADMIN_CHANGE_PROHIBITED
  end

  def tech_change_prohibited?
    statuses.include? DomainStatus::SERVER_TECH_CHANGE_PROHIBITED
  end

  class << self
    def ransackable_associations(*)
      authorizable_ransackable_associations
    end

    def ransackable_attributes(*)
      authorizable_ransackable_attributes
    end

    def nameserver_required?
      Setting.nameserver_required
    end

    def registrant_user_admin_registrant_domains(registrant_user)
      companies = Contact.registrant_user_company_contacts(registrant_user)
      from(
        "(#{registrant_user_administered_domains(registrant_user).to_sql} UNION " \
        "#{registrant_user_company_registrant(companies).to_sql} UNION " \
        "#{registrant_user_domains_company(companies, except_tech: true).to_sql}) AS domains"
      )
    end

    def registrant_user_direct_admin_registrant_domains(registrant_user)
      from(
        "(#{registrant_user_direct_domains_by_registrant(registrant_user).to_sql} UNION " \
        "#{registrant_user_direct_domains_by_contact(registrant_user,
                                                     except_tech: true).to_sql}) AS domains"
      )
    end

    def registrant_user_domains(registrant_user)
      from(
        "(#{registrant_user_domains_by_registrant(registrant_user).to_sql} UNION " \
        "#{registrant_user_indirect_domains(registrant_user).to_sql} UNION " \
        "#{registrant_user_domains_by_contact(registrant_user).to_sql}) AS domains"
      )
    end

    def registrant_user_direct_domains(registrant_user)
      from(
        "(#{registrant_user_direct_domains_by_registrant(registrant_user).to_sql} UNION " \
        "#{registrant_user_direct_domains_by_contact(registrant_user).to_sql}) AS domains"
      )
    end

    def registrant_user_administered_domains(registrant_user)
      from(
        "(#{registrant_user_domains_by_registrant(registrant_user).to_sql} UNION " \
        "#{registrant_user_domains_by_admin_contact(registrant_user).to_sql}) AS domains"
      )
    end

    def registrant_user_indirect_domains(registrant_user)
      companies = Contact.registrant_user_company_contacts(registrant_user)
      from(
        "(#{registrant_user_company_registrant(companies).to_sql} UNION "\
        "#{registrant_user_domains_company(companies).to_sql}) AS domains"
      )
    end

    private

    def registrant_user_domains_by_registrant(registrant_user)
      where(registrant: registrant_user.contacts)
    end

    def registrant_user_domains_by_contact(registrant_user)
      joins(:domain_contacts).where(domain_contacts: { contact_id: registrant_user.contacts })
    end

    def registrant_user_domains_by_admin_contact(registrant_user)
      joins(:domain_contacts).where(domain_contacts: { contact_id: registrant_user.contacts,
                                                       type: [AdminDomainContact.name] })
    end

    def registrant_user_direct_domains_by_registrant(registrant_user)
      where(registrant: registrant_user.direct_contacts)
    end

    def registrant_user_direct_domains_by_contact(registrant_user, except_tech: false)
      request = { contact_id: registrant_user.direct_contacts }
      request[:type] = [AdminDomainContact.name] if except_tech
      joins(:domain_contacts).where(domain_contacts: request)
    end

    def registrant_user_company_registrant(companies)
      where(registrant: companies)
    end

    def registrant_user_domains_company(companies, except_tech: false)
      request = { contact: companies }
      request[:type] = [AdminDomainContact.name] if except_tech
      joins(:domain_contacts).where(domain_contacts: request)
    end
  end

  def name=(value)
    value&.strip!
    value&.downcase!
    self[:name] = SimpleIDN.to_unicode(value)
    self[:name_puny] = SimpleIDN.to_ascii(value)
    self[:name_dirty] = value
  end

  # find by internationalized domain name
  # internet domain name => ascii or puny, but db::domains.name is unicode
  def self.find_by_idn(name)
    domain = find_by(name: name)
    if domain.blank? && name.include?('-')
      unicode = SimpleIDN.to_unicode name # we have no index on domains.name_puny
      domain = find_by(name: unicode)
    end
    domain
  end

  def puny_label
    name_puny.to_s.split('.').first
  end

  def registrant_typeahead
    @registrant_typeahead || registrant.try(:name) || nil
  end

  def in_reserved_list?
    @in_reserved_list ||= ReservedDomain.by_domain(name).any?
  end

  def pending_transfer
    transfers.find_by(status: DomainTransfer::PENDING)
  end

  def server_holdable?
    return false if statuses.include?(DomainStatus::SERVER_HOLD)
    return false if statuses.include?(DomainStatus::SERVER_MANUAL_INZONE)

    true
  end

  def renewable?
    return false unless renew_blocking_statuses.empty?
    return true unless Setting.days_to_renew_domain_before_expire != 0

    # if you can renew domain at days_to_renew before domain expiration
    return false if (expire_time.to_date - Time.zone.today) + 1 > Setting.days_to_renew_domain_before_expire

    true
  end

  def renew_blocking_statuses
    disallowed = [DomainStatus::DELETE_CANDIDATE, DomainStatus::PENDING_RENEW,
                  DomainStatus::PENDING_TRANSFER, DomainStatus::CLIENT_RENEW_PROHIBITED,
                  DomainStatus::PENDING_UPDATE, DomainStatus::SERVER_RENEW_PROHIBITED,
                  DomainStatus::PENDING_DELETE_CONFIRMATION]

    (statuses & disallowed)
  end

  def notify_registrar(message_key)
    # TODO: To be deleted with DomainDeleteConfirm refactoring
    registrar.notifications.create!(
      text: "#{I18n.t(message_key)}: #{name}",
      attached_obj_id: id,
      attached_obj_type: self.class.to_s
    )
  end

  def preclean_pendings
    # TODO: To be deleted with refactoring
    self.registrant_verification_token = nil
    self.registrant_verification_asked_at = nil
  end

  def clean_pendings!
    # TODO: To be deleted with refactoring
    preclean_pendings
    self.pending_json = {}
    statuses.delete(DomainStatus::PENDING_DELETE_CONFIRMATION)
    statuses.delete(DomainStatus::PENDING_UPDATE)
    statuses.delete(DomainStatus::PENDING_DELETE)
    status_notes[DomainStatus::PENDING_UPDATE] = ''
    status_notes[DomainStatus::PENDING_DELETE] = ''
    save
  end

  def pending_update!
    return true if pending_update?

    self.epp_pending_update = true # for epp

    return true unless registrant_verification_asked?

    pending_json_cache = pending_json
    token = registrant_verification_token
    asked_at = registrant_verification_asked_at
    new_registrant_id    = registrant.id
    new_registrant_email = registrant.email
    new_registrant_name  = registrant.name

    send_time = Time.zone.now + 1.minute
    RegistrantChangeConfirmEmailJob.set(wait_until: send_time).perform_later(id, new_registrant_id)
    RegistrantChangeNoticeEmailJob.set(wait_until: send_time).perform_later(id, new_registrant_id)

    reload

    self.pending_json = pending_json_cache
    self.registrant_verification_token = token
    self.registrant_verification_asked_at = asked_at
    set_pending_update
    touch_always_version
    pending_json['new_registrant_id']    = new_registrant_id
    pending_json['new_registrant_email'] = new_registrant_email
    pending_json['new_registrant_name']  = new_registrant_name

    # This pending_update! method is triggered by before_update
    # Note, all before_save callbacks are executed before before_update,
    # thus automatic statuses has already executed by this point
    # and we need to trigger automatic statuses manually (second time).
    manage_automatic_statuses
  end

  def registrant_update_confirmable?(token)
    return false if statuses.include? DomainStatus::DELETE_CANDIDATE
    return false unless pending_update?
    return false unless registrant_verification_asked?
    return false unless registrant_verification_token == token

    true
  end

  def registrant_delete_confirmable?(token)
    return false unless pending_delete?
    return false unless registrant_verification_asked?
    return false unless registrant_verification_token == token

    true
  end

  def registrant_verification_asked?
    registrant_verification_asked_at.present? && registrant_verification_token.present?
  end

  def registrant_verification_asked!(frame_str, current_user_id)
    pending_json['frame'] = frame_str
    pending_json['current_user_id'] = current_user_id
    self.registrant_verification_asked_at = Time.zone.now
    self.registrant_verification_token = SecureRandom.hex(42)
  end

  def pending_delete!
    return true if pending_delete?

    self.epp_pending_delete = true # for epp

    # TODO: if this were to ever return true, that would be wrong. EPP would report sucess pending
    return true unless registrant_verification_asked?

    pending_delete_confirmation!
    save(validate: false) # should check if this did succeed

    Domains::DeleteConfirmEmail::SendRequest.run(domain: self)
  end

  def cancel_pending_delete
    statuses.delete DomainStatus::PENDING_DELETE_CONFIRMATION
    statuses.delete DomainStatus::PENDING_DELETE
    self.delete_date = nil
  end

  def pricelist(operation_category, period_i = nil, unit = nil)
    period_i ||= period
    unit ||= period_unit

    zone_name = name.split('.').drop(1).join('.')
    zone = DNS::Zone.find_by(origin: zone_name)

    duration = "P#{period_i}#{unit.upcase}"

    Billing::Price.price_for(zone, operation_category, duration)
  end

  ### VALIDATIONS ###

  def validate_nameserver_ips
    nameservers.to_a.reject(&:marked_for_destruction?).each do |ns|
      next unless ns.hostname.end_with?(".#{name}")
      next if ns.ipv4.present? || ns.ipv6.present?

      errors.add(:nameservers, :invalid) if errors[:nameservers].blank?
      ns.errors.add(:ipv4, :blank)
    end
  end

  # used for highlighting form tabs
  def parent_valid?
    assoc_errors = errors.keys.select { |x| x.match(/\./) }
    (errors.keys - assoc_errors).empty?
  end

  ## SHARED

  def name_in_wire_format
    res = ''
    parts = name_puny.split('.')
    parts.each do |x|
      res += format('%02X', x.length) # length of label in hex
      res += x.each_byte.map { |b| format('%02X', b) }.join # label
    end

    res += '00'

    res
  end

  def to_s
    name
  end

  def pending_registrant
    return '' if pending_json.blank?
    return '' if pending_json['new_registrant_id'].blank?

    Registrant.find_by(id: pending_json['new_registrant_id'])
  end

  def pending_update?
    statuses.include?(DomainStatus::PENDING_UPDATE)
  end

  # depricated not used, not valid
  def update_prohibited?
    (statuses & DomainStatus::UPDATE_PROHIBIT_STATES).present?
  end

  # public api
  def delete_prohibited?
    statuses.include?(DomainStatus::FORCE_DELETE)
  end

  def update_unless_locked_by_registrant(update)
    update(admin_store_statuses_history: update) unless locked_by_registrant?
  end

  def update_not_by_locked_statuses(update)
    return unless locked_by_registrant?

    result = update.reject { |status| RegistryLockable::LOCK_STATUSES.include? status }
    update(admin_store_statuses_history: result)
  end

  # special handling for admin changing status
  def admin_status_update(update)
    return unless update

    PaperTrail.request(enabled: false) do
      update_unless_locked_by_registrant(update)
      update_not_by_locked_statuses(update)
    end

    # check for deleted status
    statuses.each do |s|
      next if update.include? s

      case s
      when DomainStatus::PENDING_DELETE
        self.delete_date = nil
      when DomainStatus::SERVER_MANUAL_INZONE # removal causes server hold to set
        self.outzone_at = Time.zone.now if force_delete_scheduled?
      when DomainStatus::EXPIRED # removal causes server hold to set
        self.outzone_at = expire_time + 15.day
      when DomainStatus::SERVER_HOLD # removal causes server hold to set
        self.outzone_at = nil
      end
    end
  end

  def pending_update_prohibited?
    (statuses_was & DomainStatus::UPDATE_PROHIBIT_STATES).present?
  end

  def set_pending_update
    if pending_update_prohibited?
      logger.info "DOMAIN STATUS UPDATE ISSUE ##{id}: PENDING_UPDATE not allowed to set. [#{statuses}]"
      return
    end
    statuses << DomainStatus::PENDING_UPDATE
  end

  def pending_delete?
    (statuses & [DomainStatus::PENDING_DELETE_CONFIRMATION, DomainStatus::PENDING_DELETE]).any?
  end

  def pending_delete_confirmation?
    statuses.include? DomainStatus::PENDING_DELETE_CONFIRMATION
  end

  def pending_delete_confirmation!
    statuses << DomainStatus::PENDING_DELETE_CONFIRMATION unless pending_delete_prohibited?
  end

  def pending_delete_prohibited?
    (statuses_was & DomainStatus::DELETE_PROHIBIT_STATES).present?
  end

  # let's use positive method names
  def pending_deletable?
    !pending_delete_prohibited?
  end

  def set_pending_delete
    if pending_delete_prohibited?
      logger.info "DOMAIN STATUS UPDATE ISSUE ##{id}: PENDING_DELETE not allowed to set. [#{statuses}]"
      return
    end
    statuses << DomainStatus::PENDING_DELETE
  end

  def set_server_hold
    statuses << DomainStatus::SERVER_HOLD
    self.outzone_at = Time.current
  end

  def manage_automatic_statuses
    unless self.class.nameserver_required?
      deactivate if nameservers.reject(&:marked_for_destruction?).empty?
      activate if nameservers.reject(&:marked_for_destruction?).size >= Setting.ns_min_count
    end

    cancel_force_delete if force_delete_scheduled? && will_save_change_to_registrant_id?

    if statuses.empty? && valid?
      statuses << DomainStatus::OK
    elsif (statuses.length > 1) || !valid?
      statuses.delete(DomainStatus::OK)
    end

    p_d = statuses.include?(DomainStatus::PENDING_DELETE)
    s_h = (statuses & [DomainStatus::SERVER_MANUAL_INZONE, DomainStatus::SERVER_HOLD]).empty?
    statuses << DomainStatus::SERVER_HOLD if p_d && s_h
  end

  def children_log
    log = HashWithIndifferentAccess.new
    log[:admin_contacts] = admin_contact_ids
    log[:tech_contacts] = tech_contact_ids
    log[:nameservers] = nameserver_ids
    log[:dnskeys] = dnskey_ids
    log[:legal_documents] = [legal_document_id]
    log[:registrant] = [registrant_id]
    log
  end

  def update_whois_record
    UpdateWhoisRecordJob.set(wait: 1.minute).perform_later name, 'domain'
  end

  def status_notes_array=(notes)
    self.status_notes = {}
    notes ||= []
    statuses.each_with_index do |status, i|
      status_notes[status] = notes[i]
    end
  end

  def primary_contact_emails
    (admin_contacts.emails + [registrant.email]).uniq
  end

  def expired_domain_contact_emails
    (primary_contact_emails +
    ["info@#{name}", "#{prepared_domain_name}@#{name}"]).uniq
  end

  def all_related_emails
    (admin_contacts.emails +  tech_contacts.emails + [registrant.email]).uniq
  end

  def force_delete_contact_emails
    (primary_contact_emails + tech_contacts.pluck(:email) +
      ["info@#{name}", "#{prepared_domain_name}@#{name}"]).uniq
  end

  def prepared_domain_name
    name.split('.')&.first
  end

  def new_registrant_email
    pending_json['new_registrant_email']
  end

  def new_registrant_id
    pending_json['new_registrant_id']
  end

  def as_json(_options)
    hash = super
    hash['auth_info'] = hash.delete('transfer_code') # API v1 requirement
    hash['valid_from'] = hash['created_at'] # API v1 requirement
    hash
  end

  def domain_name
    DNS::DomainName.new(name)
  end

  def contact_emails_verification_failed
    contacts.select(&:email_verification_failed?)&.map(&:email)&.uniq
  end

  def as_csv_row
    [
      name,
      registrant_info[0],
      registrant_info[1],
      registrant_info[2],
      registrant_info[3],
      valid_to.to_formatted_s(:db),
      registrar,
      created_at.to_formatted_s(:db),
      statuses,
      admin_contacts.map { |c| "#{c.name}, #{c.code}, #{ApplicationController.helpers.ident_for(c)}" },
      tech_contacts.map { |c| "#{c.name}, #{c.code}, #{ApplicationController.helpers.ident_for(c)}" },
      nameservers.pluck(:hostname),
      force_delete_date,
      force_delete_data,
    ]
  end

  def as_pdf
    domain_html = ApplicationController.render(template: 'domain/pdf', assigns: { domain: self })
    generator = PDFKit.new(domain_html, { enable_local_file_access: true })
    generator.to_pdf
  end

  def registrant_info
    if registrant
      return [registrant.name, registrant.ident, registrant.ident_country_code,
              registrant.ident_type]
    end

    ver = Version::ContactVersion.where(item_id: registrant_id).last
    contact = Contact.all_versions_for([registrant_id], created_at).first

    contact = ObjectVersionsParser.new(ver).parse if contact.nil? && ver

    [contact.try(:name), contact.try(:ident), contact.try(:ident_country_code),
     contact.try(:ident_type)] || ['Deleted']
  end

  def registrant_ident_info
    return ApplicationController.helpers.ident_for(registrant) if registrant
  end

  def self.csv_header
    [
      'Domain', 'Registrant name', 'Registrant ident', 'Registrant ident country code',
      'Registrant ident type', 'Valid to', 'Registrar', 'Created at',
      'Statuses', 'Admin. contacts', 'Tech. contacts', 'Nameservers', 'Force delete date',
      'Force delete data'
    ]
  end

  def self.pdf(html)
    kit = PDFKit.new(html)
    kit.to_pdf
  end

  def self.expire_warning_period
    Setting.expire_warning_period.days
  end

  def self.redemption_grace_period
    Setting.redemption_grace_period.days
  end

  def self.outzone_candidates
    where("#{attribute_alias(:outzone_time)} < ?", Time.zone.now)
  end

  def self.uses_zone?(zone)
    exists?(['name ILIKE ?', "%.#{zone.origin}"])
  end

  def self.swap_elements(array, indexes)
    indexes.each do |index|
      array[index[0]], array[index[1]] = array[index[1]], array[index[0]]
    end
    array
  end
end