Class: WikiPage

Inherits:
Object
  • Object
show all
Extended by:
ActiveModel::Naming
Includes:
ActiveModel::Conversion, ActiveModel::Validations, Gitlab::Utils::StrongMemoize, Referable, StaticModel
Defined in:
app/models/wiki_page.rb,
app/models/wiki_page/meta.rb,
app/models/wiki_page/slug.rb

Overview

rubocop:disable Rails/ActiveRecordAliases

Defined Under Namespace

Classes: Meta, Slug

Constant Summary collapse

PageChangedError =
Class.new(StandardError)
PageRenameError =
Class.new(StandardError)
FrontMatterTooLong =
Class.new(StandardError)
RESERVED_SLUGS =

Reserved wiki page slugs that conflict with wiki routes. These cannot be used as the first path component of a wiki page title. See config/routes/wiki.rb for the routes that define these reserved paths.

%w[
  pages
  templates
  new
  git_access
  -
].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from StaticModel

#[], #destroyed?, #new_record?

Methods included from Referable

#referable_inspect, #to_reference_base

Constructor Details

#initialize(wiki, page = nil) ⇒ WikiPage

Construct a new WikiPage

Parameters:



127
128
129
130
131
132
133
# File 'app/models/wiki_page.rb', line 127

def initialize(wiki, page = nil)
  @wiki       = wiki
  @page       = page
  @attributes = {}.with_indifferent_access

  set_attributes if persisted?
end

Instance Attribute Details

#attributesObject

The attributes Hash used for storing and validating new Page values before writing to the raw repository.



117
118
119
# File 'app/models/wiki_page.rb', line 117

def attributes
  @attributes
end

#pageObject (readonly)

The raw Gitlab::Git::WikiPage instance.



113
114
115
# File 'app/models/wiki_page.rb', line 113

def page
  @page
end

#wikiObject (readonly)

The GitLab Wiki instance.



108
109
110
# File 'app/models/wiki_page.rb', line 108

def wiki
  @wiki
end

Class Method Details

.extensible_reference_prefixObject



50
51
52
# File 'app/models/wiki_page.rb', line 50

def self.extensible_reference_prefix
  '[wiki_page:'
end


80
81
82
83
84
85
86
# File 'app/models/wiki_page.rb', line 80

def self.link_reference_pattern
  @link_reference_pattern ||= project_or_group_link_reference_pattern(
    'wikis',
    namespace_reference_pattern,
    %r{(?<wiki_page>[\/\w-]+)}
  )
end

.model_nameObject



34
35
36
# File 'app/models/wiki_page.rb', line 34

def self.model_name
  ActiveModel::Name.new(self, nil, 'wiki')
end

.namespace_reference_patternObject



73
74
75
76
77
78
# File 'app/models/wiki_page.rb', line 73

def self.namespace_reference_pattern
  @namespace_reference_pattern ||= %r{
    (?<!#{Gitlab::PathRegex::PATH_START_CHAR})
    ((?<group_or_project_namespace>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX}))
  }x
end

.primary_keyObject



30
31
32
# File 'app/models/wiki_page.rb', line 30

def self.primary_key
  'slug'
end

.reference_patternObject



62
63
64
65
66
67
68
69
70
71
# File 'app/models/wiki_page.rb', line 62

