summaryrefslogtreecommitdiff
path: root/lib/banzai/filter
diff options
context:
space:
mode:
Diffstat (limited to 'lib/banzai/filter')
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb28
-rw-r--r--lib/banzai/filter/commit_range_reference_filter.rb2
-rw-r--r--lib/banzai/filter/commit_trailers_filter.rb152
-rw-r--r--lib/banzai/filter/emoji_filter.rb2
-rw-r--r--lib/banzai/filter/gollum_tags_filter.rb2
-rw-r--r--lib/banzai/filter/inline_diff_filter.rb2
-rw-r--r--lib/banzai/filter/label_reference_filter.rb49
-rw-r--r--lib/banzai/filter/merge_request_reference_filter.rb39
-rw-r--r--lib/banzai/filter/milestone_reference_filter.rb2
9 files changed, 252 insertions, 26 deletions
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index c9e3f8ce42b..a848154b2d4 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -171,7 +171,7 @@ module Banzai
end
if object
- title = object_link_title(object)
+ title = object_link_title(object, matches)
klass = reference_class(object_sym)
data = data_attributes_for(link_content || match, parent, object,
@@ -196,13 +196,15 @@ module Banzai
end
end
- def data_attributes_for(text, project, object, link_content: false, link_reference: false)
+ def data_attributes_for(text, parent, object, link_content: false, link_reference: false)
+ object_parent_type = parent.is_a?(Group) ? :group : :project
+
data_attribute(
- original: text,
- link: link_content,
- link_reference: link_reference,
- project: project.id,
- object_sym => object.id
+ original: text,
+ link: link_content,
+ link_reference: link_reference,
+ object_parent_type => parent.id,
+ object_sym => object.id
)
end
@@ -213,10 +215,14 @@ module Banzai
extras << "comment #{$1}"
end
+ extension = matches[:extension] if matches.names.include?("extension")
+
+ extras << extension if extension
+
extras
end
- def object_link_title(object)
+ def object_link_title(object, matches)
object.title
end
@@ -337,6 +343,12 @@ module Banzai
def parent
parent_type == :project ? project : group
end
+
+ def full_group_path(group_ref)
+ return current_parent_path unless group_ref
+
+ group_ref
+ end
end
end
end
diff --git a/lib/banzai/filter/commit_range_reference_filter.rb b/lib/banzai/filter/commit_range_reference_filter.rb
index 21bcb1c5ca8..99fa2d9d8fb 100644
--- a/lib/banzai/filter/commit_range_reference_filter.rb
+++ b/lib/banzai/filter/commit_range_reference_filter.rb
@@ -34,7 +34,7 @@ module Banzai
range.to_param.merge(only_path: context[:only_path]))
end
- def object_link_title(range)
+ def object_link_title(range, matches)
nil
end
end
diff --git a/lib/banzai/filter/commit_trailers_filter.rb b/lib/banzai/filter/commit_trailers_filter.rb
new file mode 100644
index 00000000000..ef16df1f3ae
--- /dev/null
+++ b/lib/banzai/filter/commit_trailers_filter.rb
@@ -0,0 +1,152 @@
+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
diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb
index b82c6ca6393..e1261e7bbbe 100644
--- a/lib/banzai/filter/emoji_filter.rb
+++ b/lib/banzai/filter/emoji_filter.rb
@@ -11,7 +11,7 @@ module Banzai
IGNORED_ANCESTOR_TAGS = %w(pre code tt).to_set
def call
- search_text_nodes(doc).each do |node|
+ doc.search(".//text()").each do |node|
content = node.to_html
next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS)
diff --git a/lib/banzai/filter/gollum_tags_filter.rb b/lib/banzai/filter/gollum_tags_filter.rb
index c2b42673376..f2e9a5a1116 100644
--- a/lib/banzai/filter/gollum_tags_filter.rb
+++ b/lib/banzai/filter/gollum_tags_filter.rb
@@ -57,7 +57,7 @@ module Banzai
ALLOWED_IMAGE_EXTENSIONS = /.+(jpg|png|gif|svg|bmp)\z/i.freeze
def call
- search_text_nodes(doc).each do |node|
+ doc.search(".//text()").each do |node|
# A Gollum ToC tag is `[[_TOC_]]`, but due to MarkdownFilter running
# before this one, it will be converted into `[[<em>TOC</em>]]`, so it
# needs special-case handling
diff --git a/lib/banzai/filter/inline_diff_filter.rb b/lib/banzai/filter/inline_diff_filter.rb
index beb21b19ab3..73e82a4d7e3 100644
--- a/lib/banzai/filter/inline_diff_filter.rb
+++ b/lib/banzai/filter/inline_diff_filter.rb
@@ -4,7 +4,7 @@ module Banzai
IGNORED_ANCESTOR_TAGS = %w(pre code tt).to_set
def call
- search_text_nodes(doc).each do |node|
+ doc.search(".//text()").each do |node|
next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS)
content = node.to_html
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index d5360ad8f68..1cbada818fb 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -32,16 +32,25 @@ module Banzai
end
end
- def find_label(project_ref, label_id, label_name)
- project = parent_from_ref(project_ref)
- return unless project
+ def find_label(parent_ref, label_id, label_name)
+ parent = parent_from_ref(parent_ref)
+ return unless parent
label_params = label_params(label_id, label_name)
- find_labels(project).find_by(label_params)
+ find_labels(parent).find_by(label_params)
end
- def find_labels(project)
- LabelsFinder.new(nil, project_id: project.id).execute(skip_authorization: true)
+ def find_labels(parent)
+ params = if parent.is_a?(Group)
+ { group_id: parent.id,
+ include_ancestor_groups: true,
+ only_group_labels: true }
+ else
+ { project_id: parent.id,
+ include_ancestor_groups: true }
+ end
+
+ LabelsFinder.new(nil, params).execute(skip_authorization: true)
end
# Parameters to pass to `Label.find_by` based on the given arguments
@@ -59,25 +68,39 @@ module Banzai
end
end
- def url_for_object(label, project)
+ def url_for_object(label, parent)
h = Gitlab::Routing.url_helpers
- h.project_issues_url(project, label_name: label.name, only_path: context[:only_path])
+
+ if parent.is_a?(Project)
+ h.project_issues_url(parent, label_name: label.name, only_path: context[:only_path])
+ elsif context[:label_url_method]
+ h.public_send(context[:label_url_method], parent, label_name: label.name, only_path: context[:only_path]) # rubocop:disable GitlabSecurity/PublicSend
+ end
end
def object_link_text(object, matches)
- project_path = full_project_path(matches[:namespace], matches[:project])
- project_from_ref = from_ref_cached(project_path)
- reference = project_from_ref.to_human_reference(project)
- label_suffix = " <i>in #{reference}</i>" if reference.present?
+ label_suffix = ''
+
+ if project || full_path_ref?(matches)
+ project_path = full_project_path(matches[:namespace], matches[:project])
+ parent_from_ref = from_ref_cached(project_path)
+ reference = parent_from_ref.to_human_reference(project || group)
+
+ label_suffix = " <i>in #{reference}</i>" if reference.present?
+ end
LabelsHelper.render_colored_label(object, label_suffix)
end
+ def full_path_ref?(matches)
+ matches[:namespace] && matches[:project]
+ end
+
def unescape_html_entities(text)
CGI.unescapeHTML(text.to_s)
end
- def object_link_title(object)
+ def object_link_title(object, matches)
# use title of wrapped element instead
nil
end
diff --git a/lib/banzai/filter/merge_request_reference_filter.rb b/lib/banzai/filter/merge_request_reference_filter.rb
index b3cfa97d0e0..5cbdb01c130 100644
--- a/lib/banzai/filter/merge_request_reference_filter.rb
+++ b/lib/banzai/filter/merge_request_reference_filter.rb
@@ -17,10 +17,19 @@ module Banzai
only_path: context[:only_path])
end
+ def object_link_title(object, matches)
+ object_link_commit_title(object, matches) || super
+ end
+
def object_link_text_extras(object, matches)
extras = super
+ if commit_ref = object_link_commit_ref(object, matches)
+ return extras.unshift(commit_ref)
+ end
+
path = matches[:path] if matches.names.include?("path")
+
case path
when '/diffs'
extras.unshift "diffs"
@@ -38,6 +47,36 @@ module Banzai
.where(iid: ids.to_a)
.includes(target_project: :namespace)
end
+
+ private
+
+ def object_link_commit_title(object, matches)
+ object_link_commit(object, matches)&.title
+ end
+
+ def object_link_commit_ref(object, matches)
+ object_link_commit(object, matches)&.short_id
+ end
+
+ def object_link_commit(object, matches)
+ return unless matches.names.include?('query') && query = matches[:query]
+
+ # Removes leading "?". CGI.parse expects "arg1&arg2&arg3"
+ params = CGI.parse(query.sub(/^\?/, ''))
+
+ return unless commit_sha = params['commit_id']&.first
+
+ if commit = find_commit_by_sha(object, commit_sha)
+ Commit.from_hash(commit.to_hash, object.project)
+ end
+ end
+
+ def find_commit_by_sha(object, commit_sha)
+ @all_commits ||= {}
+ @all_commits[object.id] ||= object.all_commits
+
+ @all_commits[object.id].find { |commit| commit.sha == commit_sha }
+ end
end
end
end
diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb
index 8ec696ce5fc..1a1d7dbeb3d 100644
--- a/lib/banzai/filter/milestone_reference_filter.rb
+++ b/lib/banzai/filter/milestone_reference_filter.rb
@@ -84,7 +84,7 @@ module Banzai
end
end
- def object_link_title(object)
+ def object_link_title(object, matches)
nil
end
end