class Inkcite::Renderer::Link

Constants

BLANK

Value to open links in a new window.

MAILTO

Property controlling where missing links are pointed.

PLUS_PLUS

Signifies an auto-incrementing link ID.

The configuration name of the field that holds the query parameter that will be tacked onto the end of all links.

The configuration name of the field that holds the domain name(s) for links that will be tagged.

The property name used to indicate that links in this email should be replaced with [trackable URLs].

Public Class Methods

process(id, href, force, ctx, add_tag=true) click to toggle source
# File lib/inkcite/renderer/link.rb, line 108
def self.process id, href, force, ctx, add_tag=true

  # Initially assume a blank target isn't needed
  target_blank = false

  # If a URL wasn't provided in the HTML, then check to see if there is
  # a link declared in the project's links_tsv file.  If so, we need to
  # duplicate it so that tagging doesn't get applied multiple times.
  if href.blank?
    links_tsv_href = ctx.links_tsv[id]
    href = links_tsv_href unless links_tsv_href.blank?
  end

  # Always duplicate the string provided just to make sure we're not
  # modifying a frozen link or adding tagging to a previously tagged HREF.
  href = href.dup if href

  # True if the href is missing.  If so, we may try to look it up by it's ID
  # or we'll insert a default TBD link.
  missing = href.blank?

  # True if it's a link deeper into the content.
  hash = !missing && href.starts_with?(POUND_SIGN)

  # True if this is a mailto link.
  mailto = !missing && !hash && href.starts_with?(MAILTO)

  # Only perform special processing on the link if it's TBD or not a link to
  # something in the page.
  unless hash || mailto

    if id.blank?

      # Generate a placeholder ID and warn the user about it.
      id = "link#{ctx.links.size + 1}"
      ctx.error 'Link missing ID', { :href => href }

    else

      # Check to see if we've encountered an auto-incrementing link ID (e.g. event++)
      # Replace the ++ with a unique count for this ID prefix.
      id = id.gsub(PLUS_PLUS, ctx.unique_id(id).to_s) if id.end_with?(PLUS_PLUS)

    end

    # Get the HREF that we have previously encountered for this ID.  When not blank
    # we'll sanity check that the URL is the same.
    last_href = ctx.links[id]

    if missing

      # If we don't have a URL, check to see if we've encountered this
      href = last_href || ctx[MISSING_LINK_HREF]

      ctx.error 'Link missing href', { :id => id } unless last_href

    else

      # Ensure the validity of the URL in the link to prevent problems -
      # e.g. unexpected carriage return in the href.
      ctx.error('Link href appears to be invalid', { :id => id, :href => href }) unless force || valid?(href)

      # Optionally tag the link's query string for post-send log analytics.
      href = add_tagging(id, href, ctx) if add_tag

      if last_href.blank?

        # Associate the href with it's ID in case we bump into this link again.
        ctx.links[id] = href

      elsif last_href != href

        # It saves everyone a lot of time if you alert them that an ID appears multiple times
        # in the email and with mismatched URLs.
        ctx.error 'Link href mismatch', { :id => id, :expected => last_href, :found => href }

      end

    end

    # Optionally replace the href with an ESP trackable url.  Gotta do this after
    # the link has been stored in the context because we don't want trackable
    # URLs interfering with the links file.
    href = add_tracking(id, href, ctx)

    target_blank = true

  end

  [id, href, target_blank]
end

Private Class Methods

add_tagging(id, href, ctx) click to toggle source
# File lib/inkcite/renderer/link.rb, line 225
def self.add_tagging id, href, ctx

  # Check to see if we're tagging links.
  tag_map = ctx[TAG_LINKS]
  unless tag_map.blank?

    # This is the tag to be applied.
    tag = nil

    # Support for a map of tags where the matching domain is
    # the key and the value is the tagging.
    if tag_map.is_a?(Hash)
      longest_domain = ''

      tag_map.each do |domain, _tag|
        if href =~ /^https?:\/\/[^\/]*#{domain}/
          if domain.length > longest_domain.length
            longest_domain = domain
            tag = _tag
          end
        end
      end

    else

      # Blank tag domain means tag all the links - otherwise, make sure the
      # href matches the desired domain name.
      tag_domain = ctx[TAG_LINKS_DOMAIN]
      tag = tag_map if tag_domain.blank? || href =~ /^https?:\/\/[^\/]*#{tag_domain}/

    end

    # If a tag was identified then apply it to the href.
    href = Util::add_query_param(href, replace_tag(tag, id, ctx)) unless tag.blank?

  end

  href
