snippet.rb 6.6 KB
Newer Older
1 2
# frozen_string_literal: true

3
class Snippet < ApplicationRecord
V
Valery Sizov 已提交
4
  include Gitlab::VisibilityLevel
5
  include Redactable
6
  include CacheMarkdownField
7
  include Noteable
8
  include Participable
9 10
  include Referable
  include Sortable
11
  include Awardable
12
  include Mentionable
S
Sean McGivern 已提交
13
  include Spammable
14
  include Editable
15
  include Gitlab::SQL::Pattern
16
  include FromUnion
17
  extend ::Gitlab::Utils::Override
G
gitlabhq 已提交
18

19
  cache_markdown_field :title, pipeline: :single_line
20
  cache_markdown_field :description
21 22
  cache_markdown_field :content

23 24
  redact_field :description

B
blackst0ne 已提交
25
  # Aliases to make application_helper#edited_time_ago_with_tooltip helper work properly with snippets.
26
  # See https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/10392/diffs#note_28719102
B
blackst0ne 已提交
27 28 29
  alias_attribute :last_edited_at, :updated_at
  alias_attribute :last_edited_by, :updated_by

30 31 32 33 34 35
  # If file_name changes, it invalidates content
  alias_method :default_content_html_invalidator, :content_html_invalidated?
  def content_html_invalidated?
    default_content_html_invalidator || file_name_changed?
  end

R
Robert Speicher 已提交
36 37
  belongs_to :author, class_name: 'User'
  belongs_to :project
A
Andrew8xx8 已提交
38

39
  has_many :notes, as: :noteable, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
G
gitlabhq 已提交
40

D
Dmitriy Zaporozhets 已提交
41
  delegate :name, :email, to: :author, prefix: true, allow_nil: true
G
gitlabhq 已提交
42

A
Andrey Kumanyaev 已提交
43
  validates :author, presence: true
44
  validates :title, presence: true, length: { maximum: 255 }
45
  validates :file_name,
46
    length: { maximum: 255 }
47

V
Valeriy Sizov 已提交
48
  validates :content, presence: true
V
Valery Sizov 已提交
49
  validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values }
G
gitlabhq 已提交
50

A
Andrey Kumanyaev 已提交
51
  # Scopes
52
  scope :are_internal, -> { where(visibility_level: Snippet::INTERNAL) }
V
Valery Sizov 已提交
53 54 55
  scope :are_private, -> { where(visibility_level: Snippet::PRIVATE) }
  scope :are_public, -> { where(visibility_level: Snippet::PUBLIC) }
  scope :public_and_internal, -> { where(visibility_level: [Snippet::PUBLIC, Snippet::INTERNAL]) }
56
  scope :fresh, -> { order("created_at DESC") }
57
  scope :inc_author, -> { includes(:author) }
58
  scope :inc_relations_for_view, -> { includes(author: :status) }
N
Nihad Abbasov 已提交
59

Y
Yorick Peterse 已提交
60 61
  participant :author
  participant :notes_with_associations
62

S
Sean McGivern 已提交
63 64 65
  attr_spammable :title, spam_title: true
  attr_spammable :content, spam_description: true

66 67 68 69 70 71 72 73
  def self.with_optional_visibility(value = nil)
    if value
      where(visibility_level: value)
    else
      all
    end
  end

74
  def self.only_personal_snippets
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
    where(project_id: nil)
  end

  def self.only_include_projects_visible_to(current_user = nil)
    levels = Gitlab::VisibilityLevel.levels_for_user(current_user)

    joins(:project).where('projects.visibility_level IN (?)', levels)
  end

  def self.only_include_projects_with_snippets_enabled(include_private: false)
    column = ProjectFeature.access_level_attribute(:snippets)
    levels = [ProjectFeature::ENABLED, ProjectFeature::PUBLIC]

    levels << ProjectFeature::PRIVATE if include_private

    joins(project: :project_feature)
      .where(project_features: { column => levels })
  end

  def self.only_include_authorized_projects(current_user)
    where(
      'EXISTS (?)',
      ProjectAuthorization
        .select(1)
        .where('project_id = snippets.project_id')
        .where(user_id: current_user.id)
    )
  end

  def self.for_project_with_user(project, user = nil)
    return none unless project.snippets_visible?(user)

    if user && project.team.member?(user)
      project.snippets
    else
      project.snippets.public_to_user(user)
    end
  end

  def self.visible_to_or_authored_by(user)
    where(
      'snippets.visibility_level IN (?) OR snippets.author_id = ?',
      Gitlab::VisibilityLevel.levels_for_user(user),
      user.id
    )
  end

