Merge remote-tracking branch 'origin/dev' into bump/angular9

pull/8051/head
Oliver Günther 5 years ago
commit 8e3ba17ae5
  1. 3
      .pkgr.yml
  2. 96
      .travis.yml
  3. 4
      Gemfile
  4. 37
      Gemfile.lock
  5. 4
      Gemfile.modules
  6. 18
      app/assets/javascripts/top_menu.js
  7. 2
      app/assets/stylesheets/content/_advanced_filters.sass
  8. 25
      app/assets/stylesheets/content/_tabs.lsg
  9. 21
      app/assets/stylesheets/content/_tabs.sass
  10. 4
      app/assets/stylesheets/layout/_top_menu_mobile.sass
  11. 2
      app/assets/stylesheets/layout/work_packages/_details_view.sass
  12. 2
      app/assets/stylesheets/layout/work_packages/_mobile.sass
  13. 49
      app/assets/stylesheets/layout/work_packages/_print.sass
  14. 21
      app/assets/stylesheets/layout/work_packages/_table.sass
  15. 15
      app/assets/stylesheets/layout/work_packages/_table_embedded.sass
  16. 6
      app/assets/stylesheets/openproject/_variables.scss.erb
  17. 13
      app/cells/members/row_cell.rb
  18. 2
      app/contracts/concerns/assignable_custom_field_values.rb
  19. 2
      app/contracts/concerns/assignable_values_contract.rb
  20. 2
      app/contracts/concerns/unchanged_project.rb
  21. 2
      app/contracts/custom_actions/cu_contract.rb
  22. 4
      app/contracts/oauth/application_contract.rb
  23. 4
      app/contracts/projects/base_contract.rb
  24. 6
      app/contracts/time_entries/base_contract.rb
  25. 2
      app/contracts/time_entries/update_contract.rb
  26. 2
      app/contracts/versions/base_contract.rb
  27. 4
      app/contracts/work_packages/base_contract.rb
  28. 2
      app/contracts/work_packages/update_contract.rb
  29. 14
      app/controllers/account_controller.rb
  30. 2
      app/controllers/admin/incoming_mails_controller.rb
  31. 2
      app/controllers/admin/mail_notifications_controller.rb
  32. 2
      app/controllers/application_controller.rb
  33. 85
      app/controllers/concerns/accounts/authentication_stages.rb
  34. 2
      app/controllers/concerns/accounts/omniauth_login.rb
  35. 2
      app/controllers/concerns/accounts/redirect_after_login.rb
  36. 2
      app/controllers/concerns/accounts/user_consent.rb
  37. 2
      app/controllers/concerns/accounts/user_limits.rb
  38. 3
      app/controllers/concerns/accounts/user_password_change.rb
  39. 26
      app/controllers/concerns/admin_settings_updater.rb
  40. 198
      app/controllers/concerns/auth_source_sso.rb
  41. 87
      app/controllers/concerns/authentication_stages.rb
  42. 2
      app/controllers/concerns/layout.rb
  43. 2
      app/controllers/concerns/password_confirmation.rb
  44. 10
      app/controllers/custom_styles_controller.rb
  45. 2
      app/controllers/journals_controller.rb
  46. 8
      app/controllers/my_controller.rb
  47. 3
      app/controllers/news_controller.rb
  48. 2
      app/controllers/project_settings/repository_controller.rb
  49. 8
      app/controllers/repositories_controller.rb
  50. 2
      app/controllers/search_controller.rb
  51. 2
      app/controllers/settings_controller.rb
  52. 2
      app/controllers/sys_controller.rb
  53. 2
      app/controllers/timelog_controller.rb
  54. 4
      app/controllers/users_controller.rb
  55. 1
      app/controllers/wiki_controller.rb
  56. 2
      app/controllers/work_packages/moves_controller.rb
  57. 2
      app/controllers/work_packages_controller.rb
  58. 2
      app/helpers/application_helper.rb
  59. 2
      app/helpers/password_helper.rb
  60. 2
      app/helpers/repositories_helper.rb
  61. 3
      app/models/activities/base_activity_provider.rb
  62. 2
      app/models/activities/changeset_activity_provider.rb
  63. 2
      app/models/activities/message_activity_provider.rb
  64. 2
      app/models/activities/news_activity_provider.rb
  65. 12
      app/models/activities/time_entry_activity_provider.rb
  66. 2
      app/models/activities/wiki_content_activity_provider.rb
  67. 2
      app/models/activities/work_package_activity_provider.rb
  68. 29
      app/models/anonymous_user.rb
  69. 6
      app/models/attachment.rb
  70. 112
      app/models/concerns/virtual_attribute.rb
  71. 29
      app/models/deleted_user.rb
  72. 6
      app/models/design_color.rb
  73. 2
      app/models/enabled_module.rb
  74. 2
      app/models/journal/attachable_journal.rb
  75. 2
      app/models/journal/attachment_journal.rb
  76. 2
      app/models/journal/changeset_journal.rb
  77. 3
      app/models/journal/customizable_journal.rb
  78. 2
      app/models/journal/message_journal.rb
  79. 2
      app/models/journal/news_journal.rb
  80. 2
      app/models/journal/time_entry_journal.rb
  81. 2
      app/models/journal/wiki_content_journal.rb
  82. 2
      app/models/journal/work_package_journal.rb
  83. 0
      app/models/journal_notification_mailer.rb
  84. 7
      app/models/news.rb
  85. 2
      app/models/project.rb
  86. 8
      app/models/project/activity.rb
  87. 2
      app/models/queries/news/orders/default_order.rb
  88. 1
      app/models/queries/projects.rb
  89. 2
      app/models/queries/projects/orders/default_order.rb
  90. 24
      app/models/queries/projects/orders/name_order.rb
  91. 2
      app/models/queries/time_entries/orders/default_order.rb
  92. 2
      app/models/queries/work_packages/columns/property_column.rb
  93. 2
      app/models/queries/work_packages/filter/principal_base_filter.rb
  94. 2
      app/models/query/group_by.rb
  95. 2
      app/models/query/results.rb
  96. 2
      app/models/relation.rb
  97. 26
      app/models/repository.rb
  98. 6
      app/models/repository/git.rb
  99. 6
      app/models/repository/subversion.rb
  100. 22
      app/models/time_entry_activity.rb
  101. Some files were not shown because too many files have changed in this diff Show More

@ -17,6 +17,9 @@ targets:
<<: *debian9
ubuntu-18.04:
<<: *debian9
centos-8:
dependencies:
- epel-release
centos-7:
dependencies:
- epel-release

