diff options
Diffstat (limited to 'lib/banzai/filter')
-rw-r--r-- | lib/banzai/filter/base_relative_link_filter.rb | 18 | ||||
-rw-r--r-- | lib/banzai/filter/markdown_pre_escape_filter.rb | 2 | ||||
-rw-r--r-- | lib/banzai/filter/references/label_reference_filter.rb | 93 | ||||
-rw-r--r-- | lib/banzai/filter/references/reference_cache.rb | 48 | ||||
-rw-r--r-- | lib/banzai/filter/references/reference_filter.rb | 4 | ||||
-rw-r--r-- | lib/banzai/filter/upload_link_filter.rb | 14 |
6 files changed, 104 insertions, 75 deletions
diff --git a/lib/banzai/filter/base_relative_link_filter.rb b/lib/banzai/filter/base_relative_link_filter.rb index 3f775abb185..b2eaeb69f61 100644 --- a/lib/banzai/filter/base_relative_link_filter.rb +++ b/lib/banzai/filter/base_relative_link_filter.rb @@ -13,18 +13,12 @@ module Banzai protected def linkable_attributes - if Feature.enabled?(:optimize_linkable_attributes, project, default_enabled: :yaml) - # Nokorigi Nodeset#search performs badly for documents with many nodes - # - # Here we store fetched attributes in the shared variable "result" - # This variable is passed through the chain of filters and can be - # accessed by them - result[:linkable_attributes] ||= fetch_linkable_attributes - else - strong_memoize(:linkable_attributes) do - fetch_linkable_attributes - end - end + # Nokorigi Nodeset#search performs badly for documents with many nodes + # + # Here we store fetched attributes in the shared variable "result" + # This variable is passed through the chain of filters and can be + # accessed by them + result[:linkable_attributes] ||= fetch_linkable_attributes end def relative_url_root diff --git a/lib/banzai/filter/markdown_pre_escape_filter.rb b/lib/banzai/filter/markdown_pre_escape_filter.rb index 0c53444681d..8d54d140877 100644 --- a/lib/banzai/filter/markdown_pre_escape_filter.rb +++ b/lib/banzai/filter/markdown_pre_escape_filter.rb @@ -30,8 +30,6 @@ module Banzai LITERAL_KEYWORD = 'cmliteral' def call - return @text unless Feature.enabled?(:honor_escaped_markdown, context[:group] || context[:project]&.group) - @text.gsub(ASCII_PUNCTUATION) do |match| # The majority of markdown does not have literals. If none # are found, we can bypass the post filter diff --git a/lib/banzai/filter/references/label_reference_filter.rb b/lib/banzai/filter/references/label_reference_filter.rb index bf6b3e47d3b..12afece6e53 100644 --- a/lib/banzai/filter/references/label_reference_filter.rb +++ b/lib/banzai/filter/references/label_reference_filter.rb @@ -8,21 +8,57 @@ module Banzai self.reference_type = :label self.object_class = Label + def parent_records(parent, ids) + return Label.none unless parent.is_a?(Project) || parent.is_a?(Group) + + labels = find_labels(parent) + label_ids = ids.map {|y| y[:label_id]}.compact + label_names = ids.map {|y| y[:label_name]}.compact + id_relation = labels.where(id: label_ids) + label_relation = labels.where(title: label_names) + + Label.from_union([id_relation, label_relation]) + end + def find_object(parent_object, id) - find_labels(parent_object).find(id) + key = reference_cache.records_per_parent[parent_object].keys.find do |k| + k[:label_id] == id[:label_id] || k[:label_name] == id[:label_name] + end + + reference_cache.records_per_parent[parent_object][key] if key + end + + # Transform a symbol extracted from the text to a meaningful value + # + # This method has the contract that if a string `ref` refers to a + # record `record`, then `parse_symbol(ref) == record_identifier(record)`. + # + # This contract is slightly broken here, as we only have either the label_id + # or the label_name, but not both. But below, we have both pieces of information. + # But it's accounted for in `find_object` + def parse_symbol(symbol, match_data) + { label_id: match_data[:label_id]&.to_i, label_name: match_data[:label_name]&.tr('"', '') } + end + + # 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)`. + # See note in `parse_symbol` above + def record_identifier(record) + { label_id: record.id, label_name: record.title } end def references_in(text, pattern = Label.reference_pattern) labels = {} - unescaped_html = unescape_html_entities(text).gsub(pattern) do |match| - namespace = $~[:namespace] - project = $~[:project] - project_path = reference_cache.full_project_path(namespace, project) - label = find_label_cached(project_path, $~[:label_id], $~[:label_name]) - - if label - labels[label.id] = yield match, label.id, project, namespace, $~ - "#{REFERENCE_PLACEHOLDER}#{label.id}" + + unescaped_html = unescape_html_entities(text).gsub(pattern).with_index do |match, index| + ident = identifier($~) + label = yield match, ident, $~[:project], $~[:namespace], $~ + + if label != match + labels[index] = label + "#{REFERENCE_PLACEHOLDER}#{index}" else match end @@ -33,20 +69,6 @@ module Banzai escape_with_placeholders(unescaped_html, labels) end - def find_label_cached(parent_ref, label_id, label_name) - cached_call(:banzai_find_label_cached, label_name&.tr('"', '') || label_id, path: [object_class, parent_ref]) do - find_label(parent_ref, label_id, label_name) - end - end - - 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(parent).find_by(label_params) - end - def find_labels(parent) params = if parent.is_a?(Group) { group_id: parent.id, @@ -60,21 +82,6 @@ module Banzai LabelsFinder.new(nil, params).execute(skip_authorization: true) end - # Parameters to pass to `Label.find_by` based on the given arguments - # - # id - Integer ID to pass. If present, returns {id: id} - # name - String name to pass. If `id` is absent, finds by name without - # surrounding quotes. - # - # Returns a Hash. - def label_params(id, name) - if name - { name: name.tr('"', '') } - else - { id: id.to_i } - end - end - def url_for_object(label, parent) label_url_method = if context[:label_url_method] @@ -121,6 +128,14 @@ module Banzai presenter = object.present(issuable_subject: project || group) LabelsHelper.label_tooltip_title(presenter) end + + def parent + project || group + end + + def requires_unescaping? + true + end end end end diff --git a/lib/banzai/filter/references/reference_cache.rb b/lib/banzai/filter/references/reference_cache.rb index ab0c74e00d9..24b8b4984cd 100644 --- a/lib/banzai/filter/references/reference_cache.rb +++ b/lib/banzai/filter/references/reference_cache.rb @@ -29,15 +29,15 @@ module Banzai refs = Hash.new { |hash, key| hash[key] = Set.new } nodes.each do |node| - node.to_html.scan(regex) do - path = if parent_type == :project - full_project_path($~[:namespace], $~[:project]) - else - full_group_path($~[:group]) - end + prepare_node_for_scan(node).scan(regex) do + parent_path = if parent_type == :project + full_project_path($~[:namespace], $~[:project]) + else + full_group_path($~[:group]) + end ident = filter.identifier($~) - refs[path] << ident if ident + refs[parent_path] << ident if ident end end @@ -55,9 +55,23 @@ module Banzai @per_reference ||= {} @per_reference[parent_type] ||= begin - refs = references_per_parent.keys.to_set + refs = references_per_parent.keys + parent_ref = {} - find_for_paths(refs.to_a).index_by(&:full_path) + # if we already have a parent, no need to query it again + refs.each do |ref| + next unless ref + + if context[:project]&.full_path == ref + parent_ref[ref] = context[:project] + elsif context[:group]&.full_path == ref + parent_ref[ref] = context[:group] + end + + refs -= [ref] if parent_ref[ref] + end + + find_for_paths(refs).index_by(&:full_path).merge(parent_ref) end end @@ -87,7 +101,7 @@ module Banzai @_records_per_project[filter.object_class.to_s.underscore] end - def relation_for_paths(paths) + def objects_for_paths(paths) klass = parent_type.to_s.camelize.constantize result = klass.where_full_path_in(paths) return result if parent_type == :group @@ -102,7 +116,7 @@ module Banzai to_query = paths - cache.keys unless to_query.empty? - records = relation_for_paths(to_query) + records = objects_for_paths(to_query) found = [] records.each do |record| @@ -119,7 +133,7 @@ module Banzai cache.slice(*paths).values.compact else - relation_for_paths(paths) + objects_for_paths(paths) end end @@ -170,6 +184,16 @@ module Banzai def refs_cache Gitlab::SafeRequestStore["banzai_#{parent_type}_refs".to_sym] ||= {} end + + def prepare_node_for_scan(node) + html = node.to_html + + filter.requires_unescaping? ? unescape_html_entities(html) : html + end + + def unescape_html_entities(text) + CGI.unescapeHTML(text.to_s) + end end end end diff --git a/lib/banzai/filter/references/reference_filter.rb b/lib/banzai/filter/references/reference_filter.rb index 58436f4505e..6c2c993cc01 100644 --- a/lib/banzai/filter/references/reference_filter.rb +++ b/lib/banzai/filter/references/reference_filter.rb @@ -109,6 +109,10 @@ module Banzai context[:group] end + def requires_unescaping? + false + end + private # Returns a data attribute String to attach to a reference link diff --git a/lib/banzai/filter/upload_link_filter.rb b/lib/banzai/filter/upload_link_filter.rb index ceb7547a85d..2572481c8fc 100644 --- a/lib/banzai/filter/upload_link_filter.rb +++ b/lib/banzai/filter/upload_link_filter.rb @@ -15,16 +15,10 @@ module Banzai def call return doc if context[:system_note] - if Feature.enabled?(:optimize_linkable_attributes, project, default_enabled: :yaml) - # We exclude processed upload links from the linkable attributes to - # prevent further modifications by RepositoryLinkFilter - linkable_attributes.reject! do |attr| - process_link_to_upload_attr(attr) - end - else - linkable_attributes.each do |attr| - process_link_to_upload_attr(attr) - end + # We exclude processed upload links from the linkable attributes to + # prevent further modifications by RepositoryLinkFilter + linkable_attributes.reject! do |attr| + process_link_to_upload_attr(attr) end doc |