122 123 124 125
  def self.reference_prefix
    '$'
  end

126 127 128 129
  # Pattern used to extract `$123` snippet references from text
  #
  # This pattern supports cross-project references.
  def self.reference_pattern
130
    @reference_pattern ||= %r{
131 132
      (#{Project.reference_pattern})?
      #{Regexp.escape(reference_prefix)}(?<snippet>\d+)
133 134 135
    }x
  end

136
  def self.link_reference_pattern
137
    @link_reference_pattern ||= super("snippets", /(?<snippet>\d+)/)
138 139
  end

140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
  def initialize(attributes = {})
    # We can't use default_value_for because the database has a default
    # value of 0 for visibility_level. If someone attempts to create a
    # private snippet, default_value_for will assume that the
    # visibility_level hasn't changed and will use the application
    # setting default, which could be internal or public.
    #
    # To fix the problem, we assign the actual snippet default if no
    # explicit visibility has been initialized.
    attributes ||= {}

    unless visibility_attribute_present?(attributes)
      attributes[:visibility_level] = Gitlab::CurrentSettings.default_snippet_visibility
    end

    super
  end

J
Jarka Kadlecova 已提交
158
  def to_reference(from = nil, full: false)
159 160
    reference = "#{self.class.reference_prefix}#{id}"

161
    if project.present?
J
Jarka Kadlecova 已提交
162
      "#{project.to_reference(from, full: full)}#{reference}"
163 164
    else
      reference
165 166 167
    end
  end

G
gitlabhq 已提交
168
  def self.content_types
N
Nihad Abbasov 已提交
169
    [
G
gitlabhq 已提交
170 171 172 173 174
      ".rb", ".py", ".pl", ".scala", ".c", ".cpp", ".java",
      ".haml", ".html", ".sass", ".scss", ".xml", ".php", ".erb",
      ".js", ".sh", ".coffee", ".yml", ".md"
    ]
  end
G
gitlabhq 已提交
175

D
Douwe Maan 已提交
176 177
  def blob
    @blob ||= Blob.decorate(SnippetBlob.new(self), nil)
178 179
  end

180 181 182 183
  def hook_attrs
    attributes
  end

184 185 186 187
  def file_name
    super.to_s
  end

188 189 190 191
  def sanitized_file_name
    file_name.gsub(/[^a-zA-Z0-9_\-\.]+/, '')
  end

V
Valery Sizov 已提交
192
  def visibility_level_field
193
    :visibility_level
194
  end
V
Valery Sizov 已提交
195

196
  def embeddable?
197 198 199
    ability = project_id? ? :read_project_snippet : :read_personal_snippet

    Ability.allowed?(nil, ability, self)
200 201
  end

Y
Yorick Peterse 已提交
202
  def notes_with_associations
203
    notes.includes(:author)
Y
Yorick Peterse 已提交
204 205
  end

S
Sean McGivern 已提交
206
  def check_for_spam?
207 208
    visibility_level_changed?(to: Snippet::PUBLIC) ||
      (public? && (title_changed? || content_changed?))
S
Sean McGivern 已提交
209 210
  end

211 212 213 214 215 216
  # snippers are the biggest sources of spam
  override :allow_possible_spam?
  def allow_possible_spam?
    false
  end

S
Sean McGivern 已提交
217 218 219 220
  def spammable_entity_type
    'snippet'
  end

221 222 223 224
  def to_ability_name
    model_name.singular
  end

225
  class << self
226 227 228 229 230 231 232
    # Searches for snippets with a matching title or file name.
    #
    # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
    #
    # query - The search query as a String.
    #
    # Returns an ActiveRecord::Relation.
233
    def search(query)
234
      fuzzy_search(query, [:title, :file_name])
235 236
    end

237 238 239 240 241 242 243
    # Searches for snippets with matching content.
    #
    # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
    #
    # query - The search query as a String.
    #
    # Returns an ActiveRecord::Relation.
244
    def search_code(query)
245
      fuzzy_search(query, [:content])
246
    end
J
Jan Provaznik 已提交
247 248 249 250

    def parent_class
      ::Project
    end
251
  end
G
gitlabhq 已提交
252
end
253 254

Snippet.prepend_if_ee('EE::Snippet')