Replace raw and explicit html_safe calls

This commit is contained in:
Oliver Günther
2026-02-18 07:47:11 +01:00
parent 00317e7197
commit 4d731dcab6
111 changed files with 607 additions and 513 deletions
@@ -34,7 +34,10 @@ See COPYRIGHT and LICENSE files for more details.
<% else %>
<%= @event.event_title %>
<% end %>
<%= project_suffix %>
<% suffix = project_suffix %>
<% if suffix.present? %>
(<%= suffix %>)
<% end %>
</div>
<%=
render(
+1 -2
View File
@@ -45,8 +45,7 @@ class Activities::ItemComponent < ViewComponent::Base
return if activity_is_from_current_project?
kind = activity_is_from_subproject? ? "subproject" : "project"
suffix = I18n.t("events.title.#{kind}", name: link_to(@event.project.name, @event.project))
"(#{suffix})".html_safe # rubocop:disable Rails/OutputSafety
helpers.t("events.title.#{kind}_html", name: link_to(@event.project.name, @event.project))
end
def display_user?
@@ -29,10 +29,11 @@ See COPYRIGHT and LICENSE files for more details.
<div class="op-activity-list--item-subtitle">
<%=
# OG: html_safe usage has been double-checked and would otherwise require a lot of i18n key change
I18n.t(
i18n_key,
user: user_html,
datetime: datetime_html
).html_safe # OG: html_safe usage has been double-checked and would otherwise require a lot of i18n key change
user: h(user_html),
datetime: h(datetime_html)
).html_safe # rubocop:disable Rails/OutputSafety
%>
</div>
@@ -48,7 +48,7 @@ module EnterpriseEdition
def description
@description || begin
if I18n.exists?(:description_html, scope: i18n_scope)
I18n.t(:description_html, scope: i18n_scope).html_safe
helpers.t(:description_html, scope: i18n_scope)
else
I18n.t(:description, scope: i18n_scope)
end
@@ -85,7 +85,7 @@ module EnterpriseEdition
I18n.t("ee.upsell.plan_name", plan: plan.capitalize)
end
I18n.t("ee.upsell.plan_text_html", plan_name:).html_safe
helpers.t("ee.upsell.plan_text_html", plan_name:)
end
end
end
@@ -65,7 +65,7 @@ module EnterpriseEdition
end
def description
I18n.t("ee.teaser.description", trial_plan: plan_name).html_safe
helpers.t("ee.teaser.description_html", trial_plan: plan_name)
end
def plan_name
@@ -28,7 +28,11 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<%=
render(Primer::OpenProject::PageHeader.new) do |header|
header.with_title { "#{avatar @placeholder_user} #{h(@placeholder_user.name)}".html_safe }
header.with_title do
concat avatar(@placeholder_user)
concat @placeholder_user.name
end
header.with_breadcrumbs(breadcrumb_items)
if @current_user.allowed_globally?(:manage_placeholder_user)
@@ -27,9 +27,10 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<tr
<%= "id=\"#{row_css_id}\"".html_safe if row_css_id %>
<%= "class=\"#{row_css_class}\"".html_safe if row_css_class %>>
<%= content_tag(:tr,
id: row_css_id,
class: row_css_class
) do %>
<% columns.each do |column| %>
<td class="<%= column_css_class(column) %>">
<%= column_value(column) %>
@@ -40,4 +41,4 @@ See COPYRIGHT and LICENSE files for more details.
<%= link %>
<% end %>
</td>
</tr>
<% end %>
+11 -13
View File
@@ -211,17 +211,15 @@ module Projects
def project_status
return nil unless user_can_view_project_attributes?
content = "".html_safe
status_code = project.status_code
if status_code
classes = helpers.project_status_css_class(status_code)
content << content_tag(:span, "", class: "project-status--bulb -inline #{classes}")
content << content_tag(:span, helpers.project_status_name(status_code), class: "project-status--name #{classes}")
end
content
capture do
concat content_tag(:span, "", class: "project-status--bulb -inline #{classes}")
concat content_tag(:span, helpers.project_status_name(status_code), class: "project-status--name #{classes}")
end
end
end
def status_explanation
@@ -250,7 +248,7 @@ module Projects
def row_css_class
classes = %w[basics context-menu--reveal op-project-row-component]
classes << project_css_classes
classes += project_css_classes
classes << row_css_level_classes
classes.join(" ")
@@ -269,13 +267,13 @@ module Projects
end
def project_css_classes
s = " project ".html_safe
output = ["project"]
s << " root" if project.root?
s << " child" if project.child?
s << (project.leaf? ? " leaf" : " parent")
output << "root" if project.root?
output << "child" if project.child?
output << (project.leaf? ? "leaf" : "parent")
s
output
end
def column_css_class(column)
@@ -31,7 +31,10 @@ See COPYRIGHT and LICENSE files for more details.
<div class="generic-table--flex-container">
<div class="generic-table--container <%= container_class %>">
<div class="generic-table--results-container">
<table class="generic-table" data-controller="table-highlighting" <%= table_id ? "id=\"#{table_id}\"".html_safe : "" %>>
<%= content_tag :table,
id: table_id,
class: "generic-table",
data: { controller: "table-highlighting" } do %>
<colgroup>
<% columns.each do |column| %>
<col <%= "opHighlightCol" unless column.attribute == :hierarchy %>>
@@ -84,7 +87,7 @@ See COPYRIGHT and LICENSE files for more details.
<% end %>
<%= render_collection rows %>
</tbody>
</table>
<% end %>
<% if inline_create_link && show_inline_create %>
<div class="wp-inline-create-button">
<%= inline_create_link %>
+5 -4
View File
@@ -27,9 +27,10 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<tr
<%= "id=\"#{row_css_id}\"".html_safe if row_css_id %>
<%= "class=\"#{row_css_class}\"".html_safe if row_css_class %>>
<%= content_tag(:tr,
id: row_css_id,
class: row_css_class
) do %>
<% columns.each do |column| %>
<td class="<%= column_css_class(column) %>">
<%= column_value(column) %>
@@ -40,4 +41,4 @@ See COPYRIGHT and LICENSE files for more details.
<%= link %>
<% end %>
</td>
</tr>
<% end %>
@@ -104,7 +104,7 @@ module Settings
private
def internal_comments_translation = t("ee.features.internal_comments").html_safe
def internal_comments_translation = t("ee.features.internal_comments")
end
end
end
@@ -39,11 +39,11 @@
user_limit_row.with_column do
render(Primer::Beta::Text.new(color: :danger)) do
I18n.t(
helpers.link_translate(
"sharing.warning_user_limit_reached#{'_admin' if User.current.admin?}",
upgrade_url: OpenProject::Enterprise.upgrade_url,
entity: entity.model_name.human
).html_safe
i18n_args: { entity: entity.model_name.human },
links: { upgrade_url: OpenProject::Enterprise.upgrade_url }
)
end
end
end
+2 -2
View File
@@ -90,9 +90,9 @@ class Users::HoverCardComponent < ApplicationComponent
remaining_count_link = link_to(t("users.groups.more", count: remaining_count), user_path(@user))
if remaining_count > 0
t("users.groups.summary_with_more", names: summary_links, count_link: remaining_count_link).html_safe
t("users.groups.summary_with_more_html", names: summary_links, count_link: remaining_count_link)
else
t("users.groups.summary", names: summary_links).html_safe
t("users.groups.summary_html", names: summary_links)
end
end
end
@@ -29,7 +29,8 @@ See COPYRIGHT and LICENSE files for more details.
<%=
render(Primer::OpenProject::PageHeader.new) do |header|
header.with_title do
"#{avatar(@user, hover_card: { active: false })} #{h(@user.name)}".html_safe
concat avatar(@user, hover_card: { active: false })
concat @user.name
end
header.with_breadcrumbs(breadcrumb_items)
@@ -42,13 +42,13 @@
target: "_blank"
)
) do
I18n.t("js.label_committed_link", revision_identifier: short_revision)
I18n.t(:label_committed_link, revision_identifier: short_revision)
end
I18n.t(
"js.label_committed_at",
committed_revision_link: committed_text.html_safe,
:label_committed_at_html,
committed_revision_link: committed_text,
date: format_time(changeset.committed_on)
).html_safe
)
end
end
end
@@ -69,13 +69,13 @@
target: "_blank"
)
) do
I18n.t("js.label_committed_link", revision_identifier: short_revision)
I18n.t(:label_committed_link, revision_identifier: short_revision)
end
I18n.t(
"js.label_committed_at",
committed_revision_link: committed_text.html_safe,
t(
:label_committed_at_html,
committed_revision_link: committed_text,
date: format_time(changeset.committed_on)
).html_safe
)
end
end
end
@@ -96,15 +96,17 @@ module WorkPackages
end
def mobile_description
text = if @manually_scheduled
I18n.t("work_packages.datepicker_modal.banner.description.manual_mobile")
else
I18n.t("work_packages.datepicker_modal.banner.description.automatic_mobile")
end
text =
if @manually_scheduled
I18n.t("work_packages.datepicker_modal.banner.description.manual_mobile")
else
I18n.t("work_packages.datepicker_modal.banner.description.automatic_mobile")
end
"#{text} #{render(Primer::Beta::Link.new(tag: :a, href: link, target: '_blank', underline: true)) do
I18n.t('work_packages.datepicker_modal.show_relations')
end}".html_safe
capture do
concat text
concat render(Primer::Beta::Link.new(tag: :a, href: link, target: "_blank", underline: true)) { I18n.t("work_packages.datepicker_modal.show_relations") }
end
end
def overlapping_predecessor?
@@ -122,7 +124,7 @@ module WorkPackages
predecessor_work_packages.filter_map(&:due_date)
.max
&.before?(@work_package.start_date - 2)
&.before?(@work_package.start_date - 2)
end
def predecessor_relations
@@ -131,8 +133,8 @@ module WorkPackages
def predecessor_work_packages
@predecessor_work_packages ||= predecessor_relations
.includes(:to)
.map(&:to)
.includes(:to)
.map(&:to)
end
def children
@@ -49,8 +49,13 @@ module WorkPackages
end
def gantt_chart_label
label = I18n.t("export.dialog.pdf.export_type.options.gantt.label")
gantt_chart_allowed? ? label : (label + enterprise_icon).html_safe # rubocop:disable Rails/OutputSafety
capture do
concat I18n.t("export.dialog.pdf.export_type.options.gantt.label")
unless gantt_chart_allowed?
concat enterprise_icon
end
end
end
def pdf_export_types
+1 -1
View File
@@ -283,7 +283,7 @@ class AccountController < ApplicationController
ldap_auth_source_id: user.ldap_auth_source_id
}
flash[:notice] = I18n.t("account.auth_source_login", login: user.login).html_safe
flash[:notice] = helpers.t("account.auth_source_login_html", login: user.login)
redirect_to signin_path(username: user.login)
end
@@ -91,18 +91,16 @@ module Accounts::UserLimits
end
def user_limit_warning
warning = if current_user.admin?
I18n.t(
:warning_user_limit_reached_admin,
upgrade_url: OpenProject::Enterprise.upgrade_url
)
else
I18n.t(
:warning_user_limit_reached
)
end
warning.html_safe
if current_user.admin?
link_translate(
:warning_user_limit_reached_admin,
links: { upgrade_url: OpenProject::Enterprise.upgrade_url }
)
else
I18n.t(
:warning_user_limit_reached
)
end
end
def show_imminent_user_limit_warning!(flash_now: false)
@@ -115,12 +113,10 @@ module Accounts::UserLimits
# A warning for when the user limit has technically not been reached yet
# but if all invited users were to activate their accounts it would be reached.
def imminent_user_limit_warning
warning = I18n.t(
link_translate(
:warning_imminent_user_limit,
upgrade_url: OpenProject::Enterprise.upgrade_url
links: { upgrade_url: OpenProject::Enterprise.upgrade_url }
)
warning.html_safe
end
def user_limit_reached?
@@ -53,8 +53,8 @@ class WorkPackages::RemindersController < ApplicationController
.call(reminder_params)
if service_result.success?
message = I18n.t("work_package.reminders.create_success_message",
reminder_time: reminder_chosen_time(service_result.result)).html_safe
message = helpers.t("work_package.reminders.create_success_message_html",
reminder_time: reminder_chosen_time(service_result.result))
respond_with_success_flash_message(message:)
else
respond_with_error_modal_component(service_result)
@@ -9,9 +9,9 @@
<% end %>
<%= render(Primer::Beta::Text.new(tag: :p, mb: 0)) do %>
<%=
I18n.t(
helpers.link_translate(
:setting_apiv3_write_readonly_attributes_additional,
api_documentation_link: static_link_to(:api_docs)
).html_safe
links: { api_documentation_link: %i[api_docs] }
)
%>
<% end %>
@@ -3,8 +3,8 @@
<% end %>
<%= render(Primer::Beta::Text.new(tag: :p)) do %>
<%= I18n.t(
<%= helpers.link_translate(
:setting_apiv3_cors_origins_text_html,
origin_link: ::OpenProject::Static::Links.url_for(:origin_mdn_documentation)
).html_safe %>
links: { docs_url: %i[origin_mdn_documentation] }
) %>
<% end %>
@@ -1,10 +1,10 @@
<%=
I18n.t(
helpers.t(
:setting_allowed_link_protocols_text_html,
tel_code: content_tag(:code, "tel"),
element_code: content_tag(:code, "element"),
http_code: content_tag(:code, "http"),
https_code: content_tag(:code, "https"),
mailto_code: content_tag(:code, "mailto")
).html_safe
)
%>
@@ -1,8 +1,8 @@
<%=
I18n.t(
helpers.t(
:text_notice_security_badge_displayed_html,
information_panel_label: I18n.t(:label_information),
more_info_url: ::OpenProject::Static::Links.url_for(:security_badge_documentation),
information_panel_path: url_helpers.info_admin_index_path
).html_safe
)
%>
+2
View File
@@ -37,6 +37,8 @@ class ApplicationForm < Primer::Forms::Base
end
end
delegate :helpers, to: :ApplicationController
def url_helpers
Rails.application.routes.url_helpers
end
+9 -2
View File
@@ -67,13 +67,20 @@ module EnterpriseTrials
f.check_box(
required: true,
label: I18n.t("ee.trial.consent_html").html_safe,
label: helpers.link_translate("ee.trial.consent",
links: {
tos_url: %i[terms_of_service],
privacy_url: %i[data_privacy]
}),
name: :general_consent
)
f.check_box(
required: false,
label: I18n.t("ee.trial.receive_newsletter_html").html_safe,
label: helpers.link_translate("ee.trial.receive_newsletter",
links: {
newsletter_url: %i[newsletter]
}),
name: :newsletter_consent
)
end
+2 -2
View File
@@ -84,7 +84,7 @@ class My::LookAndFeelForm < ApplicationForm
private
def disable_keyboard_shortcuts_caption
attribute_name(:disable_keyboard_shortcuts_caption_html,
href: OpenProject::Static::Links.url_for(:shortcuts)).html_safe # rubocop:disable Rails/OutputSafety
helpers.link_translate(:"user_preferences.disable_keyboard_shortcuts_caption",
links: { docs_url: %i[shortcuts] })
end
end
@@ -77,7 +77,7 @@ module Projects
f.autocompleter(
name: :project_creation_wizard_assignee_custom_field_id,
label: I18n.t("settings.project_initiation_request.submission.assignee"),
caption: I18n.t("settings.project_initiation_request.submission.assignee_caption_html").html_safe,
caption: helpers.t("settings.project_initiation_request.submission.assignee_caption_html"),
required: false,
input_width: :large,
autocomplete_options: {
@@ -51,16 +51,8 @@ module Projects::Settings::WorkPackages::Activities
private
def caption_text
link = render(
Primer::Beta::Link.new(
href: OpenProject::Static::Links.url_for(:enterprise_features, :internal_comments),
underline: true
)
) do
I18n.t("label_learn_more")
end
I18n.t("settings.work_packages.activities.helper_text", link:).html_safe
helpers.link_translate("settings.work_packages.activities.helper_text",
links: { docs_url: %i[enterprise_features internal_comments] })
end
end
end
+1 -1
View File
@@ -59,7 +59,7 @@ module ScimClients
client_form.select_list(
name: :authentication_method,
label: ScimClient.human_attribute_name(:authentication_method),
caption: I18n.t("admin.scim_clients.form.authentication_method_description_html").html_safe,
caption: helpers.t("admin.scim_clients.form.authentication_method_description_html"),
input_width: :large,
include_blank: false,
disabled: model.persisted?,
@@ -91,13 +91,13 @@ module Settings
f.text_field(
name: :after_first_login_redirect_url,
caption: I18n.t(:setting_after_first_login_redirect_url_text_html).html_safe,
caption: helpers.t(:setting_after_first_login_redirect_url_text_html),
input_width: :large
)
f.text_field(
name: :after_login_default_redirect_url,
caption: I18n.t(:setting_after_login_default_redirect_url_text_html).html_safe,
caption: helpers.t(:setting_after_login_default_redirect_url_text_html),
input_width: :large
)
+2 -2
View File
@@ -56,8 +56,8 @@ module Settings
# @param names [Array<Symbol>] The name(s) of the setting
# @return [String] The translated HTML-safe caption
def setting_caption(*names)
I18n.t("setting_#{names.join('_')}_caption_html", default: nil)&.html_safe \
|| I18n.t("setting_#{names.join('_')}_caption", default: nil)
ApplicationController.helpers.t("setting_#{names.join('_')}_caption_html", default: nil) ||
I18n.t("setting_#{names.join('_')}_caption", default: nil)
end
# Retrieves the current value of a setting
+3 -3
View File
@@ -79,7 +79,7 @@ module Statuses
label: attribute_name(:is_readonly),
name: :is_readonly,
disabled: readonly_disabled?,
caption: I18n.t("statuses.edit.status_readonly_html").html_safe,
caption: helpers.t("statuses.edit.status_readonly_html"),
data: {
"admin--statuses-target": "isReadonlyCheckbox",
restricted: readonly_work_packages_restricted?
@@ -115,8 +115,8 @@ module Statuses
end
def percent_complete_field_caption
I18n.t("statuses.edit.status_percent_complete_text",
href: url_helpers.admin_settings_progress_tracking_path).html_safe
helpers.link_translate("statuses.edit.status_percent_complete_text",
links: { setting_url: url_helpers.admin_settings_progress_tracking_path })
end
def already_default_status?
+7 -8
View File
@@ -30,17 +30,16 @@
module AccessibilityHelper
def you_are_here_info(condition = true, disabled = nil)
if condition && !disabled
"<span style = 'display: block' class = 'position-label sr-only'>#{I18n.t(:description_current_position)}</span>".html_safe
elsif condition && disabled
"<span style = 'display: none' class = 'position-label sr-only'>#{I18n.t(:description_current_position)}</span>".html_safe
else
""
end
return "" unless condition
content_tag(:span,
I18n.t(:description_current_position),
style: disabled ? "display: none" : "display: block",
class: "position-label sr-only")
end
def empty_element_tag
@empty_element_tag ||= ApplicationController.new.render_to_string(partial: "accessibility/empty_element_tag").html_safe
@empty_element_tag ||= ApplicationController.new.render_to_string(partial: "accessibility/empty_element_tag")
end
# Returns the locale :en for the given menu item if the user locale is
+26 -7
View File
@@ -130,8 +130,8 @@ module ApplicationHelper
class: :user_status_class)
end
def labeled_check_box_tags(name, collection, options = {})
collection.sort.map do |object|
def labeled_check_box_tags(name, collection, options = {}) # rubocop:disable Metrics/AbcSize
fields = collection.sort.map do |object|
id = name.gsub(/[\[\]]+/, "_") + object.id.to_s
object_options = options.inject({}) do |h, (k, v)|
@@ -146,18 +146,30 @@ module ApplicationHelper
styled_check_box_tag(name, object.id, false, id:) + object.to_s
end
end
end.join.html_safe
end
safe_join(fields)
end
def authoring(created, author, options = {})
label = options[:label] || :label_added_time_by
I18n.t(label, author: link_to_user(author), age: time_tag(created)).html_safe
# Ensure we pass inputs here to html_escape
# which will respect html_safe?
author = ERB::Util.html_escape link_to_user(author)
age = ERB::Util.html_escape time_tag(created)
# OG: html_safe is used here with explicitly escaped inputs except for the translation file
I18n.t(label, author:, age:).html_safe
end
def authoring_at(creation_date, author)
return if author.nil?
I18n.t(:label_added_by_on, author: link_to_user(author), date: creation_date).html_safe
author = ERB::Util.html_escape link_to_user(author)
date = ERB::Util.html_escape creation_date
# OG: html_safe is used here to avoid having to change this reusable key
I18n.t(:label_added_by_on, author:, date:).html_safe
end
def time_tag(time)
@@ -196,7 +208,8 @@ module ApplicationHelper
formats = capture(Redmine::Views::OtherFormatsBuilder.new(self), &)
unless formats.nil? || formats.strip.empty?
content_tag "p", class: "other-formats" do
(I18n.t(:label_export_to) + formats).html_safe
concat I18n.t(:label_export_to)
concat formats
end
end
end
@@ -251,6 +264,12 @@ module ApplicationHelper
.sort_by(&:first)
end
def blank_select_option
content_tag(:option,
"--- #{t(:actionview_instancetag_blank_option)} ---",
disabled: true)
end
def theme_options_for_select
[
[I18n.t("themes.light"), "light"],
@@ -424,7 +443,7 @@ module ApplicationHelper
# @param [optional, String] content the content of the ROBOTS tag.
# defaults to no index, follow, and no archive
def robot_exclusion_tag(content = "NOINDEX,FOLLOW,NOARCHIVE")
"<meta name='ROBOTS' content='#{h(content)}' />".html_safe
content_tag(:meta, name: "ROBOTS", content:)
end
def permitted_params
+4 -5
View File
@@ -30,10 +30,9 @@
module BreadcrumbHelper
def nested_breadcrumb_element(section_header, title)
output = "".html_safe
output << "#{section_header}: "
output << content_tag(:b, title)
output
capture do
concat "#{section_header}: "
concat content_tag(:b, title)
end
end
end
+2 -2
View File
@@ -63,7 +63,7 @@ module ErrorMessageHelper
list_of_messages(base_error_messages),
text_header_invalid_fields(base_error_messages, fields_error_messages),
list_of_messages(fields_error_messages)
].compact, "<br/>".html_safe)
].compact, tag(:br))
end
def text_header_invalid_fields(base_error_messages, fields_error_messages)
@@ -77,6 +77,6 @@ module ErrorMessageHelper
def list_of_messages(messages)
return if messages.blank?
safe_join(messages, "<br/>".html_safe)
safe_join(messages, tag(:br))
end
end
@@ -32,17 +32,15 @@
module FlashMessagesOutputSafetyHelper
extend ActiveSupport::Concern
included do
# For .safe_join in join_flash_messages
include ActionView::Helpers::OutputSafetyHelper
end
###
# Joins individual flash messages with a Line Break Element.
#
# @param [String|Array<String>] messages the flash messages to join.
# @return [String] the joined messages as an HTML-safe string.
def join_flash_messages(messages)
safe_join(Array(messages), "<br />".html_safe)
ApplicationController.helpers.safe_join(
Array(messages),
ApplicationController.helpers.tag(:br)
)
end
end
+4 -1
View File
@@ -57,7 +57,10 @@ module HookHelper
default_context = { project: @project, hook_caller: self }
default_context[:controller] = controller if respond_to?(:controller)
default_context[:request] = request if respond_to?(:request)
OpenProject::Hook.call_hook(hook, default_context.merge(context)).join(" ").html_safe
ApplicationController.helpers.safe_join(
OpenProject::Hook.call_hook(hook, default_context.merge(context)),
" "
)
end
end
end
+2 -3
View File
@@ -30,12 +30,11 @@
module IconsHelper
##
# Create an <i> tag with the given icon class names
# Create a <i> tag with the given icon class names
# and make it aria-hidden since screenreaders otherwise
# output the css `content` of the icon.
def op_icon(classnames, title: nil)
title = "title=\"#{h(title)}\"" unless title.nil?
%(<i class="#{classnames}" #{title} aria-hidden="true"></i>).html_safe
content_tag(:i, nil, class: classnames, title:, "aria-hidden": true)
end
##
+1 -1
View File
@@ -37,7 +37,7 @@ module OAuthHelper
if strings.empty?
I18n.t("oauth.scopes.api_v3")
else
safe_join(strings.map { |scope| I18n.t("oauth.scopes.#{scope}", default: scope) }, "</br>".html_safe)
safe_join(strings.map { |scope| I18n.t("oauth.scopes.#{scope}", default: scope) }, tag(:br))
end
end
+1 -1
View File
@@ -121,7 +121,7 @@ module PaginationHelper
def pagination_options_section(paginator, params:, allowed_params:)
per_page_options = Setting.per_page_options_array
return "".html_safe if per_page_options.empty?
return "" if per_page_options.empty?
allowed_params ||= %w[filters sortBy]
content_tag(:div, class: "op-pagination--options") do
+7 -4
View File
@@ -69,10 +69,13 @@ module PasswordHelper
def render_password_complexity_hint
rules = password_rules_description
s = OpenProject::Passwords::Evaluator.min_length_description
s += "<br> #{rules}" if rules.present?
s.html_safe
capture do
concat OpenProject::Passwords::Evaluator.min_length_description
if rules.present?
concat tag(:br)
concat rules
end
end
end
private
+44 -53
View File
@@ -47,16 +47,6 @@ module RepositoriesHelper
text&.gsub(%r{^(.{#{length}}[^\n]*)\n.+$}m, '\\1...')
end
def render_properties(properties)
unless properties.nil? || properties.empty?
content = +""
properties.keys.sort.each do |property|
content << content_tag("li", raw("<b>#{h property}</b>: <span>#{h properties[property]}</span>"))
end
content_tag("ul", content.html_safe, class: "properties")
end
end
def render_changeset_changes
changes = @changeset.file_changes.limit(1000).order(Arel.sql("path")).filter_map do |change|
case change.action
@@ -113,58 +103,59 @@ module RepositoriesHelper
seen.size == 1 ? seen.first : :open
end
def render_changes_tree(tree)
return "".html_safe if tree.nil?
# rubocop:disable Rails/HelperInstanceVariable
def render_changes_tree(tree) # rubocop:disable Metrics/AbcSize,Metrics/PerceivedComplexity
return "" if tree.nil?
items = tree.keys.sort.flat_map do |file|
style = "change"
items = tree.keys.sort.filter_map do |file|
text = File.basename(file)
if (s = tree[file][:s])
style += " folder"
path_param = without_leading_slash(to_path_param(@repository.relative_path(file)))
text = link_to(h(text),
link = link_to(text,
show_revisions_path_project_repository_path(project_id: @project,
repo_path: path_param,
rev: @changeset.identifier),
title: I18n.t(:label_folder))
folder_li = content_tag(:li, text,
class: "#{style} icon icon-folder-#{calculate_folder_action(s)}")
[folder_li, render_changes_tree(s)]
content_tag(:li, safe_join([link, render_changes_tree(s)]),
class: "change folder icon icon-folder-#{calculate_folder_action(s)}")
elsif (c = tree[file][:c])
style += " change-#{c.action}"
path_param = without_leading_slash(to_path_param(@repository.relative_path(c.path)))
parts = []
text_parts = []
parts <<
if c.action == "D"
text
else
link_to(text,
entry_revision_project_repository_path(project_id: @project,
repo_path: path_param,
rev: @changeset.identifier),
title: changes_tree_change_title(c.action))
end
unless c.action == "D"
text = link_to(h(text),
entry_revision_project_repository_path(project_id: @project,
repo_path: path_param,
rev: @changeset.identifier),
title: changes_tree_change_title(c.action))
end
text_parts << text
text_parts << " - " << h(c.revision) if c.revision.present?
parts << safe_join([" - ", c.revision]) if c.revision.present?
if c.action == "M"
text_parts << " (" << link_to(I18n.t(:label_diff),
diff_revision_project_repository_path(project_id: @project,
repo_path: path_param,
rev: @changeset.identifier)) << ") "
diff_link = link_to(I18n.t(:label_diff),
diff_revision_project_repository_path(project_id: @project,
repo_path: path_param,
rev: @changeset.identifier))
parts << safe_join([" (", diff_link, ") "])
end
text_parts << " " << content_tag(:span, c.from_path, class: "copied-from") if c.from_path.present?
parts << safe_join([" ", content_tag(:span, c.from_path, class: "copied-from")]) if c.from_path.present?
[changes_tree_li_element(c.action, safe_join(text_parts), style)]
changes_tree_li_element(c.action, safe_join(parts), "change change-#{c.action}")
end
end.compact
end
content_tag(:ul, safe_join(items))
end
# rubocop:enable Rails/HelperInstanceVariable
def to_utf8_for_repositories(str)
return str if str.nil?
@@ -203,14 +194,16 @@ module RepositoriesHelper
if str.respond_to?(:force_encoding)
str.force_encoding("UTF-8")
if !str.valid_encoding?
str = str.encode("US-ASCII", invalid: :replace,
undef: :replace, replace: "?").encode("UTF-8")
unless str.valid_encoding?
str = str.encode("US-ASCII",
invalid: :replace,
undef: :replace, replace: "?")
.encode("UTF-8")
end
else
# removes invalid UTF8 sequences
begin
(str + " ").encode("UTF-8", invalid: :replace, undef: :replace, replace: "?")[0..-3]
"#{str} ".encode("UTF-8", invalid: :replace, undef: :replace, replace: "?")[0..-3]
rescue Encoding::InvalidByteSequenceError, Encoding::UndefinedConversionError
end
end
@@ -297,17 +290,15 @@ module RepositoriesHelper
end
def changes_tree_li_element(action, text, style)
icon_name = case action
when "A" then "icon-add"
when "D" then "icon-delete"
when "C" then "icon-copy"
when "R" then "icon-rename"
else
"icon-arrow-left-right"
end
icon_name =
case action
when "A" then "icon-add"
when "D" then "icon-delete"
when "C" then "icon-copy"
when "R" then "icon-rename"
else "icon-arrow-left-right"
end
content_tag(:li, text,
class: "#{style} icon #{icon_name}",
title: changes_tree_change_title(action))
content_tag(:li, text, class: "#{style} icon #{icon_name}", title: changes_tree_change_title(action))
end
end
+19 -14
View File
@@ -34,21 +34,27 @@ module SearchHelper
return nil unless split_text.length > 1 || text_on_not_found
result = +""
parts = []
total_length = 0
split_text.each_with_index do |words, i|
if result.length > 1200
if total_length > 1200
# maximum length of the preview reached
result << "..."
parts << "..."
break
end
result << if i.even?
abbreviated_text(words)
else
token_span(tokens, words)
end
part = if i.even?
abbreviated_text(words)
else
token_span(tokens, words)
end
parts << part
total_length += part.length
end
result.html_safe
safe_join(parts)
end
def highlight_tokens_in_event(event, tokens)
@@ -66,12 +72,11 @@ module SearchHelper
def highlight_and_abbreviate_html(event_description, tokens)
html = OpenProject::TextFormatting::Renderer.format_text(event_description)
highlighted_html = highlight_tokens_in_html(html, tokens)
# rubocop:disable Rails/OutputSafety
abbreviated_html(highlighted_html).html_safe
# rubocop:enable Rails/OutputSafety
# This html_safe call is fine, as coming from our html pipeline
abbreviated_html(highlighted_html).html_safe # rubocop:disable Rails/OutputSafety
end
def highlight_tokens_in_html(html, tokens)
def highlight_tokens_in_html(html, tokens) # rubocop:disable Metrics/AbcSize
doc = Nokogiri::HTML::DocumentFragment.parse(html)
tokens.each do |token|
@@ -84,7 +89,7 @@ module SearchHelper
t = (tokens.index(node.content.downcase) || 0) % 4
highlighted_text = escaped_text.gsub(/(#{escaped_token})/i) do
%{<span class="search-highlight token-#{t}">#{$1}</span>}
content_tag(:span, $1, class: "search-highlight token-#{t}")
end
node.replace(Nokogiri::HTML::DocumentFragment.parse(highlighted_text))
+25 -16
View File
@@ -163,7 +163,7 @@ module SettingsHelper
def setting_label(setting, options = {})
label = options[:label]
return "".html_safe if label == false
return "" if label == false
styled_label_tag(
"settings_#{setting}", options[:not_translated_label] || I18n.t(label || "setting_#{setting}"),
@@ -192,18 +192,23 @@ module SettingsHelper
def build_settings_matrix_head(settings, options = {})
content_tag(:tr, class: "form--matrix-header-row") do
content_tag(:th, I18n.t(options[:label_choices] || :label_choices),
class: "form--matrix-header-cell") +
settings.map do |setting|
content_tag(:th, class: "form--matrix-header-cell") do
hidden_field_tag("settings[#{setting}][]", "") +
I18n.t("setting_#{setting}")
end
end.join.html_safe
class: "form--matrix-header-cell") + build_settings_matrix_head_values(settings)
end
end
def build_settings_matrix_head_values(settings)
values = settings.map do |setting|
content_tag(:th, class: "form--matrix-header-cell") do
hidden_field_tag("settings[#{setting}][]", "") +
I18n.t("setting_#{setting}")
end
end
safe_join(values)
end
def build_settings_matrix_body(settings, choices)
choices.map do |choice|
body = choices.map do |choice|
value = choice[:value]
caption = choice[:caption] || value.to_s
exceptions = Array(choice[:except]).compact
@@ -211,11 +216,13 @@ module SettingsHelper
content_tag(:td, caption, class: "form--matrix-cell") +
settings_matrix_tds(settings, exceptions, value)
end
end.join.html_safe # rubocop:disable Rails/OutputSafety
end
safe_join(body)
end
def settings_matrix_tds(settings, exceptions, value)
settings.map do |setting|
tds = settings.map do |setting|
content_tag(:td, class: "form--matrix-checkbox-cell") do
unless exceptions.include?(setting)
styled_check_box_tag("settings[#{setting}][]", value,
@@ -223,14 +230,16 @@ module SettingsHelper
disabled_setting_option(setting).merge(id: "#{setting}_#{value}"))
end
end
end.join.html_safe # rubocop:disable Rails/OutputSafety
end
safe_join(tds)
end
def setting_multiselect_choice(setting, choice, options)
def setting_multiselect_choice(setting, choice, options) # rubocop:disable Metrics/AbcSize
text, value, choice_options = (choice.is_a?(Array) ? choice : [choice, choice])
choice_options = disabled_setting_option(setting)
.merge(choice_options || {})
.merge(options.except(:id))
.merge(choice_options || {})
.merge(options.except(:id))
choice_options[:id] = "#{setting}_#{value}"
content_tag(:label, class: "form--label-with-check-box") do
@@ -255,7 +264,7 @@ module SettingsHelper
if writable_setting?(setting)
yield
else
"".html_safe
ActiveSupport::SafeBuffer.new
end
end
end
+4 -5
View File
@@ -80,7 +80,6 @@ module TextFormattingHelper
end
def truncate_formatted_text(text, length: 120, replace_newlines: true)
# rubocop:disable Rails/OutputSafety
stripped_text = strip_tags(format_text(text.to_s))
stripped_text = if length
@@ -91,13 +90,13 @@ module TextFormattingHelper
.strip
if replace_newlines
stripped_text
.gsub(/[\r\n]+/, "<br />")
stripped_text.gsub!(/[\r\n]+/, "<br/>")
else
stripped_text
end
.html_safe
# rubocop:enable Rails/OutputSafety
# Sanitized through strip_tags
stripped_text.html_safe # rubocop:disable Rails/OutputSafety
end
def truncate_multiline(string, length)
+5 -4
View File
@@ -89,11 +89,12 @@ module UsersHelper
# Create buttons to lock/unlock a user and reset failed logins
def build_change_user_status_action(user)
result = "".html_safe
iterate_user_statusses(user) do |title, name|
result << ((yield title, name) + " ") # rubocop:disable Style/StringConcatenation
capture do
iterate_user_statusses(user) do |title, name|
concat yield(title, name)
concat " "
end
end
result
end
def iterate_user_statusses(user)
+2 -2
View File
@@ -45,7 +45,7 @@ module VersionsHelper
html_options = html_options.merge(id: link_to_version_id(version))
link_name = options[:before_text].to_s.html_safe + format_version_name(version, options[:project] || @project)
link_name = format_version_name(version, options[:project] || @project) # rubocop:disable Rails/HelperInstanceVariable
link_to_if version.visible?,
link_name,
{ controller: "/versions", action: "show", id: version },
@@ -57,7 +57,7 @@ module VersionsHelper
%i[start_date due_date]
.filter { |attr| version.send(attr) }
.map { |attr| "#{Version.human_attribute_name(attr)} #{format_date(version.send(attr))}" }
safe_join(formatted_dates, "<br>".html_safe)
safe_join(formatted_dates, tag(:br))
end
def link_to_version_id(version)
+1 -17
View File
@@ -39,7 +39,7 @@ module WorkPackagesHelper
# link_to_work_package(package, link_subject: true) # => Defect #6: This is the subject (everything within the link)
# link_to_work_package(package, display_project: true) # => Foo - Defect #6: This is the subject
def link_to_work_package(work_package, display_project: false, link_subject: false) # rubocop:disable Metrics/AbcSize
output = "".html_safe
output = ActiveSupport::SafeBuffer.new
output << "#{work_package.project} - " if display_project && work_package.project_id
link = link_to(work_package_path(work_package),
@@ -101,22 +101,6 @@ module WorkPackagesHelper
end
end
def work_package_associations_to_address(associated)
ret = "".html_safe
ret += content_tag(:p, I18n.t(:text_destroy_with_associated), class: "bold")
ret += content_tag(:ul) do
associated.inject("".html_safe) do |list, associated_class|
list += content_tag(:li, associated_class.model_name.human, class: "decorated")
list
end
end
ret
end
def back_url_is_wp_show?
route = Rails.application.routes.recognize_path(params[:back_url] || request.env["HTTP_REFERER"])
route[:controller] == "work_packages" && route[:action] == "index" && route[:state]&.match?(/^\d+/)
@@ -51,7 +51,7 @@ class Activities::ProjectActivityProvider < Activities::BaseActivityProvider
end
def event_title(event)
I18n.t("events.title.project", name: event["project_name"])
I18n.t("events.title.project_html", name: event["project_name"])
end
def event_path(event)
+2 -6
View File
@@ -33,14 +33,10 @@ See COPYRIGHT and LICENSE files for more details.
<% html_title t(:label_login) %>
<%= call_hook :view_account_login_top %>
<%
signin_link = link_to I18n.t("label_here"), signin_path
instruction_text = I18n.t "instructions_#{instructions}", signin: signin_link
%>
<div id="login-form" class="form -bordered">
<h1><%= I18n.t(:label_login) %></h1>
<hr class="form--separator">
<p><%= instruction_text.html_safe %></p>
<p><%= link_translate "instructions_#{instructions}",
links: { signin_url: signin_path } %></p>
</div>
<%= call_hook :view_account_login_bottom %>
+7 -1
View File
@@ -31,7 +31,13 @@ See COPYRIGHT and LICENSE files for more details.
<%=
render Primer::OpenProject::PageHeader.new do |header|
header.with_title { (@author.nil? ? t(:label_activity) : t(:label_user_activity, value: link_to_user(@author))).html_safe }
header.with_title do
if @author.nil?
t(:label_activity)
else
t(:label_user_activity_html, value: link_to_user(@author))
end
end
header.with_description { t(:label_date_from_to, start: format_date(@date_to - @days), end: format_date(@date_to - 1)) }
header.with_breadcrumbs(@project ? [{ href: project_overview_path(@project.id), text: @project.name }, t(:label_activity)] : nil)
end
+5 -3
View File
@@ -62,10 +62,12 @@ See COPYRIGHT and LICENSE files for more details.
<p>
<%= t(
"backup.reset_token.verification",
word: "<em class=\"danger-zone--expected-value\">#{t("backup.reset_token.verification_word_#{action}")}</em>",
"backup.reset_token.verification_html",
word: content_tag(:em,
t("backup.reset_token.verification_word_#{action}"),
class: "danger-zone--expected-value"),
action: t("backup.reset_token.verification_word_#{action}")
).html_safe %>
) %>
</p>
<div class="danger-zone--verification">
<input type="text" name="login_verification">
+7 -9
View File
@@ -58,14 +58,12 @@ See COPYRIGHT and LICENSE files for more details.
scheme: :warning,
icon: :alert
)) do
<<~STR.html_safe
Starting with OpenProject 16.0, PostgreSQL 16 is required to use OpenProject.
Your installation will remain functional with your current database, but anticipate incompatability
in future releases.
<br/>
We have prepared #{static_link_to(:postgres_17_upgrade, label: 'upgrade guides for all installation methods')}.
You can perform the upgrade ahead of the next release at any time by following the guides.
STR
link_translate(
:"admin.info.database_deprecation_html",
links: {
upgrade_guide: %i[postgres_17_upgrade]
}
)
end
component.with_attribute(key: "Deprecation warning", value:)
end
@@ -103,7 +101,7 @@ end %>
component.with_attribute(
key: t(:label_storage_for),
value: safe_join(entries[:labels], "<br/>".html_safe)
value: safe_join(entries[:labels], tag(:br))
)
component.with_attribute(
@@ -29,9 +29,9 @@ See COPYRIGHT and LICENSE files for more details.
<%=
settings_primer_form_with(scope: :settings,
url: admin_settings_authentication_path(tab: params[:tab]),
data: { turbo_method: :patch },
method: :patch) do |f|
url: admin_settings_authentication_path(tab: params[:tab]),
data: { turbo_method: :patch },
method: :patch) do |f|
render_inline_settings_form(f) do |form|
disabled = OpenProject::Configuration.disable_password_login?
@@ -42,10 +42,8 @@ See COPYRIGHT and LICENSE files for more details.
icon: :info,
my: 2
) do
I18n.t(
:note_password_login_disabled,
configuration: static_link_to(:disable_password_login, label: I18n.t('label_configuration'))
).html_safe
link_translate(:note_password_login_disabled,
links: { configuration_url: %i[disable_password_login] })
end
end
end
@@ -64,7 +62,7 @@ See COPYRIGHT and LICENSE files for more details.
group.check_box(value:,
label: I18n.t("label_password_rule_#{value}"),
checked: OpenProject::Passwords::Evaluator.active_rule?(value),
)
)
end
end
@@ -37,10 +37,10 @@ See COPYRIGHT and LICENSE files for more details.
name: :omniauth_direct_login_provider,
input_width: :medium,
label: I18n.t(:setting_omniauth_direct_login_provider),
caption: I18n.t(
caption: t(
"settings.authentication.omniauth_direct_login_hint_html",
internal_path: internal_signin_url
).html_safe,
),
include_blank: I18n.t(:label_none_parentheses)
) do |select|
AuthProvider
+5 -1
View File
@@ -102,7 +102,11 @@ See COPYRIGHT and LICENSE files for more details.
<div class="op-toast -warning icon-warning d-none"
data-user-limit-target="limitWarning">
<div class="op-toast--content">
<p><%= I18n.t("warning_user_limit_reached#{'_admin' if current_user.admin?}", upgrade_url: OpenProject::Enterprise.upgrade_url).html_safe %></p>
<p><%=
link_translate(
"warning_user_limit_reached#{'_admin' if current_user.admin?}",
links: { upgrade_url: OpenProject::Enterprise.upgrade_url }
) %>
</div>
</div>
<% end %>
+5 -1
View File
@@ -29,7 +29,11 @@ See COPYRIGHT and LICENSE files for more details.
<%=
render Primer::OpenProject::PageHeader.new do |header|
header.with_title { "#{avatar(@news.author)} #{h @news.title}".html_safe }
header.with_title do
concat avatar(@news.author)
concat @news.title
end
header.with_breadcrumbs(
[
{ href: project_overview_path(@project.id), text: @project.name },
@@ -27,7 +27,7 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<% name = @placeholder_user.name %>
<% html_title(t(:label_administration), t("placeholder_users.deletion_info.heading", name: name).to_s) -%>
<% html_title(t(:label_administration), t("placeholder_users.deletion_info.heading_html", name:).to_s) -%>
<%= labelled_tabular_form_for(
:placeholder_user,
@@ -40,11 +40,7 @@ See COPYRIGHT and LICENSE files for more details.
<div class='wiki'>
<section class="form--section">
<h3 class="form--section-title">
<%= t(
"placeholder_users.deletion_info.heading",
name: content_tag(:em, name)
)
.html_safe %>
<%= t("placeholder_users.deletion_info.heading_html", name: content_tag(:em, name)) %>
</h3>
<p>
@@ -56,9 +52,9 @@ See COPYRIGHT and LICENSE files for more details.
</p>
<p>
<%= t(
"placeholder_users.deletion_info.confirmation",
"placeholder_users.deletion_info.confirmation_html",
name: content_tag(:em, name, class: "danger-zone--expected-value")
).html_safe %>
) %>
</p>
<div class="danger-zone--verification">
<input type="text" name="name_verification">
@@ -31,4 +31,4 @@ See COPYRIGHT and LICENSE files for more details.
<p><%= t("copy_project.text.failed", source_project_name: @source_project.name, target_project_name: @target_project_name) %></p>
<%= @errors.join("<br/>".html_safe) %>
<%= safe_join @errors, tag(:br) %>
@@ -0,0 +1,39 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<% if @properties.present? %>
<ul class="properties">
<% @properties.keys.sort.each do |property| %>
<li>
<b><%= property %></b>:
<span><%= @properties[property] %></span>
</li>
<% end %>
</ul>
<% end %>
+1 -1
View File
@@ -32,7 +32,7 @@ See COPYRIGHT and LICENSE files for more details.
<p><%= render partial: "link_to_functions" %></p>
<%= render_properties(@properties) %>
<%= render partial: "properties" %>
<%= unless @changesets.empty?
render(
+24 -14
View File
@@ -42,12 +42,15 @@ See COPYRIGHT and LICENSE files for more details.
<%= styled_form_tag(project_repository_path(@project), method: :delete, class: "danger-zone") do %>
<section class="form--section">
<h3 class="form--section-title">
<%= t("repositories.destroy.title", repository_type: "<em>#{h(@repository.repository_type)} - #{t(:project_module_repository)}</em>").html_safe %>
<%= t("repositories.destroy.title_html", repository_type: content_tag(:em, "#{@repository.repository_type} - #{t(:project_module_repository)}")) %>
</h3>
<p>
<%= t("repositories.destroy.subtitle", repository_type: "#{h(@repository.repository_type)} - #{t(:project_module_repository)}", project_name: h(@project.identifier)).html_safe %> <br>
<%= t("repositories.destroy.subtitle",
repository_type: "#{@repository.repository_type} - #{t(:project_module_repository)}",
project_name: @project.identifier) %>
<br/>
<%= t("repositories.destroy.confirmation") %>
<br>
<br/>
<%= t("repositories.destroy.managed_path_note", path: @repository.root_url) %>
</p>
<p class="danger-zone--warning">
@@ -55,7 +58,8 @@ See COPYRIGHT and LICENSE files for more details.
<span><%= t("repositories.destroy.info") %></span>
</p>
<p>
<%= t("repositories.destroy.repository_verification", identifier: "<em class=\"danger-zone--expected-value\">#{h(@project.identifier)}</em>").html_safe %>
<%= t("repositories.destroy.repository_verification_html",
identifier: content_tag(:em, @project.identifier, class: "danger-zone--expected-value")) %>
</p>
<div class="danger-zone--verification">
<input type="text">
@@ -66,13 +70,13 @@ See COPYRIGHT and LICENSE files for more details.
title: t(:button_delete),
disabled: true,
class: "-primary" do %>
<%= op_icon("button--icon icon-delete") %>
<span class="button--text"><%= t(:button_delete) %></span>
<%= op_icon("button--icon icon-delete") %>
<span class="button--text"><%= t(:button_delete) %></span>
<% end %>
<%= link_to project_settings_repository_path(@project),
title: t(:button_cancel),
class: "button -with-icon icon-cancel" do %>
<%= t(:button_cancel) %>
<%= t(:button_cancel) %>
<% end %>
</div>
</section>
@@ -80,14 +84,20 @@ See COPYRIGHT and LICENSE files for more details.
<% else %>
<div class="op-toast -warning">
<div class="op-toast--content">
<p><strong><%= t("repositories.destroy.title_not_managed", repository_type: "<em>#{h(@repository.repository_type)} - #{t(:project_module_repository)}</em>").html_safe %></strong><br></p>
<p>
<strong>
<%= t("repositories.destroy.title_not_managed",
repository_type: content_tag(:em, "#{@repository.repository_type} - #{t(:project_module_repository)}")) %>
</strong>
<br/>
</p>
<p>
<%= t(
"repositories.destroy.subtitle_not_managed",
repository_type: "#{h(@repository.repository_type)} - #{t(:project_module_repository)}",
project_name: h(@project.identifier),
url: h(@repository.url)
).html_safe %> <br>
"repositories.destroy.subtitle_not_managed_html",
repository_type: "#{@repository.repository_type} - #{t(:project_module_repository)}",
project_name: @project.identifier,
url: @repository.url
) %> <br>
</p>
<p>
<span><%= t("repositories.destroy.info_not_managed") %></span>
@@ -106,7 +116,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= link_to project_settings_repository_path(@project),
title: t(:button_cancel),
class: "button -with-icon icon-cancel" do %>
<%= t(:button_cancel) %>
<%= t(:button_cancel) %>
<% end %>
</p>
</div>
+5 -8
View File
@@ -65,14 +65,11 @@ See COPYRIGHT and LICENSE files for more details.
<%= render partial: 'common/diff', locals: { diff: @diff, diff_type: @diff_type } %>
<% end -%>
<%= other_formats_links do |f| %>
<% unidiff_link = f.link_to 'Diff', url: permitted_params.repository_diff.to_h, caption: 'Unified diff' %>
<% if !@path.blank? %>
<% unidiff_link.gsub!('?', '&amp;') %>
<% end %>
<% wrong_url = CGI::escapeHTML(CGI.escape(with_leading_slash(@path))).concat('.diff') %>
<% good_url = '.diff'.concat('?repo_path=', CGI.escape(without_leading_slash(@path)).gsub('&', '%26')) %>
<% unidiff_link.gsub!(wrong_url, good_url) %>
<%= unidiff_link.html_safe %>
<%
unidiff_params = permitted_params.repository_diff.to_h.merge(format: "diff")
unidiff_params[:repo_path] = without_leading_slash(@path) if @path.present?
%>
<%= f.link_to "Diff", url: unidiff_params, caption: "Unified diff" %>
<% end %>
<% html_title(h(with_leading_slash(@path)), 'Diff') -%>
+1 -1
View File
@@ -34,7 +34,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= render partial: "dir_list" %>
<% end %>
<%= render_properties(@properties) %>
<%= render partial: "properties" %>
<div class="repository--revision-toolbar">
<%# rev => nil prevents overwriting the rev parameter queried for in the form with the parameter from the request %>
@@ -36,12 +36,12 @@
<tr>
<td style="<%= placeholder_text_styles %>">
<%
formatted_actions = @allowed_work_package_actions.map do |action|
"<span style=\"font-weight:bold;\">#{action.downcase}</span>"
allowed_actions = @allowed_work_package_actions.map do |action|
content_tag(:span, action.downcase, class: "-bold")
end.to_sentence
%>
<%= t("mail.sharing.work_packages.allowed_actions", allowed_actions: formatted_actions).html_safe %>
<%= t("mail.sharing.work_packages.allowed_actions_html", allowed_actions:) %>
</td>
</tr>
</table>
@@ -23,7 +23,7 @@
<%= "=" * (("# " + @work_package.id.to_s + @work_package.subject).length + 4) %>
<%= I18n.t("mail.work_packages.reason.shared") %>:
<%= t("mail.sharing.work_packages.allowed_actions", allowed_actions: @allowed_work_package_actions.to_sentence).html_safe %>
<%= t("mail.sharing.work_packages.allowed_actions_html", allowed_actions: @allowed_work_package_actions.to_sentence) %>
<%= t("mail.sharing.work_packages.open_work_package") %>
<%= @url %>
@@ -30,7 +30,7 @@ See COPYRIGHT and LICENSE files for more details.
<p>
<% mail_link = content_tag(:a, @email, href: "mailto:#{@email}") %>
<% host_link = content_tag(:a, Setting.app_title, href: Rails.application.root_url) %>
<%= t("mail_user_activation_limit_reached.message", email: mail_link, host: host_link).html_safe %>
<%= t("mail_user_activation_limit_reached.message_html", email: mail_link, host: host_link) %>
</p>
<span><%= t("mail_user_activation_limit_reached.steps.label") %></span>
@@ -27,7 +27,7 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<%= t("mail_user_activation_limit_reached.message", email: @email, host: Rails.application.root_url) %>
<%= t("mail_user_activation_limit_reached.message_html", email: @email, host: Rails.application.root_url) %>
<%= t("mail_user_activation_limit_reached.steps.label") %>
- <%= t("mail_user_activation_limit_reached.steps.a").sub(link_regex, OpenProject::Enterprise.upgrade_url) %>
+7 -9
View File
@@ -28,11 +28,11 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<%= fields_for :pref, @user.pref, builder: TabularFormBuilder, lang: current_language do |pref_fields| %>
<%= render Settings::TimeZoneSettingComponent.new(
"time_zone",
form: pref_fields,
include_blank: false,
container_class: defined?(input_size) ? "-#{input_size}" : "-wide"
) %>
"time_zone",
form: pref_fields,
include_blank: false,
container_class: defined?(input_size) ? "-#{input_size}" : "-wide"
) %>
<div class="form--field">
<%= pref_fields.select :theme, theme_options_for_select, container_class: "-middle" %> <!-- TODO -->
<div class="form--field-instructions">
@@ -44,10 +44,8 @@ See COPYRIGHT and LICENSE files for more details.
<%= pref_fields.check_box :disable_keyboard_shortcuts,
label: I18n.t("activerecord.attributes.user_preference.disable_keyboard_shortcuts") %>
<span class="form--field-instructions">
<%= I18n.t(
"activerecord.attributes.user_preference.disable_keyboard_shortcuts_caption_html",
href: OpenProject::Static::Links.url_for(:shortcuts)
).html_safe %>
<%= link_translate(:"user_preferences.disable_keyboard_shortcuts_caption",
links: { docs_url: %i[shortcuts] }) %>
</span>
</div>
<% end %>
@@ -48,8 +48,12 @@ See COPYRIGHT and LICENSE files for more details.
<%= t("work_package.destroy.title") %>
</h3>
<%= work_package_associations_to_address(associated) %>
<p class="bold"><%= t(:text_destroy_with_associated) %></p>
<ul>
<% associated.each do |associated_class| %>
<li class="decorated"><%= associated_class.model_name.human %></li>
<% end %>
</ul>
<p>
<strong><%= t(:text_destroy_what_to_do) %></strong>
</p>
@@ -108,9 +112,9 @@ See COPYRIGHT and LICENSE files for more details.
<% end %>
<div class="danger-zone--verification">
<%= styled_button_tag title: t(:button_delete), class: "-primary" do
concat content_tag :i, "", class: "button--icon icon-delete"
concat content_tag :span, t(:button_delete), class: "button--text"
end %>
concat content_tag :i, "", class: "button--icon icon-delete"
concat content_tag :span, t(:button_delete), class: "button--text"
end %>
<%= link_to_function t(:button_cancel),
"history.back()",
title: t(:button_cancel),
+9 -10
View File
@@ -39,18 +39,19 @@ See COPYRIGHT and LICENSE files for more details.
<label class="form--label" for="source_type_id"><%= Type.model_name.human %></label>
<%= select_tag(
"source_type_id",
"<option value=\"\">--- #{t(:actionview_instancetag_blank_option)} ---</option>".html_safe +
"<option value=\"any\">--- #{t(:label_copy_same_as_target)} ---</option>".html_safe +
options_from_collection_for_select(@types, "id", "name", @source_type && @source_type.id), class: "form--select"
(blank_select_option +
content_tag(:option, "--- #{t(:label_copy_same_as_target)} ---") +
options_from_collection_for_select(@types, "id", "name", @source_type && @source_type.id)),
class: "form--select"
) %>
</div>
<div class="form--select-container -middle">
<label class="form--label" for='source_role_id'><%= Role.model_name.human %></label>
<%= select_tag(
"source_role_id",
"<option value=\"\">--- #{t(:actionview_instancetag_blank_option)} ---</option>".html_safe +
"<option value=\"any\">--- #{t(:label_copy_same_as_target)} ---</option>".html_safe +
options_from_collection_for_select(@roles, "id", "name", @source_role && @source_role.id), class: "form--select"
(blank_select_option +
content_tag(:option, "--- #{t(:label_copy_same_as_target)} ---") +
options_from_collection_for_select(@roles, "id", "name", @source_type && @source_type.id)),
) %>
</div>
</div>
@@ -62,16 +63,14 @@ See COPYRIGHT and LICENSE files for more details.
<div class="form--select-container -middle">
<label class="form--label" for="target_type_ids"><%= Type.model_name.human %></label>
<%= select_tag "target_type_ids",
"<option value=\"\" disabled=\"disabled\">--- #{t(:actionview_instancetag_blank_option)} ---</option>".html_safe +
options_from_collection_for_select(@types, "id", "name", @target_types && @target_types.map(&:id)),
blank_select_option + options_from_collection_for_select(@types, "id", "name", @target_types && @target_types.map(&:id)),
multiple: true,
size: 20, class: "form--select" %>
</div>
<div class="form--select-container -middle">
<label class="form--label" for='target_role_ids'><%= Role.model_name.human %></label>
<%= select_tag "target_role_ids",
"<option value=\"\" disabled=\"disabled\">--- #{t(:actionview_instancetag_blank_option)} ---</option>".html_safe +
options_from_collection_for_select(@roles, "id", "name", @target_roles && @target_roles.map(&:id)),
blank_select_option + options_from_collection_for_select(@roles, "id", "name", @target_roles && @target_roles.map(&:id)),
multiple: true,
size: 20,
class: "form--select" %>
+52 -36
View File
@@ -436,6 +436,15 @@ en:
default_transitions: "Default transitions"
user_author: "User is author"
user_assignee: "User is assignee"
info:
database_deprecation_html: >
Starting with OpenProject 16.0, PostgreSQL 16 is required to use OpenProject.
Your installation will remain functional with your current database, but anticipate incompatability
in future releases.
<br/>
We have prepared [upgrade guides for all installation methods](upgrade_guide).
You can perform the upgrade ahead of the next release at any time by following the guides.
authentication:
login_and_registration: "Login and registration"
@@ -1101,9 +1110,9 @@ en:
groups:
member_in_these_groups: "This user is currently a member of the following groups:"
no_results_title_text: This user is currently not a member in any group.
summary_with_more: Member of %{names} and %{count_link}.
summary_with_more_html: Member of %{names} and %{count_link}.
more: "%{count} more"
summary: Member of %{names}.
summary_html: Member of %{names}.
memberships:
no_results_title_text: This user is currently not a member of a project.
open_profile: "Open profile"
@@ -1219,6 +1228,9 @@ en:
work_days_count:
one: "1 working day"
other: "%{count} working days"
user_preferences:
disable_keyboard_shortcuts_caption: >
You can choose to disable default [keyboard shortcuts](docs_url) if you use a screen reader or want to avoid accidentally triggering an action with a shortcut.
page:
text: "Text"
placeholder_users:
@@ -1227,7 +1239,7 @@ en:
You do not have the right to manage members for all projects that the placeholder user is a member of.
delete_tooltip: "Delete placeholder user"
deletion_info:
heading: "Delete placeholder user %{name}"
heading_html: "Delete placeholder user %{name}"
data_consequences: >
All occurrences of the placeholder user (e.g., as assignee, responsible or other user values)
will be reassigned to an account called "Deleted user".
@@ -1236,7 +1248,7 @@ en:
it will not be possible to distinguish the data the user created from
the data of another deleted account.
irreversible: "This action is irreversible"
confirmation: "Enter the placeholder user name %{name} to confirm the deletion."
confirmation_html: "Enter the placeholder user name %{name} to confirm the deletion."
priorities:
edit:
priority_color_text: |
@@ -1272,7 +1284,7 @@ en:
Check this option to exclude work packages with this status from totals of Work,
Remaining work, and % Complete in a hierarchy.
status_percent_complete_text: |-
In <a href="%{href}">status-based progress calculation mode</a>, the % Complete of a work
In [status-based progress calculation mode](setting_url), the % Complete of a work
package is automatically set to this value when this status is selected.
Ignored in work-based mode.
status_readonly_html: |
@@ -1560,7 +1572,7 @@ en:
account for you or change the self registration limit for this provider.
login_with_auth_provider: "or sign in with your existing account"
signup_with_auth_provider: "or sign up using"
auth_source_login: Please login as <em>%{login}</em> to activate your account.
auth_source_login_html: Please login as <em>%{login}</em> to activate your account.
omniauth_login: Please login to activate your account.
actionview_instancetag_blank_option: "Please select"
@@ -1853,8 +1865,6 @@ en:
button_update_user_information: "Update profile"
comments_sorting: "Display work package activity sorted by"
disable_keyboard_shortcuts: "Disable keyboard shortcuts"
disable_keyboard_shortcuts_caption_html: |-
You can choose to disable default <a href="%{href}">keyboard shortcuts</a> if you use a screen reader or want to avoid accidentally triggering an action with a shortcut.
dismissed_enterprise_banners: "Hidden enterprise banners"
impaired: "Accessibility mode"
auto_hide_popups: "Automatically hide success banners"
@@ -2618,7 +2628,7 @@ en:
You will need to generate a backup token to be able to create a backup.
Each time you want to request a backup you will have to provide this token.
You can delete the backup token to disable backups for this user.
verification: >
verification_html: >
Enter %{word} to confirm you want to %{action} the backup token.
verification_word_reset: reset
verification_word_create: create
@@ -3077,7 +3087,7 @@ en:
title:
one: "One day left of %{trial_plan} trial token"
other: "%{count} days left of %{trial_plan} trial token"
description: "You have access to all %{trial_plan} features."
description_html: "You have access to all %{trial_plan} features."
trial:
not_found: "You have requested a trial token, but that request is no longer available. Please try again."
wait_for_confirmation: "We sent you an email to confirm your address in order to retrieve a trial token."
@@ -3096,11 +3106,8 @@ en:
confirmation_subline: >
Please, check your inbox and follow the steps to start your 14-day free trial.
domain_caption: The token will be valid for your currently configured host name.
receive_newsletter_html: >
I want to receive the OpenProject <a target="_blank" href="https://www.openproject.org/newsletter/">newsletter</a>.
consent_html: >
I agree with the <a target="_blank" href="https://www.openproject.org/terms-of-service/">terms of service</a>
and the <a target="_blank" href="https://www.openproject.org/data-privacy-and-security/">privacy policy</a>.
receive_newsletter: "I want to receive the OpenProject [newsletter](newsletter_url)."
consent: "I agree with the [terms of service](tos_url) and the [privacy policy](privacy_url)."
email_calendar_updates:
state:
@@ -3195,8 +3202,8 @@ en:
work_package_edit: "Work Package edited"
work_package_note: "Work Package note added"
title:
project: "Project: %{name}"
subproject: "Subproject: %{name}"
project_html: "Project: %{name}"
subproject_html: "Subproject: %{name}"
export:
dialog:
@@ -3466,9 +3473,9 @@ en:
configuration_guide: "Configuration guide"
get_in_touch: "You have questions? Get in touch with us."
instructions_after_registration: "You can sign in as soon as your account has been activated by clicking %{signin}."
instructions_after_logout: "You can sign in again by clicking %{signin}."
instructions_after_error: "You can try to sign in again by clicking %{signin}. If the error persists, ask your admin for help."
instructions_after_registration: "You can sign in as soon as your account has been activated by clicking [here](signin_url)."
instructions_after_logout: "You can sign in again by clicking [here](signin_url)."
instructions_after_error: "You can try to sign in again by clicking [here](signin_url). If the error persists, ask your admin for help."
menus:
admin:
@@ -3664,6 +3671,8 @@ en:
label_calendar_show: "Show Calendar"
label_category: "Category"
label_completed: Completed
label_committed_at_html: "%{committed_revision_link} at %{date}"
label_committed_link: "committed revision %{revision_identifier}"
label_consent_settings: "User Consent"
label_wiki_menu_item: Wiki menu item
label_select_main_menu_item: Select new main menu item
@@ -4189,7 +4198,7 @@ en:
label_user: "User"
label_user_and_permission: "Users and permissions"
label_user_named: "User %{name}"
label_user_activity: "%{value}'s activity"
label_user_activity_html: "%{value}'s activity"
label_user_anonymous: "Anonymous"
label_user_menu: "User menu"
label_user_new: "New user"
@@ -4373,7 +4382,7 @@ en:
note: "Note: “%{note}”"
sharing:
work_packages:
allowed_actions: "You may %{allowed_actions} this work package. This can change depending on your project role and permissions."
allowed_actions_html: "You may %{allowed_actions} this work package. This can change depending on your project role and permissions."
create_account: "To access this work package, you will need to create and activate an account on %{instance}."
open_work_package: "Open work package"
subject: "Work package #%{id} was shared with you"
@@ -4476,7 +4485,7 @@ en:
mail_user_activation_limit_reached:
subject: User activation limit reached
message: |
message_html: |
A new user (%{email}) tried to create an account on an OpenProject environment that you manage (%{host}).
The user cannot activate their account since the user limit has been reached.
steps:
@@ -4832,10 +4841,10 @@ en:
info: "Deleting the repository is an irreversible action."
info_not_managed: "Note: This will NOT delete the contents of this repository, as it is not managed by OpenProject."
managed_path_note: "The following directory will be erased: %{path}"
repository_verification: "Enter the project's identifier %{identifier} to verify the deletion of its repository."
repository_verification_html: "Enter the project's identifier %{identifier} to verify the deletion of its repository."
subtitle: "Do you really want to delete the %{repository_type} of the project %{project_name}?"
subtitle_not_managed: "Do you really want to remove the linked %{repository_type} %{url} from the project %{project_name}?"
title: "Delete the %{repository_type}"
subtitle_not_managed_html: "Do you really want to remove the linked %{repository_type} %{url} from the project %{project_name}?"
title_html: "Delete the %{repository_type}"
title_not_managed: "Remove the linked %{repository_type}?"
errors:
build_failed: "Unable to create the repository with the selected configuration. %{reason}"
@@ -4946,7 +4955,7 @@ en:
setting_apiv3_cors_origins_text_html: >
If CORS is enabled, these are the origins that are allowed to access OpenProject API.
<br/>
Please check the <a href="%{origin_link}" target="_blank">Documentation on the Origin header</a> on how to specify the expected values.
Please check the [Documentation on the Origin header](docs_url) on how to specify the expected values.
setting_apiv3_write_readonly_attributes: "Write access to read-only attributes"
setting_apiv3_write_readonly_attributes_instructions: >
If enabled, the API will allow administrators to write static read-only attributes during creation,
@@ -4956,7 +4965,7 @@ en:
administrators to impersonate the creation of items as other users. All creation requests are being
logged however with the true author.
setting_apiv3_write_readonly_attributes_additional: >
For more information on attributes and supported resources, please see the %{api_documentation_link}.
For more information on attributes and supported resources, please see the [API documentation](api_documentation_link).
setting_apiv3_max_page_size: "Maximum API page size"
setting_apiv3_max_page_size_instructions: >
Set the maximum page size the API will respond with.
@@ -5366,7 +5375,10 @@ en:
not_allowed_text: "You do not have the necessary permissions to view this page."
activities:
enable_internal_comments: "Enable internal comments"
helper_text: "Internal comments allow an internal team to communicate amongst themselves privately. These are only visible to selected roles that have the necessary permissions and will not be visible publicly. %{link}"
helper_text: >
Internal comments allow an internal team to communicate amongst themselves privately.
These are only visible to selected roles that have the necessary permissions and will not be visible publicly.
[Click here to learn more](docs_url)
text_formatting:
markdown: "Markdown"
@@ -5647,21 +5659,23 @@ en:
version_status_open: "open"
note: Note
note_password_login_disabled: "Password login has been disabled by %{configuration}."
note_password_login_disabled: "Password login has been disabled through a [configuration setting](configuration_url)."
warning: Warning
warning_attachments_not_saved: "%{count} file(s) could not be saved."
warning_imminent_user_limit: >
You invited more users than are supported by your current plan.
Invited users may not be able to join your OpenProject environment.
Please <a href="%{upgrade_url}">upgrade your plan</a> or block existing
Please [upgrade your plan](upgrade_url) or block existing
users in order to allow invited and registered users to join.
warning_registration_token_expired: |
The activation email has expired. We sent you a new one to %{email}.
Please click the link inside of it to activate your account.
warning_user_limit_reached: >
Adding additional users will exceed the current limit. Please contact an administrator to increase the user limit to ensure external users are able to access this instance.
Adding additional users will exceed the current limit.
Please contact an administrator to increase the user limit to ensure external users are able to access this instance.
warning_user_limit_reached_admin: >
Adding additional users will exceed the current limit. Please <a href="%{upgrade_url}">upgrade your plan</a> to be able to ensure external users are able to access this instance.
Adding additional users will exceed the current limit.
Please [upgrade your plan](upgrade_url) to be able to ensure external users are able to access this instance.
warning_user_limit_reached_instructions: >
You reached your user limit (%{current}/%{max} active users).
Please contact sales@openproject.com to upgrade your Enterprise edition plan and add additional users.
@@ -5730,7 +5744,7 @@ en:
reminders:
label_remind_at: "Date"
note_placeholder: "Why are you setting this reminder?"
create_success_message: "Reminder set successfully. You will receive a notification for this work package %{reminder_time}."
create_success_message_html: "Reminder set successfully. You will receive a notification for this work package %{reminder_time}."
success_update_message: "Reminder updated successfully."
success_deletion_message: "Reminder deleted successfully."
sharing:
@@ -5760,9 +5774,11 @@ en:
text_user_limit_reached: "Adding additional users will exceed the current limit. Please contact an administrator to increase the user limit to ensure external users are able to access this %{entity}."
text_user_limit_reached_admins: 'Adding additional users will exceed the current limit. Please <a href="%{upgrade_url}">upgrade your plan</a> to be able to add more users.'
warning_user_limit_reached: >
Adding additional users will exceed the current limit. Please contact an administrator to increase the user limit to ensure external users are able to access this %{entity}.
Adding additional users will exceed the current limit.
Please contact an administrator to increase the user limit to ensure external users are able to access this %{entity}.
warning_user_limit_reached_admin: >
Adding additional users will exceed the current limit. Please <a href="%{upgrade_url}">upgrade your plan</a> to be able to ensure external users are able to access this %{entity}.
Adding additional users will exceed the current limit.
Please [upgrade your plan](upgrade_url) to be able to ensure external users are able to access this %{entity}.
warning_no_selected_user: "Please select users to share this %{entity} with"
warning_locked_user: "The user %{user} is locked and cannot be shared with"
user_details:
-2
View File
@@ -389,8 +389,6 @@ en:
label_collapse_all: "Collapse all"
label_collapse_text: "Collapse text"
label_comment: "Comment"
label_committed_at: "%{committed_revision_link} at %{date}"
label_committed_link: "committed revision %{revision_identifier}"
label_contains: "contains"
label_created_on: "created on"
label_edit_comment: "Edit this comment"
+2
View File
@@ -201,6 +201,8 @@ sysadmin_docs:
href: https://www.openproject.org/docs/system-admin-guide/authentication/scim/#step-3-choose-an-authentication-method
scim_jwt_authetication_method:
href: https://www.openproject.org/docs/system-admin-guide/authentication/scim/#c-jwt-from-identity-provider
terms_of_service:
href: https://www.openproject.org/terms-of-service/
text_formatting:
href: https://www.openproject.org/docs/user-guide/wysiwyg/
label: :setting_text_formatting
+6 -7
View File
@@ -142,15 +142,14 @@ class CustomFieldFormBuilder < TabularFormBuilder
for: custom_field_field_id,
class: classes,
title: custom_field.name do
output = "".html_safe
output += custom_field.name
capture do
concat custom_field.name
# Render a help text icon
if options[:help_text]
output += content_tag("attribute-help-text", "", data: options[:help_text])
# Render a help text icon
if options[:help_text]
concat content_tag("attribute-help-text", "", data: options[:help_text])
end
end
output
end
end
end
+5 -1
View File
@@ -167,7 +167,11 @@ module OpenProject
def project_link_name(project, show_icon)
if show_icon && User.current.member_of?(project)
icon_wrapper("icon-context icon-star", I18n.t(:description_my_project).html_safe + "&nbsp;".html_safe) + project.name
label = ActiveSupport::SafeBuffer.new
label << I18n.t(:description_my_project)
label << "&nbsp;".html_safe
icon_wrapper("icon-context icon-star", label) + project.name
else
project.name
end
+1 -1
View File
@@ -39,7 +39,7 @@ module OpenProject
concat render_page_hierarchy(pages, page.id, options) if is_parent
end
end
chunks.join.html_safe
safe_join(chunks)
end
end
@@ -44,7 +44,11 @@ module ActionView
def wrap_with_error_span(html_tag, object, method)
object_identifier = erroneous_object_identifier(object.object_id.to_s, method)
"<span id='#{object_identifier}' class=\"errorSpan\"><a name=\"#{object_identifier}\"></a>#{html_tag}</span>".html_safe
helpers = ActionController::Base.helpers
helpers.content_tag(:span, id: object_identifier, class: "errorSpan") do
helpers.content_tag(:a, "", name: object_identifier) + html_tag
end
end
def erroneous_object_identifier(id, method)
+4 -6
View File
@@ -180,10 +180,10 @@ module Redmine::MenuManager::MenuHelper
caption, url, selected = extract_node_details(item, project)
shown_in_main_menu = menu_class == "op-menu"
link_text = "".html_safe
link_text = ActiveSupport::SafeBuffer.new
if item.icon(project).present?
link_text += render(Primer::Beta::Octicon.new(
link_text << render(Primer::Beta::Octicon.new(
icon: item.icon,
mr: shown_in_main_menu ? 3 : 0,
size: shown_in_main_menu ? :small : :medium
@@ -192,7 +192,7 @@ module Redmine::MenuManager::MenuHelper
badge_class = item.badge(project:).present? ? " #{menu_class}--item-title_has-badge" : ""
link_text += content_tag(:span,
link_text << content_tag(:span,
class: "#{menu_class}--item-title#{badge_class}",
lang: menu_item_locale(item)) do
title_text = content_tag(:span, caption, class: "ellipsis") + badge_for(item)
@@ -205,7 +205,7 @@ module Redmine::MenuManager::MenuHelper
end
if item.icon_after.present?
link_text += render(Primer::Beta::Octicon.new(icon: item.icon_after, classes: "trailing-icon"))
link_text << render(Primer::Beta::Octicon.new(icon: item.icon_after, classes: "trailing-icon"))
end
html_options = item.html_options(selected:)
@@ -424,8 +424,6 @@ module Redmine::MenuManager::MenuHelper
key = item.badge(project: @project)
if key.present?
content_tag("span", I18n.t(key), class: "main-item--badge")
else
"".html_safe
end
end
+2 -2
View File
@@ -207,7 +207,7 @@ class TabularFormBuilder < ActionView::Helpers::FormBuilder
if prefix
ret.prepend content_tag(:span,
prefix.html_safe,
prefix,
class: "form--field-affix",
id: options[:prefix_id],
"aria-hidden": true)
@@ -215,7 +215,7 @@ class TabularFormBuilder < ActionView::Helpers::FormBuilder
if suffix
ret.concat content_tag(:span,
suffix.html_safe,
suffix,
class: "form--field-affix",
id: options[:suffix_id],
"aria-hidden": true)
+1 -1
View File
@@ -290,7 +290,7 @@ module Redmine
href:,
target:,
underline:,
data: { allow_external_link: true },
data: { allow_external_link: true }
)
component.with_trailing_visual_icon(icon: :"link-external") if external
component.with_content(text)
+4 -4
View File
@@ -439,8 +439,7 @@ class Admin::Settings::GeneralSettingsForm < ApplicationForm
name: :per_page_options,
label: I18n.t("setting_per_page_options"),
value: Setting[:per_page_options],
caption: "#{I18n.t(:text_comma_separated)}<br/>" \
"#{I18n.t(:text_notice_too_many_values_are_inperformant)}".html_safe)
caption: safe_join [I18n.t(:text_comma_separated), helpers.tag(:br), I18n.t(:text_notice_too_many_values_are_inperformant)],
disabled: !Setting.per_page_options_writable?
)
general_form.text_field(
@@ -492,8 +491,9 @@ class Admin::Settings::GeneralSettingsForm < ApplicationForm
settings_form do |general_form|
general_form.text_field(name: :app_title)
general_form.text_field(name: :per_page_options,
caption: "#{I18n.t(:text_comma_separated)}<br/>" \
"#{I18n.t(:text_notice_too_many_values_are_inperformant)}".html_safe)
caption: safe_join [I18n.t(:text_comma_separated),
helpers.tag(:br),
I18n.t(:text_notice_too_many_values_are_inperformant)])
general_form.text_field(name: :activity_days_default,
type: :number)
general_form.text_field(name: :host_name,
@@ -36,7 +36,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= t("saml.delete_title") %>
</h3>
<p>
<%= t("provider.delete_warning.provider", name: content_tag(:strong, @provider.display_name)).html_safe %>
<%= t("provider.delete_warning.provider_html", name: content_tag(:strong, @provider.display_name)) %>
</p>
<ul class="mb-3">
<li><%= t("provider.delete_warning.delete_result_1") %></li>
@@ -50,7 +50,8 @@ See COPYRIGHT and LICENSE files for more details.
<span><%= t("provider.delete_warning.irreversible_notice") %></span>
</p>
<p>
<%= t("provider.delete_warning.input_delete_confirmation", name: "<em class=\"danger-zone--expected-value\">#{h(@provider.display_name)}</em>").html_safe %>
<%= t("provider.delete_warning.input_delete_confirmation_html",
name: content_tag(:em, @provider.display_name, class: "danger-zone--expected-value")) %>
</p>
<div class="danger-zone--verification">
<%= text_field_tag :delete_confirmation %>
@@ -10,7 +10,7 @@
<% if @user == current_user %>
<p>
<strong><%= t "note" %></strong>:
<%= t("avatars.text_change_gravatar_html", gravatar_url: link_to("gravatar.com", "https://www.gravatar.com")).html_safe %>
<%= t("avatars.text_change_gravatar_html", gravatar_url: link_to("gravatar.com", "https://www.gravatar.com")) %>
</p>
<% end %>
</fieldset>
@@ -43,7 +43,7 @@
download: mime_type_pdf? ? nil : "download",
data: {
"job-status-polling-target": "download"
})
}
)
end
end
@@ -52,9 +52,9 @@
<% end %>
<p>
<%= t(
"ldap_groups.synchronized_filters.destroy.verification",
name: "<em class=\"danger-zone--expected-value\">#{h(@filter.name)}</em>"
).html_safe %>
"ldap_groups.synchronized_filters.destroy.verification_html",
name: content_tag(:em, @filter.name, class: "danger-zone--expected-value")
) %>
</p>
<div class="danger-zone--verification">
@@ -20,8 +20,8 @@
<br>
<%= t(
"ldap_groups.synchronized_groups.destroy.verification",
name: "<em class=\"danger-zone--expected-value\">#{h(@group.dn)}</em>"
).html_safe %>
name: content_tag(:em, @group.dn, class: "danger-zone--expected-value")
) %>
</p>
<div class="danger-zone--verification">
<input type="text">
+2 -2
View File
@@ -48,7 +48,7 @@ en:
title: 'Remove synchronized filter %{name}'
confirmation: "If you continue, the synchronized filter %{name} and all groups %{groups_count} created through it will be removed."
removed_groups: "Warning: This will remove the following groups from OpenProject and remove it from all projects!"
verification: "Enter the filter name %{name} to verify the deletion."
verification_html: "Enter the filter name %{name} to verify the deletion."
form:
group_name_attribute_text: 'Enter the attribute of the LDAP group used for setting the OpenProject group name.'
filter_string_text: 'Enter the RFC4515 LDAP filter that returns groups in your LDAP to synchronize with OpenProject.'
@@ -63,7 +63,7 @@ en:
title: 'Remove synchronized group %{name}'
confirmation: "If you continue, the synchronized group %{name} and all %{users_count} users synchronized through it will be removed."
info: "Note: The OpenProject group itself and members added outside this LDAP synchronization will not be removed."
verification: "Enter the group's name %{name} to verify the deletion."
verification_html: "Enter the group's name %{name} to verify the deletion."
help_text_html: |
This module allows you to set up a synchronization between LDAP and OpenProject groups.
It depends on LDAP groups need to use the <em>groupOfNames / memberOf</em> attribute set to be working with OpenProject.
@@ -52,6 +52,6 @@ class BaseErrorsComponent < ApplicationComponent
def joined_messages
messages = @keys.map { |key| @errors.full_messages_for(key) }.flatten
helpers.safe_join(messages, "<br />".html_safe)
helpers.safe_join(messages, helpers.tag(:br))
end
end
@@ -119,13 +119,16 @@ class MeetingsController < ApplicationController
@meeting = call.result
if call.success?
text = I18n.t(:notice_successful_create)
text = ActiveSupport::SafeBuffer.new
text << I18n.t(:notice_successful_create)
unless User.current.pref.time_zone?
link = I18n.t(:notice_timezone_missing, zone: formatted_time_zone_offset)
text += " #{view_context.link_to(link, { controller: '/my', action: :locale, anchor: 'pref_time_zone' },
class: 'link_to_profile')}"
text << " "
text << view_context.link_to(link,
{ controller: "/my", action: :locale, anchor: "pref_time_zone" },
class: "link_to_profile")
end
flash[:notice] = text.html_safe # rubocop:disable Rails/OutputSafety
flash[:notice] = text
redirect_to status: :see_other, action: "show", id: @meeting
else
@@ -317,7 +320,7 @@ class MeetingsController < ApplicationController
.call
.on_failure { |call| render_500(message: call.message) }
.on_success do |call|
send_data call.result, filename: filename_for_content_disposition("#{@meeting.title}.ics")
send_data call.result, filename: filename_for_content_disposition("#{@meeting.title}.ics")
end
end
@@ -379,8 +382,8 @@ class MeetingsController < ApplicationController
def exit_draft_mode
call = ::Meetings::UpdateService
.new(user: current_user, model: @meeting)
.call({ state: "open", notify: meeting_params[:notify] == "1" })
.new(user: current_user, model: @meeting)
.call({ state: "open", notify: meeting_params[:notify] == "1" })
if call.success?
deliver_invitation_mails
@@ -419,11 +422,11 @@ class MeetingsController < ApplicationController
.participants
.invited
.find_each do |participant|
MeetingMailer.invited(
@meeting,
participant.user,
User.current
).deliver_later
MeetingMailer.invited(
@meeting,
participant.user,
User.current
).deliver_later
end
end
@@ -646,12 +649,13 @@ class MeetingsController < ApplicationController
service = MeetingNotificationService.new(@meeting)
result = service.call(:invited)
message = if result.success?
I18n.t(:notice_successful_notification)
else
I18n.t(:error_notification_with_errors,
recipients: result.errors.map(&:name).join("; "))
end
message =
if result.success?
I18n.t(:notice_successful_notification)
else
I18n.t(:error_notification_with_errors,
recipients: result.errors.map(&:name).join("; "))
end
if type == :notify
flash[result.success? ? :notice : :error] = message
@@ -669,11 +673,11 @@ class MeetingsController < ApplicationController
.participants
.invited
.find_each do |participant|
MeetingSeriesMailer.invited(
recurring_meeting,
participant.user,
User.current
).deliver_later
MeetingSeriesMailer.invited(
recurring_meeting,
participant.user,
User.current
).deliver_later
end
render_success_flash_message_via_turbo_stream(message: I18n.t(:notice_successful_notification))
@@ -86,7 +86,7 @@ class RecurringMeetingsController < ApplicationController
@recurring_meeting = call.result
if call.success?
flash[:notice] = I18n.t(:notice_successful_create).html_safe
flash[:notice] = I18n.t(:notice_successful_create)
redirect_to project_meeting_path(@recurring_meeting.project, @recurring_meeting.template),
status: :see_other
else
@@ -36,7 +36,8 @@ See COPYRIGHT and LICENSE files for more details.
<%= t("openid_connect.delete_title") %>
</h3>
<p>
<%= t("provider.delete_warning.provider", name: content_tag(:strong, @provider.display_name)).html_safe %>
<%= t("provider.delete_warning.provider_html",
name: content_tag(:strong, @provider.display_name)) %>
</p>
<ul class="mb-3">
<li><%= t("provider.delete_warning.delete_result_1") %></li>
@@ -50,7 +51,8 @@ See COPYRIGHT and LICENSE files for more details.
<span><%= t("provider.delete_warning.irreversible_notice") %></span>
</p>
<p>
<%= t("provider.delete_warning.input_delete_confirmation", name: "<em class=\"danger-zone--expected-value\">#{h(@provider.display_name)}</em>").html_safe %>
<%= t("provider.delete_warning.input_delete_confirmation_html",
name: content_tag(:em, @provider.display_name, class: "danger-zone--expected-value")) %>
</p>
<div class="danger-zone--verification">
<%= text_field_tag :delete_confirmation %>
+2 -2
View File
@@ -51,9 +51,9 @@ en:
provider:
delete_warning:
input_delete_confirmation: Enter the provider name %{name} to confirm deletion.
input_delete_confirmation_html: Enter the provider name %{name} to confirm deletion.
irreversible_notice: Deleting an SSO provider is an irreversible action.
provider: 'Are you sure you want to delete the SSO provider %{name}? To confirm this action please enter the name of the provider in the field below, this will:'
provider_html: 'Are you sure you want to delete the SSO provider %{name}? To confirm this action please enter the name of the provider in the field below, this will:'
delete_result_1: Remove the provider from the list of available providers.
delete_result_user_count:
zero: No users are currently using this provider. No further action is required.
@@ -27,12 +27,12 @@
container_class: "-middle" %>
</div>
<div class="form--field-instructions">
<%= I18n.t(
<%= t(
"recaptcha.settings.captcha_description_html",
hcaptcha_link: link_to("https://docs.hcaptcha.com/switch/", "https://docs.hcaptcha.com/switch/", target: "_blank"),
recaptcha_link: link_to("https://www.google.com/recaptcha", "https://www.google.com/recaptcha", target: "_blank"),
turnstile_link: link_to("https://developers.cloudflare.com/turnstile/", "https://developers.cloudflare.com/turnstile/", target: "_blank")
).html_safe %>
) %>
</div>
</div>
<div class="form--field">
@@ -4,7 +4,11 @@
flex_layout(flex_items: :center) do |credentials_row|
credentials_row.with_row(mb: 3, test_selector: "storage-openproject_oauth_application_warning") do
render(Primer::Alpha::Banner.new(icon: :alert, scheme: :warning)) do
I18n.t("storages.instructions.oauth_application_details", oauth_application_details_link: oauth_application_details_link).html_safe
helpers.link_translate(
"storages.instructions.oauth_application_details",
links: { oauth_application_details_link: ::Storages::UrlBuilder.url(storage.uri, "settings/admin/openproject") },
external: true
)
end
end
@@ -40,16 +40,6 @@ module Storages::Admin
def self.wrapper_key = :storage_openproject_oauth_section
def oauth_application_details_link
render(
Primer::Beta::Link.new(
href: ::Storages::UrlBuilder.url(storage.uri, "settings/admin/openproject"),
data: { allow_external_link: true },
target: "_blank"
)
) { I18n.t("storages.instructions.oauth_application_details_link_text") }
end
def submit_button_options
{
scheme: :primary,
@@ -43,7 +43,8 @@ See COPYRIGHT and LICENSE files for more details.
<% dialog.with_additional_details do %>
<% render(Primer::OpenProject::FlexLayout.new) do |flex| %>
<% flex.with_row do %>
<%= t("storages.delete_warning.storage", file_storage: "<strong>#{h(@storage.name)}</strong>").html_safe %>
<%= t("storages.delete_warning.storage_html",
file_storage: content_tag(:strong, @storage.name)) %>
<% end %>
<% flex.with_row do %>
<ul>
@@ -44,7 +44,8 @@ See COPYRIGHT and LICENSE files for more details.
<% dialog.with_additional_details do %>
<% render(Primer::OpenProject::FlexLayout.new) do |flex| %>
<% flex.with_row do %>
<%= t("storages.delete_warning.project_storage", file_storage: "<strong>#{h(@project_storage.storage.name)}</strong>").html_safe %>
<%= t("storages.delete_warning.project_storage_html",
file_storage: content_tag(:strong, @project_storage.storage.name)) %>
<% end %>
<% flex.with_row do %>
<ul>

Some files were not shown because too many files have changed in this diff Show More