mirror of
https://github.com/gitlabhq/gitlabhq.git
synced 2025-08-06 11:10:08 +00:00
486 lines
14 KiB
Ruby
486 lines
14 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module WikiActions
|
|
include DiffHelper
|
|
include PreviewMarkdown
|
|
include SendsBlob
|
|
include Gitlab::Utils::StrongMemoize
|
|
include ProductAnalyticsTracking
|
|
include SafeFormatHelper
|
|
extend ActiveSupport::Concern
|
|
include StrongPaginationParams
|
|
|
|
RESCUE_GIT_TIMEOUTS_IN = %w[show raw edit history diff pages templates].freeze
|
|
|
|
included do
|
|
content_security_policy do |p|
|
|
next if p.directives.blank?
|
|
next unless Gitlab::CurrentSettings.diagramsnet_enabled?
|
|
|
|
default_frame_src = p.directives['frame-src'] || p.directives['default-src']
|
|
frame_src_values = Array.wrap(default_frame_src) | [Gitlab::CurrentSettings.diagramsnet_url].compact
|
|
|
|
p.frame_src(*frame_src_values)
|
|
end
|
|
|
|
before_action { respond_to :html }
|
|
|
|
before_action :authorize_read_wiki!
|
|
before_action :authorize_create_wiki!, only: [:edit, :create, :destroy]
|
|
|
|
before_action :wiki
|
|
before_action :page, only: [:show, :edit, :update, :history, :destroy, :diff]
|
|
before_action :load_sidebar, except: [:pages]
|
|
|
|
before_action do
|
|
push_frontend_feature_flag(:preserve_markdown, container)
|
|
push_force_frontend_feature_flag(:glql_integration, !!container&.glql_integration_feature_flag_enabled?)
|
|
push_force_frontend_feature_flag(:glql_load_on_click, !!container&.glql_load_on_click_feature_flag_enabled?)
|
|
push_force_frontend_feature_flag(:continue_indented_text,
|
|
!!container&.continue_indented_text_feature_flag_enabled?)
|
|
end
|
|
|
|
before_action only: [:show, :edit, :update] do
|
|
@valid_encoding = valid_encoding?
|
|
end
|
|
|
|
before_action only: [:edit, :update], unless: :valid_encoding? do
|
|
if params[:id].present?
|
|
redirect_to wiki_page_path(wiki, page || params[:id])
|
|
else
|
|
redirect_to wiki_path(wiki)
|
|
end
|
|
end
|
|
|
|
track_internal_event :show, name: 'view_wiki_page'
|
|
|
|
helper_method :view_file_button, :diff_file_html_data
|
|
|
|
rescue_from ::Gitlab::Git::CommandTimedOut do |exc|
|
|
raise exc unless RESCUE_GIT_TIMEOUTS_IN.include?(action_name)
|
|
|
|
render 'shared/wikis/git_error'
|
|
end
|
|
|
|
rescue_from Gitlab::Git::Repository::NoRepository do
|
|
@error = s_('Wiki|Could not access the Wiki Repository at this time.')
|
|
|
|
render 'shared/wikis/empty'
|
|
end
|
|
end
|
|
|
|
def new
|
|
redirect_to wiki_page_path(wiki, SecureRandom.uuid, random_title: true)
|
|
end
|
|
|
|
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
|
def pages
|
|
@wiki_entries = WikiDirectory.group_pages(pages_list)
|
|
|
|
render 'shared/wikis/pages'
|
|
end
|
|
|
|
def pages_list
|
|
strong_memoize(:pages_list) do
|
|
Kaminari.paginate_array(
|
|
# only include pages not starting with 'templates/'
|
|
wiki
|
|
.list_pages(direction: params[:direction])
|
|
.reject { |page| page.slug.start_with?('templates/') }
|
|
).page(pagination_params[:page])
|
|
end
|
|
end
|
|
|
|
def templates_list
|
|
strong_memoize(:templates_list) do
|
|
Kaminari.paginate_array(
|
|
# only include pages starting with 'templates/'
|
|
wiki
|
|
.list_pages(direction: params[:direction])
|
|
.select { |page| page.slug.start_with?('templates/') }
|
|
).page(pagination_params[:page])
|
|
end
|
|
end
|
|
|
|
def templates
|
|
@wiki_entries_count = templates_list.total_count
|
|
@wiki_entries = WikiDirectory.group_pages(templates_list, templates: true)
|
|
|
|
render 'shared/wikis/templates'
|
|
end
|
|
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
|
|
|
# `#show` handles a number of scenarios:
|
|
#
|
|
# - If `id` matches a WikiPage, then show the wiki page.
|
|
# - If `id` is a file in the wiki repository, then send the file.
|
|
# - If we know the user wants to create a new page with the given `id`,
|
|
# then display a create form.
|
|
# - Otherwise show the empty wiki page and invite the user to create a page.
|
|
#
|
|
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
|
def show
|
|
if page
|
|
set_encoding_error unless valid_encoding?
|
|
|
|
# Assign vars expected by MarkupHelper
|
|
@ref = params[:version_id]
|
|
@path = page.path
|
|
@templates = templates_list
|
|
|
|
render 'shared/wikis/show'
|
|
elsif file_blob
|
|
# This is needed by [GitLab JH](https://gitlab.com/gitlab-jh/gitlab/-/issues/247)
|
|
send_wiki_file_blob(wiki, file_blob)
|
|
else
|
|
handle_redirection
|
|
end
|
|
end
|
|
|
|
def handle_redirection
|
|
redir = find_redirection(params[:id]) unless params[:redirect_limit_reached] || params[:no_redirect]
|
|
if redir.is_a?(Hash) && redir[:error]
|
|
message = safe_format(
|
|
s_('Wiki|The page at %{code_start}%{redirected_from}%{code_end} redirected too many times. ' \
|
|
'You are now editing the page at %{code_start}%{redirected_from}%{code_end}.'),
|
|
tag_pair(helpers.content_tag(:code), :code_start, :code_end),
|
|
redirected_from: params[:id]
|
|
)
|
|
redirect_to(
|
|
"#{wiki_page_path(wiki, params[:id])}?redirect_limit_reached=true",
|
|
status: :found,
|
|
notice: message
|
|
)
|
|
elsif redir
|
|
redirected_from = params[:redirected_from] || params[:id]
|
|
message = safe_format(
|
|
s_('Wiki|The page at %{code_start}%{redirected_from}%{code_end} ' \
|
|
'has been moved to %{code_start}%{redirected_to}%{code_end}.'),
|
|
tag_pair(helpers.content_tag(:code), :code_start, :code_end),
|
|
redirected_from: redirected_from,
|
|
redirected_to: redir
|
|
)
|
|
redirect_to(
|
|
"#{wiki_page_path(wiki, redir)}?redirected_from=#{redirected_from}",
|
|
status: :found,
|
|
notice: message
|
|
)
|
|
elsif show_create_form?
|
|
handle_create_form
|
|
elsif wiki.exists?
|
|
render 'shared/wikis/404', status: :not_found
|
|
else
|
|
render 'shared/wikis/empty'
|
|
end
|
|
end
|
|
|
|
def handle_create_form
|
|
title = params[:id]
|
|
if params[:redirected_from] # override the notice if redirected
|
|
redirected_link = helpers.link_to('', "#{wiki_page_path(wiki, params[:redirected_from])}?no_redirect=true")
|
|
flash[:notice] = safe_format(
|
|
s_('Wiki|The page at %{code_start}%{redirected_from}%{code_end} tried to redirect to ' \
|
|
'%{code_start}%{redirected_to}%{code_end}, but it does not exist. You are now ' \
|
|
'editing the page at %{code_start}%{redirected_to}%{code_end}. %{link_start}Edit ' \
|
|
'page at %{code_start}%{redirected_from}%{code_end} instead.%{link_end}'
|
|
),
|
|
tag_pair(helpers.content_tag(:code), :code_start, :code_end),
|
|
tag_pair(redirected_link, :link_start, :link_end),
|
|
redirected_from: params[:redirected_from],
|
|
redirected_to: params[:id]
|
|
)
|
|
end
|
|
|
|
@page = build_page(title: title)
|
|
@templates = templates_list
|
|
|
|
render 'shared/wikis/edit'
|
|
|
|
flash[:notice] = nil if params[:redirected_from]
|
|
end
|
|
|
|
def raw
|
|
response.headers['Content-Type'] = 'text/plain'
|
|
render plain: page.raw_content
|
|
end
|
|
|
|
def edit
|
|
@templates = templates_list
|
|
|
|
render 'shared/wikis/edit'
|
|
end
|
|
|
|
def update
|
|
return render('shared/wikis/empty') unless can?(current_user, :create_wiki, container)
|
|
|
|
response = WikiPages::UpdateService.new(
|
|
container: container,
|
|
current_user: current_user,
|
|
params: wiki_params
|
|
).execute(page)
|
|
@page = response.payload[:page]
|
|
|
|
if response.success?
|
|
handle_action_success :updated, @page
|
|
else
|
|
@error = response.message
|
|
@templates = templates_list
|
|
|
|
render 'shared/wikis/edit'
|
|
end
|
|
end
|
|
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
|
|
|
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
|
def create
|
|
response = WikiPages::CreateService.new(container: container, current_user: current_user,
|
|
params: wiki_params).execute
|
|
@page = response.payload[:page]
|
|
|
|
if response.success?
|
|
handle_action_success :created, @page
|
|
else
|
|
@templates = templates_list
|
|
|
|
render 'shared/wikis/edit'
|
|
end
|
|
end
|
|
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
|
|
|
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
|
def history
|
|
if page
|
|
@commits_count = page.count_versions
|
|
@commits = Kaminari.paginate_array(page.versions(page: pagination_params[:page].to_i),
|
|
total_count: page.count_versions)
|
|
.page(pagination_params[:page])
|
|
|
|
render 'shared/wikis/history'
|
|
else
|
|
redirect_to(
|
|
wiki_path(wiki),
|
|
notice: _("Page not found")
|
|
)
|
|
end
|
|
end
|
|
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
|
|
|
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
|
def diff
|
|
return render_404 unless page
|
|
|
|
apply_diff_view_cookie!
|
|
|
|
@diffs = page.diffs(diff_options)
|
|
@diff_notes_disabled = true
|
|
|
|
render 'shared/wikis/diff'
|
|
end
|
|
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
|
|
|
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
|
def destroy
|
|
return render_404 unless page
|
|
|
|
response = WikiPages::DestroyService.new(container: container, current_user: current_user).execute(page)
|
|
|
|
if response.success?
|
|
flash[:toast] = _("Wiki page was successfully deleted.")
|
|
|
|
redirect_to wiki_path(wiki), status: :found
|
|
else
|
|
@error = response.message
|
|
@templates = templates_list
|
|
|
|
render 'shared/wikis/edit'
|
|
end
|
|
end
|
|
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
|
|
|
def git_access
|
|
render 'shared/wikis/git_access'
|
|
end
|
|
|
|
private
|
|
|
|
def handle_action_success(action, page)
|
|
if page.title == Wiki::SIDEBAR
|
|
flash[:toast] = s_('Wiki|Sidebar was successfully created.') if action == :created
|
|
flash[:toast] = s_('Wiki|Sidebar was successfully updated.') if action == :updated
|
|
|
|
redirect_to wiki_path(wiki)
|
|
else
|
|
flash[:toast] = s_('Wiki|Wiki page was successfully created.') if action == :created
|
|
flash[:toast] = s_('Wiki|Wiki page was successfully updated.') if action == :updated
|
|
|
|
redirect_to(wiki_page_path(wiki, page))
|
|
end
|
|
end
|
|
|
|
def container
|
|
raise NotImplementedError
|
|
end
|
|
|
|
def show_create_form?
|
|
can?(current_user, :create_wiki, container) &&
|
|
page.nil? &&
|
|
# Always show the create form when the wiki has had at least one page created.
|
|
# Otherwise, we only show the form when the user has navigated from
|
|
# the 'empty wiki' page
|
|
(wiki.exists? || params[:view] == 'create')
|
|
end
|
|
|
|
def wiki
|
|
strong_memoize(:wiki) do
|
|
wiki = Wiki.for_container(container, current_user)
|
|
wiki.create_wiki_repository
|
|
|
|
wiki
|
|
end
|
|
rescue Wiki::CouldNotCreateWikiError
|
|
flash[:notice] = _("Could not create Wiki Repository at this time. Please try again later.")
|
|
redirect_to container
|
|
false
|
|
end
|
|
|
|
def page
|
|
strong_memoize(:page) do
|
|
wiki.find_page(*page_params, load_content: load_content?)
|
|
end
|
|
end
|
|
|
|
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
|
def load_sidebar
|
|
@sidebar_page = wiki.find_sidebar(params[:version_id])
|
|
@wiki_pages_count = pages_list.total_count
|
|
end
|
|
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
|
|
|
def wiki_params
|
|
params.require(:wiki).permit(:title, :content, :format, :message, :last_commit_sha)
|
|
end
|
|
|
|
def build_page(args = {})
|
|
WikiPage.new(wiki).tap do |page|
|
|
page.update_attributes(args) # rubocop:disable Rails/ActiveRecordAliases
|
|
end
|
|
end
|
|
|
|
def page_params
|
|
keys = [:id]
|
|
keys << :version_id if %w[show diff].include?(params[:action])
|
|
|
|
params.values_at(*keys)
|
|
end
|
|
|
|
def valid_encoding?
|
|
page_encoding == Encoding::UTF_8
|
|
end
|
|
|
|
def page_encoding
|
|
strong_memoize(:page_encoding) { page&.content&.encoding }
|
|
end
|
|
|
|
def set_encoding_error
|
|
flash.now[:notice] =
|
|
_("The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.")
|
|
end
|
|
|
|
def file_blob
|
|
strong_memoize(:file_blob) do
|
|
commit = wiki.repository.commit(wiki.default_branch)
|
|
|
|
next unless commit
|
|
|
|
wiki.repository.blob_at(commit.id, params[:id])
|
|
end
|
|
end
|
|
|
|
# Override CommitsHelper#view_file_button
|
|
def view_file_button(commit_sha, *args)
|
|
path = wiki_page_path(wiki, page, version_id: page.version.id)
|
|
|
|
helpers.link_button_to(path) do
|
|
helpers.raw(_('View page @ ')) + helpers.content_tag(:span, Commit.truncate_sha(commit_sha), class: 'commit-sha')
|
|
end
|
|
end
|
|
|
|
# Override DiffHelper#diff_file_html_data
|
|
def diff_file_html_data(_project, _diff_file_path, diff_commit_id)
|
|
{
|
|
blob_diff_path: wiki_page_path(wiki, page, action: :diff, version_id: diff_commit_id),
|
|
view: diff_view
|
|
}
|
|
end
|
|
|
|
def send_wiki_file_blob(wiki, file_blob)
|
|
send_blob(wiki.repository, file_blob)
|
|
end
|
|
|
|
def load_content?
|
|
skip_actions = %w[history destroy diff]
|
|
|
|
return false if skip_actions.include?(params[:action])
|
|
|
|
true
|
|
end
|
|
|
|
def tracking_project_source
|
|
container if container.is_a?(Project)
|
|
end
|
|
|
|
def tracking_namespace_source
|
|
case container
|
|
when Project
|
|
container.namespace
|
|
when Group
|
|
container
|
|
end
|
|
end
|
|
|
|
def find_redirection(path, redirect_limit = 50)
|
|
seen = Set[]
|
|
current_path = path
|
|
|
|
redirect_limit.times do
|
|
seen << current_path
|
|
next_path = find_single_redirection(current_path)
|
|
|
|
# if no single redirect is found, then use the current path
|
|
# unless it is the same as the original path
|
|
return current_path == path ? nil : current_path if next_path.nil?
|
|
|
|
# if the file was already seen, then we have a loop
|
|
return { error: true, reason: :loop } if seen.include?(next_path)
|
|
|
|
current_path = next_path
|
|
end
|
|
|
|
{ error: true, reason: :limit }
|
|
end
|
|
|
|
def find_single_redirection(path)
|
|
current = path
|
|
rest = []
|
|
|
|
until current == '.'
|
|
redirect = redirections[current]
|
|
return File.join(redirect, *rest) if redirect
|
|
|
|
current, basename = File.split(current)
|
|
rest.unshift(basename)
|
|
end
|
|
|
|
nil
|
|
end
|
|
|
|
def redirections
|
|
strong_memoize(:redirections) do
|
|
redirects_file = wiki.repository.blob_at(wiki.default_branch, Wiki::REDIRECTS_YML, limit: 0.5.megabytes)
|
|
redirects_file ? YAML.safe_load(redirects_file.data).to_h : {}
|
|
end
|
|
end
|
|
end
|
|
|
|
WikiActions.prepend_mod
|