@ -91,140 +91,68 @@ jobs:
- bash script/ci/runner.sh npm
- stage: test
name: 'spec_legacy (1/1) - standard'
name: 'spec_legacy (1/1)'
script:
- bash script/ci/setup.sh spec_legacy
- bash script/ci/runner.sh spec_legacy 1 1
- stage: test
name: 'spec_legacy (1/1) - bim'
script:
- bash script/ci/setup.sh spec_legacy bim
- bash script/ci/runner.sh spec_legacy 1 1 bim
if: head_branch =~ /^(bim\/|dev|release\/)/ OR (head_branch IS blank AND branch =~ /^(bim\/|dev|release\/)/)
- stage: test
name: 'units (1/4) - standard'
name: 'units (1/4)'
script:
- bash script/ci/setup.sh units
- bash script/ci/runner.sh units 4 1
- stage: test
name: 'units (1/4) - bim'
script:
- bash script/ci/setup.sh units bim
- bash script/ci/runner.sh units 4 1 bim
if: head_branch =~ /^(bim\/|dev|release\/)/ OR (head_branch IS blank AND branch =~ /^(bim\/|dev|release\/)/)
- stage: test
name: 'units (2/4) - standard'
name: 'units (2/4)'
script:
- bash script/ci/setup.sh units
- bash script/ci/runner.sh units 4 2
- stage: test
name: 'units (2/4) - bim'
script:
- bash script/ci/setup.sh units bim
- bash script/ci/runner.sh units 4 2 bim
if: head_branch =~ /^(bim\/|dev|release\/)/ OR (head_branch IS blank AND branch =~ /^(bim\/|dev|release\/)/)
- stage: test
name: 'units (3/4) - standard'
name: 'units (3/4)'
script:
- bash script/ci/setup.sh units
- bash script/ci/runner.sh units 4 3
- stage: test
name: 'units (3/4) - bim'
script:
- bash script/ci/setup.sh units bim
- bash script/ci/runner.sh units 4 3 bim
if: head_branch =~ /^(bim\/|dev|release\/)/ OR (head_branch IS blank AND branch =~ /^(bim\/|dev|release\/)/)
- stage: test
name: 'units (4/4) - standard'
name: 'units (4/4)'
script:
- bash script/ci/setup.sh units
- bash script/ci/runner.sh units 4 4
- stage: test
name: 'units (4/4) - bim'
script:
- bash script/ci/setup.sh units bim
- bash script/ci/runner.sh units 4 4 bim
if: head_branch =~ /^(bim\/|dev|release\/)/ OR (head_branch IS blank AND branch =~ /^(bim\/|dev|release\/)/)
- stage: test
name: 'features (1/4) - standard'
name: 'features (1/4)'
script:
- bash script/ci/setup.sh features
- bash script/ci/runner.sh features 4 1
- stage: test
name: 'features (1/4) - bim'
script:
- bash script/ci/setup.sh features bim
- bash script/ci/runner.sh features 4 1 bim
if: head_branch =~ /^(bim\/|dev|release\/)/ OR (head_branch IS blank AND branch =~ /^(bim\/|dev|release\/)/)
- stage: test
name: 'features (2/4) - standard'
name: 'features (2/4)'
script:
- bash script/ci/setup.sh features
- bash script/ci/runner.sh features 4 2
- stage: test
name: 'features (2/4) - bim'
script:
- bash script/ci/setup.sh features bim
- bash script/ci/runner.sh features 4 2 bim
if: head_branch =~ /^(bim\/|dev|release\/)/ OR (head_branch IS blank AND branch =~ /^(bim\/|dev|release\/)/)
- stage: test
name: 'features (3/4) - standard'
name: 'features (3/4)'
script:
- bash script/ci/setup.sh features
- bash script/ci/runner.sh features 4 3
- stage: test
name: 'features (3/4) - bim'
script:
- bash script/ci/setup.sh features bim
- bash script/ci/runner.sh features 4 3 bim
if: head_branch =~ /^(bim\/|dev|release\/)/ OR (head_branch IS blank AND branch =~ /^(bim\/|dev|release\/)/)
- stage: test
name: 'features (4/4) - standard'
name: 'features (4/4)'
script:
- bash script/ci/setup.sh features
- bash script/ci/runner.sh features 4 4
- stage: test
name: 'features (4/4) - bim'
script:
- bash script/ci/setup.sh features bim
- bash script/ci/runner.sh features 4 4 bim
if: head_branch =~ /^(bim\/|dev|release\/)/ OR (head_branch IS blank AND branch =~ /^(bim\/|dev|release\/)/)
- stage: test
name: 'plugins:units (1/1) - standard'
name: 'plugins:units (1/1)'
script:
- bash script/ci/setup.sh plugins:units
- bash script/ci/runner.sh plugins:units 1 1
if: head_branch !~ /^core\//
- stage: test
name: 'plugins:units (1/1) - bim'
script:
- bash script/ci/setup.sh plugins:units bim
- bash script/ci/runner.sh plugins:units 1 1 bim
if: head_branch =~ /^(bim\/|dev|release\/)/ OR (head_branch IS blank AND branch =~ /^(bim\/|dev|release\/)/)
- stage: test
name: 'plugins:features (1/1) - standard'
name: 'plugins:features (1/1)'
script:
- bash script/ci/setup.sh plugins:features
- bash script/ci/runner.sh plugins:features 1 1
if: head_branch !~ /^core\//
- stage: test
name: 'plugins:features (1/1) - bim'
script:
- bash script/ci/setup.sh plugins:features bim
- bash script/ci/runner.sh plugins:features 1 1 bim
if: head_branch =~ /^(bim\/|dev|release\/)/ OR (head_branch IS blank AND branch =~ /^(bim\/|dev|release\/)/)
- stage: test
name: 'plugins:cucumber (1/1) - standard'
name: 'plugins:cucumber (1/1)'
script:
- bash script/ci/setup.sh plugins:cucumber
- bash script/ci/runner.sh plugins:cucumber 1 1
if: head_branch !~ /^core\//
- stage: test
name: 'plugins:cucumber (1/1) - bim'
script:
- bash script/ci/setup.sh plugins:cucumber bim
- bash script/ci/runner.sh plugins:cucumber 1 1 bim
if: head_branch =~ /^(bim\/|dev|release\/)/ OR (head_branch IS blank AND branch =~ /^(bim\/|dev|release\/)/)
addons:
chrome: stable

@ -42,7 +42,7 @@ gem 'rdoc', '>= 2.4.2'
# Maintain our own omniauth due to relative URL root issues
# see upstream PR: https://github.com/omniauth/omniauth/pull/903
gem 'omniauth', git: 'https://github.com/opf/omniauth', ref: 'fe862f986b2e846e291784d2caa3d90a658c67f0'
gem 'doorkeeper', git: 'https://github.com/doorkeeper-gem/doorkeeper', ref: 'ce969eee6c16aa8082b0c77ebb5968d9e9b6a57b'
gem 'doorkeeper', '~> 5.3.1'
gem 'request_store', '~> 1.4.1'
gem 'warden', '~> 1.2'
@ -165,7 +165,7 @@ gem 'unicorn'
gem 'puma', '~> 4.3.1' # used for development and optionally for production
gem 'nokogiri', '~> 1.10.5'
gem 'nokogiri', '~> 1.10.8'
gem 'carrierwave', '~> 1.3.1'
gem 'fog-aws'

@ -1,11 +1,3 @@
GIT
remote: https://github.com/doorkeeper-gem/doorkeeper
revision: ce969eee6c16aa8082b0c77ebb5968d9e9b6a57b
ref: ce969eee6c16aa8082b0c77ebb5968d9e9b6a57b
specs:
doorkeeper (5.0.2)
railties (>= 4.2)
GIT
remote: https://github.com/finnlabs/omniauth-openid-connect.git
revision: 9e7fd0e7bd36d40451c6b3c2ea641e8d237c295d
@ -102,18 +94,12 @@ PATH
openproject-pdf_export
PATH
remote: modules/bcf
remote: modules/bim
specs:
openproject-bcf (1.0.0)
openproject-bim (1.0.0)
activerecord-import
rubyzip (~> 1.2)
PATH
remote: modules/bim_seeder
specs:
openproject-bim_seeder (1.0.0)
openproject-bcf
PATH
remote: modules/boards
specs:
@ -151,13 +137,6 @@ PATH
specs:
grids (1.0.0)
PATH
remote: modules/ifc_models
specs:
openproject-ifc_models (1.0.0)
activerecord-import
rubyzip (~> 1.2)
PATH
remote: modules/ldap_groups
specs:
@ -474,6 +453,8 @@ GEM
uber (< 0.2.0)
domain_name (0.5.20180417)
unf (>= 0.0.5, < 1.0.0)
doorkeeper (5.3.1)
railties (>= 5)
dry-configurable (0.9.0)
concurrent-ruby (~> 1.0)
dry-core (~> 0.4, >= 0.4.7)
@ -649,7 +630,7 @@ GEM
newrelic_rpm (6.6.0.358)
nio4r (2.5.2)
no_proxy_fix (0.1.2)
nokogiri (1.10.7)
nokogiri (1.10.8)
mini_portile2 (~> 2.4.0)
nokogumbo (2.0.1)
nokogiri (~> 1.8, >= 1.8.4)
@ -1007,7 +988,7 @@ DEPENDENCIES
date_validator (~> 0.9.0)
deckar01-task_list (= 2.2.0)
delayed_job_active_record (~> 4.1.4)
doorkeeper!
doorkeeper (~> 5.3.1)
equivalent-xml (~> 0.6)
escape_utils (~> 1.0)
factory_bot (~> 4.8)
@ -1035,7 +1016,7 @@ DEPENDENCIES
my_page!
net-ldap (~> 0.16.0)
newrelic_rpm
nokogiri (~> 1.10.5)
nokogiri (~> 1.10.8)
oj (~> 3.9.1)
okcomputer (~> 1.18.1)
omniauth!
@ -1046,14 +1027,12 @@ DEPENDENCIES
openproject-auth_saml!
openproject-avatars!
openproject-backlogs!
openproject-bcf!
openproject-bim_seeder!
openproject-bim!
openproject-boards!
openproject-costs!
openproject-documents!
openproject-github_integration!
openproject-global_roles!
openproject-ifc_models!
openproject-ldap_groups!
openproject-meeting!
openproject-openid_connect!

@ -45,7 +45,5 @@ group :opf_plugins do
gem 'openproject-boards', path: 'modules/boards'
gem 'overviews', path: 'modules/overviews'
gem 'openproject-bim_seeder', path: 'modules/bim_seeder', require: !!(ENV['OPENPROJECT_EDITION'] == 'bim')
gem 'openproject-bcf', path: 'modules/bcf', require: !!(ENV['OPENPROJECT_EDITION'] == 'bim')
gem 'openproject-ifc_models', path: 'modules/ifc_models', require: !!(ENV['OPENPROJECT_EDITION'] == 'bim')
gem 'openproject-bim', path: 'modules/bim'
end

