diff options
Diffstat (limited to 'lib/banzai/filter/references/milestone_reference_filter.rb')
-rw-r--r-- | lib/banzai/filter/references/milestone_reference_filter.rb | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/lib/banzai/filter/references/milestone_reference_filter.rb b/lib/banzai/filter/references/milestone_reference_filter.rb new file mode 100644 index 00000000000..49110194ddc --- /dev/null +++ b/lib/banzai/filter/references/milestone_reference_filter.rb @@ -0,0 +1,140 @@ +# frozen_string_literal: true + +module Banzai + module Filter + module References + # HTML filter that replaces milestone references with links. + class MilestoneReferenceFilter < AbstractReferenceFilter + include Gitlab::Utils::StrongMemoize + + self.reference_type = :milestone + + def self.object_class + Milestone + end + + # Links to project milestones contain the IID, but when we're handling + # 'regular' references, we need to use the global ID to disambiguate + # between group and project milestones. + def find_object(parent, id) + return unless valid_context?(parent) + + find_milestone_with_finder(parent, id: id) + end + + def find_object_from_link(parent, iid) + return unless valid_context?(parent) + + find_milestone_with_finder(parent, iid: iid) + end + + def valid_context?(parent) + strong_memoize(:valid_context) do + group_context?(parent) || project_context?(parent) + end + end + + def group_context?(parent) + strong_memoize(:group_context) do + parent.is_a?(Group) + end + end + + def project_context?(parent) + strong_memoize(:project_context) do + parent.is_a?(Project) + end + end + + def references_in(text, pattern = Milestone.reference_pattern) + # We'll handle here the references that follow the `reference_pattern`. + # Other patterns (for example, the link pattern) are handled by the + # default implementation. + return super(text, pattern) if pattern != Milestone.reference_pattern + + milestones = {} + unescaped_html = unescape_html_entities(text).gsub(pattern) do |match| + milestone = find_milestone($~[:project], $~[:namespace], $~[:milestone_iid], $~[:milestone_name]) + + if milestone + milestones[milestone.id] = yield match, milestone.id, $~[:project], $~[:namespace], $~ + "#{REFERENCE_PLACEHOLDER}#{milestone.id}" + else + match + end + end + + return text if milestones.empty? + + escape_with_placeholders(unescaped_html, milestones) + end + + def find_milestone(project_ref, namespace_ref, milestone_id, milestone_name) + project_path = full_project_path(namespace_ref, project_ref) + + # Returns group if project is not found by path + parent = parent_from_ref(project_path) + + return unless parent + + milestone_params = milestone_params(milestone_id, milestone_name) + + find_milestone_with_finder(parent, milestone_params) + end + + def milestone_params(iid, name) + if name + { name: name.tr('"', '') } + else + { iid: iid.to_i } + end + end + + def find_milestone_with_finder(parent, params) + finder_params = milestone_finder_params(parent, params[:iid].present?) + + MilestonesFinder.new(finder_params).find_by(params) + end + + def milestone_finder_params(parent, find_by_iid) + { order: nil, state: 'all' }.tap do |params| + params[:project_ids] = parent.id if project_context?(parent) + + # We don't support IID lookups because IIDs can clash between + # group/project milestones and group/subgroup milestones. + params[:group_ids] = self_and_ancestors_ids(parent) unless find_by_iid + end + end + + def self_and_ancestors_ids(parent) + if group_context?(parent) + parent.self_and_ancestors.select(:id) + elsif project_context?(parent) + parent.group&.self_and_ancestors&.select(:id) + end + end + + def url_for_object(milestone, project) + Gitlab::Routing + .url_helpers + .milestone_url(milestone, only_path: context[:only_path]) + end + + def object_link_text(object, matches) + milestone_link = escape_once(super) + reference = object.project&.to_reference_base(project) + + if reference.present? + "#{milestone_link} <i>in #{reference}</i>".html_safe + else + milestone_link + end + end + + def object_link_title(object, matches) + nil + end + end + end + end +end |