gitlab-org--gitlab-foss/lib/banzai/filter/commit_trailers_filter.rb

153 lines
4.4 KiB
Ruby

module Banzai
module Filter
# HTML filter that replaces users' names and emails in commit trailers
# with links to their GitLab accounts or mailto links to their mentioned
# emails.
#
# Commit trailers are special labels in the form of `*-by:` and fall on a
# single line, ex:
#
# Reported-By: John S. Doe <john.doe@foo.bar>
#
# More info about this can be found here:
# * https://git.wiki.kernel.org/index.php/CommitMessageConventions
class CommitTrailersFilter < HTML::Pipeline::Filter
include ActionView::Helpers::TagHelper
include ApplicationHelper
include AvatarsHelper
TRAILER_REGEXP = /(?<label>[[:alpha:]-]+-by:)/i.freeze
AUTHOR_REGEXP = /(?<author_name>.+)/.freeze
# Devise.email_regexp wouldn't work here since its designed to match
# against strings that only contains email addresses; the \A and \z
# around the expression will only match if the string being matched
# contains just the email nothing else.
MAIL_REGEXP = /&lt;(?<author_email>[^@\s]+@[^@\s]+)&gt;/.freeze
FILTER_REGEXP = /(?<trailer>^\s*#{TRAILER_REGEXP}\s*#{AUTHOR_REGEXP}\s+#{MAIL_REGEXP}$)/mi.freeze
def call
doc.xpath('descendant-or-self::text()').each do |node|
content = node.to_html
next unless content.match(FILTER_REGEXP)
html = trailer_filter(content)
next if html == content
node.replace(html)
end
doc
end
private
# Replace trailer lines with links to GitLab users or mailto links to
# non GitLab users.
#
# text - String text to replace trailers in.
#
# Returns a String with all trailer lines replaced with links to GitLab
# users and mailto links to non GitLab users. All links have `data-trailer`
# and `data-user` attributes attached.
def trailer_filter(text)
text.gsub(FILTER_REGEXP) do |author_match|
label = $~[:label]
"#{label} #{parse_user($~[:author_name], $~[:author_email], label)}"
end
end
# Find a GitLab user using the supplied email and generate
# a valid link to them, otherwise, generate a mailto link.
#
# name - String name used in the commit message for the user
# email - String email used in the commit message for the user
# trailer - String trailer used in the commit message
#
# Returns a String with a link to the user.
def parse_user(name, email, trailer)
link_to_user User.find_by_any_email(email),
name: name,
email: email,
trailer: trailer
end
def urls
Gitlab::Routing.url_helpers
end
def link_to_user(user, name:, email:, trailer:)
wrapper = link_wrapper(data: {
trailer: trailer,
user: user.try(:id)
})
avatar = user_avatar_without_link(
user: user,
user_email: email,
css_class: 'avatar-inline',
has_tooltip: false
)
link_href = user.nil? ? "mailto:#{email}" : urls.user_url(user)
avatar_link = link_tag(
link_href,
content: avatar,
title: email
)
name_link = link_tag(
link_href,
content: name,
title: email
)
email_link = link_tag(
"mailto:#{email}",
content: email,
title: email
)
wrapper << "#{avatar_link}#{name_link} <#{email_link}>"
end
def link_wrapper(data: {})
data_attributes = data_attributes_from_hash(data)
doc.document.create_element(
'span',
data_attributes
)
end
def link_tag(url, title: "", content: "", data: {})
data_attributes = data_attributes_from_hash(data)
attributes = data_attributes.merge(
href: url,
title: title
)
link = doc.document.create_element('a', attributes)
if content.html_safe?
link << content
else
link.content = content # make sure we escape content using nokogiri's #content=
end
link
end
def data_attributes_from_hash(data = {})
data.reject! {|_, value| value.nil?}
data.map do |key, value|
[%(data-#{key.to_s.dasherize}), value]
end.to_h
end
end
end
end