@ -36,11 +36,10 @@
TopMenu.prototype = $.extend(TopMenu.prototype, {
setup: function () {
var self = this;
this.hover = false;
this.menuIsOpen = false;
this.withHeadingFoldOutAtBorder();
this.setupDropdownHoverAndClick();
this.setupDropdownClick();
this.registerEventHandlers();
this.closeOnBodyClick();
this.accessibility();
@ -97,11 +96,11 @@
closeOnBodyClick: function () {
var self = this;
$('html').click(function() {
if (self.menuIsOpen) {
document.getElementById('wrapper').addEventListener('click', function (evt) {
if (self.menuIsOpen && !self.openDropdowns()[0].contains(evt.target)) {
self.closing();
}
});
}, true);
},
openDropdowns: function () {
@ -125,20 +124,13 @@
}
},
setupDropdownHoverAndClick: function () {
setupDropdownClick: function () {
var self = this;
this.dropdowns().each(function (ix, it) {
$(it).click(function () {
self.toggleClick($(this));
return false;
});
$(it).hover(function() {
// only do something if the menu is in hover mode
// AND the dropdown we hover on is not currently open anyways
if (self.hover && self.isClosed($(this))) {
self.open($(this));
}
});
$(it).on('touchstart', function(e) {
// This shall avoid the hover event is fired,
// which would otherwise lead to menu being closed directly after its opened.

@ -83,7 +83,7 @@ $advanced-filters--grid-gap: 10px
.advanced-filters--add-filter
display: grid
grid-template-columns: $advanced-filters--label-size $advanced-filters--values-and-operator-size $advanced-filters--close-icon-size
grid-grap: $advanced-filters--grid-gap
grid-gap: $advanced-filters--grid-gap
// The type="text" is required to be more specific
.advanced-filters--text-field[type="text"],

@ -22,3 +22,28 @@
</div>
</div>
```
The height of the tabs section can be reduced applying the `-narrow` modification class.
```
<div class="scrollable-tabs -narrow">
<ul class="tabrow"">
<li class="selected"
tab-id="tab_1">
<a href="#scrollable-tabs?tab=tab_1">Tab 1</a>
</li>
<li tab-id="tab_2">
<a href="#scrollable-tabs?tab=tab_2">Tab 2</a>
</li>
<li tab-id="tab_3">
<a href="#scrollable-tabs?tab=tab_3">Tab 3</a>
</li>
</ul>
<div hidden="true" class="scrollable-tabs--button -left">
<span class="icon-arrow-left2"></span>
</div>
<div hidden="true" class="scrollable-tabs--button -right">
<span class="icon-arrow-right2"></span>
</div>
</div>
```

@ -41,6 +41,7 @@
text-decoration: none
&:hover
text-decoration: none
li.tab-icon
width: 5%
.icon-context:before
@ -63,6 +64,13 @@
li:hover
border-bottom-color: #DDDDDD
li.-disabled
cursor: default
pointer-events: none
border-bottom-width: 0
a
color: $tabs-font-color-disabled
content-tabs
min-height: 73px
display: block
@ -74,6 +82,7 @@ content-tabs
height: 40px
border-bottom: 1px solid #DDDDDD
margin-bottom: 1rem
.tabrow
display: block
overflow-x: auto
@ -85,6 +94,18 @@ content-tabs
padding-right: 1rem
font-size: 14px
&.-narrow
margin-bottom: 0
height: initial
.tabrow
height: initial
line-height: initial
li
line-height: initial
padding-bottom: 6px
.scrollable-tabs--button
display: block
width: 20px

@ -87,8 +87,8 @@
max-width: none
#more-menu.drop-down--modules
// 58 = Width of one menu item
right: -58px
// 66 = Width of one menu item
right: -66px
#account-nav-left
#projects-menu

@ -31,7 +31,7 @@
body.router--work-packages-split-view,
body.router--work-packages-split-view-new
.work-packages-split-view--details-side
.work-packages-partitioned-page--content-right
overflow-x: hidden
overflow-y: auto
position: relative

@ -138,7 +138,7 @@
.router--work-packages-split-view,
.router--work-packages-split-view-new
.work-packages-split-view--details-side
.work-packages-partitioned-page--content-right
overflow-x: auto
.router--work-packages-list-view

@ -25,36 +25,35 @@
overflow: visible !important
position: relative
.work-packages-list-view--container
.work-packages-partitioned-query-space--container
padding-left: 0
.work-packages-split-view
overflow: visible
.work-packages-split-view--tabletimeline-side
.work-packages-split-view--tabletimeline-content
.wp-table--hierarchy-aditional-row,
tr.-checked,
.row-hovered
// Remove highlighting of first and selected rows:
background-color: transparent
.work-packages-split-view--tabletimeline-side
.work-packages-split-view--tabletimeline-content
.wp-table--hierarchy-aditional-row,
tr.-checked,
.row-hovered
// Remove highlighting of first and selected rows:
background-color: transparent
.work-packages-tabletimeline--table-side
contain: initial // For printing in Chrome
overflow: visible
.work-packages-tabletimeline--table-side
contain: initial // For printing in Chrome
overflow: visible
&:not(.-timeline-visible)
// Only repeat table header when only table visible and no
// timelines.
thead.-sticky th
position: initial
&:not(.-timeline-visible)
// Only repeat table header when only table visible and no
// timelines.
thead.-sticky th
position: initial
&.-timeline-visible
// Do not show table on timelines print outs.
display: none
&.-timeline-visible
// Do not show table on timelines print outs.
display: none
.work-packages-tabletimeline--timeline-side
contain: initial // For printing in Chrome
border-left: none
.work-packages-tabletimeline--timeline-side
contain: initial // For printing in Chrome
border-left: none
flex-basis: 100% !important
.work-package-table--container,
.generic-table--results-container
@ -70,7 +69,7 @@
#content-wrapper,
#content
.work-packages-list-view--container
.work-packages-partitioned-query-space--container
display: block
.toolbar-container
display: none

@ -50,31 +50,13 @@
.work-packages-page--ui-view
height: 100%
.work-packages-list-view--container
// Flexbox for the toolbar, filters and work package split view
display: flex
flex-direction: column
height: 100%
.work-packages-partitioned-query-space--container
> .toolbar-container
margin-top: 5px
input[type="text"].toolbar--editable-toolbar
font-size: 24px
.work-packages--filters-optional-container
// not flex-item
height: auto
overflow: auto
flex-shrink: 0
// Outer Flex container for (table+timeline)|details
.work-packages-split-view
flex: 1 1 auto
display: flex
// Required for correctly scrolling the inner containers
overflow: hidden
// Match both rows and timeline specifically
tr.row-hovered,
div.row-hovered
@ -86,6 +68,7 @@
// Left part of the split view
// == flex container for (table|timeline)
.work-packages-split-view--tabletimeline-side
height: 100%
flex: 2
display: flex
flex-direction: column

@ -37,6 +37,7 @@ $table-timeline--compact-row-height: 28px
flex-direction: column
overflow: hidden
width: 100%
height: 100%
// Align with section header
.wp-table--table-header:first-child .generic-table--sort-header-outer,
@ -61,6 +62,17 @@ $table-timeline--compact-row-height: 28px
.generic-table--sort-header
font-size: 12px
.work-packages-embedded-view--grid-view
height: 100%
overflow: auto
@include styled-scroll-bar
.work-packages-partitioned-page--content-right &
margin-right: -14px
.wp-cards-container.-horizontal
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))
&.-compact-tables
.wp-table--row
border-bottom: none !important
@ -104,5 +116,8 @@ $table-timeline--compact-row-height: 28px
max-width: 50%
overflow-x: scroll
.pagination
padding-bottom: 0
wp-query-group .wp-relations-create-button
margin-left: -6px

@ -11,12 +11,12 @@ primary-color-dark
alternative-color
This allows us to dynamically define CSS4 + Sass variables from a single map
%>
<% ::OpenProject::Design.variables.each do |var, hexcode| %>
<% ::OpenProject::CustomStyles::Design.variables.each do |var, hexcode| %>
$<%= var %>: <%= hexcode %>;
<% end %>
:root {
<% ::OpenProject::Design.variables.each do |var, definition| %>
<% ::OpenProject::CustomStyles::Design.variables.each do |var, definition| %>
<% css4definition = definition.gsub(/\$([\w-]+)/, 'var(--\1)') %>
--<%= var %>: <%= css4definition %>;
<% end %>
@ -24,7 +24,7 @@ $<%= var %>: <%= hexcode %>;
<%# Construct a sass map to lookup variables in the mixin below %>
$variable-map: (
<% ::OpenProject::Design.variables.each do |var, definition| %>
<% ::OpenProject::CustomStyles::Design.variables.each do |var, definition| %>
<%= var %>: #{<%= definition %>},
<% end %>
);

@ -39,12 +39,13 @@ module Members
end
def roles
label =
if principal&.admin?
I18n.t(:label_member_all_admin)
else
h member.roles.sort.collect(&:name).join(', ')
end
label = h member.roles.sort.collect(&:name).join(', ')
if principal&.admin?
label << tag(:br)
label << I18n.t(:label_member_all_admin)
end
span = content_tag "span", label, id: "member-#{member.id}-roles"
if may_update?

@ -28,7 +28,7 @@
# See docs/COPYRIGHT.rdoc for more details.
#++
module Concerns::AssignableCustomFieldValues
module AssignableCustomFieldValues
extend ActiveSupport::Concern
included do

@ -1,4 +1,4 @@
module Concerns::AssignableValuesContract
module AssignableValuesContract
def assignable_values(column, _user)
method_name = "assignable_#{column.to_s.pluralize}"

@ -28,7 +28,7 @@
# See docs/COPYRIGHT.rdoc for more details.
#++
module Concerns::UnchangedProject
module UnchangedProject
extend ActiveSupport::Concern
included do

@ -32,7 +32,7 @@ require 'model_contract'
# Contract for create (c) and update (u)
module CustomActions
class CUContract < ::ModelContract
class CuContract < ::ModelContract
def self.model
CustomAction
end

@ -28,9 +28,7 @@
# See docs/COPYRIGHT.rdoc for more details.
#++
require 'model_contract'
module Oauth
module OAuth
class ApplicationContract < ::ModelContract
def self.model
::Doorkeeper::Application

@ -30,8 +30,8 @@
module Projects
class BaseContract < ::ModelContract
include Concerns::AssignableValuesContract
include Concerns::AssignableCustomFieldValues
include AssignableValuesContract
include AssignableCustomFieldValues
attribute :name
attribute :identifier

@ -28,12 +28,10 @@
# See docs/COPYRIGHT.rdoc for more details.
#++
require 'model_contract'
module TimeEntries
class BaseContract < ::ModelContract
include Concerns::AssignableValuesContract
include Concerns::AssignableCustomFieldValues
include AssignableValuesContract
include AssignableCustomFieldValues
delegate :work_package,
:project,

@ -30,7 +30,7 @@
module TimeEntries
class UpdateContract < BaseContract
include Concerns::UnchangedProject
include UnchangedProject
def validate
unless user_allowed_to_update?

@ -28,7 +28,7 @@
module Versions
class BaseContract < ::ModelContract
include Concerns::AssignableValuesContract
include AssignableValuesContract
def self.model
Version

@ -28,12 +28,10 @@
# See docs/COPYRIGHT.rdoc for more details.
#++
require 'model_contract'
module WorkPackages
class BaseContract < ::ModelContract
include ::Attachments::ValidateReplacements
include ::Concerns::AssignableValuesContract
include AssignableValuesContract
attribute :subject
attribute :description

@ -32,7 +32,7 @@ require 'work_packages/base_contract'
module WorkPackages
class UpdateContract < BaseContract
include Concerns::UnchangedProject
include UnchangedProject
attribute :lock_version,
permission: %i[edit_work_packages assign_versions manage_subtasks move] do

@ -30,12 +30,12 @@
class AccountController < ApplicationController
include CustomFieldsHelper
include OmniauthHelper
include Concerns::OmniauthLogin
include Concerns::RedirectAfterLogin
include Concerns::AuthenticationStages
include Concerns::UserConsent
include Concerns::UserLimits
include Concerns::UserPasswordChange
include Accounts::OmniauthLogin
include Accounts::RedirectAfterLogin
include Accounts::AuthenticationStages
include Accounts::UserConsent
include Accounts::UserLimits
include Accounts::UserPasswordChange
# prevents login action to be filtered by check_if_login_required application scope filter
skip_before_action :check_if_login_required
@ -250,7 +250,7 @@ class AccountController < ApplicationController
# When making changes here, also check MyController.change_password
def change_password
# Retrieve user_id from session
@user = User.find(flash[:_password_change_user_id])
@user = User.find(params[:password_change_user_id])
change_password_flow(user: @user, params: params, show_user_name: true) do
password_authentication(@user.login, params[:new_password])

@ -29,7 +29,7 @@
#++
class Admin::IncomingMailsController < ApplicationController
include Concerns::AdminSettingsUpdater
include AdminSettingsUpdater
current_menu_item [:show] do
:incoming_mails

@ -29,7 +29,7 @@
#++
class Admin::MailNotificationsController < ApplicationController
include Concerns::AdminSettingsUpdater
include AdminSettingsUpdater
current_menu_item [:show] do
:mail_notifications

@ -658,5 +658,5 @@ class ApplicationController < ActionController::Base
# http://simonecarletti.com/blog/2011/04/understanding-ruby-and-rails-lazy-load-hooks/
ActiveSupport.run_load_hooks(:application_controller, self)
prepend Concerns::AuthSourceSSO
prepend AuthSourceSSO
end

@ -0,0 +1,85 @@
module Accounts::AuthenticationStages
def stage_success
stage = session[:authentication_stages]&.first
if stage && stage.to_s == params[:stage]
if params[:secret] == stage_secrets[stage]
session[:authentication_stages] = session[:authentication_stages].drop(1)
successful_authentication User.find(session[:authenticated_user_id]), reset_stages: false
else
flash[:error] = I18n.t :notice_auth_stage_verification_error, stage: stage
redirect_to signin_path
end
else
flash[:error] = I18n.t(
:notice_auth_stage_wrong_stage,
expected: stage || '(none)',
actual: params[:stage]
)
redirect_to signin_path
end
end
def stage_failure
flash[:error] = flash[:error] || I18n.t(:notice_auth_stage_error, stage: params[:stage])
redirect_to signin_path
end
private
def authentication_stages(after_activation: false, reset: true)
if OpenProject::Authentication::Stage.stages.select(&:active?).any?
session.delete [:authentication_stages, :stage_secrets, :back_url] if reset
if session.include?(:authentication_stages)
lookup_authentication_stages
else
init_authentication_stages after_activation: after_activation
end
else
[]
end
end
def lookup_authentication_stages
OpenProject::Authentication::Stage.find_all session[:authentication_stages]
end
def init_authentication_stages(after_activation:)
stages = active_stages after_activation
session[:authentication_stages] = stages.map(&:identifier)
session[:stage_secrets] = new_stage_secrets
# Remember back_url from params since we're redirecting
# but don't use the referer
session[:back_url] = params[:back_url]
stages
end
def active_stages(after_activation)
OpenProject::Authentication::Stage
.stages
.select(&:active?)
.select { |s| s.run_after_activation? || !after_activation }
end
def stage_secrets
Hash(session[:stage_secrets])
end
def new_stage_secrets
session[:authentication_stages]
.map { |ident| [ident, stage_secret(ident)] }
.to_h
end
def stage_secret(ident)
SecureRandom.hex(16)
end
end

@ -30,7 +30,7 @@ require 'uri'
##
# Intended to be used by the AccountController to handle omniauth logins
module Concerns::OmniauthLogin
module Accounts::OmniauthLogin
extend ActiveSupport::Concern
included do

@ -29,7 +29,7 @@
##
# Intended to be used by the AccountController to decide where to
# send the user when they logged in.
module Concerns::RedirectAfterLogin
module Accounts::RedirectAfterLogin
def redirect_after_login(user)
if user.first_login
user.update_attribute(:first_login, false)

@ -29,7 +29,7 @@
##
# Intended to be used by the AccountController to implement the user consent
# check.
module Concerns::UserConsent
module Accounts::UserConsent
include ::UserConsentHelper
def consent

@ -28,7 +28,7 @@
##
# Intended to be used by the UsersController to enforce the user limit.
module Concerns::UserLimits
module Accounts::UserLimits
def enforce_user_limit(
redirect_to: users_path,
hard: OpenProject::Enterprise.fail_fast?,

@ -28,7 +28,7 @@
##
# Intended to be used by the MyController and AccountController for password change flows
module Concerns::UserPasswordChange
module Accounts::UserPasswordChange
##
# Process a password change form, used when the user is forced
# to change the password.
@ -81,7 +81,6 @@ module Concerns::UserPasswordChange
def render_password_change(user, message, show_user_name: false)
flash[:error] = message unless message.nil?
flash[:_password_change_user_id] = user.id
@user = user
@username = user.login
render 'my/password', locals: { show_user_name: show_user_name }

@ -28,24 +28,22 @@
# See docs/COPYRIGHT.rdoc for more details.
#++
module Concerns
module AdminSettingsUpdater
extend ActiveSupport::Concern
module AdminSettingsUpdater
extend ActiveSupport::Concern
included do
layout 'admin'
included do
layout 'admin'
before_action :require_admin
before_action :require_admin
def update
if params[:settings]
Settings::UpdateService
.new(user: current_user)
.call(settings: permitted_params.settings.to_h)
def update
if params[:settings]
Settings::UpdateService
.new(user: current_user)
.call(settings: permitted_params.settings.to_h)
flash[:notice] = t(:notice_successful_update)
redirect_to action: 'show', tab: params[:tab]
end
flash[:notice] = t(:notice_successful_update)
redirect_to action: 'show', tab: params[:tab]
end
end
end

@ -1,139 +1,137 @@
module Concerns
##
# If OPENPROJECT_AUTH__SOURCE__SSO_HEADER and OPENPROJECT_AUTH__SOURCE__SSO_SECRET are
# configured OpenProject will login the user given in the HTTP header with the given name
# together with the secret in the form of `login:$secret`.
module AuthSourceSSO
def find_current_user
user = super
##
# If OPENPROJECT_AUTH__SOURCE__SSO_HEADER and OPENPROJECT_AUTH__SOURCE__SSO_SECRET are
# configured OpenProject will login the user given in the HTTP header with the given name
# together with the secret in the form of `login:$secret`.
module AuthSourceSSO
def find_current_user
user = super
return user if user || sso_in_progress!
return user if user || sso_in_progress!
if login = read_sso_login
user = find_user_from_auth_source(login) || create_user_from_auth_source(login)
if login = read_sso_login
user = find_user_from_auth_source(login) || create_user_from_auth_source(login)
handle_sso_for! user, login
end
handle_sso_for! user, login
end
end
def read_sso_login
return nil unless header_name && secret
def read_sso_login
return nil unless header_name && secret
login, given_secret = String(request.headers[header_name]).split(":")
login, given_secret = String(request.headers[header_name]).split(":")
login if valid_credentials? login, given_secret
end
login if valid_credentials? login, given_secret
end
def sso_config
@sso_config ||= OpenProject::Configuration.auth_source_sso.try(:with_indifferent_access)
end
def sso_config
@sso_config ||= OpenProject::Configuration.auth_source_sso.try(:with_indifferent_access)
end
def header_name
sso_config && sso_config[:header]
end
def header_name
sso_config && sso_config[:header]
end
def secret
sso_config && sso_config[:secret]
end
def secret
sso_config && sso_config[:secret]
end
def valid_credentials?(login, secret)
!invalid_credentials?(login, secret)
end
def valid_credentials?(login, secret)
!invalid_credentials?(login, secret)
end
def invalid_credentials?(login, secret)
if secret != self.secret.to_s
Rails.logger.error(
"Secret contained in auth source SSO header not valid. " +
"(#{header_name}: #{request.headers[header_name]})"
)
def invalid_credentials?(login, secret)
if secret != self.secret.to_s
Rails.logger.error(
"Secret contained in auth source SSO header not valid. " +
"(#{header_name}: #{request.headers[header_name]})"
)
true
elsif login.nil?
Rails.logger.error(
"No login contained in auth source SSO header. " +
"(#{header_name}: #{request.headers[header_name]})"
)
true
elsif login.nil?
Rails.logger.error(
"No login contained in auth source SSO header. " +
"(#{header_name}: #{request.headers[header_name]})"
)
true
end
true
end
end
def find_user_from_auth_source(login)
User.where(login: login).where.not(auth_source_id: nil).first
end
def find_user_from_auth_source(login)
User.where(login: login).where.not(auth_source_id: nil).first
end
def create_user_from_auth_source(login)
if attrs = AuthSource.find_user(login)
# login is both safe and protected in chilis core code
# in case it's intentional we keep it that way
user = User.new attrs.except(:login)
user.login = login
user.language = Setting.default_language
def create_user_from_auth_source(login)
if attrs = AuthSource.find_user(login)
# login is both safe and protected in chilis core code
# in case it's intentional we keep it that way
user = User.new attrs.except(:login)
user.login = login
user.language = Setting.default_language
save_user! user
save_user! user
user
end
user
end
end
def save_user!(user)
if user.save
user.reload
def save_user!(user)
if user.save
user.reload
if logger && user.auth_source
logger.info(
"User '#{user.login}' created from external auth source: " +
"#{user.auth_source.type} - #{user.auth_source.name}"
)
end
if logger && user.auth_source
logger.info(
"User '#{user.login}' created from external auth source: " +
"#{user.auth_source.type} - #{user.auth_source.name}"
)
end
end
end
def sso_in_progress!
sso_failure_in_progress! || session[:auth_source_registration]
end
def sso_in_progress!
sso_failure_in_progress! || session[:auth_source_registration]
end
def sso_failure_in_progress!
failure = session[:auth_source_sso_failure]
def sso_failure_in_progress!
failure = session[:auth_source_sso_failure]
if failure
if failure[:ttl] > 0
session[:auth_source_sso_failure] = failure.merge(ttl: failure[:ttl] - 1)
else
session.delete :auth_source_sso_failure
if failure
if failure[:ttl] > 0
session[:auth_source_sso_failure] = failure.merge(ttl: failure[:ttl] - 1)
else
session.delete :auth_source_sso_failure
nil
end
nil
end
end
end
def sso_login_failed?(user)
user.nil? || user.new_record? || !user.active?
end
def sso_login_failed?(user)
user.nil? || user.new_record? || !user.active?
end
def handle_sso_for!(user, login)
if sso_login_failed?(user)
handle_sso_failure! user, login
else # valid user
handle_sso_success user
end
def handle_sso_for!(user, login)
if sso_login_failed?(user)
handle_sso_failure! user, login
else # valid user
handle_sso_success user
end
end
def handle_sso_success(user)
session[:user_id] = user.id
def handle_sso_success(user)
session[:user_id] = user.id
user
end
user
end
def handle_sso_failure!(user, login)
session[:auth_source_sso_failure] = {
user: user,
login: login,
back_url: request.base_url + request.original_fullpath,
ttl: 1
}
def handle_sso_failure!(user, login)
session[:auth_source_sso_failure] = {
user: user,
login: login,
back_url: request.base_url + request.original_fullpath,
ttl: 1
}
redirect_to sso_failure_path
end
redirect_to sso_failure_path
end
end

@ -1,87 +0,0 @@
module Concerns
module AuthenticationStages
def stage_success
stage = session[:authentication_stages]&.first
if stage && stage.to_s == params[:stage]
if params[:secret] == stage_secrets[stage]
session[:authentication_stages] = session[:authentication_stages].drop(1)
successful_authentication User.find(session[:authenticated_user_id]), reset_stages: false
else
flash[:error] = I18n.t :notice_auth_stage_verification_error, stage: stage
redirect_to signin_path
end
else
flash[:error] = I18n.t(
:notice_auth_stage_wrong_stage,
expected: stage || '(none)',
actual: params[:stage]
)
redirect_to signin_path
end
end
def stage_failure
flash[:error] = flash[:error] || I18n.t(:notice_auth_stage_error, stage: params[:stage])
redirect_to signin_path
end
private
def authentication_stages(after_activation: false, reset: true)
if OpenProject::Authentication::Stage.stages.select(&:active?).any?
session.delete [:authentication_stages, :stage_secrets, :back_url] if reset
if session.include?(:authentication_stages)
lookup_authentication_stages
else
init_authentication_stages after_activation: after_activation
end
else
[]
end
end
def lookup_authentication_stages
OpenProject::Authentication::Stage.find_all session[:authentication_stages]
end
def init_authentication_stages(after_activation:)
stages = active_stages after_activation
session[:authentication_stages] = stages.map(&:identifier)
session[:stage_secrets] = new_stage_secrets
# Remember back_url from params since we're redirecting
# but don't use the referer
session[:back_url] = params[:back_url]
stages
end
def active_stages(after_activation)
OpenProject::Authentication::Stage
.stages
.select(&:active?)
.select { |s| s.run_after_activation? || !after_activation }
end
def stage_secrets
Hash(session[:stage_secrets])
end
def new_stage_secrets
session[:authentication_stages]
.map { |ident| [ident, stage_secret(ident)] }
.to_h
end
def stage_secret(ident)
SecureRandom.hex(16)
end
end
end

@ -28,7 +28,7 @@
# See docs/COPYRIGHT.rdoc for more details.
#++
module Concerns::Layout
module Layout
extend ActiveSupport::Concern
included do

@ -29,7 +29,7 @@
##
# Acts as a filter for actions that require password confirmation to have
# passed before it may be accessed.
module Concerns::PasswordConfirmation
module PasswordConfirmation
def check_password_confirmation
return true unless password_confirmation_required?

@ -54,7 +54,7 @@ class CustomStylesController < ApplicationController
end
def update
@custom_style = CustomStyle.current
@custom_style = get_or_create_custom_style
if @custom_style.update(custom_style_params)
redirect_to custom_style_path
else
@ -121,7 +121,7 @@ class CustomStylesController < ApplicationController
end
def set_logo(logo)
CustomStyle.current.update(theme_logo: logo)
get_or_create_custom_style.update(theme_logo: logo)
end
def set_colors(variable_params)
@ -144,10 +144,14 @@ class CustomStylesController < ApplicationController
def set_theme(params)
theme = ActionController::Parameters.new(theme: params[:theme] || '').permit(:theme)
@custom_style = CustomStyle.current
@custom_style = get_or_create_custom_style
@custom_style.update(theme)
end
def get_or_create_custom_style
CustomStyle.current || CustomStyle.create!
end
def require_ee_token
unless EnterpriseToken.allows_to?(:define_custom_style)
redirect_to custom_style_upsale_path

@ -27,8 +27,6 @@
# See docs/COPYRIGHT.rdoc for more details.
#++
require 'diff'
class JournalsController < ApplicationController
before_action :find_journal, except: [:index]
before_action :find_optional_project, only: [:index]

@ -29,8 +29,8 @@
#++
class MyController < ApplicationController
include Concerns::PasswordConfirmation
include Concerns::UserPasswordChange
include PasswordConfirmation
include Accounts::UserPasswordChange
include ActionView::Helpers::TagHelper
layout 'my'
@ -91,7 +91,7 @@ class MyController < ApplicationController
# Create a new feeds key
def generate_rss_key
if request.post?
token = Token::Rss.create!(user: current_user)
token = Token::RSS.create!(user: current_user)
flash[:info] = [
t('my.access_token.notice_reset_token', type: 'RSS').html_safe,
content_tag(:strong, token.plain_value),
@ -108,7 +108,7 @@ class MyController < ApplicationController
# Create a new API key
def generate_api_key
if request.post?
token = Token::Api.create!(user: current_user)
token = Token::API.create!(user: current_user)
flash[:info] = [
t('my.access_token.notice_reset_token', type: 'API').html_safe,
content_tag(:strong, token.plain_value),

@ -1,4 +1,5 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2020 the OpenProject GmbH
@ -29,7 +30,7 @@
class NewsController < ApplicationController
include PaginationHelper
include Concerns::Layout
include Layout
default_search_scope :news

@ -40,7 +40,7 @@ class ProjectSettings::RepositoryController < ProjectSettingsController
def new_repository
return unless params[:scm_vendor]
service = Scm::RepositoryFactoryService.new(@project, params)
service = SCM::RepositoryFactoryService.new(@project, params)
if service.build_temporary
@repository = service.repository
else

@ -50,7 +50,7 @@ class RepositoriesController < ApplicationController
before_action :find_repository, except: [:edit, :update, :create, :destroy, :destroy_info]
accept_key_auth :revisions
rescue_from OpenProject::Scm::Exceptions::ScmError, with: :show_error_command_failed
rescue_from OpenProject::SCM::Exceptions::SCMError, with: :show_error_command_failed
def update
@repository = @project.repository
@ -60,7 +60,7 @@ class RepositoriesController < ApplicationController
end
def create
service = Scm::RepositoryFactoryService.new(@project, params)
service = SCM::RepositoryFactoryService.new(@project, params)
if service.build_and_save
@repository = service.repository
flash[:notice] = l('repositories.create_successful')
@ -333,7 +333,7 @@ class RepositoriesController < ApplicationController
# Prepare checkout instructions
# available on all pages (even empty!)
@path = params[:repo_path] || ''
@instructions = ::Scm::CheckoutInstructionsService.new(@repository, path: @path)
@instructions = ::SCM::CheckoutInstructionsService.new(@repository, path: @path)
# Asserts repository availability, or renders an appropriate error
@repository.scm.check_availability!
@ -346,7 +346,7 @@ class RepositoriesController < ApplicationController
raise InvalidRevisionParam
end
end
rescue OpenProject::Scm::Exceptions::ScmEmpty
rescue OpenProject::SCM::Exceptions::SCMEmpty
render 'empty'
rescue ActiveRecord::RecordNotFound
render_404

@ -28,7 +28,7 @@
#++
class SearchController < ApplicationController
include Concerns::Layout
include Layout
before_action :find_optional_project,
:prepare_tokens,

@ -29,7 +29,7 @@
#++
class SettingsController < ApplicationController
include Concerns::AdminSettingsUpdater
include AdminSettingsUpdater
helper_method :gon

@ -105,7 +105,7 @@ class SysController < ActionController::Base
def update_storage_information(repository, force = false)
if force
::Scm::StorageUpdaterJob.perform_later(repository)
::SCM::StorageUpdaterJob.perform_later(repository)
true
else
repository.update_required_storage

@ -41,7 +41,7 @@ class TimelogController < ApplicationController
include TimelogHelper
include CustomFieldsHelper
include PaginationHelper
include Concerns::Layout
include Layout
menu_item :time_entries

@ -48,10 +48,10 @@ class UsersController < ApplicationController
:destroy]
# Password confirmation helpers and actions
include Concerns::PasswordConfirmation
include PasswordConfirmation
before_action :check_password_confirmation, only: [:destroy]
include Concerns::UserLimits
include Accounts::UserLimits
before_action :enforce_user_limit, only: [:create]
before_action -> { enforce_user_limit flash_now: true }, only: [:new]

@ -28,7 +28,6 @@
# See docs/COPYRIGHT.rdoc for more details.
#++
require 'diff'
require 'htmldiff'
# The WikiController follows the Rails REST controller pattern but with

@ -132,6 +132,7 @@ class WorkPackages::MovesController < ApplicationController
@target_project = @allowed_projects.detect { |p| p.id.to_s == params[:new_project_id].to_s } if params[:new_project_id]
@target_project ||= @project
@types = @target_project.types
@available_versions = @target_project.shared_versions.order_by_newest_date
@available_statuses = Workflow.available_statuses(@project)
@notes = params[:notes]
@notes ||= ''
@ -160,6 +161,7 @@ class WorkPackages::MovesController < ApplicationController
:start_date,
:due_date,
:status_id,
:fixed_version_id,
:priority_id)
.to_h
.reject { |_, v| v.blank? }

@ -31,7 +31,7 @@
class WorkPackagesController < ApplicationController
include QueriesHelper
include PaginationHelper
include Concerns::Layout
include Layout
accept_key_auth :index, :show

@ -301,7 +301,7 @@ module ApplicationHelper
# Returns the theme, controller name, and action as css classes for the
# HTML body.
def body_css_classes
css = ['theme-' + OpenProject::Design.identifier.to_s]
css = ['theme-' + OpenProject::CustomStyles::Design.identifier.to_s]
if params[:controller] && params[:action]
css << 'controller-' + params[:controller]

@ -28,7 +28,7 @@
#++
module PasswordHelper
include Concerns::PasswordConfirmation
include PasswordConfirmation
##
# Decorate the form_for helper with the request-for-confirmation directive

@ -224,7 +224,7 @@ module RepositoriesHelper
# displaying an existing repository.
def scm_options(repository = nil)
options = []
OpenProject::Scm::Manager.enabled.each do |vendor, klass|
OpenProject::SCM::Manager.enabled.each do |vendor, klass|
# Skip repositories that were configured to have no
# available types left.
next if klass.available_types.empty?

@ -41,7 +41,7 @@
# #
# See the comments on the methods to get additional information. #
###############################################################################
class Activity::BaseActivityProvider
class Activities::BaseActivityProvider
include Redmine::Acts::ActivityProvider
include I18n
include Redmine::I18n
@ -91,7 +91,6 @@ class Activity::BaseActivityProvider
def activitied_type(_activity = nil)
activity_type = self.class.name
namespace = activity_type.deconstantize
class_name = activity_type.demodulize
class_name.gsub('ActivityProvider', '').constantize

@ -27,7 +27,7 @@
# See docs/COPYRIGHT.rdoc for more details.
#++
class Activity::ChangesetActivityProvider < Activity::BaseActivityProvider
class Activities::ChangesetActivityProvider < Activities::BaseActivityProvider
acts_as_activity_provider type: 'changesets',
permission: :view_changesets

@ -27,7 +27,7 @@
# See docs/COPYRIGHT.rdoc for more details.
#++
class Activity::MessageActivityProvider < Activity::BaseActivityProvider
class Activities::MessageActivityProvider < Activities::BaseActivityProvider
acts_as_activity_provider type: 'messages',
permission: :view_messages

@ -27,7 +27,7 @@
# See docs/COPYRIGHT.rdoc for more details.
#++
class Activity::NewsActivityProvider < Activity::BaseActivityProvider
class Activities::NewsActivityProvider < Activities::BaseActivityProvider
acts_as_activity_provider type: 'news',
permission: :view_news

@ -27,7 +27,7 @@
# See docs/COPYRIGHT.rdoc for more details.
#++
class Activity::TimeEntryActivityProvider < Activity::BaseActivityProvider
class Activities::TimeEntryActivityProvider < Activities::BaseActivityProvider
acts_as_activity_provider type: 'time_entries',
permission: :view_time_entries
@ -63,11 +63,11 @@ class Activity::TimeEntryActivityProvider < Activity::BaseActivityProvider
end
def work_package_title(event)
Activity::WorkPackageActivityProvider.work_package_title(event['work_package_id'],
event['work_package_subject'],
event['type_name'],
event['status_name'],
event['is_standard'])
Activities::WorkPackageActivityProvider.work_package_title(event['work_package_id'],
event['work_package_subject'],
event['type_name'],
event['status_name'],
event['is_standard'])
end
def event_description(event, _activity)

@ -27,7 +27,7 @@
# See docs/COPYRIGHT.rdoc for more details.
#++
class Activity::WikiContentActivityProvider < Activity::BaseActivityProvider
class Activities::WikiContentActivityProvider < Activities::BaseActivityProvider
acts_as_activity_provider type: 'wiki_edits',
permission: :view_wiki_edits

@ -27,7 +27,7 @@
# See docs/COPYRIGHT.rdoc for more details.
#++
class Activity::WorkPackageActivityProvider < Activity::BaseActivityProvider
class Activities::WorkPackageActivityProvider < Activities::BaseActivityProvider
acts_as_activity_provider type: 'work_packages',
permission: :view_work_packages

@ -0,0 +1,29 @@
class AnonymousUser < User
validate :validate_unique_anonymous_user, on: :create
# There should be only one AnonymousUser in the database
def validate_unique_anonymous_user
errors.add :base, 'An anonymous user already exists.' if AnonymousUser.any?
end
def available_custom_fields
[]
end
# Overrides a few properties
def logged?; false end
def builtin?; true end
def admin; false end
def name(*_args); I18n.t(:label_user_anonymous) end
def mail; nil end
def time_zone; nil end
def rss_key; nil end
def destroy; false end
end

@ -236,7 +236,11 @@ class Attachment < ActiveRecord::Base
.pluck(:container_type)
.compact
.select do |container_class|
container_class.constantize.attachment_tsv_extracted?
klass = container_class.constantize
klass.respond_to?(:attachment_tsv_extracted?) && klass.attachment_tsv_extracted?
rescue NameError
false
end
end

@ -26,82 +26,80 @@
# See docs/COPYRIGHT.rdoc for more details.
#++
module Concerns
module VirtualAttribute
extend ActiveSupport::Concern
module VirtualAttribute
extend ActiveSupport::Concern
class_methods do
def virtual_attribute(attribute, cast_type: :string, &block)
attribute attribute, cast_type
define_attribute_method attribute
class_methods do
def virtual_attribute(attribute, cast_type: :string, &block)
attribute attribute, cast_type
define_attribute_method attribute
include InstanceMethods
include InstanceMethods
_define_virtual_attribute_setter(attribute)
_define_virtual_attribute_getter(attribute, &block)
_define_virtual_attribute_reload(attribute)
_define_virtual_attributes_hook(attribute)
end
private
_define_virtual_attribute_setter(attribute)
_define_virtual_attribute_getter(attribute, &block)
_define_virtual_attribute_reload(attribute)
_define_virtual_attributes_hook(attribute)
end
def _define_virtual_attributes_hook(attribute)
define_method :attributes do |*args|
# Ensure attribute has been read
send(attribute)
super(*args)
end
private
# Ensure the virtual attribute is unset before destroying
before_destroy { send(:"#{attribute}=", nil) }
def _define_virtual_attributes_hook(attribute)
define_method :attributes do |*args|
# Ensure attribute has been read
send(attribute)
super(*args)
end
def _define_virtual_attribute_setter(attribute)
define_method "#{attribute}=" do |value|
set_virtual_attribute(attribute, value) if send(attribute) != value
instance_variable_set(:"@#{attribute}_set", true)
instance_variable_set(:"@#{attribute}", value)
end
# Ensure the virtual attribute is unset before destroying
before_destroy { send(:"#{attribute}=", nil) }
end
def _define_virtual_attribute_setter(attribute)
define_method "#{attribute}=" do |value|
set_virtual_attribute(attribute, value) if send(attribute) != value
instance_variable_set(:"@#{attribute}_set", true)
instance_variable_set(:"@#{attribute}", value)
end
end
def _define_virtual_attribute_getter(attribute, &block)
define_method attribute do
if instance_variable_get(:"@#{attribute}_set")
instance_variable_get(:"@#{attribute}")
else
value = instance_eval(&block)
def _define_virtual_attribute_getter(attribute, &block)
define_method attribute do
if instance_variable_get(:"@#{attribute}_set")
instance_variable_get(:"@#{attribute}")
else
value = instance_eval(&block)
set_virtual_attribute_was(attribute, value)
set_virtual_attribute_was(attribute, value)
instance_variable_set(:"@#{attribute}", value)
end
instance_variable_set(:"@#{attribute}", value)
end
end
end
def _define_virtual_attribute_reload(attribute)
define_method :reload do |*args|
instance_variable_set(:"@#{attribute}", nil)
instance_variable_set(:"@#{attribute}_set", nil)
def _define_virtual_attribute_reload(attribute)
define_method :reload do |*args|
instance_variable_set(:"@#{attribute}", nil)
instance_variable_set(:"@#{attribute}_set", nil)
super(*args)
end
super(*args)
end
end
end
module InstanceMethods
# Used to persists the changes to the virtual attribute in the mutation_tracker used by
# AR::Dirty so that it looks like every other attribute.
# Using attribute_will_change! does not place the value in the tracker but merely forces
# the attribute to be returned when asking the object for changes.
def set_virtual_attribute_was(attribute, value)
attributes = mutations_from_database.send(:attributes)
attributes[attribute.to_s].instance_variable_set(:@value_before_type_cast, value)
end
module InstanceMethods
# Used to persists the changes to the virtual attribute in the mutation_tracker used by
# AR::Dirty so that it looks like every other attribute.
# Using attribute_will_change! does not place the value in the tracker but merely forces
# the attribute to be returned when asking the object for changes.
def set_virtual_attribute_was(attribute, value)
attributes = mutations_from_database.send(:attributes)
attributes[attribute.to_s].instance_variable_set(:@value_before_type_cast, value)
end
def set_virtual_attribute(attribute, value)
attributes = mutations_from_database.send(:attributes)
attributes[attribute.to_s] = attributes[attribute.to_s].with_value_from_user(value)
end
def set_virtual_attribute(attribute, value)
attributes = mutations_from_database.send(:attributes)
attributes[attribute.to_s] = attributes[attribute.to_s].with_value_from_user(value)
end
end
end

@ -0,0 +1,29 @@
class DeletedUser < User
validate :validate_unique_deleted_user, on: :create
# There should be only one DeletedUser in the database
def validate_unique_deleted_user
errors.add :base, 'A DeletedUser already exists.' if DeletedUser.any?
end
def self.first
super || create(type: to_s, status: STATUSES[:locked])
end
# Overrides a few properties
def logged?; false end
def builtin?; true end
def admin; false end
def name(*_args); I18n.t('user.deleted') end
def mail; nil end
def time_zone; nil end
def rss_key; nil end
def destroy; false end
end

@ -47,19 +47,19 @@ class DesignColor < ActiveRecord::Base
class << self
def defaults
OpenProject::Design.resolved_variables
OpenProject::CustomStyles::Design.resolved_variables
end
def setables
overwritten_values = self.overwritten
OpenProject::Design.customizable_variables.map do |varname|
OpenProject::CustomStyles::Design.customizable_variables.map do |varname|
overwritten_value = overwritten_values.detect { |var| var.variable == varname }
overwritten_value || new(variable: varname)
end
end
def overwritten
overridable = OpenProject::Design.customizable_variables
overridable = OpenProject::CustomStyles::Design.customizable_variables
all.to_a.select do |color|
overridable.include?(color.variable) && self.defaults[color] != color.get_hexcode

@ -61,7 +61,7 @@ class EnabledModule < ActiveRecord::Base
scm_type: Repository.managed_type
}
service = Scm::RepositoryFactoryService.new(project,
service = SCM::RepositoryFactoryService.new(project,
ActionController::Parameters.new(params))
service.build_and_save
end

@ -32,5 +32,3 @@ class Journal::AttachableJournal < Journal::BaseJournal
belongs_to :attachment
end
::AttachableJournal = Journal::AttachableJournal

@ -30,5 +30,3 @@
class Journal::AttachmentJournal < Journal::BaseJournal
self.table_name = 'attachment_journals'
end
::AttachmentJournal = Journal::AttachmentJournal

@ -30,5 +30,3 @@
class Journal::ChangesetJournal < Journal::BaseJournal
self.table_name = 'changeset_journals'
end
::ChangesetJournal = Journal::ChangesetJournal

@ -1,4 +1,5 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2020 the OpenProject GmbH
@ -32,5 +33,3 @@ class Journal::CustomizableJournal < Journal::BaseJournal
belongs_to :custom_field, foreign_key: :custom_field_id
end
::CustomizableJournal = Journal::CustomizableJournal

@ -30,5 +30,3 @@
class Journal::MessageJournal < Journal::BaseJournal
self.table_name = 'message_journals'
end
::MessageJournal = Journal::MessageJournal

@ -30,5 +30,3 @@
class Journal::NewsJournal < Journal::BaseJournal
self.table_name = 'news_journals'
end
::NewsJournal = Journal::NewsJournal

@ -30,5 +30,3 @@
class Journal::TimeEntryJournal < Journal::BaseJournal
self.table_name = 'time_entry_journals'
end
::TimeEntryJournal = Journal::TimeEntryJournal

@ -30,5 +30,3 @@
class Journal::WikiContentJournal < Journal::BaseJournal
self.table_name = 'wiki_content_journals'
end
::WikiContentJournal = Journal::WikiContentJournal

@ -30,5 +30,3 @@
class Journal::WorkPackageJournal < Journal::BaseJournal
self.table_name = 'work_package_journals'
end
::WorkPackageJournal = Journal::WorkPackageJournal

@ -41,11 +41,12 @@ class News < ActiveRecord::Base
acts_as_journalized
acts_as_event url: Proc.new { |o| { controller: '/news', action: 'show', id: o.id } },
datetime: :created_on
datetime: :created_at
acts_as_searchable columns: ["#{table_name}.title", "#{table_name}.summary", "#{table_name}.description"],
include: :project,
references: :projects
references: :projects,
date_column: "#{table_name}.created_at"
acts_as_watchable
@ -85,7 +86,7 @@ class News < ActiveRecord::Base
# table_name shouldn't be needed :(
def self.newest_first
order "#{table_name}.created_on DESC"
order "#{table_name}.created_at DESC"
end
def new_comment(attributes = {})

@ -306,7 +306,7 @@ class Project < ActiveRecord::Base
# reduce the number of db queries when performing operations including the
# project's versions.
def assignable_versions
@all_shared_versions ||= shared_versions.with_status_open.to_a
@all_shared_versions ||= shared_versions.with_status_open.order_by_newest_date.to_a
end
# Returns a hash of project users grouped by role

@ -43,11 +43,11 @@ module Project::Activity
def latest_project_activity
@latest_project_activity ||=
Constants::ProjectActivity.registered.map { |params|
build_latest_project_activity_for(on: params[:on],
chain: params[:chain],
Constants::ProjectActivity.registered.map do |params|
build_latest_project_activity_for(on: params[:on].constantize,
chain: Array(params[:chain]).map(&:constantize),
attribute: params[:attribute])
}
end
end
def with_latest_activity

@ -32,6 +32,6 @@ class Queries::News::Orders::DefaultOrder < Queries::BaseOrder
self.model = News
def self.key
/\A(id|created_on)\z/
/\A(id|created_at|updated_at)\z/
end
end

@ -51,4 +51,5 @@ module Queries::Projects
register.order query, orders::RequiredDiskSpaceOrder
register.order query, orders::CustomFieldOrder
register.order query, orders::ProjectStatusOrder
register.order query, orders::NameOrder
end

@ -32,6 +32,6 @@ class Queries::Projects::Orders::DefaultOrder < Queries::BaseOrder
self.model = Project
def self.key
/\A(id|name|created_at|public|lft)\z/
/\A(id|created_at|public|lft)\z/
end
end

@ -1,4 +1,5 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2020 the OpenProject GmbH
@ -27,15 +28,20 @@
# See docs/COPYRIGHT.rdoc for more details.
#++
module Redmine #:nodoc:
module CoreExtensions #:nodoc:
module String #:nodoc:
# Custom string inflections
module Inflections
def with_leading_slash
starts_with?('/') ? self : "/#{ self }"
end
end
class Queries::Projects::Orders::NameOrder < Queries::BaseOrder
self.model = Project
def self.key
:name
end
def scope
super.select('projects.*', 'lower(projects.name)')
end
def order
with_raise_on_invalid do
model.order(Arel.sql("lower(projects.name)").send(direction))
end
end
end

@ -32,6 +32,6 @@ class Queries::TimeEntries::Orders::DefaultOrder < Queries::BaseOrder
self.model = TimeEntry
def self.key
/\A(id|hours|spent_on|created_on)\z/
/\A(id|hours|spent_on|created_on|updated_on)\z/
end
end

@ -97,7 +97,7 @@ class Queries::WorkPackages::Columns::PropertyColumn < Queries::WorkPackages::Co
},
fixed_version: {
association: 'fixed_version',
sortable: %w(start_date effective_date),
sortable: %w(start_date effective_date name),
default_order: 'DESC',
null_handling: 'NULLS LAST',
groupable: "#{WorkPackage.table_name}.fixed_version_id"

@ -30,7 +30,7 @@
class Queries::WorkPackages::Filter::PrincipalBaseFilter <
Queries::WorkPackages::Filter::WorkPackageFilter
include MeValueFilterMixin
include Queries::WorkPackages::Filter::MeValueFilterMixin
def allowed_values
@allowed_values ||= begin

@ -28,7 +28,7 @@
# See docs/COPYRIGHT.rdoc for more details.
#++
module ::Query::Grouping
module ::Query::GroupBy
# Returns the work package count by group or nil if query is not grouped
def work_package_count_by_group
@work_package_count_by_group ||= begin

@ -32,7 +32,7 @@ require_dependency 'query/group_by'
require_dependency 'query/sums'
class ::Query::Results
include ::Query::Grouping
include ::Query::GroupBy
include ::Query::Sums
include Redmine::I18n

@ -29,7 +29,7 @@
#++
class Relation < ActiveRecord::Base
include Concerns::VirtualAttribute
include VirtualAttribute
scope :of_work_package,
->(work_package) { where('from_id = ? OR to_id = ?', work_package, work_package) }

@ -29,7 +29,7 @@
class Repository < ActiveRecord::Base
include Redmine::Ciphering
include OpenProject::Scm::ManageableRepository
include OpenProject::SCM::ManageableRepository
belongs_to :project
has_many :changesets, -> {
@ -55,7 +55,7 @@ class Repository < ActiveRecord::Base
# Checks if the SCM is enabled when creating a repository
def validate_enabled_scm
errors.add(:type, :not_available) unless OpenProject::Scm::Manager.enabled?(vendor)
errors.add(:type, :not_available) unless OpenProject::SCM::Manager.enabled?(vendor)
end
# Removes leading and trailing whitespace
@ -151,7 +151,7 @@ class Repository < ActiveRecord::Base
entries = scm.entries(path, identifier)
if limit && limit < entries.size
result = OpenProject::Scm::Adapters::Entries.new entries.take(limit)
result = OpenProject::SCM::Adapters::Entries.new entries.take(limit)
result.truncated = entries.size - result.size
result
@ -205,7 +205,7 @@ class Repository < ActiveRecord::Base
if storage_updated_at.nil? ||
storage_updated_at < oldest_cachable_time
::Scm::StorageUpdaterJob.perform_later(self)
::SCM::StorageUpdaterJob.perform_later(self)
return true
end
end
@ -305,7 +305,7 @@ class Repository < ActiveRecord::Base
if project.repository
begin
project.repository.fetch_changesets
rescue OpenProject::Scm::Exceptions::CommandFailed => e
rescue OpenProject::SCM::Exceptions::CommandFailed => e
logger.error "scm: error during fetching changesets: #{e.message}"
end
end
@ -328,7 +328,7 @@ class Repository < ActiveRecord::Base
#
# @param [Symbol] type SCM tag to determine the type this repository should be built as
#
# @raise [OpenProject::Scm::RepositoryBuildError]
# @raise [OpenProject::SCM::RepositoryBuildError]
# Raised when the instance could not be built
# given the parameters.
# @raise [::NameError] Raised when the given +vendor+ could not be resolved to a class.
@ -355,10 +355,10 @@ class Repository < ActiveRecord::Base
# Build a temporary model instance of the given vendor for temporary use in forms.
# Will not receive any args.
def self.build_scm_class(vendor)
klass = OpenProject::Scm::Manager.registered[vendor]
klass = OpenProject::SCM::Manager.registered[vendor]
if klass.nil?
raise OpenProject::Scm::Exceptions::RepositoryBuildError.new(
raise OpenProject::SCM::Exceptions::RepositoryBuildError.new(
I18n.t('repositories.errors.disabled_or_unknown_vendor', vendor: vendor)
)
else
@ -372,7 +372,7 @@ class Repository < ActiveRecord::Base
if repository.class.available_types.include? type
repository.scm_type = type
else
raise OpenProject::Scm::Exceptions::RepositoryBuildError.new(
raise OpenProject::SCM::Exceptions::RepositoryBuildError.new(
I18n.t('repositories.errors.disabled_or_unknown_type',
type: type,
vendor: repository.vendor)
@ -391,7 +391,7 @@ class Repository < ActiveRecord::Base
end
def self.enabled?
OpenProject::Scm::Manager.enabled?(vendor)
OpenProject::SCM::Manager.enabled?(vendor)
end
##
@ -430,11 +430,11 @@ class Repository < ActiveRecord::Base
# Create local managed repository request when the built instance
# is managed by OpenProject
def create_managed_repository
service = Scm::CreateManagedRepositoryService.new(self)
service = SCM::CreateManagedRepositoryService.new(self)
if service.call
true
else
raise OpenProject::Scm::Exceptions::RepositoryBuildError.new(
raise OpenProject::SCM::Exceptions::RepositoryBuildError.new(
service.localized_rejected_reason
)
end
@ -444,7 +444,7 @@ class Repository < ActiveRecord::Base
# Destroy local managed repository request when the built instance
# is managed by OpenProject
def delete_managed_repository
service = Scm::DeleteManagedRepositoryService.new(self)
service = SCM::DeleteManagedRepositoryService.new(self)
if service.call
true
else

@ -34,13 +34,13 @@ class Repository::Git < Repository
validate :validity_of_local_url
def self.scm_adapter_class
OpenProject::Scm::Adapters::Git
OpenProject::SCM::Adapters::Git
end
def configure(scm_type, _args)
if scm_type == self.class.managed_type
unless manageable?
raise OpenProject::Scm::Exceptions::RepositoryBuildError.new(
raise OpenProject::SCM::Exceptions::RepositoryBuildError.new(
I18n.t('repositories.managed.error_not_manageable')
)
end
@ -89,7 +89,7 @@ class Repository::Git < Repository
end
def self.authorization_policy
::Scm::GitAuthorizationPolicy
::SCM::GitAuthorizationPolicy
end
# Returns the identifier for the given git changeset

@ -34,13 +34,13 @@ class Repository::Subversion < Repository
validates_format_of :url, with: /\A(http|https|svn(\+[^\s:\/\\]+)?|file):\/\/.+\z/i
def self.scm_adapter_class
OpenProject::Scm::Adapters::Subversion
OpenProject::SCM::Adapters::Subversion
end
def configure(scm_type, _args)
if scm_type == self.class.managed_type
unless manageable?
raise OpenProject::Scm::Exceptions::RepositoryBuildError.new(
raise OpenProject::SCM::Exceptions::RepositoryBuildError.new(
I18n.t('repositories.managed.error_not_manageable')
)
end
@ -51,7 +51,7 @@ class Repository::Subversion < Repository
end
def self.authorization_policy
::Scm::SubversionAuthorizationPolicy
::SCM::SubversionAuthorizationPolicy
end
def self.permitted_params(params)

@ -50,18 +50,28 @@ class TimeEntryActivity < Enumeration
def active_in_project?(project)
teap = if time_entry_activities_projects.loaded?
time_entry_activities_projects.detect { |t| t.project_id == project.id }&.active?
detect_project_time_entry_activity_active_state(project)
else
time_entry_activities_projects
.where(project_id: project.id)
.pluck(:active)
.first
pluck_project_time_entry_activity_active_state(project)
end
teap.present? && teap
!teap.nil? && teap || teap.nil? && active?
end
def activated_projects
Project::Scopes::ActivatedTimeActivity.fetch(self)
end
private
def detect_project_time_entry_activity_active_state(project)
time_entry_activities_projects.detect { |t| t.project_id == project.id }&.active?
end
def pluck_project_time_entry_activity_active_state(project)
time_entry_activities_projects
.where(project_id: project.id)
.pluck(:active)
.first
end
end

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

Loading…
Cancel
Save