end
add_tracking(id, href, ctx) click to toggle source
# File lib/inkcite/renderer/link.rb, line 265
def self.add_tracking id, href, ctx

  # Check to see if a trackable link string has been defined.
  tracking = ctx[Inkcite::Email::TRACK_LINKS]

  # Replace the fully-qualified URL with a tracking tag - presuming that the
  # ESP will replace this href with it's own trackable URL at deployment.
  href = URI.encode(replace_tag(tracking, id, ctx)) unless tracking.blank?

  href
end
replace_tag(tag, id, ctx) click to toggle source
# File lib/inkcite/renderer/link.rb, line 277
def self.replace_tag tag, id, ctx

  # Inject the link's ID into the tag - that's the only value that can't
  # be resolved from the context.
  tag = tag.gsub('{id}', id)

  Inkcite::Renderer.render(tag, ctx)
end
valid?(url) click to toggle source

Tests whether or not the href provided is a valid http(s) link. Courtest stackoverflow.com/questions/7167895/whats-a-good-way-to-validate-links-urls-in-rails

# File lib/inkcite/renderer/link.rb, line 288
def self.valid? url
  begin
    uri = URI.parse(url)
    uri.kind_of?(URI::HTTP)
  rescue URI::InvalidURIError
    false
  end
end

Public Instance Methods

render(tag, opt, ctx) click to toggle source
# File lib/inkcite/renderer/link.rb, line 5
def render tag, opt, ctx

  tag_stack = ctx.tag_stack(:a)

  if tag == '/a'

    # Grab the attributes of the opening tag.
    opening = tag_stack.pop

    # Nothing to do in the
    return '' if ctx.text?

    html = '</a>'

    # Check to see if the declaration has been marked as a block
    # element and if so, close the div.
    html << '</div>' if opening[:div]

    return html
  end

  # Push the link's options onto the tag stack so that we can have
  # access to its attributes when we close it.
  tag_stack << opt

  # Get the currently open table cell and see if link color is
  # overridden in there.
  td_parent = ctx.tag_stack(:td).opts
  table_parent = ctx.tag_stack(:table).opts

  # Choose a color from the parameters or inherit from the parent td, table or context.
  opt[:color] = detect(opt[:color], td_parent[:link], table_parent[:link], ctx[LINK_COLOR])

  a = Element.new('a')

  # Mixes the attributes common to all container elements
  # including font, background color, border, etc.
  mix_all a, opt, ctx

  # If no-tag is specified, override the default tagging
  # behavior.  Useful when the tagging interferes with
  # behavior on dynamic pages.
  add_tag = opt[:'no-tag'] != true

  id, href, target_blank = Link.process(opt[:id], opt[:href], opt[:force], ctx, add_tag)

  a[:target] = BLANK if target_blank

  # Make sure that these types of links have quotes.
  href = quote(href) unless ctx.text?

  # Set the href attribute to the resolved href.
  a[:href] = href

  # Links never get any text decoration.
  a.style[TEXT_DECORATION] = NONE

  # Force the display: block attribute if the boolean block parameter has
  # been specified.
  a.style[:display] = :block if opt[:block]

  if ctx.browser?

    # Programmatically we can install onclick listeners for hosted versions.
    # Check to see if one is specified and the Javascript is permitted in
    # this version.
    onclick = opt[:onclick]
    a[:onclick] = quote(onclick) unless onclick.blank?

  end

  html = ''

  if ctx.text?
    html << a[:href]

  else

    klass = opt[:class]
    a.classes << klass unless klass.blank?

    mix_responsive a, opt, ctx

    # Some responsive modes (e.g. button) change the display type from in-line
    # to block.  This change can cause unexpected whitespace or other unexpected
    # layout changes.  Outlook doesn't support block display on link elements
    # so the best workaround is simply to wrap the element in <div> tags.
    if a.responsive_styles.any?(&:block?)
      html << '<div>'

      # Remember that we made this element block-display so that we can append
      # the extra div when we close the tag.
      opt[:div] = true

    end

    html << a.to_s

  end

  html
end