summaryrefslogtreecommitdiff
path: root/lib/banzai/filter/user_reference_filter.rb
blob: 11960047e5baedcb9ae53dd86892c56ca494115a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# frozen_string_literal: true

module Banzai
  module Filter
    # HTML filter that replaces user or group references with links.
    #
    # A special `@all` reference is also supported.
    class UserReferenceFilter < ReferenceFilter
      self.reference_type = :user

      # Public: Find `@user` user references in text
      #
      #   UserReferenceFilter.references_in(text) do |match, username|
      #     "<a href=...>@#{user}</a>"
      #   end
      #
      # text - String text to search.
      #
      # Yields the String match, and the String user name.
      #
      # Returns a String replaced with the return of the block.
      def self.references_in(text)
        text.gsub(User.reference_pattern) do |match|
          yield match, $~[:user]
        end
      end

      def call
        return doc if project.nil? && group.nil? && !skip_project_check?

        ref_pattern = User.reference_pattern
        ref_pattern_start = /\A#{ref_pattern}\z/

        nodes.each do |node|
          if text_node?(node)
            replace_text_when_pattern_matches(node, ref_pattern) do |content|
              user_link_filter(content)
            end
          elsif element_node?(node)
            yield_valid_link(node) do |link, inner_html|
              if link =~ ref_pattern_start
                replace_link_node_with_href(node, link) do
                  user_link_filter(link, link_content: inner_html)
                end
              end
            end
          end
        end

        doc
      end

      # Replace `@user` user references in text with links to the referenced
      # user's profile page.
      #
      # text - String text to replace references in.
      # link_content - Original content of the link being replaced.
      #
      # Returns a String with `@user` references replaced with links. All links
      # have `gfm` and `gfm-project_member` class names attached for styling.
      def user_link_filter(text, link_content: nil)
        self.class.references_in(text) do |match, username|
          if username == 'all' && !skip_project_check?
            link_to_all(link_content: link_content)
          else
            cached_call(:banzai_url_for_object, match, path: [User, username.downcase]) do
              if namespace = namespaces[username.downcase]
                link_to_namespace(namespace, link_content: link_content) || match
              else
                match
              end
            end
          end
        end
      end

      # Returns a Hash containing all Namespace objects for the username
      # references in the current document.
      #
      # The keys of this Hash are the namespace paths, the values the
      # corresponding Namespace objects.
      def namespaces
        @namespaces ||= Namespace.eager_load(:owner, :route)
                                 .where_full_path_in(usernames)
                                 .index_by(&:full_path)
                                 .transform_keys(&:downcase)
      end

      # Returns all usernames referenced in the current document.
      def usernames
        refs = Set.new

        nodes.each do |node|
          node.to_html.scan(User.reference_pattern) do
            refs << $~[:user]
          end
        end

        refs.to_a
      end

      private

      def urls
        Gitlab::Routing.url_helpers
      end

      def link_class
        reference_class(:project_member)
      end

      def link_to_all(link_content: nil)
        author = context[:author]

        if author && !team_member?(author)
          link_content
        else
          parent_url(link_content, author)
        end
      end

      def link_to_namespace(namespace, link_content: nil)
        if namespace.is_a?(Group)
          link_to_group(namespace.full_path, namespace, link_content: link_content)
        else
          link_to_user(namespace.path, namespace, link_content: link_content)
        end
      end

      def link_to_group(group, namespace, link_content: nil)
        url = urls.group_url(group, only_path: context[:only_path])
        data = data_attribute(group: namespace.id)
        content = link_content || Group.reference_prefix + group

        link_tag(url, data, content, namespace.full_name)
      end

      def link_to_user(user, namespace, link_content: nil)
        url = urls.user_url(user, only_path: context[:only_path])
        data = data_attribute(user: namespace.owner_id)
        content = link_content || User.reference_prefix + user

        link_tag(url, data, content, namespace.owner_name)
      end

      def link_tag(url, data, link_content, title)
        %(<a href="#{url}" #{data} class="#{link_class}" title="#{escape_once(title)}">#{link_content}</a>)
      end

      def parent
        context[:project] || context[:group]
      end

      def parent_group?
        parent.is_a?(Group)
      end

      def team_member?(user)
        if parent_group?
          parent.member?(user)
        else
          parent.team.member?(user)
        end
      end

      def parent_url(link_content, author)
        if parent_group?
          url = urls.group_url(parent, only_path: context[:only_path])
          data = data_attribute(group: group.id, author: author.try(:id))
        else
          url = urls.project_url(parent, only_path: context[:only_path])
          data = data_attribute(project: project.id, author: author.try(:id))
        end

        content = link_content || User.reference_prefix + 'all'
        link_tag(url, data, content, 'All Project and Group Members')
      end
    end
  end
end