def self.reference_pattern
  @reference_pattern ||= %r{
    #{Regexp.escape(extensible_reference_prefix)}
    (#{namespace_reference_pattern}#{Regexp.escape(reference_prefix)})?
      (?<wiki_page>
        [^\s]+
      )
    #{Regexp.escape(reference_postfix)}
  }x
end

.reference_postfixObject



58
59
60
# File 'app/models/wiki_page.rb', line 58

def self.reference_postfix
  ']'
end

.reference_prefixObject



54
55
56
# File 'app/models/wiki_page.rb', line 54

def self.reference_prefix
  ':'
end

.unhyphenize(name) ⇒ Object



46
47
48
# File 'app/models/wiki_page.rb', line 46

def self.unhyphenize(name)
  name.gsub(/-+/, ' ')
end

Instance Method Details

#content_changed?Boolean

Returns:

  • (Boolean)


363
364
365
366
367
368
369
370
371
# File 'app/models/wiki_page.rb', line 363

def content_changed?
  if persisted?
    # To avoid end-of-line differences depending if Git is enforcing CRLF or not,
    # we compare just the Wiki Content.
    raw_content.lines(chomp: true) != page&.text_data&.lines(chomp: true)
  else
    raw_content.present?
  end
end

#count_versionsObject



230
231
232
233
234
# File 'app/models/wiki_page.rb', line 230

def count_versions
  return [] unless persisted?

  wiki.repository.count_commits(ref: wiki.default_branch, path: page.path)
end

#create(attrs = {}) ⇒ Object

Creates a new Wiki Page.

attr - Hash of attributes to set on the new page.

:title   - The title (optionally including dir) for the new page.
:content - The raw markup content.
:format  - Optional symbol representing the
           content format. Can be any type
           listed in the Wiki::VALID_USER_MARKUPS
           Hash.
:message - Optional commit message to set on
           the new page.

Returns the String SHA1 of the newly created page or False if the save was unsuccessful.



282
283
284
285
286
287
288
# File 'app/models/wiki_page.rb', line 282

def create(attrs = {})
  update_attributes(attrs)

  save do
    wiki.create_page(title, raw_content, format, attrs[:message])
  end
end

#deleteObject

Destroys the Wiki Page.

Returns boolean True or False.



331
332
333
334
335
336
337
# File 'app/models/wiki_page.rb', line 331

def delete
  if wiki.delete_page(page)
    true
  else
    false
  end
end

#diffs(diff_options = {}) ⇒ Object



394
395
396
# File 'app/models/wiki_page.rb', line 394

def diffs(diff_options = {})
  Gitlab::Diff::FileCollection::WikiPage.new(self, diff_options: diff_options)
end

#directoryObject

The hierarchy of the directory this page is contained in.



184
185
186
# File 'app/models/wiki_page.rb', line 184

def directory
  wiki.page_title_and_dir(slug)&.last.to_s
end

#eql?(other) ⇒ Boolean Also known as: ==

Returns:

  • (Boolean)


38
39
40
41
42
# File 'app/models/wiki_page.rb', line 38

def eql?(other)
  return false unless other.present? && other.is_a?(self.class)

  slug == other.slug && wiki.container == other.wiki.container
end

#find_or_create_metaObject



135
136
137
# File 'app/models/wiki_page.rb', line 135

def find_or_create_meta
  WikiPage::Meta.find_or_create(slug, self)
end

#formatObject

The markup format for the page.



189
190
191
# File 'app/models/wiki_page.rb', line 189

def format
  attributes[:format] || :markdown
end

#front_matter_titleObject



168
169
170
# File 'app/models/wiki_page.rb', line 168

def front_matter_title
  front_matter[:title]
end

#historical?Boolean

Returns boolean True or False if this instance is an old version of the page.

Returns:

  • (Boolean)


250
251
252
253
254
# File 'app/models/wiki_page.rb', line 250

def historical?
  return false unless last_commit_sha && version

  page.historical? && last_commit_sha != version.sha
end

#hook_attrsObject



119
120
121
# File 'app/models/wiki_page.rb', line 119

def hook_attrs
  Gitlab::HookData::WikiPageBuilder.new(self).build
end

#human_titleObject



147
148
149
150
151
152
# File 'app/models/wiki_page.rb', line 147

def human_title
  return front_matter_title if front_matter_title.present?
  return 'Home' if title == Wiki::HOMEPAGE

  title
end

#last_commit_shaObject



240
241
242
# File 'app/models/wiki_page.rb', line 240

def last_commit_sha
  last_version&.sha
end

#last_versionObject



236
237
238
# File 'app/models/wiki_page.rb', line 236

def last_version
  @last_version ||= wiki.repository.last_commit_for_path(wiki.default_branch, page.path) if page
end

#latest?Boolean

Returns boolean True or False if this instance is the latest commit version of the page.

Returns:

  • (Boolean)


258
259
260
# File 'app/models/wiki_page.rb', line 258

def latest?
  !historical?
end

#messageObject

The commit message for this page version.



194
195
196
# File 'app/models/wiki_page.rb', line 194

def message
  version.try(:message)
end

#pathObject



205
206
207
208
209
# File 'app/models/wiki_page.rb', line 205

def path
  return unless persisted?

  @path ||= @page.path
end

#persisted?Boolean

Returns boolean True or False if this instance has been fully created on disk or not.

Returns:

  • (Boolean)


264
265
266
# File 'app/models/wiki_page.rb', line 264

def persisted?
  page.present?
end

#raw_contentObject



172
173
174
# File 'app/models/wiki_page.rb', line 172

def raw_content
  attributes[:content] ||= page&.text_data
end

#raw_content=(content) ⇒ Object



176
177
178
179
180
181
# File 'app/models/wiki_page.rb', line 176

def raw_content=(content)
  return if page.nil?

  page.raw_data = content
  attributes[:content] = page.text_data
end


94
95
96
# File 'app/models/wiki_page.rb', line 94

def reference_link_text(_from = nil)
  human_title
end

#shaObject



345
346
347
# File 'app/models/wiki_page.rb', line 345

def sha
  page.version&.sha
end

#slugObject Also known as: id, to_param

The escaped URL path of this page.



140
141
142
# File 'app/models/wiki_page.rb', line 140

def slug
  attributes[:slug].presence || ::Wiki.preview_slug(title, format)
end

#sluggified_titleObject



164
165
166
# File 'app/models/wiki_page.rb', line 164

def sluggified_title
  Wiki.sluggified_title(title)
end

#template?Boolean

Returns:

  • (Boolean)


244
245
246
# File 'app/models/wiki_page.rb', line 244

def template?
  slug.start_with?(Wiki::TEMPLATES_DIR)
end

#titleObject

The formatted title of this page.



155
156
157
# File 'app/models/wiki_page.rb', line 155

def title
  attributes[:title] || ''
end

#title=(new_title) ⇒ Object

Sets the title of this page.



160
161
162
# File 'app/models/wiki_page.rb', line 160

def title=(new_title)
  attributes[:title] = new_title
end

#title_changed?Boolean

Returns:

  • (Boolean)


349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'app/models/wiki_page.rb', line 349

def title_changed?
  if persisted?
    # A page's `title` will be returned from Gollum/Gitaly with any +<>
    # characters changed to -, whereas the `path` preserves these characters.
    path_without_extension = Pathname(page.path).sub_ext('').to_s
    old_title, old_dir = wiki.page_title_and_dir(self.class.unhyphenize(path_without_extension))
    new_title, new_dir = wiki.page_title_and_dir(self.class.unhyphenize(title))

    new_title != old_title || (title.include?('/') && new_dir != old_dir)
  else
    title.present?
  end
end

#to_ability_nameObject



384
385
386
# File 'app/models/wiki_page.rb', line 384

def to_ability_name
  'wiki_page'
end

#to_keyObject



98
99
100
# File 'app/models/wiki_page.rb', line 98

def to_key
  [:slug]
end

#to_partial_pathObject

Relative path to the partial to be used when rendering collections of this object.



341
342
343
# File 'app/models/wiki_page.rb', line 341

def to_partial_path
  'shared/wikis/wiki_page'
end

#to_reference(from = nil, **_params) ⇒ Object



88
89
90
91
92
# File 'app/models/wiki_page.rb', line 88

def to_reference(from = nil, **_params)
  base = container.to_reference_base(from, full: true)

  "[wiki_page:#{base}:#{slug}]" if base.present?
end

#update(attrs = {}) ⇒ Object

Updates an existing Wiki Page, creating a new version.

attrs - Hash of attributes to be updated on the page.

:content         - The raw markup content to replace the existing.
:format          - Optional symbol representing the content format.
                   See Wiki::VALID_USER_MARKUPS Hash for available formats.
:message         - Optional commit message to set on the new version.
:last_commit_sha - Optional last commit sha to validate the page unchanged.
:title           - The Title (optionally including dir) to replace existing title

Returns the String SHA1 of the newly created page or False if the save was unsuccessful.



302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'app/models/wiki_page.rb', line 302

def update(attrs = {})
  last_commit_sha = attrs.delete(:last_commit_sha)

  if last_commit_sha && last_commit_sha != self.last_commit_sha
    raise PageChangedError, s_(
      'WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{wikiLinkStart}the page%{wikiLinkEnd} and make sure your changes will not unintentionally remove theirs.')
  end

  update_attributes(attrs)

  if title.present? && title_changed? && wiki.find_page(title, load_content: false).present?
    attributes[:title] = page.title
    raise PageRenameError, s_('WikiEdit|There is already a page with the same title in that path.')
  end

  save do
    wiki.update_page(
      page,
      content: raw_content,
      format: format,
      message: attrs[:message],
      title: title
    )
  end
end

#update_attributes(attrs) ⇒ Object

Updates the current @attributes hash by merging a hash of params



374
375
376
377
378
379
380
381
382
# File 'app/models/wiki_page.rb', line 374

def update_attributes(attrs)
  attrs[:title] = process_title(attrs[:title]) if attrs[:title].present?
  update_front_matter(attrs)

  attrs.slice!(:content, :format, :message, :title)
  clear_memoization(:parsed_content) if attrs.has_key?(:content)

  attributes.merge!(attrs)
end

#versionObject

The GitLab Commit instance for this page.



199
200
201
202
203
# File 'app/models/wiki_page.rb', line 199

def version
  return unless persisted?

  @version ||= @page.version || last_version
end

#version_commit_timestampObject



388
389
390
391
392
# File 'app/models/wiki_page.rb', line 388

def version_commit_timestamp
  return version&.committed_date if version.is_a?(Commit)

  version&.commit&.committed_date
end

#versions(options = {}) ⇒ Object

Returns a CommitCollection

Queries the commits for current page’s path, equivalent to ‘git log path/to/page`. Filters and options supported: gitlab.com/gitlab-org/gitaly/-/blob/master/proto/commit.proto#L322-344



216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'app/models/wiki_page.rb', line 216

def versions(options = {})
  return [] unless persisted?

  default_per_page = Kaminari.config.default_per_page
  offset = [options[:page].to_i - 1, 0].max * options.fetch(:per_page, default_per_page)

  wiki.repository.commits(
    wiki.default_branch,
    path: page.path,
    limit: options.fetch(:limit, default_per_page),
    offset: offset
  )
end