diff --git a/.gitignore b/.gitignore
index a677a999f57..40406202280 100644
--- a/.gitignore
+++ b/.gitignore
@@ -114,7 +114,6 @@ npm-debug.log*
node_modules/
# Ignore global package-lock.json that generates
-/package-lock.json
plaintext.yml
structure.sql
diff --git a/app/components/settings/project_life_cycle_step_definitions/form_header_component.html.erb b/app/components/settings/project_life_cycle_step_definitions/form_header_component.html.erb
index 13a2b1f52a1..2900f2ef9d8 100644
--- a/app/components/settings/project_life_cycle_step_definitions/form_header_component.html.erb
+++ b/app/components/settings/project_life_cycle_step_definitions/form_header_component.html.erb
@@ -27,8 +27,12 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
+<% helpers.html_title t(:label_administration),
+ t("settings.project_phase_definitions.heading"),
+ definition_heading %>
+
<%= render Primer::OpenProject::PageHeader.new do |header|
- header.with_title { t("settings.project_phase_definitions.#{heading_scope}.heading") }
+ header.with_title { definition_heading }
header.with_description { t("settings.project_phase_definitions.new.description") }
header.with_breadcrumbs(breadcrumbs_items)
end %>
diff --git a/app/components/settings/project_life_cycle_step_definitions/form_header_component.rb b/app/components/settings/project_life_cycle_step_definitions/form_header_component.rb
index a1f8a770b1b..ea694a38df5 100644
--- a/app/components/settings/project_life_cycle_step_definitions/form_header_component.rb
+++ b/app/components/settings/project_life_cycle_step_definitions/form_header_component.rb
@@ -31,7 +31,13 @@
module Settings
module ProjectLifeCycleStepDefinitions
class FormHeaderComponent < ApplicationComponent
- options :heading_scope
+ def definition_heading
+ if model.persisted?
+ model.name
+ else
+ t("settings.project_phase_definitions.new.heading")
+ end
+ end
def breadcrumbs_items
[
@@ -47,7 +53,7 @@ module Settings
href: admin_settings_project_phase_definitions_path,
text: t("settings.project_phase_definitions.heading")
},
- t("settings.project_phase_definitions.#{heading_scope}.heading")
+ definition_heading
]
end
end
diff --git a/app/contracts/attachments/create_contract.rb b/app/contracts/attachments/create_contract.rb
index 2ec0d41d3fe..a3be099f6bc 100644
--- a/app/contracts/attachments/create_contract.rb
+++ b/app/contracts/attachments/create_contract.rb
@@ -68,29 +68,29 @@ module Attachments
end
##
- # Validates the content type, if a whitelist is set
+ # Validates the content type, if a allowlist is set
def validate_content_type
- # If the whitelist is empty, assume all files are allowed
+ # If the allowlist is empty, assume all files are allowed
# as before
- unless matches_whitelist?(attachment_whitelist)
- Rails.logger.info { "Uploaded file #{model.filename} with type #{model.content_type} does not match whitelist" }
- errors.add :content_type, :not_whitelisted, value: model.content_type
+ unless matches_allowlist?(attachment_allowlist)
+ Rails.logger.info { "Uploaded file #{model.filename} with type #{model.content_type} does not match allowlist" }
+ errors.add :content_type, :not_allowlisted, value: model.content_type
end
end
##
- # Get the user-defined whitelist or a custom whitelist
+ # Get the user-defined allowlist or a custom allowlist
# defined for this invocation
- def attachment_whitelist
- Array(options.fetch(:whitelist, Setting.attachment_whitelist))
+ def attachment_allowlist
+ Array(options.fetch(:allowlist, Setting.attachment_whitelist))
end
##
- # Returns whether the attachment matches the whitelist
- def matches_whitelist?(whitelist)
- return true if whitelist.empty?
+ # Returns whether the attachment matches the allowlist
+ def matches_allowlist?(allowlist)
+ return true if allowlist.empty?
- whitelist.include?(model.content_type) || whitelist.include?("*#{model.extension}")
+ allowlist.include?(model.content_type) || allowlist.include?("*#{model.extension}")
end
end
end
diff --git a/app/contracts/work_packages/base_contract.rb b/app/contracts/work_packages/base_contract.rb
index 65edad4d4a9..7f1335dca63 100644
--- a/app/contracts/work_packages/base_contract.rb
+++ b/app/contracts/work_packages/base_contract.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -289,6 +291,7 @@ module WorkPackages
def validate_parent_not_self
if model.parent == model
+ errors.delete(:parent_id) # remove the error added by closure_tree's cycle detection
errors.add :parent, :cannot_be_self_assigned
end
end
@@ -304,11 +307,22 @@ module WorkPackages
if model.parent_id_changed? &&
model.parent_id &&
errors.exclude?(:parent) &&
- WorkPackage.relatable(model, Relation::TYPE_PARENT).where(id: model.parent_id).empty?
+ current_parent_unrelatable?
+ # closure_tree adds an error on :parent_id because of the cycle
+ # detection, and active_record sees the error when saving the children
+ # association and adds an error on :children as well. We need to remove
+ # them.
+ errors.delete(:parent_id) # remove the error added by closure_tree
+ errors.delete(:children) # remove the error added by active_record
+ # add our own error
errors.add :parent, :cant_link_a_work_package_with_a_descendant
end
end
+ def current_parent_unrelatable?
+ WorkPackage.relatable(model, Relation::TYPE_PARENT).where(id: model.parent_id).empty?
+ end
+
def validate_status_exists
errors.add :status, :does_not_exist if model.status && !status_exists?
end
diff --git a/app/controllers/work_packages/activities_tab_controller.rb b/app/controllers/work_packages/activities_tab_controller.rb
index 38c9c6cc656..319c03e7b5f 100644
--- a/app/controllers/work_packages/activities_tab_controller.rb
+++ b/app/controllers/work_packages/activities_tab_controller.rb
@@ -111,7 +111,6 @@ class WorkPackages::ActivitiesTabController < ApplicationController
call = create_journal_service_call
if call.success? && call.result
- claim_journal_attachments_for(call.result)
set_last_server_timestamp_to_headers
handle_successful_create_call(call)
else
@@ -129,7 +128,6 @@ class WorkPackages::ActivitiesTabController < ApplicationController
call = update_journal_service_call
if call.success? && call.result
- claim_journal_attachments_for(call.result)
update_item_show_component(journal: call.result, grouped_emoji_reactions: grouped_emoji_reactions_for_journal)
else
handle_failed_create_or_update_call(call)
@@ -321,12 +319,6 @@ class WorkPackages::ActivitiesTabController < ApplicationController
Journals::UpdateService.new(model: @journal, user: User.current).call(notes:)
end
- def claim_journal_attachments_for(journal)
- WorkPackages::ActivitiesTab::CommentAttachmentsClaims::ClaimsService
- .new(user: User.current, model: journal)
- .call
- end
-
def generate_time_based_update_streams(last_update_timestamp)
journals = @work_package
.journals
diff --git a/app/services/add_work_package_note_service.rb b/app/services/add_work_package_note_service.rb
index 929c3b2aa09..34efb080103 100644
--- a/app/services/add_work_package_note_service.rb
+++ b/app/services/add_work_package_note_service.rb
@@ -44,7 +44,7 @@ class AddWorkPackageNoteService
self.contract_class = WorkPackages::CreateNoteContract
end
- def call(notes, send_notifications: nil, internal: false)
+ def call(notes, send_notifications: nil, internal: false) # rubocop:disable Metrics/AbcSize
in_context(work_package, send_notifications:) do
work_package.add_journal(user:, notes:, internal:)
@@ -60,7 +60,18 @@ class AddWorkPackageNoteService
end
end
- ServiceResult.new(success:, result: journal, errors:)
+ ServiceResult.new(success:, result: journal, errors:).on_success do |r|
+ attachments_claims = claim_attachments_for(r.result)
+ r.add_dependent!(attachments_claims)
+ end
end
end
+
+ private
+
+ def claim_attachments_for(journal)
+ WorkPackages::ActivitiesTab::CommentAttachmentsClaims::ClaimsService
+ .new(user: User.current, model: journal)
+ .call
+ end
end
diff --git a/app/services/attachments/base_service.rb b/app/services/attachments/base_service.rb
index 3ebf934fd84..fda62e662b3 100644
--- a/app/services/attachments/base_service.rb
+++ b/app/services/attachments/base_service.rb
@@ -36,8 +36,8 @@ module Attachments
# @param whitelist A custom whitelist to validate with, or empty to disable validation
#
# Warning: When passing an empty whitelist, this results in no validations on the content type taking place.
- def self.bypass_whitelist(user:, whitelist: [])
- new(user:, contract_options: { whitelist: whitelist.map(&:to_s) })
+ def self.bypass_allowlist(user:, allowlist: [])
+ new(user:, contract_options: { allowlist: allowlist.map(&:to_s) })
end
end
end
diff --git a/app/services/attachments/finish_direct_upload_service.rb b/app/services/attachments/finish_direct_upload_service.rb
new file mode 100644
index 00000000000..545a9e10e54
--- /dev/null
+++ b/app/services/attachments/finish_direct_upload_service.rb
@@ -0,0 +1,140 @@
+# frozen_string_literal: true
+
+# -- 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.
+# ++
+
+module Attachments
+ class FinishDirectUploadService < BaseServices::BaseContracted
+ def initialize(user:, model:, contract_class: nil, contract_options: {})
+ self.model = model
+ super(user:, contract_class:, contract_options:)
+ end
+
+ protected
+
+ alias_method :attachment, :model
+
+ def service_context(send_notifications:, &)
+ # Overwriting the call to in_context to place the semaphore on the container and not on the attachment.
+ # Since the service will write a journal on the container, there should not be another
+ # process that modifies the container while this service is running.
+ in_context(attachment.container, send_notifications:, &)
+ end
+
+ def validate_params(_params)
+ super.tap do |call|
+ validate_local_file_exists(call)
+ end
+ end
+
+ def before_perform(*)
+ super.tap do
+ set_attachment_parameters
+ end
+ end
+
+ def persist(call)
+ super.tap do |service_result|
+ unless attachment.save
+ service_result.errors = attachment.errors
+ service_result.success = false
+ end
+ end
+ end
+
+ def after_perform(service_call)
+ super.tap do
+ journalize_container
+ attachment_created_event
+ schedule_jobs
+ end
+ end
+
+ def validate_local_file_exists(call)
+ unless local_file
+ call.errors.add(:base, "File for attachment #{attachment.filename} was not uploaded.")
+ call.success = false
+ end
+ end
+
+ def set_attachment_parameters
+ attachment.extend(OpenProject::ChangedBySystem)
+ attachment.change_by_system do
+ attachment.status = :uploaded
+ attachment.file = local_file
+ end
+ end
+
+ def schedule_jobs
+ attachment.extract_fulltext
+ end
+
+ def journalize_container
+ journable = attachment.container
+
+ return unless journable&.class&.journaled?
+
+ # Touching the journable will lead to the journal created next having its own timestamp.
+ # That timestamp will not adequately reflect the time the attachment was uploaded. This job
+ # right here might be executed way later than the time the attachment was uploaded. Ideally,
+ # the journals would be created bearing the time stamps of the attachment's created_at.
+ # This remains a TODO.
+ # But with the timestamp update in place as it is, at least the collapsing of aggregated journals
+ # from days before with the newly uploaded attachment is prevented.
+ touch_journable(journable)
+
+ Journals::CreateService
+ .new(journable, attachment.author)
+ .call
+ end
+
+ def touch_journable(journable)
+ # Not using touch here on purpose,
+ # as to avoid changing lock versions on the journables for this change
+ attributes = journable.send(:timestamp_attributes_for_update_in_model)
+
+ timestamps = attributes.index_with { Time.current }
+ journable.update_columns(timestamps) if timestamps.any?
+ end
+
+ def attachment_created_event
+ OpenProject::Notifications.send(
+ OpenProject::Events::ATTACHMENT_CREATED,
+ attachment:
+ )
+ end
+
+ def default_contract_class
+ ::Attachments::CreateContract
+ end
+
+ def local_file
+ attachment&.diskfile
+ end
+ end
+end
diff --git a/app/services/journals/update_service.rb b/app/services/journals/update_service.rb
index db3ba619ede..b6837cd920f 100644
--- a/app/services/journals/update_service.rb
+++ b/app/services/journals/update_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -31,11 +33,28 @@ module Journals
protected
def after_perform(call)
+ if call.success? && activity_comment?(call.result)
+ attachments_claims = claim_attachments_for(call.result)
+ call.add_dependent!(attachments_claims)
+ end
+
OpenProject::Notifications.send(OpenProject::Events::JOURNAL_UPDATED,
journal: call.result,
send_notification: Journal::NotificationConfiguration.active?)
call
end
+
+ private
+
+ def activity_comment?(journal)
+ journal.notes.present?
+ end
+
+ def claim_attachments_for(journal)
+ WorkPackages::ActivitiesTab::CommentAttachmentsClaims::ClaimsService
+ .new(user: User.current, model: journal)
+ .call
+ end
end
end
diff --git a/app/services/reminders/update_service.rb b/app/services/reminders/update_service.rb
index e7a24243ab4..4a8365d78be 100644
--- a/app/services/reminders/update_service.rb
+++ b/app/services/reminders/update_service.rb
@@ -31,17 +31,9 @@ module Reminders
include Reminders::ServiceHelpers
def after_perform(service_call)
- reschedule_reminder(service_call.result) if remind_at_changed?
+ reschedule_reminder(service_call.result) if model.saved_change_to_remind_at?
service_call
end
-
- private
-
- def remind_at_changed?
- # For some reason reminder.remind_at_changed? returns false
- # so we assume a change if remind_at is present in the params (would have passed contract validation)
- params[:remind_at].present?
- end
end
end
diff --git a/app/services/work_packages/schedule_dependency.rb b/app/services/work_packages/schedule_dependency.rb
index 76f291a83a0..bac24732f6c 100644
--- a/app/services/work_packages/schedule_dependency.rb
+++ b/app/services/work_packages/schedule_dependency.rb
@@ -102,13 +102,21 @@ class WorkPackages::ScheduleDependency
def automatically_scheduled_ancestors(work_package)
@automatically_scheduled_ancestors ||= {}
@automatically_scheduled_ancestors[work_package] ||= begin
- parent = parent_of(work_package)
+ work_packages_to_process = [work_package]
+ result = []
+ processed_ids = Set.new
- if parent&.schedule_automatically?
- [parent, *automatically_scheduled_ancestors(parent)]
- else
- []
+ while current = work_packages_to_process.shift
+ processed_ids.add(current.id)
+
+ parent = parent_of(current)
+
+ if parent&.schedule_automatically?
+ result << parent unless parent.id == work_package.id
+ work_packages_to_process << parent unless processed_ids.include?(parent.id)
+ end
end
+ result
end
end
diff --git a/app/services/work_packages/schedule_dependency/dependency_graph.rb b/app/services/work_packages/schedule_dependency/dependency_graph.rb
index ee40436716b..d7dcd7a8aa2 100644
--- a/app/services/work_packages/schedule_dependency/dependency_graph.rb
+++ b/app/services/work_packages/schedule_dependency/dependency_graph.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -62,9 +64,28 @@ class WorkPackages::ScheduleDependency::DependencyGraph
def full_dependencies_of(work_package_id)
@full_dependent_ids ||= {}
@full_dependent_ids[work_package_id] ||= begin
- ids = dependent_ids_for_work_package_id(work_package_id)
- ids += ids.flat_map { full_dependencies_of(_1) }
- ids.uniq
+ visited = Set.new
+ stack = [work_package_id]
+ result = Set.new
+
+ while stack.any?
+ current_id = stack.pop
+
+ visited.add(current_id)
+
+ # Get direct dependencies for current work package
+ dependent_ids = dependent_ids_for_work_package_id(current_id)
+
+ dependent_ids.each do |dependent_id|
+ # Add to result set
+ result.add(dependent_id) unless dependent_id == work_package_id
+
+ # Add to stack for further processing (if not already visited)
+ stack.push(dependent_id) unless visited.include?(dependent_id)
+ end
+ end
+
+ result.to_a
end
end
diff --git a/app/views/admin/settings/project_life_cycle_definitions/form.html.erb b/app/views/admin/settings/project_life_cycle_definitions/form.html.erb
index ceab5ffd9f8..172200816ff 100644
--- a/app/views/admin/settings/project_life_cycle_definitions/form.html.erb
+++ b/app/views/admin/settings/project_life_cycle_definitions/form.html.erb
@@ -27,12 +27,6 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
-<% heading_scope = @definition.persisted? ? :edit : :new %>
-
-<% html_title t(:label_administration),
- t("settings.project_phase_definitions.heading"),
- t("settings.project_phase_definitions.#{heading_scope}.heading") %>
-
-<%= render Settings::ProjectLifeCycleStepDefinitions::FormHeaderComponent.new(@definition, heading_scope:) %>
+<%= render Settings::ProjectLifeCycleStepDefinitions::FormHeaderComponent.new(@definition) %>
<%= render Projects::LifeCycleStepDefinitions::FormComponent.new(@definition) %>
diff --git a/app/views/custom_actions/_form.html.erb b/app/views/custom_actions/_form.html.erb
index 8dc2f99e13a..d467f6392db 100644
--- a/app/views/custom_actions/_form.html.erb
+++ b/app/views/custom_actions/_form.html.erb
@@ -85,6 +85,7 @@
<% selected = action.value_objects.map { |v| { id: v[:value], name: v[:label] } } %>
<%= angular_component_tag "opce-user-autocompleter",
inputs: {
+ multiple: action.multi_value?,
hideSelected: true,
defaultData: false,
placeholder: I18n.t(:label_user_search),
diff --git a/app/workers/attachments/finish_direct_upload_job.rb b/app/workers/attachments/finish_direct_upload_job.rb
index 18865d4cc3d..0c295ac4d34 100644
--- a/app/workers/attachments/finish_direct_upload_job.rb
+++ b/app/workers/attachments/finish_direct_upload_job.rb
@@ -29,115 +29,53 @@
class Attachments::FinishDirectUploadJob < ApplicationJob
queue_with_priority :high
- def perform(attachment_id, whitelist: true)
+ def perform(attachment_id, allowlist: true)
attachment = Attachment.pending_direct_upload.find_by(id: attachment_id)
- # An attachment is guaranteed to have a file.
- # But if the attachment is nil the expression attachment&.file will be nil and attachment&.file.local_file
- # will throw a NoMethodError: undefined method local_file' for nil:NilClass`.
- local_file = attachment && attachment.file.local_file
- if local_file.nil?
- return Rails.logger.error("File for attachment #{attachment_id} was not uploaded.")
+ unless attachment
+ log_not_found(attachment_id)
+ return
end
- User.execute_as(attachment.author) do
- attach_uploaded_file(attachment, local_file, whitelist)
+ Attachments::FinishDirectUploadService
+ .new(user: attachment.author, model: attachment, contract_options: derive_contract_options(allowlist))
+ .call
+ .on_failure do |call|
+ destroy_attachment_and_log_errors(attachment, call.errors)
end
end
private
- def attach_uploaded_file(attachment, local_file, whitelist)
- set_attributes_from_file(attachment, local_file)
- validate_attachment(attachment, whitelist)
- save_attachment(attachment)
- journalize_container(attachment)
- attachment_created_event(attachment)
- schedule_jobs(attachment)
- rescue StandardError => e
- ::OpenProject.logger.error e
+ def destroy_attachment_and_log_errors(attachment, errors)
attachment.destroy
- ensure
- FileUtils.rm_rf(local_file.path)
+ errors = errors.full_messages
+
+ OpenProject.logger.error(
+ <<~MSG
+ Failed to finish attachment upload for:
+ * user: #{attachment.author_id} - #{attachment.author.name}
+ * container: #{attachment.container_id} - #{attachment.container}
+ * attachment file name: #{attachment.filename}
+
+ Errors:
+ #{errors.join("\n ")}
+ MSG
+ )
end
- def set_attributes_from_file(attachment, local_file)
- attachment.extend(OpenProject::ChangedBySystem)
- attachment.change_by_system do
- attachment.status = :uploaded
- attachment.set_file_size local_file
- attachment.set_content_type local_file
- attachment.set_digest local_file
- end
+ def log_not_found(attachment_id)
+ OpenProject.logger.error("Attachment #{attachment_id} not found")
end
- def save_attachment(attachment)
- attachment.save! if attachment.changed?
- end
-
- def validate_attachment(attachment, whitelist)
- contract = create_contract attachment, whitelist
-
- unless contract.valid?
- errors = contract.errors.full_messages.join(", ")
- raise "Failed to validate attachment #{attachment.id}: #{errors}"
- end
- end
-
- def create_contract(attachment, whitelist)
- options = derive_contract_options(whitelist)
- ::Attachments::CreateContract.new attachment,
- attachment.author,
- options:
- end
-
- def schedule_jobs(attachment)
- attachment.extract_fulltext
- end
-
- def derive_contract_options(whitelist)
- case whitelist
+ def derive_contract_options(allowlist)
+ case allowlist
when false
- { whitelist: [] }
+ { allowlist: [] }
when Array
- { whitelist: whitelist.map(&:to_s) }
+ { allowlist: allowlist.map(&:to_s) }
else
{}
end
end
-
- def journalize_container(attachment)
- journable = attachment.container
-
- return unless journable&.class&.journaled?
-
- # Touching the journable will lead to the journal created next having its own timestamp.
- # That timestamp will not adequately reflect the time the attachment was uploaded. This job
- # right here might be executed way later than the time the attachment was uploaded. Ideally,
- # the journals would be created bearing the time stamps of the attachment's created_at.
- # This remains a TODO.
- # But with the timestamp update in place as it is, at least the collapsing of aggregated journals
- # from days before with the newly uploaded attachment is prevented.
- touch_journable(journable)
-
- Journals::CreateService
- .new(journable, attachment.author)
- .call
- end
-
- def touch_journable(journable)
- # Not using touch here on purpose,
- # as to avoid changing lock versions on the journables for this change
- attributes = journable.send(:timestamp_attributes_for_update_in_model)
-
- timestamps = attributes.index_with { Time.now }
- journable.update_columns(timestamps) if timestamps.any?
- end
-
- def attachment_created_event(attachment)
- OpenProject::Notifications.send(
- OpenProject::Events::ATTACHMENT_CREATED,
- attachment:
- )
- end
end
diff --git a/app/workers/backup_job.rb b/app/workers/backup_job.rb
index 69e75e75b58..9be533fb694 100644
--- a/app/workers/backup_job.rb
+++ b/app/workers/backup_job.rb
@@ -134,7 +134,7 @@ class BackupJob < ApplicationJob
def store_backup(file_name, backup:, user:)
File.open(file_name) do |file|
call = Attachments::CreateService
- .bypass_whitelist(user:)
+ .bypass_allowlist(user:)
.call(container: backup, filename: file_name, file:, description: "OpenProject backup")
call.on_success do
diff --git a/app/workers/exports/export_job.rb b/app/workers/exports/export_job.rb
index 95b28bc110e..fac92e52533 100644
--- a/app/workers/exports/export_job.rb
+++ b/app/workers/exports/export_job.rb
@@ -117,7 +117,7 @@ module Exports
filename = clean_filename(export_result)
call = Attachments::CreateService
- .bypass_whitelist(user: User.current)
+ .bypass_allowlist(user: User.current)
.call(container:, file:, filename:, description: "")
call.on_success do
diff --git a/config/constants/settings/definition.rb b/config/constants/settings/definition.rb
index afaf76c0ef3..3dd5f771cd9 100644
--- a/config/constants/settings/definition.rb
+++ b/config/constants/settings/definition.rb
@@ -1160,7 +1160,7 @@ module Settings
default: {
"workers" => 2,
"timeout" => Rails.env.production? ? 120 : 0,
- "wait_timeout" => 10,
+ "wait_timeout" => 30,
"min_threads" => 4,
"max_threads" => 16
},
diff --git a/config/initializers/menus.rb b/config/initializers/menus.rb
index 497b954f6cf..cd650ae2a69 100644
--- a/config/initializers/menus.rb
+++ b/config/initializers/menus.rb
@@ -122,7 +122,14 @@ Redmine::MenuManager.map :account_menu do |menu|
}
menu.push :logout,
:signout_path,
- if: ->(_) { User.current.logged? }
+ if: ->(_) { User.current.logged? },
+ html: {
+ data: {
+ # Turbo-drive needs to be disabled, as we might redirect to other origins
+ # as a result here (e.g., post logout SSO redirects).
+ turbo: false
+ }
+ }
end
Redmine::MenuManager.map :global_menu do |menu|
diff --git a/config/locales/crowdin/af.yml b/config/locales/crowdin/af.yml
index d776332be88..b43d48d2959 100644
--- a/config/locales/crowdin/af.yml
+++ b/config/locales/crowdin/af.yml
@@ -1208,7 +1208,7 @@ af:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3932,8 +3932,6 @@ af:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/ar.yml b/config/locales/crowdin/ar.yml
index 877189c9f37..dcd2e9867f6 100644
--- a/config/locales/crowdin/ar.yml
+++ b/config/locales/crowdin/ar.yml
@@ -1240,7 +1240,7 @@ ar:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -4094,8 +4094,6 @@ ar:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/az.yml b/config/locales/crowdin/az.yml
index 0dcbfb6065d..3611c4462cf 100644
--- a/config/locales/crowdin/az.yml
+++ b/config/locales/crowdin/az.yml
@@ -1208,7 +1208,7 @@ az:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3932,8 +3932,6 @@ az:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/be.yml b/config/locales/crowdin/be.yml
index 3beb487fb48..08426e7229f 100644
--- a/config/locales/crowdin/be.yml
+++ b/config/locales/crowdin/be.yml
@@ -1224,7 +1224,7 @@ be:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -4014,8 +4014,6 @@ be:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/bg.yml b/config/locales/crowdin/bg.yml
index 2bf30d5811f..c5c1ec83191 100644
--- a/config/locales/crowdin/bg.yml
+++ b/config/locales/crowdin/bg.yml
@@ -1208,7 +1208,7 @@ bg:
attributes:
content_type:
blank: "Типът на съдържанието на файла не може да бъде празен."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3932,8 +3932,6 @@ bg:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/ca.yml b/config/locales/crowdin/ca.yml
index 72a9b833290..17288f0b19b 100644
--- a/config/locales/crowdin/ca.yml
+++ b/config/locales/crowdin/ca.yml
@@ -1205,7 +1205,7 @@ ca:
attributes:
content_type:
blank: "El tipus de contingut del fitxer no pot ser buit."
- not_whitelisted: "El fitxer s'ha rebutjat per un filtre automàtic. '%{value}' no és a la llista blanca de càrrega."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3923,8 +3923,6 @@ ca:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/ckb-IR.yml b/config/locales/crowdin/ckb-IR.yml
index 09a02280ea8..acec5e8eff7 100644
--- a/config/locales/crowdin/ckb-IR.yml
+++ b/config/locales/crowdin/ckb-IR.yml
@@ -1208,7 +1208,7 @@ ckb-IR:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3932,8 +3932,6 @@ ckb-IR:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/cs.yml b/config/locales/crowdin/cs.yml
index 5e0e2b66942..536a0f7ffa1 100644
--- a/config/locales/crowdin/cs.yml
+++ b/config/locales/crowdin/cs.yml
@@ -1224,7 +1224,7 @@ cs:
attributes:
content_type:
blank: "Typ obsahu souboru nemůže být prázdný."
- not_whitelisted: "Soubor byl odmítnut automatickým filtrem. '%{value}' není na seznamu povolených pro nahrávání."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -4013,8 +4013,6 @@ cs:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/da.yml b/config/locales/crowdin/da.yml
index 3734866d0b9..3d90b0cff19 100644
--- a/config/locales/crowdin/da.yml
+++ b/config/locales/crowdin/da.yml
@@ -1206,7 +1206,7 @@ da:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3928,8 +3928,6 @@ da:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/de.yml b/config/locales/crowdin/de.yml
index 75697c8e2ff..797206165bb 100644
--- a/config/locales/crowdin/de.yml
+++ b/config/locales/crowdin/de.yml
@@ -1200,7 +1200,7 @@ de:
attributes:
content_type:
blank: "Der Content-Type der Datei darf nicht leer sein."
- not_whitelisted: "Die Datei wurde von einem automatischen Filter abgelehnt. '%{value}' ist nicht für Uploads freigegeben."
+ not_allowlisted: "Die Datei wurde von einem automatischen Filter abgelehnt. ‚%{value}‘ ist für den Upload nicht zulässig."
format: "%{message}"
capability:
context:
@@ -3924,8 +3924,6 @@ de:
new:
description: "Änderungen an dieser Projekt-Phase werden in allen sie verwendenden Projekten sichtbar."
heading: "Neue Phase"
- edit:
- heading: "Phase bearbeiten"
both_gate: "Gate zum Beginn und zum Ende"
no_gate: "Kein Gate"
start_gate: "Gate zum Beginn"
@@ -4300,12 +4298,12 @@ de:
message: "Das Teilen von Projektlisten mit einzelnen Nutzern ist ein Enterprise Add-on"
working_days:
info: >
- Days that are not selected are skipped when scheduling work packages and project life cycles (and not included in the day count). These can be overridden at a work-package level.
+ Tage, die nicht ausgewählt sind, werden bei der Planung von Arbeitspaketen und Projektphasen übersprungen (und bei der Tageszählung nicht berücksichtigt). Diese können auf Arbeitspaket-Ebene überschrieben werden.
instance_wide_info: >
Daten, die der Liste unten hinzugefügt werden, gelten als arbeitsfrei und werden bei der Planung von Arbeitspaketen übersprungen.
change_button: "Arbeitstage ändern"
warning: >
- Changing which days of the week are considered working days or non-working days can affect the start and finish days of all work packages and life cycles in all projects in this instance.
+ Die Änderung der Wochentage, die als Arbeitstage oder arbeitsfreie Tage gelten, kann sich auf die Start- und Endtage aller Arbeitspakete und Projektphasen in allen Projekten auswirken.
journal_note:
changed: _**Arbeitstage** geändert (%{changes})._
days:
diff --git a/config/locales/crowdin/el.yml b/config/locales/crowdin/el.yml
index 4af4b8ecbe5..1337cd317a0 100644
--- a/config/locales/crowdin/el.yml
+++ b/config/locales/crowdin/el.yml
@@ -1204,7 +1204,7 @@ el:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3927,8 +3927,6 @@ el:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/eo.yml b/config/locales/crowdin/eo.yml
index 50cf681815d..54b4fdb052a 100644
--- a/config/locales/crowdin/eo.yml
+++ b/config/locales/crowdin/eo.yml
@@ -1208,7 +1208,7 @@ eo:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3932,8 +3932,6 @@ eo:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/es.yml b/config/locales/crowdin/es.yml
index 61957478cc5..0877ebb6605 100644
--- a/config/locales/crowdin/es.yml
+++ b/config/locales/crowdin/es.yml
@@ -1205,7 +1205,7 @@ es:
attributes:
content_type:
blank: "El tipo de contenido del archivo no puede estar en blanco."
- not_whitelisted: "El archivo se rechazó a causa de un filtro automático. «%{value}» no se incluye en la lista de permitidos para carga."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3928,8 +3928,6 @@ es:
new:
description: "Los cambios en esta fase del proyecto se reflejarán en todos los proyectos en los que esté activada."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Puerta inicial y final"
no_gate: "Sin puerta"
start_gate: "Puerta de inicio"
diff --git a/config/locales/crowdin/et.yml b/config/locales/crowdin/et.yml
index deb4880c94e..6108e0c2d98 100644
--- a/config/locales/crowdin/et.yml
+++ b/config/locales/crowdin/et.yml
@@ -1208,7 +1208,7 @@ et:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3932,8 +3932,6 @@ et:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/eu.yml b/config/locales/crowdin/eu.yml
index 05b38c45ebd..22b642e384a 100644
--- a/config/locales/crowdin/eu.yml
+++ b/config/locales/crowdin/eu.yml
@@ -1208,7 +1208,7 @@ eu:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3932,8 +3932,6 @@ eu:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/fa.yml b/config/locales/crowdin/fa.yml
index 6908d2dad89..8dbd6e0556d 100644
--- a/config/locales/crowdin/fa.yml
+++ b/config/locales/crowdin/fa.yml
@@ -1208,7 +1208,7 @@ fa:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3932,8 +3932,6 @@ fa:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/fi.yml b/config/locales/crowdin/fi.yml
index 761acd182ac..e94e9918ac7 100644
--- a/config/locales/crowdin/fi.yml
+++ b/config/locales/crowdin/fi.yml
@@ -1208,7 +1208,7 @@ fi:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3932,8 +3932,6 @@ fi:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/fil.yml b/config/locales/crowdin/fil.yml
index b9d2e8a53ee..2816725986f 100644
--- a/config/locales/crowdin/fil.yml
+++ b/config/locales/crowdin/fil.yml
@@ -1208,7 +1208,7 @@ fil:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3930,8 +3930,6 @@ fil:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/fr.yml b/config/locales/crowdin/fr.yml
index 6ce959268db..c753659870a 100644
--- a/config/locales/crowdin/fr.yml
+++ b/config/locales/crowdin/fr.yml
@@ -1206,7 +1206,7 @@ fr:
attributes:
content_type:
blank: "Le type de contenu du fichier ne peut pas être vide."
- not_whitelisted: "Le fichier a été rejeté par un filtre automatique. « %{value} » n'est pas en liste blanche pour le téléversement."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3930,8 +3930,6 @@ fr:
new:
description: "Les modifications apportées à cette phase du projet seront répercutées dans tous les projets où elle est activée."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Porte de début et de fin"
no_gate: "Aucune porte"
start_gate: "Porte de début"
diff --git a/config/locales/crowdin/he.yml b/config/locales/crowdin/he.yml
index 6d86c64fdc3..be537593bbb 100644
--- a/config/locales/crowdin/he.yml
+++ b/config/locales/crowdin/he.yml
@@ -1224,7 +1224,7 @@ he:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -4014,8 +4014,6 @@ he:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/hi.yml b/config/locales/crowdin/hi.yml
index 981c28e83af..702a02ee0f7 100644
--- a/config/locales/crowdin/hi.yml
+++ b/config/locales/crowdin/hi.yml
@@ -1207,7 +1207,7 @@ hi:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3931,8 +3931,6 @@ hi:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/hr.yml b/config/locales/crowdin/hr.yml
index 66ba2c9d4d6..aa134ac20af 100644
--- a/config/locales/crowdin/hr.yml
+++ b/config/locales/crowdin/hr.yml
@@ -1216,7 +1216,7 @@ hr:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3973,8 +3973,6 @@ hr:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/hu.yml b/config/locales/crowdin/hu.yml
index 3b8224c2cb4..d23b733f22c 100644
--- a/config/locales/crowdin/hu.yml
+++ b/config/locales/crowdin/hu.yml
@@ -1207,7 +1207,7 @@ hu:
attributes:
content_type:
blank: "A fájltípus nem lehet üres."
- not_whitelisted: "Az automatikus szűrő visszautasította a fájlt. Feltöltésnél a '%{value}' nem engedélyezett."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}\n"
capability:
context:
@@ -3930,8 +3930,6 @@ hu:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/id.yml b/config/locales/crowdin/id.yml
index fbd5076af5e..37cb52e855c 100644
--- a/config/locales/crowdin/id.yml
+++ b/config/locales/crowdin/id.yml
@@ -1196,7 +1196,7 @@ id:
attributes:
content_type:
blank: "Jenis konten file tidak boleh kosong."
- not_whitelisted: "File ditolak oleh filter otomatis. '%{value}' tidak masuk daftar putih untuk unggahan."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3884,8 +3884,6 @@ id:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/it.yml b/config/locales/crowdin/it.yml
index 4e193fe2a70..d4a4b920c69 100644
--- a/config/locales/crowdin/it.yml
+++ b/config/locales/crowdin/it.yml
@@ -1205,7 +1205,7 @@ it:
attributes:
content_type:
blank: "Il tipo di contenuto del file non può essere vuoto."
- not_whitelisted: "Il file è stato rifiutato da un filtro automatico. Non è possibile caricare '%{value}'."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3929,8 +3929,6 @@ it:
new:
description: "Le modifiche a questa fase di progetto si rifletteranno in tutti i progetti in cui è abilitata."
heading: "Nuova fase"
- edit:
- heading: "Modifica fase"
both_gate: "Controlli di inizio e fine"
no_gate: "Nessun controllo"
start_gate: "Inizia controllo"
diff --git a/config/locales/crowdin/ja.yml b/config/locales/crowdin/ja.yml
index 9c1d51a5e75..184cefc6abb 100644
--- a/config/locales/crowdin/ja.yml
+++ b/config/locales/crowdin/ja.yml
@@ -37,8 +37,8 @@ ja:
label_submit_comment: "コメントする"
label_who: 誰が?
changed_on: "を変更した"
- created_on: "created this on"
- changed: "changed"
+ created_on: "これを作成したのは"
+ changed: "変更済み"
created: "作成する"
commented: "コメント"
internal_comment: 内部コメント
@@ -70,8 +70,8 @@ ja:
custom_colors: "カスタム色"
manage_colors: "色の選択オプションを編集"
instructions:
- primary-button-color: "Strong accent color, used for the most important button on a screen."
- accent-color: "Color for links and other decently highlighted elements."
+ primary-button-color: "画面上で最も重要なボタンに使用される強いアクセントカラー。"
+ accent-color: "リンクや強調表示された要素の色。"
header-item-bg-hover-color: "クリック可能なヘッダー項目にマウスを置いたときの背景色。"
header-item-font-color: "クリック可能なヘッダー項目のフォント色。"
header-item-font-hover-color: "クリック可能なヘッダー項目にマウスを置いたときのフォント色。"
@@ -97,7 +97,7 @@ ja:
contact: "デモについてはお問い合わせください"
enterprise_info_html: "は、Enterprise アドオンです。"
upgrade_info: "有料プランにアップグレードして、チームで使用を開始してください。"
- jemalloc_allocator: Jemalloc memory allocator
+ jemalloc_allocator: Jemalloc メモリアロケータ
journal_aggregation:
explanation:
text: "Individual actions of a user (e.g. updating a work package twice) are aggregated into a single action if their age difference is less than the specified timespan. They will be displayed as a single action within the application. This will also delay notifications by the same amount of time reducing the number of emails being sent and will also affect %{webhook_link} delay."
@@ -237,8 +237,8 @@ ja:
heading: "カスタムフィールド項目を削除しますか?"
description: "This action will irreversibly remove the item and all its sub-items. Any assigned values will be permanently deleted. If this field is required, removing items may cause existing work packages to become invalid."
placeholder:
- label: "Item label"
- short: "Short name"
+ label: "アイテムラベル"
+ short: "ショートネーム"
notice:
remember_items_and_projects: "Remember to set items and projects in the respective tabs for this custom field."
hierarchy:
@@ -286,16 +286,16 @@ ja:
short:
not_unique: "は、同じ階層レベル内で一意である必要があります。"
parent:
- not_descendant: "must be a descendant of the hierarchy root."
+ not_descendant: "階層ルートの子孫でなければなりません"
rules:
- depth: "Depth"
- item: "Item"
+ depth: "深さ"
+ item: "アイテム"
label: "Label"
- short: "Short name"
+ short: "ショートネーム"
parent: "親項目"
blueprint: "Pattern blueprint"
global_search:
- placeholder: "Search in %{app_title}"
+ placeholder: "%{app_title}で検索"
overwritten_tabs:
all: "全て"
messages: "フォーラム"
@@ -349,7 +349,7 @@ ja:
delete_modal:
title: "プロジェクトリストの削除"
heading: "このプロジェクトリストを削除しますか?"
- text: "This action will not delete any project the list contains. Are you sure you want to delete this project list?"
+ text: "この操作では、リストに含まれるプロジェクトは削除されません。本当にこのプロジェクトリストを削除しますか?"
settings:
header_details: 基本情報
header_status: プロジェクトの進捗状況
@@ -365,14 +365,14 @@ ja:
description: >
このプロジェクトは公開されています。このインスタンスにアクセスできる人は誰でも、自分の役割と関連する権限に応じて、このプロジェクトを表示し、操作することができます。サブプロジェクトは影響を受けず、独自の設定を持ちます。
private_confirmation:
- checkbox: "I understand that this will make the previously public content private."
- title: "Make this project private?"
+ checkbox: "これにより、公開されていたコンテンツが非公開になることを理解しています。"
+ title: "このプロジェクトを非公開にしますか?"
description: >
The project will only be visible to project members depending on their role and associated permissions. Sub-projects are not affected and have their own settings.
change_identifier: 識別子の変更
actions:
- label_enable_all: "Enable all"
- label_disable_all: "Disable all"
+ label_enable_all: "全て有効"
+ label_disable_all: "すべて無効"
activities:
no_results_title_text: 現在、有効な活動はありません。
forums:
@@ -398,7 +398,7 @@ ja:
title: "Project attributes"
description_html: 'These project attributes will be displayed in your project overview page under their respective sections. You can enable or disable individual attributes. Project attributes and sections are defined in the administration settings by the administrator of the instance. '
filter:
- label: "Search project attribute"
+ label: "プロジェクトの属性を検索"
actions:
label_enable_single: "Active in this project, click to disable"
label_disable_single: "Inactive in this project, click to enable"
@@ -490,14 +490,14 @@ ja:
my:
access_token:
create_dialog:
- title: Token generated
+ title: トークン生成完了
header: '%{type} トークンが生成されました'
warning: このトークンが表示されるのは今回だけなので、必ずコピーしてください。
errors:
- token_name_blank: "Please provide an API token name"
- token_name_in_use: "This API token name is already in use, please select a different one"
- new_access_token_dialog_title: "Create new API token"
- new_access_token_dialog_show_button_text: "API token"
+ token_name_blank: "APIトークン名を入力してください"
+ token_name_in_use: "この API トークン名は既に使用されています。別のトークンを選択してください"
+ new_access_token_dialog_title: "新しいAPIトークンを作成"
+ new_access_token_dialog_show_button_text: "APIトークン"
new_access_token_dialog_text_field_placeholder_text: "My API token"
new_access_token_dialog_text_field_label: "名称"
new_access_token_dialog_submit_button_text: "作成"
@@ -523,7 +523,7 @@ ja:
remembered_devices: "Remembered devices"
remembered_devices_caption: "A list of all devices that logged into this account using the 'Stay logged in' option."
session_name: "%{browser_name} %{browser_version} on %{os_name}"
- browser: "Browser"
+ browser: "ブラウザ"
device: "デバイス / OS"
unknown_browser: "不明なブラウザ"
unknown_os: "不明なオペレーティング システム"
@@ -890,7 +890,7 @@ ja:
uid: "クライアントID"
secret: "秘密鍵"
owner: "所有者"
- builtin: "Builtin"
+ builtin: "組み込み"
enabled: "アクティブ"
redirect_uri: "リダイレクトURI"
client_credentials_user_id: "クライアント資格情報ユーザーID"
@@ -967,11 +967,11 @@ ja:
custom_field_section: セクション
project/phase:
date_range: "期間"
- definition: "Definition"
+ definition: "定義"
duration: "Duration"
- start_date: "Start date"
- start_date_caption: "Follows the previous phase."
- finish_date: "Finish date"
+ start_date: "開始日"
+ start_date_caption: "前のフェーズに従います。"
+ finish_date: "終了日"
project/phase_definition:
name: "名称"
color: "色"
@@ -1198,7 +1198,7 @@ ja:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -1864,7 +1864,7 @@ ja:
#Use the strftime parameters for formats.
#When no format has been given, it uses default.
#You can provide other formats here if you like!
- default: "%m/%d/%Y"
+ default: "%Y / %m / %d"
long: "%B %d、%Y"
short: "%b%d"
#Don't forget the nil at the beginning; there's no such thing as a 0th month
@@ -1918,7 +1918,7 @@ ja:
x_months:
other: "%{count} ヶ月間"
x_years:
- other: "%{count} years"
+ other: "%{count} 年"
x_seconds:
other: "%{count} 秒"
x_seconds_abbreviated:
@@ -2177,7 +2177,7 @@ ja:
long_text_fields:
input_caption: "By default all long text fields are selected."
input_label: "Add long text fields"
- input_placeholder: "Search for long text fields"
+ input_placeholder: "長いテキストフィールドを検索"
drag_area_label: "Manage long text fields"
xls:
include_relations:
@@ -2813,7 +2813,7 @@ ja:
label_password_rule_special: "特殊文字"
label_password_rule_uppercase: "大文字"
label_path_encoding: "パスのエンコード"
- label_per_page: "ページ毎"
+ label_per_page: "1ページあたりの件数"
label_people: "人"
label_permissions: "権限"
label_permissions_report: "権限のレポート"
@@ -2914,7 +2914,7 @@ ja:
label_scroll_left: "Scroll left"
label_scroll_right: "Scroll right"
label_search: "検索"
- label_search_by_name: "Search by name"
+ label_search_by_name: "名前で検索"
label_send_information: "Send new credentials to the user"
label_send_test_email: "テスト電子メールを送信"
label_session: "セッション"
@@ -3417,7 +3417,7 @@ ja:
permission_protect_wiki_pages: "Wikiページの凍結"
permission_rename_wiki_pages: "Wikiページ名の変更"
permission_save_queries: "ビューを保存"
- permission_search_project: "Search project"
+ permission_search_project: "プロジェクトを検索"
permission_select_custom_fields: "カスタムフィールドの選択"
permission_select_project_custom_fields: "プロジェクト属性の選択"
permission_select_project_phases: "Select project phases"
@@ -3480,7 +3480,7 @@ ja:
warning_one: プロジェクトのメンバーは、プロジェクトのリポジトリを再配置する必要があります。
warning_two: プロジェクトへの既存のリンクは機能しなくなります。
title: プロジェクトの識別子を変更
- not_available: "Project N/A"
+ not_available: "プロジェクトなし"
template:
copying: >
プロジェクトは選択したテンプレートプロジェクトから作成されています。プロジェクトが利用可能になるとすぐにメールで通知されます。
@@ -3889,8 +3889,6 @@ ja:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
@@ -4303,7 +4301,7 @@ ja:
reminder: "The reminder you are looking for cannot be found or has been deleted."
expected:
date: "YYYY-MM-DD (ISO 8601 日付のみ)"
- datetime: "YYYY-MM-DDThh:mm:ss[.lll][+hh:mm] (any compatible ISO 8601 datetime)"
+ datetime: "YYYY-MM-DDThh:mm:ss[.lll][+h:mm] (ISO 8601形式の日時)"
duration: "ISO 8601 期間"
invalid_content_type: "コンテンツの種類は'%{content_type}'を期待されたが '%{actual}'を得ました。"
invalid_format: "不正なフォーマットのプロパティ '%{property}': 期待されるフォーマットは '%{expected_format}', ですが '%{actual}'を受け取りました."
diff --git a/config/locales/crowdin/js-de.yml b/config/locales/crowdin/js-de.yml
index bceaf9f7f94..e47e2db95e6 100644
--- a/config/locales/crowdin/js-de.yml
+++ b/config/locales/crowdin/js-de.yml
@@ -274,10 +274,10 @@ de:
change_button: "Speichern und neu planen"
change_title: "Arbeitstage ändern"
removed_title: "Sie werden die folgenden Tage aus der Liste der arbeitsfreien Tage entfernen:"
- change_description: "Changing which days of the week are considered working days or non-working days can affect the start and finish days of all work packages and life cycles in all projects in this instance."
+ change_description: "Die Änderung der Wochentage, die als Arbeitstage oder arbeitsfreie Tage gelten, kann sich auf die Start- und Endtage aller Arbeitspakete und Projektphasen in allen Projekten auswirken."
warning: >
- The changes might take some time to take effect. You will be notified when all relevant work packages and project life cycles have been updated.
- Are you sure you want to continue?
+ Es kann einige Zeit dauern, bis die Änderungen wirksam werden. Sie werden benachrichtigt, wenn alle relevanten Arbeitspakete und Projektphasen aktualisiert worden sind.
+ Sind Sie sicher, dass Sie fortfahren möchten?
work_packages_settings:
warning_progress_calculation_mode_change_from_status_to_field_html: >-
Wenn Sie den Modus der Fortschrittsberechnung von statusbasiert auf arbeitsbasiert ändern, ist das Feld % abgeschlossen frei editierbar. Wenn Sie optional Werte für Aufwand oder Verbleibenden Aufwand eingeben, werden diese auch mit % abgeschlossen verknüpft. Eine Änderung des verbleibenden Aufwands aktualisiert dann % abgschlossen.
diff --git a/config/locales/crowdin/js-es.yml b/config/locales/crowdin/js-es.yml
index 2e6cca98a5f..85e83b3618a 100644
--- a/config/locales/crowdin/js-es.yml
+++ b/config/locales/crowdin/js-es.yml
@@ -277,8 +277,7 @@ es:
removed_title: "Eliminará los siguientes días de la lista de días no laborables:"
change_description: "Changing which days of the week are considered working days or non-working days can affect the start and finish days of all work packages and life cycles in all projects in this instance."
warning: >
- The changes might take some time to take effect. You will be notified when all relevant work packages and project life cycles have been updated.
- Are you sure you want to continue?
+
work_packages_settings:
warning_progress_calculation_mode_change_from_status_to_field_html: >-
Cambiar el modo de cálculo del progreso de basado en el estado a basado en el trabajo hará que el campo % completado se pueda editar libremente. Si introduce opcionalmente valores para Trabajo o Trabajo restante, también se vincularán a % completado. Cambiar Trabajo restante puede entonces actualizar % completado.
diff --git a/config/locales/crowdin/js-ja.yml b/config/locales/crowdin/js-ja.yml
index 71fae0d6470..f08b9837dfc 100644
--- a/config/locales/crowdin/js-ja.yml
+++ b/config/locales/crowdin/js-ja.yml
@@ -1042,7 +1042,7 @@ ja:
tomorrow: "Tomorrow"
three_days: "In 3 days"
week: "In a week"
- month: "In a month"
+ month: "1ヶ月以内"
custom: "At a particular date/time"
scheduling:
is_parent: "このワークパッケージの日付は、その子から自動的に推定されます。日付を設定するには、「手動スケジューリング」を有効にします。"
diff --git a/config/locales/crowdin/js-ro.yml b/config/locales/crowdin/js-ro.yml
index 77aa438705e..19430b97440 100644
--- a/config/locales/crowdin/js-ro.yml
+++ b/config/locales/crowdin/js-ro.yml
@@ -274,10 +274,10 @@ ro:
change_button: "Salvează și reprogramează"
change_title: "Modifică zilelor lucrătoare"
removed_title: "Veţi elimina următoarele zile din lista de zile nelucrătoare:"
- change_description: "Changing which days of the week are considered working days or non-working days can affect the start and finish days of all work packages and life cycles in all projects in this instance."
+ change_description: "Schimbarea zilelor din săptămână care sunt considerate zile lucrătoare sau zile nelucrătoare poate afecta zilele de începere și de încheiere a tuturor pachetelor de lucru și ciclurilor de viață în toate proiectele, în acest caz."
warning: >
- The changes might take some time to take effect. You will be notified when all relevant work packages and project life cycles have been updated.
- Are you sure you want to continue?
+ Modificările ar putea dura ceva timp pentru a produce efecte. Vei fi notificat când toate pachetele de lucru relevante și ciclurile de viață ale proiectului au fost actualizate.
+ Ești sigur că vrei să continui?
work_packages_settings:
warning_progress_calculation_mode_change_from_status_to_field_html: >-
Changing progress calculation mode from status-based to work-based will make the % Complete field freely editable. If you optionally enter values for Work or Remaining work, they will also be linked to % Complete. Changing Remaining work can then update % Complete.
@@ -360,7 +360,7 @@ ro:
"16_1":
standard:
new_features_html: >
- The release contains various new features and improvements, such as
- Structure the project life cycle with phases and phase gates.
- Export meetings in PDF format.
- Set smart default options for reminders.
- Use negative lag for work package dates.
- Display hierarchy trees for hierarchy custom fields.
- Benefit from improved accessibility for the date picker with ARIA live regions.
+ Publicarea conține diverse caracteristici noi și îmbunătățiri, cum ar fi
- Structura ciclul de viață al proiectului cu etape și faze.
- Ședințe la export în format PDF.
- Setează opțiuni implicite inteligente pentru mementouri.
- Utilizați decalajul negativ pentru pachetele de lucru.
- Afișați copaci ierarhici pentru câmpuri personalizate ierarhice.
- Beneficiază de o mai bună accesibilitate pentru selectorul de date cu regiunile vii ARIA
ical_sharing_modal:
title: "Subscribe to calendar"
inital_setup_error_message: "An error occured while fetching data."
@@ -592,7 +592,7 @@ ro:
drag: "Trage și aruncă cartonașele dintr-o listă dată pentru a le reordona sau muta în altă listă.
Poți da click pe pictograma (i) din colțul din dreapta sus sau poți face dublu clic pe un card pentru a-i deschide detaliile."
wp:
toggler: "Acum să ne uităm la secțiunea Pachet de lucru, care vă oferă o vizualizare mai detaliată a activității dumneavoastră."
- list: "Acest pachet de lucru oferă o listă a tuturor activităților din proiectul tău, cum ar fi sarcini, jaloane, faze și altele.
Pachetele de lucru pot fi create și editate direct din acest punct de vedere. Pentru a accesa detaliile unui anumit pachet de lucru, faceți dublu clic pe rând."
+ list: "Acest pachet de lucru oferă o listă a tuturor activităților din proiectul tău, cum ar fi sarcini, jaloane, faze și altele.
Pachetele de lucru pot fi create și editate direct din acest punct de vedere. Pentru a accesa detaliile unui anumit pachet de lucru, fă dublu clic pe rând."
full_view: "Vizualizarea pentru detaliile pachetului de lucru furnizează toate informațiile relevante referitoare la un anumit pachet de lucru, cum ar fi descrierea, starea, prioritatea, activitățile, dependențele si comentariile."
back_button: "Utilizează săgeata de întoarcere din colțul din stânga sus pentru a ieși și pentru a reveni la lista pachetelor de lucru."
create_button: "Butonul Creează va adăuga un nou pachet de lucru la proiectul tău."
diff --git a/config/locales/crowdin/js-ru.yml b/config/locales/crowdin/js-ru.yml
index d0e81745d0e..a7f9f94f1ce 100644
--- a/config/locales/crowdin/js-ru.yml
+++ b/config/locales/crowdin/js-ru.yml
@@ -215,12 +215,12 @@ ru:
label_company: "Компания"
label_first_name: "Имя"
label_last_name: "Фамилия"
- label_domain: "Домен"
+ label_domain: "Доменное имя"
label_subscriber: "Подписчик"
label_maximum_users: "Максимальное число активных пользователей"
label_starts_at: "Начиная с"
label_expires_at: "Срок действия до"
- label_plan: "План"
+ label_plan: "Подписка"
label_additional_features: "Дополнительные возможности"
receive_newsletter: Я хочу получать новостную рассылку от OpenProject.
taken_domain: На каждом домене может быть только один активный пробный период.
@@ -274,10 +274,10 @@ ru:
change_button: "Сохранить и перенести"
change_title: "Изменить рабочие дни"
removed_title: "Следующие дни будут удалены из списка нерабочих дней:"
- change_description: "Changing which days of the week are considered working days or non-working days can affect the start and finish days of all work packages and life cycles in all projects in this instance."
+ change_description: "Изменение дней недели, считающихся рабочими или нерабочими днями, может повлиять на начало и завершение всех пакетов работ и жизненных циклов во всех проектах."
warning: >
- The changes might take some time to take effect. You will be notified when all relevant work packages and project life cycles have been updated.
- Are you sure you want to continue?
+ Для вступления изменений в силу может потребоваться некоторое время. Вы будете уведомлены, когда все соответствующие пакеты работ и жизненные циклы проектов будут обновлены.
+ Хотите продолжить?
work_packages_settings:
warning_progress_calculation_mode_change_from_status_to_field_html: >-
При изменении расчета прогресса с режима "На основе статуса" на режим "На основе трудозатрат" поле % завершения станет редактируемым. Если Вы дополнительно введете значения для Предполагаемого времени или Оставшихся часов, они также будут связаны с % завершения. Изменение значения Оставшихся часов может привести к обновлению % завершения.
@@ -360,7 +360,7 @@ ru:
"16_1":
standard:
new_features_html: >
- Релиз содержит различные новые функции и улучшения, например
- Структура жизненного цикла проекта с этапами и стадиями.
- Экспорт совещаний в формате PDF.
- Установка смарт-параметров по умолчанию для напоминаний.
- Использование отрицательного лага для даты пакета работ.
- Отображение иерархических деревьев для иерархии пользовательских полей.
- Улучшение доступности для выбора даты с ARIA регионами.
+ Релиз содержит различные новые функции и улучшения, например
- Структура жизненного цикла проекта с этапами и стадиями.
- Экспорт совещаний в формате PDF.
- Установка смарт-параметров по умолчанию для напоминаний.
- Использование отрицательной задержки для даты пакета работ.
- Отображение иерархических деревьев для иерархии пользовательских полей.
- Улучшение доступности для выбора даты с ARIA регионами.
ical_sharing_modal:
title: "Подписаться на календарь"
inital_setup_error_message: "Произошла ошибка при получении данных."
diff --git a/config/locales/crowdin/js-zh-CN.yml b/config/locales/crowdin/js-zh-CN.yml
index f66527f9113..5c1bb52dcf9 100644
--- a/config/locales/crowdin/js-zh-CN.yml
+++ b/config/locales/crowdin/js-zh-CN.yml
@@ -275,10 +275,10 @@ zh-CN:
change_button: "保存并重新安排"
change_title: "更改工作日"
removed_title: "您将从非工作日列表中删除以下日期:"
- change_description: "Changing which days of the week are considered working days or non-working days can affect the start and finish days of all work packages and life cycles in all projects in this instance."
+ change_description: "在这种情况下,更改一周中哪几天被视为工作日或非工作日会影响所有项目中所有工作包和生命周期的开始和结束日期。"
warning: >
- The changes might take some time to take effect. You will be notified when all relevant work packages and project life cycles have been updated.
- Are you sure you want to continue?
+ 更改可能需要一些时间才能生效。所有相关工作包和项目生命周期更新后,您将收到通知。
+ 您确定要继续吗?
work_packages_settings:
warning_progress_calculation_mode_change_from_status_to_field_html: >-
将进度计算模式从基于状态改为基于工时,将使完成 %字段可自由编辑。如果您选择输入工时或剩余工时的值,它们也将与完成 %相关联。更改剩余工时就可以更新完成 %。
diff --git a/config/locales/crowdin/js-zh-TW.yml b/config/locales/crowdin/js-zh-TW.yml
index 22b448dd41d..b20750ffd6b 100644
--- a/config/locales/crowdin/js-zh-TW.yml
+++ b/config/locales/crowdin/js-zh-TW.yml
@@ -1206,7 +1206,7 @@ zh-TW:
invite_to_project: "邀請%{type} 加入 %{project}"
User: "使用者"
Group: "群組"
- PlaceholderUser: "占位使用者"
+ PlaceholderUser: "佔位使用者"
invite_principal_to_project: "邀請 %{principal} 加入 %{project}"
project:
label: "專案"
@@ -1225,7 +1225,7 @@ zh-TW:
title: "群組"
description: "基於所選專案中所分配角色的權限"
placeholder:
- title: "占位使用者"
+ title: "佔位使用者"
title_no_ee: "佔位使用者(僅限企業版附加元件)"
description: "無法訪問該專案並且未發送電子郵件。"
description_no_ee: '由於沒有存取專案權限,所以不會發送電子郵件。
請查看企業版。'
@@ -1242,7 +1242,7 @@ zh-TW:
next_button: "下一個"
required:
user: "請選擇一個使用者"
- placeholder: "請選擇占位使用者"
+ placeholder: "請選擇佔位使用者"
group: "請選擇一個群組"
role:
label: "%{project} 中的角色"
diff --git a/config/locales/crowdin/ka.yml b/config/locales/crowdin/ka.yml
index e77061ee65b..000b739f664 100644
--- a/config/locales/crowdin/ka.yml
+++ b/config/locales/crowdin/ka.yml
@@ -1208,7 +1208,7 @@ ka:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3932,8 +3932,6 @@ ka:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/kk.yml b/config/locales/crowdin/kk.yml
index 2a96be25174..dff1a1fc66e 100644
--- a/config/locales/crowdin/kk.yml
+++ b/config/locales/crowdin/kk.yml
@@ -1208,7 +1208,7 @@ kk:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3932,8 +3932,6 @@ kk:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/ko.yml b/config/locales/crowdin/ko.yml
index 2fc67de0b61..e7cb5613765 100644
--- a/config/locales/crowdin/ko.yml
+++ b/config/locales/crowdin/ko.yml
@@ -1200,7 +1200,7 @@ ko:
attributes:
content_type:
blank: "파일의 콘텐츠 유형은 비워둘 수 없습니다."
- not_whitelisted: "파일이 자동 필터에 의해 거부되었습니다. '%{value}'은(는) 업로드 허용 목록에 등록되지 않았습니다."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3888,8 +3888,6 @@ ko:
new:
description: "이 프로젝트 단계의 변경 사항은 이 단계가 활성화된 모든 프로젝트에 반영됩니다."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "시작 및 완료 게이트"
no_gate: "게이트 없음"
start_gate: "시작 게이트"
diff --git a/config/locales/crowdin/lt.yml b/config/locales/crowdin/lt.yml
index 7aaf8b83c07..003e03f3d62 100644
--- a/config/locales/crowdin/lt.yml
+++ b/config/locales/crowdin/lt.yml
@@ -1221,7 +1221,7 @@ lt:
attributes:
content_type:
blank: "Failo turinio tipas negali būti tuščias."
- not_whitelisted: "Failą atmetė automatinis filtras. „%{value}“ nėra tinkamas įkėlimui."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -4008,8 +4008,6 @@ lt:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/lv.yml b/config/locales/crowdin/lv.yml
index c72dd4b96b5..ef893959d6c 100644
--- a/config/locales/crowdin/lv.yml
+++ b/config/locales/crowdin/lv.yml
@@ -1216,7 +1216,7 @@ lv:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3973,8 +3973,6 @@ lv:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/mn.yml b/config/locales/crowdin/mn.yml
index b7aeb9546c8..41189c6e158 100644
--- a/config/locales/crowdin/mn.yml
+++ b/config/locales/crowdin/mn.yml
@@ -1208,7 +1208,7 @@ mn:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3932,8 +3932,6 @@ mn:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/ms.yml b/config/locales/crowdin/ms.yml
index 0d11962b2df..68b60a28a87 100644
--- a/config/locales/crowdin/ms.yml
+++ b/config/locales/crowdin/ms.yml
@@ -1198,7 +1198,7 @@ ms:
attributes:
content_type:
blank: "Jenis kandungan fail tidak boleh kosong."
- not_whitelisted: "Fail ditolak oleh penyaring automatik. '%{value}' tidak diluluskan untuk dimuat naik."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3888,8 +3888,6 @@ ms:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/ne.yml b/config/locales/crowdin/ne.yml
index b9f13220265..13d2f397289 100644
--- a/config/locales/crowdin/ne.yml
+++ b/config/locales/crowdin/ne.yml
@@ -1208,7 +1208,7 @@ ne:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3932,8 +3932,6 @@ ne:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/nl.yml b/config/locales/crowdin/nl.yml
index 527df9d5eb9..26c01281701 100644
--- a/config/locales/crowdin/nl.yml
+++ b/config/locales/crowdin/nl.yml
@@ -1204,7 +1204,7 @@ nl:
attributes:
content_type:
blank: "Het inhoudstype van het bestand mag niet leeg zijn."
- not_whitelisted: "Het bestand is geweigerd door een automatisch filter. '%{value}' is niet gewhitelist voor uploaden."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3927,8 +3927,6 @@ nl:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/no.yml b/config/locales/crowdin/no.yml
index b172300df72..3787974722c 100644
--- a/config/locales/crowdin/no.yml
+++ b/config/locales/crowdin/no.yml
@@ -1207,7 +1207,7 @@
attributes:
content_type:
blank: "Filens innholdstype kan ikke være tom."
- not_whitelisted: "Filen ble avvist av et automatisk filter. '%{value}' er ikke hvitelistet for opplasting."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3931,8 +3931,6 @@
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/pl.yml b/config/locales/crowdin/pl.yml
index 3c870fb65ec..0e2c794e6bf 100644
--- a/config/locales/crowdin/pl.yml
+++ b/config/locales/crowdin/pl.yml
@@ -1220,7 +1220,7 @@ pl:
attributes:
content_type:
blank: "Zawartość tego typu zwartości nie może być pusta."
- not_whitelisted: "Plik został odrzucony przez automatyczny filtr. '%{value}' nie jest na białej liście do przesyłania."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -4007,8 +4007,6 @@ pl:
new:
description: "Zmiany w tej fazie projektu zostaną odzwierciedlone we wszystkich projektach, w których jest ona włączona."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Bramka początkowa i końcowa"
no_gate: "Brak bramki"
start_gate: "Bramka początkowa"
diff --git a/config/locales/crowdin/pt-BR.yml b/config/locales/crowdin/pt-BR.yml
index fa34dc2f7ce..0fcc10f9215 100644
--- a/config/locales/crowdin/pt-BR.yml
+++ b/config/locales/crowdin/pt-BR.yml
@@ -1205,7 +1205,7 @@ pt-BR:
attributes:
content_type:
blank: "O tipo de conteúdo do arquivo não pode ficar em branco."
- not_whitelisted: "O arquivo foi rejeitado por um filtro automático. '%{value}' não está na lista de permissões para ser enviado."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3928,8 +3928,6 @@ pt-BR:
new:
description: "As alterações nesta fase do projeto serão aplicadas a todos os projetos nos quais ela esteja ativada."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Controle inicial e controle final"
no_gate: "Sem controle"
start_gate: "Data inicial"
diff --git a/config/locales/crowdin/pt-PT.yml b/config/locales/crowdin/pt-PT.yml
index c86253e1bf8..0d2745d3d94 100644
--- a/config/locales/crowdin/pt-PT.yml
+++ b/config/locales/crowdin/pt-PT.yml
@@ -1205,7 +1205,7 @@ pt-PT:
attributes:
content_type:
blank: "O tipo de conteúdo do ficheiro não pode ficar em branco."
- not_whitelisted: "O ficheiro foi rejeitado por um filtro automático. '%{value}' não está na lista branca para upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3926,8 +3926,6 @@ pt-PT:
new:
description: "As alterações a esta fase do projeto serão refletidas em todos os projetos em que estiver ativada."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Porta de início e conclusão"
no_gate: "Sem porta"
start_gate: "Porta de início"
diff --git a/config/locales/crowdin/ro.seeders.yml b/config/locales/crowdin/ro.seeders.yml
index 1774617ddcf..216d689a690 100644
--- a/config/locales/crowdin/ro.seeders.yml
+++ b/config/locales/crowdin/ro.seeders.yml
@@ -36,13 +36,13 @@ ro:
name: Negru
project_phase_colors:
item_0:
- name: PM2 Orange
+ name: PM2 Portocaliu
item_1:
- name: PM2 Red
+ name: PM2 Roșu
item_2:
name: PM2 Magenta
item_3:
- name: PM2 Green Yellow
+ name: PM2 Verde Galben
document_categories:
item_0:
name: Documentație
@@ -81,16 +81,16 @@ ro:
standard:
project_phases:
item_0:
- name: Initiating
+ name: Inițializare
item_1:
name: Planificare
- start_gate: Ready for Planning
+ start_gate: Gata pentru planificare
item_2:
- name: Executing
- start_gate: Ready for Executing
+ name: Execuție
+ start_gate: Gata de execuție
item_3:
- name: Closing
- start_gate: Ready for Closing
+ name: Închidere
+ start_gate: Gata pentru închidere
priorities:
item_0:
name: Scăzută
diff --git a/config/locales/crowdin/ro.yml b/config/locales/crowdin/ro.yml
index da252212be1..3e1a81dfe5b 100644
--- a/config/locales/crowdin/ro.yml
+++ b/config/locales/crowdin/ro.yml
@@ -387,14 +387,14 @@ ro:
no_results_title_text: În acest moment nu există câmpuri personalizate disponibile.
life_cycle:
header:
- title: "Project life cycle"
- description_html: "The active project phases define this project's life cycle and are defined in the administration settings. Enabled phases will be displayed in your project overview."
- non_defined: "No phases are currently defined."
- section_header: "Phases"
+ title: "Ciclul de viață proiect"
+ description_html: "Fazele de proiect active definesc ciclul de viață al acestui proiect și sunt definite în setările de administrare . Fazele activate vor fi afișate în prezentarea generală a proiectului tău."
+ non_defined: "Nici o fază nu este definită."
+ section_header: "Faze"
step:
- use_in_project: "Use %{step} in this project"
+ use_in_project: "Utilizează %{step} în acest proiect"
filter:
- label: "Search project phase"
+ label: "Caută fază proiect"
project_custom_fields:
header:
title: "Atributele proiectului"
@@ -985,18 +985,18 @@ ro:
custom_field_section: Secțiune
project/phase:
date_range: "Interval calendaristic"
- definition: "Definition"
- duration: "Duration"
- start_date: "Start date"
- start_date_caption: "Follows the previous phase."
- finish_date: "Finish date"
+ definition: "Definiţie"
+ duration: "Durată"
+ start_date: "Dată început"
+ start_date_caption: "Urmează faza anterioară."
+ finish_date: "Dată finalizare"
project/phase_definition:
name: "Nume"
color: "Culoare"
- start_gate: "Start phase gate"
- start_gate_name: "Start phase gate name"
- finish_gate: "Finish phase gate"
- finish_gate_name: "Finish phase gate name"
+ start_gate: "Înecput poartă fază"
+ start_gate_name: "Nume început poartă fază"
+ finish_gate: "Final poartă fază"
+ finish_gate_name: "Nume final poartă fază"
query:
sums: "Sums"
columns: "Coloane"
@@ -1216,7 +1216,7 @@ ro:
attributes:
content_type:
blank: "Tipul de conținut al fișierului nu poate fi necompletat."
- not_whitelisted: "Fişierul a fost respins de un filtru automat. '%{value}' nu este whitelisted pentru încărcare."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -1302,11 +1302,11 @@ ro:
project/phase:
attributes:
start_date:
- must_be_before_finish_date: "must be before the finish date."
- non_continuous_dates: "can't be earlier than the previous phase's end date."
+ must_be_before_finish_date: "trebuie să fie înainte de data finală."
+ non_continuous_dates: "nu poate fi mai devreme de data de încheiere a fazei anterioare."
finish_date:
- must_be_after_start_date: "must be after the start date."
- cannot_be_a_non_working_day: "can't be a non-working day."
+ must_be_after_start_date: "trebuie să fie după dată început."
+ cannot_be_a_non_working_day: "nu poate fi o zi nelucrătoare."
query:
attributes:
project:
@@ -1617,14 +1617,14 @@ ro:
wiki_edit: "Wiki"
work_package: "Pachete de lucru"
project_phase:
- activated: "Activat"
- added_date: "set to %{date}"
- changed_date: "changed from %{from} to %{to}"
- deactivated: "Dezactivat"
- deleted_project_phase: "Deleted project phase"
- phase_and_both_gates: "%{phase_message}. %{start_gate_message}, and %{finish_gate_message}"
+ activated: "activat"
+ added_date: "setează la %{date}"
+ changed_date: "schimbat de la %{from} la %{to}"
+ deactivated: "dezactivat"
+ deleted_project_phase: "Fază proiect ștearsă"
+ phase_and_both_gates: "%{phase_message}. %{start_gate_message}, și %{finish_gate_message}"
phase_and_one_gate: "%{phase_message}. %{gate_message}"
- removed_date: "date deleted %{date}"
+ removed_date: "dată ștearsă %{date}"
#common attributes of all models
attributes:
active: "Activ"
@@ -1691,8 +1691,8 @@ ro:
priority: "Prioritate"
project: "Proiect"
project_ids: "Project IDs"
- project_phase: "Project phase"
- project_phase_definition: "Project phase"
+ project_phase: "Fază proiect"
+ project_phase_definition: "Fază proiect"
reason: "Motiv"
responsible: "Responsabil"
required: "Obligatoriu"
@@ -2047,7 +2047,7 @@ ro:
internal_comments: Comentarii interne
custom_actions: Custom Actions
custom_field_hierarchies: Custom fields of type hierarchy
- customize_life_cycle: Customize Life Cycle
+ customize_life_cycle: Personalizează ciclul de viață
date_alerts: Date Alerts
define_custom_style: Custom theme and logo
edit_attribute_groups: Edit Attribute Groups
@@ -2080,7 +2080,7 @@ ro:
work_package_subject_generation:
description: "Create automatically generated subjects using referenced attributes and text."
customize_life_cycle:
- description: "Create and organize different project phases than the ones provided by PM2 project cycle planning."
+ description: "Creează și organizează diferite faze de proiect decât cele furnizate de planificarea ciclului proiectului PM2."
conditional_highlighting:
description: "Need certain work packages to stand out from the mass? Use conditional highlighting in the work packages table."
work_package_query_relation_columns:
@@ -2869,7 +2869,7 @@ ro:
label_none_parentheses: "(niciuna)"
label_not_contains: "nu conține"
label_not_equals: "nu este"
- label_life_cycle_step_plural: "Project life cycle"
+ label_life_cycle_step_plural: "Ciclul de viață proiect"
label_on: "pe"
label_operator_all: "nu este gol"
label_operator_none: "este gol"
@@ -2931,7 +2931,7 @@ ro:
label_project_new: "Proiect nou"
label_project_plural: "Proiecte"
label_project_list_plural: "Listă proiecte"
- label_project_life_cycle: "Project life cycle"
+ label_project_life_cycle: "Ciclul de viață proiect"
label_project_attributes_plural: "Atribute proiect"
label_project_custom_field_plural: "Atribute proiect"
label_project_settings: "Setările proiectului"
@@ -3473,7 +3473,7 @@ ro:
permission_edit_others_internal_comments_explanation: "Atenție: Utilizatorii cu această permisiune pot edita comentariile interne ale altor utilizatori."
permission_edit_project: "Editare proiect"
permission_edit_project_attributes: "Edit project attributes"
- permission_edit_project_phases: "Edit project phases"
+ permission_edit_project_phases: "Editează faze proiect"
permission_edit_reportings: "Editare raportări"
permission_edit_time_entries: "Editează jurnalele de timp pentru alți utilizatori"
permission_edit_timelines: "Editare linii de timp"
@@ -3503,8 +3503,8 @@ ro:
permission_search_project: "Caută proiect"
permission_select_custom_fields: "Selectează câmpuri personalizate"
permission_select_project_custom_fields: "Select project attributes"
- permission_select_project_phases: "Select project phases"
- permission_select_project_phases_explanation: "Activate/Deactivate the phases in a project. Enables the user to select the life cycle appropriate for the project as inactive phases will not be visible in the project overview page nor the project list."
+ permission_select_project_phases: "Selectează faze proiect"
+ permission_select_project_phases_explanation: "Activează/Dezactivează fazele unui proiect. Permite utilizatorului să selecteze ciclul de viață potrivit pentru proiect ca fazele inactive nu vor fi vizibile în pagina de ansamblu a proiectului și nici în lista de proiecte."
permission_select_project_modules: "Selectare module proiect"
permission_share_work_packages: "Partajare pachete de lucru"
permission_manage_types: "Selectare tipuri"
@@ -3530,7 +3530,7 @@ ro:
permission_work_package_assigned_explanation: "Pachetele de lucru pot fi atribuite utilizatorilor și grupurilor care dețin acest rol în proiectul respectiv"
permission_view_project_activity: "Vezi activitate proiect"
permission_view_project_attributes: "Vezi atribute proiect"
- permission_view_project_phases: "View project phases"
+ permission_view_project_phases: "Vezi faze proiect"
permission_save_bcf_queries: "Save BCF queries"
permission_manage_public_bcf_queries: "Manage public BCF queries"
permission_edit_attribute_help_texts: "Edit attribute help texts"
@@ -3556,9 +3556,9 @@ ro:
subprojects_confirmation: "Se vor șterge și sub-proiectul/sub-proiectele: %{value}."
title: "Ștergere proiect %{name}"
filters:
- project_phase: "Project phase: %{phase}"
- project_phase_any: "Project phase: Any"
- project_phase_gate: "Project phase gate: %{gate}"
+ project_phase: "Fază proiect: %{phase}"
+ project_phase_any: "Fază proiect: Oricare"
+ project_phase_gate: "Poartă fază proiect: %{gate}"
identifier:
warning_one: Participanții la proiect vor trebui să mute arhivele proiectului.
warning_two: Legăturile existente la acest proiect nu vor mai funcționa.
@@ -3960,26 +3960,24 @@ ro:
heading: "New attribute"
description: "Modificările la acest atribut al proiectului se vor reflecta în toate proiectele în care este activată. Atributele necesare nu pot fi dezactivate pentru fiecare proiect."
project_phase_definitions:
- heading: "Project life cycle"
- heading_description: "Project life cycle define the project phases that can be used for your project planning and will appear in the overview page of each project. These attributes can be enabled or disabled but not re-ordered at a project level."
+ heading: "Ciclul de viață proiect"
+ heading_description: "Ciclul de viață al proiectului definește fazele proiectului care pot fi utilizate pentru planificarea proiectului și vor apărea în pagina de ansamblu a fiecărui proiect. Aceste atribute pot fi activate sau dezactivate, dar nu pot fi reordonate la nivel de proiect."
label_add: "Adaugă"
- label_add_description: "Add project phase definition"
+ label_add_description: "Adaugă definiție fază proiect"
filter:
- label: "Search project phase"
- section_header: "Phases"
- non_defined: "No phases are currently defined."
- phase_gates: "Phase gates"
+ label: "Caută fază proiect"
+ section_header: "Faze"
+ non_defined: "Nicio fază nu este definită."
+ phase_gates: "Porți fază"
new:
- description: "Changes to this project phase will be reflected in all projects where it is enabled."
- heading: "New phase"
- edit:
- heading: "Edit phase"
- both_gate: "Start and finish gate"
- no_gate: "No gate"
- start_gate: "Start gate"
- start_gate_caption: "Add a gate with the start date of the phase"
- finish_gate: "Finish gate"
- finish_gate_caption: "Add a gate with the end date of the phase"
+ description: "Modificările aduse acestei faze de proiect se vor reflecta în toate proiectele în care aceasta este activată."
+ heading: "Fază nouă"
+ both_gate: "Început și final poartă"
+ no_gate: "Fără poartă"
+ start_gate: "Poartă început"
+ start_gate_caption: "Adăugați o poartă cu data de începere a fazei"
+ finish_gate: "Poartă final"
+ finish_gate_caption: "Adăugă o poartă cu data de final a fazei"
projects:
missing_dependencies: "Project module %{module} was checked which depends on %{dependencies}. You need to check these dependencies as well."
section_new_projects: "Setări pentru proiecte noi"
@@ -4018,7 +4016,7 @@ ro:
text_are_you_sure_continue: "Sigur dorești să continui?"
text_are_you_sure_with_children: "Doriți ștergerea pachetului de lucru și a tuturor pachetelor fii?"
text_are_you_sure_with_project_custom_fields: "Deleting this attribute will also delete its values in all projects. Are you sure you want to do this?"
- text_are_you_sure_with_project_life_cycle_step: "Deleting this phase will also delete its usages in all projects. Are you sure you want to do this?"
+ text_are_you_sure_with_project_life_cycle_step: "Ștergerea acestei faze va șterge și utilizarea sa în toate proiectele. Ești sigur că vrei să faci asta?"
text_assign_to_project: "Alocare la proiect"
text_form_configuration: >
Puteți personaliza ce câmpuri vor fi afișate în formularele pachetelor de lucru. Puteți grupa în mod liber câmpurile pentru a reflecta nevoile domeniului dumneavoastră.
@@ -4350,12 +4348,12 @@ ro:
message: "Partajarea listelor de proiecte cu utilizatorii individuali este un add-on Enterprise."
working_days:
info: >
- Days that are not selected are skipped when scheduling work packages and project life cycles (and not included in the day count). These can be overridden at a work-package level.
+ Zile care nu sunt selectate sunt omise când se programează pachetele de lucru și ciclul de viață al proiectului (și nu sunt incluse în numărul zilelor). Acestea pot fi suprascrise la nivelul pachetului de lucru.
instance_wide_info: >
Dates added to the list below are considered non-working and skipped when scheduling work packages.
change_button: "Modifică zilelor lucrătoare"
warning: >
- Changing which days of the week are considered working days or non-working days can affect the start and finish days of all work packages and life cycles in all projects in this instance.
+ Schimbarea zilelor din săptămână care sunt considerate zile lucrătoare sau zile nelucrătoare poate afecta zilele de începere și de încheiere a tuturor pachetelor de lucru și ciclurilor de viață în toate proiectele, în acest caz.
journal_note:
changed: _**Zile lucrătoare** schimbate (%{changes})._
days:
diff --git a/config/locales/crowdin/ru.yml b/config/locales/crowdin/ru.yml
index 6a65ced6630..f8c05fb62d9 100644
--- a/config/locales/crowdin/ru.yml
+++ b/config/locales/crowdin/ru.yml
@@ -773,7 +773,7 @@ ru:
label_edit_x: "Редактировать %{x}"
label_add_description: "Добавить описание"
lag:
- subject: "Отставание"
+ subject: "Задержка"
caption: |-
Минимальное количество рабочих дней, которое должно быть между двумя пакетами работ.
Оно также может быть отрицательным.
@@ -923,7 +923,7 @@ ru:
emoji_reaction:
reactable: "Реакция на"
enterprise_token:
- domain: "Домен"
+ domain: "Доменное имя"
starts_at: "Действует с"
subscriber: "Подписчик"
encoded_token: "Маркер поддержки корпоративной версии"
@@ -1029,7 +1029,7 @@ ru:
include_subprojects: "Включить подпроекты"
results: "Результаты"
relation:
- lag: "Отставание"
+ lag: "Задержка"
from: "Похожий пакет работ"
to: "Похожий пакет работ"
relation_type: "Тип отношения"
@@ -1222,7 +1222,7 @@ ru:
attributes:
content_type:
blank: "Тип содержимого файла не может быть пустым."
- not_whitelisted: "Файл был отклонен автоматическим фильтром. '%{value}' не является белым списком для загрузки."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -4009,8 +4009,6 @@ ru:
new:
description: "Изменения на этом этапе проекта будут отражены во всех проектах, где он включен."
heading: "Новый этап"
- edit:
- heading: "Изменить этап"
both_gate: "Начать и завершить стадию"
no_gate: "Нет стадии"
start_gate: "Начать стадию"
@@ -4387,12 +4385,12 @@ ru:
message: "Совместное использование списков проектов с отдельными пользователями является корпоративным дополнением."
working_days:
info: >
- Days that are not selected are skipped when scheduling work packages and project life cycles (and not included in the day count). These can be overridden at a work-package level.
+ Невыбранные дни пропускаются при планировании пакетов работ и жизненного цикла проекта (и не включаются в подсчет дней). Они могут быть переопределены на уровне пакетов работ.
instance_wide_info: >
Даты, добавленные в список ниже, считаются нерабочими и пропускаются при планировании пакетов работ.
change_button: "Изменить рабочие дни"
warning: >
- Changing which days of the week are considered working days or non-working days can affect the start and finish days of all work packages and life cycles in all projects in this instance.
+ Изменение дней недели, считающихся рабочими или нерабочими днями, может повлиять на начало и завершение всех пакетов работ и жизненных циклов во всех проектах.
journal_note:
changed: _**Рабочие дни** изменены (%{changes})._
days:
diff --git a/config/locales/crowdin/rw.yml b/config/locales/crowdin/rw.yml
index 56ddd0497af..89c83433d0e 100644
--- a/config/locales/crowdin/rw.yml
+++ b/config/locales/crowdin/rw.yml
@@ -1208,7 +1208,7 @@ rw:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3932,8 +3932,6 @@ rw:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/si.yml b/config/locales/crowdin/si.yml
index 6dc796c20e3..a4fe3bdfb2a 100644
--- a/config/locales/crowdin/si.yml
+++ b/config/locales/crowdin/si.yml
@@ -1208,7 +1208,7 @@ si:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3932,8 +3932,6 @@ si:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/sk.yml b/config/locales/crowdin/sk.yml
index 9174d3de4ee..4cb84c332c7 100644
--- a/config/locales/crowdin/sk.yml
+++ b/config/locales/crowdin/sk.yml
@@ -1224,7 +1224,7 @@ sk:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -4013,8 +4013,6 @@ sk:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/sl.yml b/config/locales/crowdin/sl.yml
index 6fd93365a83..95b19e101da 100644
--- a/config/locales/crowdin/sl.yml
+++ b/config/locales/crowdin/sl.yml
@@ -1223,7 +1223,7 @@ sl:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -4012,8 +4012,6 @@ sl:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/sr.yml b/config/locales/crowdin/sr.yml
index ab9c453a9b8..590573f2004 100644
--- a/config/locales/crowdin/sr.yml
+++ b/config/locales/crowdin/sr.yml
@@ -1216,7 +1216,7 @@ sr:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3973,8 +3973,6 @@ sr:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/sv.yml b/config/locales/crowdin/sv.yml
index 4be60b1fa0f..2e555b2d8a8 100644
--- a/config/locales/crowdin/sv.yml
+++ b/config/locales/crowdin/sv.yml
@@ -1208,7 +1208,7 @@ sv:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3930,8 +3930,6 @@ sv:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/th.yml b/config/locales/crowdin/th.yml
index bf342282837..9a3a9672d1f 100644
--- a/config/locales/crowdin/th.yml
+++ b/config/locales/crowdin/th.yml
@@ -1200,7 +1200,7 @@ th:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3891,8 +3891,6 @@ th:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/tr.yml b/config/locales/crowdin/tr.yml
index 67577b8ddd1..1a91decdd57 100644
--- a/config/locales/crowdin/tr.yml
+++ b/config/locales/crowdin/tr.yml
@@ -1207,7 +1207,7 @@ tr:
attributes:
content_type:
blank: "Dosyanın içerik türü boş olamaz."
- not_whitelisted: "Dosya, otomatik bir filtre tarafından reddedildi. '%{value}' yükleme için beyaz listeye alınmadı."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3929,8 +3929,6 @@ tr:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/uk.yml b/config/locales/crowdin/uk.yml
index 083e61b589c..1ca0c2b7ca3 100644
--- a/config/locales/crowdin/uk.yml
+++ b/config/locales/crowdin/uk.yml
@@ -1217,7 +1217,7 @@ uk:
attributes:
content_type:
blank: "Тип контенту файлу не може бути пустий."
- not_whitelisted: "Файл відхилено автоматичним фільтром. Завантаження «%{value}» не дозволено."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -4005,8 +4005,6 @@ uk:
new:
description: "Зміни цього етапу проєкту відображатимуться в усіх проєктах із цим етапом."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Початкова й кінцева контрольні точки"
no_gate: "Немає контрольної точки"
start_gate: "Початкова контрольна точка"
diff --git a/config/locales/crowdin/uz.yml b/config/locales/crowdin/uz.yml
index c9931f63e74..2b2b946836b 100644
--- a/config/locales/crowdin/uz.yml
+++ b/config/locales/crowdin/uz.yml
@@ -1208,7 +1208,7 @@ uz:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3932,8 +3932,6 @@ uz:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/vi.yml b/config/locales/crowdin/vi.yml
index bec9b265a72..9072d151710 100644
--- a/config/locales/crowdin/vi.yml
+++ b/config/locales/crowdin/vi.yml
@@ -1200,7 +1200,7 @@ vi:
attributes:
content_type:
blank: "Loại nội dung của tệp không thể để trống."
- not_whitelisted: "Tệp đã bị từ chối bởi bộ lọc tự động. '%{value}' không được phép tải lên."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -3891,8 +3891,6 @@ vi:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/locales/crowdin/zh-CN.yml b/config/locales/crowdin/zh-CN.yml
index b8dbd8dac6d..684a2042faa 100644
--- a/config/locales/crowdin/zh-CN.yml
+++ b/config/locales/crowdin/zh-CN.yml
@@ -1196,7 +1196,7 @@ zh-CN:
attributes:
content_type:
blank: "文件的内容类型不能为空。"
- not_whitelisted: "文件被自动筛选器拒绝。“%{value}”未被列入上传白名单。"
+ not_allowlisted: "文件被自动过滤器拒绝。%{value}' 不允许上传。"
format: "%{message}"
capability:
context:
@@ -3882,8 +3882,6 @@ zh-CN:
new:
description: "对此项目阶段的更改将反映在所有启用的项目中。"
heading: "新增阶段"
- edit:
- heading: "编辑阶段"
both_gate: "开始和结束关口"
no_gate: "无关口"
start_gate: "开始关口"
@@ -4257,12 +4255,12 @@ zh-CN:
message: "与单个用户共享项目列表是一项企业附加功能。"
working_days:
info: >
- Days that are not selected are skipped when scheduling work packages and project life cycles (and not included in the day count). These can be overridden at a work-package level.
+ 在计划工作包和项目生命周期时,未选择的日期将被跳过(不计入天数)。可以在工作包级别覆盖这些选项。
instance_wide_info: >
添加到以下列表的日期被视为非工作日,在安排工作包时将被跳过。
change_button: "更改工作日"
warning: >
- Changing which days of the week are considered working days or non-working days can affect the start and finish days of all work packages and life cycles in all projects in this instance.
+ 在这种情况下,更改一周中哪几天被视为工作日或非工作日会影响所有项目中所有工作包和生命周期的开始和结束日期。
journal_note:
changed: _**工作日**已更改(%{changes})。_
days:
diff --git a/config/locales/crowdin/zh-TW.yml b/config/locales/crowdin/zh-TW.yml
index 098561cbead..c25c24aadbc 100644
--- a/config/locales/crowdin/zh-TW.yml
+++ b/config/locales/crowdin/zh-TW.yml
@@ -552,7 +552,7 @@ zh-TW:
data_consequences: >
出現的所有佔位符用戶(例如,作為受理人、負責人或其他用戶值) 將被重新分配給一個名為“已刪除用戶”的帳戶。 由於每個已刪除帳戶的數據都會被重新分配給此帳戶,因此無法將用戶創建的數據與另一個已刪除帳戶的數據區分。
irreversible: "這一行動是不可還原的"
- confirmation: "輸入占位使用者名稱 %{name} 確認刪除。"
+ confirmation: "輸入佔位使用者名稱 %{name} 確認刪除。"
priorities:
edit:
priority_color_text: |
@@ -1196,7 +1196,7 @@ zh-TW:
attributes:
content_type:
blank: "文件的內容類型不能為空。"
- not_whitelisted: "該文件被拒絕。 “%{value}”未列入上傳白名單。"
+ not_allowlisted: "檔案被自動過濾器拒絕。'%{value}' 不允許上傳。"
format: "%{message}"
capability:
context:
@@ -1513,7 +1513,7 @@ zh-TW:
news: "最新消息"
notification:
other: "通知"
- placeholder_user: "占位使用者"
+ placeholder_user: "佔位使用者"
project:
other: "專案"
project_query:
@@ -1973,7 +1973,7 @@ zh-TW:
ldap_groups: 同步LDAP使用者與群組
nextcloud_sso: Nextcloud 儲存空間的單一登入(SSO)
one_drive_sharepoint_file_storage: OneDrive/SharePoint 檔案儲存
- placeholder_users: 占位使用者
+ placeholder_users: 佔位使用者
project_list_sharing: 專案清單分享
readonly_work_packages: 唯讀工作套件
sso_auth_providers: 單一登入
@@ -2032,7 +2032,7 @@ zh-TW:
virus_scanning:
description: "確保在其他使用者存取 OpenProject 中上傳的檔案前,對其進行病毒掃描。"
placeholder_users:
- title: 占位使用者
+ title: 佔位使用者
description: >
「佔位使用者」是一種將工作套件分配給不屬於您專案成員的方法。它們在一系列場景中十分有用;例如,如果您需要追蹤尚未命名或可用的資源的任務,或者如果您不想讓該人員存取專案,但仍希望追蹤分配給他們的任務。
internal_comments:
@@ -2152,7 +2152,7 @@ zh-TW:
label: "甘特圖"
caption: "匯出甘特圖檢視中的工作套件清單。"
include_images:
- label: "包含圖片"
+ label: "圖片"
caption: "排除影像以減少 PDF 匯出的大小。"
gantt_zoom_levels:
label: "縮放等級"
@@ -2816,9 +2816,9 @@ zh-TW:
label_permissions: "權限"
label_permissions_report: "權限列表"
label_personalize_page: "個人化本頁"
- label_placeholder_user: "占位使用者"
+ label_placeholder_user: "佔位使用者"
label_placeholder_user_new: "新的佔位使用者"
- label_placeholder_user_plural: "占位使用者"
+ label_placeholder_user_plural: "佔位使用者"
label_planning: "計劃中"
label_please_login: "請登入"
label_plugins: "擴充套件"
@@ -3353,7 +3353,7 @@ zh-TW:
permission_archive_project: "封存專案"
permission_create_user: "新增使用者"
permission_manage_user: "編輯使用者"
- permission_manage_placeholder_user: "建立、編輯及刪除占位使用者"
+ permission_manage_placeholder_user: "建立、編輯及刪除佔位使用者"
permission_add_subprojects: "建立子專案"
permission_add_work_package_watchers: "新增監看者"
permission_assign_versions: "指派版本"
@@ -3885,8 +3885,6 @@ zh-TW:
new:
description: "此專案階段的變更會反映在啟用此階段的所有專案中。"
heading: "新增階段"
- edit:
- heading: "編輯階段"
both_gate: "開始以及結束關卡"
no_gate: "沒有關卡"
start_gate: "開始關卡"
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 039b252d96a..4674703d554 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -1286,7 +1286,7 @@ en:
attributes:
content_type:
blank: "The content type of the file cannot be blank."
- not_whitelisted: "The file was rejected by an automatic filter. '%{value}' is not whitelisted for upload."
+ not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
format: "%{message}"
capability:
context:
@@ -4117,8 +4117,6 @@ en:
new:
description: "Changes to this project phase will be reflected in all projects where it is enabled."
heading: "New phase"
- edit:
- heading: "Edit phase"
both_gate: "Start and finish gate"
no_gate: "No gate"
start_gate: "Start gate"
diff --git a/config/static_links.yml b/config/static_links.yml
index 01fd2380fdb..f72bc765f88 100644
--- a/config/static_links.yml
+++ b/config/static_links.yml
@@ -156,6 +156,8 @@ enterprise_features:
href: https://www.openproject.org/docs/user-guide/activity/#internal-comments-enterprise-add-on
nextcloud_sso:
href: https://www.openproject.org/docs/system-admin-guide/integrations/nextcloud/oidc-sso
+ customize_life_cycle:
+ href: https://www.openproject.org/docs/system-admin-guide/projects/project-life-cycle/
sysadmin_docs:
saml:
href: https://www.openproject.org/docs/system-admin-guide/authentication/saml/
diff --git a/docs/glossary/README.md b/docs/glossary/README.md
index a4085ebbc62..b001bd0d6ad 100644
--- a/docs/glossary/README.md
+++ b/docs/glossary/README.md
@@ -349,7 +349,7 @@ In OpenProject, the project life cycle consists of [phases](#phase) and [phase g
Phases and phase gates are visible on the [project overview](#project-overview) page, in [work package tables](#work-package-table), and in [project lists](#project-lists). They can be used for filtering, grouping, and scheduling. Project administrators can activate relevant phases and gates for each project, while the global configuration of phases is an [Enterprise add-on](#enterprise-add-on).
-[Read more acout the project life cycle in our system admin guide](../system-admin-guide/projects/project-life-cycle).
+[Read more about the project life cycle in our system admin guide](../system-admin-guide/projects/project-life-cycle).
### Project lists
diff --git a/docs/release-notes/16-1-0/README.md b/docs/release-notes/16-1-0/README.md
index 034f14f8518..25025ecd149 100644
--- a/docs/release-notes/16-1-0/README.md
+++ b/docs/release-notes/16-1-0/README.md
@@ -10,10 +10,14 @@ release_date: 2025-06-18
Release date: 2025-06-18
-We released [OpenProject 16.1.0](https://community.openproject.org/versions/2194). The release contains several bug fixes and we recommend updating to the newest version. In these Release Notes, we will give an overview of important feature changes and important technical changes. At the end, you will find a complete list of all changes and bug fixes.
+We released [OpenProject 16.1.0](https://community.openproject.org/versions/2194). The release contains several bug fixes and we recommend updating to the newest version. In these Release Notes, we will give an overview of important feature changes and important technical changes. At the end, you will find a complete list of all changes and bug fixes.
## Important feature changes
+Take a look at our release video showing the most important features introduced in OpenProject 16.1.0:
+
+
+
### Structure the project life cycle with phases and phase gates
OpenProject 16.1 introduces a powerful new way to reflect your project’s structure over time: using project phases like **Initiating, Planning, Executing, and Closing**, with optional phase gates at the start or end of each phase.
@@ -37,7 +41,7 @@ One of the most upvoted Community requests is now available: You can now **expor
Meeting organizers and participants can generate a printable PDF that includes the agenda, outcomes, participants, and more. This makes it ideal for sharing with colleagues, archiving meeting records, or informing stakeholders who could not attend.
-The export is available via the More (⋯) menu for both one-time meetings and individual occurrences of recurring meetings. You can **customize the PDF** before download by choosing wheather to include:
+The export is available via the More (⋯) menu for both one-time meetings and individual occurrences of recurring meetings. You can **customize the PDF** before download by choosing whether to include:
- the list of participants,
- the list of attachments,
@@ -152,7 +156,7 @@ Developers can now read and manage emoji reactions, create and update reminders,
- Feature: Add hovercard to gates \[[#62608](https://community.openproject.org/wp/62608)\]
- Feature: TreeView Primer View Component \[[#62667](https://community.openproject.org/wp/62667)\]
- Feature: Implementing ARIA live regions to communicate contextual changes \[[#62708](https://community.openproject.org/wp/62708)\]
-- Feature: Show a TreeView on the page of hierachy customFields \[[#62993](https://community.openproject.org/wp/62993)\]
+- Feature: Show a TreeView on the page of hierarchy customFields \[[#62993](https://community.openproject.org/wp/62993)\]
- Feature: Show attribute help texts in Primerized Project Settings > Information form \[[#63737](https://community.openproject.org/wp/63737)\]
- Feature: Primerize attribute help texts \[[#63738](https://community.openproject.org/wp/63738)\]
- Feature: Re-order and re-structure the 'More' action menu for sections and agenda items \[[#64074](https://community.openproject.org/wp/64074)\]
@@ -166,7 +170,7 @@ Developers can now read and manage emoji reactions, create and update reminders,
- Bugfix: Project search is under the Search icon in New Recurring Meeting modal \[[#59945](https://community.openproject.org/wp/59945)\]
- Bugfix: User can't save lifecycle modal if project is invalid \[[#60666](https://community.openproject.org/wp/60666)\]
- Bugfix: Activity Tab renders the same turbo frame multiple times inside of itself \[[#61544](https://community.openproject.org/wp/61544)\]
-- Bugfix: The 'overdue' date colour does not adapt well to dark mode (fixed hex/odd colour variable) \[[#62199](https://community.openproject.org/wp/62199)\]
+- Bugfix: The 'overdue' date color does not adapt well to dark mode (fixed hex/odd color variable) \[[#62199](https://community.openproject.org/wp/62199)\]
- Bugfix: Seeder fails during upgrade with ArgumentError: Nothing registered with reference :default\_role\_project\_admin (ArgumentError) \[[#62582](https://community.openproject.org/wp/62582)\]
- Bugfix: (Regression) Error on Save (in various places) \[[#62627](https://community.openproject.org/wp/62627)\]
- Bugfix: Reminders: saving without entering a date or time throws an error but also unnecessarily clears the other field \[[#63461](https://community.openproject.org/wp/63461)\]
diff --git a/docs/release-notes/16-1-0/openproject-16-1-export-meeting-to-pdf.png b/docs/release-notes/16-1-0/openproject-16-1-export-meeting-to-pdf.png
index 9d2ee61f571..c5be69437cb 100644
Binary files a/docs/release-notes/16-1-0/openproject-16-1-export-meeting-to-pdf.png and b/docs/release-notes/16-1-0/openproject-16-1-export-meeting-to-pdf.png differ
diff --git a/docs/release-notes/16-1-0/openproject-16-1-negative-lag.png b/docs/release-notes/16-1-0/openproject-16-1-negative-lag.png
index f2e317287c5..ae482ecd19e 100644
Binary files a/docs/release-notes/16-1-0/openproject-16-1-negative-lag.png and b/docs/release-notes/16-1-0/openproject-16-1-negative-lag.png differ
diff --git a/docs/release-notes/16-1-0/openproject-16-1-project-life-cycle-project-overview-highlighted.png b/docs/release-notes/16-1-0/openproject-16-1-project-life-cycle-project-overview-highlighted.png
index 25bca9f6fe5..f714adc6360 100644
Binary files a/docs/release-notes/16-1-0/openproject-16-1-project-life-cycle-project-overview-highlighted.png and b/docs/release-notes/16-1-0/openproject-16-1-project-life-cycle-project-overview-highlighted.png differ
diff --git a/docs/release-notes/16-1-0/openproject-16-1-reminder-options.png b/docs/release-notes/16-1-0/openproject-16-1-reminder-options.png
index e1e7444e204..9a53c0f0efa 100644
Binary files a/docs/release-notes/16-1-0/openproject-16-1-reminder-options.png and b/docs/release-notes/16-1-0/openproject-16-1-reminder-options.png differ
diff --git a/docs/release-notes/16-1-0/openproject-hierarchy-custom-field-tree.png b/docs/release-notes/16-1-0/openproject-hierarchy-custom-field-tree.png
index 88b86e94405..b6e98c847ab 100644
Binary files a/docs/release-notes/16-1-0/openproject-hierarchy-custom-field-tree.png and b/docs/release-notes/16-1-0/openproject-hierarchy-custom-field-tree.png differ
diff --git a/docs/release-notes/16-1-1/README.md b/docs/release-notes/16-1-1/README.md
new file mode 100644
index 00000000000..aae38e4b530
--- /dev/null
+++ b/docs/release-notes/16-1-1/README.md
@@ -0,0 +1,38 @@
+---
+title: OpenProject 16.1.1
+sidebar_navigation:
+ title: 16.1.1
+release_version: 16.1.1
+release_date: 2025-06-26
+---
+
+# OpenProject 16.1.1
+
+Release date: 2025-06-26
+
+We released OpenProject [OpenProject 16.1.1](https://community.openproject.org/versions/2206).
+The release contains several bug fixes and we recommend updating to the newest version.
+In these Release Notes, we will give an overview of important feature changes.
+At the end, you will find a complete list of all changes and bug fixes.
+
+
+
+## Bug fixes and changes
+
+
+
+
+- Bugfix: ActiveRecord::Deadlocked on Attachments::FinishDirectUploadJob#perform leading to lost uploads \[[#63380](https://community.openproject.org/wp/63380)\]
+- Bugfix: Unclear error message on bulk edit of parent and children wps \[[#64203](https://community.openproject.org/wp/64203)\]
+- Bugfix: Wrong wording for project phases in system administration \[[#64794](https://community.openproject.org/wp/64794)\]
+- Bugfix: Edited reminder is not working (not reminding at the selected time and date) \[[#64971](https://community.openproject.org/wp/64971)\]
+- Bugfix: Error 500 when making a predecessor with children a child of its successor \[[#64973](https://community.openproject.org/wp/64973)\]
+- Bugfix: Rendering and Server Error when managing a Custom Action with a multi-select user custom field. \[[#64981](https://community.openproject.org/wp/64981)\]
+- Bugfix: ActiveRecord::RecordNotUnique (app/services/journals/create\_service.rb:99 in block in Journals::CreateService#create\_journal) \[[#65009](https://community.openproject.org/wp/65009)\]
+- Bugfix: SystemStackError in WorkPackage::SchedulingRules#schedule\_automatically? \[[#65062](https://community.openproject.org/wp/65062)\]
+- Bugfix: OIDC does not forward to end\_session\_endpoint as configured \[[#65076](https://community.openproject.org/wp/65076)\]
+- Bugfix: Inline comment attachments are not linked to the comment when submit is via API \[[#65077](https://community.openproject.org/wp/65077)\]
+- Bugfix: create\_meeting\_minutes was never renamed to manage\_outcomes \[[#65081](https://community.openproject.org/wp/65081)\]
+
+
+
diff --git a/docs/release-notes/README.md b/docs/release-notes/README.md
index acbeb6351a1..2f8ffb9f22b 100644
--- a/docs/release-notes/README.md
+++ b/docs/release-notes/README.md
@@ -13,6 +13,13 @@ Stay up to date and get an overview of the new features included in the releases
+## 16.1.1
+
+Release date: 2025-06-26
+
+[Release Notes](16-1-1/)
+
+
## 16.1.0
Release date: 2025-06-18
diff --git a/docs/system-admin-guide/custom-fields/openproject_system_guide_custom_field_new_hierarchy_subitems.png b/docs/system-admin-guide/custom-fields/openproject_system_guide_custom_field_new_hierarchy_subitems.png
index 8fb64dcc2b1..d7761172a48 100644
Binary files a/docs/system-admin-guide/custom-fields/openproject_system_guide_custom_field_new_hierarchy_subitems.png and b/docs/system-admin-guide/custom-fields/openproject_system_guide_custom_field_new_hierarchy_subitems.png differ
diff --git a/docs/system-admin-guide/custom-fields/openproject_system_guide_hierarchy_field_add_further_levels.png b/docs/system-admin-guide/custom-fields/openproject_system_guide_hierarchy_field_add_further_levels.png
index 26bfe9a7fbc..9459fb8d45c 100644
Binary files a/docs/system-admin-guide/custom-fields/openproject_system_guide_hierarchy_field_add_further_levels.png and b/docs/system-admin-guide/custom-fields/openproject_system_guide_hierarchy_field_add_further_levels.png differ
diff --git a/docs/system-admin-guide/custom-fields/openproject_system_guide_hierarchy_field_add_item_button.png b/docs/system-admin-guide/custom-fields/openproject_system_guide_hierarchy_field_add_item_button.png
index 9e4728ed214..2cc79ee5d31 100644
Binary files a/docs/system-admin-guide/custom-fields/openproject_system_guide_hierarchy_field_add_item_button.png and b/docs/system-admin-guide/custom-fields/openproject_system_guide_hierarchy_field_add_item_button.png differ
diff --git a/docs/system-admin-guide/custom-fields/openproject_system_guide_hierarchy_field_add_item_form.png b/docs/system-admin-guide/custom-fields/openproject_system_guide_hierarchy_field_add_item_form.png
index 20abc629a28..0e718ffebdd 100644
Binary files a/docs/system-admin-guide/custom-fields/openproject_system_guide_hierarchy_field_add_item_form.png and b/docs/system-admin-guide/custom-fields/openproject_system_guide_hierarchy_field_add_item_form.png differ
diff --git a/docs/system-admin-guide/custom-fields/openproject_system_guide_hierarchy_field_edit_items.png b/docs/system-admin-guide/custom-fields/openproject_system_guide_hierarchy_field_edit_items.png
index 66ce691714e..3e339e815bc 100644
Binary files a/docs/system-admin-guide/custom-fields/openproject_system_guide_hierarchy_field_edit_items.png and b/docs/system-admin-guide/custom-fields/openproject_system_guide_hierarchy_field_edit_items.png differ
diff --git a/docs/system-admin-guide/custom-fields/openproject_system_guide_new_custom_field_path.png b/docs/system-admin-guide/custom-fields/openproject_system_guide_new_custom_field_path.png
index 8a5b6c35e9b..e2b818c0e28 100644
Binary files a/docs/system-admin-guide/custom-fields/openproject_system_guide_new_custom_field_path.png and b/docs/system-admin-guide/custom-fields/openproject_system_guide_new_custom_field_path.png differ
diff --git a/docs/system-admin-guide/integrations/nextcloud/oidc-sso/keycloak_capability_config.png b/docs/system-admin-guide/integrations/nextcloud/oidc-sso/keycloak_capability_config.png
index 1a9321f55be..4ca08f09a2b 100644
Binary files a/docs/system-admin-guide/integrations/nextcloud/oidc-sso/keycloak_capability_config.png and b/docs/system-admin-guide/integrations/nextcloud/oidc-sso/keycloak_capability_config.png differ
diff --git a/docs/system-admin-guide/integrations/nextcloud/oidc-sso/keycloak_new_client_scope.png b/docs/system-admin-guide/integrations/nextcloud/oidc-sso/keycloak_new_client_scope.png
deleted file mode 100644
index c175ad6e60f..00000000000
Binary files a/docs/system-admin-guide/integrations/nextcloud/oidc-sso/keycloak_new_client_scope.png and /dev/null differ
diff --git a/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle.png b/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle.png
index f5111682b4c..41f357321c6 100644
Binary files a/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle.png and b/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle.png differ
diff --git a/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle_add_new.png b/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle_add_new.png
index 7092acc8f86..2f1142f3b24 100644
Binary files a/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle_add_new.png and b/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle_add_new.png differ
diff --git a/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle_add_new_gates.png b/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle_add_new_gates.png
index d4b6279768a..7c430640067 100644
Binary files a/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle_add_new_gates.png and b/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle_add_new_gates.png differ
diff --git a/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle_delete.png b/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle_delete.png
index 19879eac911..9b02abab75b 100644
Binary files a/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle_delete.png and b/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle_delete.png differ
diff --git a/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle_edit.png b/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle_edit.png
index 7643ac7255d..9f8a40fca66 100644
Binary files a/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle_edit.png and b/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle_edit.png differ
diff --git a/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle_move.png b/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle_move.png
index c0e230d5121..d74968f21c1 100644
Binary files a/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle_move.png and b/docs/system-admin-guide/projects/project-life-cycle/openproject_userguide_project_settings_life_cycle_move.png differ
diff --git a/docs/use-cases/README.md b/docs/use-cases/README.md
index 1180acee006..1be5b18b228 100644
--- a/docs/use-cases/README.md
+++ b/docs/use-cases/README.md
@@ -13,4 +13,6 @@ keywords: use-cases
| [Resource management](resource-management) | OpenProject does not have the automated functionality to provide detailed resource management or employee work capacity calculations. This guide with detailed step-by-step instructions introduces a workaround that can provide an avenue to accomplish this manually and visually beyond the functionality the Team Planner Module provides. |
| [Portfolio management](portfolio-management) | This guide provides detailed step-by-step instruction on how to set up an overview of your project portfolio and create custom reports using the Project Overview, Wiki and the Rich text (WYSIWYG) editor in OpenProject. |
| [Implementing Scaled Agile Framework (SAFe) in OpenProject](safe-framework) | Learn how to set up and configure OpenProject to support the Scaled Agile Framework (SAFe) to successfully deliver value to customers using agile practices at scale. |
-| [Test management](test-management) | OpenProject can be configured to support lightweight test management using custom work package types, workflows and project templates. This guide describes how to manage test cases and test runs in a reusable, scalable way |
+| [Test management](test-management) | OpenProject can be configured to support lightweight test management using custom work package types, workflows and project templates. This guide describes how to manage test cases and test runs in a reusable, scalable way. |
+| [Meeting management](meeting-management) | With OpenProject's meeting management features, you can structure your meetings, define agendas, assign action items, and keep everything transparent and traceable in one place. Ensure your team stays aligned and decisions are well-documented throughout the project life cycle.
Learn how plan, conduct, and follow up on meetings with OpenProject. |
+
diff --git a/docs/use-cases/meeting-management/README.md b/docs/use-cases/meeting-management/README.md
new file mode 100644
index 00000000000..2c49c7d8954
--- /dev/null
+++ b/docs/use-cases/meeting-management/README.md
@@ -0,0 +1,87 @@
+---
+sidebar_navigation:
+ title: Meeting Management
+ priority: 900
+description: Meeting management in OpenProject
+keywords: meetings, meeting management, use case, use-case, agenda
+---
+
+# Use case: meeting management with OpenProject
+
+At OpenProject, we manage internal team coordination using the very tools we build. We actively use the [Meetings module](https://www.openproject.org/collaboration-software-features/meeting-management/) in our daily work at OpenProject. It’s a key part of how we plan, collaborate, and follow up — and it saves us real time and effort.
+
+**Key benefits we rely on**:
+
+- **Time-saving templates**: Recurring meetings with agenda templates to reduce manual setup
+- **Shared ownership**: All participants contributes to the agenda
+- **Live editing & linking of work packages**: Stay focused and in context
+- **Agenda backlog**: Park topics for future discussion — without losing them
+
+This use case shows how we run our **weekly all-hands team meetings** with the **Meetings module**, using recurring meetings, collaborative agenda building, and integrated follow-ups.
+
+------
+
+## Set up recurring meetings with agenda templates
+
+We create a **recurring meeting series** to maintain a consistent rhythm for our weekly team sync. We define the participants, the schedule and the meeting room link.
+
+
+
+We then configure a **template agenda** to structure our recurring weeklies. Every new meeting will be based on this template. It includes sections like:
+
+- **Good news** – a space where everyone can share exciting updates about OpenProject in the past week
+- **News from the CEO** – our CEO highlights any company-wide updates
+- **Team updates** – each team gives a short summary of what they've been working on
+- **Customer and user feedback** – we highlight notable user feedback, especially on newly released features
+- **[Core values](https://www.openproject.org/blog/core-values-openproject/)** – a moment to recognize each other and reflect on how we embody our values
+- **Roadmap outlook** – a look at upcoming initiatives and changes
+
+This setup saves time and ensures a clear structure. Of course, we can still add topics that are specific to a particular week.
+
+------
+
+## Collaborate on agenda building
+
+Throughout the week, team members add agenda items directly to the upcoming meeting. We also link **work packages** (for example, features and epics we work on) to agenda items — especially helpful for development updates. This keeps discussion connected to actual work and allows anyone interested to go explore the original specifications.
+
+
+
+If there are topics that we don't get to or that need to moved out of the day's agenda, we use the **Series Backlog**. This is a parking lot shared with all occurrences of a meeting series for topics and issues that aren’t urgent. It appears in every agenda, so we can easily pull in topics when time allows.
+
+------
+
+## Run the meeting: live, linked, and structured
+
+During the meeting, we project the agenda live for everyone to follow. Each item includes links to tasks, documents, or discussions. We can edit the agenda in real time and document outcomes directly.
+
+If someone adds a last-minute agenda item just before the meeting (or even during), the system shows a helpful banner saying that something was changed by another user and reminding the person viewing the agenda (including the one projecting) that they should reload the screen. This makes it easy to refresh the agenda and stay on track.
+
+
+
+------
+
+## Document important decisions
+
+We document **meeting outcomes** right after the meeting (or live during it). These include decisions, notes, and follow-ups — often linked directly to **work packages**. This ensure that next steps are clear and actionable.
+
+When we add meeting notes or document outcomes for agenda items that have linked work packages, that information is also directly visible from within each work package via the 'Meetings' tab. This makes it easier to find related discussions and decisions.
+
+Nothing gets lost, and action items stay tied to project workflows.
+
+
+
+------
+
+## Easy access for follow-up
+
+Meeting minutes are always available for team members who couldn’t attend — whether because they on vacation, busy with client work, or sick on leave. It’s easy to catch up on what was discussed, what was decided, and what the next steps are. All team members can navigate to the Meetings module and click on "All-hands weekly" from the menu on the left. They will then see a list of all past meetings so they can easily catch up.
+
+
+
+This keeps everyone aligned, even when not everyone is in the room.
+
+## Why it matters
+
+Using the Meetings module helps us keep our communication clear, our priorities visible, and our teams aligned. It’s not just a tool for note-taking — it’s a shared workspace that supports structure, transparency, and accountability across the entire company. By integrating meetings with our projects, we turned a routine process into a driver of clarity and momentum.
+
+For more information on how to use the module, you can read our [documentation on the Meetings module](../../user-guide/meetings).
diff --git a/docs/use-cases/meeting-management/openproject_use_cases_weekly_meetings_add_outcome.gif b/docs/use-cases/meeting-management/openproject_use_cases_weekly_meetings_add_outcome.gif
new file mode 100644
index 00000000000..64f3007111f
Binary files /dev/null and b/docs/use-cases/meeting-management/openproject_use_cases_weekly_meetings_add_outcome.gif differ
diff --git a/docs/use-cases/meeting-management/openproject_use_cases_weekly_meetings_past_meetings_list.png b/docs/use-cases/meeting-management/openproject_use_cases_weekly_meetings_past_meetings_list.png
new file mode 100644
index 00000000000..d20729a2f3e
Binary files /dev/null and b/docs/use-cases/meeting-management/openproject_use_cases_weekly_meetings_past_meetings_list.png differ
diff --git a/docs/use-cases/meeting-management/openproject_use_cases_weekly_meetings_setup.png b/docs/use-cases/meeting-management/openproject_use_cases_weekly_meetings_setup.png
new file mode 100644
index 00000000000..cceedd681ae
Binary files /dev/null and b/docs/use-cases/meeting-management/openproject_use_cases_weekly_meetings_setup.png differ
diff --git a/docs/use-cases/meeting-management/openproject_use_cases_weekly_meetings_team_update_example.png b/docs/use-cases/meeting-management/openproject_use_cases_weekly_meetings_team_update_example.png
new file mode 100644
index 00000000000..fc6ef0264b7
Binary files /dev/null and b/docs/use-cases/meeting-management/openproject_use_cases_weekly_meetings_team_update_example.png differ
diff --git a/docs/use-cases/meeting-management/openproject_use_cases_weekly_meetings_update_banner.png b/docs/use-cases/meeting-management/openproject_use_cases_weekly_meetings_update_banner.png
new file mode 100644
index 00000000000..f7e8bb7d2ea
Binary files /dev/null and b/docs/use-cases/meeting-management/openproject_use_cases_weekly_meetings_update_banner.png differ
diff --git a/docs/use-cases/portfolio-management/README.md b/docs/use-cases/portfolio-management/README.md
index 56d5a9b584e..af5d841b1ea 100644
--- a/docs/use-cases/portfolio-management/README.md
+++ b/docs/use-cases/portfolio-management/README.md
@@ -1,7 +1,7 @@
---
sidebar_navigation:
title: Portfolio Management and Custom Reporting
- priority: 950
+ priority: 800
description: Step-by-step instructions about portfolio management and custom reporting
keywords: use-case, portfolio management
---
diff --git a/docs/use-cases/resource-management/README.md b/docs/use-cases/resource-management/README.md
index 405bc89937c..e7b52542c89 100644
--- a/docs/use-cases/resource-management/README.md
+++ b/docs/use-cases/resource-management/README.md
@@ -1,7 +1,7 @@
---
sidebar_navigation:
title: Resource Management
- priority: 990
+ priority: 700
description: Step-by-step instruction about resource management
keywords: use-case, resource management
---
diff --git a/docs/use-cases/safe-framework/README.md b/docs/use-cases/safe-framework/README.md
index 16e782daea2..b54926a6d70 100644
--- a/docs/use-cases/safe-framework/README.md
+++ b/docs/use-cases/safe-framework/README.md
@@ -1,7 +1,7 @@
---
sidebar_navigation:
title: Scaled Agile Framework (SAFe)
- priority: 930
+ priority: 600
description: Learn how to set up and configure OpenProject to support the Scaled Agile Framework (SAFe) to successfully deliver value to customers using agile practices at scale.
keywords: safe, scaled agile, release train, program increment, ART backlog, roadmap, portfolio backlog, solution train, kanban, enabler, capability, scrum, roadmap
diff --git a/docs/use-cases/test-management/adjust-test-plan.png b/docs/use-cases/test-management/adjust-test-plan.png
index 5215888c49e..9361789b4ec 100644
Binary files a/docs/use-cases/test-management/adjust-test-plan.png and b/docs/use-cases/test-management/adjust-test-plan.png differ
diff --git a/docs/use-cases/test-management/automatically-generated-subject-after-saving2.png b/docs/use-cases/test-management/automatically-generated-subject-after-saving2.png
index a3c461a3f53..8b9b1f7cbb0 100644
Binary files a/docs/use-cases/test-management/automatically-generated-subject-after-saving2.png and b/docs/use-cases/test-management/automatically-generated-subject-after-saving2.png differ
diff --git a/docs/use-cases/test-management/automatically-generated-subject-before-saving.png b/docs/use-cases/test-management/automatically-generated-subject-before-saving.png
index c4917a3e1b4..52c9319a801 100644
Binary files a/docs/use-cases/test-management/automatically-generated-subject-before-saving.png and b/docs/use-cases/test-management/automatically-generated-subject-before-saving.png differ
diff --git a/docs/use-cases/test-management/board-for-test-case-tracking.png b/docs/use-cases/test-management/board-for-test-case-tracking.png
index e380aac20f1..d3714d96827 100644
Binary files a/docs/use-cases/test-management/board-for-test-case-tracking.png and b/docs/use-cases/test-management/board-for-test-case-tracking.png differ
diff --git a/docs/use-cases/test-management/create-test-plan.png b/docs/use-cases/test-management/create-test-plan.png
index 5ee7910a8bb..1a58652b4b4 100644
Binary files a/docs/use-cases/test-management/create-test-plan.png and b/docs/use-cases/test-management/create-test-plan.png differ
diff --git a/docs/use-cases/test-management/create-test-report.png b/docs/use-cases/test-management/create-test-report.png
index 64235a26407..1a58f15df26 100644
Binary files a/docs/use-cases/test-management/create-test-report.png and b/docs/use-cases/test-management/create-test-report.png differ
diff --git a/docs/use-cases/test-management/graph-for-bugs-overview.png b/docs/use-cases/test-management/graph-for-bugs-overview.png
index e7e2603ad1c..12824657afd 100644
Binary files a/docs/use-cases/test-management/graph-for-bugs-overview.png and b/docs/use-cases/test-management/graph-for-bugs-overview.png differ
diff --git a/docs/use-cases/test-management/test-automation-integration.png b/docs/use-cases/test-management/test-automation-integration.png
index c78c7d0c446..07b1eaf7f77 100644
Binary files a/docs/use-cases/test-management/test-automation-integration.png and b/docs/use-cases/test-management/test-automation-integration.png differ
diff --git a/docs/use-cases/test-management/test-case-configuration-example.png b/docs/use-cases/test-management/test-case-configuration-example.png
index 03b7175d142..28343f7cf96 100644
Binary files a/docs/use-cases/test-management/test-case-configuration-example.png and b/docs/use-cases/test-management/test-case-configuration-example.png differ
diff --git a/docs/use-cases/test-management/test-management-entities.png b/docs/use-cases/test-management/test-management-entities.png
index 0064d7b520e..612f6b91240 100644
Binary files a/docs/use-cases/test-management/test-management-entities.png and b/docs/use-cases/test-management/test-management-entities.png differ
diff --git a/docs/use-cases/test-management/test-run-configuration-example.png b/docs/use-cases/test-management/test-run-configuration-example.png
index 7b61d990e77..16d97c5175d 100644
Binary files a/docs/use-cases/test-management/test-run-configuration-example.png and b/docs/use-cases/test-management/test-run-configuration-example.png differ
diff --git a/docs/use-cases/test-management/test-run-workflow-configuration-example.png b/docs/use-cases/test-management/test-run-workflow-configuration-example.png
index ecc73af6921..f66870b4061 100644
Binary files a/docs/use-cases/test-management/test-run-workflow-configuration-example.png and b/docs/use-cases/test-management/test-run-workflow-configuration-example.png differ
diff --git a/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_agenda_item_more_menu.png b/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_agenda_item_more_menu.png
index 3428289fe64..a27b660f3e4 100644
Binary files a/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_agenda_item_more_menu.png and b/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_agenda_item_more_menu.png differ
diff --git a/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_copy_meeting.png b/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_copy_meeting.png
index 48f7ea27ecd..3f4d1e3f37f 100644
Binary files a/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_copy_meeting.png and b/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_copy_meeting.png differ
diff --git a/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_delete_meeting.png b/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_delete_meeting.png
index 166863841ce..cf9a7af6bbd 100644
Binary files a/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_delete_meeting.png and b/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_delete_meeting.png differ
diff --git a/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_edit_meeting_title.png b/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_edit_meeting_title.png
index 3ce838aa106..be3c8bc6a8c 100644
Binary files a/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_edit_meeting_title.png and b/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_edit_meeting_title.png differ
diff --git a/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_export_pdf.png b/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_export_pdf.png
index b641fe3e4fd..85caf635e29 100644
Binary files a/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_export_pdf.png and b/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_export_pdf.png differ
diff --git a/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_pdf_export_dialogue.png b/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_pdf_export_dialogue.png
index 30828337f87..f5a179caaff 100644
Binary files a/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_pdf_export_dialogue.png and b/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_pdf_export_dialogue.png differ
diff --git a/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_pdf_export_file.png b/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_pdf_export_file.png
index b6903d5c399..59e17ddc5e7 100644
Binary files a/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_pdf_export_file.png and b/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_pdf_export_file.png differ
diff --git a/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_section_more_menu.png b/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_section_more_menu.png
index b1464f8bb80..e4f50ead992 100644
Binary files a/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_section_more_menu.png and b/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_section_more_menu.png differ
diff --git a/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_select_meeting_history.png b/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_select_meeting_history.png
index 3c5ebd8bbe7..2585a6b98bb 100644
Binary files a/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_select_meeting_history.png and b/docs/user-guide/meetings/one-time-meetings/openproject_userguide_meetings_select_meeting_history.png differ
diff --git a/docs/user-guide/project-overview/README.md b/docs/user-guide/project-overview/README.md
index ac6dad79302..4bcd2e509b1 100644
--- a/docs/user-guide/project-overview/README.md
+++ b/docs/user-guide/project-overview/README.md
@@ -13,7 +13,7 @@ The **Project overview** page is a dashboard with important information about yo
| Topic | Content |
| ------------------------------------------------------------ | ------------------------------------------------------------ |
| [What is the project overview?](#what-is-the-project-overview) | What can I do with the project overview page? |
-| [Project life cycle](#project-attributes) | What is the project life cycle? |
+| [Project life cycle](#project-life-cycle) | What is the project life cycle? |
| [Project attributes](#project-attributes) | What are project attributes and how can I use them? |
| [Mark project as a favorite](#mark-a-project-as-favorite) | How can I mark a project as favorite? |
| [Archive a project](#archive-a-project) | How can I archive a project from the project overview page? |
diff --git a/docs/user-guide/project-overview/openproject_user_guide_project_overview.png b/docs/user-guide/project-overview/openproject_user_guide_project_overview.png
index 0a659ccf2e2..06adc478082 100644
Binary files a/docs/user-guide/project-overview/openproject_user_guide_project_overview.png and b/docs/user-guide/project-overview/openproject_user_guide_project_overview.png differ
diff --git a/docs/user-guide/project-overview/openproject_user_guide_project_overview_project_life_cycle.png b/docs/user-guide/project-overview/openproject_user_guide_project_overview_project_life_cycle.png
index 924529f0b6c..85ea3217829 100644
Binary files a/docs/user-guide/project-overview/openproject_user_guide_project_overview_project_life_cycle.png and b/docs/user-guide/project-overview/openproject_user_guide_project_overview_project_life_cycle.png differ
diff --git a/docs/user-guide/project-overview/openproject_user_guide_project_overview_project_life_cycle_edit_date_range.png b/docs/user-guide/project-overview/openproject_user_guide_project_overview_project_life_cycle_edit_date_range.png
index f9526c9c9fe..49f535583bf 100644
Binary files a/docs/user-guide/project-overview/openproject_user_guide_project_overview_project_life_cycle_edit_date_range.png and b/docs/user-guide/project-overview/openproject_user_guide_project_overview_project_life_cycle_edit_date_range.png differ
diff --git a/docs/user-guide/projects/README.md b/docs/user-guide/projects/README.md
index 8379ce669f7..1ed83dc1189 100644
--- a/docs/user-guide/projects/README.md
+++ b/docs/user-guide/projects/README.md
@@ -115,7 +115,7 @@ Setting a project to public will make it accessible to all people within your Op
### Copy a project
-You can copy an existing project by navigating to the [Project settings](project-settings) -> Information. Click the **More** (three dots) menu in the upper right corner and selec **Copy**.
+You can copy an existing project by navigating to the [Project settings](project-settings) -> Information. Click the **More** (three dots) menu in the upper right corner and select **Copy**.

diff --git a/docs/user-guide/projects/project-information-copy-project.png b/docs/user-guide/projects/project-information-copy-project.png
index ef85593ade4..e9a5672fc37 100644
Binary files a/docs/user-guide/projects/project-information-copy-project.png and b/docs/user-guide/projects/project-information-copy-project.png differ
diff --git a/docs/user-guide/projects/project-lists/openproject_user_guide_project_lists_filter_by_phase_and_gate.png b/docs/user-guide/projects/project-lists/openproject_user_guide_project_lists_filter_by_phase_and_gate.png
index 43af9f79634..2b408cd80b9 100644
Binary files a/docs/user-guide/projects/project-lists/openproject_user_guide_project_lists_filter_by_phase_and_gate.png and b/docs/user-guide/projects/project-lists/openproject_user_guide_project_lists_filter_by_phase_and_gate.png differ
diff --git a/docs/user-guide/projects/project-lists/openproject_user_guide_project_lists_filter_by_phase_and_gate_timeframe.png b/docs/user-guide/projects/project-lists/openproject_user_guide_project_lists_filter_by_phase_and_gate_timeframe.png
index 7a5e4a88378..b2111d2b6bd 100644
Binary files a/docs/user-guide/projects/project-lists/openproject_user_guide_project_lists_filter_by_phase_and_gate_timeframe.png and b/docs/user-guide/projects/project-lists/openproject_user_guide_project_lists_filter_by_phase_and_gate_timeframe.png differ
diff --git a/docs/user-guide/projects/project-lists/openproject_user_guide_project_lists_filter_by_phase_and_gate_timeframe_filter.png b/docs/user-guide/projects/project-lists/openproject_user_guide_project_lists_filter_by_phase_and_gate_timeframe_filter.png
index f0d66340e9d..b20c5e2e58c 100644
Binary files a/docs/user-guide/projects/project-lists/openproject_user_guide_project_lists_filter_by_phase_and_gate_timeframe_filter.png and b/docs/user-guide/projects/project-lists/openproject_user_guide_project_lists_filter_by_phase_and_gate_timeframe_filter.png differ
diff --git a/docs/user-guide/projects/project-lists/project-life-cycle-view-configured.png b/docs/user-guide/projects/project-lists/project-life-cycle-view-configured.png
index ccac753be0c..d7d767f6c1c 100644
Binary files a/docs/user-guide/projects/project-lists/project-life-cycle-view-configured.png and b/docs/user-guide/projects/project-lists/project-life-cycle-view-configured.png differ
diff --git a/docs/user-guide/projects/project-settings-copy-project-copy-options.png b/docs/user-guide/projects/project-settings-copy-project-copy-options.png
index b2f03cf628b..dde6748ed94 100644
Binary files a/docs/user-guide/projects/project-settings-copy-project-copy-options.png and b/docs/user-guide/projects/project-settings-copy-project-copy-options.png differ
diff --git a/docs/user-guide/projects/project-settings/activities-time-tracking/openproject_user_guide_project_settings_time_tracking_activities.png b/docs/user-guide/projects/project-settings/activities-time-tracking/openproject_user_guide_project_settings_time_tracking_activities.png
index 152449fe566..631e82525f0 100644
Binary files a/docs/user-guide/projects/project-settings/activities-time-tracking/openproject_user_guide_project_settings_time_tracking_activities.png and b/docs/user-guide/projects/project-settings/activities-time-tracking/openproject_user_guide_project_settings_time_tracking_activities.png differ
diff --git a/docs/user-guide/projects/project-settings/backlogs-settings/openproject_user_guide_project_settings_backlogs.png b/docs/user-guide/projects/project-settings/backlogs-settings/openproject_user_guide_project_settings_backlogs.png
index 630d5ab54a9..bfc3367fea4 100644
Binary files a/docs/user-guide/projects/project-settings/backlogs-settings/openproject_user_guide_project_settings_backlogs.png and b/docs/user-guide/projects/project-settings/backlogs-settings/openproject_user_guide_project_settings_backlogs.png differ
diff --git a/docs/user-guide/projects/project-settings/files/file-storages-available-in-project.png b/docs/user-guide/projects/project-settings/files/file-storages-available-in-project.png
index 9883c5454ca..e511d5d7ee2 100644
Binary files a/docs/user-guide/projects/project-settings/files/file-storages-available-in-project.png and b/docs/user-guide/projects/project-settings/files/file-storages-available-in-project.png differ
diff --git a/docs/user-guide/projects/project-settings/files/onedrive-storage-add-folders-new.png b/docs/user-guide/projects/project-settings/files/onedrive-storage-add-folders-new.png
index 296694e1f8b..a9948ae1048 100644
Binary files a/docs/user-guide/projects/project-settings/files/onedrive-storage-add-folders-new.png and b/docs/user-guide/projects/project-settings/files/onedrive-storage-add-folders-new.png differ
diff --git a/docs/user-guide/projects/project-settings/files/project-settings-attachments.png b/docs/user-guide/projects/project-settings/files/project-settings-attachments.png
index 0743874992b..492757979bc 100644
Binary files a/docs/user-guide/projects/project-settings/files/project-settings-attachments.png and b/docs/user-guide/projects/project-settings/files/project-settings-attachments.png differ
diff --git a/docs/user-guide/projects/project-settings/files/storage-add-new.png b/docs/user-guide/projects/project-settings/files/storage-add-new.png
index 38a7e28d9ad..466ff982720 100644
Binary files a/docs/user-guide/projects/project-settings/files/storage-add-new.png and b/docs/user-guide/projects/project-settings/files/storage-add-new.png differ
diff --git a/docs/user-guide/projects/project-settings/files/storage-add-project-folder.png b/docs/user-guide/projects/project-settings/files/storage-add-project-folder.png
index 83ccbdfaa03..6e871c1ea75 100644
Binary files a/docs/user-guide/projects/project-settings/files/storage-add-project-folder.png and b/docs/user-guide/projects/project-settings/files/storage-add-project-folder.png differ
diff --git a/docs/user-guide/projects/project-settings/modules/openproject_user_guide_project_settings_modules.png b/docs/user-guide/projects/project-settings/modules/openproject_user_guide_project_settings_modules.png
index b294abdae37..d12f37d7acd 100644
Binary files a/docs/user-guide/projects/project-settings/modules/openproject_user_guide_project_settings_modules.png and b/docs/user-guide/projects/project-settings/modules/openproject_user_guide_project_settings_modules.png differ
diff --git a/docs/user-guide/projects/project-settings/openproject_user_guide_project_settings_module.png b/docs/user-guide/projects/project-settings/openproject_user_guide_project_settings_module.png
index a6808f1d9b9..716a45fd70c 100644
Binary files a/docs/user-guide/projects/project-settings/openproject_user_guide_project_settings_module.png and b/docs/user-guide/projects/project-settings/openproject_user_guide_project_settings_module.png differ
diff --git a/docs/user-guide/projects/project-settings/project-attributes/open_project_user_guide_project_settings_project_attributes_list.png b/docs/user-guide/projects/project-settings/project-attributes/open_project_user_guide_project_settings_project_attributes_list.png
index 275b94e830d..c222ebd805e 100644
Binary files a/docs/user-guide/projects/project-settings/project-attributes/open_project_user_guide_project_settings_project_attributes_list.png and b/docs/user-guide/projects/project-settings/project-attributes/open_project_user_guide_project_settings_project_attributes_list.png differ
diff --git a/docs/user-guide/projects/project-settings/project-information/openproject_user_guide_project_settings_information.png b/docs/user-guide/projects/project-settings/project-information/openproject_user_guide_project_settings_information.png
index d3844ffeb22..fc5860547b6 100644
Binary files a/docs/user-guide/projects/project-settings/project-information/openproject_user_guide_project_settings_information.png and b/docs/user-guide/projects/project-settings/project-information/openproject_user_guide_project_settings_information.png differ
diff --git a/docs/user-guide/projects/project-settings/project-information/openproject_user_guide_project_settings_information_more_icon_menu.png b/docs/user-guide/projects/project-settings/project-information/openproject_user_guide_project_settings_information_more_icon_menu.png
index 3e4bafca11f..16b572f282e 100644
Binary files a/docs/user-guide/projects/project-settings/project-information/openproject_user_guide_project_settings_information_more_icon_menu.png and b/docs/user-guide/projects/project-settings/project-information/openproject_user_guide_project_settings_information_more_icon_menu.png differ
diff --git a/docs/user-guide/projects/project-settings/project-information/openproject_user_guide_project_settings_information_subproject_and_identifier.png b/docs/user-guide/projects/project-settings/project-information/openproject_user_guide_project_settings_information_subproject_and_identifier.png
index 4413a7b81d7..f29bc2cdf04 100644
Binary files a/docs/user-guide/projects/project-settings/project-information/openproject_user_guide_project_settings_information_subproject_and_identifier.png and b/docs/user-guide/projects/project-settings/project-information/openproject_user_guide_project_settings_information_subproject_and_identifier.png differ
diff --git a/docs/user-guide/projects/project-settings/project-life-cycle/README.md b/docs/user-guide/projects/project-settings/project-life-cycle/README.md
index b0f9df5487e..5c76a301f8a 100644
--- a/docs/user-guide/projects/project-settings/project-life-cycle/README.md
+++ b/docs/user-guide/projects/project-settings/project-life-cycle/README.md
@@ -24,4 +24,4 @@ You can enable or disable existing project phases by navigating to **Project set
>
> A dedicated project permission is required to enable project phases.
-When copying a project, the active status of all gates and stages is preserved.
+When copying a project, the active status of all phases and phase gates is preserved.
diff --git a/docs/user-guide/projects/project-settings/project-life-cycle/openproject_userguide_project_settings_life_cycle.png b/docs/user-guide/projects/project-settings/project-life-cycle/openproject_userguide_project_settings_life_cycle.png
index 614d5de5121..bfee3fbf7f0 100644
Binary files a/docs/user-guide/projects/project-settings/project-life-cycle/openproject_userguide_project_settings_life_cycle.png and b/docs/user-guide/projects/project-settings/project-life-cycle/openproject_userguide_project_settings_life_cycle.png differ
diff --git a/docs/user-guide/projects/project-settings/required-disk-storage/openproject-user-guide-project-settings-required-disk-storage.png b/docs/user-guide/projects/project-settings/required-disk-storage/openproject-user-guide-project-settings-required-disk-storage.png
index e55666be8c9..a3df8f882b6 100644
Binary files a/docs/user-guide/projects/project-settings/required-disk-storage/openproject-user-guide-project-settings-required-disk-storage.png and b/docs/user-guide/projects/project-settings/required-disk-storage/openproject-user-guide-project-settings-required-disk-storage.png differ
diff --git a/docs/user-guide/projects/project-settings/versions/openproject_user_guide_project_settings_work_packages_versions.png b/docs/user-guide/projects/project-settings/versions/openproject_user_guide_project_settings_work_packages_versions.png
index 4896c337c4d..ef80746c022 100644
Binary files a/docs/user-guide/projects/project-settings/versions/openproject_user_guide_project_settings_work_packages_versions.png and b/docs/user-guide/projects/project-settings/versions/openproject_user_guide_project_settings_work_packages_versions.png differ
diff --git a/docs/user-guide/projects/project-settings/versions/openproject_user_guide_project_settings_work_packages_versions_new.png b/docs/user-guide/projects/project-settings/versions/openproject_user_guide_project_settings_work_packages_versions_new.png
index 8157fadb7c4..74ba650160a 100644
Binary files a/docs/user-guide/projects/project-settings/versions/openproject_user_guide_project_settings_work_packages_versions_new.png and b/docs/user-guide/projects/project-settings/versions/openproject_user_guide_project_settings_work_packages_versions_new.png differ
diff --git a/docs/user-guide/projects/project-settings/work-packages/openproject_user_guide_project_settings_work_packages_categories.png b/docs/user-guide/projects/project-settings/work-packages/openproject_user_guide_project_settings_work_packages_categories.png
index 023951d6e9c..6adc93eb539 100644
Binary files a/docs/user-guide/projects/project-settings/work-packages/openproject_user_guide_project_settings_work_packages_categories.png and b/docs/user-guide/projects/project-settings/work-packages/openproject_user_guide_project_settings_work_packages_categories.png differ
diff --git a/docs/user-guide/projects/project-settings/work-packages/openproject_user_guide_project_settings_work_packages_categories_edit_delete.png b/docs/user-guide/projects/project-settings/work-packages/openproject_user_guide_project_settings_work_packages_categories_edit_delete.png
index 6c43d2d0583..f86d6bebb63 100644
Binary files a/docs/user-guide/projects/project-settings/work-packages/openproject_user_guide_project_settings_work_packages_categories_edit_delete.png and b/docs/user-guide/projects/project-settings/work-packages/openproject_user_guide_project_settings_work_packages_categories_edit_delete.png differ
diff --git a/docs/user-guide/projects/project-settings/work-packages/openproject_user_guide_project_settings_work_packages_custom_fields.png b/docs/user-guide/projects/project-settings/work-packages/openproject_user_guide_project_settings_work_packages_custom_fields.png
index fa9def99a8a..40050676141 100644
Binary files a/docs/user-guide/projects/project-settings/work-packages/openproject_user_guide_project_settings_work_packages_custom_fields.png and b/docs/user-guide/projects/project-settings/work-packages/openproject_user_guide_project_settings_work_packages_custom_fields.png differ
diff --git a/docs/user-guide/projects/project-settings/work-packages/openproject_user_guide_project_settings_work_packages_internal_comments.png b/docs/user-guide/projects/project-settings/work-packages/openproject_user_guide_project_settings_work_packages_internal_comments.png
index c8cf893f1b1..134f72ac12e 100644
Binary files a/docs/user-guide/projects/project-settings/work-packages/openproject_user_guide_project_settings_work_packages_internal_comments.png and b/docs/user-guide/projects/project-settings/work-packages/openproject_user_guide_project_settings_work_packages_internal_comments.png differ
diff --git a/docs/user-guide/projects/project-settings/work-packages/openproject_user_guide_project_settings_work_packages_types.png b/docs/user-guide/projects/project-settings/work-packages/openproject_user_guide_project_settings_work_packages_types.png
index 8bd828478cd..bdc36e60860 100644
Binary files a/docs/user-guide/projects/project-settings/work-packages/openproject_user_guide_project_settings_work_packages_types.png and b/docs/user-guide/projects/project-settings/work-packages/openproject_user_guide_project_settings_work_packages_types.png differ
diff --git a/docs/user-guide/work-packages/edit-work-package/openproject_user_guide_wp_reminder_quick_options.png b/docs/user-guide/work-packages/edit-work-package/openproject_user_guide_wp_reminder_quick_options.png
index a14cfdfc04e..63edb98ecd5 100644
Binary files a/docs/user-guide/work-packages/edit-work-package/openproject_user_guide_wp_reminder_quick_options.png and b/docs/user-guide/work-packages/edit-work-package/openproject_user_guide_wp_reminder_quick_options.png differ
diff --git a/docs/user-guide/work-packages/work-package-table-configuration/openproject_user_guide_wp_table_project_phases_displayed.png b/docs/user-guide/work-packages/work-package-table-configuration/openproject_user_guide_wp_table_project_phases_displayed.png
index 14e9dc83790..f4502589a59 100644
Binary files a/docs/user-guide/work-packages/work-package-table-configuration/openproject_user_guide_wp_table_project_phases_displayed.png and b/docs/user-guide/work-packages/work-package-table-configuration/openproject_user_guide_wp_table_project_phases_displayed.png differ
diff --git a/docs/user-guide/work-packages/work-package-table-configuration/openproject_user_guide_wp_table_project_phases_displayed_grouped_by_phase.png b/docs/user-guide/work-packages/work-package-table-configuration/openproject_user_guide_wp_table_project_phases_displayed_grouped_by_phase.png
index fccec18e45f..8fff0b5efe6 100644
Binary files a/docs/user-guide/work-packages/work-package-table-configuration/openproject_user_guide_wp_table_project_phases_displayed_grouped_by_phase.png and b/docs/user-guide/work-packages/work-package-table-configuration/openproject_user_guide_wp_table_project_phases_displayed_grouped_by_phase.png differ
diff --git a/docs/user-guide/work-packages/work-package-table-configuration/openproject_user_guide_wp_table_project_phases_filter.png b/docs/user-guide/work-packages/work-package-table-configuration/openproject_user_guide_wp_table_project_phases_filter.png
index 3bcb69e1096..823c5528283 100644
Binary files a/docs/user-guide/work-packages/work-package-table-configuration/openproject_user_guide_wp_table_project_phases_filter.png and b/docs/user-guide/work-packages/work-package-table-configuration/openproject_user_guide_wp_table_project_phases_filter.png differ
diff --git a/lib/open_project/version.rb b/lib/open_project/version.rb
index a828b8f97ec..63ca67bde39 100644
--- a/lib/open_project/version.rb
+++ b/lib/open_project/version.rb
@@ -33,7 +33,7 @@ module OpenProject
module VERSION # :nodoc:
MAJOR = 16
MINOR = 1
- PATCH = 0
+ PATCH = 1
class << self
# Used by semver to define the special version (if any).
diff --git a/lib/rack/timeout/suppress_internal_error_report_on_timeout.rb b/lib/rack/timeout/suppress_internal_error_report_on_timeout.rb
index 558859117b1..33ad0d9a937 100644
--- a/lib/rack/timeout/suppress_internal_error_report_on_timeout.rb
+++ b/lib/rack/timeout/suppress_internal_error_report_on_timeout.rb
@@ -2,7 +2,10 @@ module Rack
class Timeout
module SuppressInternalErrorReportOnTimeout
def op_handle_error(message_or_exception, context = {})
- return if respond_to?(:request) && request.env[Rack::Timeout::ENV_INFO_KEY].try(:state) == :timed_out
+ if respond_to?(:request) && request.env[Rack::Timeout::ENV_INFO_KEY].try(:state) == :timed_out
+ Rails.logger.error "Rack::Timeout: Receiving timeout exception: #{message_or_exception}"
+ return
+ end
super
end
diff --git a/lib_static/open_project/authentication/strategies/warden/doorkeeper_oauth.rb b/lib_static/open_project/authentication/strategies/warden/doorkeeper_oauth.rb
index 60adb896618..4393e6a5e07 100644
--- a/lib_static/open_project/authentication/strategies/warden/doorkeeper_oauth.rb
+++ b/lib_static/open_project/authentication/strategies/warden/doorkeeper_oauth.rb
@@ -42,7 +42,7 @@ module OpenProject
end
def authenticate_user(id)
- user = id && User.find_by(id:)
+ user = id && User.active.find_by(id:)
if user
success!(user)
else
diff --git a/modules/bim/app/controllers/bim/bcf/issues_controller.rb b/modules/bim/app/controllers/bim/bcf/issues_controller.rb
index 11949e96083..178b2a7cb77 100644
--- a/modules/bim/app/controllers/bim/bcf/issues_controller.rb
+++ b/modules/bim/app/controllers/bim/bcf/issues_controller.rb
@@ -215,7 +215,7 @@ module Bim
def create_attachment
filename = params[:bcf_file].original_filename
call = Attachments::CreateService
- .bypass_whitelist(user: current_user, whitelist: %w[application/zip])
+ .bypass_allowlist(user: current_user, allowlist: %w[application/zip])
.call(file: params[:bcf_file],
filename:,
description: filename)
diff --git a/modules/bim/app/controllers/bim/ifc_models/ifc_models_controller.rb b/modules/bim/app/controllers/bim/ifc_models/ifc_models_controller.rb
index 32d47c39c97..af418af6e6b 100644
--- a/modules/bim/app/controllers/bim/ifc_models/ifc_models_controller.rb
+++ b/modules/bim/app/controllers/bim/ifc_models/ifc_models_controller.rb
@@ -49,6 +49,10 @@ module Bim
.includes(:project, :uploader)
end
+ def show
+ frontend_redirect params[:id].to_i
+ end
+
def new
@ifc_model = @project.ifc_models.build
prepare_form(@ifc_model)
@@ -58,10 +62,6 @@ module Bim
prepare_form(@ifc_model)
end
- def show
- frontend_redirect params[:id].to_i
- end
-
def defaults
frontend_redirect @ifc_models.defaults.pluck(:id).uniq
end
@@ -116,7 +116,7 @@ module Bim
if service_result.success?
::Attachments::FinishDirectUploadJob.perform_later attachment.id,
- whitelist: false
+ allowlist: false
flash[:notice] = if new_model
t("ifc_models.flash_messages.upload_successful")
@@ -183,7 +183,7 @@ module Bim
return unless OpenProject::Configuration.direct_uploads?
call = ::Attachments::PrepareUploadService
- .bypass_whitelist(user: current_user)
+ .bypass_allowlist(user: current_user)
.call(filename: "model.ifc", filesize: 0)
call.on_failure { flash[:error] = call.message }
diff --git a/modules/bim/app/models/bim/bcf/viewpoint.rb b/modules/bim/app/models/bim/bcf/viewpoint.rb
index 668ed857a09..921a6ae134c 100644
--- a/modules/bim/app/models/bim/bcf/viewpoint.rb
+++ b/modules/bim/app/models/bim/bcf/viewpoint.rb
@@ -73,7 +73,7 @@ module Bim::Bcf
def build_snapshot(file, user: User.current)
::Attachments::BuildService
- .bypass_whitelist(user:)
+ .bypass_allowlist(user:)
.call(file:, container: self, filename: file.original_filename, description: "snapshot")
.result
end
diff --git a/modules/bim/app/models/bim/ifc_models/ifc_model.rb b/modules/bim/app/models/bim/ifc_models/ifc_model.rb
index e878716a430..307d0b2b139 100644
--- a/modules/bim/app/models/bim/ifc_models/ifc_model.rb
+++ b/modules/bim/app/models/bim/ifc_models/ifc_model.rb
@@ -41,7 +41,7 @@ module Bim
delete_attachment name
filename = file.respond_to?(:original_filename) ? file.original_filename : File.basename(file.path)
call = ::Attachments::CreateService
- .bypass_whitelist(user: User.current)
+ .bypass_allowlist(user: User.current)
.call(file:, container: self, filename:, description: name)
call.on_failure { Rails.logger.error "Failed to add #{name} attachment: #{call.message}" }
diff --git a/modules/bim/app/services/bim/ifc_models/set_attributes_service.rb b/modules/bim/app/services/bim/ifc_models/set_attributes_service.rb
index 8e9cc0746d2..eb7c1e79f91 100644
--- a/modules/bim/app/services/bim/ifc_models/set_attributes_service.rb
+++ b/modules/bim/app/services/bim/ifc_models/set_attributes_service.rb
@@ -78,7 +78,7 @@ module Bim
def build_ifc_attachment(ifc_attachment)
::Attachments::BuildService
- .bypass_whitelist(user:)
+ .bypass_allowlist(user:)
.call(file: ifc_attachment, container: model, filename: ifc_attachment.original_filename, description: "ifc")
.on_failure do |build_attachment_result|
build_attachment_result.errors.each do |error|
diff --git a/modules/bim/config/locales/crowdin/ro.seeders.yml b/modules/bim/config/locales/crowdin/ro.seeders.yml
index bcdfd95fcd3..5eed5ecf970 100644
--- a/modules/bim/config/locales/crowdin/ro.seeders.yml
+++ b/modules/bim/config/locales/crowdin/ro.seeders.yml
@@ -289,22 +289,22 @@ ro:
item_0:
subject: Finishing design
description: |-
- ## Goal
+ ## Obiectivul
- * Design is done
- * All parties are happy with the results of the design planning phase
+ * Designul a fost realizat
+ * Toate părțile sunt mulțumite de rezultatele fazei de planificare de design
- ## Description
+ ## Descrierea
- * The design of the project will be finished
- * All parties agree on the design
- * The owner is happy with the results
- * ...
+ * Design-ul proiectului va fi finalizat
+ * Toate părțile sunt de acord cu design-ul
+ * Proprietarul este mulțumit de rezultatele
+ * ...
item_1:
subject: Design freeze
description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note.
item_5:
- subject: Construction phase
+ subject: Fază construcție
children:
item_0:
subject: Start constructing
@@ -474,19 +474,19 @@ ro:
item_0:
subject: Gathering the project specific data and information for the BIM model
description: |-
- ## Goal
+ ## Obiectiv
- * Identify the information strategy for the customer (e.g. by using plain language questions)
- * If provided, analyze the customer information requirements for the BIM model
- * Define an information delivery strategy according to the customers needs
+ * Identificarea strategiei de informare a clientului (de exemplu, prin utilizarea întrebărilor în limbaj simplu)
+ * Dacă sunt furnizate, analizarea cerințelor de informare ale clientului pentru modelul BIM
+ * Definirea unei strategii de furnizare a informațiilor în funcție de nevoile clientului
- ## Description
+ ## Descriere
- * Analyzing the customers needs and goals for using the BIM methodology
- * Results of this tasks should be:
- * The requirements for the project
- * A strategy for the delivery phase
- * ...
+ * Analizarea nevoilor și a obiectivelor clienților pentru utilizarea metodologiei BIM
+ * Rezultatele acestei sarcini ar trebui să fie:
+ * Cerințele pentru proiect
+ * O strategie pentru faza de livrare
+ * ...
item_1:
subject: Creating the BIM execution plan
description: |-
@@ -504,7 +504,7 @@ ro:
subject: Completion of the BIM execution plan
description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note.
item_2:
- subject: End of preparation phase
+ subject: Sfârșit fază pregătire
description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note.
item_3:
subject: Creating initial BIM model
@@ -617,7 +617,7 @@ ro:
subject: Finishing modelling & coordinating, n-th cycle
description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note.
item_11:
- subject: Use model for construction phase
+ subject: Utilizează model pentru faza construcție
children:
item_0:
subject: Handover model for construction crew
@@ -649,7 +649,7 @@ ro:
item_2:
subject: Finish construction
item_12:
- subject: Issue management, construction phase
+ subject: Gestionare probleme, fază construcție
item_13:
subject: Handover for Facility Management
description: |-
diff --git a/modules/bim/spec/contracts/ifc_models/shared_contract_examples.rb b/modules/bim/spec/contracts/ifc_models/shared_contract_examples.rb
index 28db250bbab..50ab9b411cb 100644
--- a/modules/bim/spec/contracts/ifc_models/shared_contract_examples.rb
+++ b/modules/bim/spec/contracts/ifc_models/shared_contract_examples.rb
@@ -102,7 +102,7 @@ RSpec.shared_examples_for "ifc model contract" do
let(:ifc_file) { FileHelpers.mock_uploaded_file name: "model.ifc", content_type: "application/binary", binary: true }
let(:ifc_attachment) do
Attachments::BuildService
- .bypass_whitelist(user: current_user)
+ .bypass_allowlist(user: current_user)
.call(file: ifc_file, filename: "model.ifc")
.result
end
@@ -118,7 +118,7 @@ RSpec.shared_examples_for "ifc model contract" do
end
let(:ifc_attachment) do
Attachments::BuildService
- .bypass_whitelist(user: current_user)
+ .bypass_allowlist(user: current_user)
.call(file: ifc_file, filename: "model.ifc")
.result
end
diff --git a/modules/bim/spec/workers/work_packages/exports/export_job_spec.rb b/modules/bim/spec/workers/work_packages/exports/export_job_spec.rb
index 491f18d6798..0909582fb4b 100644
--- a/modules/bim/spec/workers/work_packages/exports/export_job_spec.rb
+++ b/modules/bim/spec/workers/work_packages/exports/export_job_spec.rb
@@ -66,15 +66,14 @@ RSpec.describe WorkPackages::ExportJob do
service = double("attachments create service")
- expect(Attachments::CreateService)
- .to receive(:bypass_whitelist)
- .with(user:)
+ allow(Attachments::CreateService)
+ .to receive(:bypass_allowlist)
.and_return(service)
- expect(Exports::CleanupOutdatedJob)
+ allow(Exports::CleanupOutdatedJob)
.to receive(:perform_after_grace)
- expect(service)
+ allow(service)
.to(receive(:call))
.and_return(ServiceResult.success(result: attachment))
@@ -85,6 +84,16 @@ RSpec.describe WorkPackages::ExportJob do
expect(subject.job_status).to be_present
expect(subject.job_status[:status]).to eq "success"
expect(subject.job_status[:payload]["download"]).to eq "/api/v3/attachments/1234/content"
+
+ expect(Attachments::CreateService)
+ .to have_received(:bypass_allowlist)
+ .with(user:)
+
+ expect(Exports::CleanupOutdatedJob)
+ .to have_received(:perform_after_grace)
+
+ expect(service)
+ .to have_received(:call)
end
end
end
diff --git a/modules/costs/config/locales/crowdin/es.yml b/modules/costs/config/locales/crowdin/es.yml
index f31c8ee168e..3f23cc77b87 100644
--- a/modules/costs/config/locales/crowdin/es.yml
+++ b/modules/costs/config/locales/crowdin/es.yml
@@ -77,7 +77,7 @@ es:
time_entry:
invalid_time: "debe estar entre las 00:00 y las 23:59."
cannot_log_for_this_work_package: "No se puede registrar el tiempo para este paquete de trabajo."
- duplicate_ongoing: "An ongoing time entry already exists for this user."
+ duplicate_ongoing: ""
work_package:
is_not_a_valid_target_for_cost_entries: "Paquete de trabajo #%{id} no es un objetivo válido para reasignar las entradas de costo."
nullify_is_not_valid_for_cost_entries: "Las entradas de costo no pueden ser asignadas a un proyecto."
diff --git a/modules/costs/config/locales/crowdin/ro.yml b/modules/costs/config/locales/crowdin/ro.yml
index 038ef3db279..980326e03b2 100644
--- a/modules/costs/config/locales/crowdin/ro.yml
+++ b/modules/costs/config/locales/crowdin/ro.yml
@@ -158,7 +158,7 @@ ro:
label_cost: "Cost"
label_costs: "Costuri"
label_mandatory_fields: "Mandatory fields"
- label_my_time_tracking: "My time tracking"
+ label_my_time_tracking: "Urmărirea timpului meu"
label_no_time: "No time tracked"
label_next_day: "Next day"
label_next_week: "Săptămâna viitoare"
diff --git a/modules/ldap_groups/config/locales/crowdin/ja.yml b/modules/ldap_groups/config/locales/crowdin/ja.yml
index 1b8037d6208..a3e291571e8 100644
--- a/modules/ldap_groups/config/locales/crowdin/ja.yml
+++ b/modules/ldap_groups/config/locales/crowdin/ja.yml
@@ -11,12 +11,12 @@ ja:
attributes:
ldap_groups/synchronized_group:
dn: 'DN'
- auth_source: 'Auth source'
+ auth_source: '認証ソース'
ldap_auth_source: 'LDAP 接続'
sync_users: 'ユーザーを同期'
ldap_groups/synchronized_filter:
filter_string: 'LDAPフィルタ'
- auth_source: 'Auth source'
+ auth_source: '認証ソース'
ldap_auth_source: 'LDAP 接続'
group_name_attribute: "グループ名属性"
sync_users: 'ユーザーを同期'
diff --git a/modules/meeting/config/locales/crowdin/ja.yml b/modules/meeting/config/locales/crowdin/ja.yml
index edcebafcf44..aef3f4d1067 100644
--- a/modules/meeting/config/locales/crowdin/ja.yml
+++ b/modules/meeting/config/locales/crowdin/ja.yml
@@ -55,7 +55,7 @@ ja:
start_date: "開始:"
start_time: "開始時間"
start_time_hour: "開始時間"
- end_after: "Meeting series ends"
+ end_after: "一連の会議が終了"
end_date: "完了日"
iterations: "回数"
errors:
@@ -130,7 +130,7 @@ ja:
label_recurring_meeting_restore: "この予定を復元"
label_recurring_meeting_schedule: "スケジュール"
label_recurring_meeting_next_occurrence: "次の会議"
- label_recurring_meeting_no_end_date: "There are more scheduled meetings (%{schedule})."
+ label_recurring_meeting_no_end_date: "他にも予定されているミーティングがあります(%{schedule})。"
label_recurring_meeting_more:
other: "There are %{count} more scheduled meetings (%{schedule})."
label_recurring_meeting_more_past:
@@ -139,7 +139,7 @@ ja:
label_recurring_meeting_series_create: "一連の会議を作成"
label_recurring_meeting_series_edit: "一連の会議を編集"
label_recurring_meeting_series_delete: "一連の会議を削除"
- label_recurring_meeting_series_end: "End meeting series"
+ label_recurring_meeting_series_end: "一連の会議を終了"
label_recurring_meeting_series_end_now: "End series now"
label_meeting_more:
other: "There are %{count} more meetings."
@@ -233,28 +233,28 @@ ja:
blankslate:
title: "表示する会議がありません"
desc: "条件を満たす会議はありません。"
- label_export_pdf: "Export PDF"
+ label_export_pdf: "PDF形式でエクスポート"
export:
your_meeting_export: "Meeting is being exported"
export_pdf_dialog:
- title: Export PDF
+ title: PDF形式でエクスポート
description: Generate a printable PDF file of this meeting at the current state.
include_participants:
- label: Include list of participants
+ label: 参加者を含める
caption: A list of participants will be preset above the meeting agenda.
include_attachments:
- label: Include list of attachments
+ label: 添付ファイルを含める
caption: A list containing the filenames of attachments will be appended at the end.
include_backlog:
- label: Include backlog
+ label: バックログを含める
caption: Backlog elements are not normally considered part of a meeting occurrence but you can choose to include them.
include_outcomes:
- label: Include agenda outcomes
+ label: 議題の成果を含める
caption: If your agenda outcomes contain confidential information, you can choose to not include them in the export.
footer_text:
- label: Footer text
+ label: フッターのテキスト
caption: This text will appear on every page on the right of the footer.
- submit_button: Download
+ submit_button: ダウンロード
meeting_section:
untitled_title: "タイトルなしセクション"
delete_confirmation: "セクションを削除すると、そのすべての議題項目も削除されます。これを実行してもよろしいですか?"
@@ -264,13 +264,13 @@ ja:
ended_blankslate:
title: "一連の会議が終了しました"
message: "一連の会議は終了しました。今後の会議はありません "
- action: "You can still view past occurrences or edit the meeting series to extend it."
+ action: "過去の会議を閲覧したり、会議シリーズを編集して拡張することもできます。"
occurrence:
- infoline: "This meeting is part of a recurring meeting series."
+ infoline: "このミーティングは、定期的に開催されるミーティング・シリーズの一環である。"
error_no_next: "次回の開催はありません。"
first_already_exists: "The first occurrence of this meeting series is already instantiated."
first_created: >
- The first meeting has been successfuly created from template. All future meetings will be created automatically at the time of the previous occurrence.
+ 最初の会議がテンプレートから正常に作成されました。今後作成されるミーティングはすべて、前回の開催時に自動的に作成されます。
template:
button_finalize: "Open first meeting"
blank_title: "一連の会議のテンプレートがありません"
@@ -284,32 +284,32 @@ ja:
You are currently editing a template of a meeting series: %{link}. Every new occurrence of a meeting in the series will use this template. Changes will not affect past or already created meetings.
frequency:
x_daily:
- other: "Every %{count} days"
+ other: "%{count} 日ごと"
x_weekly:
- other: "Every %{count} weeks"
- every_weekday: "Every %{day_of_the_week}"
+ other: "%{count} 週ごと"
+ every_weekday: "すべての %{day_of_the_week}"
working_days: "稼働日"
end_after:
- never: "never"
+ never: "期限なし"
specific_date: "特定の日付"
iterations: "after a number of occurrences"
- starts: "Starts"
+ starts: "開始"
in_words:
- daily_interval: "Every %{interval} days"
+ daily_interval: "%{interval} 日ごと"
working_days: "稼働日"
- weekly: "Every week on %{weekday}"
- weekly_interval: "Every %{interval} weeks on %{weekday}"
- frequency: "%{base} at %{time}"
- full: "%{base} at %{time}, ends on %{end_date}"
- full_past: "%{base} at %{time}, ended on %{end_date}"
- never_ending: "%{base} at %{time}"
+ weekly: "毎週 %{weekday}"
+ weekly_interval: "%{interval} 週間ごとの %{weekday}"
+ frequency: "%{base} で %{time}"
+ full: "%{base} は %{time}で終了します。 %{end_date}"
+ full_past: "%{base} が %{time}で終了しました。 %{end_date}"
+ never_ending: "%{base} で %{time}"
open:
- title: "Agenda opened"
+ title: "議題が開かれました"
subtitle: >
Open meetings have agendas that can be edited and show up in individual users’ ‘My meetings’ section. Changes to the meeting series template do not affect already-open meeting occurrences.
blankslate:
- title: "No open meetings at the moment"
- desc: "You can manually open a planned meeting by clicking on the 'Open' button below"
+ title: "現在公開されているミーティングはありません"
+ desc: "下の「開く」ボタンをクリックして手動でミーティングを開くことができます"
planned:
title: "計画"
subtitle: >
@@ -320,12 +320,12 @@ ja:
一連の会議に追加の会議は予定されていません。追加の会議をスケジュールしたり、一連の会議に追加する場合には、テンプレートに移動して会議の詳細から終了日、頻度、間隔を変更します。
delete_dialog:
title: "一連の会議を削除"
- heading: "Permanently delete this meeting series?"
+ heading: "このミーティングシリーズを完全に削除しますか?"
confirmation_message_html:
zero: >
The meeting series %{title} does not have any meeting occurrences. The series will be deleted for everyone. Please proceed with caution.
one: >
- Deleting %{title} will also delete one occurrence in this series. This action is not reversible. Please proceed with caution.
+ %{title} を削除すると、このシリーズで発生したものも削除されます。この操作は元に戻せません。注意してください。
other: >
Deleting %{title} will delete all %{count} occurrences in this series. This action is not reversible. Please proceed with caution.
scheduled_delete_dialog:
@@ -333,9 +333,9 @@ ja:
heading: "このミーティングをキャンセルしますか?"
confirmation_message_html: >
Any meeting information not in the template will be lost. Do you want to continue?
- confirm_button: "Cancel occurrence"
+ confirm_button: "この予定をキャンセル"
end_series_dialog:
- title: "End meeting series"
+ title: "一連の会議を終了"
notice_successful_notification: "通知が正常に送信されました"
notice_timezone_missing: タイムゾーンが設定されていない場合、%{zone} が使用されます。タイムゾーンを選択するには、ここをクリックしてください。
notice_meeting_updated: "このページは他の誰かによって更新されました。変更を表示するには再読み込みしてください。"
diff --git a/modules/meeting/config/locales/crowdin/zh-TW.yml b/modules/meeting/config/locales/crowdin/zh-TW.yml
index b693dfe202f..78d189a09f1 100644
--- a/modules/meeting/config/locales/crowdin/zh-TW.yml
+++ b/modules/meeting/config/locales/crowdin/zh-TW.yml
@@ -361,7 +361,7 @@ zh-TW:
text_meeting_empty_description1: "可以點選下幫「增加」來新增項目。 每個項目可以像標題一樣簡單,但您也可以加上其他詳細信息,例如開會時間與備註。"
text_meeting_empty_description2: '你也可以引用到現有工作套件。當您這樣做時,相關的紀錄將自動呈現於於工作套件的「會議」標籤。'
label_meeting_empty_action: "新增議程項目"
- label_meeting_actions: "會議行動"
+ label_meeting_actions: "會議任務"
label_meeting_edit_title: "編輯會議標題"
label_meeting_delete: "刪除會議"
label_meeting_created_by: "建立者"
diff --git a/modules/meeting/db/migrate/20250624155721_rename_create_minutes_to_manage_outcomes.rb b/modules/meeting/db/migrate/20250624155721_rename_create_minutes_to_manage_outcomes.rb
new file mode 100644
index 00000000000..6b7f8c011a4
--- /dev/null
+++ b/modules/meeting/db/migrate/20250624155721_rename_create_minutes_to_manage_outcomes.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class RenameCreateMinutesToManageOutcomes < ActiveRecord::Migration[8.0]
+ def up
+ execute <<-SQL.squish
+ UPDATE role_permissions
+ SET permission = 'manage_outcomes'
+ WHERE permission = 'create_meeting_minutes'
+ SQL
+ end
+
+ def down
+ execute <<-SQL.squish
+ UPDATE role_permissions
+ SET permission = 'create_meeting_minutes'
+ WHERE permission = 'manage_outcomes'
+ SQL
+ end
+end
diff --git a/modules/openid_connect/spec/features/oidc_end_session_redirect_spec.rb b/modules/openid_connect/spec/features/oidc_end_session_redirect_spec.rb
new file mode 100644
index 00000000000..82595fd6f14
--- /dev/null
+++ b/modules/openid_connect/spec/features/oidc_end_session_redirect_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+#-- 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.
+#++
+
+require "spec_helper"
+
+RSpec.describe "OpenID Connect end_session_redirect",
+ :js,
+ with_ee: %i[sso_auth_providers] do
+ let!(:provider) do
+ create(:oidc_provider,
+ slug: "keycloak",
+ end_session_endpoint: "https://example.com")
+ end
+
+ let(:password) { "password!123" }
+ let(:user) { create(:user, authentication_provider: provider, password:, password_confirmation: password) }
+
+ it "redirects to the OIDC logout endpoint without turbo (Regression #65076)" do
+ login_with(user.login, password)
+
+ visit home_path
+
+ page.find(".op-top-menu-user").click
+ click_link_or_button "Sign out"
+
+ expect(page).to have_current_path("https://example.com")
+ end
+end
diff --git a/modules/storages/config/locales/crowdin/de.yml b/modules/storages/config/locales/crowdin/de.yml
index a87223607fd..a9a34f642d0 100644
--- a/modules/storages/config/locales/crowdin/de.yml
+++ b/modules/storages/config/locales/crowdin/de.yml
@@ -298,15 +298,15 @@ de:
od_oauth_request_unauthorized: Der aktuelle Benutzer ist nicht berechtigt, auf den Remote-Datei-Speicher zuzugreifen. Bitte überprüfen Sie die Server-Protokolle für weitere Informationen.
od_oauth_token_missing: OpenProject kann die Verbindung zu OneDrive/SharePoint auf Benutzerebene nicht testen, da das Microsoft-Konto noch nicht verknüpft wurde.
od_tenant_id_wrong: Die konfigurierte Verzeichnis-(Mandanten-)ID ist ungültig. Bitte überprüfen Sie die Konfiguration.
- od_test_folder_exists: The folder %{folder_name} needed for testing already exists. Please delete it and try again.
+ od_test_folder_exists: Der für den Test benötigte Ordner %{folder_name} existiert bereits. Bitte löschen Sie ihn und versuchen Sie es erneut.
od_unexpected_content: Unerwarteter Inhalt auf dem Laufwerk gefunden.
offline_access_scope_missing: Es wird empfohlen, den OpenID Connect-Anbieter so zu konfigurieren, dass er den Scope offline_access anfordert. Die Integration kann trotzdem funktionieren, aber stellen Sie sicher, dass die Aktualisierungstoken nicht ablaufen.
oidc_cant_refresh_token: Beim Versuch, Ihren Zugriff auf den Speicher zu überprüfen, ist ein Fehler aufgetreten. Bitte überprüfen Sie die Server-Protokolle auf weitere Informationen.
oidc_non_oidc_user: Der aktuelle Benutzer wurde zwar bereitgestellt, aber nicht von einem OpenID Connect (OIDC) Identity Provider. Bitte wiederholen Sie die Prüfung mit einem OIDC-bereitgestellten Benutzer.
oidc_non_provisioned_user: Der aktuelle Benutzer wird nicht von einem OpenID Connect Identity Provider bereitgestellt. Bitte führen Sie die Prüfung mit einem bereitgestellten Benutzer erneut durch.
- oidc_token_acquisition_failed: Your OpenID Connect setup doesn't provide the necessary audience, nor does it provide token exchange capabilities. Please check out our documentation for more information.
- oidc_token_exchange_failed: There seems to be a problem with the Token Exchange setup on your OpenID Connect Provider. Please check its configuration and try again.
- oidc_token_refresh_failed: There was an error while trying to check your access to the storage. Please check the server logs for further information.
+ oidc_token_acquisition_failed: Ihre OpenID-Connect-Konfiguration stellt weder die erforderliche Audience bereit, noch unterstützt sie die Token-Austausch-Funktion. Bitte lesen Sie unsere Dokumentation für weitere Informationen.
+ oidc_token_exchange_failed: Es scheint ein Problem mit der Token-Exchange-Konfiguration Ihres OpenID-Connect-Anbieters zu geben. Bitte überprüfen Sie die Einstellungen und versuchen Sie es erneut.
+ oidc_token_refresh_failed: Beim Überprüfen Ihres Zugriffs auf den Speicher ist ein Fehler aufgetreten. Bitte sehen Sie die Serverprotokolle für weitere Informationen ein.
unknown_error: Die Verbindung konnte nicht validiert werden. Es ist ein unbekannter Fehler aufgetreten. Bitte prüfen Sie die Serverprotokolle für weitere Informationen.
label_error: Fehler
label_failed: Fehlgeschlagen
diff --git a/modules/two_factor_authentication/config/locales/crowdin/ja.yml b/modules/two_factor_authentication/config/locales/crowdin/ja.yml
index b9ac3c3e027..61cc6418425 100644
--- a/modules/two_factor_authentication/config/locales/crowdin/ja.yml
+++ b/modules/two_factor_authentication/config/locales/crowdin/ja.yml
@@ -9,7 +9,7 @@ ja:
two_factor_authentication/device:
identifier: "識別子"
default: "デフォルトとして使用"
- channel: "Channel"
+ channel: "チャンネル"
two_factor_authentication/device/sms:
phone_number: "電話番号"
two_factor_authentication/device/webauthn:
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 00000000000..75370d47c08
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1133 @@
+{
+ "name": "openproject",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "openproject",
+ "version": "0.1.0",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@xeokit/xeokit-gltf-to-xkt": "^1.3.1"
+ },
+ "devDependencies": {
+ "@redocly/openapi-cli": "^1.0.0-beta.80"
+ },
+ "engines": {
+ "node": "^22.15.0",
+ "npm": "^10.1.0"
+ }
+ },
+ "node_modules/@redocly/ajv": {
+ "version": "8.11.0",
+ "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.0.tgz",
+ "integrity": "sha512-9GWx27t7xWhDIR02PA18nzBdLcKQRgc46xNQvjFkrYk4UOmvKhJ/dawwiX0cCOeetN5LcaaiqQbVOWYK62SGHw==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/@redocly/openapi-cli": {
+ "version": "1.0.0-beta.95",
+ "resolved": "https://registry.npmjs.org/@redocly/openapi-cli/-/openapi-cli-1.0.0-beta.95.tgz",
+ "integrity": "sha512-pl/OAeKh/psk6kF9SZjRieJK15T6T5GYcKVeBHvT7vtuhIBRBkrLC3bf3BhiMQx49BdSTB7Tk4/0LFPy0zr1MA==",
+ "deprecated": "This project has been renamed to @redocly/cli. Install using new package name instead.",
+ "dev": true,
+ "dependencies": {
+ "@redocly/openapi-core": "1.0.0-beta.95",
+ "@types/node": "^14.11.8",
+ "assert-node-version": "^1.0.3",
+ "chokidar": "^3.5.1",
+ "colorette": "^1.2.0",
+ "glob": "^7.1.6",
+ "glob-promise": "^3.4.0",
+ "handlebars": "^4.7.6",
+ "portfinder": "^1.0.26",
+ "simple-websocket": "^9.0.0",
+ "yargs": "17.0.1"
+ },
+ "bin": {
+ "openapi": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/@redocly/openapi-core": {
+ "version": "1.0.0-beta.95",
+ "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.0.0-beta.95.tgz",
+ "integrity": "sha512-7Nnc4Obp/1lbrjNjD33oOnZCuoJa8awhBCEyyayPWGQFp1SkhjpZJnfnKkFuYbQzMjTIAvEeSp9DOQK/E0fgEA==",
+ "dev": true,
+ "dependencies": {
+ "@redocly/ajv": "^8.6.4",
+ "@types/node": "^14.11.8",
+ "colorette": "^1.2.0",
+ "js-levenshtein": "^1.1.6",
+ "js-yaml": "^4.1.0",
+ "lodash.isequal": "^4.5.0",
+ "minimatch": "^3.0.4",
+ "node-fetch": "^2.6.1",
+ "pluralize": "^8.0.0",
+ "yaml-ast-parser": "0.0.43"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/@types/glob": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz",
+ "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==",
+ "dev": true,
+ "dependencies": {
+ "@types/minimatch": "^5.1.2",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/minimatch": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
+ "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==",
+ "dev": true
+ },
+ "node_modules/@types/node": {
+ "version": "14.18.54",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.54.tgz",
+ "integrity": "sha512-uq7O52wvo2Lggsx1x21tKZgqkJpvwCseBBPtX/nKQfpVlEsLOb11zZ1CRsWUKvJF0+lzuA9jwvA7Pr2Wt7i3xw==",
+ "dev": true
+ },
+ "node_modules/@xeokit/xeokit-gltf-to-xkt": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@xeokit/xeokit-gltf-to-xkt/-/xeokit-gltf-to-xkt-1.3.1.tgz",
+ "integrity": "sha512-6Cpkebw3hs5S7fY4iGU6evt4yL3vcQk34gKqOyH26Ur2LvGiqon6opu/Dgpkqp/F9/0ZPXm0Yv/AHYdezt+C9g==",
+ "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
+ "license": "AGPL3",
+ "dependencies": {
+ "@xeokit/xeokit-xkt-utils": "1.3.0",
+ "commander": "^2.20.0"
+ },
+ "bin": {
+ "gltf2xkt": "gltf2xkt.js"
+ }
+ },
+ "node_modules/@xeokit/xeokit-xkt-utils": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@xeokit/xeokit-xkt-utils/-/xeokit-xkt-utils-1.3.0.tgz",
+ "integrity": "sha512-FL40Pyb7k66R+4P3wODOa/RxbuYcLWK/VQIfNldEF2FwxhATXcF0yFv/ZR2Ac+dliR/LYL/bIvropZiD7o5DZA==",
+ "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
+ "license": "AGPL3"
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/assert-node-version": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/assert-node-version/-/assert-node-version-1.0.3.tgz",
+ "integrity": "sha512-XcKBGJ1t0RrCcus9dQX57FER4PTEz/+Tee2jj+EdFIGyw5j8hwDNXZzgRYLQ916twVjSuA47adrZsSxLbpEX9A==",
+ "dev": true,
+ "dependencies": {
+ "expected-node-version": "^1.0.0",
+ "semver": "^5.0.3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/async": {
+ "version": "2.6.4",
+ "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
+ "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
+ "dev": true,
+ "dependencies": {
+ "lodash": "^4.17.14"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ],
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+ "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^7.0.0"
+ }
+ },
+ "node_modules/cliui/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/colorette": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
+ "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==",
+ "dev": true
+ },
+ "node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "license": "MIT"
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
+ },
+ "node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/expected-node-version": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/expected-node-version/-/expected-node-version-1.0.2.tgz",
+ "integrity": "sha512-OSaCdgF02srujDqJz1JWGpqk8Rq3uNYHLmtpBHJrZN3BvuMvzijJMqRVxZN1qLJtKVwjXhmOp+lfsRUqx8n54w==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/glob-promise": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-3.4.0.tgz",
+ "integrity": "sha512-q08RJ6O+eJn+dVanerAndJwIcumgbDdYiUT7zFQl3Wm1xD6fBKtah7H8ZJChj4wP+8C+QfeVy8xautR7rdmKEw==",
+ "dev": true,
+ "dependencies": {
+ "@types/glob": "*"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "glob": "*"
+ }
+ },
+ "node_modules/handlebars": {
+ "version": "4.7.7",
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
+ "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==",
+ "dev": true,
+ "dependencies": {
+ "minimist": "^1.2.5",
+ "neo-async": "^2.6.0",
+ "source-map": "^0.6.1",
+ "wordwrap": "^1.0.0"
+ },
+ "bin": {
+ "handlebars": "bin/handlebars"
+ },
+ "engines": {
+ "node": ">=0.4.7"
+ },
+ "optionalDependencies": {
+ "uglify-js": "^3.1.4"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/js-levenshtein": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
+ "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true
+ },
+ "node_modules/lodash.isequal": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+ "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
+ "dev": true
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+ "dev": true,
+ "dependencies": {
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true
+ },
+ "node_modules/neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+ "dev": true
+ },
+ "node_modules/node-fetch": {
+ "version": "2.6.12",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz",
+ "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==",
+ "dev": true,
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pluralize": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
+ "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/portfinder": {
+ "version": "1.0.32",
+ "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz",
+ "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==",
+ "dev": true,
+ "dependencies": {
+ "async": "^2.6.4",
+ "debug": "^3.2.7",
+ "mkdirp": "^0.5.6"
+ },
+ "engines": {
+ "node": ">= 0.12.0"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
+ "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dev": true,
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/semver": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/simple-websocket": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/simple-websocket/-/simple-websocket-9.1.0.tgz",
+ "integrity": "sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "debug": "^4.3.1",
+ "queue-microtask": "^1.2.2",
+ "randombytes": "^2.1.0",
+ "readable-stream": "^3.6.0",
+ "ws": "^7.4.2"
+ }
+ },
+ "node_modules/simple-websocket/node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/simple-websocket/node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "dev": true
+ },
+ "node_modules/uglify-js": {
+ "version": "3.17.4",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz",
+ "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==",
+ "dev": true,
+ "optional": true,
+ "bin": {
+ "uglifyjs": "bin/uglifyjs"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true
+ },
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "dev": true
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dev": true,
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/wordwrap": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
+ "dev": true
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true
+ },
+ "node_modules/ws": {
+ "version": "7.5.9",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
+ "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.3.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": "^5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yaml-ast-parser": {
+ "version": "0.0.43",
+ "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz",
+ "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==",
+ "dev": true
+ },
+ "node_modules/yargs": {
+ "version": "17.0.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.0.1.tgz",
+ "integrity": "sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "^7.0.2",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^20.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "20.2.9",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
+ "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
index 3161272aea9..a89014d5f4b 100644
--- a/package.json
+++ b/package.json
@@ -15,5 +15,8 @@
},
"devDependencies": {
"@redocly/openapi-cli": "^1.0.0-beta.80"
+ },
+ "dependencies": {
+ "@xeokit/xeokit-gltf-to-xkt": "^1.3.1"
}
}
diff --git a/packaging/addons/openproject-edition/bin/preinstall b/packaging/addons/openproject-edition/bin/preinstall
index d74638850e6..274b3743a8d 100755
--- a/packaging/addons/openproject-edition/bin/preinstall
+++ b/packaging/addons/openproject-edition/bin/preinstall
@@ -57,30 +57,25 @@ if [ "$edition" = "bim" ]; then
cd $tmpdir
- if [ "$(${CLI} run gltf2xkt --version 2>&1)" != "1.3.1" ] ; then
- echo "Fetching and installing xeokit-gltf-to-xkt..."
- ${CLI} run npm install @xeokit/xeokit-gltf-to-xkt@1.3.1
- fi
-
if ! ${CLI} run which COLLADA2GLTF &>/dev/null ; then
- echo "Fetching and installing COLLADA2GLTF..."
- wget --quiet https://github.com/KhronosGroup/COLLADA2GLTF/releases/download/v2.1.5/COLLADA2GLTF-v2.1.5-linux.zip
+ echo "Installing COLLADA2GLTF..."
+ cp "$APP_HOME/vendor/bim/COLLADA2GLTF-v2.1.5-linux.zip" .
unzip -qq COLLADA2GLTF-v2.1.5-linux.zip
mv COLLADA2GLTF-bin "$APP_HOME/bin/COLLADA2GLTF"
fi
if ! ${CLI} run which IfcConvert &>/dev/null ||
! echo "2d48d5df36371fc5920a71f0d74b29449b0b166a $(${CLI} run which IfcConvert)" | sha1sum -c - ; then
- echo "Fetching and installing IfcConvert..."
- wget --quiet https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.11-fea8e3a-linux64.zip
+ echo "Installing IfcConvert..."
+ cp "$APP_HOME/vendor/bim/IfcConvert-v0.7.11-fea8e3a-linux64.zip" .
unzip -qq IfcConvert-v0.7.11-fea8e3a-linux64.zip
mv IfcConvert "$APP_HOME/bin/IfcConvert"
fi
if ! ${CLI} run which xeokit-metadata &>/dev/null ||
! echo "f960fb98106280223a664d176daaeb7ca7904e6f $(${CLI} run which xeokit-metadata)" | sha1sum -c - ; then
- echo "Fetching and installing xeokit-metadata..."
- wget --quiet https://github.com/bimspot/xeokit-metadata/releases/download/1.0.1/xeokit-metadata-linux-x64.tar.gz
+ echo "Installing xeokit-metadata..."
+ cp "$APP_HOME/vendor/bim/xeokit-metadata-linux-x64.tar.gz" .
tar -zxf xeokit-metadata-linux-x64.tar.gz
chmod +x xeokit-metadata-linux-x64/xeokit-metadata
cp -r xeokit-metadata-linux-x64/ "$APP_HOME/vendor/xeokit-metadata"
diff --git a/packaging/setup b/packaging/setup
index 87fc82e7c19..a3416d2bc5b 100755
--- a/packaging/setup
+++ b/packaging/setup
@@ -14,3 +14,12 @@ cp -f packaging/.npmrc .npmrc
# replace every occurrence of _APP_NAME_ with the corresponding application name we're packaging
find packaging/ -type f -print0 | xargs -0 sed -i "s|_APP_NAME_|${APP_NAME}|"
+
+# Embed BIM dependencies for air-gapped use
+mkdir -p vendor/bim
+pushd vendor/bim
+# These are referenced in /packaging/addons/openproject-edition/bin/preinstall
+wget --quiet https://github.com/KhronosGroup/COLLADA2GLTF/releases/download/v2.1.5/COLLADA2GLTF-v2.1.5-linux.zip
+wget --quiet https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.11-fea8e3a-linux64.zip
+wget --quiet https://github.com/bimspot/xeokit-metadata/releases/download/1.0.1/xeokit-metadata-linux-x64.tar.gz
+popd
diff --git a/packaging/teardown b/packaging/teardown
index fb230cf7169..a7bb92f0cad 100755
--- a/packaging/teardown
+++ b/packaging/teardown
@@ -2,8 +2,8 @@
set -e
-echo "Cleaning up current node_modules folders"
-rm -rf node_modules frontend/node_modules || true
+echo "Cleaning up frontend/node_modules folders"
+rm -rf frontend/node_modules || true
echo "Cleaning up current docs folders except API"
# We need the api folder for openapi (https://community.openproject.org/work_packages/40352/activity)
diff --git a/publiccode.yml b/publiccode.yml
index c49cb17e9d9..617e3c3321d 100644
--- a/publiccode.yml
+++ b/publiccode.yml
@@ -7,8 +7,8 @@ name: OpenProject
applicationSuite: openDesk
url: 'https://github.com/opf/openproject'
roadmap: 'https://www.openproject.org/roadmap'
-releaseDate: '2025-06-18'
-softwareVersion: '16.1.0'
+releaseDate: '2025-06-26'
+softwareVersion: '16.1.1'
developmentStatus: stable
softwareType: standalone/web
logo: 'publiccode_logo.svg'
diff --git a/spec/contracts/attachments/create_contract_spec.rb b/spec/contracts/attachments/create_contract_spec.rb
index e2e0367981a..723ab76823b 100644
--- a/spec/contracts/attachments/create_contract_spec.rb
+++ b/spec/contracts/attachments/create_contract_spec.rb
@@ -104,28 +104,28 @@ RSpec.describe Attachments::CreateContract do
end
end
- context "with an empty whitelist",
+ context "with an empty allowlist",
with_settings: { attachment_whitelist: %w[] } do
it_behaves_like "contract is valid"
end
- context "with a matching mime whitelist",
+ context "with a matching mime allowlist",
with_settings: { attachment_whitelist: %w[image/png] } do
it_behaves_like "contract is valid"
end
- context "with a matching extension whitelist",
+ context "with a matching extension allowlist",
with_settings: { attachment_whitelist: %w[*.png] } do
it_behaves_like "contract is valid"
end
- context "with a non-matching whitelist",
+ context "with a non-matching allowlist",
with_settings: { attachment_whitelist: %w[*.jpg image/jpeg] } do
- it_behaves_like "contract is invalid", content_type: :not_whitelisted
+ it_behaves_like "contract is invalid", content_type: :not_allowlisted
context "when disabling the whitelist check" do
let(:contract_options) do
- { whitelist: [] }
+ { allowlist: [] }
end
it_behaves_like "contract is valid"
@@ -133,7 +133,7 @@ RSpec.describe Attachments::CreateContract do
context "when overriding the whitelist" do
let(:contract_options) do
- { whitelist: %w[*.png] }
+ { allowlist: %w[*.png] }
end
it_behaves_like "contract is valid"
diff --git a/spec/contracts/work_packages/base_contract_spec.rb b/spec/contracts/work_packages/base_contract_spec.rb
index 776775e118c..64e4daa5cee 100644
--- a/spec/contracts/work_packages/base_contract_spec.rb
+++ b/spec/contracts/work_packages/base_contract_spec.rb
@@ -1185,8 +1185,6 @@ RSpec.describe WorkPackages::BaseContract do
subject do
contract.validate
- # while we do validate the parent
- # the errors are still put on :base so that the messages can be reused
contract.errors.symbols_for(:parent)
end
@@ -1196,6 +1194,7 @@ RSpec.describe WorkPackages::BaseContract do
it "returns an error for the parent" do
expect(subject)
.to eq [:cannot_be_self_assigned]
+ expect(contract.errors.attribute_names).to contain_exactly(:parent)
end
end
diff --git a/spec/features/admin/project_life_cycle_step_definitions_spec.rb b/spec/features/admin/project_life_cycle_step_definitions_spec.rb
index dee72c1720c..0ef438350c5 100644
--- a/spec/features/admin/project_life_cycle_step_definitions_spec.rb
+++ b/spec/features/admin/project_life_cycle_step_definitions_spec.rb
@@ -100,6 +100,9 @@ RSpec.describe "Projects life cycle settings", :js, with_flag: { stages_and_gate
# editing steps
definitions_page.click_definition("Initiating")
+
+ definitions_page.expect_header_to_display("Initiating")
+
fill_in "Name", with: "Starting"
click_on "Update"
@@ -111,6 +114,9 @@ RSpec.describe "Projects life cycle settings", :js, with_flag: { stages_and_gate
# creating steps
definitions_page.add
+
+ definitions_page.expect_header_to_display("New phase")
+
fill_in "Name", with: "Imagining"
definitions_page.select_color("Azure")
click_on "Create"
diff --git a/spec/features/projects/life_cycle/overview_page/dialog/update_spec.rb b/spec/features/projects/life_cycle/overview_page/dialog/update_spec.rb
index 31d676359cc..44208e82c90 100644
--- a/spec/features/projects/life_cycle/overview_page/dialog/update_spec.rb
+++ b/spec/features/projects/life_cycle/overview_page/dialog/update_spec.rb
@@ -131,7 +131,13 @@ RSpec.describe "Edit project phases on project overview page", :js, with_flag: {
dialog.expect_input("Finish date", value: expected_finish_date)
dialog.expect_input("Duration", value: planning_duration, disabled: true)
- datepicker.expect_disabled(expected_start_date - 1.day)
+ # The spec uses relative dates. On some occasions, this can mean the expected_start_date
+ # is on the first date of a month. The day before that is then not shown at all.
+ # And because the date is disabled, flatpickr also does not provide a button to move to the previous
+ # month. In total, we cannot check for the date to be disabled in these occasions.
+ if datepicker.displays_date?(expected_start_date - 1.day) && !datepicker.has_previous_month_toggle?
+ datepicker.expect_disabled(expected_start_date - 1.day)
+ end
datepicker.expect_not_disabled(expected_start_date)
# Set invalid range (finish date before start date) via input field
diff --git a/spec/features/work_packages/custom_actions/custom_actions_spec.rb b/spec/features/work_packages/custom_actions/custom_actions_spec.rb
index 68d081f8e3b..2ec0ce4b9be 100644
--- a/spec/features/work_packages/custom_actions/custom_actions_spec.rb
+++ b/spec/features/work_packages/custom_actions/custom_actions_spec.rb
@@ -129,6 +129,12 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do
cf
end
+ let!(:multi_user_custom_field) do
+ create(:multi_user_wp_custom_field).tap do |cf|
+ project.work_package_custom_fields << cf
+ work_package.type.custom_fields << cf
+ end
+ end
let(:index_ca_page) { Pages::Admin::CustomActions::Index.new }
let(:activity_tab) { Components::WorkPackages::Activities.new(work_package) }
@@ -484,7 +490,7 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do
end
end
- it "disables the custom action button and editing other fields when submiting the custom action" do
+ it "disables the custom action button and editing other fields when submitting the custom action" do
# create custom action 'Unassign'
index_ca_page.visit!
@@ -543,4 +549,24 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do
wp_page.click_custom_action("Unassign", expect_success: true)
end
end
+
+ context "with a multi_user field (Bug#64981)" do
+ it "saves when a user is assigned to the custom field" do
+ index_ca_page.visit!
+
+ new_ca_page = index_ca_page.new
+ new_ca_page.set_name("MultiUser")
+ new_ca_page.set_description("Multiple User Custom Field Test")
+ new_ca_page.add_action(multi_user_custom_field.name, other_member_user.name)
+ new_ca_page.expect_action(multi_user_custom_field.name, [other_member_user.name])
+
+ new_ca_page.create
+
+ index_ca_page.expect_current_path
+ index_ca_page.expect_listed("MultiUser")
+
+ edit_ca_page = index_ca_page.edit("MultiUser")
+ edit_ca_page.expect_action(multi_user_custom_field.name, [other_member_user.name])
+ end
+ end
end
diff --git a/spec/requests/api/v3/activities_api_spec.rb b/spec/requests/api/v3/activities_api_spec.rb
index 9c97193b06e..f68dd164ffa 100644
--- a/spec/requests/api/v3/activities_api_spec.rb
+++ b/spec/requests/api/v3/activities_api_spec.rb
@@ -190,6 +190,25 @@ RSpec.describe API::V3::Activities::ActivitiesAPI, content_type: :json do
end
end
end
+
+ context "with attachments" do
+ let(:attachment1) { create(:attachment, container: nil, author: current_user) }
+ let(:attachment2) { create(:attachment, container: nil, author: current_user) }
+
+ let(:comment) do
+ <<~HTML
+
+ Lorem ipsum dolor sit amet
+
+ consectetur adipiscing elit
+ HTML
+ end
+
+ it "creates attachment claims" do
+ expect(last_response.body).to be_json_eql(comment.to_json).at_path("comment/raw")
+ expect(activity.reload.attachments).to contain_exactly(attachment1, attachment2)
+ end
+ end
end
describe "#get api" do
diff --git a/spec/requests/api/v3/activities_by_work_package_resource_spec.rb b/spec/requests/api/v3/activities_by_work_package_resource_spec.rb
index a5b8650009e..81382095e41 100644
--- a/spec/requests/api/v3/activities_by_work_package_resource_spec.rb
+++ b/spec/requests/api/v3/activities_by_work_package_resource_spec.rb
@@ -209,6 +209,28 @@ RSpec.describe API::V3::Activities::ActivitiesByWorkPackageAPI, with_ee: [:inter
end
end
end
+
+ context "with attachments" do
+ include_context "create activity"
+
+ let(:attachment1) { create(:attachment, container: nil, author: current_user) }
+ let(:attachment2) { create(:attachment, container: nil, author: current_user) }
+
+ let(:comment) do
+ <<~HTML
+
+ Lorem ipsum dolor sit amet
+
+ consectetur adipiscing elit
+ HTML
+ end
+
+ it "creates attachment claims" do
+ expect(last_response.body).to be_json_eql(comment.to_json).at_path("comment/raw")
+ journal = work_package.journals.last
+ expect(journal.attachments).to contain_exactly(attachment1, attachment2)
+ end
+ end
end
end
end
diff --git a/spec/requests/api/v3/attachments/attachment_resource_shared_examples.rb b/spec/requests/api/v3/attachments/attachment_resource_shared_examples.rb
index 19be033f486..5391a8a539a 100644
--- a/spec/requests/api/v3/attachments/attachment_resource_shared_examples.rb
+++ b/spec/requests/api/v3/attachments/attachment_resource_shared_examples.rb
@@ -157,7 +157,7 @@ RSpec.shared_examples "it supports direct uploads" do
end
end
- context "with an attachment whitelist", with_settings: { attachment_whitelist: ["text/csv"] } do
+ context "with an attachment allowlist", with_settings: { attachment_whitelist: ["text/csv"] } do
context "with an allowed content type" do
let(:metadata) { { fileName: "cats.csv", fileSize: file.size, contentType: "text/csv" } }
@@ -171,14 +171,14 @@ RSpec.shared_examples "it supports direct uploads" do
it "fails" do
expect(subject.status).to eq 422
- expect(subject.body).to include "not whitelisted"
+ expect(subject.body).to include "'text/plain' is not allowed for upload."
end
end
- context "with a non-specific content type not on the whitelist" do
+ context "with a non-specific content type not on the allowlist" do
let(:metadata) { { fileName: "cats.bin", fileSize: file.size, contentType: "application/binary" } }
- # the actual whitelist check will be performed in the FinishDirectUpload job in this case
+ # the actual allowlist check will be performed in the FinishDirectUpload job in this case
it "still succeeds" do
expect(subject.status).to eq 201
end
diff --git a/spec/requests/api/v3/authentication_spec.rb b/spec/requests/api/v3/authentication_spec.rb
index 7d38c757421..cf01ce750e4 100644
--- a/spec/requests/api/v3/authentication_spec.rb
+++ b/spec/requests/api/v3/authentication_spec.rb
@@ -149,6 +149,18 @@ RSpec.describe "API V3 Authentication" do
end
end
+ context "when the token's resource owner is locked" do
+ let(:token) { create(:oauth_access_token, resource_owner: user) }
+ let(:oauth_access_token) { token.plaintext_token }
+ let(:user) { create(:user, :locked) }
+
+ it "returns unauthorized" do
+ expect(last_response).to have_http_status :unauthorized
+ expect(last_response.header["WWW-Authenticate"]).to eq('Bearer realm="OpenProject API", error="invalid_token"')
+ expect(JSON.parse(last_response.body)).to eq(error_response_body)
+ end
+ end
+
context "when there is no resource owner on the token" do
let(:token) { create(:oauth_access_token, resource_owner: nil, application:) }
let(:application) { create(:oauth_application) }
@@ -173,6 +185,17 @@ RSpec.describe "API V3 Authentication" do
it "authenticates successfully" do
expect(last_response).to have_http_status :ok
end
+
+ context "and the client credentials user is locked" do
+ let(:user) { create(:user, :locked) }
+ let(:expected_message) { "You did not provide the correct credentials." }
+
+ it "returns unauthorized" do
+ expect(last_response).to have_http_status :unauthorized
+ expect(last_response.header["WWW-Authenticate"]).to eq('Bearer realm="OpenProject API", error="invalid_token"')
+ expect(JSON.parse(last_response.body)).to eq(error_response_body)
+ end
+ end
end
end
end
diff --git a/spec/services/add_work_package_note_service_spec.rb b/spec/services/add_work_package_note_service_spec.rb
index 5132e82b441..b86f4a2e9a8 100644
--- a/spec/services/add_work_package_note_service_spec.rb
+++ b/spec/services/add_work_package_note_service_spec.rb
@@ -57,6 +57,10 @@ RSpec.describe AddWorkPackageNoteService, type: :model do
end
let(:valid_contract) { true }
let(:contract_errors) { instance_double(ActiveModel::Errors, full_messages: ["error message"]) }
+ let(:claims_service) { WorkPackages::ActivitiesTab::CommentAttachmentsClaims::ClaimsService }
+ let(:mock_claim_service_instance) do
+ instance_double(claims_service, call: ServiceResult.success(result: []))
+ end
let(:send_notifications) { false }
@@ -64,6 +68,7 @@ RSpec.describe AddWorkPackageNoteService, type: :model do
allow(instance).to receive(:contract_class).and_return(mock_contract)
allow(work_package).to receive(:add_journal).and_call_original
allow(work_package).to receive(:save_journals).and_return(true)
+ allow(claims_service).to receive(:new).and_return(mock_claim_service_instance)
end
subject { instance.call("blubs", send_notifications:) }
@@ -86,6 +91,51 @@ RSpec.describe AddWorkPackageNoteService, type: :model do
end
end
+ context "when the journal notes have attachments" do
+ let(:attachment1) { build_stubbed(:attachment) }
+ let(:attachment2) { build_stubbed(:attachment) }
+
+ let(:notes) do
+ <<~HTML
+
+ Lorem ipsum dolor sit amet
+
+ consectetur adipiscing elit
+ HTML
+ end
+
+ let(:mock_claim_service_instance) do
+ instance_double(claims_service, call: ServiceResult.success(result: [attachment1, attachment2]))
+ end
+
+ subject { instance.call(notes, send_notifications:) }
+
+ context "and note creation is successful" do
+ it "creates an attachment claim" do
+ expect(subject).to be_success
+ expect(WorkPackages::ActivitiesTab::CommentAttachmentsClaims::ClaimsService)
+ .to have_received(:new).with(user: user, model: work_package.journals.last)
+
+ dependent_results = subject.dependent_results
+ expect(dependent_results.size).to eq(1)
+ expect(dependent_results.first).to be_a_success
+ expect(dependent_results.first.result).to contain_exactly(attachment1, attachment2)
+ end
+ end
+
+ context "and note creation is unsuccessful" do
+ let(:valid_contract) { false }
+
+ it "does not create an attachment claim" do
+ expect(subject).to be_a_failure
+ expect(WorkPackages::ActivitiesTab::CommentAttachmentsClaims::ClaimsService)
+ .not_to have_received(:new)
+
+ expect(subject.dependent_results).to be_empty
+ end
+ end
+ end
+
it "creates an advisory lock" do
allow(OpenProject::Mutex)
.to receive(:with_advisory_lock_transaction)
diff --git a/spec/services/journals/update_service_spec.rb b/spec/services/journals/update_service_spec.rb
new file mode 100644
index 00000000000..c4df6a3545b
--- /dev/null
+++ b/spec/services/journals/update_service_spec.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+#-- 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.
+#++
+
+require "spec_helper"
+require "services/base_services/behaves_like_update_service"
+
+RSpec.describe Journals::UpdateService do
+ it_behaves_like "BaseServices update service" do
+ let(:factory) { :journal }
+
+ describe "Inline attachments" do
+ let(:model_instance) { build_stubbed(:work_package_journal, notes: "Foobar") }
+ let(:claims_service) { WorkPackages::ActivitiesTab::CommentAttachmentsClaims::ClaimsService }
+ let(:attachment1) { build_stubbed(:attachment) }
+ let(:attachment2) { build_stubbed(:attachment) }
+
+ let(:notes) do
+ <<~HTML
+
+ Lorem ipsum dolor sit amet
+
+ consectetur adipiscing elit
+ HTML
+ end
+
+ let(:call_attributes) { { notes: } }
+
+ let(:mock_claim_service_instance) do
+ instance_double(claims_service, call: ServiceResult.success(result: [attachment1, attachment2]))
+ end
+
+ before do
+ allow(claims_service).to receive(:new).and_return(mock_claim_service_instance)
+ end
+
+ context "when the journal notes have attachments" do
+ context "and note creation is successful" do
+ it "creates an attachment claim" do
+ expect(subject).to be_success
+ expect(WorkPackages::ActivitiesTab::CommentAttachmentsClaims::ClaimsService)
+ .to have_received(:new).with(user: user, model: model_instance)
+
+ dependent_results = subject.dependent_results
+ expect(dependent_results.size).to eq(1)
+ expect(dependent_results.first).to be_a_success
+ expect(dependent_results.first.result).to contain_exactly(attachment1, attachment2)
+ end
+ end
+
+ context "and note creation is unsuccessful" do
+ let(:model_save_result) { false }
+
+ it "does not create an attachment claim" do
+ expect(subject).to be_a_failure
+ expect(WorkPackages::ActivitiesTab::CommentAttachmentsClaims::ClaimsService)
+ .not_to have_received(:new)
+
+ expect(subject.dependent_results).to be_empty
+ end
+ end
+ end
+
+ context "with empty notes" do
+ let(:model_instance) { build_stubbed(:work_package_journal, notes: "") }
+
+ it "does not create an attachment claim" do
+ expect(subject).to be_a_success
+
+ expect(WorkPackages::ActivitiesTab::CommentAttachmentsClaims::ClaimsService)
+ .not_to have_received(:new)
+
+ expect(subject.dependent_results).to be_empty
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/reminders/update_service_spec.rb b/spec/services/reminders/update_service_spec.rb
index be14bd68b15..acd00a363d2 100644
--- a/spec/services/reminders/update_service_spec.rb
+++ b/spec/services/reminders/update_service_spec.rb
@@ -41,7 +41,8 @@ RSpec.describe Reminders::UpdateService do
let(:model_instance) { create(:reminder, :scheduled, :with_unread_notifications, creator: user) }
let(:user) { create(:admin) }
- let(:call_attributes) { { remind_at: 2.days.from_now } }
+ let(:remind_at) { 2.days.from_now.change(hour: 12, min: 0) }
+ let(:call_attributes) { { remind_at_date: remind_at.to_date, remind_at_time: remind_at.strftime("%H:%M") } }
before do
model_instance.update(job_id: 1)
@@ -71,7 +72,7 @@ RSpec.describe Reminders::UpdateService do
end
aggregate_failures "schedule new job" do
- expect(model_instance.remind_at.to_i).to eq(call_attributes[:remind_at].to_i)
+ expect(model_instance.remind_at.to_i).to eq(remind_at.to_i)
expect(Reminders::ScheduleReminderJob).to have_received(:schedule).with(model_instance)
end
end
@@ -93,7 +94,7 @@ RSpec.describe Reminders::UpdateService do
end
aggregate_failures "schedule new job" do
- expect(model_instance.remind_at.to_i).to eq(call_attributes[:remind_at].to_i)
+ expect(model_instance.remind_at.to_i).to eq(remind_at.to_i)
expect(Reminders::ScheduleReminderJob).to have_received(:schedule).with(model_instance)
end
end
diff --git a/spec/services/work_packages/schedule_dependency/dependency_graph_spec.rb b/spec/services/work_packages/schedule_dependency/dependency_graph_spec.rb
new file mode 100644
index 00000000000..2b854f90ea9
--- /dev/null
+++ b/spec/services/work_packages/schedule_dependency/dependency_graph_spec.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+require "rails_helper"
+
+RSpec.describe WorkPackages::ScheduleDependency::DependencyGraph do
+ create_shared_association_defaults_for_work_package_factory
+
+ describe "#depends_on?" do
+ context "with simple linear predecessor/successor dependencies" do
+ # create a linear dependency: wp1 -> wp2 -> wp3
+ let_work_packages(<<~TABLE)
+ hierarchy | scheduling mode | successors
+ wp1 | manual | wp2
+ wp2 | automatic | wp3
+ wp3 | automatic |
+ TABLE
+
+ let(:schedule_dependency) { WorkPackages::ScheduleDependency.new([wp1]) }
+ let(:wp1_dependency) { WorkPackages::ScheduleDependency::Dependency.new(wp1, schedule_dependency) }
+ let(:wp2_dependency) { WorkPackages::ScheduleDependency::Dependency.new(wp2, schedule_dependency) }
+ let(:wp3_dependency) { WorkPackages::ScheduleDependency::Dependency.new(wp3, schedule_dependency) }
+ let(:dependencies) { [wp1_dependency, wp2_dependency, wp3_dependency] }
+
+ it "returns true when a given work package depends on the work package from the given dependency" do
+ dependency_graph = described_class.new(dependencies)
+
+ expect(dependency_graph.depends_on?(wp1, wp1_dependency)).to be(false)
+ expect(dependency_graph.depends_on?(wp1, wp2_dependency)).to be(false)
+ expect(dependency_graph.depends_on?(wp1, wp3_dependency)).to be(false)
+
+ expect(dependency_graph.depends_on?(wp2, wp1_dependency)).to be(true)
+ expect(dependency_graph.depends_on?(wp2, wp2_dependency)).to be(false)
+ expect(dependency_graph.depends_on?(wp2, wp3_dependency)).to be(false)
+
+ expect(dependency_graph.depends_on?(wp3, wp1_dependency)).to be(true)
+ expect(dependency_graph.depends_on?(wp3, wp2_dependency)).to be(true)
+ expect(dependency_graph.depends_on?(wp3, wp3_dependency)).to be(false)
+ end
+ end
+
+ context "with circular dependencies between two work packages" do
+ # Create circular dependency: wp1 -> wp2 -> wp1
+ let_work_packages(<<~TABLE)
+ subject | scheduling mode | successors
+ wp1 | automatic | wp2
+ wp2 | automatic | wp1
+ TABLE
+
+ let(:schedule_dependency) { WorkPackages::ScheduleDependency.new([wp1]) }
+ let(:wp1_dependency) { WorkPackages::ScheduleDependency::Dependency.new(wp1, schedule_dependency) }
+ let(:wp2_dependency) { WorkPackages::ScheduleDependency::Dependency.new(wp2, schedule_dependency) }
+ let(:dependencies) { [wp1_dependency, wp2_dependency] }
+
+ it "avoids infinite recursion and returns true when given a dependent dependency" do
+ dependency_graph = described_class.new(dependencies)
+
+ expect(dependency_graph.depends_on?(wp1, wp1_dependency)).to be(false)
+ expect(dependency_graph.depends_on?(wp1, wp2_dependency)).to be(true)
+
+ expect(dependency_graph.depends_on?(wp2, wp1_dependency)).to be(true)
+ expect(dependency_graph.depends_on?(wp2, wp2_dependency)).to be(false)
+ end
+ end
+
+ context "with circular dependency between one work package" do
+ # Create circular dependency: wp1 -> wp2 -> wp1
+ let_work_packages(<<~TABLE)
+ subject | scheduling mode | successors
+ wp1 | automatic | wp1
+ TABLE
+
+ let(:schedule_dependency) { WorkPackages::ScheduleDependency.new([wp1]) }
+ let(:wp1_dependency) { WorkPackages::ScheduleDependency::Dependency.new(wp1, schedule_dependency) }
+ let(:dependencies) { [wp1_dependency] }
+
+ it "avoids infinite recursion and returns false when given itsef as a dependency" do
+ dependency_graph = described_class.new(dependencies)
+
+ expect(dependency_graph.depends_on?(wp1, wp1_dependency)).to be(false)
+ end
+ end
+ end
+end
diff --git a/spec/services/work_packages/schedule_dependency_spec.rb b/spec/services/work_packages/schedule_dependency_spec.rb
index 47dbb8ae8e8..c9fb9f3522d 100644
--- a/spec/services/work_packages/schedule_dependency_spec.rb
+++ b/spec/services/work_packages/schedule_dependency_spec.rb
@@ -75,4 +75,117 @@ RSpec.describe WorkPackages::ScheduleDependency do
end
end
end
+
+ describe "#automatically_scheduled_ancestors" do
+ shared_let(:work_package) { create(:work_package, subject: "work_package") }
+ let(:schedule_dependency) { described_class.new(work_package) }
+
+ context "with no parent" do
+ it "returns an empty array" do
+ expect(schedule_dependency.automatically_scheduled_ancestors(work_package)).to be_empty
+ end
+ end
+
+ context "with a single automatically scheduled parent" do
+ let_work_packages(<<~TABLE)
+ hierarchy | scheduling mode
+ parent | automatic
+ TABLE
+
+ before do
+ work_package.update(parent:)
+ end
+
+ it "returns the parent" do
+ expect(schedule_dependency.automatically_scheduled_ancestors(work_package)).to contain_exactly(parent)
+ end
+ end
+
+ context "with a manually scheduled parent" do
+ let_work_packages(<<~TABLE)
+ hierarchy | scheduling mode
+ parent | manual
+ TABLE
+
+ before do
+ work_package.update(parent:)
+ end
+
+ it "returns an empty array" do
+ expect(schedule_dependency.automatically_scheduled_ancestors(work_package)).to be_empty
+ end
+ end
+
+ context "with multiple levels of automatically scheduled ancestors" do
+ let_work_packages(<<~TABLE)
+ hierarchy | scheduling mode
+ grandparent | automatic
+ parent | automatic
+ TABLE
+
+ before do
+ work_package.update(parent:)
+ end
+
+ it "returns all automatically scheduled ancestors" do
+ expect(schedule_dependency.automatically_scheduled_ancestors(work_package)).to contain_exactly(parent, grandparent)
+ end
+ end
+
+ context "with mixed scheduling modes in the hierarchy" do
+ let_work_packages(<<~TABLE)
+ hierarchy | scheduling mode
+ grandparent | automatic
+ parent | manual
+ TABLE
+
+ before do
+ work_package.update(parent:)
+ end
+
+ it "returns only automatically scheduled ancestors" do
+ expect(schedule_dependency.automatically_scheduled_ancestors(work_package)).to be_empty
+ end
+ end
+
+ context "with a cycle in the hierarchy" do
+ let_work_packages(<<~TABLE)
+ hierarchy | scheduling mode
+ child | automatic
+ grandchild | manual
+ TABLE
+
+ before do
+ child.update(parent: work_package)
+ work_package.update(schedule_manually: false)
+
+ # Create the cycle: set the parent with a current child, but do not
+ # save, like when set by an update during a set attributes service call
+ work_package.parent = child
+ end
+
+ it "handles the cycle gracefully and does not cause an infinite loop" do
+ expect { schedule_dependency.automatically_scheduled_ancestors(work_package) }.not_to raise_error
+ # Should return the parent but not get stuck in an infinite loop
+ expect(schedule_dependency.automatically_scheduled_ancestors(work_package)).to contain_exactly(child)
+ end
+ end
+
+ context "with a complex hierarchy" do
+ let_work_packages(<<~TABLE)
+ hierarchy | scheduling mode
+ great_grandparent | automatic
+ grandparent | manual
+ parent | automatic
+ TABLE
+
+ before do
+ work_package.update(parent:)
+ end
+
+ it "returns only automatically scheduled ancestors" do
+ expect(schedule_dependency.automatically_scheduled_ancestors(work_package)).to contain_exactly(parent)
+ end
+ end
+ end
end
diff --git a/spec/services/work_packages/update_service_integration_spec.rb b/spec/services/work_packages/update_service_integration_spec.rb
index 9ea6044bdf6..7e53bd4277e 100644
--- a/spec/services/work_packages/update_service_integration_spec.rb
+++ b/spec/services/work_packages/update_service_integration_spec.rb
@@ -64,7 +64,7 @@ RSpec.describe WorkPackages::UpdateService, "integration", type: :model do
set_factory_default(:user, user)
end
- shared_let(:work_package, refind: true, reload: false) do
+ let(:work_package) do
create(:work_package,
subject: "work_package")
end
@@ -1878,4 +1878,52 @@ RSpec.describe WorkPackages::UpdateService, "integration", type: :model do
.to eq [[custom_field_of_new_type.id, "8"]]
end
end
+
+ context "when a predecessor with a child is made a child of its successor" do
+ let_work_packages(<<~TABLE)
+ hierarchy | scheduling mode | successors
+ work_package | automatic | successor
+ child | manual |
+ successor | automatic |
+ TABLE
+ let(:attributes) do
+ {
+ parent: successor
+ }
+ end
+
+ # Bug #64973: this was causing an infinite loop when computing the future
+ # dates of the predecessor.
+ it "displays an error about the inability to have multiple relations between the same work packages (Bug #64973)" do
+ expect(subject).to be_failure
+
+ expect(subject.errors.attribute_names).to contain_exactly(:parent)
+ # the error message in this case is far from ideal
+ expect(subject.errors.details).to include(parent: [{ error: :cant_link_a_work_package_with_a_descendant }])
+ end
+ end
+
+ context "when a work package with a child and a grandchild is made a child of its child" do
+ let_work_packages(<<~TABLE)
+ hierarchy | scheduling mode
+ work_package | automatic
+ child | automatic
+ grandchild | manual
+ TABLE
+ let(:attributes) do
+ {
+ parent: child
+ }
+ end
+
+ # Bug #65062: this was causing an infinite loop when computing automatically
+ # scheduled ancestors of the updated work package.
+ it "displays an error about the inability to have multiple relations between the same work packages (Bug #65062)" do
+ expect(subject).to be_failure
+
+ expect(subject.errors.attribute_names).to contain_exactly(:parent)
+ # the error message in this case is far from ideal
+ expect(subject.errors.details).to include(parent: [{ error: :cant_link_a_work_package_with_a_descendant }])
+ end
+ end
end
diff --git a/spec/support/components/datepicker/datepicker.rb b/spec/support/components/datepicker/datepicker.rb
index 90cd4f19f6b..7d158a3be42 100644
--- a/spec/support/components/datepicker/datepicker.rb
+++ b/spec/support/components/datepicker/datepicker.rb
@@ -206,6 +206,15 @@ module Components
expect(page).to have_css(".flatpickr-day:not(.flatpickr-disabled):not(.flatpickr-non-working-day)[aria-label='#{label}']")
end
+ def displays_date?(date)
+ label = date.strftime("%B %-d, %Y")
+ page.has_css?(".flatpickr-day.flatpickr-disabled[aria-label='#{label}']")
+ end
+
+ def has_previous_month_toggle?
+ page.has_css?(".flatpickr-prev-month")
+ end
+
protected
def save_button_label
diff --git a/spec/support/pages/admin/settings/project_life_cycle_step_definitions.rb b/spec/support/pages/admin/settings/project_life_cycle_step_definitions.rb
index 6d5d2359cdc..54147b45f3a 100644
--- a/spec/support/pages/admin/settings/project_life_cycle_step_definitions.rb
+++ b/spec/support/pages/admin/settings/project_life_cycle_step_definitions.rb
@@ -36,6 +36,12 @@ module Pages
class ProjectLifeCycleStepDefinitions < ::Pages::Page
def path = "/admin/settings/project_life_cycle"
+ def expect_header_to_display(text)
+ expect(page).to have_css("h2", text:)
+ expect(page).to have_css(".breadcrumb-item-selected a", text:)
+ expect(page).to have_title("#{text} | Project life cycle | Administration | OpenProject")
+ end
+
def expect_listed(names)
page.document.synchronize do
found = page.all("[data-test-selector=project-life-cycle-step-definition-name]").collect(&:text)
diff --git a/spec/workers/attachments/finish_direct_upload_job_integration_spec.rb b/spec/workers/attachments/finish_direct_upload_job_integration_spec.rb
index 08097ce47d1..e6b0aef64e8 100644
--- a/spec/workers/attachments/finish_direct_upload_job_integration_spec.rb
+++ b/spec/workers/attachments/finish_direct_upload_job_integration_spec.rb
@@ -136,7 +136,19 @@ RSpec.describe Attachments::FinishDirectUploadJob, "integration", type: :job do
it_behaves_like "adding a journal to the attachment in the name of the attachment's author"
end
- context "with an incompatible attachment whitelist",
+ context "for an attachment that has been removed in the meantime" do
+ let!(:container) { create(:work_package) }
+
+ it "logs the error and resolves the job as done" do
+ allow(OpenProject.logger).to receive(:error)
+
+ job.perform(pending_attachment.id + 1)
+
+ expect(OpenProject.logger).to have_received(:error)
+ end
+ end
+
+ context "with an incompatible attachment allowlist",
with_settings: { attachment_whitelist: %w[image/png] } do
let!(:container) { create(:work_package) }
let!(:container_timestamp) { container.updated_at }
@@ -156,9 +168,9 @@ RSpec.describe Attachments::FinishDirectUploadJob, "integration", type: :job do
expect { pending_attachment.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
- context "when the job is getting a whitelist override" do
+ context "when the job is getting a allowlist override" do
it "Does save the attachment" do
- job.perform(pending_attachment.id, whitelist: false)
+ job.perform(pending_attachment.id, allowlist: false)
container.reload
diff --git a/spec/workers/work_packages/exports/export_job_spec.rb b/spec/workers/work_packages/exports/export_job_spec.rb
index 9480c7b2e28..114b492904b 100644
--- a/spec/workers/work_packages/exports/export_job_spec.rb
+++ b/spec/workers/work_packages/exports/export_job_spec.rb
@@ -68,15 +68,14 @@ RSpec.describe WorkPackages::ExportJob do
let(:exporter_instance) { instance_double(exporter) }
it "exports" do
- expect(Attachments::CreateService)
- .to receive(:bypass_whitelist)
- .with(user:)
+ allow(Attachments::CreateService)
+ .to receive(:bypass_allowlist)
.and_return(service)
- expect(Exports::CleanupOutdatedJob)
+ allow(Exports::CleanupOutdatedJob)
.to receive(:perform_after_grace)
- expect(service)
+ allow(service)
.to receive(:call) do |file:, **_args|
expect(File.basename(file))
.to start_with "some_title"
@@ -96,6 +95,13 @@ RSpec.describe WorkPackages::ExportJob do
expect(subject.job_status.reference).to eq export
expect(subject.job_status[:status]).to eq "success"
expect(subject.job_status[:payload]["download"]).to eq "/api/v3/attachments/1234/content"
+
+ expect(Attachments::CreateService)
+ .to have_received(:bypass_allowlist)
+ .with(user:)
+
+ expect(Exports::CleanupOutdatedJob)
+ .to have_received(:perform_after_grace)
end
end