Class: Banzai::Filter::References::AbstractReferenceFilter
- Inherits:
-
ReferenceFilter
- Object
- HTML::Pipeline::Filter
- ReferenceFilter
- Banzai::Filter::References::AbstractReferenceFilter
- Defined in:
- lib/banzai/filter/references/abstract_reference_filter.rb
Overview
Issues, merge requests, Snippets, Commits and Commit Ranges share similar functionality in reference filtering.
Direct Known Subclasses
CommitRangeReferenceFilter, CommitReferenceFilter, DesignReferenceFilter, IssuableReferenceFilter, LabelReferenceFilter, MilestoneReferenceFilter, SnippetReferenceFilter
Constant Summary collapse
- REFERENCE_PLACEHOLDER =
REFERENCE_PLACEHOLDER is used for re-escaping HTML text except found reference (which we replace with placeholder during re-scaping). The random number helps ensure it’s pretty close to unique. Since it’s a transitory value (it never gets saved) we can initialize once, and it doesn’t matter if it changes on a restart.
"_reference_#{SecureRandom.hex(16)}_"
- REFERENCE_PLACEHOLDER_PATTERN =
%r{#{REFERENCE_PLACEHOLDER}(\d+)}
Constants included from Concerns::TimeoutFilterHandler
Concerns::TimeoutFilterHandler::COMPLEX_MARKDOWN_MESSAGE, Concerns::TimeoutFilterHandler::RENDER_TIMEOUT, Concerns::TimeoutFilterHandler::SANITIZATION_RENDER_TIMEOUT
Constants included from Concerns::PipelineTimingCheck
Concerns::PipelineTimingCheck::MAX_PIPELINE_SECONDS
Constants inherited from ReferenceFilter
ReferenceFilter::REFERENCE_TYPE_DATA_ATTRIBUTE
Instance Method Summary collapse
- #call ⇒ Object
- #data_attributes_for(text, parent, object, link_content: false, link_reference: false) ⇒ Object
-
#find_object(parent_object, id) ⇒ Object
Implement in child class Example: project.merge_requests.find.
- #find_object_cached(parent_object, id) ⇒ Object
-
#find_object_from_link(parent_object, id) ⇒ Object
Override if the link reference pattern produces a different ID (global ID vs internal ID, for instance) to the regular reference pattern.
- #find_object_from_link_cached(parent_object, id) ⇒ Object
- #from_ref_cached(ref) ⇒ Object
- #identifier(match_data) ⇒ Object
-
#initialize(doc, context = nil, result = nil) ⇒ AbstractReferenceFilter
constructor
A new instance of AbstractReferenceFilter.
-
#object_link_filter(text, pattern, link_content: nil, link_reference: false) ⇒ Object
Replace references (like ‘!123` for merge requests) in text with links to the referenced object’s details page.
- #object_link_text(object, matches) ⇒ Object
- #object_link_text_extras(object, matches) ⇒ Object
- #object_link_title(object, matches) ⇒ Object
- #parent ⇒ Object
- #parent_type ⇒ Object
-
#parse_symbol(symbol, match_data) ⇒ Object
Transform a symbol extracted from the text to a meaningful value In most cases these will be integers, so we call #to_i by default.
-
#record_identifier(record) ⇒ Object
We assume that most classes are identifying records by ID.
-
#references_in(text, pattern = object_class.reference_pattern) ⇒ Object
Public: Find references in text (like ‘!123` for merge requests).
- #symbol_from_match(match) ⇒ Object
-
#url_for_object(object, parent_object) ⇒ Object
Implement in child class Example: project_merge_request_url.
- #url_for_object_cached(object, parent_object) ⇒ Object
- #wrap_link(link, object) ⇒ Object
Methods included from CrossProjectReference
Methods included from Concerns::PipelineTimingCheck
Methods inherited from ReferenceFilter
call, #call_and_update_nodes, #each_node, #group, #nodes, #nodes?, #object_class, #project, #requires_unescaping?
Methods included from Concerns::OutputSafety
Methods included from RequestStoreReferenceCache
#cached_call, #get_or_set_cache
Constructor Details
#initialize(doc, context = nil, result = nil) ⇒ AbstractReferenceFilter
Returns a new instance of AbstractReferenceFilter.
13 14 15 16 17 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 13 def initialize(doc, context = nil, result = nil) super @reference_cache = ReferenceCache.new(self, context, result) end |
Instance Method Details
#call ⇒ Object
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 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 121 def call return doc unless project || group || user reference_cache.load_reference_cache(nodes) if respond_to?(:parent_records) && nodes.present? ref_pattern = object_reference_pattern link_pattern = object_class.link_reference_pattern # Compile often used regexps only once outside of the loop ref_pattern_anchor = /\A#{ref_pattern}\z/ link_pattern_start = /\A#{link_pattern}/ link_pattern_anchor = /\A#{link_pattern}\z/ nodes.each_with_index do |node, index| if text_node?(node) && ref_pattern replace_text_when_pattern_matches(node, index, ref_pattern) do |content| object_link_filter(content, ref_pattern) end elsif element_node?(node) yield_valid_link(node) do |link, inner_html| if ref_pattern && link =~ ref_pattern_anchor replace_link_node_with_href(node, index, link) do object_link_filter(link, ref_pattern_anchor, link_content: inner_html) end next end next unless link_pattern if link == inner_html && inner_html =~ link_pattern_start replace_link_node_with_text(node, index) do object_link_filter(inner_html, link_pattern_start, link_reference: true) end next end if link_pattern_anchor.match?(link) replace_link_node_with_href(node, index, link) do object_link_filter(link, link_pattern_anchor, link_content: inner_html, link_reference: true) end next end end end end doc end |
#data_attributes_for(text, parent, object, link_content: false, link_reference: false) ⇒ Object
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 248 def data_attributes_for(text, parent, object, link_content: false, link_reference: false) parent_id = case parent when Group { group: parent.id, namespace: parent.id } when Project { project: parent.id } when Namespaces::ProjectNamespace { namespace: parent.id, project: parent.project.id } end { original: escape_html_entities(text), link: link_content, link_reference: link_reference, object_sym => object.id }.merge(parent_id) end |
#find_object(parent_object, id) ⇒ Object
Implement in child class Example: project.merge_requests.find
81 82 83 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 81 def find_object(parent_object, id) raise NotImplementedError, "#{self.class} must implement method: #{__callee__}" end |
#find_object_cached(parent_object, id) ⇒ Object
97 98 99 100 101 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 97 def find_object_cached(parent_object, id) cached_call(:banzai_find_object, id, path: [object_class, parent_object.id]) do find_object(parent_object, id) end end |
#find_object_from_link(parent_object, id) ⇒ Object
Override if the link reference pattern produces a different ID (global ID vs internal ID, for instance) to the regular reference pattern.
87 88 89 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 87 def find_object_from_link(parent_object, id) find_object(parent_object, id) end |
#find_object_from_link_cached(parent_object, id) ⇒ Object
103 104 105 106 107 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 103 def find_object_from_link_cached(parent_object, id) cached_call(:banzai_find_object_from_link, id, path: [object_class, parent_object.id]) do find_object_from_link(parent_object, id) end end |
#from_ref_cached(ref) ⇒ Object
109 110 111 112 113 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 109 def from_ref_cached(ref) cached_call("banzai_#{parent_type}_refs".to_sym, ref) do parent_from_ref(ref) end end |
#identifier(match_data) ⇒ Object
51 52 53 54 55 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 51 def identifier(match_data) symbol = symbol_from_match(match_data) parse_symbol(symbol, match_data) if object_class.reference_valid?(symbol) end |
#object_link_filter(text, pattern, link_content: nil, link_reference: false) ⇒ Object
Replace references (like ‘!123` for merge requests) in text with links to the referenced object’s details page.
text - String text to replace references in. pattern - Reference pattern to match against. link_content - Original content of the link being replaced. link_reference - True if this was using the link reference pattern,
false otherwise.
Returns a String with references replaced with links. All links have ‘gfm` and `gfm-OBJECT_NAME` class names attached for styling.
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 185 def object_link_filter(text, pattern, link_content: nil, link_reference: false) references_in(text, pattern) do |match, id, project_ref, namespace_ref, matches| parent_path = if parent_type == :group reference_cache.full_group_path(namespace_ref) elsif parent_type == :namespace reference_cache.full_namespace_path(matches) else reference_cache.full_project_path(namespace_ref, project_ref, matches) end parent = from_ref_cached(parent_path) if parent object = if link_reference find_object_from_link_cached(parent, id) else find_object_cached(parent, id) end end if object title = object_link_title(object, matches) klass = reference_class(object_sym) data_attributes = data_attributes_for( link_content || match, parent, object, link_content: !!link_content, link_reference: link_reference ) data_attributes[:reference_format] = matches[:format] if matches.names.include?("format") data_attributes.merge!(additional_object_attributes(object)) data = data_attribute(data_attributes) url = if matches.names.include?("url") && matches[:url] matches[:url] else url_for_object_cached(object, parent) end url.chomp!(matches[:format]) if matches.names.include?("format") content = link_content || object_link_text(object, matches) link = %(<a href="#{url}" #{data} title="#{escape_once(title)}" class="#{klass}">#{content}</a>) wrap_link(link, object) else match end end end |
#object_link_text(object, matches) ⇒ Object
284 285 286 287 288 289 290 291 292 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 284 def object_link_text(object, matches) parent = project || group || user text = object.reference_link_text(parent) extras = object_link_text_extras(object, matches) text += " (#{extras.join(', ')})" if extras.any? text end |
#object_link_text_extras(object, matches) ⇒ Object
266 267 268 269 270 271 272 273 274 275 276 277 278 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 266 def object_link_text_extras(object, matches) extras = [] if matches.names.include?("anchor") && matches[:anchor] && matches[:anchor] =~ /\A\#note_(\d+)\z/ extras << "comment #{Regexp.last_match(1)}" end extension = matches[:extension] if matches.names.include?("extension") extras << extension if extension extras end |
#object_link_title(object, matches) ⇒ Object
280 281 282 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 280 def object_link_title(object, matches) object.title end |
#parent ⇒ Object
298 299 300 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 298 def parent parent_type == :project ? project : group end |
#parent_type ⇒ Object
294 295 296 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 294 def parent_type :project end |
#parse_symbol(symbol, match_data) ⇒ Object
Transform a symbol extracted from the text to a meaningful value In most cases these will be integers, so we call #to_i by default
This method has the contract that if a string ‘ref` refers to a record `record`, then `parse_symbol(ref) == record_identifier(record)`.
67 68 69 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 67 def parse_symbol(symbol, match_data) symbol.to_i end |
#record_identifier(record) ⇒ Object
We assume that most classes are identifying records by ID.
This method has the contract that if a string ‘ref` refers to a record `record`, then `class.parse_symbol(ref) == record_identifier(record)`.
75 76 77 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 75 def record_identifier(record) record.id end |
#references_in(text, pattern = object_class.reference_pattern) ⇒ Object
Public: Find references in text (like ‘!123` for merge requests)
references_in(text) do |match, id, project_ref, matches|
object = find_object(project_ref, id)
"<a href=...>#{object.to_reference}</a>"
end
text - String text to search.
Yields the String match, the Integer referenced object ID, an optional String of the external project reference, and all of the matchdata.
Returns a String replaced with the return of the block.
40 41 42 43 44 45 46 47 48 49 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 40 def references_in(text, pattern = object_class.reference_pattern) Gitlab::Utils::Gsub.gsub_with_limit(text, pattern, limit: Banzai::Filter::FILTER_ITEM_LIMIT) do |match_data| if ident = identifier(match_data) yield match_data[0], ident, match_data.named_captures['project'], match_data.named_captures['namespace'], match_data else match_data[0] end end end |
#symbol_from_match(match) ⇒ Object
57 58 59 60 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 57 def symbol_from_match(match) key = object_sym match[key] if match.names.include?(key.to_s) end |
#url_for_object(object, parent_object) ⇒ Object
Implement in child class Example: project_merge_request_url
93 94 95 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 93 def url_for_object(object, parent_object) raise NotImplementedError, "#{self.class} must implement method: #{__callee__}" end |
#url_for_object_cached(object, parent_object) ⇒ Object
115 116 117 118 119 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 115 def url_for_object_cached(object, parent_object) cached_call(:banzai_url_for_object, object, path: [object_class, parent_object.id]) do url_for_object(object, parent_object) end end |
#wrap_link(link, object) ⇒ Object
244 245 246 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 244 def wrap_link(link, object) link end |