Merge branch 'dev' into feature/42358-standardise-date-pickers-2

feature/42358-standardise-date-pickers-drop-modal-portal
Yule 2 years ago committed by GitHub
commit d67d18c8f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      .github/workflows/brakeman-scan-core.yml
  2. 6
      .github/workflows/codeql-scan-core.yml
  3. 2
      .github/workflows/create-merge-release-into-dev-pr.yml
  4. 9
      .github/workflows/crowdin.yml
  5. 2
      .github/workflows/docker.yml
  6. 2
      .github/workflows/eslint-core.yml
  7. 2
      .github/workflows/pullpreview.yml
  8. 2
      .github/workflows/rubocop-core.yml
  9. 12
      .github/workflows/test-core.yml
  10. 2
      .rubocop.yml
  11. 12
      Gemfile
  12. 198
      Gemfile.lock
  13. 2
      Gemfile.modules
  14. 4
      app/cells/projects/table_cell.rb
  15. 2
      app/contracts/base_contract.rb
  16. 7
      app/contracts/concerns/requires_admin_guard.rb
  17. 40
      app/contracts/projects/archive_contract.rb
  18. 25
      app/contracts/projects/base_contract.rb
  19. 9
      app/contracts/projects/unarchive_contract.rb
  20. 8
      app/contracts/projects/update_contract.rb
  21. 2
      app/contracts/settings/working_days_params_contract.rb
  22. 26
      app/controllers/admin/settings/working_days_settings_controller.rb
  23. 23
      app/controllers/admin/settings_controller.rb
  24. 19
      app/controllers/projects/archive_controller.rb
  25. 8
      app/controllers/projects_controller.rb
  26. 19
      app/helpers/application_helper.rb
  27. 16
      app/helpers/projects_helper.rb
  28. 30
      app/models/actions/scopes/default.rb
  29. 4
      app/models/attribute_help_text/project.rb
  30. 29
      app/models/capabilities/scopes/default.rb
  31. 4
      app/models/custom_actions/actions/custom_field.rb
  32. 2
      app/models/custom_actions/actions/strategies/custom_field.rb
  33. 4
      app/models/custom_actions/actions/strategies/user_custom_field.rb
  34. 18
      app/models/custom_field.rb
  35. 4
      app/models/custom_value.rb
  36. 10
      app/models/project.rb
  37. 22
      app/models/queries/filters/shared/custom_field_filter.rb
  38. 2
      app/models/queries/filters/shared/custom_fields/base.rb
  39. 2
      app/models/queries/work_packages/columns/custom_field_column.rb
  40. 2
      app/models/queries/work_packages/filter/search_filter.rb
  41. 4
      app/models/role.rb
  42. 4
      app/models/setting.rb
  43. 2
      app/models/type/attributes.rb
  44. 2
      app/models/user_preference.rb
  45. 12
      app/models/users/project_role_cache.rb
  46. 4
      app/seeders/development_data/custom_fields_seeder.rb
  47. 14
      app/services/authentication/omniauth_service.rb
  48. 2
      app/services/authorization/user_allowed_service.rb
  49. 6
      app/services/custom_fields/create_service.rb
  50. 24
      app/services/projects/update_service.rb
  51. 23
      app/services/settings/update_service.rb
  52. 98
      app/services/settings/working_days_update_service.rb
  53. 20
      app/services/users/register_user_service.rb
  54. 4
      app/views/customizable/_form.html.erb
  55. 4
      app/views/projects/_toolbar.html.erb
  56. 5
      app/views/roles/report.html.erb
  57. 36
      app/workers/notifications/create_date_alerts_notifications_job.rb
  58. 31
      app/workers/notifications/create_date_alerts_notifications_job/alertable_work_packages.rb
  59. 43
      app/workers/notifications/create_date_alerts_notifications_job/service.rb
  60. 63
      app/workers/notifications/schedule_date_alerts_notifications_job.rb
  61. 74
      app/workers/notifications/schedule_date_alerts_notifications_job/service.rb
  62. 29
      bin/compose
  63. 8
      config/application.rb
  64. 8
      config/constants/settings/definitions.rb
  65. 18
      config/initializers/cronjobs.rb
  66. 4
      config/initializers/new_framework_defaults_6_1.rb
  67. 2
      config/initializers/omniauth.rb
  68. 22
      config/initializers/permissions.rb
  69. 9
      config/locales/crowdin/af.yml
  70. 9
      config/locales/crowdin/ar.yml
  71. 9
      config/locales/crowdin/az.yml
  72. 3206
      config/locales/crowdin/be.yml
  73. 9
      config/locales/crowdin/bg.yml
  74. 9
      config/locales/crowdin/ca.yml
  75. 9
      config/locales/crowdin/ckb-IR.yml
  76. 9
      config/locales/crowdin/cs.yml
  77. 9
      config/locales/crowdin/da.yml
  78. 37
      config/locales/crowdin/de.yml
  79. 9
      config/locales/crowdin/el.yml
  80. 9
      config/locales/crowdin/eo.yml
  81. 11
      config/locales/crowdin/es.yml
  82. 9
      config/locales/crowdin/et.yml
  83. 3162
      config/locales/crowdin/eu.yml
  84. 9
      config/locales/crowdin/fa.yml
  85. 9
      config/locales/crowdin/fi.yml
  86. 9
      config/locales/crowdin/fil.yml
  87. 9
      config/locales/crowdin/fr.yml
  88. 9
      config/locales/crowdin/he.yml
  89. 9
      config/locales/crowdin/hi.yml
  90. 9
      config/locales/crowdin/hr.yml
  91. 9
      config/locales/crowdin/hu.yml
  92. 134
      config/locales/crowdin/id.yml
  93. 27
      config/locales/crowdin/it.yml
  94. 9
      config/locales/crowdin/ja.yml
  95. 3
      config/locales/crowdin/js-af.yml
  96. 3
      config/locales/crowdin/js-ar.yml
  97. 3
      config/locales/crowdin/js-az.yml
  98. 1261
      config/locales/crowdin/js-be.yml
  99. 3
      config/locales/crowdin/js-bg.yml
  100. 3
      config/locales/crowdin/js-ca.yml
  101. Some files were not shown because too many files have changed in this diff Show More

@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setup Ruby
uses: ruby/setup-ruby@v1
@ -38,6 +38,6 @@ jobs:
brakeman -i config/brakeman.ignore -f sarif -o output.sarif.json .
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v1
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: output.sarif.json

@ -24,12 +24,12 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

@ -23,7 +23,7 @@ jobs:
exit 1
fi
echo "::set-output name=branch::${BRANCH}"
echo "branch=${BRANCH}" >> $GITHUB_OUTPUT
crowdin:
permissions:

@ -25,8 +25,7 @@ jobs:
exit 1
fi
echo "::set-output name=branch::${BRANCH}"
echo "::set-output name=crowdin_release_branch::release"
echo "branch=${BRANCH}" >> $GITHUB_OUTPUT
crowdin:
permissions:
@ -42,7 +41,7 @@ jobs:
- dev
- "${{ needs.setup.outputs.latest_release_branch }}"
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
ref: ${{ matrix.branch }}
fetch-depth: 1
@ -54,9 +53,9 @@ jobs:
run: |
echo "Setting crowdin branch from $BRANCH"
if [ "$BRANCH" = "dev" ]; then
echo "::set-output name=crowdin_branch::dev"
echo "crowdin_branch=dev" >> $GITHUB_OUTPUT
else
echo "::set-output name=crowdin_branch::release"
echo "crowdin_branch=release" >> $GITHUB_OUTPUT
fi
- name: "Updating translations"
uses: crowdin/github-action@1.4.4

@ -19,7 +19,7 @@ jobs:
env:
INPUT_BUILDOPTIONS: --pull
steps:
- uses: actions/checkout@master
- uses: actions/checkout@v3
- name: Prepare docker files
run: |
cp ./docker/prod/Dockerfile ./Dockerfile

@ -14,7 +14,7 @@ jobs:
name: eslint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: opf/action-eslint@v2

@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Generate .env.pullpreview file
run: |
echo "OP_ADMIN_USER_SEEDER_FORCE_PASSWORD_CHANGE=off" >> .env.pullpreview

@ -13,7 +13,7 @@ jobs:
name: rubocop
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: ruby/setup-ruby@v1

@ -28,9 +28,9 @@ jobs:
CI_RETRY_COUNT: 3
LOCAL_DEV_CHECK: 1
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: cache
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: /tmp/cache
key: ${{ runner.os }}-ruby32-core-tests-units-${{ hashFiles('**/Gemfile.lock') }}
@ -58,9 +58,9 @@ jobs:
CAPYBARA_AWS_ACCESS_KEY_ID: "${{ secrets.CAPYBARA_AWS_ACCESS_KEY_ID }}"
CAPYBARA_AWS_SECRET_ACCESS_KEY: "${{ secrets.CAPYBARA_AWS_SECRET_ACCESS_KEY }}"
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: cache
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: /tmp/cache
key: ${{ runner.os }}-ruby32-core-tests-features-${{ hashFiles('**/Gemfile.lock') }}
@ -81,11 +81,11 @@ jobs:
if: github.repository == 'opf/openproject'
runs-on: [ubuntu-latest]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
with:
node-version: '14'
- run: ./script/api/validate_spec

@ -10,7 +10,7 @@ inherit_mode:
- Exclude
AllCops:
TargetRubyVersion: 3.1
TargetRubyVersion: 3.2
# Enable any new cops in new versions by default
NewCops: enable
Exclude:

@ -39,9 +39,7 @@ gem 'responders', '~> 3.0'
gem 'ffi', '~> 1.15'
# Lock mail at 2.7 due to incompatibility
# TODO: Remove it once the new version is fixed
gem 'mail', '~> 2.7.1'
gem 'mail', '~> 2.8.0'
gem 'rdoc', '>= 2.4.2'
@ -181,7 +179,7 @@ gem 'puma', '~> 6.0'
gem 'puma-plugin-statsd', '~> 2.0'
gem 'rack-timeout', '~> 0.6.3', require: "rack/timeout/base"
gem 'nokogiri', '~> 1.14.0.rc1'
gem 'nokogiri', '~> 1.14.0'
gem 'carrierwave', '~> 1.3.1'
gem 'carrierwave_direct', '~> 2.1.0'
@ -213,7 +211,7 @@ group :test do
# Test prof provides factories from code
# and other niceties
gem 'test-prof', '~> 1.0.0'
gem 'test-prof', '~> 1.1.0'
gem 'rack_session_access'
gem 'rspec', '~> 3.12.0'
@ -257,7 +255,7 @@ group :ldap do
end
group :development do
gem 'listen', '~> 3.7.0' # Use for event-based reloaders
gem 'listen', '~> 3.8.0' # Use for event-based reloaders
gem 'letter_opener'
@ -307,7 +305,7 @@ gem 'bootsnap', '~> 1.15.0', require: false
# API gems
gem 'grape', '~> 1.7.0'
gem 'grape_logging', '~> 1.8.4'
gem 'roar', '~> 1.1.0'
gem 'roar', '~> 1.2.0'
# CORS for API
gem 'rack-cors', '~> 1.1.1'

