mbleigh/subdomain-fu

View on GitHub
lib/subdomain_fu/subdomain_fu.rb

Summary

Maintainability
A
3 hrs
Test Coverage
module SubdomainFu
  class << self
    attr_accessor :config
    
    def config
      self.config = Configuration.new unless @config
      @config
    end
  end

  # The configurable options of Subdomain Fu. Use like so:
  # 
  #     SubdomainFu.configure do |config|
  #       config.tld_size = 2
  #       config.preferred_mirror = 'www'
  #     end
  # 
  # Available configurations are:
  #
  # <tt>tld_size</tt>: :: The size of the top-level domain. For example, 'localhost' is 0, 'example.com' is 1, and 'example.co.uk' is 2.
  # <tt>mirrors</tt>: :: An array of subdomains that should be equivalent to no subdomain. Defaults to <tt>['www']</tt>.
  # <tt>preferred_mirror</tt>: The preferred mirror subdomain to which to rewrite URLs. No subdomain is used by default.
  # <tt>override_only_path</tt>: :: If <tt>true</tt>, changing the subdomain will emit a full URL in url_for options, even if it wouldn't have otherwise.
  def self.configure
    self.config ||= Configuration.new
    yield(self.config)
  end
  
  class Configuration
    attr_accessor :tld_sizes, :mirrors, :preferred_mirror, :override_only_path
    
    @@defaults = {
      :tld_sizes => { :development => 0, :test => 0, :production => 1 }.freeze,
      :mirrors => %w(www).freeze,
      :preferred_mirror => nil,
      :override_only_path => false
    }.freeze
    
    def initialize
      @@defaults.each_pair do |k, v|
        self.send("#{k}=", (v.dup rescue v))
      end
    end
    
    def self.defaults
      @@defaults
    end
    
    def tld_size=(size)
      tld_sizes[Rails.env.to_sym] = size
    end
    
    def tld_size
      tld_sizes[Rails.env.to_sym]
    end
  end


  def self.has_domain?(host)
    !host.blank? && !(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
  end

  # Is the current subdomain either nil or not a mirror?
  def self.has_subdomain?(subdomain)
    subdomain != false && !subdomain.blank? && !SubdomainFu.config.mirrors.include?(subdomain)
  end

  def self.is_mirror?(subdomain)
    subdomain != false && !subdomain.blank? && SubdomainFu.config.mirrors.include?(subdomain)
  end

  # Is the subdomain a preferred mirror
  def self.preferred_mirror?(subdomain)
    subdomain == SubdomainFu.config.preferred_mirror || SubdomainFu.config.preferred_mirror.nil?
  end

  # Gets the subdomain from the host based on the TLD size
  def self.subdomain_from(host)
    return nil unless has_domain?(host)
    parts = host.split('.')
    sub = parts[0..-(SubdomainFu.config.tld_size+2)].join(".")
    sub.blank? ? nil : sub
  end

  # Gets only non-mirror subdomains from the host based on the TLD size
  def self.non_mirror_subdomain_from(host)
    sub = subdomain_from(host)
    has_subdomain?(sub) ? sub : nil
  end

  def self.host_without_subdomain(host)
    parts = host.split('.')
    Array(parts[-(config.tld_size+1)..-1]).join(".")
  end

  # Rewrites the subdomain of the host unless they are equivalent (i.e. mirrors of each other)
  def self.rewrite_host_for_subdomains(subdomain, host)
    if needs_rewrite?(subdomain, host)
      change_subdomain_of_host(subdomain || SubdomainFu.config.preferred_mirror, host)
    else
      if has_subdomain?(subdomain) || preferred_mirror?(subdomain_from(host)) ||
          (subdomain.nil? && has_subdomain?(subdomain_from(host)))
        host
      else
        change_subdomain_of_host(SubdomainFu.config.preferred_mirror, host)
      end
    end
  end

  # Changes the subdomain of the host to whatever is passed in.
  def self.change_subdomain_of_host(subdomain, host)
    host = SubdomainFu.host_without_subdomain(host)
    host = "#{subdomain}.#{host}" if subdomain
    host
  end

  # Is this subdomain equivalent to the subdomain found in this host string?
  def self.same_subdomain?(subdomain, host)
    subdomain = nil unless subdomain
    (subdomain == subdomain_from(host)) ||
      (!has_subdomain?(subdomain) && !has_subdomain?(subdomain_from(host)))
  end

  # Is the host without subdomain equivalent to the subdomain_host in this subdomain_host string?
  def self.same_host?(subdomain_host, host)
    SubdomainFu.host_without_subdomain(host) == subdomain_host
  end

  def self.override_only_path?
    config.override_only_path
  end

  def self.needs_rewrite?(subdomain, host)
    return false unless host && host.count('.') >= config.tld_size
    case subdomain
      when nil
        #rewrite when there is a preferred mirror set and there is no subdomain on the host
        return config.preferred_mirror && subdomain_from(host).nil?
      when false
        h = subdomain_from(host)
        #if the host has a subdomain
        if !h.nil?
          #rewrite when there is a subdomain in the host, and it is not a preferred mirror
          return true if !preferred_mirror?(h)
          #rewrite when there is a preferred mirror set and the subdomain of the host is not a mirror
          return true if config.preferred_mirror && !is_mirror?(h)
          #no rewrite if host already has mirror subdomain
          #it { SubdomainFu.needs_rewrite?(false,"www.localhost").should be_false }
          return false if is_mirror?(h)
        end
    end

    (!has_subdomain?(subdomain) && preferred_mirror?(subdomain) && !preferred_mirror?(subdomain_from(host))) ||
      !same_subdomain?(subdomain, host)
  end

  #returns nil or the subdomain(s)
  def self.current_subdomain(request)
    subdomain = (request.subdomains(SubdomainFu.config.tld_size) - config.mirrors).join(".")
    if has_subdomain?(subdomain)
      subdomain
    else
      nil
    end
  end

  #returns nil or the domain or ip
  #Enables subdomain-fu to more completely replace DHH's account_location plugin
  def self.current_domain(request)
    return request.domain unless has_domain?(request.domain)
    domain = ""
    domain << request.subdomains[1..-1].join(".") + "." if request.subdomains.length > 1
    domain << request.domain.to_s + request.port_string
  end

  module Controller
    def self.included(controller)
      controller.helper_method(:current_subdomain)
      controller.helper_method(:current_domain)
    end

    protected
    def current_subdomain
      SubdomainFu.current_subdomain(request)
    end
    def current_domain
      SubdomainFu.current_domain(request)
    end
  end
end