summaryrefslogtreecommitdiff
path: root/lib/banzai/filter/user_reference_filter.rb
blob: eea3af842b6e10b5be98112a543ccdf638c72c9e (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
module Banzai
  module Filter
    # HTML filter that replaces user or group references with links.
    #
    # A special `@all` reference is also supported.
    class UserReferenceFilter < ReferenceFilter
      # 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 self.referenced_by(node)
        if node.has_attribute?('data-group')
          group = Group.find(node.attr('data-group')) rescue nil
          return unless group

          { user: group.users }
        elsif node.has_attribute?('data-user')
          { user: LazyReference.new(User, node.attr('data-user')) }
        elsif node.has_attribute?('data-project')
          project = Project.find(node.attr('data-project')) rescue nil
          return unless project

          { user: project.team.members.flatten }
        end
      end

      def self.user_can_see_reference?(user, node, context)
        if node.has_attribute?('data-group')
          group = Group.find(node.attr('data-group')) rescue nil
          Ability.abilities.allowed?(user, :read_group, group)
        else
          super
        end
      end

      def self.user_can_reference?(user, node, context)
        # Only team members can reference `@all`
        if node.has_attribute?('data-project')
          project = Project.find(node.attr('data-project')) rescue nil
          return false unless project

          user && project.team.member?(user)
        else
          super
        end
      end

      def call
        return doc if project.nil?

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

        each_node 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, text|
              if link =~ ref_pattern_start
                replace_link_node_with_href(node, link) do
                  user_link_filter(link, link_text: text)
                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.
      #
      # 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_text: nil)
        self.class.references_in(text) do |match, username|
          if username == 'all'
            link_to_all(link_text: link_text)
          elsif namespace = Namespace.find_by(path: username)
            link_to_namespace(namespace, link_text: link_text) || match
          else
            match
          end
        end
      end

      private

      def urls
        Gitlab::Routing.url_helpers
      end

      def link_class
        reference_class(:project_member)
      end

      def link_to_all(link_text: nil)
        project = context[:project]
        url = urls.namespace_project_url(project.namespace, project,
                                         only_path: context[:only_path])
        data = data_attribute(project: project.id)
        text = link_text || User.reference_prefix + 'all'

        link_tag(url, data, text)
      end

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

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

        link_tag(url, data, text)
      end

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

        link_tag(url, data, text)
      end

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