@ -9,8 +9,8 @@ GIT
GIT
remote: https://github.com/opf/omniauth-openid-connect.git
revision: 27d9d4a05c80a9b29709ae8681696de11156e0e5
ref: 27d9d4a05c80a9b29709ae8681696de11156e0e5
revision: 0d2cd719e87021a14dd2b5cf8a6bf1831d4a497e
ref: 0d2cd71
specs:
omniauth-openid-connect (0.4.0)
addressable (~> 2.5)
@ -169,7 +169,7 @@ PATH
remote: modules/two_factor_authentication
specs:
openproject-two_factor_authentication (1.0.0)
aws-sdk-sns (~> 1.57.0)
aws-sdk-sns (~> 1.58.0)
messagebird-rest (~> 1.4.2)
rotp (~> 6.1)
@ -188,34 +188,34 @@ GEM
remote: https://rubygems.org/
specs:
Ascii85 (1.1.0)
actioncable (7.0.4)
actionpack (= 7.0.4)
activesupport (= 7.0.4)
actioncable (7.0.4.1)
actionpack (= 7.0.4.1)
activesupport (= 7.0.4.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (7.0.4)
actionpack (= 7.0.4)
activejob (= 7.0.4)
activerecord (= 7.0.4)
activestorage (= 7.0.4)
activesupport (= 7.0.4)
actionmailbox (7.0.4.1)
actionpack (= 7.0.4.1)
activejob (= 7.0.4.1)
activerecord (= 7.0.4.1)
activestorage (= 7.0.4.1)
activesupport (= 7.0.4.1)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.0.4)
actionpack (= 7.0.4)
actionview (= 7.0.4)
activejob (= 7.0.4)
activesupport (= 7.0.4)
actionmailer (7.0.4.1)
actionpack (= 7.0.4.1)
actionview (= 7.0.4.1)
activejob (= 7.0.4.1)
activesupport (= 7.0.4.1)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
actionpack (7.0.4)
actionview (= 7.0.4)
activesupport (= 7.0.4)
actionpack (7.0.4.1)
actionview (= 7.0.4.1)
activesupport (= 7.0.4.1)
rack (~> 2.0, >= 2.2.0)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
@ -223,31 +223,31 @@ GEM
actionpack-xml_parser (2.0.1)
actionpack (>= 5.0)
railties (>= 5.0)
actiontext (7.0.4)
actionpack (= 7.0.4)
activerecord (= 7.0.4)
activestorage (= 7.0.4)
activesupport (= 7.0.4)
actiontext (7.0.4.1)
actionpack (= 7.0.4.1)
activerecord (= 7.0.4.1)
activestorage (= 7.0.4.1)
activesupport (= 7.0.4.1)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.0.4)
activesupport (= 7.0.4)
actionview (7.0.4.1)
activesupport (= 7.0.4.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (7.0.4)
activesupport (= 7.0.4)
activejob (7.0.4.1)
activesupport (= 7.0.4.1)
globalid (>= 0.3.6)
activemodel (7.0.4)
activesupport (= 7.0.4)
activemodel (7.0.4.1)
activesupport (= 7.0.4.1)
activemodel-serializers-xml (1.0.2)
activemodel (> 5.x)
activesupport (> 5.x)
builder (~> 3.1)
activerecord (7.0.4)
activemodel (= 7.0.4)
activesupport (= 7.0.4)
activerecord (7.0.4.1)
activemodel (= 7.0.4.1)
activesupport (= 7.0.4.1)
activerecord-import (1.4.1)
activerecord (>= 4.2)
activerecord-nulldb-adapter (0.8.0)
@ -258,14 +258,14 @@ GEM
multi_json (~> 1.11, >= 1.11.2)
rack (>= 2.0.8, < 3)
railties (>= 5.2.4.1)
activestorage (7.0.4)
actionpack (= 7.0.4)
activejob (= 7.0.4)
activerecord (= 7.0.4)
activesupport (= 7.0.4)
activestorage (7.0.4.1)
actionpack (= 7.0.4.1)
activejob (= 7.0.4.1)
activerecord (= 7.0.4.1)
activesupport (= 7.0.4.1)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (7.0.4)
activesupport (7.0.4.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@ -282,7 +282,7 @@ GEM
airbrake-ruby (~> 6.0)
airbrake-ruby (6.2.0)
rbtree3 (~> 0.5)
appsignal (3.3.0)
appsignal (3.3.2)
rack
ast (2.4.2)
attr_required (1.0.1)
@ -291,20 +291,20 @@ GEM
awesome_nested_set (3.5.0)
activerecord (>= 4.0.0, < 7.1)
aws-eventstream (1.2.0)
aws-partitions (1.686.0)
aws-sdk-core (3.168.4)
aws-partitions (1.697.0)
aws-sdk-core (3.169.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.61.0)
aws-sdk-kms (1.62.0)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.117.2)
aws-sdk-s3 (1.118.0)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
aws-sdk-sns (1.57.0)
aws-sdk-sns (1.58.0)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.5.2)
@ -458,13 +458,13 @@ GEM
tzinfo
eventmachine (1.2.7)
eventmachine_httpserver (0.2.1)
excon (0.95.0)
excon (0.97.2)
factory_bot (6.2.1)
activesupport (>= 5.0.0)
factory_bot_rails (6.2.0)
factory_bot (~> 6.2.0)
railties (>= 5.0.0)
faraday (1.10.2)
faraday (1.10.3)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
@ -512,16 +512,16 @@ GEM
formatador (1.1.0)
friendly_id (5.5.0)
activerecord (>= 4.0.0)
fugit (1.8.0)
fugit (1.8.1)
et-orbi (~> 1, >= 1.2.7)
raabro (~> 1.4)
fuubar (2.5.1)
rspec-core (~> 3.0)
ruby-progressbar (~> 1.4)
git (1.13.0)
git (1.13.1)
addressable (~> 2.8)
rchardet (~> 1.8)
globalid (1.0.0)
globalid (1.0.1)
activesupport (>= 5.0)
gon (6.4.0)
actionpack (>= 3.0.20)
@ -587,10 +587,10 @@ GEM
open4 (~> 1.0)
launchy (2.5.2)
addressable (~> 2.8)
lefthook (1.2.6)
lefthook (1.2.8)
letter_opener (1.8.1)
launchy (>= 2.2, < 3)
listen (3.7.1)
listen (3.8.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
livingstyleguide (2.1.0)
@ -611,8 +611,11 @@ GEM
loofah (2.19.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
mail (2.8.0.1)
mini_mime (>= 0.1.1)
net-imap
net-pop
net-smtp
marcel (1.0.2)
matrix (0.4.2)
messagebird-rest (1.4.2)
@ -648,7 +651,7 @@ GEM
netrc (0.11.0)
nio4r (2.5.8)
no_proxy_fix (0.1.2)
nokogiri (1.14.0.rc1)
nokogiri (1.14.0)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
octokit (5.6.1)
@ -676,9 +679,9 @@ GEM
activerecord (>= 5.2)
request_store (~> 1.1)
parallel (1.22.1)
parallel_tests (4.0.0)
parallel_tests (4.1.0)
parallel
parser (3.1.3.0)
parser (3.2.0.0)
ast (~> 2.4.1)
pdf-core (0.9.0)
pdf-inspector (1.3.0)
@ -704,7 +707,7 @@ GEM
prawn-table
prawn-table (0.2.2)
prawn (>= 1.3.0, < 3.0.0)
pry (0.14.1)
pry (0.14.2)
coderay (~> 1.1)
method_source (~> 1.0)
pry-byebug (3.10.1)
@ -715,7 +718,7 @@ GEM
pry-rescue (1.5.2)
interception (>= 0.5)
pry (>= 0.12.0)
psych (5.0.1)
psych (5.0.2)
stringio
public_suffix (5.0.1)
puffing-billy (3.0.4)
@ -732,7 +735,7 @@ GEM
puma (>= 5.0, < 7)
raabro (1.4.0)
racc (1.6.2)
rack (2.2.5)
rack (2.2.6.2)
rack-accept (0.4.5)
rack (>= 0.4)
rack-attack (6.6.1)
@ -755,20 +758,20 @@ GEM
rack_session_access (0.2.0)
builder (>= 2.0.0)
rack (>= 1.0.0)
rails (7.0.4)
actioncable (= 7.0.4)
actionmailbox (= 7.0.4)
actionmailer (= 7.0.4)
actionpack (= 7.0.4)
actiontext (= 7.0.4)
actionview (= 7.0.4)
activejob (= 7.0.4)
activemodel (= 7.0.4)
activerecord (= 7.0.4)
activestorage (= 7.0.4)
activesupport (= 7.0.4)
rails (7.0.4.1)
actioncable (= 7.0.4.1)
actionmailbox (= 7.0.4.1)
actionmailer (= 7.0.4.1)
actionpack (= 7.0.4.1)
actiontext (= 7.0.4.1)
actionview (= 7.0.4.1)
activejob (= 7.0.4.1)
activemodel (= 7.0.4.1)
activerecord (= 7.0.4.1)
activestorage (= 7.0.4.1)
activesupport (= 7.0.4.1)
bundler (>= 1.15.0)
railties (= 7.0.4)
railties (= 7.0.4.1)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
@ -776,14 +779,14 @@ GEM
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.4.4)
rails-html-sanitizer (1.5.0)
loofah (~> 2.19, >= 2.19.1)
rails-i18n (7.0.6)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8)
railties (7.0.4)
actionpack (= 7.0.4)
activesupport (= 7.0.4)
railties (7.0.4.1)
actionpack (= 7.0.4.1)
activesupport (= 7.0.4.1)
method_source
rake (>= 12.2)
thor (~> 1.0)
@ -800,7 +803,7 @@ GEM
recaptcha (5.12.3)
json
redcarpet (3.5.1)
regexp_parser (2.6.1)
regexp_parser (2.6.2)
reline (0.3.2)
io-console (~> 0.5)
representable (3.2.0)
@ -820,8 +823,8 @@ GEM
retriable (3.1.2)
rexml (3.2.5)
rinku (2.0.6)
roar (1.1.1)
representable (~> 3.0)
roar (1.2.0)
representable (~> 3.1)
rotp (6.2.2)
rouge (4.0.1)
rspec (3.12.0)
@ -830,10 +833,10 @@ GEM
rspec-mocks (~> 3.12.0)
rspec-core (3.12.0)
rspec-support (~> 3.12.0)
rspec-expectations (3.12.1)
rspec-expectations (3.12.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-mocks (3.12.1)
rspec-mocks (3.12.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-rails (6.0.1)
@ -847,24 +850,27 @@ GEM
rspec-retry (0.6.2)
rspec-core (> 3.3)
rspec-support (3.12.0)
rubocop (1.42.0)
rubocop (1.44.0)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.1.2.1)
parser (>= 3.2.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.24.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.24.1)
parser (>= 3.1.1.0)
rubocop-capybara (2.17.0)
rubocop (~> 1.41)
rubocop-rails (2.17.4)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
rubocop-rspec (2.16.0)
rubocop-rspec (2.18.1)
rubocop (~> 1.33)
rubocop-capybara (~> 2.17)
ruby-duration (3.2.3)
activesupport (>= 3.0.0)
i18n
@ -873,8 +879,8 @@ GEM
ruby-prof (1.4.5)
ruby-progressbar (1.11.0)
ruby-rc4 (0.1.5)
ruby-saml (1.14.0)
nokogiri (>= 1.10.5)
ruby-saml (1.15.0)
nokogiri (>= 1.13.10)
rexml
ruby2_keywords (0.0.5)
rubytree (2.0.0)
@ -905,7 +911,7 @@ GEM
activesupport (>= 5.2.0)
spreadsheet (1.3.0)
ruby-ole
spring (4.1.0)
spring (4.1.1)
spring-commands-rspec (1.0.4)
spring (>= 0.9.1)
sprockets (3.7.2)
@ -931,7 +937,7 @@ GEM
temple (0.9.1)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
test-prof (1.0.11)
test-prof (1.1.0)
thor (1.2.1)
tilt (2.0.11)
timecop (0.9.6)
@ -948,7 +954,7 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (2.3.0)
unicode-display_width (2.4.2)
uri_template (0.7.0)
validate_email (0.1.6)
activemodel (>= 3.0)
@ -1051,17 +1057,17 @@ DEPENDENCIES
launchy (~> 2.5.0)
lefthook
letter_opener
listen (~> 3.7.0)
listen (~> 3.8.0)
livingstyleguide (~> 2.1.0)
lograge (~> 0.12.0)
mail (~> 2.7.1)
mail (~> 2.8.0)
matrix (~> 0.4.2)
meta-tags (~> 2.18.0)
mini_magick (~> 4.12.0)
multi_json (~> 1.15.0)
my_page!
net-ldap (~> 0.17.0)
nokogiri (~> 1.14.0.rc1)
nokogiri (~> 1.14.0)
oj (~> 3.13.0)
okcomputer (~> 1.18.1)
omniauth!
@ -1120,7 +1126,7 @@ DEPENDENCIES
rest-client (~> 2.0)
retriable (~> 3.1.1)
rinku (~> 2.0.4)
roar (~> 1.1.0)
roar (~> 1.2.0)
rouge (~> 4.0.0)
rspec (~> 3.12.0)
rspec-rails (~> 6.0.0)
@ -1149,7 +1155,7 @@ DEPENDENCIES
svg-graph (~> 2.2.0)
sys-filesystem (~> 1.4.0)
table_print (~> 1.5.6)
test-prof (~> 1.0.0)
test-prof (~> 1.1.0)
timecop (~> 0.9.0)
typed_dag (~> 2.0.2)
tzinfo-data (~> 1.2022.1)

@ -14,7 +14,7 @@ gem 'omniauth-openid_connect-providers',
gem 'omniauth-openid-connect',
git: 'https://github.com/opf/omniauth-openid-connect.git',
ref: '27d9d4a05c80a9b29709ae8681696de11156e0e5'
ref: '0d2cd71'
group :opf_plugins do
# included so that engines can reference OpenProject::Version

@ -88,7 +88,7 @@ module Projects
def custom_field_columns
project_custom_fields.values.map do |custom_field|
[:"cf_#{custom_field.id}", { caption: custom_field.name, custom_field: true }]
[custom_field.column_name.to_sym, { caption: custom_field.name, custom_field: true }]
end
end
@ -102,7 +102,7 @@ module Projects
end
fields
.index_by { |cf| :"cf_#{cf.id}" }
.index_by { |cf| cf.column_name.to_sym }
end
end
end

@ -227,7 +227,7 @@ class BaseContract < Disposable::Twin
end
if model.respond_to?(:available_custom_fields)
writable += model.available_custom_fields.map { |cf| "custom_field_#{cf.id}" }
writable += model.available_custom_fields.map(&:attribute_name)
end
writable

@ -30,12 +30,11 @@ module RequiresAdminGuard
extend ActiveSupport::Concern
included do
validate { validate_admin_only(user, errors) }
validate :validate_admin_only
end
module_function
def validate_admin_only(user, errors)
# Adds an error if user is archived or not an admin.
def validate_admin_only
unless user.admin? && user.active?
errors.add :base, :error_unauthorized
end

@ -27,16 +27,44 @@
#++
module Projects
class ArchiveContract < ModelContract
include RequiresAdminGuard
include Projects::Archiver
class ArchiveContract < ::BaseContract
validate :validate_no_foreign_wp_references
validate :validate_has_archive_project_permission
protected
def validate_model?
false
# Check that there is no wp of a non descendant project that is assigned
# to one of the project or descendant versions
def validate_no_foreign_wp_references
version_ids = model.rolled_up_versions.select(:id)
exists = WorkPackage
.where.not(project_id: model.self_and_descendants.select(:id))
.exists?(version_id: version_ids)
errors.add :base, :foreign_wps_reference_version if exists
end
def validate_has_archive_project_permission
validate_can_archive_project
validate_can_archive_subprojects
end
def validate_can_archive_project
return if user.allowed_to?(:archive_project, model)
errors.add :base, :error_unauthorized
end
def validate_can_archive_subprojects
# prevent adding another error if there is already one present
return if errors.present?
subprojects = model.descendants
return if subprojects.empty?
return if user.allowed_to?(:archive_project, subprojects)
errors.add :base, :archive_permission_missing_on_subprojects
end
end
end

@ -30,7 +30,6 @@ module Projects
class BaseContract < ::ModelContract
include AssignableValuesContract
include AssignableCustomFieldValues
include Projects::Archiver
attribute :name
attribute :identifier
@ -90,9 +89,7 @@ module Projects
def validate_user_allowed_to_manage
with_unchanged_id do
with_active_assumed do
errors.add :base, :error_unauthorized unless user.allowed_to?(manage_permission, model)
end
errors.add :base, :error_unauthorized unless user.allowed_to?(manage_permission, model)
end
end
@ -119,26 +116,14 @@ module Projects
model.id = project_id
end
def with_active_assumed
active = model.active
model.active = true
yield
ensure
model.active = active
end
def validate_changing_active
return unless model.active_changed?
RequiresAdminGuard.validate_admin_only(user, errors)
contract_klass = model.being_archived? ? ArchiveContract : UnarchiveContract
contract = contract_klass.new(model, user)
contract.validate
if model.active?
# switched to active -> unarchiving
validate_all_ancestors_active
else
validate_no_foreign_wp_references
end
errors.merge!(contract.errors)
end
end
end

@ -27,16 +27,17 @@
#++
module Projects
class UnarchiveContract < ModelContract
class UnarchiveContract < ::BaseContract
include RequiresAdminGuard
include Projects::Archiver
validate :validate_all_ancestors_active
protected
def validate_model?
false
def validate_all_ancestors_active
if model.ancestors.any?(&:archived?)
errors.add :base, :archived_ancestor
end
end
end
end

@ -31,7 +31,13 @@ module Projects
private
def manage_permission
:edit_project
if changed_by_user == ["active"]
:archive_project
else
# if "active" is changed, :archive_project permission will also be
# checked in `Projects::BaseContract#validate_changing_active`
:edit_project
end
end
end
end

@ -36,7 +36,7 @@ module Settings
protected
def working_days_are_present
if working_days.empty?
if working_days.blank?
errors.add :base, :working_days_are_missing
end
end

@ -12,14 +12,34 @@ module Admin::Settings
true
end
def failure_callback(call)
@modified_non_working_days = call.result
flash[:error] = call.message || I18n.t(:notice_internal_server_error)
render action: 'show', tab: params[:tab]
end
protected
def settings_params
settings = super
settings[:working_days] = settings[:working_days].compact_blank.map(&:to_i).uniq
settings[:working_days] = working_days_params(settings)
settings[:non_working_days] = non_working_days_params
settings
end
def contract_options
{ params_contract: Settings::WorkingDaysParamsContract }
def update_service
::Settings::WorkingDaysUpdateService
end
private
def working_days_params(settings)
settings[:working_days] ? settings[:working_days].compact_blank.map(&:to_i).uniq : []
end
def non_working_days_params
non_working_days = params[:settings].to_unsafe_hash[:non_working_days] || {}
non_working_days.to_h.values
end
end
end

@ -50,13 +50,12 @@ module Admin
def update
return unless params[:settings]
call = ::Settings::UpdateService
.new(user: current_user, contract_options:)
call = update_service
.new(user: current_user)
.call(settings_params)
call.on_success { flash[:notice] = t(:notice_successful_update) }
call.on_failure { flash[:error] = call.message || I18n.t(:notice_internal_server_error) }
redirect_to action: 'show', tab: params[:tab]
call.on_success { success_callback(call) }
call.on_failure { failure_callback(call) }
end
def show_plugin
@ -94,8 +93,18 @@ module Admin
permitted_params.settings.to_h
end
def contract_options
{}
def update_service
::Settings::UpdateService
end
def success_callback(_call)
flash[:notice] = t(:notice_successful_update)
redirect_to action: 'show', tab: params[:tab]
end
def failure_callback(call)
flash[:error] = call.message || I18n.t(:notice_internal_server_error)
redirect_to action: 'show', tab: params[:tab]
end
end
end

@ -28,7 +28,8 @@
class Projects::ArchiveController < ApplicationController
before_action :find_project_by_project_id
before_action :require_admin
before_action :authorize, only: [:create]
before_action :require_admin, only: [:destroy]
def create
change_status_action(:archive)
@ -44,24 +45,24 @@ class Projects::ArchiveController < ApplicationController
service_call = change_status(status)
if service_call.success?
redirect_to(project_path_with_status)
redirect_to(projects_path)
else
flash[:error] = t(:"error_can_not_#{status}_project",
errors: service_call.errors.full_messages.join(', '))
redirect_back fallback_location: project_path_with_status
redirect_back fallback_location: projects_path
end
end
def change_status(status)
"Projects::#{status.to_s.camelcase}Service"
.constantize
service_class(status)
.new(user: current_user, model: @project)
.call
end
def project_path_with_status
acceptable_params = params.permit(:status).to_h.compact.select { |_, v| v.present? }
projects_path(acceptable_params)
def service_class(status)
case status
when :archive then Projects::ArchiveService
when :unarchive then Projects::UnarchiveService
end
end
end

@ -89,7 +89,7 @@ class ProjectsController < ApplicationController
flash[:error] = I18n.t('projects.delete.schedule_failed', errors: service_call.errors.full_messages.join("\n"))
end
redirect_to project_path_with_status
redirect_to projects_path
end
def destroy_info
@ -119,12 +119,6 @@ class ProjectsController < ApplicationController
@project = nil
end
def project_path_with_status
acceptable_params = params.permit(:status).to_h.compact.select { |_, v| v.present? }
projects_path(acceptable_params)
end
def load_query
@query = ParamsToQueryService.new(Project, current_user).call(params)

@ -438,25 +438,6 @@ module ApplicationHelper
"<meta name='ROBOTS' content='#{h(content)}' />".html_safe
end
#
# Returns the footer text displayed in the layout file.
#
def footer_content
elements = []
elements << I18n.t(:text_powered_by, link: link_to(OpenProject::Info.app_name,
OpenProject::Info.url))
unless OpenProject::Footer.content.nil?
OpenProject::Footer.content.each do |name, value|
content = value.respond_to?(:call) ? value.call : value
if content
elements << content_tag(:span, content, class: "footer_#{name}")
end
end
end
elements << Setting.additional_footer_content if Setting.additional_footer_content.present?
elements.join(', ').html_safe
end
def permitted_params
PermittedParams.new(params, current_user)
end

@ -67,6 +67,7 @@ module ProjectsHelper
def project_more_menu_items(project)
[project_more_menu_subproject_item(project),
project_more_menu_settings_item(project),
project_more_menu_activity_item(project),
project_more_menu_archive_item(project),
project_more_menu_unarchive_item(project),
project_more_menu_copy_item(project),
@ -91,8 +92,19 @@ module ProjectsHelper
end
end
def project_more_menu_activity_item(project)
if User.current.allowed_to?(:view_project_activity, project)
[
t(:label_project_activity),
project_activity_index_path(project, event_types: ['project_attributes']),
{ class: 'icon-context icon-checkmark',
title: t(:label_project_activity) }
]
end
end
def project_more_menu_archive_item(project)
if User.current.admin? && project.active?
if User.current.allowed_to?(:archive_project, project) && project.active?
[t(:button_archive),
project_archive_path(project, status: params[:status]),
{ data: { confirm: t('project.archive.are_you_sure', name: project.name) },
@ -103,7 +115,7 @@ module ProjectsHelper
end
def project_more_menu_unarchive_item(project)
if User.current.admin? && !project.active? && (project.parent.nil? || project.parent.active?)
if User.current.admin? && project.archived? && (project.parent.nil? || project.parent.active?)
[t(:button_unarchive),
project_archive_path(project, status: params[:status]),
{ method: :delete,

@ -32,13 +32,16 @@ module Actions::Scopes
class_methods do
def default
actions_sql = <<~SQL.squish
(SELECT id, permission, global, module, grant_to_admin
FROM (VALUES #{action_map}) AS t(id, permission, global, module, grant_to_admin)) actions
SQL
RequestStore[:action_default_scope] ||= begin
actions_sql = <<~SQL.squish
(SELECT id, permission, global, module, grant_to_admin, public
FROM (VALUES #{action_map}) AS t(id, permission, global, module, grant_to_admin, public)) actions
SQL
select('actions.*')
.from(actions_sql)
unscoped # prevent triggering the default scope again
.select('actions.*')
.from(actions_sql)
end
end
private
@ -51,14 +54,17 @@ module Actions::Scopes
.join(', ')
end
def map_actions(permission, actions:, global:, module_name:, grant_to_admin:)
def map_actions(permission, actions:, global:, module_name:, grant_to_admin:, public:)
actions.map do |namespace, actions|
actions.map do |action|
values = [quote_string("#{action_v3_name(namespace)}/#{action}"),
quote_string(permission),
global,
module_name ? quote_string(module_name) : 'NULL',
grant_to_admin].join(', ')
values = [
quote_string("#{action_v3_name(namespace)}/#{action}"),
quote_string(permission),
global,
module_name ? quote_string(module_name) : 'NULL',
grant_to_admin,
public
].join(', ')
"(#{values})"
end

@ -35,8 +35,8 @@ class AttributeHelpText::Project < AttributeHelpText
.reject { |key, _| skip.include?(key.to_s) }
.transform_values { |definition| definition[:name_source].call }
ProjectCustomField.all.find_each do |field|
attributes["custom_field_#{field.id}"] = field.name
ProjectCustomField.find_each do |field|
attributes[field.attribute_name] = field.name
end
attributes

@ -46,21 +46,22 @@ module Capabilities::Scopes
) capabilities
SQL
select('capabilities.*')
unscoped # prevent triggering the default scope again
.select('capabilities.*')
.from(capabilities_sql)
end
private
def default_sql_by_member
<<~SQL.squish
<<~SQL_PART
SELECT DISTINCT
actions.id "action",
users.id principal_id,
projects.id context_id
FROM (#{Action.default.to_sql}) actions
LEFT OUTER JOIN "role_permissions" ON "role_permissions"."permission" = "actions"."permission"
LEFT OUTER JOIN "roles" ON "roles".id = "role_permissions".role_id
LEFT OUTER JOIN "roles" ON "roles".id = "role_permissions".role_id OR "actions"."public"
LEFT OUTER JOIN "member_roles" ON "member_roles".role_id = "roles".id
LEFT OUTER JOIN "members" ON members.id = member_roles.member_id
JOIN (#{Principal.visible.not_builtin.not_locked.to_sql}) users
@ -72,11 +73,11 @@ module Capabilities::Scopes
AND actions.module = enabled_modules.name
WHERE (projects.active AND (enabled_modules.project_id IS NOT NULL OR "actions".module IS NULL))
OR (projects.id IS NULL AND "actions".global)
SQL
SQL_PART
end
def default_sql_by_admin
<<~SQL.squish
<<~SQL_PART
SELECT DISTINCT
actions.id "action",
users.id principal_id,
@ -91,18 +92,18 @@ module Capabilities::Scopes
ON enabled_modules.project_id = projects.id
AND actions.module = enabled_modules.name
WHERE (projects.id IS NOT NULL AND (enabled_modules.project_id IS NOT NULL OR "actions".module IS NULL)) OR "actions".global
SQL
SQL_PART
end
def default_sql_by_non_member
<<~SQL.squish
<<~SQL_PART
SELECT DISTINCT
actions.id "action",
users.id principal_id,
projects.id context_id
FROM (#{Action.default.to_sql}) actions
JOIN "role_permissions" ON "role_permissions"."permission" = "actions"."permission"
JOIN "roles" ON "roles".id = "role_permissions".role_id AND roles.builtin = #{Role::BUILTIN_NON_MEMBER}
LEFT JOIN "role_permissions" ON "role_permissions"."permission" = "actions"."permission"
JOIN "roles" ON ("roles".id = "role_permissions".role_id OR "actions"."public") AND roles.builtin = #{Role::BUILTIN_NON_MEMBER}
JOIN (#{Principal.visible.not_builtin.not_locked.to_sql}) users
ON 1 = 1
JOIN "projects"
@ -117,18 +118,18 @@ module Capabilities::Scopes
AND actions.module = enabled_modules.name
WHERE enabled_modules.project_id IS NOT NULL OR "actions".module IS NULL
SQL
SQL_PART
end
def default_sql_by_non_member_with_anonymous
<<~SQL.squish
<<~SQL_PART
SELECT DISTINCT
actions.id "action",
users.id principal_id,
projects.id context_id
FROM (#{Action.default.to_sql}) actions
JOIN "role_permissions" ON "role_permissions"."permission" = "actions"."permission"
JOIN "roles" ON "roles".id = "role_permissions".role_id AND roles.builtin = #{Role::BUILTIN_ANONYMOUS}
LEFT JOIN "role_permissions" ON "role_permissions"."permission" = "actions"."permission"
JOIN "roles" ON ("roles".id = "role_permissions".role_id OR "actions"."public") AND roles.builtin = #{Role::BUILTIN_ANONYMOUS}
JOIN users ON users.type = '#{AnonymousUser.name}'
JOIN "projects"
ON "projects".active = true
@ -138,7 +139,7 @@ module Capabilities::Scopes
AND actions.module = enabled_modules.name
WHERE enabled_modules.project_id IS NOT NULL OR "actions".module IS NULL
SQL
SQL_PART
end
end
end

@ -28,7 +28,7 @@
class CustomActions::Actions::CustomField < CustomActions::Actions::Base
def self.key
:"custom_field_#{custom_field.id}"
custom_field.attribute_name.to_sym
end
def self.custom_field
@ -44,7 +44,7 @@ class CustomActions::Actions::CustomField < CustomActions::Actions::Base
end
def apply(work_package)
work_package.send(:"#{custom_field.accessor_name}=", values) if work_package.respond_to?(:"#{custom_field.accessor_name}=")
work_package.send(custom_field.attribute_setter, values) if work_package.respond_to?(custom_field.attribute_setter)
end
def self.all

@ -28,7 +28,7 @@
module CustomActions::Actions::Strategies::CustomField
def apply(work_package)
work_package.send(:"#{custom_field.accessor_name}=", values) if work_package.respond_to?(:"#{custom_field.accessor_name}=")
work_package.send(custom_field.attribute_setter, values) if work_package.respond_to?(custom_field.attribute_setter)
end
delegate :required?, to: :custom_field

@ -31,8 +31,8 @@ module CustomActions::Actions::Strategies::UserCustomField
include ::CustomActions::Actions::Strategies::MeAssociated
def apply(work_package)
if work_package.respond_to?(:"#{custom_field.accessor_name}=")
work_package.send(:"#{custom_field.accessor_name}=", transformed_value(values.first))
if work_package.respond_to?(custom_field.attribute_setter)
work_package.send(custom_field.attribute_setter, transformed_value(values.first))
end
end

@ -232,10 +232,24 @@ class CustomField < ApplicationRecord
where(is_filter: true)
end
def accessor_name
def attribute_name(format = nil)
return "customField#{id}" if format == :camel_case
"custom_field_#{id}"
end
def attribute_getter
attribute_name.to_sym
end
def attribute_setter
:"#{attribute_name}="
end
def column_name
"cf_#{id}"
end
def type_name
nil
end
@ -325,7 +339,7 @@ class CustomField < ApplicationRecord
def destroy_help_text
AttributeHelpText
.where(attribute_name: "custom_field_#{id}")
.where(attribute_name:)
.destroy_all
end
end

@ -61,6 +61,10 @@ class CustomValue < ApplicationRecord
end
end
def default?
value == custom_field.default_value
end
protected
def validate_presence_of_required_value

@ -162,6 +162,10 @@ class Project < ApplicationRecord
!active?
end
def being_archived?
(active == false) && (active_was == true)
end
def copy_allowed?
User.current.allowed_to?(:copy_projects, self)
end
@ -403,9 +407,9 @@ class Project < ApplicationRecord
end
def allowed_actions
@actions_allowed ||= allowed_permissions
.map { |permission| OpenProject::AccessControl.allowed_actions(permission) }
.flatten
@allowed_actions ||= allowed_permissions.flat_map do |permission|
OpenProject::AccessControl.allowed_actions(permission)
end
end
def remove_white_spaces_from_project_name

@ -39,23 +39,13 @@ module Queries::Filters::Shared::CustomFieldFilter
/cf_(\d+)/
end
##
# TODO this differs from CustomField#accessor_name for reasons I don't see,
# however this name will be persisted in queries so we can't just map one to the other.
def custom_field_accessor(custom_field)
"cf_#{custom_field.id}"
end
def all_for(context = nil)
custom_field_context.custom_fields(context).map do |cf|
cf_accessor = custom_field_accessor(cf)
begin
create!(name: cf_accessor, custom_field: cf, context:)
rescue ::Queries::Filters::InvalidError
Rails.logger.error "Failed to map custom field filter for #{cf_accessor} (CF##{cf.id}."
nil
end
end.compact
custom_field_context.custom_fields(context).filter_map do |cf|
create!(name: cf.column_name, custom_field: cf, context:)
rescue ::Queries::Filters::InvalidError
Rails.logger.error "Failed to map custom field filter for #{cf.column_name} (CF##{cf.id})."
nil
end
end
##

@ -34,7 +34,7 @@ module Queries::Filters::Shared
attr_reader :custom_field, :custom_field_context
def initialize(custom_field:, custom_field_context:, **options)
name = :"cf_#{custom_field.id}"
name = custom_field.column_name.to_sym
@custom_field = custom_field
@custom_field_context = custom_field_context

@ -69,7 +69,7 @@ class Queries::WorkPackages::Columns::CustomFieldColumn < Queries::WorkPackages:
private
def set_name!
self.name = "cf_#{custom_field.id}".to_sym
self.name = custom_field.column_name.to_sym
end
def set_sortable!

@ -91,7 +91,7 @@ class Queries::WorkPackages::Filter::SearchFilter <
custom_fields.map do |custom_field|
Queries::WorkPackages::Filter::FilterConfiguration.new(
Queries::WorkPackages::Filter::CustomFieldFilter,
"cf_#{custom_field.id}",
custom_field.column_name,
CONTAINS_OPERATOR
)
end

@ -183,9 +183,9 @@ class Role < ApplicationRecord
end
def allowed_actions
@actions_allowed ||= allowed_permissions.map do |permission|
@allowed_actions ||= allowed_permissions.flat_map do |permission|
OpenProject::AccessControl.allowed_actions(permission)
end.flatten
end
end
def check_deletable

@ -27,7 +27,6 @@
#++
class Setting < ApplicationRecord
extend CallbacksHelper
extend Aliases
extend MailSettings
@ -213,9 +212,6 @@ class Setting < ApplicationRecord
# Delete the cache
clear_cache(old_cache_key)
# fire callbacks for name and pass as much information as possible
fire_callbacks(name, new_value, old_value)
new_value
else
old_value

@ -144,7 +144,7 @@ module Type::Attributes
def add_custom_fields_to_form_attributes(attributes)
WorkPackageCustomField.includes(:custom_options).all.find_each do |field|
attributes["custom_field_#{field.id}"] = {
attributes[field.attribute_name] = {
required: field.is_required,
has_default: field.default_value.present?,
is_cf: true,

@ -132,7 +132,7 @@ class UserPreference < ApplicationRecord
end
def immediate_reminders
super.presence || { mentioned: false }.with_indifferent_access
super.presence || { mentioned: true }.with_indifferent_access
end
def pause_reminders

@ -40,8 +40,9 @@ class Users::ProjectRoleCache
private
def roles(project)
# No role on archived projects
return [] unless !project || project&.active?
# Project is nil if checking global role
# No roles on archived projects, unless the active state is being changed
return [] if project && archived?(project)
# Return all roles if user is admin
return all_givable_roles if user.admin?
@ -56,4 +57,11 @@ class Users::ProjectRoleCache
def all_givable_roles
@all_givable_roles ||= Role.givable.to_a
end
def archived?(project)
# project for which activity is being changed is still considered active
return false if project.being_archived?
project.archived?
end
end

@ -44,14 +44,14 @@ module DevelopmentData
def create_types!(cfs)
# Create ALL CFs types
non_req_cfs = cfs.reject(&:is_required).map { |cf| "custom_field_#{cf.id}" }
non_req_cfs = cfs.reject(&:is_required).map(&:attribute_name)
type = FactoryBot.build :type, name: 'All CFS'
extend_group(type, ['Custom fields', non_req_cfs])
type.save!
print_status '.'
# Create type
req_cfs = cfs.select(&:is_required).map { |cf| "custom_field_#{cf.id}" }
req_cfs = cfs.select(&:is_required).map(&:attribute_name)
type_req = FactoryBot.build :type, name: 'Required CF'
extend_group(type_req, ['Custom fields', req_cfs])
type_req.save!

@ -191,7 +191,7 @@ module Authentication
end
def activate_user!
if user.new_record? || user.invited?
if activatable?
::Users::RegisterUserService
.new(user)
.call
@ -200,6 +200,16 @@ module Authentication
end
end
##
# Determines if the given user is activatable on the fly, that is:
#
# 1. The user has just been initialized by us
# 2. The user has been invited
# 3. The user had been registered manually (e.g., through a previous self-registration setting)
def activatable?
user.new_record? || user.invited? || user.registered?
end
##
# Maps the omniauth attribute hash
# to our internal user attributes
@ -221,7 +231,7 @@ module Authentication
# Remove any nil values to avoid
# overriding existing attributes
attribute_map.compact!
attribute_map.compact_blank!
Rails.logger.debug { "Mapped auth_hash user attributes #{attribute_map.inspect}" }
attribute_map

@ -79,7 +79,7 @@ class Authorization::UserAllowedService
end
# No action allowed on archived projects
return false unless project.active?
return false unless project.active? || project.being_archived?
# No action allowed on disabled modules
return false unless project.allows_to?(action)
# Inactive users are never authorized

@ -55,7 +55,7 @@ module CustomFields
cf = call.result
if cf.is_a?(ProjectCustomField)
add_cf_to_visible_columns(cf.id)
add_cf_to_visible_columns(cf)
end
call
@ -63,8 +63,8 @@ module CustomFields
private
def add_cf_to_visible_columns(id)
Setting.enabled_projects_columns = (Setting.enabled_projects_columns + ["cf_#{id}"]).uniq
def add_cf_to_visible_columns(custom_field)
Setting.enabled_projects_columns = (Setting.enabled_projects_columns + [custom_field.column_name]).uniq
end
end
end

@ -90,17 +90,19 @@ module Projects
def handle_archiving
return unless model.saved_change_to_active?
if model.active?
# was unarchived
Projects::UnarchiveService
.new(user:, model:)
.call
else
# as archived
Projects::ArchiveService
.new(user:, model:)
.call
end
service_class =
if model.active?
# was unarchived
Projects::UnarchiveService
else
# was archived
Projects::ArchiveService
end
# EmptyContract is used because archive/unarchive conditions have
# already been checked in Projects::UpdateContract
service = service_class.new(user:, model:, contract_class: EmptyContract)
service.call
end
end
end

@ -26,30 +26,23 @@
# See COPYRIGHT and LICENSE files for more details.
#++
class Settings::UpdateService < ::BaseServices::BaseContracted
def initialize(user:, contract_options: {})
class Settings::UpdateService < BaseServices::BaseContracted
def initialize(user:)
super user:,
contract_options:,
contract_class: Settings::UpdateContract
end
def validate_params(params)
if contract_options[:params_contract]
contract = contract_options[:params_contract].new(model, user, params:)
ServiceResult.new success: contract.valid?,
errors: contract.errors,
result: model
else
super
end
def after_validate(params, call)
params.keys.each(&method(:remember_previous_value))
call
end
def after_validate(params, call)
# We will have a problem with error handling on the form.
# How can we still display the user changed values in case the form is not successfully saved?
def persist(call)
params.each do |name, value|
remember_previous_value(name)
set_setting_value(name, value)
end
call
end

@ -0,0 +1,98 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2022 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.
#++
class Settings::WorkingDaysUpdateService < Settings::UpdateService
def call(params)
params = params.to_h.deep_symbolize_keys
self.non_working_days_params = params.delete(:non_working_days) || []
super
end
def validate_params(params)
contract = Settings::WorkingDaysParamsContract.new(model, user, params:)
ServiceResult.new success: contract.valid?,
errors: contract.errors,
result: model
end
def persist(call)
results = call
ActiveRecord::Base.transaction do
# The order of merging the service is important to preserve
# the errors model's base object, which is a NonWorkingDay
results = persist_non_working_days
results.merge!(super) if results.success?
raise ActiveRecord::Rollback if results.failure?
end
results
end
private
attr_accessor :non_working_days_params
def persist_non_working_days
# We don't support update for now
to_create, to_delete = attributes_to_create_and_delete
results = destroy_records(to_delete)
create_results = create_records(to_create)
results.merge!(create_results)
results.result = Array(results.result) + Array(create_results.result)
results
end
def attributes_to_create_and_delete
non_working_days_params.reduce([[], []]) do |results, nwd|
results.first << nwd if !nwd[:id]
results.last << nwd[:id] if nwd[:_destroy] && nwd[:id]
results
end
end
def create_records(attributes)
wrap_result(attributes.map { |attrs| NonWorkingDay.create(attrs) })
end
def destroy_records(ids)
wrap_result NonWorkingDay.where(id: ids).destroy_all
end
def wrap_result(result)
model = NonWorkingDay.new
errors = model.errors.tap do |err|
result.each do |r|
err.merge!(r.errors)
end
end
success = model.errors.empty?
ServiceResult.new(success:, errors:, result:)
end
end

@ -39,6 +39,7 @@ module Users
ensure_user_limit_not_reached!
register_invited_user
register_ldap_user
register_omniauth_user
ensure_registration_allowed!
register_by_email_activation
register_automatically
@ -88,14 +89,27 @@ module Users
##
# Try to register a user with an auth source connection
# bypassing regular restrictions
# bypassing regular account registration restrictions
def register_ldap_user
return unless user.auth_source_id.present?
return if user.auth_source_id.blank?
user.activate
with_saved_user_result(success_message: I18n.t(:notice_account_registered_and_logged_in)) do
Rails.logger.info { "User #{user.login} was successfully activated after invitation." }
Rails.logger.info { "User #{user.login} was successfully activated with LDAP association after invitation." }
end
end
##
# Try to register a user with an existsing omniauth connection
# bypassing regular account registration restrictions
def register_omniauth_user
return if user.identity_url.blank?
user.activate
with_saved_user_result(success_message: I18n.t(:notice_account_registered_and_logged_in)) do
Rails.logger.info { "User #{user.login} was successfully activated after arriving from omniauth." }
end
end

@ -45,7 +45,7 @@ See COPYRIGHT and LICENSE files for more details.
<% show_required = custom_field.is_required? && !custom_field.boolean? %>
<%= content_tag :div,
class: ['form--field',
"custom_field_#{custom_field.id}",
custom_field.attribute_name,
(show_required ? '-required' : ''),
wide_labels] do
options = {
@ -53,7 +53,7 @@ See COPYRIGHT and LICENSE files for more details.
}
if help_text
options[:help_text] = help_text.merge(attribute: "customField#{custom_field.id}")
options[:help_text] = help_text.merge(attribute: custom_field.attribute_name(:camel_case))
end
cf_form.cf_form_field(options)

@ -53,7 +53,7 @@ See COPYRIGHT and LICENSE files for more details.
<% end %>
</li>
<% end %>
<% if User.current.admin? %>
<% if User.current.allowed_to?(:archive_project, @project) %>
<li class="toolbar-item hidden-for-mobile">
<%= link_to(project_archive_path(@project, status: '', name: @project.name),
data: { confirm: t('project.archive.are_you_sure', name: @project.name) },
@ -64,6 +64,8 @@ See COPYRIGHT and LICENSE files for more details.
<span class="button--text"><%= t(:button_archive) %></span>
<% end %>
</li>
<% end %>
<% if User.current.admin? %>
<li class="toolbar-item hidden-for-mobile">
<% label = @project.templated ? 'remove_from_templates' : 'make_template' %>
<%= link_to(project_templated_path(@project),

@ -39,13 +39,14 @@ See COPYRIGHT and LICENSE files for more details.
<% group_permissions_by_module(@permissions).each do |mod, mod_permissions| %>
<% module_name = mod.blank? ? "form--" + I18n.t('attributes.project') : "form--" + l_or_humanize(mod, prefix: 'project_module_').gsub(' ','_') %>
<fieldset class="form--fieldset -collapsible" id= "<%= module_name %>">
<% escaped_name = module_name.parameterize %>
<fieldset class="form--fieldset -collapsible" id= "<%= escaped_name %>">
<legend class="form--fieldset-legend" >
<%= mod.blank? ? I18n.t('attributes.project') : l_or_humanize(mod, prefix: 'project_module_') %>
</legend>
<div class="form--toolbar">
<span class="form--toolbar-item">
(<%= check_all_links module_name %>)
(<%= check_all_links escaped_name %>)
</span>
</div>

@ -23,41 +23,17 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
# See COPYRIGHT and LICENSE files for more details.
#++
module Notifications
# Creates date alerts for users whose local time is 1:00 am.
class CreateDateAlertsNotificationsJob < Cron::CronJob
# runs every quarter of an hour, so 00:00, 00:15,..., 15:30, 15:45, 16:00, ...
self.cron_expression = '*/15 * * * *'
def perform
class CreateDateAlertsNotificationsJob < ApplicationJob
def perform(user)
return unless EnterpriseToken.allows_to?(:date_alerts)
service = Service.new(times_from_scheduled_to_execution)
service.call
end
# Returns times from scheduled execution time to current time in 15 minutes
# steps.
#
# As scheduled execution time can be different from current time by more
# than 15 minutes when workers are busy, all times at 15 minutes interval
# between scheduled time and current time need to be considered to match
# with 1:00am in a time zone.
def times_from_scheduled_to_execution
time = scheduled_time
times = []
begin
times << time
time += 15.minutes
end while time < Time.current
times
end
def scheduled_time
self.class.delayed_job.run_at.then { |t| t.change(min: t.min / 15 * 15) }
Service
.new(user)
.call
end
end
end

@ -23,7 +23,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
# See COPYRIGHT and LICENSE files for more details.
#++
class Notifications::CreateDateAlertsNotificationsJob::AlertableWorkPackages
@ -34,26 +34,27 @@ class Notifications::CreateDateAlertsNotificationsJob::AlertableWorkPackages
end
def alertable_for_start
find_alertables
.filter_map { |row| row["id"] if row["start_alert"] }
.then { |ids| WorkPackage.where(id: ids) }
alertable_for("start_alert")
end
def alertable_for_due
find_alertables
.filter_map { |row| row["id"] if row["due_alert"] }
.then { |ids| WorkPackage.where(id: ids) }
alertable_for("due_alert")
end
private
def alertable_for(alert)
find_alertables
.filter_map { |row| row["id"] if row[alert] }
.then { |ids| WorkPackage.where(id: ids) }
end
def find_alertables
@find_alertables ||= ActiveRecord::Base.connection.execute(query).to_a
end
def query
today = Arel::Nodes::build_quoted(Date.current).to_sql
alertable_durations = Arel::Nodes::Grouping.new(UserPreferences::ParamsContract::DATE_ALERT_DURATIONS.compact).to_sql
alertables = alertable_work_packages
.select(:id,
@ -61,9 +62,9 @@ class Notifications::CreateDateAlertsNotificationsJob::AlertableWorkPackages
"work_packages.start_date - #{today} AS start_delta",
"work_packages.due_date - #{today} AS due_delta",
"#{today} - work_packages.due_date AS overdue_delta")
.where("work_packages.start_date - #{today} IN #{alertable_durations} " \
"OR work_packages.due_date - #{today} IN #{alertable_durations} " \
"OR #{today} - work_packages.due_date > 0")
.where("work_packages.start_date IN #{alertable_dates} " \
"OR work_packages.due_date IN #{alertable_dates} " \
"OR work_packages.due_date < #{today}")
<<~SQL.squish
WITH
@ -123,4 +124,12 @@ class Notifications::CreateDateAlertsNotificationsJob::AlertableWorkPackages
join_dependency = work_packages.construct_join_dependency([:status], Arel::Nodes::OuterJoin)
work_packages.joins(join_dependency)
end
def alertable_dates
dates = UserPreferences::ParamsContract::DATE_ALERT_DURATIONS
.compact
.map { |offset| Arel::Nodes::build_quoted(Date.current + offset.days) }
Arel::Nodes::Grouping.new(dates).to_sql
end
end

@ -26,36 +26,23 @@
# See COPYRIGHT and LICENSE files for more details.
#++
# Creates date alerts notifications for users whose local time is 1am for the
# given run_times.
class Notifications::CreateDateAlertsNotificationsJob::Service
attr_reader :run_times
# @param run_times [Array<DateTime>] the times for which the service is run.
# Must be multiple of 15 minutes (xx:00, xx:15, xx:30, or xx:45).
def initialize(run_times)
@run_times = run_times
def initialize(user)
@user = user
end
def call
return unless EnterpriseToken.allows_to?(:date_alerts)
time_zones = time_zones_covering_1am_local_time
return if time_zones.empty?
# warning: there may be a subtle bug here: if many run_times are given, time
# zones will have different time shifting. This should be ok: as the period
# covered is small this should not have any impact. If the period is more
# than 23h, then the day will change.
Time.use_zone(time_zones.first) do
User.with_time_zone(time_zones).find_each do |user|
send_date_alert_notifications(user)
end
Time.use_zone(user.time_zone) do
send_date_alert_notifications(user)
end
end
private
attr_accessor :user
def send_date_alert_notifications(user)
alertables = Notifications::CreateDateAlertsNotificationsJob::AlertableWorkPackages.new(user)
create_date_alert_notifications(user, alertables.alertable_for_start, :date_alert_start_date)
@ -70,6 +57,8 @@ class Notifications::CreateDateAlertsNotificationsJob::Service
end
def mark_previous_notifications_as_read(user, work_packages, reason)
return if work_packages.empty?
Notification
.where(recipient: user,
reason:,
@ -86,20 +75,4 @@ class Notifications::CreateDateAlertsNotificationsJob::Service
reason:
)
end
def time_zones_covering_1am_local_time
UserPreferences::UpdateContract
.assignable_time_zones
.select { |time_zone| executing_at_1am_for_timezone?(time_zone) }
.map { |time_zone| time_zone.tzinfo.canonical_zone.name }
end
def executing_at_1am_for_timezone?(time_zone)
run_times.any? { |time| is_1am?(time, time_zone) }
end
def is_1am?(time, time_zone)
local_time = time.in_time_zone(time_zone)
local_time.strftime('%H:%M') == '01:00'
end
end

@ -0,0 +1,63 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2023 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 docs/COPYRIGHT.rdoc for more details.
#++
module Notifications
# Creates date alert jobs for users whose local time is 1:00 am.
class ScheduleDateAlertsNotificationsJob < Cron::CronJob
# runs every quarter of an hour, so 00:00, 00:15,..., 15:30, 15:45, 16:00, ...
self.cron_expression = '*/15 * * * *'
def perform
return unless EnterpriseToken.allows_to?(:date_alerts)
service = Service.new(times_from_scheduled_to_execution)
service.call
end
# Returns times from scheduled execution time to current time in 15 minutes
# steps.
#
# As scheduled execution time can be different from current time by more
# than 15 minutes when workers are busy, all times at 15 minutes interval
# between scheduled time and current time need to be considered to match
# with 1:00am in a time zone.
def times_from_scheduled_to_execution
time = scheduled_time
times = []
begin
times << time
time += 15.minutes
end while time < Time.current
times
end
def scheduled_time
self.class.delayed_job.run_at.then { |t| t.change(min: t.min / 15 * 15) }
end
end
end

@ -0,0 +1,74 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2023 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.
#++
# Creates date alerts notifications for users whose local time is 1am for the
# given run_times.
class Notifications::ScheduleDateAlertsNotificationsJob::Service
attr_reader :run_times
# @param run_times [Array<DateTime>] the times for which the service is run.
# Must be multiple of 15 minutes (xx:00, xx:15, xx:30, or xx:45).
def initialize(run_times)
@run_times = run_times
end
def call
return unless EnterpriseToken.allows_to?(:date_alerts)
users_at_1am_with_notification_settings.find_each do |user|
Notifications::CreateDateAlertsNotificationsJob.perform_later(user)
end
end
private
def time_zones_covering_1am_local_time
UserPreferences::UpdateContract
.assignable_time_zones
.select { |time_zone| executing_at_1am_for_timezone?(time_zone) }
.map { |time_zone| time_zone.tzinfo.canonical_zone.name }
end
def executing_at_1am_for_timezone?(time_zone)
run_times.any? { |time| is_1am?(time, time_zone) }
end
def is_1am?(time, time_zone)
local_time = time.in_time_zone(time_zone)
local_time.strftime('%H:%M') == '01:00'
end
def users_at_1am_with_notification_settings
User
.with_time_zone(time_zones_covering_1am_local_time)
.not_locked
.where("EXISTS (SELECT 1 FROM notification_settings " \
"WHERE user_id = users.id AND " \
"(overdue IS NOT NULL OR start_date IS NOT NULL OR due_date IS NOT NULL))")
end
end

@ -36,24 +36,31 @@ if [ -f config/database.yml ]; then
exit 1
fi
if command -v docker-compose &> /dev/null
then
DOCKER_COMPOSE="docker-compose"
else
DOCKER_COMPOSE="docker compose"
fi
if [[ "$@" = "start" ]]; then
# backend will be started automatically as a dependency of the frontend
docker-compose -f $COMPOSE_FILE up -d frontend
$DOCKER_COMPOSE -f $COMPOSE_FILE up -d frontend
elif [[ "$@" = "run" ]]; then
docker-compose -f $COMPOSE_FILE up -d frontend
docker-compose -f $COMPOSE_FILE stop backend
docker-compose -f $COMPOSE_FILE run --rm backend rm -f tmp/pids/server.pid # delete if necessary so new server can come up
docker-compose -f $COMPOSE_FILE run --rm -p ${PORT:-3000}:3000 --name rails backend # run backend in TTY so you can debug using pry for instance
$DOCKER_COMPOSE -f $COMPOSE_FILE up -d frontend
$DOCKER_COMPOSE -f $COMPOSE_FILE stop backend
$DOCKER_COMPOSE -f $COMPOSE_FILE run --rm backend rm -f tmp/pids/server.pid # delete if necessary so new server can come up
$DOCKER_COMPOSE -f $COMPOSE_FILE run --rm -p ${PORT:-3000}:3000 --name rails backend # run backend in TTY so you can debug using pry for instance
elif [[ "$1" = "setup" ]]; then
docker-compose -f $COMPOSE_FILE run backend setup
yes no | docker-compose -f $COMPOSE_FILE run frontend npm install
$DOCKER_COMPOSE -f $COMPOSE_FILE run backend setup
yes no | $DOCKER_COMPOSE -f $COMPOSE_FILE run frontend npm install
elif [[ "$1" = "reset" ]]; then
docker-compose -f $COMPOSE_FILE down && docker volume rm `docker volume ls -q | grep ${PWD##*/}_`
$DOCKER_COMPOSE -f $COMPOSE_FILE down && docker volume rm `docker volume ls -q | grep ${PWD##*/}_`
elif [[ "$1" = "rspec" ]]; then
if ! docker ps | grep ${PWD##*/}_backend-test_1 > /dev/null; then
echo "Test backend not running yet. Starting it..."
docker-compose -f $COMPOSE_FILE up -d backend-test
$DOCKER_COMPOSE -f $COMPOSE_FILE up -d backend-test
while ! docker logs --since 1m ${PWD##*/}_backend-test_1 | grep "Ready for tests" > /dev/null; do
sleep 1
@ -63,7 +70,7 @@ elif [[ "$1" = "rspec" ]]; then
echo "Ready for tests"
fi
docker-compose -f $COMPOSE_FILE exec backend-test bundle exec rspec "${@:2}"
$DOCKER_COMPOSE -f $COMPOSE_FILE exec backend-test bundle exec rspec "${@:2}"
else
docker-compose -f $COMPOSE_FILE $*
$DOCKER_COMPOSE -f $COMPOSE_FILE $*
fi

@ -64,6 +64,14 @@ module OpenProject
# files. See https://community.openproject.org/wp/45463 for details.
# config.load_defaults 5.0
# Use new connection handling API. For most applications this won't have any
# effect. For applications using multiple databases, this new API provides
# support for granular connection swapping.
# It has to be done here to prevent having the deprecation warning
# displayed. This line and its comment can safely be removed
# once `config.load_defaults 6.1` is used.
config.active_record.legacy_connection_handling = false
# Sets up logging for STDOUT and configures the default logger formatter
# so that all environments receive level and timestamp information
#

@ -37,10 +37,6 @@ Settings::Definition.define do
add :activity_days_default,
default: 30
add :additional_footer_content,
format: :string,
default: nil
add :after_first_login_redirect_url,
format: :string,
default: nil,
@ -529,7 +525,7 @@ Settings::Definition.define do
writable: true
add :log_level,
default: 'info',
default: Rails.env.development? ? 'debug' : 'info',
writable: false
add :log_requesting_user,
@ -582,7 +578,7 @@ Settings::Definition.define do
allowed: -> { Role.pluck(:id) }
add :oauth_allow_remapping_of_existing_users,
default: false
default: true
add :omniauth_direct_login_provider,
format: :string,

@ -2,14 +2,14 @@
OpenProject::Application.configure do |application|
application.config.to_prepare do
::Cron::CronJob.register! ::Cron::ClearOldSessionsJob,
::Cron::ClearTmpCacheJob,
::Cron::ClearUploadedFilesJob,
::OAuth::CleanupJob,
::PaperTrailAudits::CleanupJob,
::Attachments::CleanupUncontaineredJob,
::Notifications::CreateDateAlertsNotificationsJob,
::Notifications::ScheduleReminderMailsJob,
::Ldap::SynchronizationJob
Cron::CronJob.register! Cron::ClearOldSessionsJob,
Cron::ClearTmpCacheJob,
Cron::ClearUploadedFilesJob,
OAuth::CleanupJob,
PaperTrailAudits::CleanupJob,
Attachments::CleanupUncontaineredJob,
Notifications::ScheduleDateAlertsNotificationsJob,
Notifications::ScheduleReminderMailsJob,
Ldap::SynchronizationJob
end
end

@ -57,8 +57,8 @@
# Use new connection handling API. For most applications this won't have any
# effect. For applications using multiple databases, this new API provides
# support for granular connection swapping.
# Previous versions had false. Rails 6.1+ default is true.
# Rails.application.config.active_record.legacy_connection_handling = false
# Previous versions had true. Rails 6.1+ default is false.
Rails.application.config.active_record.legacy_connection_handling = false
# https://guides.rubyonrails.org/configuring.html#config-action-view-form-with-generates-remote-forms
# Make `form_with` generate non-remote forms by default.

@ -26,6 +26,8 @@
# See COPYRIGHT and LICENSE files for more details.
#++
OmniAuth.config.logger = Rails.logger
Rails.application.config.middleware.use OmniAuth::Builder do
unless Rails.env.production?
provider :developer, fields: %i[first_name last_name email]

@ -35,11 +35,17 @@ Rails.application.reloader.to_prepare do
global: true,
contract_actions: { projects: %i[create] }
map.permission :archive_project,
{
'projects/archive': %i[create]
},
require: :member
map.permission :create_backup,
{
admin: %i[index],
'admin/backups': %i[delete_token perform_token_reset reset_token show]
},
admin: %i[index],
'admin/backups': %i[delete_token perform_token_reset reset_token show]
},
require: :loggedin,
global: true,
enabled: -> { OpenProject::Configuration.backup_enabled? }
@ -65,8 +71,7 @@ Rails.application.reloader.to_prepare do
contract_actions: { placeholder_users: %i[create read update] }
map.permission :view_project,
{ projects: [:show],
activities: [:index] },
{ projects: [:show] },
public: true
map.permission :search_project,
@ -356,6 +361,11 @@ Rails.application.reloader.to_prepare do
require: :loggedin
end
map.project_module :activity
map.project_module :activity do
map.permission :view_project_activity,
{ activities: [:index] },
public: true,
contract_actions: { activities: %i[read] }
end
end
end

@ -690,6 +690,11 @@ af:
no_notification_reason: 'cannot be blank as IAN is chosen as a channel.'
reason_mail_digest:
no_notification_reason: 'cannot be blank as mail digest is chosen as a channel.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -700,6 +705,8 @@ af:
archived_ancestor: 'The project has an archived ancestor.'
foreign_wps_reference_version: 'Work packages in non descendant projects reference versions of the project or its descendants.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "still in use by work packages: %{types}"
enabled_modules:
@ -1790,6 +1797,7 @@ af:
label_product_version: "Produk weergawe"
label_professional_support: "Professional support"
label_profile: "Profiel"
label_project_activity: "Project activity"
label_project_attribute_plural: "Project attributes"
label_project_count: "Total number of projects"
label_project_copy_notifications: "Stuur e-pos kennisgewings gedurende die projek afskrif"
@ -2213,6 +2221,7 @@ af:
permission_add_work_packages: "Add work packages"
permission_add_messages: "Post messages"
permission_add_project: "Create project"
permission_archive_project: "Archive project"
permission_manage_user: "Create and edit users"
permission_manage_placeholder_user: "Create, edit, and delete placeholder users"
permission_add_subprojects: "Create subprojects"

@ -694,6 +694,11 @@ ar:
no_notification_reason: 'cannot be blank as IAN is chosen as a channel.'
reason_mail_digest:
no_notification_reason: 'cannot be blank as mail digest is chosen as a channel.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -704,6 +709,8 @@ ar:
archived_ancestor: 'The project has an archived ancestor.'
foreign_wps_reference_version: 'Work packages in non descendant projects reference versions of the project or its descendants.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "لا يزال قيد الاستخدام من قبل مجموعات العمل: %{types}"
enabled_modules:
@ -1857,6 +1864,7 @@ ar:
label_product_version: "إصدار المنتج"
label_professional_support: "الدعم الفني"
label_profile: "الصفحة الشخصية"
label_project_activity: "Project activity"
label_project_attribute_plural: "Project attributes"
label_project_count: "العدد الإجمالي للمشاريع"
label_project_copy_notifications: "إرسال إشعارات البريد الالكتروني أثناء نسخ المشروع"
@ -2284,6 +2292,7 @@ ar:
permission_add_work_packages: "إضافة مجموعات عمل"
permission_add_messages: "نشر الرسائل"
permission_add_project: "إنشاء مشروع"
permission_archive_project: "Archive project"
permission_manage_user: "Create and edit users"
permission_manage_placeholder_user: "Create, edit, and delete placeholder users"
permission_add_subprojects: "إنشاء مشاريع فرعية"

@ -690,6 +690,11 @@ az:
no_notification_reason: 'cannot be blank as IAN is chosen as a channel.'
reason_mail_digest:
no_notification_reason: 'cannot be blank as mail digest is chosen as a channel.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -700,6 +705,8 @@ az:
archived_ancestor: 'The project has an archived ancestor.'
foreign_wps_reference_version: 'Work packages in non descendant projects reference versions of the project or its descendants.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "still in use by work packages: %{types}"
enabled_modules:
@ -1790,6 +1797,7 @@ az:
label_product_version: "Product version"
label_professional_support: "Professional support"
label_profile: "Profile"
label_project_activity: "Project activity"
label_project_attribute_plural: "Project attributes"
label_project_count: "Total number of projects"
label_project_copy_notifications: "Send email notifications during the project copy"
@ -2213,6 +2221,7 @@ az:
permission_add_work_packages: "Add work packages"
permission_add_messages: "Post messages"
permission_add_project: "Create project"
permission_archive_project: "Archive project"
permission_manage_user: "Create and edit users"
permission_manage_placeholder_user: "Create, edit, and delete placeholder users"
permission_add_subprojects: "Create subprojects"

File diff suppressed because it is too large Load Diff

@ -690,6 +690,11 @@ bg:
no_notification_reason: 'cannot be blank as IAN is chosen as a channel.'
reason_mail_digest:
no_notification_reason: 'cannot be blank as mail digest is chosen as a channel.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -700,6 +705,8 @@ bg:
archived_ancestor: 'The project has an archived ancestor.'
foreign_wps_reference_version: 'Work packages in non descendant projects reference versions of the project or its descendants.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "все още се използва от работни пакети: %{types}"
enabled_modules:
@ -1790,6 +1797,7 @@ bg:
label_product_version: "Версия на продукта"
label_professional_support: "Професионална поддръжка"
label_profile: "Профил"
label_project_activity: "Project activity"
label_project_attribute_plural: "Project attributes"
label_project_count: "Общ брой на проектите"
label_project_copy_notifications: "Изпраща имейл известия по време на копиране на проект"
@ -2213,6 +2221,7 @@ bg:
permission_add_work_packages: "Add work packages"
permission_add_messages: "Post messages"
permission_add_project: "Създаване на проект"
permission_archive_project: "Archive project"
permission_manage_user: "Създавайте и редактирайте потребители"
permission_manage_placeholder_user: "Create, edit, and delete placeholder users"
permission_add_subprojects: "Създаване на подпроекти"

@ -686,6 +686,11 @@ ca:
no_notification_reason: 'no pot ser buit ja que s''ha escollit com a canal IAN (notificacions en aplicació).'
reason_mail_digest:
no_notification_reason: 'no pot ser buit ja que s''ha escollit com a canal correu automàtic.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -696,6 +701,8 @@ ca:
archived_ancestor: 'El projecte té un ancestre arxivat.'
foreign_wps_reference_version: 'Els paquets de treball en projectes no descendents referencien versions del projecte o els seus descendents.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "encara en ús pels paquets de treball: %{types}"
enabled_modules:
@ -1786,6 +1793,7 @@ ca:
label_product_version: "Versió del producte"
label_professional_support: "Suport professional"
label_profile: "Perfil"
label_project_activity: "Project activity"
label_project_attribute_plural: "Project attributes"
label_project_count: "Nombre total de projectes"
label_project_copy_notifications: "Envia notificacions de correu electrònic durant la còpia del projecte"
@ -2204,6 +2212,7 @@ ca:
permission_add_work_packages: "Afegir paquets de treball"
permission_add_messages: "Publicar missatges"
permission_add_project: "Crear un projecte"
permission_archive_project: "Archive project"
permission_manage_user: "Crear i editar usuaris"
permission_manage_placeholder_user: "Crea, edita i elimina usuaris de marcador de posició"
permission_add_subprojects: "Crear subprojectes"

@ -690,6 +690,11 @@ ckb-IR:
no_notification_reason: 'cannot be blank as IAN is chosen as a channel.'
reason_mail_digest:
no_notification_reason: 'cannot be blank as mail digest is chosen as a channel.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -700,6 +705,8 @@ ckb-IR:
archived_ancestor: 'The project has an archived ancestor.'
foreign_wps_reference_version: 'Work packages in non descendant projects reference versions of the project or its descendants.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "still in use by work packages: %{types}"
enabled_modules:
@ -1790,6 +1797,7 @@ ckb-IR:
label_product_version: "Product version"
label_professional_support: "Professional support"
label_profile: "Profile"
label_project_activity: "Project activity"
label_project_attribute_plural: "Project attributes"
label_project_count: "Total number of projects"
label_project_copy_notifications: "Send email notifications during the project copy"
@ -2213,6 +2221,7 @@ ckb-IR:
permission_add_work_packages: "Add work packages"
permission_add_messages: "Post messages"
permission_add_project: "Create project"
permission_archive_project: "Archive project"
permission_manage_user: "Create and edit users"
permission_manage_placeholder_user: "Create, edit, and delete placeholder users"
permission_add_subprojects: "Create subprojects"

@ -692,6 +692,11 @@ cs:
no_notification_reason: 'nemůže být prázdné, protože IAN je vybrán jako kanál.'
reason_mail_digest:
no_notification_reason: 'nemůže být prázdné, protože poštovní digest je vybrán jako kanál.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -702,6 +707,8 @@ cs:
archived_ancestor: 'Projekt má archivovaného předka.'
foreign_wps_reference_version: 'Work packages in non descendant projects reference versions of the project or its descendants.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "stále používáno pracovními balíčky: %{types}"
enabled_modules:
@ -1824,6 +1831,7 @@ cs:
label_product_version: "Verze produktu"
label_professional_support: "Profesionální podpora"
label_profile: "Profil"
label_project_activity: "Project activity"
label_project_attribute_plural: "Project attributes"
label_project_count: "Celkový počet projektů"
label_project_copy_notifications: "Během kopie projektu odeslat oznámení e-mailem"
@ -2250,6 +2258,7 @@ cs:
permission_add_work_packages: "Přidat pracovní balíčky"
permission_add_messages: "Odesílat zprávy"
permission_add_project: "Vytvořit projekt"
permission_archive_project: "Archive project"
permission_manage_user: "Vytvořit a upravit uživatele"
permission_manage_placeholder_user: "Vytvořit, upravit a odstranit placeholder uživatele"
permission_add_subprojects: "Vytvořit podprojekty"

@ -688,6 +688,11 @@ da:
no_notification_reason: 'cannot be blank as IAN is chosen as a channel.'
reason_mail_digest:
no_notification_reason: 'cannot be blank as mail digest is chosen as a channel.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -698,6 +703,8 @@ da:
archived_ancestor: 'Projektet har en arkiveret forfader.'
foreign_wps_reference_version: 'Arbejdspakker i ikke-efterkommerprojekter refererer til versioner af projektet eller dets efterkommere.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "still in use by work packages: %{types}"
enabled_modules:
@ -1788,6 +1795,7 @@ da:
label_product_version: "Product version"
label_professional_support: "Professionel support"
label_profile: "Profil"
label_project_activity: "Project activity"
label_project_attribute_plural: "Project attributes"
label_project_count: "Samlet antal projekter"
label_project_copy_notifications: "Adviser via e-mail under kopiering af projekt"
@ -2211,6 +2219,7 @@ da:
permission_add_work_packages: "Tilføj arbejdspakker"
permission_add_messages: "Send beskeder"
permission_add_project: "Opret projekt"
permission_archive_project: "Archive project"
permission_manage_user: "Opret og rediger brugere"
permission_manage_placeholder_user: "Opret, rediger og slet brugere af pladsholdere"
permission_add_subprojects: "Opret underprojekter"

@ -49,7 +49,7 @@ de:
main-menu-border-color: "Rahmenfarbe des Hauptmenüs"
custom_colors: "Benutzerdefinierte Farben"
customize: "Passen Sie Ihre OpenProject Installation mit Ihrem eigenen Logo und eigenen Farben an."
enterprise_notice: "As a special 'Thank you!' for their financial contribution to develop OpenProject, this tiny add-on is only available for Enterprise edition support subscribers."
enterprise_notice: "Diese kleine Erweiterung steht den Abonnenten der Enterprise edition ganz exklusiv als kleines Dankeschön für deren finanzielle Unterstützung zur Verfügung."
enterprise_more_info: "Hinweis: Das verwendete Logo wird öffentlich zugänglich sein."
manage_colors: "Farbauswahloptionen bearbeiten"
instructions:
@ -64,15 +64,15 @@ de:
main-menu-bg-color: "Hintergrundfarbe des Menüs in der linken Seitenleiste."
theme_warning: Das Ändern des Themes wird Ihr benutzerdefiniertes Design überschreiben. Alle Änderungen werden dann verloren gehen. Sind Sie sicher, dass Sie fortfahren möchten?
enterprise:
upgrade_to_ee: "Auf Enterprise-Edition upgraden"
add_token: "Enterprise-Edition Support Token hochladen"
upgrade_to_ee: "Auf Enterprise edition upgraden"
add_token: "Enterprise edition Support Token hochladen"
delete_token_modal:
text: "Sind Sie sicher, dass Sie das aktuelle Enterprise Edition-Token entfernen möchten?"
text: "Sind Sie sicher, dass Sie das aktuelle Enterprise edition token entfernen möchten?"
title: "Token löschen"
replace_token: "Aktuellen Enterprise-Edition Support Token ersetzen"
replace_token: "Aktuellen Enterprise edition Support Token ersetzen"
order: "Enterprise on-premises bestellen"
paste: "Enterprise-Edition Support Token hier einfügen"
required_for_feature: "Dieses Add-on ist nur mit einem aktiven Enterprise-Edition Support-Token verfügbar."
paste: "Enterprise edition Support Token hier einfügen"
required_for_feature: "Dieses Add-on ist nur mit einem aktiven Enterprise edition Support-Token verfügbar."
enterprise_link: "Klicken Sie hier für weitere Informationen."
start_trial: 'Kostenlose Testversion starten'
book_now: 'Jetzt buchen'
@ -447,7 +447,7 @@ de:
starts_at: "Aktiv seit"
expires_at: "Endet am"
subscriber: "Abonnent"
encoded_token: "Enterprise-Edition Support Token"
encoded_token: "Enterprise edition Support Token"
active_user_count_restriction: "Maximale aktive Nutzer"
grids/grid:
page: "Seite"
@ -669,7 +669,7 @@ de:
scopes:
not_match_configured: "stimmt nicht mit verfügbaren Geltungsbereichen überein."
enterprise_token:
unreadable: "kann nicht gelesen werden. Ist dies ein Enterprise-Edition Support Token?"
unreadable: "kann nicht gelesen werden. Ist dies ein Enterprise edition Support Token?"
grids/grid:
overlaps: 'überlappen.'
outside: 'ist außerhalb des Rasters.'
@ -685,6 +685,11 @@ de:
no_notification_reason: 'kann nicht leer sein, da In-App-Benachrichtigung als Kanal ausgewählt ist.'
reason_mail_digest:
no_notification_reason: 'darf nicht leer sein, da tägliche E-Mail-Übersicht als Kanal ausgewählt ist.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -695,6 +700,8 @@ de:
archived_ancestor: 'Das Projekt hat einen archivierten Vorgänger.'
foreign_wps_reference_version: 'Arbeitspakete in nicht verknüpften Projekten verweisen auf Versionen des Projekts oder seiner verknüpften Projekte.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "wird noch genutzt von folgenden Arbeitspaketen: %{types}"
enabled_modules:
@ -1285,7 +1292,7 @@ de:
error_cookie_missing: 'Das OpenProject Cookie fehlt. Bitte stellen Sie sicher, dass Cookies aktiviert sind, da diese Applikation ohne aktivierte Cookies nicht korrekt funktioniert.'
error_custom_option_not_found: "Option ist nicht vorhanden."
error_enterprise_activation_user_limit: "Ihr Konto konnte nicht aktiviert werden (Nutzerlimit erreicht). Bitte kontaktieren Sie Ihren Administrator um Zugriff zu erhalten."
error_enterprise_token_invalid_domain: "Die Enterprise-Edition ist nicht aktiv. Die aktuelle Domain (%{actual}) entspricht nicht dem erwarteten Hostnamen (%{expected})."
error_enterprise_token_invalid_domain: "Die Enterprise edition ist nicht aktiv. Die aktuelle Domain (%{actual}) entspricht nicht dem erwarteten Hostnamen (%{expected})."
error_failed_to_delete_entry: 'Fehler beim Löschen dieses Eintrags.'
error_in_dependent: "Fehler beim Versuch, abhängiges Objekt zu ändern: %{dependent_class} #%{related_id} - %{related_subject}: %{error}" #%{related_id} - %{related_subject}: %{error}"
error_in_new_dependent: "Fehler beim Versuch, abhängiges Objekt zu erstellen: %{dependent_class} - %{related_subject}: %{error}"
@ -1376,10 +1383,10 @@ de:
blocks:
community: "OpenProject Community"
upsale:
title: "Auf Enterprise-Edition upgraden"
title: "Auf Enterprise edition upgraden"
more_info: "Weitere Informationen"
links:
upgrade_enterprise_edition: "Auf Enterprise-Edition upgraden"
upgrade_enterprise_edition: "Auf Enterprise edition upgraden"
postgres_migration: "Migration Ihrer Installation zu PostgreSQL"
user_guides: "Benutzerhandbuch"
faq: "Häufig gestellte Fragen"
@ -1595,7 +1602,7 @@ de:
label_enumerations: "Aufzählungen"
label_enterprise: "Enterprise"
label_enterprise_active_users: "%{current}/%{limit} gebuchte aktive Nutzer"
label_enterprise_edition: "Enterprise Edition"
label_enterprise_edition: "Enterprise edition"
label_environment: "Umgebung"
label_estimates_and_time: "Schätzungen & Zeiten"
label_equals: "ist"
@ -1785,6 +1792,7 @@ de:
label_product_version: "Produktversion"
label_professional_support: "Professionelle Unterstützung"
label_profile: "Profil"
label_project_activity: "Project activity"
label_project_attribute_plural: "Project attributes"
label_project_count: "Gesamtzahl der Projekte"
label_project_copy_notifications: "Sende Mailbenachrichtigungen beim Kopieren des Projekts."
@ -2208,6 +2216,7 @@ de:
permission_add_work_packages: "Arbeitspakete hinzufügen"
permission_add_messages: "Forenbeiträge hinzufügen"
permission_add_project: "Projekt erstellen"
permission_archive_project: "Archive project"
permission_manage_user: "Benutzer erstellen und bearbeiten"
permission_manage_placeholder_user: "Platzhalter Benutzer erstellen, bearbeiten und löschen"
permission_add_subprojects: "Unterprojekte erstellen"
@ -2914,7 +2923,7 @@ de:
warning_user_limit_reached: >
Nutzerlimit erreicht. Sie können keine weiteren Nutzer aktivieren. Bitte <a href="%{upgrade_url}">upgraden Sie Ihren Tarif</a> oder blockieren Sie Mitglieder, um Platz für weitere Nutzer zu schaffen.
warning_user_limit_reached_instructions: >
Du hast dein Nutzerlimit erreicht (%{current}/%{max} active users). Bitte kontaktiere sales@openproject.com um deinen Enterprise Edition Plan upzugraden und weitere Nutzer hinzuzufügen.
Du hast dein Nutzerlimit erreicht (%{current}/%{max} active users). Bitte kontaktiere sales@openproject.com um deinen Enterprise edition Plan upzugraden und weitere Nutzer hinzuzufügen.
warning_protocol_mismatch_html: >
warning_bar:

@ -686,6 +686,11 @@ el:
no_notification_reason: 'cannot be blank as IAN is chosen as a channel.'
reason_mail_digest:
no_notification_reason: 'cannot be blank as mail digest is chosen as a channel.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -696,6 +701,8 @@ el:
archived_ancestor: 'Το έργο έχει έναν αρχειοθετημένο πρόγονο.'
foreign_wps_reference_version: 'Πακέτα εργασίας σε εκδόσεις αναφοράς του έργου ή των απογόνων του έργου που δεν προέρχονται απο απογόνους.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "χρησιμοποιείται ακόμη από τα πακέτα εργασίας: %{types}"
enabled_modules:
@ -1786,6 +1793,7 @@ el:
label_product_version: "Έκδοση προϊόντος"
label_professional_support: "Επαγγελματική υποστήριξη"
label_profile: "Προφίλ"
label_project_activity: "Project activity"
label_project_attribute_plural: "Project attributes"
label_project_count: "Συνολικός αριθμός έργων"
label_project_copy_notifications: "Αποστολή ειδοποιήσεων με email κατά την διάρκεια της αντιγραφής του έργου"
@ -2208,6 +2216,7 @@ el:
permission_add_work_packages: "Προσθήκη πακέτων εργασίας"
permission_add_messages: "Δημοσίευση μηνυμάτων"
permission_add_project: "Δημιουργία έργου"
permission_archive_project: "Archive project"
permission_manage_user: "Δημιουργία και επεξεργασία χρηστών"
permission_manage_placeholder_user: "Δημιουργήστε, επεξεργαστείτε και διαγράψτε τους placeholder χρήστες"
permission_add_subprojects: "Δημιουργία υποέργων"

@ -690,6 +690,11 @@ eo:
no_notification_reason: 'cannot be blank as IAN is chosen as a channel.'
reason_mail_digest:
no_notification_reason: 'cannot be blank as mail digest is chosen as a channel.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -700,6 +705,8 @@ eo:
archived_ancestor: 'La projekto havas antaŭan projekton arkivita.'
foreign_wps_reference_version: 'Work packages in non descendant projects reference versions of the project or its descendants.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "ankoraŭ uzara de la laborpakaĵoj: %{types}"
enabled_modules:
@ -1790,6 +1797,7 @@ eo:
label_product_version: "Versio de la produkto"
label_professional_support: "Profesia subteno"
label_profile: "Profilo"
label_project_activity: "Project activity"
label_project_attribute_plural: "Project attributes"
label_project_count: "Totala kvanto da projektoj"
label_project_copy_notifications: "Sendi retpoŝtajn sciigojn dum la projekta kopiado"
@ -2213,6 +2221,7 @@ eo:
permission_add_work_packages: "Add work packages"
permission_add_messages: "Post messages"
permission_add_project: "Create project"
permission_archive_project: "Archive project"
permission_manage_user: "Create and edit users"
permission_manage_placeholder_user: "Create, edit, and delete placeholder users"
permission_add_subprojects: "Create subprojects"

@ -334,7 +334,7 @@ es:
wiki:
page_not_editable_index: La página solicitada no existe (todavía). Has sido redirigido al inicio las páginas de la wiki.
no_results_title_text: Actualmente no hay paginas de wiki.
print_hint: This will print the content of this wiki page without any navigation bars.
print_hint: Esto imprimirá el contenido de esta página wiki sin ninguna barra de navegación.
index:
no_results_content_text: Añadir una nueva página wiki
work_flows:
@ -687,6 +687,11 @@ es:
no_notification_reason: 'no puede estar en blanco, ya que se han elegido las notificaciones en la aplicación como un canal.'
reason_mail_digest:
no_notification_reason: 'no puede estar en blanco, ya que se ha elegido el resumen de correo como un canal.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -697,6 +702,8 @@ es:
archived_ancestor: 'El proyecto tiene un antecesor archivado.'
foreign_wps_reference_version: 'Los paquetes de trabajo en los proyectos no descendientes hacen referencia a versiones del proyecto o a sus descendientes.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "todavia en uso por los paquetes de trabajo: %{types}"
enabled_modules:
@ -1787,6 +1794,7 @@ es:
label_product_version: "Versión del producto"
label_professional_support: "Soporte profesional"
label_profile: "Perfil"
label_project_activity: "Project activity"
label_project_attribute_plural: "Atributos del proyecto"
label_project_count: "Número total de proyectos"
label_project_copy_notifications: "Enviar notificaciones por correo electrónico durante la copia del proyecto"
@ -2209,6 +2217,7 @@ es:
permission_add_work_packages: "Añadir paquetes de trabajo"
permission_add_messages: "Publicar mensajes"
permission_add_project: "Crear proyecto"
permission_archive_project: "Archive project"
permission_manage_user: "Crear y editar usuarios"
permission_manage_placeholder_user: "Crear, editar y eliminar usuarios de marcador de posición"
permission_add_subprojects: "Crear subproyectos"

@ -690,6 +690,11 @@ et:
no_notification_reason: 'cannot be blank as IAN is chosen as a channel.'
reason_mail_digest:
no_notification_reason: 'cannot be blank as mail digest is chosen as a channel.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -700,6 +705,8 @@ et:
archived_ancestor: 'The project has an archived ancestor.'
foreign_wps_reference_version: 'Work packages in non descendant projects reference versions of the project or its descendants.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "still in use by work packages: %{types}"
enabled_modules:
@ -1790,6 +1797,7 @@ et:
label_product_version: "Product version"
label_professional_support: "Professionaalne kasutajatugi"
label_profile: "Profiil"
label_project_activity: "Project activity"
label_project_attribute_plural: "Project attributes"
label_project_count: "Total number of projects"
label_project_copy_notifications: "Saada e-postiga teateid projekti kopeerimise ajal"
@ -2213,6 +2221,7 @@ et:
permission_add_work_packages: "Teemasid lisada"
permission_add_messages: "Postitusi lisada"
permission_add_project: "Projekte luua"
permission_archive_project: "Archive project"
permission_manage_user: "Create and edit users"
permission_manage_placeholder_user: "Create, edit, and delete placeholder users"
permission_add_subprojects: "Alamprojekte luua"

File diff suppressed because it is too large Load Diff

@ -690,6 +690,11 @@ fa:
no_notification_reason: 'cannot be blank as IAN is chosen as a channel.'
reason_mail_digest:
no_notification_reason: 'cannot be blank as mail digest is chosen as a channel.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -700,6 +705,8 @@ fa:
archived_ancestor: 'The project has an archived ancestor.'
foreign_wps_reference_version: 'Work packages in non descendant projects reference versions of the project or its descendants.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "still in use by work packages: %{types}"
enabled_modules:
@ -1790,6 +1797,7 @@ fa:
label_product_version: "Product version"
label_professional_support: "Professional support"
label_profile: "Profile"
label_project_activity: "Project activity"
label_project_attribute_plural: "Project attributes"
label_project_count: "Total number of projects"
label_project_copy_notifications: "Send email notifications during the project copy"
@ -2213,6 +2221,7 @@ fa:
permission_add_work_packages: "Add work packages"
permission_add_messages: "Post messages"
permission_add_project: "Create project"
permission_archive_project: "Archive project"
permission_manage_user: "Create and edit users"
permission_manage_placeholder_user: "Create, edit, and delete placeholder users"
permission_add_subprojects: "Create subprojects"

@ -690,6 +690,11 @@ fi:
no_notification_reason: 'cannot be blank as IAN is chosen as a channel.'
reason_mail_digest:
no_notification_reason: 'cannot be blank as mail digest is chosen as a channel.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -700,6 +705,8 @@ fi:
archived_ancestor: 'The project has an archived ancestor.'
foreign_wps_reference_version: 'Work packages in non descendant projects reference versions of the project or its descendants.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "edelleen käytössä tehtävissä: %{types}"
enabled_modules:
@ -1790,6 +1797,7 @@ fi:
label_product_version: "Tuotteen versio"
label_professional_support: "Ammatillinen apu"
label_profile: "Profile"
label_project_activity: "Project activity"
label_project_attribute_plural: "Project attributes"
label_project_count: "Total number of projects"
label_project_copy_notifications: "Lähetä sähköposti-ilmoituksia projektin kopioinnin aikana"
@ -2213,6 +2221,7 @@ fi:
permission_add_work_packages: "Lisää tehtävä"
permission_add_messages: "Jätä viesti"
permission_add_project: "Luo projekti"
permission_archive_project: "Archive project"
permission_manage_user: "Create and edit users"
permission_manage_placeholder_user: "Create, edit, and delete placeholder users"
permission_add_subprojects: "Luoda aliprojekteja"

@ -690,6 +690,11 @@ fil:
no_notification_reason: 'cannot be blank as IAN is chosen as a channel.'
reason_mail_digest:
no_notification_reason: 'cannot be blank as mail digest is chosen as a channel.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -700,6 +705,8 @@ fil:
archived_ancestor: 'The project has an archived ancestor.'
foreign_wps_reference_version: 'Work packages in non descendant projects reference versions of the project or its descendants.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "ginagamit pa sa mga work packages: %{types}"
enabled_modules:
@ -1790,6 +1797,7 @@ fil:
label_product_version: "Produktong bersyon"
label_professional_support: "Propesyonal na suporta"
label_profile: "Profile"
label_project_activity: "Project activity"
label_project_attribute_plural: "Project attributes"
label_project_count: "Kabuuang bilang ng mga proyekto"
label_project_copy_notifications: "Magpadala ng mga email na abiso habang ang proyekto ay kinopya"
@ -2213,6 +2221,7 @@ fil:
permission_add_work_packages: "Magdagdag ng mga work package"
permission_add_messages: "Mga post na mensahe"
permission_add_project: "Lumikha ng proyekto"
permission_archive_project: "Archive project"
permission_manage_user: "Create and edit users"
permission_manage_placeholder_user: "Create, edit, and delete placeholder users"
permission_add_subprojects: "Lumikha ng mga subproject"

@ -690,6 +690,11 @@ fr:
no_notification_reason: 'ne peut pas être vide car IAN est choisi comme canal.'
reason_mail_digest:
no_notification_reason: 'ne peut pas être vide car le digest d''email est est choisi comme un canal.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -700,6 +705,8 @@ fr:
archived_ancestor: 'Le projet possède un ancêtre archivé.'
foreign_wps_reference_version: 'Les lots de travaux dans les projets non-descendants font référence aux versions du projet ou de ses descendants.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "toujours en cours d'utilisation par les lots de travaux : %{types}"
enabled_modules:
@ -1790,6 +1797,7 @@ fr:
label_product_version: "Version du produit"
label_professional_support: "Support professionnel"
label_profile: "Profil"
label_project_activity: "Project activity"
label_project_attribute_plural: "Attributs du projet"
label_project_count: "Nombre total de projets"
label_project_copy_notifications: "Notifier par courriel lors de la copie du projet"
@ -2213,6 +2221,7 @@ fr:
permission_add_work_packages: "Ajouter des lots de travaux"
permission_add_messages: "Poster des messages"
permission_add_project: "Créer un projet"
permission_archive_project: "Archive project"
permission_manage_user: "Créer et éditer des utilisateurs"
permission_manage_placeholder_user: "Créer, modifier et supprimer des utilisateurs fictifs"
permission_add_subprojects: "Créer des sous-projets"

@ -692,6 +692,11 @@ he:
no_notification_reason: 'cannot be blank as IAN is chosen as a channel.'
reason_mail_digest:
no_notification_reason: 'cannot be blank as mail digest is chosen as a channel.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -702,6 +707,8 @@ he:
archived_ancestor: 'The project has an archived ancestor.'
foreign_wps_reference_version: 'Work packages in non descendant projects reference versions of the project or its descendants.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "still in use by work packages: %{types}"
enabled_modules:
@ -1824,6 +1831,7 @@ he:
label_product_version: "Product version"
label_professional_support: "Professional support"
label_profile: "פרופיל"
label_project_activity: "Project activity"
label_project_attribute_plural: "Project attributes"
label_project_count: "Total number of projects"
label_project_copy_notifications: "Send email notifications during the project copy"
@ -2251,6 +2259,7 @@ he:
permission_add_work_packages: "Add work packages"
permission_add_messages: "Post messages"
permission_add_project: "Create project"
permission_archive_project: "Archive project"
permission_manage_user: "Create and edit users"
permission_manage_placeholder_user: "Create, edit, and delete placeholder users"
permission_add_subprojects: "Create subprojects"

@ -688,6 +688,11 @@ hi:
no_notification_reason: 'cannot be blank as IAN is chosen as a channel.'
reason_mail_digest:
no_notification_reason: 'cannot be blank as mail digest is chosen as a channel.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -698,6 +703,8 @@ hi:
archived_ancestor: 'The project has an archived ancestor.'
foreign_wps_reference_version: 'Work packages in non descendant projects reference versions of the project or its descendants.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "still in use by work packages: %{types}"
enabled_modules:
@ -1788,6 +1795,7 @@ hi:
label_product_version: "Product version"
label_professional_support: "Professional support"
label_profile: "पइल"
label_project_activity: "Project activity"
label_project_attribute_plural: "Project attributes"
label_project_count: "Total number of projects"
label_project_copy_notifications: "पट परतििन ईमल सचन"
@ -2211,6 +2219,7 @@ hi:
permission_add_work_packages: "Add work packages"
permission_add_messages: "Post messages"
permission_add_project: "Create project"
permission_archive_project: "Archive project"
permission_manage_user: "Create and edit users"
permission_manage_placeholder_user: "Create, edit, and delete placeholder users"
permission_add_subprojects: "Create subprojects"

@ -691,6 +691,11 @@ hr:
no_notification_reason: 'cannot be blank as IAN is chosen as a channel.'
reason_mail_digest:
no_notification_reason: 'cannot be blank as mail digest is chosen as a channel.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -701,6 +706,8 @@ hr:
archived_ancestor: 'The project has an archived ancestor.'
foreign_wps_reference_version: 'Work packages in non descendant projects reference versions of the project or its descendants.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "još uvijek u uporabi od strane radnih paketa: %{types}"
enabled_modules:
@ -1807,6 +1814,7 @@ hr:
label_product_version: "Verzija proizvoda"
label_professional_support: "Profesionalna Podrška"
label_profile: "Profil"
label_project_activity: "Project activity"
label_project_attribute_plural: "Project attributes"
label_project_count: "Ukupan broj projekata"
label_project_copy_notifications: "Slanje obavijesti e-mailom tijekom kopiranja projekta"
@ -2232,6 +2240,7 @@ hr:
permission_add_work_packages: "Dodaj radne pakete"
permission_add_messages: "Pošalji poruke"
permission_add_project: "Stvori projekt"
permission_archive_project: "Archive project"
permission_manage_user: "Create and edit users"
permission_manage_placeholder_user: "Create, edit, and delete placeholder users"
permission_add_subprojects: "Kreiraj potprojekte"

@ -687,6 +687,11 @@ hu:
no_notification_reason: "nem lehet üres, mivel az IAN-t választották csatornának.\n"
reason_mail_digest:
no_notification_reason: "nem lehet üres, mivel a mail digest csatornaként van kiválasztva.\n"
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -697,6 +702,8 @@ hu:
archived_ancestor: 'A projektnek archívált őse van.'
foreign_wps_reference_version: 'Munkacsomagok nem leszármazottak a projektekben, a projekt vagy annak leszármazottainak referenciaverziói.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "munkacsomagok használatban vannak: %{types}"
enabled_modules:
@ -1787,6 +1794,7 @@ hu:
label_product_version: "Termékverzió"
label_professional_support: "Szakmai támogatás"
label_profile: "Profil"
label_project_activity: "Project activity"
label_project_attribute_plural: "Project attributes"
label_project_count: "A projektek teljes száma"
label_project_copy_notifications: "E-mail értesítéseket küld a projekt másolás során"
@ -2209,6 +2217,7 @@ hu:
permission_add_work_packages: "feladatcsoport hozzáadása"
permission_add_messages: "Elküldött üzenetek"
permission_add_project: "Projekt létrehozása"
permission_archive_project: "Archive project"
permission_manage_user: "Felhasználó létrehozása és szerkesztése"
permission_manage_placeholder_user: "Teszt felhasználók létrehozása, módosítása, törlése"
permission_add_subprojects: "Alprojektek létrehozása"

@ -48,9 +48,9 @@ id:
main-menu-hover-font-color: "Font menu utama melayang"
main-menu-border-color: "Batas menu utama"
custom_colors: "Warna kostum"
customize: "Customize your OpenProject installation with your own logo and colors."
customize: "Sesuaikan instalasi OpenProject Anda dengan logo dan warna Anda sendiri."
enterprise_notice: "As a special 'Thank you!' for their financial contribution to develop OpenProject, this tiny add-on is only available for Enterprise edition support subscribers."
enterprise_more_info: "Note: the used logo will be publicly accessible."
enterprise_more_info: "Catatan: logo yang digunakan akan dapat diakses publik."
manage_colors: "Edit warna pilih opsi"
instructions:
alternative-color: "Warna cerah, umumnya dipakai sebagai penanda tombol penting pada layar monitor."
@ -68,30 +68,30 @@ id:
add_token: "Upload an Enterprise edition support token"
delete_token_modal:
text: "Are you sure you want to remove the current Enterprise edition token used?"
title: "Delete token"
title: "Hapus token"
replace_token: "Gantilah token bantuan yang sedang digunakan"
order: "Order Enterprise on-premises edition"
paste: "Paste your Enterprise edition support token"
required_for_feature: "This add-on is only available with an active Enterprise edition support token."
enterprise_link: "Untuk informasi lebih lanjut, klik di sini."
start_trial: 'Start free trial'
book_now: 'Book now'
get_quote: 'Get a quote'
start_trial: 'Mulai uji coba gratis'
book_now: 'Pesan sekarang'
get_quote: 'Dapatkan penawaran'
buttons:
upgrade: "Upgrade now"
contact: "Contact us for a demo"
upgrade: "Tingkatkan sekarang"
contact: "Hubungi kami untuk demo"
enterprise_info_html: "is an Enterprise <span class='spot-icon spot-icon_enterprise-addons'></span> add-on."
upgrade_info: "Please upgrade to a paid plan to activate and start using it in your team."
upgrade_info: "Tingkatkan ke paket berbayar untuk mengaktifkan dan mulai menggunakannya di tim Anda."
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."
text: "Setiap tindakan pengguna (mis. memperbarui paket kerja dua kali) digabungkan menjadi satu tindakan jika perbedaan usianya kurang dari rentang waktu yang ditentukan. Mereka akan ditampilkan sebagai tindakan tunggal dalam aplikasi. Ini juga akan menunda pemberitahuan dengan jumlah waktu yang sama sehingga mengurangi jumlah email yang dikirim dan juga akan memengaruhi penundaan %{webhook_link}."
link: "webhook"
announcements:
show_until: Show until
is_active: currently displayed
is_inactive: currently not displayed
attribute_help_texts:
note_public: 'Any text and images you add to this field is publicly visible to all logged in users!'
note_public: 'Teks dan gambar apa pun yang Anda tambahkan ke bidang ini dapat dilihat secara publik oleh semua pengguna yang masuk!'
text_overview: 'Pada tampilan ini, anda dapat membuat teks-teks bantuan yang dikhususkan untuk tampilan atribut-atribut tersebut. Ketika didefinisikan, teks-teks ini dapat ditampilkan dengan mengklik ikon bantuan pada atribut miliknya.'
label_plural: 'Atribut teks bantuan'
show_preview: 'Teks pratinjau'
@ -105,8 +105,8 @@ id:
no_results_content_text: Buat mode otentikasi baru
background_jobs:
status:
error_requeue: "Job experienced an error but is retrying. The error was: %{message}"
cancelled_due_to: "Job was cancelled due to error: %{message}"
error_requeue: "Pekerjaan mengalami kesalahan tetapi mencoba lagi. Kesalahannya adalah: %{message}"
cancelled_due_to: "Pekerjaan dibatalkan karena kesalahan: %{message}"
ldap_auth_sources:
technical_warning_html: |
Formulir LDAP ini membutuhkan pengetahuan teknis LDAP anda / pengaturan Direktori Aktif. <br/><a href="https://www.openproject.org/help/administration/manage-ldap-authentication/">Silahkan kungjungi dokumentasi untuk instruksi lebih jelas</a>.
@ -126,11 +126,9 @@ id:
OpenProject akan memfilter username pada subtree saja.
Contoh: users,dc=example,dc=com
filter_string: |
Add an optional RFC4515 filter to apply to the results returned for users filtered in the LDAP.
This can be used to restrict the set of users that are found by OpenProject for authentication and group synchronization.
Tambahkan filter RFC4515 opsional untuk diterapkan pada hasil yang dikembalikan untuk pengguna yang difilter di LDAP. Ini dapat digunakan untuk membatasi kumpulan pengguna yang ditemukan oleh OpenProject untuk autentikasi dan sinkronisasi grup.
filter_string_concat: |
OpenProject will always filter for the login attribute provided by the user to identify the record. If you provide a filter here,
it will be concatenated with an AND. By default, a catch-all (objectClass=*) will be used as a filter.
OpenProject akan selalu memfilter atribut login yang disediakan oleh pengguna untuk mengidentifikasi record. Jika Anda memberikan filter di sini, itu akan digabungkan dengan DAN. Secara default, catch-all (objectClass=*) akan digunakan sebagai filter.
onthefly_register: |
Jika anda ceklis box ini, OpenProject akan secara otomatis membuat pengguna baru dari seluruh LDAP saat pertamakali autentikasi dengan OpenProject.
Biarkan tidak terceklis untuk mengizinkan akun yang ada di OpenProject untuk mengautentikasi melalui LDAP!
@ -143,8 +141,7 @@ id:
ldap_details: 'detil dari LDAP'
user_settings: 'Pemetaan atribut'
user_settings_legend: |
The following fields are related to how users are created in OpenProject from LDAP entries and
what LDAP attributes are used to define the attributes of an OpenProject user (attribute mapping).
Bidang berikut terkait dengan cara pengguna dibuat di OpenProject dari entri LDAP dan atribut LDAP apa yang digunakan untuk menentukan atribut pengguna OpenProject (pemetaan atribut).
tls_mode:
plain: 'kosong'
simple_tls: 'LDAPS'
@ -153,7 +150,7 @@ id:
simple_tls_description: "Use LDAPS. Requires a separate port on the LDAP server. This mode is often deprecated, we recommend using STARTTLS whenever possible."
start_tls_description: "Sends a STARTTLS command after connecting to the standard LDAP port. Recommended for encrypted connections."
section_more_info_link_html: >
This section concerns the connection security of this LDAP authentication source. For more information, visit <a href="%{link}">the Net::LDAP documentation</a>.
Bagian ini menyangkut keamanan koneksi dari sumber autentikasi LDAP ini. Untuk informasi selengkapnya, kunjungi <a href="%{link}">dokumentasi Net::LDAP</a>.
tls_options:
verify_peer: "Verify SSL certificate"
verify_peer_description_html: >
@ -166,21 +163,21 @@ id:
index:
no_results_title_text: Tidak ada warna saat ini.
no_results_content_text: Buat warna baru
label_no_color: 'No color'
label_no_color: 'Tidak ada warna'
custom_actions:
actions:
name: 'Tindakan'
add: 'Tambahkan tindakan'
assigned_to:
executing_user_value: '(Assign to executing user)'
executing_user_value: '(Tetapkan ke pengguna pelaksana)'
conditions: 'Kondisi'
plural: 'Tindakan khusus'
new: 'New custom action'
edit: 'Edit custom action %{name}'
execute: 'Execute %{name}'
new: 'Tindakan kustom baru'
edit: 'Edit tindakan kustom %{name}'
execute: 'Jalankan %{nama}'
upsale:
title: 'Custom actions'
description: 'Custom actions are one-click shortcuts to a set of pre-defined actions that you can make available on certain work packages based on status, role, type or project.'
title: 'Tindakan khusus'
description: 'Tindakan kustom adalah pintasan sekali klik ke serangkaian tindakan yang ditentukan sebelumnya yang dapat Anda sediakan pada paket kerja tertentu berdasarkan status, peran, jenis, atau proyek.'
custom_fields:
text_add_new_custom_field: >
Untuk menambahkan kolom kustom baru untuk sebuah proyek, pertama-tama Anda harus membuatnya, sebelum Anda dapat menambahkannya ke proyek ini.
@ -188,15 +185,15 @@ id:
enabled_in_project: 'Diaktifkan pada proyek'
contained_in_type: 'Terkandung dalam jenis'
confirm_destroy_option: "Menghapus sebuah pilihan akan menghapus semua kemunculan yang terjadi (mis. dalam paket pekerjaan). Apakah Anda yakin ingin menghapusnya?"
reorder_alphabetical: "Reorder values alphabetically"
reorder_confirmation: "Warning: The current order of available values will be lost. Continue?"
reorder_alphabetical: "Susun ulang nilai menurut abjad"
reorder_confirmation: "Peringatan: Urutan nilai yang tersedia saat ini akan hilang. Melanjutkan?"
tab:
no_results_title_text: Tidak ada bidang kustom saat ini.
no_results_content_text: Buat bidang kustom baru
concatenation:
single: 'atau'
documentation:
see_more_link: For more information, please see our documentation on this topic.
see_more_link: Untuk informasi lebih lanjut, silakan lihat dokumentasi kami tentang topik ini.
global_search:
overwritten_tabs:
wiki_pages: "Wiki"
@ -215,28 +212,28 @@ id:
projects:
copy:
#Contains custom strings for options when copying a project that cannot be found elsewhere.
members: 'Project members'
overviews: 'Project overview'
queries: 'Work packages: saved views'
wiki_page_attachments: 'Wiki pages: attachments'
work_package_attachments: 'Work packages: attachments'
work_package_categories: 'Work packages: categories'
work_package_file_links: 'Work packages: file links'
members: 'Anggota proyek'
overviews: 'Ulasan Proyek'
queries: 'Paket kerja: tampilan tersimpan'
wiki_page_attachments: 'Halaman wiki: lampiran'
work_package_attachments: 'Paket kerja: lampiran'
work_package_categories: 'Kategori paket kerja'
work_package_file_links: 'Tautan file paket kerja'
delete:
scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result."
scheduled: "Penghapusan telah dijadwalkan dan dilakukan di latar belakang. Anda akan diberitahu tentang hasilnya."
schedule_failed: "Proyek tidak dapat dihapus: %{errors}"
failed: "Penghapusan proyek %{name} telah gagal"
failed_text: "The request to delete project %{name} has failed. The project was left archived."
completed: "Deletion of project %{name} completed"
completed_text: "The request to delete project '%{name}' has been completed."
failed_text: "Permintaan untuk menghapus proyek %{name} telah gagal. Proyek dibiarkan diarsipkan."
completed: "Penghapusan proyek %{name} selesai"
completed_text: "Permintaan untuk menghapus proyek '%{name}' telah selesai."
index:
open_as_gantt: 'Open as Gantt view'
open_as_gantt_title: "Use this button to generate a Gantt view that filters work packages for the projects visible on this page."
open_as_gantt_title_admin: "You can modify the view settings (such as selected work package types) in the administration under project settings."
open_as_gantt: 'Buka sebagai tampilan Gantt'
open_as_gantt_title: "Gunakan tombol ini untuk membuat tampilan Gantt yang memfilter paket kerja untuk proyek yang terlihat di halaman ini."
open_as_gantt_title_admin: "Anda dapat memodifikasi pengaturan tampilan (seperti jenis paket pekerjaan yang dipilih) di administrasi di bawah pengaturan proyek."
no_results_title_text: Tidak ada proyek saat ini
no_results_content_text: Buat proyek baru
settings:
change_identifier: Change identifier
change_identifier: Ubah pengenal
activities:
no_results_title_text: There are currently no activities available.
forums:
@ -253,12 +250,12 @@ id:
no_results_title_text: There are currently no versions for the project.
no_results_content_text: Create a new version
storage:
no_results_title_text: There is no additional recorded disk space consumed by this project.
no_results_title_text: Tidak ada ruang disk terekam tambahan yang digunakan oleh proyek ini.
members:
index:
no_results_title_text: There are currently no members part of this project.
no_results_content_text: Add a member to the project
invite_by_mail: "Send invite to %{mail}"
invite_by_mail: "Kirim undangan ke %{mail}"
my:
access_token:
failed_to_reset_token: "Gagal untuk menset ulang akses token: %{error}"
@ -451,10 +448,10 @@ id:
encoded_token: "Token pendukung perusahaan"
active_user_count_restriction: "Pengguna aktif maksimum"
grids/grid:
page: "Page"
row_count: "Number of rows"
column_count: "Number of columns"
widgets: "Widgets"
page: "Halaman"
row_count: "Jumlah baris"
column_count: "Jumlah kolom"
widgets: "Widget"
relation:
delay: "Jeda"
from: "Paket-Penugasan"
@ -578,34 +575,34 @@ id:
before: "harus sebelum %{date}."
before_or_equal_to: "harus sebelum atau maksimal %{date}."
blank: "harus di isi."
blank_nested: "needs to have the property '%{property}' set."
blank_nested: "harus menyetel properti '%{property}'."
cant_link_a_work_package_with_a_descendant: "Work package tidak dapat dihubungkan dengan subtask-nya."
circular_dependency: "Relasi ini menyebabkan dependensi circular."
confirmation: "tidak sesuai dengan %{attribute}."
could_not_be_copied: "%{dependency} could not be (fully) copied."
could_not_be_copied: "%{dependency} tidak dapat (sepenuhnya) disalin."
does_not_exist: "tidak ditemukan."
error_enterprise_only: "%{action} is only available in the OpenProject Enterprise edition"
error_unauthorized: "may not be accessed."
error_readonly: "was attempted to be written but is not writable."
email: "is not a valid email address."
error_unauthorized: "mungkin tidak dapat diakses."
error_readonly: "Mencoba untuk ditulis tetapi tidak dapat ditulis."
email: "bukan alamat email yang valid."
empty: "harus di isi."
even: "harus imbang."
exclusion: "telah dipesan."
file_too_large: "ukuran maksimum yang diperbolehkan %{count} Bytes."
filter_does_not_exist: "filter does not exist."
format: "does not match the expected format '%{expected}'."
format_nested: "does not match the expected format '%{expected}' at path '%{path}'."
filter_does_not_exist: "filter tidak ada."
format: "tidak cocok dengan format yang diharapkan '%{expected}'."
format_nested: "tidak cocok dengan format yang diharapkan '%{expected}' di jalur '%{path}'."
greater_than: "harus lebih besar dari %{count}."
greater_than_or_equal_to: "harus lebih besar atau sama dengan %{count}."
greater_than_or_equal_to_start_date: "must be greater than or equal to the start date."
greater_than_start_date: "must be greater than the start date."
inclusion: "belum di set dengan nilai yang diperbolehkan."
inclusion_nested: "is not set to one of the allowed values at path '%{path}'."
inclusion_nested: "tidak disetel ke salah satu nilai yang diizinkan di jalur '%{path}'."
invalid: "tidak valid."
invalid_url: 'bukanlah URL yang Valid.'
invalid_url_scheme: 'bukanlah sebuah protokol yang didukung (diperbolehkan: %{allowed_schemes}).'
less_than_or_equal_to: "harus kurang dari atau sama dengan %{count}."
not_available: "is not available due to a system configuration."
not_available: "tidak tersedia karena konfigurasi sistem."
not_deletable: "cannot be deleted."
not_current_user: "is not the current user."
not_a_date: "bukan tanggal yang valid."
@ -672,9 +669,9 @@ id:
enterprise_token:
unreadable: "tidak dapat dibaca. Apakah Anda yakin ini adalah token pendukung?"
grids/grid:
overlaps: 'overlap.'
outside: 'is outside of the grid.'
end_before_start: 'end value needs to be larger than the start value.'
overlaps: 'tumpang tindih.'
outside: 'berada di luar jaringan.'
end_before_start: 'nilai akhir harus lebih besar dari nilai awal.'
notifications:
at_least_one_channel: 'At least one channel for sending notifications needs to be specified.'
attributes:
@ -686,6 +683,11 @@ id:
no_notification_reason: 'cannot be blank as IAN is chosen as a channel.'
reason_mail_digest:
no_notification_reason: 'cannot be blank as mail digest is chosen as a channel.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -696,6 +698,8 @@ id:
archived_ancestor: 'The project has an archived ancestor.'
foreign_wps_reference_version: 'Work packages in non descendant projects reference versions of the project or its descendants.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "still in use by work packages: %{types}"
enabled_modules:
@ -1770,6 +1774,7 @@ id:
label_product_version: "Product version"
label_professional_support: "Professional support"
label_profile: "Profil"
label_project_activity: "Project activity"
label_project_attribute_plural: "Project attributes"
label_project_count: "Jumlah proyek"
label_project_copy_notifications: "Kirim email notifikasi selama meng-copy Project"
@ -2191,6 +2196,7 @@ id:
permission_add_work_packages: "Tambah Paket-Penugasan"
permission_add_messages: "Kirim pesan"
permission_add_project: "Buat Project"
permission_archive_project: "Archive project"
permission_manage_user: "Create and edit users"
permission_manage_placeholder_user: "Buat, ubah, dan hapus pengguna placeholder"
permission_add_subprojects: "Buat Sub Project"

@ -64,14 +64,14 @@ it:
main-menu-bg-color: "Colore di sfondo del menu a sinistra."
theme_warning: Cambiando il tema si sovrascriverà lo stile personalizzato. Il design andrà poi perso. Sei sicuro di voler continuare?
enterprise:
upgrade_to_ee: "Aggiorna a Enterprise Edition"
add_token: "Carica un token di assistenza per Enterprise Edition"
upgrade_to_ee: "Aggiorna a Enterprise edition"
add_token: "Carica un token di assistenza per Enterprise edition"
delete_token_modal:
text: "Vuoi davvero rimuovere il token Enterprise Edition attualmente utilizzato?"
text: "Vuoi davvero rimuovere il token Enterprise edition attualmente utilizzato?"
title: "Elimina token"
replace_token: "Sostituisci il token di assistenza attuale"
order: "Order Enterprise on-premises edition"
paste: "Incolla il tuo token di assistenza per Enterprise Edition"
paste: "Incolla il tuo token di assistenza per Enterprise edition"
required_for_feature: "This add-on is only available with an active Enterprise edition support token."
enterprise_link: "Per ulteriori informazioni, clicca qui."
start_trial: 'Inizia la prova gratuita'
@ -585,7 +585,7 @@ it:
confirmation: "non coincide con %{attribute}."
could_not_be_copied: "%{dependency} non può essere (completamente) copiato."
does_not_exist: "non esiste."
error_enterprise_only: "%{action} è disponibile solo in OpenProject Enterprise Edition"
error_enterprise_only: "%{action} è disponibile solo in OpenProject Enterprise edition"
error_unauthorized: "potrebbe non essere accessibile."
error_readonly: "è in sola lettura, pertanto non è stato possibile modificarlo."
email: "non è un indirizzo email valido"
@ -687,6 +687,11 @@ it:
no_notification_reason: 'non può essere vuoto perché IAN è scelto come canale.'
reason_mail_digest:
no_notification_reason: 'non può essere vuoto perché la mail automatica è scelta come canale.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -697,6 +702,8 @@ it:
archived_ancestor: 'Il progetto ha un antenato archiviato.'
foreign_wps_reference_version: 'Macro-attività in progetti non discendenti contengono riferimenti a versioni del progetto o dei suoi discendenti.'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "ancora in uso dalla macro-attività: %{types}"
enabled_modules:
@ -1287,7 +1294,7 @@ it:
error_cookie_missing: 'Il cookie di OpenProject è mancante. Prego, verifica che i cookie siano attivati, questa applicazione non funziona correttamente senza.'
error_custom_option_not_found: "L'opzione non esiste."
error_enterprise_activation_user_limit: "Il tuo account potrebbe non essere attivo (raggiunto il limite utente). Si prega di contattare l'amministratore per ottenere l'accesso."
error_enterprise_token_invalid_domain: "L'Enterprise Edition non è attiva. Il dominio del token Enterprise (%{actual}) non corrisponde al nome host del sistema (%{expected})."
error_enterprise_token_invalid_domain: "L'Enterprise edition non è attiva. Il dominio del token Enterprise (%{actual}) non corrisponde al nome host del sistema (%{expected})."
error_failed_to_delete_entry: 'Cancellazione voce non riuscita.'
error_in_dependent: "Errore nel tentativo di modificare l'oggetto dipendente: %{dependent_class} #%{related_id} - %{related_subject}: %{error}" #%{related_id} - %{related_subject}: %{error}"
error_in_new_dependent: "Errore nel tentativo di creare un oggetto dipendente: %{dependent_class} - %{related_subject}: %{error}"
@ -1378,10 +1385,10 @@ it:
blocks:
community: "Comunità di OpenProject"
upsale:
title: "Aggiorna ad Enterprise Edition"
title: "Aggiorna ad Enterprise edition"
more_info: "Altre informazioni"
links:
upgrade_enterprise_edition: "Aggiorna ad Enterprise Edition"
upgrade_enterprise_edition: "Aggiorna ad Enterprise edition"
postgres_migration: "Migrazione dell'installazione su PostgreSQL"
user_guides: "Guide utente"
faq: "FAQ"
@ -1597,7 +1604,7 @@ it:
label_enumerations: "Enumerazioni"
label_enterprise: "Enterprise"
label_enterprise_active_users: "%{current}/%{limit} utenti attivi riservati"
label_enterprise_edition: "Enterprise Edition"
label_enterprise_edition: "Enterprise edition"
label_environment: "Ambiente"
label_estimates_and_time: "Stime e tempi"
label_equals: "è"
@ -1787,6 +1794,7 @@ it:
label_product_version: "Versione del prodotto"
label_professional_support: "Supporto professionale"
label_profile: "Profilo"
label_project_activity: "Project activity"
label_project_attribute_plural: "Project attributes"
label_project_count: "Numero totale di progetti"
label_project_copy_notifications: "Invia le email di notifica durante la copia del progetto"
@ -2210,6 +2218,7 @@ it:
permission_add_work_packages: "Aggiungere macro-attività (work package)"
permission_add_messages: "Postare messaggi"
permission_add_project: "Creare un progetto"
permission_archive_project: "Archive project"
permission_manage_user: "Crea e modifica utenti"
permission_manage_placeholder_user: "Crea, modifica ed elimina utenti del segnaposto"
permission_add_subprojects: "Creare sotto-progetti"

@ -685,6 +685,11 @@ ja:
no_notification_reason: 'cannot be blank as IAN is chosen as a channel.'
reason_mail_digest:
no_notification_reason: 'cannot be blank as mail digest is chosen as a channel.'
non_working_day:
attributes:
date:
taken: "A non-working day already exists for %{value}."
format: "%{message}"
parse_schema_filter_params_service:
attributes:
base:
@ -695,6 +700,8 @@ ja:
archived_ancestor: 'プロジェクトの祖先がアーカイブされています。'
foreign_wps_reference_version: '子孫でないプロジェクトのワークパッケージが、プロジェクトまたはその子孫のバージョンを参照しています。'
attributes:
base:
archive_permission_missing_on_subprojects: "You do not have the permissions required to archive all sub-projects. Please contact an administrator."
types:
in_use_by_work_packages: "まだワークパッケージによって使用されています: %{types}"
enabled_modules:
@ -1769,6 +1776,7 @@ ja:
label_product_version: "製品バージョン"
label_professional_support: "プロフェッショナルサポート"
label_profile: "プロファイル"
label_project_activity: "Project activity"
label_project_attribute_plural: "Project attributes"
label_project_count: "プロジェクトの合計数"
label_project_copy_notifications: "プロジェクトのコピーの時、コピーした内容をメール通知する"
@ -2190,6 +2198,7 @@ ja:
permission_add_work_packages: "ワークパッケージの追加"
permission_add_messages: "メッセージの投稿"
permission_add_project: "プロジェクトの作成"
permission_archive_project: "Archive project"
permission_manage_user: "ユーザーの作成と編集"
permission_manage_placeholder_user: "プレースホルダーユーザーの作成、編集、削除"
permission_add_subprojects: "子プロジェクトの追加"

@ -478,7 +478,7 @@ af:
label_drop_files: "Drop files here to attach files."
label_drop_or_click_files: "Drop files here or click to attach files."
label_drop_folders_hint: You cannot upload folders as an attachment. Please select single files.
label_add_attachments: "Add attachments"
label_add_attachments: "Attach files"
label_formattable_attachment_hint: "Attach and link files by dropping on this field, or pasting from the clipboard."
label_remove_file: "Delete %{fileName}"
label_remove_watcher: "Remove watcher %{name}"
@ -657,6 +657,7 @@ af:
project:
required_outside_context: >
Please choose a project to create the work package in to see all attributes. You can only select projects which have the type above activated.
details_activity: 'Project details activity'
context: 'Project context'
work_package_belongs_to: 'This work package belongs to project %{projectname}.'
click_to_switch_context: 'Open this work package in that project.'

@ -478,7 +478,7 @@ ar:
label_drop_files: "Drop files here to attach files."
label_drop_or_click_files: "Drop files here or click to attach files."
label_drop_folders_hint: You cannot upload folders as an attachment. Please select single files.
label_add_attachments: "إضافة مرفقات"
label_add_attachments: "Attach files"
label_formattable_attachment_hint: "Attach and link files by dropping on this field, or pasting from the clipboard."
label_remove_file: "حذف%{fileName}"
label_remove_watcher: "حذف المراقب %{name}"
@ -661,6 +661,7 @@ ar:
project:
required_outside_context: >
Please choose a project to create the work package in to see all attributes. You can only select projects which have the type above activated.
details_activity: 'Project details activity'
context: 'Project context'
work_package_belongs_to: 'This work package belongs to project %{projectname}.'
click_to_switch_context: 'Open this work package in that project.'

@ -478,7 +478,7 @@ az:
label_drop_files: "Drop files here to attach files."
label_drop_or_click_files: "Drop files here or click to attach files."
label_drop_folders_hint: You cannot upload folders as an attachment. Please select single files.
label_add_attachments: "Add attachments"
label_add_attachments: "Attach files"
label_formattable_attachment_hint: "Attach and link files by dropping on this field, or pasting from the clipboard."
label_remove_file: "Delete %{fileName}"
label_remove_watcher: "Remove watcher %{name}"
@ -657,6 +657,7 @@ az:
project:
required_outside_context: >
Please choose a project to create the work package in to see all attributes. You can only select projects which have the type above activated.
details_activity: 'Project details activity'
context: 'Project context'
work_package_belongs_to: 'This work package belongs to project %{projectname}.'
click_to_switch_context: 'Open this work package in that project.'

File diff suppressed because it is too large Load Diff

@ -478,7 +478,7 @@ bg:
label_drop_files: "Drop files here to attach files."
label_drop_or_click_files: "Drop files here or click to attach files."
label_drop_folders_hint: You cannot upload folders as an attachment. Please select single files.
label_add_attachments: "Add attachments"
label_add_attachments: "Attach files"
label_formattable_attachment_hint: "Attach and link files by dropping on this field, or pasting from the clipboard."
label_remove_file: "Изтриване на %{fileName}"
label_remove_watcher: "Премахване на наблюдателя %{name}"
@ -657,6 +657,7 @@ bg:
project:
required_outside_context: >
Please choose a project to create the work package in to see all attributes. You can only select projects which have the type above activated.
details_activity: 'Project details activity'
context: 'Project context'
work_package_belongs_to: 'This work package belongs to project %{projectname}.'
click_to_switch_context: 'Open this work package in that project.'

@ -478,7 +478,7 @@ ca:
label_drop_files: "Drop files here to attach files."
label_drop_or_click_files: "Drop files here or click to attach files."
label_drop_folders_hint: No pots carregar carpetes com a fitxers adjunts. Si us plau, selecciona fitxers únics.
label_add_attachments: "Afegir fitxers adjunts"
label_add_attachments: "Attach files"
label_formattable_attachment_hint: "Adjuntar i enllaça fitxers arrossegant-los en aquest camp, o enganxant-los des del porta-papers."
label_remove_file: "Eliminar %{fileName}"
label_remove_watcher: "Elimina l'observador %{name}"
@ -657,6 +657,7 @@ ca:
project:
required_outside_context: >
Si us plau, selecciona un projecte on crear el paquet de treball per a veure tots els atributs. Només pots seleccionar projectes que tinguin activat la classe indicada anteriorment.
details_activity: 'Project details activity'
context: 'Context del projecte'
work_package_belongs_to: 'Aquest paquet de treball pertany al projecte %{projectname}.'
click_to_switch_context: 'Obre aquest paquet de treball en aquell projecte.'

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

Loading…
Cancel
Save