Compare commits

..

60 Commits

Author SHA1 Message Date
Jean-Philippe Lang b0bf728118 tagged version 2.4.4
git-svn-id: http://svn.redmine.org/redmine/tags/2.4.4@12953 e93f8b46-1217-0410-a6f0-8f06a7374b81
2014-03-02 11:26:23 +00:00
Jean-Philippe Lang 92dce4f80d Updates for 2.4.4 release.
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12949 e93f8b46-1217-0410-a6f0-8f06a7374b81
2014-03-02 09:38:40 +00:00
Jean-Philippe Lang d05cbf4df8 Merged r12896 (#16081).
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12946 e93f8b46-1217-0410-a6f0-8f06a7374b81
2014-03-02 09:30:35 +00:00
Jean-Philippe Lang e9e0ffb6dd Merged r12941 (#16161).
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12943 e93f8b46-1217-0410-a6f0-8f06a7374b81
2014-02-28 12:53:34 +00:00
Jean-Philippe Lang 40abbfa82d Backported r12938 (#16169).
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12940 e93f8b46-1217-0410-a6f0-8f06a7374b81
2014-02-28 11:40:02 +00:00
Toshi MARUYAMA 8d7fd10f90 Merged r12935 from trunk to 2.4-stable (#16177)
use Python getattr instead of hasattr.

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12937 e93f8b46-1217-0410-a6f0-8f06a7374b81
2014-02-26 16:55:12 +00:00
Toshi MARUYAMA dac4f38eb4 Merged r12930 from trunk to 2.4-stable (#16177)
Mercurial 2.9 compatibility.

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12934 e93f8b46-1217-0410-a6f0-8f06a7374b81
2014-02-26 13:06:02 +00:00
Toshi MARUYAMA 5e031bce47 Merged r12929 from trunk to 2.4-stable (#16177)
scm: mercurial: add one "closed" branch to test repository.

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12933 e93f8b46-1217-0410-a6f0-8f06a7374b81
2014-02-26 13:05:45 +00:00
Toshi MARUYAMA dc431edbc8 Merged r12906 from trunk to 2.4-stable
update Rails to 3.2.17

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12908 e93f8b46-1217-0410-a6f0-8f06a7374b81
2014-02-19 10:19:06 +00:00
Jean-Philippe Lang 8980f7a01a Merged r12861, updates for 2.4.3 release.
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12862 e93f8b46-1217-0410-a6f0-8f06a7374b81
2014-02-08 08:01:44 +00:00
Jean-Philippe Lang c47aacd6b0 Merged r12736 (#15977).
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12860 e93f8b46-1217-0410-a6f0-8f06a7374b81
2014-02-08 07:58:49 +00:00
Jean-Philippe Lang 287bcacd81 Merged r12848 (#16032).
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12853 e93f8b46-1217-0410-a6f0-8f06a7374b81
2014-02-07 08:09:36 +00:00
Jean-Philippe Lang d6b718b43c Merged r12844 (#16038).
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12852 e93f8b46-1217-0410-a6f0-8f06a7374b81
2014-02-07 08:06:38 +00:00
Jean-Philippe Lang b8ffc995c1 Merged r12745 (#15870).
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12847 e93f8b46-1217-0410-a6f0-8f06a7374b81
2014-02-06 18:00:37 +00:00
Jean-Philippe Lang f52253c2ef Merged r12652 (#15664).
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12846 e93f8b46-1217-0410-a6f0-8f06a7374b81
2014-02-06 17:59:08 +00:00
Jean-Philippe Lang a11aec0d4c Merged r12660 (#13544).
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12845 e93f8b46-1217-0410-a6f0-8f06a7374b81
2014-02-06 17:58:24 +00:00
Toshi MARUYAMA a9f44b323f Merged r12695 from trunk to 2.4-stable (#15960)
pt-BR translation for 2.4-stable updated by Leandro Gehlen.

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12697 e93f8b46-1217-0410-a6f0-8f06a7374b81
2014-01-25 05:11:54 +00:00
Toshi MARUYAMA fbeba062cf Merged r12456 from trunk to 2.4-stable (#15756)
change requirement in bazaar lib as same with other scm libs.

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12458 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-12-24 07:17:01 +00:00
Toshi MARUYAMA 37a07789d1 backport r12453 AbstractAdapter change to 2.4-stable (#15756)
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12454 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-12-24 05:00:26 +00:00
Jean-Philippe Lang 742ab6f6b3 Merged r12447 for 2.4.2 release.
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12448 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-12-23 08:47:47 +00:00
Jean-Philippe Lang aacaa9da8e Merged r12438 (#15735).
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12444 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-12-22 14:48:46 +00:00
Jean-Philippe Lang 9ebcb1e734 Merged r12435 (#15741).
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12436 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-12-21 11:35:02 +00:00
Jean-Philippe Lang 36c35080b2 Merged r12419.
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12434 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-12-21 11:26:01 +00:00
Jean-Philippe Lang beed3c5746 Merged r12420 (#15684).
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12433 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-12-21 11:25:25 +00:00
Jean-Philippe Lang fa2b39bf73 Merged r12415 (#15677).
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12432 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-12-21 11:24:41 +00:00
Jean-Philippe Lang c295f67329 Merged r12414 (#15623).
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12431 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-12-21 11:23:58 +00:00
Jean-Philippe Lang 624b0071ad Merged r12349 (#15523).
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12430 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-12-21 11:23:12 +00:00
Jean-Philippe Lang 8ea273d427 Merged r12309 (#15398).
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12429 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-12-21 11:21:59 +00:00
Toshi MARUYAMA d05042fbb5 Merged r12424 from trunk to 2.4-stable.
fix svn raw diff failure on svn version 1.6.17 (revision 1128011).

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12425 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-12-20 17:46:11 +00:00
Toshi MARUYAMA 00ff3361d6 Merged r12408 from trunk to 2.4-stable (#15696)
Russian translation updated by Alex Stein.

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12411 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-12-15 05:30:32 +00:00
Toshi MARUYAMA 4f937b42ac Merged r12407 from trunk to 2.4-stable (#15688)
Spanish translation updated by Borja Campina.

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12410 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-12-15 05:30:15 +00:00
Toshi MARUYAMA 64a72f2ea2 Merged r12273 from trunk to 2.4-stable.
Fixed uninitialized constant Redmine::Scm::Adapters::CommandFailed error when reloading in development mode.

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12389 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-12-09 05:44:08 +00:00
Toshi MARUYAMA 260d6d2c78 Merged r12387 from trunk to 2.4-stable.
fix disable to run test environment.

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12388 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-12-09 04:55:22 +00:00
Toshi MARUYAMA 5b1073095a Merged r12367 and r12368 from trunk to 2.4-stable
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12369 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-12-07 03:51:26 +00:00
Toshi MARUYAMA 6d33524888 Merged r12365 from trunk to 2.4-stable
prevent i18n deprecated warning

i18n 0.6.4 on Rails 3.2.16 changes behavior.
3b6e56e06f

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12366 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-12-07 02:06:53 +00:00
Toshi MARUYAMA b8a93cd445 Merged r12358 from trunk to 2.4-stable (#15601)
Turkish translation updated by Mert Salih Kaplan.

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12359 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-12-05 03:26:03 +00:00
Toshi MARUYAMA 3c0114f020 Merged r12355 from trunk to 2.4-stable.
update Rails version 3.2.16.

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12357 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-12-04 08:33:22 +00:00
Toshi MARUYAMA cfe0ff70fb Merged r12340 from trunk to 2.4-stable.
use escaping html in wiki_syntax_detailed.html.

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12342 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-25 05:46:19 +00:00
Toshi MARUYAMA afab881b01 Merged r12339 from trunk to 2.4-stable (#15524)
Japanese translation updated by Go MAEDA.

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12341 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-25 05:45:43 +00:00
Toshi MARUYAMA f9d083a94b Merged r12331 from trunk to 2.4-stable (#9442)
use escaping html in Russian wiki_syntax.html.

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12332 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-23 23:42:55 +00:00
Toshi MARUYAMA 5ae3de8c06 Merged r12326 from trunk to 2.4-stable (#9442)
Russian wiki syntax help translations by Denis Savitsky.

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12327 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-23 18:52:09 +00:00
Jean-Philippe Lang 931c198de4 Merged r12314.
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12315 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-23 08:27:16 +00:00
Jean-Philippe Lang bf3d9f0851 Merged r12310 (#15414).
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12313 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-23 08:24:00 +00:00
Jean-Philippe Lang a74ffeff07 Merged r12311 (#15427).
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12312 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-22 23:28:12 +00:00
Toshi MARUYAMA 1cf93f6e32 Merged r12302 from trunk to 2.4-stable (#15391, #15401)
Fix wiki syntax "bold italic".

Contributed by Karel Pičman.

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12307 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-19 13:22:50 +00:00
Toshi MARUYAMA cd13c5e6d1 Merged r12301 from trunk to 2.4-stable (#15391, #15400)
Czech Wiki syntax traslation updated by Karel Pičman.

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12306 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-19 13:22:11 +00:00
Toshi MARUYAMA 9bae5a7695 Merged r12300 from trunk to 2.4-stable (#15395)
German translation updated by Daniel Felix.

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12305 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-19 13:21:59 +00:00
Toshi MARUYAMA 496abc8ee3 Merged r12299 from trunk to 2.4-stable (#15391, #15402)
Czech traslation for 2.4-stable updated by Karel Pičman.

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12304 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-19 13:21:48 +00:00
Toshi MARUYAMA caed8500a3 Merged r12296 from trunk to 2.4-stable (#15376)
Traditional Chinese translation updated by ChunChang Lo.

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12297 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-18 08:24:02 +00:00
Jean-Philippe Lang 80aa5f4058 Merged r12292.
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12293 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-17 11:23:12 +00:00
Jean-Philippe Lang 6ff53935a2 Merged r12288 (#15369).
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12289 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-16 08:48:17 +00:00
Jean-Philippe Lang b5a605c808 Fixed UI tests.
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12286 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-16 08:45:02 +00:00
Jean-Philippe Lang 4710cd8e6f Merged r12265, r12268, r12272 (#15307).
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12282 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-16 07:48:27 +00:00
Jean-Philippe Lang a435ca7278 Merged r12278 (#15311).
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12281 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-15 20:35:37 +00:00
Jean-Philippe Lang c462a8790e Merged r12277 (#15344).
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12280 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-15 20:33:35 +00:00
Toshi MARUYAMA 42a2dbd4ab Merged r12274 from trunk to 2.4-stable (#13348)
fix repository tree can't handle two loading at once.

Contributed by Vadim Pushtaev.

git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12276 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-13 10:16:02 +00:00
Jean-Philippe Lang 6628610ed6 Merged r12267.
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12271 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-11 21:41:10 +00:00
Jean-Philippe Lang ddef51599b Merged r12269.
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12270 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-11 21:16:05 +00:00
Jean-Philippe Lang 84100235e0 Set version to 2.4.0 stable.
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12264 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-10 11:21:50 +00:00
Jean-Philippe Lang 000ad0ac6f Added 2.4-stable branch.
git-svn-id: http://svn.redmine.org/redmine/branches/2.4-stable@12262 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-11-10 11:11:34 +00:00
424 changed files with 5125 additions and 9667 deletions

View File

@ -1,38 +0,0 @@
# Redmine runs tests on own continuous integration server.
# http://www.redmine.org/projects/redmine/wiki/Continuous_integration
# You can also run tests on your environment.
language: ruby
rvm:
- 1.8.7
- 1.9.3
- 2.0
- 2.1
- jruby
matrix:
allow_failures:
# SCM tests fail randomly due to IO.popen().
# https://github.com/jruby/jruby/issues/779
- rvm: jruby
env:
- "TEST_SUITE=units DATABASE_ADAPTER=postgresql"
- "TEST_SUITE=functionals DATABASE_ADAPTER=postgresql"
- "TEST_SUITE=integration DATABASE_ADAPTER=postgresql"
- "TEST_SUITE=units DATABASE_ADAPTER=mysql"
- "TEST_SUITE=functionals DATABASE_ADAPTER=mysql"
- "TEST_SUITE=integration DATABASE_ADAPTER=mysql"
- "TEST_SUITE=units DATABASE_ADAPTER=sqlite3"
- "TEST_SUITE=functionals DATABASE_ADAPTER=sqlite3"
- "TEST_SUITE=integration DATABASE_ADAPTER=sqlite3"
before_install:
- "sudo apt-get update -qq"
- "sudo apt-get --no-install-recommends install bzr cvs git mercurial subversion"
script:
- "SCMS=bazaar,cvs,subversion,git,mercurial,filesystem"
- "export SCMS"
- "git --version"
- "bundle install"
- "RUN_ON_NOT_OFFICIAL='' RUBY_VER=1.9 BRANCH=trunk bundle exec rake config/database.yml"
- "bundle install"
- "JRUBY_OPTS=-J-Xmx1024m bundle exec rake ci"
notifications:
email: false

16
Gemfile
View File

@ -1,13 +1,10 @@
source 'https://rubygems.org'
gem "rails", "3.2.18"
gem "rake", "~> 10.1.1"
gem "rails", "3.2.17"
gem "jquery-rails", "~> 2.0.2"
gem "coderay", "~> 1.1.0"
gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby]
gem "builder", "3.0.0"
gem "request_store"
gem "mime-types"
# Optional gem for LDAP authentication
group :ldap do
@ -20,20 +17,14 @@ group :openid do
gem "rack-openid"
end
# Optional gem for exporting the gantt to a PNG file, not supported with jruby
platforms :mri, :mingw do
# Optional gem for exporting the gantt to a PNG file, not supported with jruby
group :rmagick do
# RMagick 2 supports ruby 1.9
# RMagick 1 would be fine for ruby 1.8 but Bundler does not support
# different requirements for the same gem on different platforms
gem "rmagick", ">= 2.0.0"
end
# Optional Markdown support, not for JRuby
group :markdown do
# TODO: upgrade to redcarpet 3.x when ruby1.8 support is dropped
gem "redcarpet", "~> 2.3.0"
end
end
platforms :jruby do
@ -86,10 +77,11 @@ end
group :test do
gem "shoulda", "~> 3.3.2"
gem "mocha", "~> 1.0.0", :require => 'mocha/api'
gem "mocha", ">= 0.14", :require => 'mocha/api'
if RUBY_VERSION >= '1.9.3'
gem "capybara", "~> 2.1.0"
gem "selenium-webdriver"
gem "database_cleaner"
end
end

View File

@ -34,7 +34,7 @@ class AccountController < ApplicationController
def login
if request.get?
if User.current.logged?
redirect_back_or_default home_url, :referer => true
redirect_to home_url
end
else
authenticate_user

View File

@ -44,7 +44,6 @@ class ApplicationController < ActionController::Base
unless api_request?
super
cookies.delete(autologin_cookie_name)
self.logged_user = nil
render_error :status => 422, :message => "Invalid form authenticity token."
end
end
@ -120,7 +119,7 @@ class ApplicationController < ActionController::Base
if (key = api_key_from_request)
# Use API key
user = User.find_by_api_key(key)
elsif request.authorization.to_s =~ /\ABasic /i
else
# HTTP Basic, either username/password or API key/random
authenticate_with_http_basic do |username, password|
user = User.try_to_login(username, password) || User.find_by_api_key(username)
@ -202,7 +201,7 @@ class ApplicationController < ActionController::Base
if User.current.logged?
lang = find_language(User.current.language)
end
if lang.nil? && !Setting.force_default_language_for_anonymous? && request.env['HTTP_ACCEPT_LANGUAGE']
if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
if !accept_lang.blank?
accept_lang = accept_lang.downcase
@ -374,13 +373,13 @@ class ApplicationController < ActionController::Base
url
end
def redirect_back_or_default(default, options={})
def redirect_back_or_default(default)
back_url = params[:back_url].to_s
if back_url.present?
begin
uri = URI.parse(back_url)
# do not redirect user to another host or to the login or register page
if ((uri.relative? && back_url.match(%r{\A/(\w.*)?\z})) || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
redirect_to(back_url)
return
end
@ -388,9 +387,6 @@ class ApplicationController < ActionController::Base
logger.warn("Could not redirect to invalid URL #{back_url}")
# redirect to default
end
elsif options[:referer]
redirect_to_referer_or default
return
end
redirect_to default
false
@ -558,7 +554,7 @@ class ApplicationController < ActionController::Base
# Returns a string that can be used as filename value in Content-Disposition header
def filename_for_content_disposition(name)
request.env['HTTP_USER_AGENT'] =~ %r{(MSIE|Trident)} ? ERB::Util.url_encode(name) : name
request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
end
def api_request?

View File

@ -55,10 +55,12 @@ class ContextMenusController < ApplicationController
@options_by_custom_field = {}
if @can[:edit]
custom_fields = @issues.map(&:editable_custom_fields).reduce(:&).reject(&:multiple?)
custom_fields = @issues.map(&:available_custom_fields).reduce(:&).select do |f|
%w(bool list user version).include?(f.field_format) && !f.multiple?
end
custom_fields.each do |field|
values = field.possible_values_options(@projects)
if values.present?
if values.any?
@options_by_custom_field[field] = values
end
end

View File

@ -27,6 +27,7 @@ class CustomFieldsController < ApplicationController
respond_to do |format|
format.html {
@custom_fields_by_type = CustomField.all.group_by {|f| f.class.name }
@tab = params[:tab] || 'IssueCustomField'
}
format.api {
@custom_fields = CustomField.all
@ -35,8 +36,6 @@ class CustomFieldsController < ApplicationController
end
def new
@custom_field.field_format = 'string' if @custom_field.field_format.blank?
@custom_field.default_value = nil
end
def create
@ -76,7 +75,9 @@ class CustomFieldsController < ApplicationController
def build_new_custom_field
@custom_field = CustomField.new_subclass_instance(params[:type], params[:custom_field])
if @custom_field.nil?
render :action => 'select_type'
render_404
else
@custom_field.default_value = nil
end
end

View File

@ -90,7 +90,7 @@ class GroupsController < ApplicationController
end
def add_users
@users = User.where(:id => (params[:user_id] || params[:user_ids])).all
@users = User.find_all_by_id(params[:user_id] || params[:user_ids])
@group.users << @users if request.post?
respond_to do |format|
format.html { redirect_to edit_group_path(@group, :tab => 'users') }

View File

@ -29,7 +29,7 @@ class IssueStatusesController < ApplicationController
render :action => "index", :layout => false if request.xhr?
}
format.api {
@issue_statuses = IssueStatus.order('position').all
@issue_statuses = IssueStatus.all(:order => 'position')
}
end
end

View File

@ -62,14 +62,10 @@ class IssuesController < ApplicationController
case params[:format]
when 'csv', 'pdf'
@limit = Setting.issues_export_limit.to_i
if params[:columns] == 'all'
@query.column_names = @query.available_inline_columns.map(&:name)
end
when 'atom'
@limit = Setting.feeds_limit.to_i
when 'xml', 'json'
@offset, @limit = api_offset_and_limit
@query.column_names = %w(author)
else
@limit = per_page_option
end
@ -301,7 +297,7 @@ class IssuesController < ApplicationController
else
@saved_issues = @issues
@unsaved_issues = unsaved_issues
@issues = Issue.visible.where(:id => @unsaved_issues.map(&:id)).all
@issues = Issue.visible.find_all_by_id(@unsaved_issues.map(&:id))
bulk_edit
render :action => 'bulk_edit'
end
@ -314,15 +310,14 @@ class IssuesController < ApplicationController
when 'destroy'
# nothing to do
when 'nullify'
TimeEntry.where(['issue_id IN (?)', @issues]).update_all('issue_id = NULL')
TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
when 'reassign'
reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
if reassign_to.nil?
flash.now[:error] = l(:error_issue_not_found_in_project)
return
else
TimeEntry.where(['issue_id IN (?)', @issues]).
update_all("issue_id = #{reassign_to.id}")
TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
end
else
# display the destroy form if it's a user request
@ -432,10 +427,7 @@ class IssuesController < ApplicationController
@priorities = IssuePriority.active
@allowed_statuses = @issue.new_statuses_allowed_to(User.current, @issue.new_record?)
@available_watchers = @issue.watcher_users
if @issue.project.users.count <= 20
@available_watchers = (@available_watchers + @issue.project.users.sort).uniq
end
@available_watchers = (@issue.project.users.sort + @issue.watcher_users).uniq
end
def check_for_default_issue_status

View File

@ -66,7 +66,7 @@ class JournalsController < ApplicationController
text = @issue.description
end
# Replaces pre blocks with [...]
text = text.to_s.strip.gsub(%r{<pre>(.*?)</pre>}m, '[...]')
text = text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]')
@content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> "
@content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
rescue ActiveRecord::RecordNotFound

View File

@ -28,11 +28,12 @@ class MembersController < ApplicationController
@member_count = @project.member_principals.count
@member_pages = Paginator.new @member_count, @limit, params['page']
@offset ||= @member_pages.offset
@members = @project.member_principals.
order("#{Member.table_name}.id").
limit(@limit).
offset(@offset).
all
@members = @project.member_principals.all(
:order => "#{Member.table_name}.id",
:limit => @limit,
:offset => @offset
)
respond_to do |format|
format.html { head 406 }
format.api

View File

@ -113,7 +113,7 @@ class MessagesController < ApplicationController
@subject = "RE: #{@subject}" unless @subject.starts_with?('RE:')
@content = "#{ll(Setting.default_language, :text_user_wrote, @message.author)}\n> "
@content << @message.content.to_s.strip.gsub(%r{<pre>(.*?)</pre>}m, '[...]').gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
@content << @message.content.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
end
def preview
@ -126,14 +126,14 @@ class MessagesController < ApplicationController
private
def find_message
return unless find_board
@message = @board.messages.includes(:parent).find(params[:id])
@message = @board.messages.find(params[:id], :include => :parent)
@topic = @message.root
rescue ActiveRecord::RecordNotFound
render_404
end
def find_board
@board = Board.includes(:project).find(params[:board_id])
@board = Board.find(params[:board_id], :include => :project)
@project = @board.project
rescue ActiveRecord::RecordNotFound
render_404

View File

@ -139,7 +139,7 @@ class MyController < ApplicationController
@blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
@block_options = []
BLOCKS.each do |k, v|
unless @blocks.values.flatten.include?(k)
unless %w(top left right).detect {|f| (@blocks[f] ||= []).include?(k)}
@block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]
end
end

View File

@ -42,11 +42,11 @@ class NewsController < ApplicationController
@news_count = scope.count
@news_pages = Paginator.new @news_count, @limit, params['page']
@offset ||= @news_pages.offset
@newss = scope.includes([:author, :project]).
order("#{News.table_name}.created_on DESC").
limit(@limit).
offset(@offset).
all
@newss = scope.all(:include => [:author, :project],
:order => "#{News.table_name}.created_on DESC",
:offset => @offset,
:limit => @limit)
respond_to do |format|
format.html {
@news = News.new # for adding news inline

View File

@ -151,8 +151,8 @@ class ProjectsController < ApplicationController
cond = @project.project_condition(Setting.display_subprojects_issues?)
@open_issues_by_tracker = Issue.visible.open.where(cond).group(:tracker).count
@total_issues_by_tracker = Issue.visible.where(cond).group(:tracker).count
@open_issues_by_tracker = Issue.visible.open.where(cond).count(:group => :tracker)
@total_issues_by_tracker = Issue.visible.where(cond).count(:group => :tracker)
if User.current.allowed_to?(:view_time_entries, @project)
@total_hours = TimeEntry.visible.where(cond).sum(:hours).to_f

View File

@ -31,13 +31,11 @@ class QueriesController < ApplicationController
else
@limit = per_page_option
end
@query_count = IssueQuery.visible.count
@query_pages = Paginator.new @query_count, @limit, params['page']
@queries = IssueQuery.visible.
order("#{Query.table_name}.name").
limit(@limit).
offset(@offset).
all
@queries = IssueQuery.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name")
respond_to do |format|
format.api
end

View File

@ -94,7 +94,7 @@ class RepositoriesController < ApplicationController
@committers = @repository.committers
@users = @project.users
additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id)
@users += User.where(:id => additional_user_ids).all unless additional_user_ids.empty?
@users += User.find_all_by_id(additional_user_ids) unless additional_user_ids.empty?
@users.compact!
@users.sort!
if request.post? && params[:committers].is_a?(Hash)
@ -411,7 +411,7 @@ class RepositoriesController < ApplicationController
commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
# Remove email address in usernames
# Remove email adress in usernames
fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
graph = SVG::Graph::BarHorizontal.new(

View File

@ -232,7 +232,7 @@ private
end
def find_time_entries
@time_entries = TimeEntry.where(:id => params[:id] || params[:ids]).all
@time_entries = TimeEntry.find_all_by_id(params[:id] || params[:ids])
raise ActiveRecord::RecordNotFound if @time_entries.empty?
@projects = @time_entries.collect(&:project).compact.uniq
@project = @projects.first if @projects.size == 1

View File

@ -90,9 +90,11 @@ class UsersController < ApplicationController
@user.admin = params[:user][:admin] || false
@user.login = params[:user][:login]
@user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id
@user.pref.attributes = params[:pref]
if @user.save
@user.pref.attributes = params[:pref]
@user.pref.save
Mailer.account_information(@user, @user.password).deliver if params[:send_information]
respond_to do |format|

View File

@ -30,7 +30,6 @@ class WatchersController < ApplicationController
accept_api_auth :create, :destroy
def new
@users = users_for_new_watcher
end
def create
@ -45,7 +44,7 @@ class WatchersController < ApplicationController
end
respond_to do |format|
format.html { redirect_to_referer_or {render :text => 'Watcher added.', :layout => true}}
format.js { @users = users_for_new_watcher }
format.js
format.api { render_api_ok }
end
end
@ -53,10 +52,7 @@ class WatchersController < ApplicationController
def append
if params[:watcher].is_a?(Hash)
user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
@users = User.active.where(:id => user_ids).all
end
if @users.blank?
render :nothing => true
@users = User.active.find_all_by_id(user_ids)
end
end
@ -70,7 +66,10 @@ class WatchersController < ApplicationController
end
def autocomplete_for_user
@users = users_for_new_watcher
@users = User.active.sorted.like(params[:q]).limit(100).all
if @watched
@users -= @watched.watcher_users
end
render :layout => false
end
@ -92,14 +91,8 @@ class WatchersController < ApplicationController
def find_watchables
klass = Object.const_get(params[:object_type].camelcase) rescue nil
if klass && klass.respond_to?('watched_by')
@watchables = klass.where(:id => Array.wrap(params[:object_id])).all
raise Unauthorized if @watchables.any? {|w|
if w.respond_to?(:visible?)
!w.visible?
elsif w.respond_to?(:project) && w.project
!w.project.visible?
end
}
@watchables = klass.find_all_by_id(Array.wrap(params[:object_id]))
raise Unauthorized if @watchables.any? {|w| w.respond_to?(:visible?) && !w.visible?}
end
render_404 unless @watchables.present?
end
@ -113,17 +106,4 @@ class WatchersController < ApplicationController
format.js { render :partial => 'set_watcher', :locals => {:user => user, :watched => watchables} }
end
end
def users_for_new_watcher
users = []
if params[:q].blank? && @project.present?
users = @project.users.sorted
else
users = User.active.sorted.like(params[:q]).limit(100)
end
if @watched
users -= @watched.watcher_users
end
users
end
end

View File

@ -277,19 +277,14 @@ class WikiController < ApplicationController
# Export wiki to a single pdf or html file
def export
@pages = @wiki.pages.
order('title').
includes([:content, {:attachments => :author}]).
all
@pages = @wiki.pages.all(:order => 'title', :include => [:content, {:attachments => :author}])
respond_to do |format|
format.html {
export = render_to_string :action => 'export_multiple', :layout => false
send_data(export, :type => 'text/html', :filename => "wiki.html")
}
format.pdf {
send_data(wiki_pages_to_pdf(@pages, @project),
:type => 'application/pdf',
:filename => "#{@project.identifier}.pdf")
send_data(wiki_pages_to_pdf(@pages, @project), :type => 'application/pdf', :filename => "#{@project.identifier}.pdf")
}
end
end
@ -356,10 +351,6 @@ private
end
def load_pages_for_index
@pages = @wiki.pages.with_updated_on.
reorder("#{WikiPage.table_name}.title").
includes(:wiki => :project).
includes(:parent).
all
@pages = @wiki.pages.with_updated_on.reorder("#{WikiPage.table_name}.title").includes(:wiki => :project).includes(:parent).all
end
end

View File

@ -18,30 +18,39 @@
class WorkflowsController < ApplicationController
layout 'admin'
before_filter :require_admin
before_filter :require_admin, :find_roles, :find_trackers
def index
@workflow_counts = WorkflowTransition.count_by_tracker_and_role
end
def edit
find_trackers_roles_and_statuses_for_edit
@role = Role.find_by_id(params[:role_id]) if params[:role_id]
@tracker = Tracker.find_by_id(params[:tracker_id]) if params[:tracker_id]
if request.post? && @roles && @trackers && params[:transitions]
transitions = params[:transitions].deep_dup
transitions.each do |old_status_id, transitions_by_new_status|
transitions_by_new_status.each do |new_status_id, transition_by_rule|
transition_by_rule.reject! {|rule, transition| transition == 'no_change'}
end
if request.post?
WorkflowTransition.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id])
(params[:issue_status] || []).each { |status_id, transitions|
transitions.each { |new_status_id, options|
author = options.is_a?(Array) && options.include?('author') && !options.include?('always')
assignee = options.is_a?(Array) && options.include?('assignee') && !options.include?('always')
WorkflowTransition.create(:role_id => @role.id, :tracker_id => @tracker.id, :old_status_id => status_id, :new_status_id => new_status_id, :author => author, :assignee => assignee)
}
}
if @role.save
redirect_to workflows_edit_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only])
return
end
WorkflowTransition.replace_transitions(@trackers, @roles, transitions)
flash[:notice] = l(:notice_successful_update)
redirect_to_referer_or workflows_edit_path
return
end
if @trackers && @roles && @statuses.any?
workflows = WorkflowTransition.where(:role_id => @roles.map(&:id), :tracker_id => @trackers.map(&:id))
@used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
if @tracker && @used_statuses_only && @tracker.issue_statuses.any?
@statuses = @tracker.issue_statuses
end
@statuses ||= IssueStatus.sorted.all
if @tracker && @role && @statuses.any?
workflows = WorkflowTransition.where(:role_id => @role.id, :tracker_id => @tracker.id).all
@workflows = {}
@workflows['always'] = workflows.select {|w| !w.author && !w.assignee}
@workflows['author'] = workflows.select {|w| w.author}
@ -50,30 +59,35 @@ class WorkflowsController < ApplicationController
end
def permissions
find_trackers_roles_and_statuses_for_edit
@role = Role.find_by_id(params[:role_id]) if params[:role_id]
@tracker = Tracker.find_by_id(params[:tracker_id]) if params[:tracker_id]
if request.post? && @roles && @trackers && params[:permissions]
permissions = params[:permissions].deep_dup
permissions.each { |field, rule_by_status_id|
rule_by_status_id.reject! {|status_id, rule| rule == 'no_change'}
}
WorkflowPermission.replace_permissions(@trackers, @roles, permissions)
flash[:notice] = l(:notice_successful_update)
redirect_to_referer_or workflows_permissions_path
if request.post? && @role && @tracker
WorkflowPermission.replace_permissions(@tracker, @role, params[:permissions] || {})
redirect_to workflows_permissions_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only])
return
end
if @roles && @trackers
@fields = (Tracker::CORE_FIELDS_ALL - @trackers.map(&:disabled_core_fields).reduce(:&)).map {|field| [field, l("field_"+field.sub(/_id$/, ''))]}
@custom_fields = @trackers.map(&:custom_fields).flatten.uniq.sort
@permissions = WorkflowPermission.rules_by_status_id(@trackers, @roles)
@used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
if @tracker && @used_statuses_only && @tracker.issue_statuses.any?
@statuses = @tracker.issue_statuses
end
@statuses ||= IssueStatus.sorted.all
if @role && @tracker
@fields = (Tracker::CORE_FIELDS_ALL - @tracker.disabled_core_fields).map {|field| [field, l("field_"+field.sub(/_id$/, ''))]}
@custom_fields = @tracker.custom_fields
@permissions = WorkflowPermission.where(:tracker_id => @tracker.id, :role_id => @role.id).all.inject({}) do |h, w|
h[w.old_status_id] ||= {}
h[w.old_status_id][w.field_name] = w.rule
h
end
@statuses.each {|status| @permissions[status.id] ||= {}}
end
end
def copy
@roles = Role.sorted
@trackers = Tracker.sorted
if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any'
@source_tracker = nil
@ -85,10 +99,10 @@ class WorkflowsController < ApplicationController
else
@source_role = Role.find_by_id(params[:source_role_id].to_i)
end
@target_trackers = params[:target_tracker_ids].blank? ?
nil : Tracker.where(:id => params[:target_tracker_ids]).all
@target_roles = params[:target_role_ids].blank? ?
nil : Role.where(:id => params[:target_role_ids]).all
@target_trackers = params[:target_tracker_ids].blank? ? nil : Tracker.find_all_by_id(params[:target_tracker_ids])
@target_roles = params[:target_role_ids].blank? ? nil : Role.find_all_by_id(params[:target_role_ids])
if request.post?
if params[:source_tracker_id].blank? || params[:source_role_id].blank? || (@source_tracker.nil? && @source_role.nil?)
flash.now[:error] = l(:error_workflow_copy_source)
@ -104,37 +118,11 @@ class WorkflowsController < ApplicationController
private
def find_trackers_roles_and_statuses_for_edit
find_roles
find_trackers
find_statuses
end
def find_roles
ids = Array.wrap(params[:role_id])
if ids == ['all']
@roles = Role.sorted.all
elsif ids.present?
@roles = Role.where(:id => ids).all
end
@roles = nil if @roles.blank?
@roles = Role.sorted.all
end
def find_trackers
ids = Array.wrap(params[:tracker_id])
if ids == ['all']
@trackers = Tracker.sorted.all
elsif ids.present?
@trackers = Tracker.where(:id => ids).all
end
@trackers = nil if @trackers.blank?
end
def find_statuses
@used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
if @trackers && @used_statuses_only
@statuses = @trackers.map(&:issue_statuses).flatten.uniq.sort.presence
end
@statuses ||= IssueStatus.sorted.all
@trackers = Tracker.sorted.all
end
end

View File

@ -24,12 +24,4 @@ module AdminHelper
[l(:project_status_closed), '5'],
[l(:project_status_archived), '9']], selected.to_s)
end
def plugin_data_for_updates(plugins)
data = {"v" => Redmine::VERSION.to_s, "p" => {}}
plugins.each do |plugin|
data["p"].merge! plugin.id => {"v" => plugin.version, "n" => plugin.name, "a" => plugin.author}
end
data
end
end

View File

@ -72,16 +72,15 @@ module ApplicationHelper
subject = nil
text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}"
if options[:subject] == false
title = issue.subject.truncate(60)
title = truncate(issue.subject, :length => 60)
else
subject = issue.subject
if truncate_length = options[:truncate]
subject = subject.truncate(truncate_length)
if options[:truncate]
subject = truncate(subject, :length => options[:truncate])
end
end
only_path = options[:only_path].nil? ? true : options[:only_path]
s = link_to(text, issue_path(issue, :only_path => only_path),
:class => issue.css_classes, :title => title)
s = link_to text, issue_path(issue, :only_path => only_path), :class => issue.css_classes, :title => title
s << h(": #{subject}") if subject
s = h("#{issue.project} - ") + s if options[:project]
s
@ -118,7 +117,7 @@ module ApplicationHelper
# Generates a link to a message
def link_to_message(message, options={}, html_options = nil)
link_to(
message.subject.truncate(60),
truncate(message.subject, :length => 60),
board_message_path(message.board_id, message.parent_id || message.id, {
:r => (message.parent_id && message.id),
:anchor => (message.parent_id ? "message-#{message.id}" : nil)
@ -157,50 +156,6 @@ module ApplicationHelper
end
end
# Helper that formats object for html or text rendering
def format_object(object, html=true, &block)
if block_given?
object = yield object
end
case object.class.name
when 'Array'
object.map {|o| format_object(o, html)}.join(', ').html_safe
when 'Time'
format_time(object)
when 'Date'
format_date(object)
when 'Fixnum'
object.to_s
when 'Float'
sprintf "%.2f", object
when 'User'
html ? link_to_user(object) : object.to_s
when 'Project'
html ? link_to_project(object) : object.to_s
when 'Version'
html ? link_to(object.name, version_path(object)) : object.to_s
when 'TrueClass'
l(:general_text_Yes)
when 'FalseClass'
l(:general_text_No)
when 'Issue'
object.visible? && html ? link_to_issue(object) : "##{object.id}"
when 'CustomValue', 'CustomFieldValue'
if object.custom_field
f = object.custom_field.format.formatted_custom_value(self, object, html)
if f.nil? || f.is_a?(String)
f
else
format_object(f, html, &block)
end
else
object.value.to_s
end
else
html ? h(object) : object.to_s
end
end
def wiki_page_path(page, options={})
url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
end
@ -227,7 +182,7 @@ module ApplicationHelper
end
def format_activity_title(text)
h(truncate_single_line_raw(text, 100))
h(truncate_single_line(text, :length => 100))
end
def format_activity_day(date)
@ -235,7 +190,7 @@ module ApplicationHelper
end
def format_activity_description(text)
h(text.to_s.truncate(120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
).gsub(/[\r\n]+/, "<br />").html_safe
end
@ -312,13 +267,9 @@ module ApplicationHelper
end
# Renders tabs and their content
def render_tabs(tabs, selected=params[:tab])
def render_tabs(tabs)
if tabs.any?
unless tabs.detect {|tab| tab[:name] == selected}
selected = nil
end
selected ||= tabs.first[:name]
render :partial => 'common/tabs', :locals => {:tabs => tabs, :selected_tab => selected}
render :partial => 'common/tabs', :locals => {:tabs => tabs}
else
content_tag 'p', l(:label_no_data), :class => "nodata"
end
@ -404,17 +355,9 @@ module ApplicationHelper
# Truncates and returns the string as a single line
def truncate_single_line(string, *args)
ActiveSupport::Deprecation.warn(
"ApplicationHelper#truncate_single_line is deprecated and will be removed in Rails 4 poring")
# Rails 4 ActionView::Helpers::TextHelper#truncate escapes.
# So, result is broken.
truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
end
def truncate_single_line_raw(string, length)
string.truncate(length).gsub(%r{[\r\n]+}m, ' ')
end
# Truncates at line break after 250 characters or options[:length]
def truncate_lines(string, options={})
length = options[:length] || 250
@ -765,30 +708,21 @@ module ApplicationHelper
repository = project.repository
end
# project.changesets.visible raises an SQL error because of a double join on repositories
if repository &&
(changeset = Changeset.visible.
find_by_repository_id_and_revision(repository.id, identifier))
link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"),
{:only_path => only_path, :controller => 'repositories',
:action => 'revision', :id => project,
:repository_id => repository.identifier_param,
:rev => changeset.revision},
:class => 'changeset',
:title => truncate_single_line_raw(changeset.comments, 100))
if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision},
:class => 'changeset',
:title => truncate_single_line(changeset.comments, :length => 100))
end
end
elsif sep == '#'
oid = identifier.to_i
case prefix
when nil
if oid.to_s == identifier &&
issue = Issue.visible.includes(:status).find_by_id(oid)
if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status)
anchor = comment_id ? "note-#{comment_id}" : nil
link = link_to(h("##{oid}#{comment_suffix}"),
{:only_path => only_path, :controller => 'issues',
:action => 'show', :id => oid, :anchor => anchor},
:class => issue.css_classes,
:title => "#{issue.subject.truncate(100)} (#{issue.status.name})")
link = link_to(h("##{oid}#{comment_suffix}"), {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
:class => issue.css_classes,
:title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
end
when 'document'
if document = Document.visible.find_by_id(oid)
@ -801,7 +735,7 @@ module ApplicationHelper
:class => 'version'
end
when 'message'
if message = Message.visible.includes(:parent).find_by_id(oid)
if message = Message.visible.find_by_id(oid, :include => :parent)
link = link_to_message(message, {:only_path => only_path}, :class => 'message')
end
when 'forum'
@ -822,7 +756,6 @@ module ApplicationHelper
elsif sep == ':'
# removes the double quotes if any
name = identifier.gsub(%r{^"(.*)"$}, "\\1")
name = CGI.unescapeHTML(name)
case prefix
when 'document'
if project && document = project.documents.visible.find_by_title(name)
@ -857,7 +790,7 @@ module ApplicationHelper
if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
:class => 'changeset',
:title => truncate_single_line_raw(changeset.comments, 100)
:title => truncate_single_line(changeset.comments, :length => 100)
end
else
if repository && User.current.allowed_to?(:browse_repository, project)
@ -873,8 +806,7 @@ module ApplicationHelper
repo_prefix = nil
end
when 'attachment'
attachments = options[:attachments] || []
attachments += obj.attachments if obj.respond_to?(:attachments)
attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
if attachments && attachment = Attachment.latest_attach(attachments, name)
link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
end
@ -982,20 +914,19 @@ module ApplicationHelper
end
end
TOC_RE = /<p>\{\{((<|&lt;)|(>|&gt;))?toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
# Renders the TOC with given headings
def replace_toc(text, headings)
text.gsub!(TOC_RE) do
left_align, right_align = $2, $3
# Keep only the 4 first levels
headings = headings.select{|level, anchor, item| level <= 4}
if headings.empty?
''
else
div_class = 'toc'
div_class << ' right' if right_align
div_class << ' left' if left_align
div_class << ' right' if $1 == '>'
div_class << ' left' if $1 == '<'
out = "<ul class=\"#{div_class}\"><li>"
root = headings.map(&:first).min
current = root
@ -1296,21 +1227,7 @@ module ApplicationHelper
end
def favicon
"<link rel='shortcut icon' href='#{favicon_path}' />".html_safe
end
# Returns the path to the favicon
def favicon_path
icon = (current_theme && current_theme.favicon?) ? current_theme.favicon_path : '/favicon.ico'
image_path(icon)
end
# Returns the full URL to the favicon
def favicon_url
# TODO: use #image_url introduced in Rails4
path = favicon_path
base = url_for(:controller => 'welcome', :action => 'index', :only_path => false)
base.sub(%r{/+$},'') + '/' + path.sub(%r{^/+},'')
"<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
end
def robot_exclusion_tag
@ -1332,7 +1249,7 @@ module ApplicationHelper
def api_meta(options)
if params[:nometa].present? || request.headers['X-Redmine-Nometa']
# compatibility mode for activeresource clients that raise
# an error when deserializing an array with attributes
# an error when unserializing an array with attributes
nil
else
options

View File

@ -40,48 +40,55 @@ module CustomFieldsHelper
:label => DocumentCategory::OptionName}
]
def render_custom_fields_tabs(types)
tabs = CUSTOM_FIELDS_TABS.select {|h| types.include?(h[:name]) }
render_tabs tabs
end
def custom_field_type_options
CUSTOM_FIELDS_TABS.map {|h| [l(h[:label]), h[:name]]}
end
def render_custom_field_format_partial(form, custom_field)
partial = custom_field.format.form_partial
if partial
render :partial => custom_field.format.form_partial, :locals => {:f => form, :custom_field => custom_field}
end
end
def custom_field_tag_name(prefix, custom_field)
name = "#{prefix}[custom_field_values][#{custom_field.id}]"
name << "[]" if custom_field.multiple?
name
end
def custom_field_tag_id(prefix, custom_field)
"#{prefix}_custom_field_values_#{custom_field.id}"
def custom_fields_tabs
CUSTOM_FIELDS_TABS
end
# Return custom field html tag corresponding to its format
def custom_field_tag(prefix, custom_value)
custom_value.custom_field.format.edit_tag self,
custom_field_tag_id(prefix, custom_value.custom_field),
custom_field_tag_name(prefix, custom_value.custom_field),
custom_value,
:class => "#{custom_value.custom_field.field_format}_cf"
def custom_field_tag(name, custom_value)
custom_field = custom_value.custom_field
field_name = "#{name}[custom_field_values][#{custom_field.id}]"
field_name << "[]" if custom_field.multiple?
field_id = "#{name}_custom_field_values_#{custom_field.id}"
tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"}
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
case field_format.try(:edit_as)
when "date"
text_field_tag(field_name, custom_value.value, tag_options.merge(:size => 10)) +
calendar_for(field_id)
when "text"
text_area_tag(field_name, custom_value.value, tag_options.merge(:rows => 3))
when "bool"
hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, tag_options)
when "list"
blank_option = ''.html_safe
unless custom_field.multiple?
if custom_field.is_required?
unless custom_field.default_value.present?
blank_option = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---", :value => '')
end
else
blank_option = content_tag('option')
end
end
s = select_tag(field_name, blank_option + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value),
tag_options.merge(:multiple => custom_field.multiple?))
if custom_field.multiple?
s << hidden_field_tag(field_name, '')
end
s
else
text_field_tag(field_name, custom_value.value, tag_options)
end
end
# Return custom field label tag
def custom_field_label_tag(name, custom_value, options={})
required = options[:required] || custom_value.custom_field.is_required?
title = custom_value.custom_field.description.presence
content = content_tag 'span', custom_value.custom_field.name, :title => title
content_tag "label", content +
content_tag "label", h(custom_value.custom_field.name) +
(required ? " <span class=\"required\">*</span>".html_safe : ""),
:for => "#{name}_custom_field_values_#{custom_value.custom_field.id}"
end
@ -91,30 +98,65 @@ module CustomFieldsHelper
custom_field_label_tag(name, custom_value, options) + custom_field_tag(name, custom_value)
end
# Returns the custom field tag for when bulk editing objects
def custom_field_tag_for_bulk_edit(prefix, custom_field, objects=nil, value='')
custom_field.format.bulk_edit_tag self,
custom_field_tag_id(prefix, custom_field),
custom_field_tag_name(prefix, custom_field),
custom_field,
objects,
value,
:class => "#{custom_field.field_format}_cf"
def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil, value='')
field_name = "#{name}[custom_field_values][#{custom_field.id}]"
field_name << "[]" if custom_field.multiple?
field_id = "#{name}_custom_field_values_#{custom_field.id}"
tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"}
unset_tag = ''
unless custom_field.is_required?
unset_tag = content_tag('label',
check_box_tag(field_name, '__none__', (value == '__none__'), :id => nil, :data => {:disables => "##{field_id}"}) + l(:button_clear),
:class => 'inline'
)
end
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
case field_format.try(:edit_as)
when "date"
text_field_tag(field_name, value, tag_options.merge(:size => 10)) +
calendar_for(field_id) +
unset_tag
when "text"
text_area_tag(field_name, value, tag_options.merge(:rows => 3)) +
'<br />'.html_safe +
unset_tag
when "bool"
select_tag(field_name, options_for_select([[l(:label_no_change_option), ''],
[l(:general_text_yes), '1'],
[l(:general_text_no), '0']], value), tag_options)
when "list"
options = []
options << [l(:label_no_change_option), ''] unless custom_field.multiple?
options << [l(:label_none), '__none__'] unless custom_field.is_required?
options += custom_field.possible_values_options(projects)
select_tag(field_name, options_for_select(options, value), tag_options.merge(:multiple => custom_field.multiple?))
else
text_field_tag(field_name, value, tag_options) +
unset_tag
end
end
# Return a string used to display a custom value
def show_value(custom_value, html=true)
format_object(custom_value, html)
def show_value(custom_value)
return "" unless custom_value
format_value(custom_value.value, custom_value.custom_field.field_format)
end
# Return a string used to display a custom value
def format_value(value, custom_field)
format_object(custom_field.format.formatted_value(self, custom_field, value, false), false)
def format_value(value, field_format)
if value.is_a?(Array)
value.collect {|v| format_value(v, field_format)}.compact.sort.join(', ')
else
Redmine::CustomFieldFormat.format_value(value, field_format)
end
end
# Return an array of custom field formats which can be used in select_tag
def custom_field_formats_for_select(custom_field)
Redmine::FieldFormat.as_select(custom_field.class.customized_class.name)
Redmine::CustomFieldFormat.as_select(custom_field.class.customized_class.name)
end
# Renders the custom_values in api views
@ -137,8 +179,4 @@ module CustomFieldsHelper
end
end unless custom_values.empty?
end
def edit_tag_style_tag(form)
form.select :edit_tag_style, [[l(:label_drop_down_list), ''], [l(:label_checkboxes), 'check_box']], :label => :label_display
end
end

View File

@ -171,9 +171,8 @@ module IssuesHelper
s = "<tr>\n"
n = 0
ordered_values.compact.each do |value|
css = "cf_#{value.custom_field.id}"
s << "</tr>\n<tr>\n" if n > 0 && (n % 2) == 0
s << "\t<th class=\"#{css}\">#{ h(value.custom_field.name) }:</th><td class=\"#{css}\">#{ h(show_value(value)) }</td>\n"
s << "\t<th>#{ h(value.custom_field.name) }:</th><td>#{ simple_format_without_paragraph(h(show_value(value))) }</td>\n"
n += 1
end
s << "</tr>\n"
@ -240,7 +239,7 @@ module IssuesHelper
end
end
issue.visible_custom_field_values(user).each do |value|
items << "#{value.custom_field.name}: #{show_value(value, false)}"
items << "#{value.custom_field.name}: #{show_value(value)}"
end
items
end
@ -325,8 +324,8 @@ module IssuesHelper
if custom_field
multiple = custom_field.multiple?
label = custom_field.name
value = format_value(detail.value, custom_field) if detail.value
old_value = format_value(detail.old_value, custom_field) if detail.old_value
value = format_value(detail.value, custom_field.field_format) if detail.value
old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
end
when 'attachment'
label = l(:label_attachment)
@ -340,8 +339,7 @@ module IssuesHelper
old_value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.old_value}" :
(no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path]))
end
relation_type = IssueRelation::TYPES[detail.prop_key]
label = l(relation_type[:name]) if relation_type
label = l(detail.prop_key.to_sym)
end
call_hook(:helper_issues_show_detail_after_setting,
{:detail => detail, :label => label, :value => value, :old_value => old_value })

View File

@ -20,7 +20,7 @@
module ProjectsHelper
def link_to_version(version, options = {})
return '' unless version && version.is_a?(Version)
link_to_if version.visible?, format_version_name(version), version_path(version), options
link_to_if version.visible?, format_version_name(version), { :controller => 'versions', :action => 'show', :id => version }, options
end
def project_settings_tabs
@ -51,21 +51,6 @@ module ProjectsHelper
content_tag('select', options.html_safe, :name => 'project[parent_id]', :id => 'project_parent_id')
end
def render_project_action_links
links = []
if User.current.allowed_to?(:add_project, nil, :global => true)
links << link_to(l(:label_project_new), new_project_path, :class => 'icon icon-add')
end
if User.current.allowed_to?(:view_issues, nil, :global => true)
links << link_to(l(:label_issue_view_all), issues_path)
end
if User.current.allowed_to?(:view_time_entries, nil, :global => true)
links << link_to(l(:label_overall_spent_time), time_entries_path)
end
links << link_to(l(:label_overall_activity), activity_path)
links.join(" | ").html_safe
end
# Renders the projects index
def render_project_hierarchy(projects)
render_project_nested_lists(projects) do |project|

View File

@ -18,8 +18,6 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module QueriesHelper
include ApplicationHelper
def filters_options_for_select(query)
options_for_select(filters_options(query))
end
@ -58,7 +56,7 @@ module QueriesHelper
def available_block_columns_tags(query)
tags = ''.html_safe
query.available_block_columns.each do |column|
tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column), :id => nil) + " #{column.caption}", :class => 'inline')
tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column)) + " #{column.caption}", :class => 'inline')
end
tags
end
@ -83,7 +81,7 @@ module QueriesHelper
end
def column_content(column, issue)
value = column.value_object(issue)
value = column.value(issue)
if value.is_a?(Array)
value.collect {|v| column_value(column, issue, v)}.compact.join(', ').html_safe
else
@ -92,27 +90,53 @@ module QueriesHelper
end
def column_value(column, issue, value)
case column.name
when :id
link_to value, issue_path(issue)
when :subject
link_to value, issue_path(issue)
when :description
issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : ''
when :done_ratio
progress_bar(value, :width => '80px')
when :relations
case value.class.name
when 'String'
if column.name == :subject
link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
elsif column.name == :description
issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : ''
else
h(value)
end
when 'Time'
format_time(value)
when 'Date'
format_date(value)
when 'Fixnum'
if column.name == :id
link_to value, issue_path(issue)
elsif column.name == :done_ratio
progress_bar(value, :width => '80px')
else
value.to_s
end
when 'Float'
sprintf "%.2f", value
when 'User'
link_to_user value
when 'Project'
link_to_project value
when 'Version'
link_to(h(value), :controller => 'versions', :action => 'show', :id => value)
when 'TrueClass'
l(:general_text_Yes)
when 'FalseClass'
l(:general_text_No)
when 'Issue'
value.visible? ? link_to_issue(value) : "##{value.id}"
when 'IssueRelation'
other = value.other_issue(issue)
content_tag('span',
(l(value.label_for(issue)) + " " + link_to_issue(other, :subject => false, :tracker => false)).html_safe,
:class => value.css_classes_for(issue))
else
format_object(value)
h(value)
end
end
def csv_content(column, issue)
value = column.value_object(issue)
value = column.value(issue)
if value.is_a?(Array)
value.collect {|v| csv_value(column, issue, v)}.compact.join(', ')
else
@ -121,16 +145,22 @@ module QueriesHelper
end
def csv_value(column, issue, value)
format_object(value, false) do |value|
case value.class.name
when 'Float'
sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
when 'IssueRelation'
other = value.other_issue(issue)
l(value.label_for(issue)) + " ##{other.id}"
else
value
end
case value.class.name
when 'Time'
format_time(value)
when 'Date'
format_date(value)
when 'Float'
sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
when 'IssueRelation'
other = value.other_issue(issue)
l(value.label_for(issue)) + " ##{other.id}"
when 'TrueClass'
l(:general_text_Yes)
when 'FalseClass'
l(:general_text_No)
else
value.to_s
end
end

View File

@ -43,7 +43,7 @@ module RepositoriesHelper
end
def render_changeset_changes
changes = @changeset.filechanges.limit(1000).reorder('path').collect do |change|
changes = @changeset.filechanges.limit(1000).reorder('path').all.collect do |change|
case change.action
when 'A'
# Detects moved/copied files

View File

@ -79,29 +79,17 @@ module SettingsHelper
def setting_label(setting, options={})
label = options.delete(:label)
label != false ? label_tag("settings_#{setting}", l(label || "setting_#{setting}"), options[:label_options]).html_safe : ''
label != false ? label_tag("settings_#{setting}", l(label || "setting_#{setting}")).html_safe : ''
end
# Renders a notification field for a Redmine::Notifiable option
def notification_field(notifiable)
tag_data = notifiable.parent.present? ?
{:parent_notifiable => notifiable.parent} :
{:disables => "input[data-parent-notifiable=#{notifiable.name}]"}
tag = check_box_tag('settings[notified_events][]',
notifiable.name,
Setting.notified_events.include?(notifiable.name),
:id => nil,
:data => tag_data)
text = l_or_humanize(notifiable.name, :prefix => 'label_')
options = {}
if notifiable.parent.present?
options[:class] = "parent"
end
content_tag(:label, tag + text, options)
return content_tag(:label,
check_box_tag('settings[notified_events][]',
notifiable.name,
Setting.notified_events.include?(notifiable.name), :id => nil).html_safe +
l_or_humanize(notifiable.name, :prefix => 'label_').html_safe,
:class => notifiable.parent.present? ? "parent" : '').html_safe
end
def cross_project_subtasks_options

View File

@ -96,10 +96,8 @@ module TimelogHelper
else
obj
end
elsif cf = criteria_options[:custom_field]
format_value(value, cf)
else
value.to_s
format_value(value, criteria_options[:format])
end
end

View File

@ -19,7 +19,7 @@
module UsersHelper
def users_status_options_for_select(selected)
user_count_by_status = User.group('status').count.to_hash
user_count_by_status = User.count(:group => 'status').to_hash
options_for_select([[l(:label_all), ''],
["#{l(:status_active)} (#{user_count_by_status[1].to_i})", '1'],
["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", '2'],

View File

@ -27,7 +27,6 @@ module WatchersHelper
def watcher_link(objects, user)
return '' unless user && user.logged?
objects = Array.wrap(objects)
return '' unless objects.any?
watched = Watcher.any_watched?(objects, user)
css = [watcher_css(objects), watched ? 'icon icon-fav' : 'icon icon-fav-off'].join(' ')

View File

@ -18,78 +18,24 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module WorkflowsHelper
def options_for_workflow_select(name, objects, selected, options={})
option_tags = ''.html_safe
multiple = false
if selected
if selected.size == objects.size
selected = 'all'
else
selected = selected.map(&:id)
if selected.size > 1
multiple = true
end
end
else
selected = objects.first.try(:id)
end
all_tag_options = {:value => 'all', :selected => (selected == 'all')}
if multiple
all_tag_options.merge!(:style => "display:none;")
end
option_tags << content_tag('option', l(:label_all), all_tag_options)
option_tags << options_from_collection_for_select(objects, "id", "name", selected)
select_tag name, option_tags, {:multiple => multiple}.merge(options)
end
def field_required?(field)
field.is_a?(CustomField) ? field.is_required? : %w(project_id tracker_id subject priority_id is_private).include?(field)
end
def field_permission_tag(permissions, status, field, roles)
def field_permission_tag(permissions, status, field, role)
name = field.is_a?(CustomField) ? field.id.to_s : field
options = [["", ""], [l(:label_readonly), "readonly"]]
options << [l(:label_required), "required"] unless field_required?(field)
html_options = {}
if perm = permissions[status.id][name]
if perm.uniq.size > 1 || perm.size < @roles.size * @trackers.size
options << [l(:label_no_change_option), "no_change"]
selected = 'no_change'
else
selected = perm.first
end
end
hidden = field.is_a?(CustomField) &&
!field.visible? &&
!roles.detect {|role| role.custom_fields.to_a.include?(field)}
selected = permissions[status.id][name]
hidden = field.is_a?(CustomField) && !field.visible? && !role.custom_fields.to_a.include?(field)
if hidden
options[0][0] = l(:label_hidden)
selected = ''
html_options[:disabled] = true
end
select_tag("permissions[#{status.id}][#{name}]", options_for_select(options, selected), html_options)
end
def transition_tag(workflows, old_status, new_status, name)
w = workflows.select {|w| w.old_status_id == old_status.id && w.new_status_id == new_status.id}.size
tag_name = "transitions[#{ old_status.id }][#{new_status.id}][#{name}]"
if w == 0 || w == @roles.size * @trackers.size
hidden_field_tag(tag_name, "0") +
check_box_tag(tag_name, "1", w != 0,
:class => "old-status-#{old_status.id} new-status-#{new_status.id}")
else
select_tag tag_name,
options_for_select([
[l(:general_text_Yes), "1"],
[l(:general_text_No), "0"],
[l(:label_no_change_option), "no_change"]
], "no_change")
end
select_tag("permissions[#{name}][#{status.id}]", options_for_select(options, selected), html_options)
end
end

View File

@ -77,7 +77,7 @@ class AuthSource < ActiveRecord::Base
# Try to authenticate a user not yet registered against available sources
def self.authenticate(login, password)
AuthSource.where(:onthefly_register => true).each do |source|
AuthSource.where(:onthefly_register => true).all.each do |source|
begin
logger.debug "Authenticating '#{login}' against '#{source.name}'" if logger && logger.debug?
attrs = source.authenticate(login, password)

View File

@ -60,10 +60,10 @@ class Board < ActiveRecord::Base
# Updates topics_count, messages_count and last_message_id attributes for +board_id+
def self.reset_counters!(board_id)
board_id = board_id.to_i
where(["id = ?", board_id]).
update_all("topics_count = (SELECT COUNT(*) FROM #{Message.table_name} WHERE board_id=#{board_id} AND parent_id IS NULL)," +
update_all("topics_count = (SELECT COUNT(*) FROM #{Message.table_name} WHERE board_id=#{board_id} AND parent_id IS NULL)," +
" messages_count = (SELECT COUNT(*) FROM #{Message.table_name} WHERE board_id=#{board_id})," +
" last_message_id = (SELECT MAX(id) FROM #{Message.table_name} WHERE board_id=#{board_id})")
" last_message_id = (SELECT MAX(id) FROM #{Message.table_name} WHERE board_id=#{board_id})",
["id = ?", board_id])
end
def self.board_tree(boards, parent_id=nil, level=0)

View File

@ -198,7 +198,7 @@ class Changeset < ActiveRecord::Base
# Finds an issue that can be referenced by the commit message
def find_referenced_issue_by_id(id)
return nil if id.blank?
issue = Issue.includes(:project).where(:id => id.to_i).first
issue = Issue.find_by_id(id.to_i, :include => :project)
if Setting.commit_cross_project_ref?
# all issues can be referenced/fixed
elsif issue

View File

@ -22,18 +22,14 @@ class CustomField < ActiveRecord::Base
has_and_belongs_to_many :roles, :join_table => "#{table_name_prefix}custom_fields_roles#{table_name_suffix}", :foreign_key => "custom_field_id"
acts_as_list :scope => 'type = \'#{self.class}\''
serialize :possible_values
store :format_store
validates_presence_of :name, :field_format
validates_uniqueness_of :name, :scope => :type
validates_length_of :name, :maximum => 30
validates_inclusion_of :field_format, :in => Proc.new { Redmine::FieldFormat.available_formats }
validates_inclusion_of :field_format, :in => Proc.new { Redmine::CustomFieldFormat.available_formats }
validate :validate_custom_field
before_validation :set_searchable
before_save do |field|
field.format.before_custom_field_save(field)
end
after_save :handle_multiplicity_change
after_save do |field|
if field.visible_changed? && field.visible
@ -61,29 +57,23 @@ class CustomField < ActiveRecord::Base
visible? || user.admin?
end
def format
@format ||= Redmine::FieldFormat.find(field_format)
end
def field_format=(arg)
# cannot change format of a saved custom field
if new_record?
@format = nil
super
end
super if new_record?
end
def set_searchable
# make sure these fields are not searchable
self.searchable = false unless format.class.searchable_supported
self.searchable = false if %w(int float date bool).include?(field_format)
# make sure only these fields can have multiple values
self.multiple = false unless format.class.multiple_supported
self.multiple = false unless %w(list user version).include?(field_format)
true
end
def validate_custom_field
format.validate_custom_field(self).each do |attribute, message|
errors.add attribute, message
if self.field_format == "list"
errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty?
errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array
end
if regexp.present?
@ -94,49 +84,80 @@ class CustomField < ActiveRecord::Base
end
end
if default_value.present?
validate_field_value(default_value).each do |message|
errors.add :default_value, message
end
if default_value.present? && !valid_field_value?(default_value)
errors.add(:default_value, :invalid)
end
end
def possible_custom_value_options(custom_value)
format.possible_custom_value_options(custom_value)
end
def possible_values_options(object=nil)
if object.is_a?(Array)
object.map {|o| format.possible_values_options(self, o)}.reduce(:&) || []
def possible_values_options(obj=nil)
case field_format
when 'user', 'version'
if obj.respond_to?(:project) && obj.project
case field_format
when 'user'
obj.project.users.sort.collect {|u| [u.to_s, u.id.to_s]}
when 'version'
obj.project.shared_versions.sort.collect {|u| [u.to_s, u.id.to_s]}
end
elsif obj.is_a?(Array)
obj.collect {|o| possible_values_options(o)}.reduce(:&)
else
[]
end
when 'bool'
[[l(:general_text_Yes), '1'], [l(:general_text_No), '0']]
else
format.possible_values_options(self, object) || []
possible_values || []
end
end
def possible_values
values = read_attribute(:possible_values)
if values.is_a?(Array)
values.each do |value|
value.force_encoding('UTF-8') if value.respond_to?(:force_encoding)
end
values
def possible_values(obj=nil)
case field_format
when 'user', 'version'
possible_values_options(obj).collect(&:last)
when 'bool'
['1', '0']
else
[]
values = super()
if values.is_a?(Array)
values.each do |value|
value.force_encoding('UTF-8') if value.respond_to?(:force_encoding)
end
values
else
[]
end
end
end
# Makes possible_values accept a multiline string
def possible_values=(arg)
if arg.is_a?(Array)
values = arg.compact.collect(&:strip).select {|v| !v.blank?}
write_attribute(:possible_values, values)
super(arg.compact.collect(&:strip).select {|v| !v.blank?})
else
self.possible_values = arg.to_s.split(/[\n\r]+/)
end
end
def cast_value(value)
format.cast_value(self, value)
casted = nil
unless value.blank?
case field_format
when 'string', 'text', 'list'
casted = value
when 'date'
casted = begin; value.to_date; rescue; nil end
when 'bool'
casted = (value == '1' ? true : false)
when 'int'
casted = value.to_i
when 'float'
casted = value.to_f
when 'user', 'version'
casted = (value.blank? ? nil : field_format.classify.constantize.find_by_id(value.to_i))
end
end
casted
end
def value_from_keyword(keyword, customized)
@ -160,32 +181,96 @@ class CustomField < ActiveRecord::Base
# Returns nil if the custom field can not be used for sorting.
def order_statement
return nil if multiple?
format.order_statement(self)
case field_format
when 'string', 'text', 'list', 'date', 'bool'
# COALESCE is here to make sure that blank and NULL values are sorted equally
"COALESCE(#{join_alias}.value, '')"
when 'int', 'float'
# Make the database cast values into numeric
# Postgresql will raise an error if a value can not be casted!
# CustomValue validations should ensure that it doesn't occur
"CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,3))"
when 'user', 'version'
value_class.fields_for_order_statement(value_join_alias)
else
nil
end
end
# Returns a GROUP BY clause that can used to group by custom value
# Returns nil if the custom field can not be used for grouping.
def group_statement
return nil if multiple?
format.group_statement(self)
case field_format
when 'list', 'date', 'bool', 'int'
order_statement
when 'user', 'version'
"COALESCE(#{join_alias}.value, '')"
else
nil
end
end
def join_for_order_statement
format.join_for_order_statement(self)
case field_format
when 'user', 'version'
"LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
" ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
" AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
" AND #{join_alias}.custom_field_id = #{id}" +
" AND (#{visibility_by_project_condition})" +
" AND #{join_alias}.value <> ''" +
" AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
" WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
" AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
" AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" +
" LEFT OUTER JOIN #{value_class.table_name} #{value_join_alias}" +
" ON CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,0)) = #{value_join_alias}.id"
when 'int', 'float'
"LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
" ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
" AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
" AND #{join_alias}.custom_field_id = #{id}" +
" AND (#{visibility_by_project_condition})" +
" AND #{join_alias}.value <> ''" +
" AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
" WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
" AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
" AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)"
when 'string', 'text', 'list', 'date', 'bool'
"LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
" ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
" AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
" AND #{join_alias}.custom_field_id = #{id}" +
" AND (#{visibility_by_project_condition})" +
" AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
" WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
" AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
" AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)"
else
nil
end
end
def visibility_by_project_condition(project_key=nil, user=User.current, id_column=nil)
def join_alias
"cf_#{id}"
end
def value_join_alias
join_alias + "_" + field_format
end
def visibility_by_project_condition(project_key=nil, user=User.current)
if visible? || user.admin?
"1=1"
elsif user.anonymous?
"1=0"
else
project_key ||= "#{self.class.customized_class.table_name}.project_id"
id_column ||= id
"#{project_key} IN (SELECT DISTINCT m.project_id FROM #{Member.table_name} m" +
" INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
" INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
" WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id_column})"
" WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id})"
end
end
@ -208,7 +293,12 @@ class CustomField < ActiveRecord::Base
# Returns the class that values represent
def value_class
format.target_class if format.respond_to?(:target_class)
case field_format
when 'user', 'version'
field_format.classify.constantize
else
nil
end
end
def self.customized_class
@ -227,8 +317,7 @@ class CustomField < ActiveRecord::Base
# Returns the error messages for the given value
# or an empty array if value is a valid value for the custom field
def validate_custom_value(custom_value)
value = custom_value.value
def validate_field_value(value)
errs = []
if value.is_a?(Array)
if !multiple?
@ -237,20 +326,16 @@ class CustomField < ActiveRecord::Base
if is_required? && value.detect(&:present?).nil?
errs << ::I18n.t('activerecord.errors.messages.blank')
end
value.each {|v| errs += validate_field_value_format(v)}
else
if is_required? && value.blank?
errs << ::I18n.t('activerecord.errors.messages.blank')
end
errs += validate_field_value_format(value)
end
errs += format.validate_custom_value(custom_value)
errs
end
# Returns the error messages for the default custom field value
def validate_field_value(value)
validate_custom_value(CustomValue.new(:custom_field => self, :value => value))
end
# Returns true if value is a valid value for the custom field
def valid_field_value?(value)
validate_field_value(value).empty?
@ -262,6 +347,29 @@ class CustomField < ActiveRecord::Base
protected
# Returns the error message for the given value regarding its format
def validate_field_value_format(value)
errs = []
unless value.to_s == ''
errs << ::I18n.t('activerecord.errors.messages.invalid') unless regexp.blank? or value =~ Regexp.new(regexp)
errs << ::I18n.t('activerecord.errors.messages.too_short', :count => min_length) if min_length > 0 and value.length < min_length
errs << ::I18n.t('activerecord.errors.messages.too_long', :count => max_length) if max_length > 0 and value.length > max_length
# Format specific validations
case field_format
when 'int'
errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value =~ /^[+-]?\d+$/
when 'float'
begin; Kernel.Float(value); rescue; errs << ::I18n.t('activerecord.errors.messages.invalid') end
when 'date'
errs << ::I18n.t('activerecord.errors.messages.not_a_date') unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end
when 'list'
errs << ::I18n.t('activerecord.errors.messages.inclusion') unless possible_values.include?(value)
end
end
errs
end
# Removes multiple values for the custom field after setting the multiple attribute to false
# We kepp the value with the highest id for each customized object
def handle_multiplicity_change
@ -278,5 +386,3 @@ class CustomField < ActiveRecord::Base
end
end
end
require_dependency 'redmine/field_format'

View File

@ -16,13 +16,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class CustomFieldValue
attr_accessor :custom_field, :customized, :value, :value_was
def initialize(attributes={})
attributes.each do |name, v|
send "#{name}=", v
end
end
attr_accessor :custom_field, :customized, :value
def custom_field_id
custom_field.id
@ -49,7 +43,7 @@ class CustomFieldValue
end
def validate_value
custom_field.validate_custom_value(self).each do |message|
custom_field.validate_field_value(value).each do |message|
customized.errors.add(:base, custom_field.name + ' ' + message)
end
end

View File

@ -17,7 +17,6 @@
class EnabledModule < ActiveRecord::Base
belongs_to :project
acts_as_watchable
validates_presence_of :name
validates_uniqueness_of :name, :scope => :project_id

View File

@ -60,7 +60,7 @@ class Enumeration < ActiveRecord::Base
def check_default
if is_default? && is_default_changed?
Enumeration.where({:type => type}).update_all({:is_default => false})
Enumeration.update_all({:is_default => false}, {:type => type})
end
end
@ -73,7 +73,7 @@ class Enumeration < ActiveRecord::Base
self.objects_count != 0
end
# Is this enumeration overriding a system level enumeration?
# Is this enumeration overiding a system level enumeration?
def is_override?
!self.parent.nil?
end
@ -103,14 +103,8 @@ class Enumeration < ActiveRecord::Base
subclasses
end
# TODO: remove in Redmine 3.0
def self.overridding_change?(new, previous)
ActiveSupport::Deprecation.warn "Enumeration#overridding_change? is deprecated and will be removed in Redmine 3.0. Please use #overriding_change?."
overriding_change?(new, previous)
end
# Does the +new+ Hash override the previous Enumeration?
def self.overriding_change?(new, previous)
def self.overridding_change?(new, previous)
if (same_active_state?(new['active'], previous.active)) && same_custom_values?(new,previous)
return false
else

View File

@ -18,10 +18,8 @@
class Group < Principal
include Redmine::SafeAttributes
has_and_belongs_to_many :users,
:join_table => "#{table_name_prefix}groups_users#{table_name_suffix}",
:after_add => :user_added,
:after_remove => :user_removed
has_and_belongs_to_many :users, :after_add => :user_added,
:after_remove => :user_removed
acts_as_customizable
@ -68,6 +66,7 @@ class Group < Principal
MemberRole.
includes(:member).
where("#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids).
all.
each(&:destroy)
end
end
@ -86,6 +85,6 @@ class Group < Principal
def remove_references_before_destroy
return if self.id.nil?
Issue.where(['assigned_to_id = ?', id]).update_all('assigned_to_id = NULL')
Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
end
end

View File

@ -33,12 +33,12 @@ class Issue < ActiveRecord::Base
has_many :visible_journals,
:class_name => 'Journal',
:as => :journalized,
:conditions => Proc.new {
:conditions => Proc.new {
["(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(User.current, :view_private_notes)}))", false]
},
:readonly => true
has_many :time_entries, :dependent => :destroy
has_many :time_entries, :dependent => :delete_all
has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
@ -94,7 +94,7 @@ class Issue < ActiveRecord::Base
before_create :default_assign
before_save :close_duplicates, :update_done_ratio_from_issue_status,
:force_updated_on_change, :update_closed_on, :set_assigned_to_was
after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
after_save :reschedule_following_issues, :update_nested_set_attributes,
:update_parent_attributes, :create_journal
# Should be after_create but would be called before previous after_save callbacks
@ -200,7 +200,7 @@ class Issue < ActiveRecord::Base
# Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
def available_custom_fields
(project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields) : []
(project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : []
end
def visible_custom_field_values(user=nil)
@ -218,7 +218,7 @@ class Issue < ActiveRecord::Base
self.status = issue.status
self.author = User.current
unless options[:attachments] == false
self.attachments = issue.attachments.map do |attachement|
self.attachments = issue.attachments.map do |attachement|
attachement.copy(:container => self)
end
end
@ -394,10 +394,10 @@ class Issue < ActiveRecord::Base
:if => lambda {|issue, user| user.allowed_to?(:add_issue_notes, issue.project)}
safe_attributes 'private_notes',
:if => lambda {|issue, user| !issue.new_record? && user.allowed_to?(:set_notes_private, issue.project)}
:if => lambda {|issue, user| !issue.new_record? && user.allowed_to?(:set_notes_private, issue.project)}
safe_attributes 'watcher_user_ids',
:if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
:if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
safe_attributes 'is_private',
:if => lambda {|issue, user|
@ -483,11 +483,6 @@ class Issue < ActiveRecord::Base
end
end
# Returns the custom fields that can be edited by the given user
def editable_custom_fields(user=nil)
editable_custom_field_values(user).map(&:custom_field).uniq
end
# Returns the names of attributes that are read-only for user or the current user
# For users with multiple roles, the read-only fields are the intersection of
# read-only fields of each role
@ -529,7 +524,7 @@ class Issue < ActiveRecord::Base
return {} if roles.empty?
result = {}
workflow_permissions = WorkflowPermission.where(:tracker_id => tracker_id, :old_status_id => status_id, :role_id => roles.map(&:id))
workflow_permissions = WorkflowPermission.where(:tracker_id => tracker_id, :old_status_id => status_id, :role_id => roles.map(&:id)).all
if workflow_permissions.any?
workflow_rules = workflow_permissions.inject({}) do |h, wp|
h[wp.field_name] ||= []
@ -767,7 +762,7 @@ class Issue < ActiveRecord::Base
initial_status ||= status
initial_assigned_to_id = assigned_to_id_changed? ? assigned_to_id_was : assigned_to_id
assignee_transitions_allowed = initial_assigned_to_id.present? &&
assignee_transitions_allowed = initial_assigned_to_id.present? &&
(user.id == initial_assigned_to_id || user.group_ids.include?(initial_assigned_to_id))
statuses = initial_status.find_new_statuses_allowed_to(
@ -844,10 +839,8 @@ class Issue < ActiveRecord::Base
# spent_hours => 0.0
# spent_hours => 50.2
def total_spent_hours
@total_spent_hours ||=
self_and_descendants.
joins("LEFT JOIN #{TimeEntry.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id").
sum("#{TimeEntry.table_name}.hours").to_f || 0.0
@total_spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours",
:joins => "LEFT JOIN #{TimeEntry.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id").to_f || 0.0
end
def relations
@ -1061,7 +1054,7 @@ class Issue < ActiveRecord::Base
if leaf.start_date
# Only move subtask if it starts at the same date as the parent
# or if it starts before the given date
if start_date == leaf.start_date || date > leaf.start_date
if start_date == leaf.start_date || date > leaf.start_date
leaf.reschedule_on!(date)
end
else
@ -1112,10 +1105,7 @@ class Issue < ActiveRecord::Base
def self.update_versions_from_hierarchy_change(project)
moved_project_ids = project.self_and_descendants.reload.collect(&:id)
# Update issues of the moved projects and issues assigned to a version of a moved project
Issue.update_versions(
["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)",
moved_project_ids, moved_project_ids]
)
Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
end
def parent_issue_id=(arg)
@ -1199,13 +1189,13 @@ class Issue < ActiveRecord::Base
end
def self.by_subproject(project)
ActiveRecord::Base.connection.select_all("select s.id as status_id,
s.is_closed as closed,
ActiveRecord::Base.connection.select_all("select s.id as status_id,
s.is_closed as closed,
#{Issue.table_name}.project_id as project_id,
count(#{Issue.table_name}.id) as total
from
count(#{Issue.table_name}.id) as total
from
#{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s
where
where
#{Issue.table_name}.status_id=s.id
and #{Issue.table_name}.project_id = #{Project.table_name}.id
and #{visible_condition(User.current, :project => project, :with_subprojects => true)}
@ -1232,7 +1222,7 @@ class Issue < ActiveRecord::Base
def after_project_change
# Update project_id on related time entries
TimeEntry.where({:issue_id => id}).update_all(["project_id = ?", project_id])
TimeEntry.update_all(["project_id = ?", project_id], {:issue_id => id})
# Delete issue relations
unless Setting.cross_project_issue_relations?
@ -1295,7 +1285,8 @@ class Issue < ActiveRecord::Base
if root_id.nil?
# issue was just created
self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id)
Issue.where(["id = ?", id]).update_all(["root_id = ?", root_id])
set_default_left_and_right
Issue.update_all(["root_id = ?, lft = ?, rgt = ?", root_id, lft, rgt], ["id = ?", id])
if @parent_issue
move_to_child_of(@parent_issue)
end
@ -1318,18 +1309,13 @@ class Issue < ActiveRecord::Base
move_to_right_of(root)
end
old_root_id = root_id
in_tenacious_transaction do
@parent_issue.reload_nested_set if @parent_issue
self.reload_nested_set
self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id)
cond = ["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt]
self.class.base_class.select('id').lock(true).where(cond)
offset = right_most_bound + 1 - lft
Issue.where(cond).
update_all(["root_id = ?, lft = lft + ?, rgt = rgt + ?", root_id, offset, offset])
self[left_column_name] = lft + offset
self[right_column_name] = rgt + offset
end
self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id )
target_maxright = nested_set_scope.maximum(right_column_name) || 0
offset = target_maxright + 1 - lft
Issue.update_all(["root_id = ?, lft = lft + ?, rgt = rgt + ?", root_id, offset, offset],
["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt])
self[left_column_name] = lft + offset
self[right_column_name] = rgt + offset
if @parent_issue
move_to_child_of(@parent_issue)
end
@ -1351,7 +1337,7 @@ class Issue < ActiveRecord::Base
def recalculate_attributes_for(issue_id)
if issue_id && p = Issue.find_by_id(issue_id)
# priority = highest priority of children
if priority_position = p.children.joins(:priority).maximum("#{IssuePriority.table_name}.position")
if priority_position = p.children.maximum("#{IssuePriority.table_name}.position", :joins => :priority)
p.priority = IssuePriority.find_by_position(priority_position)
end
@ -1370,9 +1356,8 @@ class Issue < ActiveRecord::Base
if average == 0
average = 1
end
done = p.leaves.joins(:status).
sum("COALESCE(CASE WHEN estimated_hours > 0 THEN estimated_hours ELSE NULL END, #{average}) " +
"* (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)").to_f
done = p.leaves.sum("COALESCE(CASE WHEN estimated_hours > 0 THEN estimated_hours ELSE NULL END, #{average}) " +
"* (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)", :joins => :status).to_f
progress = done / (average * leaves_count)
p.done_ratio = progress.round
end
@ -1441,7 +1426,7 @@ class Issue < ActiveRecord::Base
def close_duplicates
if closing?
duplicates.each do |duplicate|
# Reload is needed in case the duplicate was updated by a previous duplicate
# Reload is need in case the duplicate was updated by a previous duplicate
duplicate.reload
# Don't re-close it if it's already closed
next if duplicate.closed?
@ -1496,11 +1481,11 @@ class Issue < ActiveRecord::Base
before = @custom_values_before_change[c.custom_field_id]
after = c.value
next if before == after || (before.blank? && after.blank?)
if before.is_a?(Array) || after.is_a?(Array)
before = [before] unless before.is_a?(Array)
after = [after] unless after.is_a?(Array)
# values removed
(before - after).reject(&:blank?).each do |value|
@current_journal.details << JournalDetail.new(:property => 'cf',
@ -1561,14 +1546,14 @@ class Issue < ActiveRecord::Base
where = "#{Issue.table_name}.#{select_field}=j.id"
ActiveRecord::Base.connection.select_all("select s.id as status_id,
s.is_closed as closed,
ActiveRecord::Base.connection.select_all("select s.id as status_id,
s.is_closed as closed,
j.id as #{select_field},
count(#{Issue.table_name}.id) as total
from
count(#{Issue.table_name}.id) as total
from
#{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s, #{joins} j
where
#{Issue.table_name}.status_id=s.id
where
#{Issue.table_name}.status_id=s.id
and #{where}
and #{Issue.table_name}.project_id=#{Project.table_name}.id
and #{visible_condition(User.current, :project => project)}

View File

@ -35,7 +35,7 @@ class IssueCategory < ActiveRecord::Base
# If a category is specified, issues are reassigned to this category
def destroy(reassign_to = nil)
if reassign_to && reassign_to.is_a?(IssueCategory) && reassign_to.project == self.project
Issue.where({:category_id => id}).update_all({:category_id => reassign_to.id})
Issue.update_all({:category_id => reassign_to.id}, {:category_id => id})
end
destroy_without_reassign
end

View File

@ -28,14 +28,13 @@ class IssueCustomField < CustomField
super || (roles & user.roles_for_project(project)).present?
end
def visibility_by_project_condition(project_key=nil, user=User.current, id_column=nil)
def visibility_by_project_condition(*args)
sql = super
id_column ||= id
tracker_condition = "#{Issue.table_name}.tracker_id IN (SELECT tracker_id FROM #{table_name_prefix}custom_fields_trackers#{table_name_suffix} WHERE custom_field_id = #{id_column})"
project_condition = "EXISTS (SELECT 1 FROM #{CustomField.table_name} ifa WHERE ifa.is_for_all = #{connection.quoted_true} AND ifa.id = #{id_column})" +
" OR #{Issue.table_name}.project_id IN (SELECT project_id FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} WHERE custom_field_id = #{id_column})"
"((#{sql}) AND (#{tracker_condition}) AND (#{project_condition}))"
additional_sql = "#{Issue.table_name}.tracker_id IN (SELECT tracker_id FROM #{table_name_prefix}custom_fields_trackers#{table_name_suffix} WHERE custom_field_id = #{id})"
unless is_for_all?
additional_sql << " AND #{Issue.table_name}.project_id IN (SELECT project_id FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} WHERE custom_field_id = #{id})"
end
"((#{sql}) AND (#{additional_sql}))"
end
def validate_custom_field

View File

@ -48,7 +48,7 @@ class IssuePriority < Enumeration
# Updates position_name for active priorities
# Called from migration 20121026003537_populate_enumerations_position_name
def self.compute_position_names
priorities = where(:active => true).sort_by(&:position)
priorities = where(:active => true).all.sort_by(&:position)
if priorities.any?
default = priorities.detect(&:is_default?) || priorities[(priorities.size - 1) / 2]
priorities.each_with_index do |priority, index|
@ -61,7 +61,7 @@ class IssuePriority < Enumeration
index == (priorities.size - 1) ? "highest" : "high#{priorities.size - index}"
end
where(:id => priority.id).update_all({:position_name => name})
update_all({:position_name => name}, :id => priority.id)
end
end
end

View File

@ -142,7 +142,7 @@ class IssueQuery < Query
if all_projects.any?
principals += Principal.member_of(all_projects)
end
versions = Version.visible.where(:sharing => 'system').all
versions = Version.visible.find_all_by_sharing('system')
issue_custom_fields = IssueCustomField.where(:is_for_all => true)
end
principals.uniq!
@ -150,7 +150,7 @@ class IssueQuery < Query
users = principals.select {|p| p.is_a?(User)}
add_available_filter "status_id",
:type => :list_status, :values => IssueStatus.sorted.collect{|s| [s.name, s.id.to_s] }
:type => :list_status, :values => IssueStatus.sorted.all.collect{|s| [s.name, s.id.to_s] }
if project.nil?
project_values = []
@ -333,9 +333,8 @@ class IssueQuery < Query
limit(options[:limit]).
offset(options[:offset])
scope = scope.preload(:custom_values)
if has_column?(:author)
scope = scope.preload(:author)
if has_custom_field_column?
scope = scope.preload(:custom_values)
end
issues = scope.all
@ -410,7 +409,7 @@ class IssueQuery < Query
groups = Group.all
operator = '!' # Override the operator since we want to find by assigned_to
else
groups = Group.where(:id => value).all
groups = Group.find_all_by_id(value)
end
groups ||= []

View File

@ -185,12 +185,12 @@ class IssueRelation < ActiveRecord::Base
def create_journal_after_create
journal = issue_from.init_journal(User.current)
journal.details << JournalDetail.new(:property => 'relation',
:prop_key => relation_type_for(issue_from),
:prop_key => label_for(issue_from).to_s,
:value => issue_to.id)
journal.save
journal = issue_to.init_journal(User.current)
journal.details << JournalDetail.new(:property => 'relation',
:prop_key => relation_type_for(issue_to),
:prop_key => label_for(issue_to).to_s,
:value => issue_from.id)
journal.save
end
@ -198,12 +198,12 @@ class IssueRelation < ActiveRecord::Base
def create_journal_after_delete
journal = issue_from.init_journal(User.current)
journal.details << JournalDetail.new(:property => 'relation',
:prop_key => relation_type_for(issue_from),
:prop_key => label_for(issue_from).to_s,
:old_value => issue_to.id)
journal.save
journal = issue_to.init_journal(User.current)
journal.details << JournalDetail.new(:property => 'relation',
:prop_key => relation_type_for(issue_to),
:prop_key => label_for(issue_to).to_s,
:old_value => issue_from.id)
journal.save
end

View File

@ -32,7 +32,7 @@ class IssueStatus < ActiveRecord::Base
scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
def update_default
IssueStatus.where(['id <> ?', id]).update_all({:is_default => false}) if self.is_default?
IssueStatus.update_all({:is_default => false}, ['id <> ?', id]) if self.is_default?
end
# Returns the default status for new issues
@ -43,8 +43,8 @@ class IssueStatus < ActiveRecord::Base
# Update all the +Issues+ setting their done_ratio to the value of their +IssueStatus+
def self.update_issue_done_ratios
if Issue.use_status_for_done_ratio?
IssueStatus.where("default_done_ratio >= 0").each do |status|
Issue.where({:status_id => status.id}).update_all({:done_ratio => status.default_done_ratio})
IssueStatus.where("default_done_ratio >= 0").all.each do |status|
Issue.update_all({:done_ratio => status.default_done_ratio}, {:status_id => status.id})
end
end

View File

@ -80,20 +80,15 @@ class Journal < ActiveRecord::Base
end
end
# Returns the JournalDetail for the given attribute, or nil if the attribute
# was not updated
def detail_for_attribute(attribute)
details.detect {|detail| detail.prop_key == attribute}
end
# Returns the new status if the journal contains a status change, otherwise nil
def new_status
s = new_value_for('status_id')
s ? IssueStatus.find_by_id(s.to_i) : nil
c = details.detect {|detail| detail.prop_key == 'status_id'}
(c && c.value) ? IssueStatus.find_by_id(c.value.to_i) : nil
end
def new_value_for(prop)
detail_for_attribute(prop).try(:value)
c = details.detect {|detail| detail.prop_key == prop}
c ? c.value : nil
end
def editable_by?(usr)
@ -153,7 +148,7 @@ class Journal < ActiveRecord::Base
def self.preload_journals_details_custom_fields(journals)
field_ids = journals.map(&:details).flatten.select {|d| d.property == 'cf'}.map(&:prop_key).uniq
if field_ids.any?
fields_by_id = CustomField.where(:id => field_ids).inject({}) {|h, f| h[f.id] = f; h}
fields_by_id = CustomField.find_all_by_id(field_ids).inject({}) {|h, f| h[f.id] = f; h}
journals.each do |journal|
journal.details.each do |detail|
if detail.property == 'cf'
@ -190,7 +185,6 @@ class Journal < ActiveRecord::Base
if notify? && (Setting.notified_events.include?('issue_updated') ||
(Setting.notified_events.include?('issue_note_added') && notes.present?) ||
(Setting.notified_events.include?('issue_status_updated') && new_status.present?) ||
(Setting.notified_events.include?('issue_assigned_to_updated') && detail_for_attribute('assigned_to_id').present?) ||
(Setting.notified_events.include?('issue_priority_updated') && new_value_for('priority_id').present?)
)
Mailer.deliver_issue_edit(self)

View File

@ -46,14 +46,6 @@ class MailHandler < ActionMailer::Base
super(email)
end
# Receives an email and rescues any exception
def self.safe_receive(*args)
receive(*args)
rescue => e
logger.error "An unexpected error occurred when receiving email: #{e.message}" if logger
return false
end
# Extracts MailHandler options from environment variables
# Use when receiving emails with rake tasks
def self.extract_options_from_env(env)
@ -198,7 +190,6 @@ class MailHandler < ActionMailer::Base
issue.subject = '(no subject)'
end
issue.description = cleaned_up_text_body
issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
# add To and Cc as watchers before saving so the watchers can reply to Redmine
add_watchers(issue)
@ -305,9 +296,8 @@ class MailHandler < ActionMailer::Base
if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
unless addresses.empty?
User.active.where('LOWER(mail) IN (?)', addresses).each do |w|
obj.add_watcher(w)
end
watchers = User.active.where('LOWER(mail) IN (?)', addresses).all
watchers.each {|w| obj.add_watcher(w)}
end
end
end
@ -420,11 +410,7 @@ class MailHandler < ActionMailer::Base
part.header[:content_disposition].try(:disposition_type) == 'attachment'
end
@plain_text_body = parts.map do |p|
body_charset = p.charset.respond_to?(:force_encoding) ?
Mail::RubyVer.pick_encoding(p.charset).to_s : p.charset
Redmine::CodesetUtil.to_utf8(p.body.decoded, body_charset)
end.join("\r\n")
@plain_text_body = parts.map {|p| Redmine::CodesetUtil.to_utf8(p.body.decoded, p.charset)}.join("\r\n")
# strip html tags and remove doctype directive
if parts.any? {|p| p.mime_type == 'text/html'}

View File

@ -80,7 +80,7 @@ class Mailer < ActionMailer::Base
def self.deliver_issue_edit(journal)
issue = journal.journalized.reload
to = journal.notified_users
cc = journal.notified_watchers - to
cc = journal.notified_watchers
journal.each_notification(to + cc) do |users|
issue.each_notification(users) do |users2|
Mailer.issue_edit(journal, to & users2, cc & users2).deliver
@ -158,7 +158,6 @@ class Mailer < ActionMailer::Base
@news = news
@news_url = url_for(:controller => 'news', :action => 'show', :id => news)
mail :to => news.recipients,
:cc => news.cc_for_added_news,
:subject => "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
end
@ -267,7 +266,7 @@ class Mailer < ActionMailer::Base
# Mailer.account_activation_request(user).deliver => sends an email to all active administrators
def account_activation_request(user)
# Send the email to all active administrators
recipients = User.active.where(:admin => true).collect { |u| u.mail }.compact
recipients = User.active.where(:admin => true).all.collect { |u| u.mail }.compact
@user = user
@url = url_for(:controller => 'users', :action => 'index',
:status => User::STATUS_REGISTERED,
@ -331,8 +330,8 @@ class Mailer < ActionMailer::Base
scope = scope.where(:assigned_to_id => user_ids) if user_ids.present?
scope = scope.where(:project_id => project.id) if project
scope = scope.where(:tracker_id => tracker.id) if tracker
issues_by_assignee = scope.includes(:status, :assigned_to, :project, :tracker).
group_by(&:assigned_to)
issues_by_assignee = scope.includes(:status, :assigned_to, :project, :tracker).all.group_by(&:assigned_to)
issues_by_assignee.keys.each do |assignee|
if assignee.is_a?(Group)
assignee.users.each do |user|
@ -464,7 +463,7 @@ class Mailer < ActionMailer::Base
if rand
hash << Redmine::Utils.random_hex(8)
end
host = Setting.mail_from.to_s.strip.gsub(%r{^.*@|>}, '')
host = Setting.mail_from.to_s.gsub(%r{^.*@}, '')
host = "#{::Socket.gethostname}.redmine" if host.empty?
"#{hash.join('.')}@#{host}"
end

View File

@ -84,12 +84,11 @@ class Member < ActiveRecord::Base
def set_issue_category_nil
if user
# remove category based auto assignments for this member
IssueCategory.where(["project_id = ? AND assigned_to_id = ?", project.id, user.id]).
update_all("assigned_to_id = NULL")
IssueCategory.update_all "assigned_to_id = NULL", ["project_id = ? AND assigned_to_id = ?", project.id, user.id]
end
end
# Find or initialize a Member with an id, attributes, and for a Principal
# Find or initilize a Member with an id, attributes, and for a Principal
def self.edit_membership(id, new_attributes, principal=nil)
@membership = id.present? ? Member.find(id) : Member.new(:principal => principal)
@membership.attributes = new_attributes

View File

@ -64,8 +64,7 @@ class MemberRole < ActiveRecord::Base
end
def remove_inherited_roles
MemberRole.where(:inherited_from => id).group_by(&:member).
each do |member, member_roles|
MemberRole.where(:inherited_from => id).all.group_by(&:member).each do |member, member_roles|
member_roles.each(&:destroy)
end
end

View File

@ -68,7 +68,7 @@ class Message < ActiveRecord::Base
def update_messages_board
if board_id_changed?
Message.where(["id = ? OR parent_id = ?", root.id, root.id]).update_all({:board_id => board_id})
Message.update_all({:board_id => board_id}, ["id = ? OR parent_id = ?", root.id, root.id])
Board.reset_counters!(board_id_was)
Board.reset_counters!(board_id)
end
@ -76,7 +76,7 @@ class Message < ActiveRecord::Base
def reset_counters!
if parent && parent.id
Message.where({:id => parent.id}).update_all({:last_reply_id => parent.children.maximum(:id)})
Message.update_all({:last_reply_id => parent.children.maximum(:id)}, {:id => parent.id})
end
board.reset_counters!
end

View File

@ -51,19 +51,7 @@ class News < ActiveRecord::Base
end
def recipients
project.users.select {|user| user.notify_about?(self) && user.allowed_to?(:view_news, project)}.map(&:mail)
end
# Returns the email addresses that should be cc'd when a new news is added
def cc_for_added_news
cc = []
if m = project.enabled_module('news')
cc = m.notified_watchers
unless project.is_public?
cc = cc.select {|user| project.users.include?(user)}
end
end
cc.map(&:mail)
project.users.select {|user| user.notify_about?(self)}.map(&:mail)
end
# returns latest news for projects visible by user

View File

@ -25,11 +25,7 @@ class Principal < ActiveRecord::Base
STATUS_LOCKED = 3
has_many :members, :foreign_key => 'user_id', :dependent => :destroy
has_many :memberships, :class_name => 'Member',
:foreign_key => 'user_id',
:include => [:project, :roles],
:conditions => "#{Project.table_name}.status<>#{Project::STATUS_ARCHIVED}",
:order => "#{Project.table_name}.name"
has_many :memberships, :class_name => 'Member', :foreign_key => 'user_id', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status<>#{Project::STATUS_ARCHIVED}", :order => "#{Project.table_name}.name"
has_many :projects, :through => :memberships
has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify

View File

@ -26,7 +26,7 @@ class Project < ActiveRecord::Base
# Maximum length for project identifiers
IDENTIFIER_MAX_LENGTH = 100
# Specific overridden Activities
# Specific overidden Activities
has_many :time_entry_activities
has_many :members, :include => [:principal, :roles], :conditions => "#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{Principal::STATUS_ACTIVE}"
has_many :memberships, :class_name => 'Member'
@ -39,7 +39,7 @@ class Project < ActiveRecord::Base
has_many :issues, :dependent => :destroy, :include => [:status, :tracker]
has_many :issue_changes, :through => :issues, :source => :journals
has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
has_many :time_entries, :dependent => :destroy
has_many :time_entries, :dependent => :delete_all
has_many :queries, :class_name => 'IssueQuery', :dependent => :delete_all
has_many :documents, :dependent => :destroy
has_many :news, :dependent => :destroy, :include => :author
@ -56,7 +56,7 @@ class Project < ActiveRecord::Base
:join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
:association_foreign_key => 'custom_field_id'
acts_as_nested_set :dependent => :destroy
acts_as_nested_set :order => 'name', :dependent => :destroy
acts_as_attachable :view_permission => :view_files,
:delete_permission => :manage_files
@ -74,7 +74,7 @@ class Project < ActiveRecord::Base
validates_length_of :name, :maximum => 255
validates_length_of :homepage, :maximum => 255
validates_length_of :identifier, :in => 1..IDENTIFIER_MAX_LENGTH
# downcase letters, digits, dashes but not digits only
# donwcase letters, digits, dashes but not digits only
validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :if => Proc.new { |p| p.identifier_changed? }
# reserved words
validates_exclusion_of :identifier, :in => %w( new )
@ -249,17 +249,18 @@ class Project < ActiveRecord::Base
# does not successfully save.
def create_time_entry_activity_if_needed(activity)
if activity['parent_id']
parent_activity = TimeEntryActivity.find(activity['parent_id'])
activity['name'] = parent_activity.name
activity['position'] = parent_activity.position
if Enumeration.overriding_change?(activity, parent_activity)
if Enumeration.overridding_change?(activity, parent_activity)
project_activity = self.time_entry_activities.create(activity)
if project_activity.new_record?
raise ActiveRecord::Rollback, "Overriding TimeEntryActivity was not successfully saved"
raise ActiveRecord::Rollback, "Overridding TimeEntryActivity was not successfully saved"
else
self.time_entries.
where(["activity_id = ?", parent_activity.id]).
update_all("activity_id = #{project_activity.id}")
self.time_entries.update_all("activity_id = #{project_activity.id}", ["activity_id = ?", parent_activity.id])
end
end
end
@ -421,7 +422,6 @@ class Project < ActiveRecord::Base
transaction do
update_all "lft = NULL, rgt = NULL"
rebuild!(false)
all.each { |p| p.set_or_update_position_under(p.parent) }
end
end
@ -440,7 +440,7 @@ class Project < ActiveRecord::Base
# Closes open and locked project versions that are completed
def close_completed_versions
Version.transaction do
versions.where(:status => %w(open locked)).each do |version|
versions.where(:status => %w(open locked)).all.each do |version|
if version.completed?
version.update_attribute(:status, 'closed')
end
@ -480,7 +480,7 @@ class Project < ActiveRecord::Base
# Returns a hash of project users grouped by role
def users_by_role
members.includes(:user, :roles).inject({}) do |h, m|
members.includes(:user, :roles).all.inject({}) do |h, m|
m.roles.each do |r|
h[r] ||= []
h[r] << m.user
@ -502,7 +502,7 @@ class Project < ActiveRecord::Base
assignable.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.principal}.sort
end
# Returns the mail addresses of users that should be always notified on project events
# Returns the mail adresses of users that should be always notified on project events
def recipients
notified_users.collect {|user| user.mail}
end
@ -514,7 +514,7 @@ class Project < ActiveRecord::Base
end
# Returns a scope of all custom fields enabled for project issues
# (explicitly associated custom fields and custom fields enabled for all projects)
# (explictly associated custom fields and custom fields enabled for all projects)
def all_issue_custom_fields
@all_issue_custom_fields ||= IssueCustomField.
sorted.
@ -622,16 +622,9 @@ class Project < ActiveRecord::Base
end
end
# Return the enabled module with the given name
# or nil if the module is not enabled for the project
def enabled_module(name)
name = name.to_s
enabled_modules.detect {|m| m.name == name}
end
# Return true if the module with the given name is enabled
def module_enabled?(name)
enabled_module(name).present?
def module_enabled?(module_name)
module_name = module_name.to_s
enabled_modules.detect {|m| m.name == module_name}
end
def enabled_module_names=(module_names)
@ -847,7 +840,7 @@ class Project < ActiveRecord::Base
# Copies issues from +project+
def copy_issues(project)
# Stores the source issue id as a key and the copied issues as the
# value. Used to map the two together for issue relations.
# value. Used to map the two togeather for issue relations.
issues_map = {}
# Store status and reopen locked/closed versions
@ -858,7 +851,7 @@ class Project < ActiveRecord::Base
# Get issues sorted by root_id, lft so that parent issues
# get copied before their children
project.issues.reorder('root_id, lft').each do |issue|
project.issues.reorder('root_id, lft').all.each do |issue|
new_issue = Issue.new
new_issue.copy_from(issue, :subtasks => false, :link => false)
new_issue.project = self
@ -1002,15 +995,15 @@ class Project < ActiveRecord::Base
# Returns the systemwide active activities merged with the project specific overrides
def system_activities_and_project_overrides(include_inactive=false)
t = TimeEntryActivity.table_name
scope = TimeEntryActivity.where(
"(#{t}.project_id IS NULL AND #{t}.id NOT IN (?)) OR (#{t}.project_id = ?)",
time_entry_activities.map(&:parent_id), id
)
unless include_inactive
scope = scope.active
if include_inactive
return TimeEntryActivity.shared.
where("id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)).all +
self.time_entry_activities
else
return TimeEntryActivity.shared.active.
where("id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)).all +
self.time_entry_activities.active
end
scope
end
# Archives subprojects recursively
@ -1025,8 +1018,6 @@ class Project < ActiveRecord::Base
set_or_update_position_under(parent)
end
public
# Inserts/moves the project so that target's children or root projects stay alphabetically sorted
def set_or_update_position_under(target_parent)
parent_was = parent

View File

@ -57,10 +57,6 @@ class QueryColumn
object.send name
end
def value_object(object)
object.send name
end
def css_classes
name
end
@ -84,21 +80,10 @@ class QueryCustomFieldColumn < QueryColumn
@cf
end
def value_object(object)
if custom_field.visible_by?(object.project, User.current)
cv = object.custom_values.select {|v| v.custom_field_id == @cf.id}
cv.size > 1 ? cv.sort {|a,b| a.value.to_s <=> b.value.to_s} : cv.first
else
nil
end
end
def value(object)
raw = value_object(object)
if raw.is_a?(Array)
raw.map {|r| @cf.cast_value(r.value)}
elsif raw
@cf.cast_value(raw.value)
if custom_field.visible_by?(object.project, User.current)
cv = object.custom_values.select {|v| v.custom_field_id == @cf.id}.collect {|v| @cf.cast_value(v.value)}
cv.size > 1 ? cv.sort {|a,b| a.to_s <=> b.to_s} : cv.first
else
nil
end
@ -120,7 +105,7 @@ class QueryAssociationCustomFieldColumn < QueryCustomFieldColumn
@association = association
end
def value_object(object)
def value(object)
if assoc = object.send(@association)
super(assoc)
end
@ -159,8 +144,8 @@ class Query < ActiveRecord::Base
after_save do |query|
if query.visibility_changed? && query.visibility != VISIBILITY_ROLES
query.roles.clear
end
query.roles.clear
end
end
class_attribute :operators
@ -257,9 +242,7 @@ class Query < ActiveRecord::Base
when :date, :date_past
case operator_for(field)
when "=", ">=", "<=", "><"
add_filter_error(field, :invalid) if values_for(field).detect {|v|
v.present? && (!v.match(/\A\d{4}-\d{2}-\d{2}(T\d{2}((:)?\d{2}){0,2}(Z|\d{2}:?\d{2})?)?\z/) || parse_date(v).nil?)
}
add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && (!v.match(/^\d{4}-\d{2}-\d{2}$/) || (Date.parse(v) rescue nil).nil?) }
when ">t-", "<t-", "t-", ">t+", "<t+", "t+", "><t+", "><t-"
add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
end
@ -556,7 +539,7 @@ class Query < ActiveRecord::Base
next unless v and !v.empty?
operator = operator_for(field)
# "me" value substitution
# "me" value subsitution
if %w(assigned_to_id author_id user_id watcher_id).include?(field)
if v.delete("me")
if User.current.logged?
@ -604,7 +587,7 @@ class Query < ActiveRecord::Base
db_field = 'value'
filter = @available_filters[field]
return nil unless filter
if filter[:field].format.target_class && filter[:field].format.target_class <= User
if filter[:format] == 'user'
if value.delete('me')
value.push User.current.id.to_s
end
@ -641,7 +624,7 @@ class Query < ActiveRecord::Base
if value.any?
case type_for(field)
when :date, :date_past
sql = date_clause(db_table, db_field, parse_date(value.first), parse_date(value.first))
sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil))
when :integer
if is_custom_filter
sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) = #{value.first.to_i})"
@ -676,7 +659,7 @@ class Query < ActiveRecord::Base
sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
when ">="
if [:date, :date_past].include?(type_for(field))
sql = date_clause(db_table, db_field, parse_date(value.first), nil)
sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil)
else
if is_custom_filter
sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) >= #{value.first.to_f})"
@ -686,7 +669,7 @@ class Query < ActiveRecord::Base
end
when "<="
if [:date, :date_past].include?(type_for(field))
sql = date_clause(db_table, db_field, nil, parse_date(value.first))
sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil))
else
if is_custom_filter
sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) <= #{value.first.to_f})"
@ -696,7 +679,7 @@ class Query < ActiveRecord::Base
end
when "><"
if [:date, :date_past].include?(type_for(field))
sql = date_clause(db_table, db_field, parse_date(value[0]), parse_date(value[1]))
sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil))
else
if is_custom_filter
sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) BETWEEN #{value[0].to_f} AND #{value[1].to_f})"
@ -781,13 +764,29 @@ class Query < ActiveRecord::Base
# Adds a filter for the given custom field
def add_custom_field_filter(field, assoc=nil)
options = field.format.query_filter_options(field, self)
if field.format.target_class && field.format.target_class <= User
if options[:values].is_a?(Array) && User.current.logged?
options[:values].unshift ["<< #{l(:label_me)} >>", "me"]
case field.field_format
when "text"
options = { :type => :text }
when "list"
options = { :type => :list_optional, :values => field.possible_values }
when "date"
options = { :type => :date }
when "bool"
options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] }
when "int"
options = { :type => :integer }
when "float"
options = { :type => :float }
when "user", "version"
return unless project
values = field.possible_values_options(project)
if User.current.logged? && field.field_format == 'user'
values.unshift ["<< #{l(:label_me)} >>", "me"]
end
options = { :type => :list_optional, :values => values }
else
options = { :type => :string }
end
filter_id = "cf_#{field.id}"
filter_name = field.name
if assoc.present?
@ -796,6 +795,7 @@ class Query < ActiveRecord::Base
end
add_available_filter filter_id, options.merge({
:name => filter_name,
:format => field.field_format,
:field => field
})
end
@ -826,24 +826,19 @@ class Query < ActiveRecord::Base
def date_clause(table, field, from, to)
s = []
if from
if from.is_a?(Date)
from = Time.local(from.year, from.month, from.day).yesterday.end_of_day
else
from = from - 1 # second
end
from_yesterday = from - 1
from_yesterday_time = Time.local(from_yesterday.year, from_yesterday.month, from_yesterday.day)
if self.class.default_timezone == :utc
from = from.utc
from_yesterday_time = from_yesterday_time.utc
end
s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from)])
s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from_yesterday_time.end_of_day)])
end
if to
if to.is_a?(Date)
to = Time.local(to.year, to.month, to.day).end_of_day
end
to_time = Time.local(to.year, to.month, to.day)
if self.class.default_timezone == :utc
to = to.utc
to_time = to_time.utc
end
s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to)])
s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to_time.end_of_day)])
end
s.join(' AND ')
end
@ -853,15 +848,6 @@ class Query < ActiveRecord::Base
date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
end
# Returns a Date or Time from the given filter value
def parse_date(arg)
if arg.to_s =~ /\A\d{4}-\d{2}-\d{2}T/
Time.parse(arg) rescue nil
else
Date.parse(arg) rescue nil
end
end
# Additional joins required for the given sort options
def joins_for_order_statement(order_options)
joins = []

View File

@ -40,7 +40,7 @@ class Repository < ActiveRecord::Base
validates_length_of :identifier, :maximum => IDENTIFIER_MAX_LENGTH, :allow_blank => true
validates_presence_of :identifier, :unless => Proc.new { |r| r.is_default? || r.set_as_default? }
validates_uniqueness_of :identifier, :scope => :project_id, :allow_blank => true
validates_exclusion_of :identifier, :in => %w(browse show entry raw changes annotate diff statistics graph revisions revision)
validates_exclusion_of :identifier, :in => %w(show entry raw changes annotate diff show stats graph)
# donwcase letters, digits, dashes, underscores but not digits only
validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :allow_blank => true
# Checks if the SCM is enabled when creating a repository
@ -194,13 +194,8 @@ class Repository < ActiveRecord::Base
scm.entry(path, identifier)
end
def scm_entries(path=nil, identifier=nil)
scm.entries(path, identifier)
end
protected :scm_entries
def entries(path=nil, identifier=nil)
entries = scm_entries(path, identifier)
entries = scm.entries(path, identifier)
load_entries_changesets(entries)
entries
end
@ -292,8 +287,9 @@ class Repository < ActiveRecord::Base
new_user_id = h[committer]
if new_user_id && (new_user_id.to_i != user_id.to_i)
new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
Changeset.where(["repository_id = ? AND committer = ?", id, committer]).
update_all("user_id = #{new_user_id.nil? ? 'NULL' : new_user_id}")
Changeset.update_all(
"user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }",
["repository_id = ? AND committer = ?", id, committer])
end
end
@committers = nil
@ -412,7 +408,7 @@ class Repository < ActiveRecord::Base
self.is_default = true
end
if is_default? && is_default_changed?
Repository.where(["project_id = ?", project_id]).update_all(["is_default = ?", false])
Repository.update_all(["is_default = ?", false], ["project_id = ?", project_id])
end
end

View File

@ -57,7 +57,7 @@ class Repository::Bazaar < Repository
scm.diff(path, rev, rev_to)
end
def scm_entries(path=nil, identifier=nil)
def entries(path=nil, identifier=nil)
scm.bzr_path_encodig = log_encoding
entries = scm.entries(path, identifier)
if entries
@ -80,9 +80,9 @@ class Repository::Bazaar < Repository
end
end
end
load_entries_changesets(entries)
entries
end
protected :scm_entries
def fetch_changesets
scm.bzr_path_encodig = log_encoding

View File

@ -47,7 +47,7 @@ class Repository::Cvs < Repository
scm.entry(path, rev.nil? ? nil : rev.committed_on)
end
def scm_entries(path=nil, identifier=nil)
def entries(path=nil, identifier=nil)
rev = nil
if ! identifier.nil?
rev = changesets.find_by_revision(identifier)
@ -69,9 +69,9 @@ class Repository::Cvs < Repository
end
end
end
load_entries_changesets(entries)
entries
end
protected :scm_entries
def cat(path, identifier=nil)
rev = nil
@ -138,9 +138,9 @@ class Repository::Cvs < Repository
# is not exclusive at all.
tmp_time = revision.time.clone
unless filechanges.find_by_path_and_revision(
scm.with_leading_slash(revision.paths[0][:path]),
revision.paths[0][:revision]
)
scm.with_leading_slash(revision.paths[0][:path]),
revision.paths[0][:revision]
)
cmt = Changeset.normalize_comments(revision.message, repo_log_encoding)
author_utf8 = Changeset.to_utf8(revision.author, repo_log_encoding)
cs = changesets.where(
@ -150,7 +150,7 @@ class Repository::Cvs < Repository
).first
# create a new changeset....
unless cs
# we use a temporary revision number here (just for inserting)
# we use a temporaray revision number here (just for inserting)
# later on, we calculate a continous positive number
tmp_time2 = tmp_time.clone.gmtime
branch = revision.paths[0][:branch]
@ -186,7 +186,7 @@ class Repository::Cvs < Repository
order('committed_on ASC, id ASC').
where("repository_id = ? AND revision LIKE 'tmp%'", id).
each do |changeset|
changeset.update_attribute :revision, next_revision_number
changeset.update_attribute :revision, next_revision_number
end
end # transaction
@current_revision_number = nil

View File

@ -45,7 +45,7 @@ class Repository::Darcs < Repository
scm.entry(path, patch.nil? ? nil : patch.scmid)
end
def scm_entries(path=nil, identifier=nil)
def entries(path=nil, identifier=nil)
patch = nil
if ! identifier.nil?
patch = changesets.find_by_revision(identifier)
@ -66,9 +66,9 @@ class Repository::Darcs < Repository
end
end
end
load_entries_changesets(entries)
entries
end
protected :scm_entries
def cat(path, identifier=nil)
patch = identifier.nil? ? nil : changesets.find_by_revision(identifier.to_s)

View File

@ -93,10 +93,11 @@ class Repository::Git < Repository
end
end
def scm_entries(path=nil, identifier=nil)
scm.entries(path, identifier, :report_last_commit => extra_report_last_commit)
def entries(path=nil, identifier=nil)
entries = scm.entries(path, identifier, :report_last_commit => extra_report_last_commit)
load_entries_changesets(entries)
entries
end
protected :scm_entries
# With SCMs that have a sequential commit numbering,
# such as Subversion and Mercurial,
@ -179,13 +180,10 @@ class Repository::Git < Repository
# So, Redmine needs to scan revisions and database every time.
#
# This is replacing the one-after-one queries.
# Find all revisions, that are in the database, and then remove them
# from the revision array.
# Find all revisions, that are in the database, and then remove them from the revision array.
# Then later we won't need any conditions for db existence.
# Query for several revisions at once, and remove them
# from the revisions array, if they are there.
# Do this in chunks, to avoid eventual memory problems
# (in case of tens of thousands of commits).
# Query for several revisions at once, and remove them from the revisions array, if they are there.
# Do this in chunks, to avoid eventual memory problems (in case of tens of thousands of commits).
# If there are no revisions (because the original code's algorithm filtered them),
# then this part will be stepped over.
# We make queries, just if there is any revision.
@ -194,12 +192,13 @@ class Repository::Git < Repository
revisions_copy = revisions.clone # revisions will change
while offset < revisions_copy.size
scmids = revisions_copy.slice(offset, limit).map{|x| x.scmid}
recent_changesets_slice = changesets.where(:scmid => scmids)
recent_changesets_slice = changesets.where(:scmid => scmids).all
# Subtract revisions that redmine already knows about
recent_revisions = recent_changesets_slice.map{|c| c.scmid}
revisions.reject!{|r| recent_revisions.include?(r.scmid)}
offset += limit
end
revisions.each do |rev|
transaction do
# There is no search in the db for this revision, because above we ensured,
@ -241,6 +240,7 @@ class Repository::Git < Repository
def latest_changesets(path,rev,limit=10)
revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
return [] if revisions.nil? || revisions.empty?
changesets.where(:scmid => revisions.map {|c| c.scmid}).all
end

View File

@ -59,7 +59,7 @@ class Repository::Mercurial < Repository
# Returns the readable identifier for the given mercurial changeset
def self.format_changeset_identifier(changeset)
"#{changeset.revision}:#{changeset.scmid[0, 12]}"
"#{changeset.revision}:#{changeset.scmid}"
end
# Returns the identifier for the given Mercurial changeset
@ -71,28 +71,6 @@ class Repository::Mercurial < Repository
super(cs, cs_to, ' ')
end
def modify_entry_lastrev_identifier(entry)
if entry.lastrev && entry.lastrev.identifier
entry.lastrev.identifier = scmid_for_inserting_db(entry.lastrev.identifier)
end
end
private :modify_entry_lastrev_identifier
def entry(path=nil, identifier=nil)
entry = scm.entry(path, identifier)
return nil if entry.nil?
modify_entry_lastrev_identifier(entry)
entry
end
def scm_entries(path=nil, identifier=nil)
entries = scm.entries(path, identifier)
return nil if entries.nil?
entries.each {|entry| modify_entry_lastrev_identifier(entry)}
entries
end
protected :scm_entries
# Finds and returns a revision with a number or the beginning of a hash
def find_changeset_by_name(name)
return nil if name.blank?
@ -122,28 +100,6 @@ class Repository::Mercurial < Repository
all
end
def is_short_id_in_db?
return @is_short_id_in_db unless @is_short_id_in_db.nil?
cs = changesets.first
@is_short_id_in_db = (!cs.nil? && cs.scmid.length != 40)
end
private :is_short_id_in_db?
def scmid_for_inserting_db(scmid)
is_short_id_in_db? ? scmid[0, 12] : scmid
end
def nodes_in_branch(rev, branch_limit)
scm.nodes_in_branch(rev, :limit => branch_limit).collect do |b|
scmid_for_inserting_db(b)
end
end
def tag_scmid(rev)
scmid = scm.tagmap[rev]
scmid.nil? ? nil : scmid_for_inserting_db(scmid)
end
def latest_changesets_cond(path, rev, limit)
cond, args = [], []
if scm.branchmap.member? rev
@ -155,11 +111,11 @@ class Repository::Mercurial < Repository
# Revisions in root directory and sub directory are not equal.
# So, in order to get correct limit, we need to get all revisions.
# But, it is very heavy.
# Mercurial does not treat directory.
# Mercurial does not treat direcotry.
# So, "hg log DIR" is very heavy.
branch_limit = path.blank? ? limit : ( limit * 5 )
args << nodes_in_branch(rev, branch_limit)
elsif last = rev ? find_changeset_by_name(tag_scmid(rev) || rev) : nil
args << scm.nodes_in_branch(rev, :limit => branch_limit)
elsif last = rev ? find_changeset_by_name(scm.tagmap[rev] || rev) : nil
cond << "#{Changeset.table_name}.id <= ?"
args << last.id
end
@ -185,23 +141,16 @@ class Repository::Mercurial < Repository
(db_rev + 1).step(scm_rev, FETCH_AT_ONCE) do |i|
scm.each_revision('', i, [i + FETCH_AT_ONCE - 1, scm_rev].min) do |re|
transaction do
parents = (re.parents || []).collect do |rp|
find_changeset_by_name(scmid_for_inserting_db(rp))
end.compact
parents = (re.parents || []).collect{|rp| find_changeset_by_name(rp)}.compact
cs = Changeset.create(:repository => self,
:revision => re.revision,
:scmid => scmid_for_inserting_db(re.scmid),
:scmid => re.scmid,
:committer => re.author,
:committed_on => re.time,
:comments => re.message,
:parents => parents)
unless cs.new_record?
re.paths.each do |e|
if from_revision = e[:from_revision]
e[:from_revision] = scmid_for_inserting_db(from_revision)
end
cs.create_change(e)
end
re.paths.each { |e| cs.create_change(e) }
end
end
end

View File

@ -90,13 +90,12 @@ class Repository::Subversion < Repository
def load_entries_changesets(entries)
return unless entries
entries_with_identifier =
entries.select {|entry| entry.lastrev && entry.lastrev.identifier.present?}
entries_with_identifier = entries.select {|entry| entry.lastrev && entry.lastrev.identifier.present?}
identifiers = entries_with_identifier.map {|entry| entry.lastrev.identifier}.compact.uniq
if identifiers.any?
changesets_by_identifier =
changesets.where(:revision => identifiers).
includes(:user, :repository).group_by(&:revision)
changesets_by_identifier = changesets.where(:revision => identifiers).includes(:user, :repository).all.group_by(&:revision)
entries_with_identifier.each do |entry|
if m = changesets_by_identifier[entry.lastrev.identifier]
entry.changeset = m.first

View File

@ -83,9 +83,7 @@ class Setting < ActiveRecord::Base
validates_uniqueness_of :name
validates_inclusion_of :name, :in => @@available_settings.keys
validates_numericality_of :value, :only_integer => true, :if => Proc.new { |setting|
(s = @@available_settings[setting.name]) && s['format'] == 'int'
}
validates_numericality_of :value, :only_integer => true, :if => Proc.new { |setting| @@available_settings[setting.name]['format'] == 'int' }
# Hash used to cache setting values
@cached_settings = {}
@ -242,10 +240,9 @@ private
def self.find_or_default(name)
name = name.to_s
raise "There's no setting named #{name}" unless @@available_settings.has_key?(name)
setting = where(:name => name).first
setting = find_by_name(name)
unless setting
setting = new
setting.name = name
setting = new(:name => name)
setting.value = @@available_settings[name]['default']
end
setting

View File

@ -77,16 +77,6 @@ class TimeEntry < ActiveRecord::Base
end
end
def safe_attributes=(attrs, user=User.current)
attrs = super
if !new_record? && issue && issue.project_id != project_id
if user.allowed_to?(:log_time, issue.project)
self.project_id = issue.project_id
end
end
attrs
end
def set_project_if_nil
self.project = issue.project if issue && project.nil?
end

View File

@ -91,10 +91,8 @@ class TimeEntryQuery < Query
def available_columns
return @available_columns if @available_columns
@available_columns = self.class.available_columns.dup
@available_columns += TimeEntryCustomField.visible.
map {|cf| QueryCustomFieldColumn.new(cf) }
@available_columns += IssueCustomField.visible.
map {|cf| QueryAssociationCustomFieldColumn.new(:issue, cf) }
@available_columns += TimeEntryCustomField.visible.all.map {|cf| QueryCustomFieldColumn.new(cf) }
@available_columns += IssueCustomField.visible.all.map {|cf| QueryAssociationCustomFieldColumn.new(:issue, cf) }
@available_columns
end

View File

@ -63,7 +63,8 @@ class Tracker < ActiveRecord::Base
connection.select_rows("SELECT DISTINCT old_status_id, new_status_id FROM #{WorkflowTransition.table_name} WHERE tracker_id = #{id} AND type = 'WorkflowTransition'").
flatten.
uniq
@issue_statuses = IssueStatus.where(:id => ids).all.sort
@issue_statuses = IssueStatus.find_all_by_id(ids).sort
end
def disabled_core_fields

View File

@ -32,11 +32,6 @@ class User < Principal
:order => %w(firstname lastname id),
:setting_order => 2
},
:firstinitial_lastname => {
:string => '#{firstname.to_s.gsub(/(([[:alpha:]])[[:alpha:]]*\.?)/, \'\2.\')} #{lastname}',
:order => %w(firstname lastname id),
:setting_order => 2
},
:firstname => {
:string => '#{firstname}',
:order => %w(firstname id),
@ -73,10 +68,8 @@ class User < Principal
['none', :label_user_mail_option_none]
]
has_and_belongs_to_many :groups,
:join_table => "#{table_name_prefix}groups_users#{table_name_suffix}",
:after_add => Proc.new {|user, group| group.user_added(user)},
:after_remove => Proc.new {|user, group| group.user_removed(user)}
has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
:after_remove => Proc.new {|user, group| group.user_removed(user)}
has_many :changesets, :dependent => :nullify
has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
@ -314,18 +307,6 @@ class User < Principal
@time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
end
def force_default_language?
Setting.force_default_language_for_loggedin?
end
def language
if force_default_language?
Setting.default_language
else
super
end
end
def wants_comments_in_reverse_order?
self.pref[:comments_sorting] == 'desc'
end
@ -384,10 +365,10 @@ class User < Principal
# Find a user account by matching the exact login and then a case-insensitive
# version. Exact matches will be given priority.
def self.find_by_login(login)
login = Redmine::CodesetUtil.replace_invalid_utf8(login.to_s)
if login.present?
login = login.to_s
# First look for an exact match
user = where(:login => login).detect {|u| u.login == login}
user = where(:login => login).all.detect {|u| u.login == login}
unless user
# Fail over to case-insensitive if none was found
user = where("LOWER(login) = ?", login.downcase).first
@ -626,11 +607,11 @@ class User < Principal
end
def self.current=(user)
RequestStore.store[:current_user] = user
Thread.current[:current_user] = user
end
def self.current
RequestStore.store[:current_user] ||= User.anonymous
Thread.current[:current_user] ||= User.anonymous
end
# Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
@ -683,27 +664,23 @@ class User < Principal
return if self.id.nil?
substitute = User.anonymous
Attachment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
Comment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
Issue.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
Issue.where(['assigned_to_id = ?', id]).update_all('assigned_to_id = NULL')
Journal.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
JournalDetail.
where(["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]).
update_all(['old_value = ?', substitute.id.to_s])
JournalDetail.
where(["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]).
update_all(['value = ?', substitute.id.to_s])
Message.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
News.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
# Remove private queries and keep public ones
::Query.delete_all ['user_id = ? AND visibility = ?', id, ::Query::VISIBILITY_PRIVATE]
::Query.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
TimeEntry.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
::Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
Token.delete_all ['user_id = ?', id]
Watcher.delete_all ['user_id = ?', id]
WikiContent.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
WikiContent::Version.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
end
# Return password digest

View File

@ -203,7 +203,7 @@ class Version < ActiveRecord::Base
["(CASE WHEN #{table}.effective_date IS NULL THEN 1 ELSE 0 END)", "#{table}.effective_date", "#{table}.name", "#{table}.id"]
end
scope :sorted, lambda { order(fields_for_order_statement) }
scope :sorted, order(fields_for_order_statement)
# Returns the sharings that +user+ can set the version to
def allowed_sharings(user = User.current)
@ -232,7 +232,7 @@ class Version < ActiveRecord::Base
unless @issue_count
@open_issues_count = 0
@closed_issues_count = 0
fixed_issues.group(:status).count.each do |status, count|
fixed_issues.count(:all, :group => :status).each do |status, count|
if status.is_closed?
@closed_issues_count += count
else
@ -256,7 +256,7 @@ class Version < ActiveRecord::Base
# Returns the average estimated time of assigned issues
# or 1 if no issue has an estimated time
# Used to weight unestimated issues in progress calculation
# Used to weigth unestimated issues in progress calculation
def estimated_average
if @estimated_average.nil?
average = fixed_issues.average(:estimated_hours).to_f

View File

@ -42,7 +42,7 @@ class Watcher < ActiveRecord::Base
prune_single_user(options[:user], options)
else
pruned = 0
User.where("id IN (SELECT DISTINCT user_id FROM #{table_name})").each do |user|
User.where("id IN (SELECT DISTINCT user_id FROM #{table_name})").all.each do |user|
pruned += prune_single_user(user, options)
end
pruned
@ -60,14 +60,13 @@ class Watcher < ActiveRecord::Base
def self.prune_single_user(user, options={})
return unless user.is_a?(User)
pruned = 0
where(:user_id => user.id).each do |watcher|
where(:user_id => user.id).all.each do |watcher|
next if watcher.watchable.nil?
if options.has_key?(:project)
unless watcher.watchable.respond_to?(:project) &&
watcher.watchable.project == options[:project]
next
end
next unless watcher.watchable.respond_to?(:project) && watcher.watchable.project == options[:project]
end
if watcher.watchable.respond_to?(:visible?)
unless watcher.watchable.visible?(user)
watcher.destroy

View File

@ -40,7 +40,7 @@ class WikiContent < ActiveRecord::Base
page.nil? ? [] : page.attachments
end
# Returns the mail addresses of users that should be notified
# Returns the mail adresses of users that should be notified
def recipients
notified = project.notified_users
notified.reject! {|user| !visible?(user)}

View File

@ -82,12 +82,12 @@ class WikiPage < ActiveRecord::Base
# Manage redirects if the title has changed
if !@previous_title.blank? && (@previous_title != title) && !new_record?
# Update redirects that point to the old title
wiki.redirects.where(:redirects_to => @previous_title).each do |r|
wiki.redirects.find_all_by_redirects_to(@previous_title).each do |r|
r.redirects_to = title
r.title == r.redirects_to ? r.destroy : r.save
end
# Remove redirects for the new title
wiki.redirects.where(:title => title).each(&:destroy)
wiki.redirects.find_all_by_title(title).each(&:destroy)
# Create a redirect to the new title
wiki.redirects << WikiRedirect.new(:title => @previous_title, :redirects_to => title) unless redirect_existing_links == "0"
@previous_title = nil
@ -96,7 +96,7 @@ class WikiPage < ActiveRecord::Base
def remove_redirects
# Remove redirects to this page
wiki.redirects.where(:redirects_to => title).each(&:destroy)
wiki.redirects.find_all_by_redirects_to(title).each(&:destroy)
end
def pretty_title
@ -104,11 +104,9 @@ class WikiPage < ActiveRecord::Base
end
def content_for_version(version=nil)
if content
result = content.versions.find_by_version(version.to_i) if version
result ||= content
result
end
result = content.versions.find_by_version(version.to_i) if version
result ||= content
result
end
def diff(version_to=nil, version_from=nil)

View File

@ -19,43 +19,20 @@ class WorkflowPermission < WorkflowRule
validates_inclusion_of :rule, :in => %w(readonly required)
validate :validate_field_name
# Returns the workflow permissions for the given trackers and roles
# grouped by status_id
# Replaces the workflow permissions for the given tracker and role
#
# Example:
# WorkflowPermission.rules_by_status_id trackers, roles
# # => {1 => {'start_date' => 'required', 'due_date' => 'readonly'}}
def self.rules_by_status_id(trackers, roles)
WorkflowPermission.where(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id)).inject({}) do |h, w|
h[w.old_status_id] ||= {}
h[w.old_status_id][w.field_name] ||= []
h[w.old_status_id][w.field_name] << w.rule
h
end
end
# WorkflowPermission.replace_permissions role, tracker, {'due_date' => {'1' => 'readonly', '2' => 'required'}}
def self.replace_permissions(tracker, role, permissions)
destroy_all(:tracker_id => tracker.id, :role_id => role.id)
# Replaces the workflow permissions for the given trackers and roles
#
# Example:
# WorkflowPermission.replace_permissions trackers, roles, {'1' => {'start_date' => 'required', 'due_date' => 'readonly'}}
def self.replace_permissions(trackers, roles, permissions)
trackers = Array.wrap trackers
roles = Array.wrap roles
transaction do
permissions.each { |status_id, rule_by_field|
rule_by_field.each { |field, rule|
destroy_all(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id), :old_status_id => status_id, :field_name => field)
if rule.present?
trackers.each do |tracker|
roles.each do |role|
WorkflowPermission.create(:role_id => role.id, :tracker_id => tracker.id, :old_status_id => status_id, :field_name => field, :rule => rule)
end
end
end
}
permissions.each { |field, rule_by_status_id|
rule_by_status_id.each { |status_id, rule|
if rule.present?
WorkflowPermission.create(:role_id => role.id, :tracker_id => tracker.id, :old_status_id => status_id, :field_name => field, :rule => rule)
end
}
end
}
end
protected

View File

@ -21,8 +21,9 @@ class WorkflowTransition < WorkflowRule
# Returns workflow transitions count by tracker and role
def self.count_by_tracker_and_role
counts = connection.select_all("SELECT role_id, tracker_id, count(id) AS c FROM #{table_name} WHERE type = 'WorkflowTransition' GROUP BY role_id, tracker_id")
roles = Role.sorted
trackers = Tracker.sorted
roles = Role.sorted.all
trackers = Tracker.sorted.all
result = []
trackers.each do |tracker|
t = []
@ -32,71 +33,7 @@ class WorkflowTransition < WorkflowRule
end
result << [tracker, t]
end
result
end
def self.replace_transitions(trackers, roles, transitions)
trackers = Array.wrap trackers
roles = Array.wrap roles
transaction do
records = WorkflowTransition.where(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id)).all
transitions.each do |old_status_id, transitions_by_new_status|
transitions_by_new_status.each do |new_status_id, transition_by_rule|
transition_by_rule.each do |rule, transition|
trackers.each do |tracker|
roles.each do |role|
w = records.select {|r|
r.old_status_id == old_status_id.to_i &&
r.new_status_id == new_status_id.to_i &&
r.tracker_id == tracker.id &&
r.role_id == role.id &&
!r.destroyed?
}
if rule == 'always'
w = w.select {|r| !r.author && !r.assignee}
else
w = w.select {|r| r.author || r.assignee}
end
if w.size > 1
w[1..-1].each(&:destroy)
end
w = w.first
if transition == "1" || transition == true
unless w
w = WorkflowTransition.new(:old_status_id => old_status_id, :new_status_id => new_status_id, :tracker_id => tracker.id, :role_id => role.id)
records << w
end
w.author = true if rule == "author"
w.assignee = true if rule == "assignee"
w.save if w.changed?
elsif w
if rule == 'always'
w.destroy
elsif rule == 'author'
if w.assignee
w.author = false
w.save if w.changed?
else
w.destroy
end
elsif rule == 'assignee'
if w.author
w.assignee = false
w.save if w.changed?
else
w.destroy
end
end
end
end
end
end
end
end
end
end
end

View File

@ -16,10 +16,7 @@
<p><%= f.text_field :firstname, :required => true %></p>
<p><%= f.text_field :lastname, :required => true %></p>
<p><%= f.text_field :mail, :required => true %></p>
<% unless @user.force_default_language? %>
<p><%= f.select :language, lang_options_for_select %></p>
<% end %>
<% if Setting.openid? %>
<p><%= f.text_field :identity_url %></p>

View File

@ -9,55 +9,11 @@
<%= content_tag('span', link_to(h(plugin.url), plugin.url), :class => 'url') unless plugin.url.blank? %>
</td>
<td class="author"><%= plugin.author_url.blank? ? h(plugin.author) : link_to(h(plugin.author), plugin.author_url) %></td>
<td class="version"><span class="icon"><%= plugin.version %></span></td>
<td class="version"><%=h plugin.version %></td>
<td class="configure"><%= link_to(l(:button_configure), plugin_settings_path(plugin)) if plugin.configurable? %></td>
</tr>
<% end %>
</table>
<p><a href="#" id="check-for-updates"><%= l(:label_check_for_updates) %></a></p>
<% else %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% end %>
<%= javascript_tag do %>
$(document).ready(function(){
$("#check-for-updates").click(function(e){
e.preventDefault();
$.ajax({
dataType: "jsonp",
url: "http://www.redmine.org/plugins/check_updates",
data: <%= raw_json plugin_data_for_updates(@plugins) %>,
timeout: 3000,
beforeSend: function(){
$('#ajax-indicator').show();
},
success: function(data){
$('#ajax-indicator').hide();
$("table.plugins td.version span").addClass("unknown");
$.each(data, function(plugin_id, plugin_data){
var s = $("tr#plugin-"+plugin_id+" td.version span");
s.removeClass("icon-checked icon-warning unknown");
if (plugin_data.url) {
if (s.parent("a").length>0) {
s.unwrap();
}
s.addClass("found");
s.wrap($("<a></a>").attr("href", plugin_data.url).attr("target", "_blank"));
}
if (plugin_data.c == s.text()) {
s.addClass("icon-checked");
} else if (plugin_data.c) {
s.addClass("icon-warning");
s.attr("title", "<%= escape_javascript l(:label_latest_compatible_version) %>: "+plugin_data.c);
}
});
$("table.plugins td.version span.unknown").addClass("icon-help").attr("title", "<%= escape_javascript l(:label_unknown_plugin) %>");
},
error: function(){
$('#ajax-indicator').hide();
alert("Unable to retrieve plugin informations from www.redmine.org");
}
});
});
});
<% end if @plugins.any? %>

View File

@ -1,6 +1,6 @@
<%= raw @issues.map {|issue| {
'id' => issue.id,
'label' => "#{issue.tracker} ##{issue.id}: #{issue.subject.to_s.truncate(60)}",
'label' => "#{issue.tracker} ##{issue.id}: #{truncate issue.subject.to_s, :length => 60}",
'value' => issue.id
}
}.to_json

View File

@ -1,3 +1,5 @@
<% selected_tab = params[:tab] ? params[:tab].to_s : tabs.first[:name] %>
<div class="tabs">
<ul>
<% tabs.each do |tab| -%>
@ -8,8 +10,8 @@
<% end -%>
</ul>
<div class="tabs-buttons" style="display:none;">
<button class="tab-left" onclick="moveTabLeft(this); return false;"></button>
<button class="tab-right" onclick="moveTabRight(this); return false;"></button>
<button class="tab-left" onclick="moveTabLeft(this);"></button>
<button class="tab-right" onclick="moveTabRight(this);"></button>
</div>
</div>

View File

@ -1,10 +1,9 @@
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do
xml.title truncate_single_line_raw(@title, 100)
xml.title truncate_single_line(@title, :length => 100)
xml.link "rel" => "self", "href" => url_for(params.merge(:only_path => false))
xml.link "rel" => "alternate", "href" => url_for(params.merge(:only_path => false, :format => nil, :key => nil))
xml.id url_for(:controller => 'welcome', :only_path => false)
xml.icon favicon_url
xml.updated((@items.first ? @items.first.event_datetime : Time.now).xmlschema)
xml.author { xml.name "#{Setting.app_title}" }
xml.generator(:uri => Redmine::Info.url) { xml.text! Redmine::Info.app_name; }
@ -12,9 +11,9 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do
xml.entry do
url = url_for(item.event_url(:only_path => false))
if @project
xml.title truncate_single_line_raw(item.event_title, 100)
xml.title truncate_single_line(item.event_title, :length => 100)
else
xml.title truncate_single_line_raw("#{item.project} - #{item.event_title}", 100)
xml.title truncate_single_line("#{item.project} - #{item.event_title}", :length => 100)
end
xml.link "rel" => "alternate", "href" => url
xml.id url

View File

@ -1,12 +1,14 @@
<%= error_messages_for 'custom_field' %>
<% if @custom_field.is_a?(IssueCustomField) %>
<div class="splitcontentleft">
<div class="box tabular">
<p><%= f.select :field_format, custom_field_formats_for_select(@custom_field), {}, :disabled => !@custom_field.new_record? %></p>
<p><%= f.text_field :name, :required => true %></p>
<p><%= f.text_area :description, :rows => 7 %></p>
<% end %>
<% if @custom_field.format.multiple_supported %>
<div class="box tabular">
<p><%= f.text_field :name, :required => true %></p>
<p><%= f.select :field_format, custom_field_formats_for_select(@custom_field), {}, :disabled => !@custom_field.new_record? %></p>
<% if @custom_field.format_in? 'list', 'user', 'version' %>
<p>
<%= f.check_box :multiple %>
<% if !@custom_field.new_record? && @custom_field.multiple %>
@ -15,38 +17,56 @@
</p>
<% end %>
<%= render_custom_field_format_partial f, @custom_field %>
<% unless @custom_field.format_in? 'list', 'bool', 'date', 'user', 'version' %>
<p><label for="custom_field_min_length"><%=l(:label_min_max_length)%></label>
<%= f.text_field :min_length, :size => 5, :no_label => true %> -
<%= f.text_field :max_length, :size => 5, :no_label => true %><br />(<%=l(:text_min_max_length_info)%>)</p>
<p><%= f.text_field :regexp, :size => 50 %><br />(<%=l(:text_regexp_info)%>)</p>
<% end %>
<% if @custom_field.format_in? 'list' %>
<p>
<%= f.text_area :possible_values, :value => @custom_field.possible_values.to_a.join("\n"), :rows => 15 %>
<em class="info"><%= l(:text_custom_field_possible_values_info) %></em>
</p>
<% end %>
<% case @custom_field.field_format %>
<% when 'bool' %>
<p><%= f.check_box(:default_value) %></p>
<% when 'text' %>
<p><%= f.text_area(:default_value, :rows => 8) %></p>
<% when 'date' %>
<p><%= f.text_field(:default_value, :size => 10) %></p>
<%= calendar_for('custom_field_default_value') %>
<% when 'user', 'version' %>
<% else %>
<p><%= f.text_field(:default_value) %></p>
<% end %>
<%= call_hook(:view_custom_fields_form_upper_box, :custom_field => @custom_field, :form => f) %>
</div>
<p><%= submit_tag l(:button_save) %></p>
</div>
<div class="splitcontentright">
<div class="box tabular">
<% case @custom_field.class.name
when "IssueCustomField" %>
<p><%= f.check_box :is_required %></p>
<p><%= f.check_box :is_for_all, :data => {:disables => '#custom_field_project_ids input'} %></p>
<p><%= f.check_box :is_for_all %></p>
<p><%= f.check_box :is_filter %></p>
<% if @custom_field.format.searchable_supported %>
<p><%= f.check_box :searchable %></p>
<% end %>
<p>
<label><%= l(:field_visible) %></label>
<label class="block">
<%= radio_button_tag 'custom_field[visible]', 1, @custom_field.visible?, :id => 'custom_field_visible_on',
:data => {:disables => '.custom_field_role input'} %>
<%= radio_button_tag 'custom_field[visible]', 1, @custom_field.visible?, :id => 'custom_field_visible_on' %>
<%= l(:label_visibility_public) %>
</label>
<label class="block">
<%= radio_button_tag 'custom_field[visible]', 0, !@custom_field.visible?, :id => 'custom_field_visible_off',
:data => {:enables => '.custom_field_role input'} %>
<%= radio_button_tag 'custom_field[visible]', 0, !@custom_field.visible?, :id => 'custom_field_visible_off' %>
<%= l(:label_visibility_roles) %>:
</label>
<% Role.givable.sorted.each do |role| %>
<label class="block custom_field_role" style="padding-left:2em;">
<%= check_box_tag 'custom_field[role_ids][]', role.id, @custom_field.roles.include?(role), :id => nil %>
<%= check_box_tag 'custom_field[role_ids][]', role.id, @custom_field.roles.include?(role) %>
<%= role.name %>
</label>
<% end %>
@ -62,9 +82,7 @@ when "IssueCustomField" %>
<% when "ProjectCustomField" %>
<p><%= f.check_box :is_required %></p>
<p><%= f.check_box :visible %></p>
<% if @custom_field.format.searchable_supported %>
<p><%= f.check_box :searchable %></p>
<% end %>
<p><%= f.check_box :is_filter %></p>
<% when "VersionCustomField" %>
@ -85,10 +103,13 @@ when "IssueCustomField" %>
<% end %>
<%= call_hook(:"view_custom_fields_form_#{@custom_field.type.to_s.underscore}", :custom_field => @custom_field, :form => f) %>
</div>
<%= submit_tag l(:button_save) %>
<% if @custom_field.is_a?(IssueCustomField) %>
<fieldset class="box" id="custom_field_tracker_ids"><legend><%=l(:label_tracker_plural)%></legend>
<% Tracker.sorted.each do |tracker| %>
</div>
<div class="splitcontentright">
<fieldset class="box"><legend><%=l(:label_tracker_plural)%></legend>
<% Tracker.sorted.all.each do |tracker| %>
<%= check_box_tag "custom_field[tracker_ids][]",
tracker.id,
(@custom_field.trackers.include? tracker),
@ -98,7 +119,6 @@ when "IssueCustomField" %>
</label>
<% end %>
<%= hidden_field_tag "custom_field[tracker_ids][]", '' %>
<p><%= check_all_links 'custom_field_tracker_ids' %></p>
</fieldset>
<fieldset class="box" id="custom_field_project_ids"><legend><%= l(:label_project_plural) %></legend>
@ -108,7 +128,20 @@ when "IssueCustomField" %>
<%= hidden_field_tag('custom_field[project_ids][]', '', :id => nil) %>
<p><%= check_all_links 'custom_field_project_ids' %></p>
</fieldset>
<% end %>
</div>
<% end %>
<% include_calendar_headers_tags %>
<%= javascript_tag do %>
function toggleCustomFieldRoles(){
var checked = $("#custom_field_visible_on").is(':checked');
$('.custom_field_role input').attr('disabled', checked);
}
$("#custom_field_visible_on, #custom_field_visible_off").change(toggleCustomFieldRoles);
$(document).ready(toggleCustomFieldRoles);
$("#custom_field_is_for_all").change(function(){
$("#custom_field_project_ids input").attr("disabled", $(this).is(":checked"));
}).trigger('change');
<% end %>

View File

@ -14,7 +14,7 @@
<% (@custom_fields_by_type[tab[:name]] || []).sort.each do |custom_field| -%>
<tr class="<%= cycle("odd", "even") %>">
<td class="name"><%= link_to h(custom_field.name), edit_custom_field_path(custom_field) %></td>
<td><%= l(custom_field.format.label) %></td>
<td><%= l(Redmine::CustomFieldFormat.label_for(custom_field.field_format)) %></td>
<td><%= checked_image custom_field.is_required? %></td>
<% if tab[:name] == 'IssueCustomField' %>
<td><%= checked_image custom_field.is_for_all? %></td>
@ -28,3 +28,5 @@
<% end; reset_cycle %>
</tbody>
</table>
<p><%= link_to l(:label_custom_field_new), new_custom_field_path(:type => tab[:name]), :class => 'icon icon-add' %></p>

View File

@ -1,3 +0,0 @@
<p><%= f.select :default_value, [[]]+@custom_field.possible_values_options %></p>
<p><%= f.text_field :url_pattern, :size => 50, :label => :label_link_values_to %></p>
<p><%= edit_tag_style_tag f %></p>

View File

@ -1,3 +0,0 @@
<p><%= f.text_field(:default_value, :size => 10) %></p>
<%= calendar_for('custom_field_default_value') %>
<p><%= f.text_field :url_pattern, :size => 50, :label => :label_link_values_to %></p>

View File

@ -1,3 +0,0 @@
<%= render :partial => 'custom_fields/formats/regexp', :locals => {:f => f, :custom_field => custom_field} %>
<p><%= f.text_field :url_pattern, :size => 50, :label => :field_url %></p>
<p><%= f.text_field(:default_value) %></p>

View File

@ -1,7 +0,0 @@
<p>
<%= f.text_area :possible_values, :value => @custom_field.possible_values.to_a.join("\n"), :rows => 15 %>
<em class="info"><%= l(:text_custom_field_possible_values_info) %></em>
</p>
<p><%= f.text_field(:default_value) %></p>
<p><%= f.text_field :url_pattern, :size => 50, :label => :label_link_values_to %></p>
<p><%= edit_tag_style_tag f %></p>

View File

@ -1,3 +0,0 @@
<%= render :partial => 'custom_fields/formats/regexp', :locals => {:f => f, :custom_field => custom_field} %>
<p><%= f.text_field(:default_value) %></p>
<p><%= f.text_field :url_pattern, :size => 50, :label => :label_link_values_to %></p>

View File

@ -1,9 +0,0 @@
<p>
<label for="custom_field_min_length"><%=l(:label_min_max_length)%></label>
<%= f.text_field :min_length, :size => 5, :no_label => true %> -
<%= f.text_field :max_length, :size => 5, :no_label => true %>
</p>
<p>
<%= f.text_field :regexp, :size => 50 %>
<em class="info"><%= l(:text_regexp_info) %></em>
</p>

View File

@ -1,4 +0,0 @@
<%= render :partial => 'custom_fields/formats/regexp', :locals => {:f => f, :custom_field => custom_field} %>
<p><%= f.check_box :text_formatting, {:label => :setting_text_formatting, :data => {:disables => '#custom_field_url_pattern'}}, 'full', '' %></p>
<p><%= f.text_field(:default_value) %></p>
<p><%= f.text_field :url_pattern, :size => 50, :label => :label_link_values_to %></p>

View File

@ -1,3 +0,0 @@
<%= render :partial => 'custom_fields/formats/regexp', :locals => {:f => f, :custom_field => custom_field} %>
<p><%= f.check_box :text_formatting, {:label => :setting_text_formatting}, 'full', '' %></p>
<p><%= f.text_area(:default_value, :rows => 5) %></p>

View File

@ -1,24 +0,0 @@
<p>
<label><%= l(:label_role) %></label>
<label class="block">
<%= radio_button_tag 'status', 1, custom_field.user_role.blank?, :id => 'custom_field_user_role_all',
:data => {:disables => '.custom_field_user_role input'} %>
<%= l(:label_all) %>
</label>
<label class="block">
<%= radio_button_tag 'status', 0, custom_field.user_role.present?, :id => 'custom_field_user_role_only',
:data => {:enables => '.custom_field_user_role input'} %>
<%= l(:label_only) %>:
</label>
<% Role.givable.sorted.each do |role| %>
<label class="block custom_field_user_role" style="padding-left:2em;">
<%= check_box_tag 'custom_field[user_role][]',
role.id,
custom_field.user_role.is_a?(Array) && custom_field.user_role.include?(role.id.to_s),
:id => nil %>
<%= role.name %>
</label>
<% end %>
<%= hidden_field_tag 'custom_field[user_role][]', '' %>
</p>
<p><%= edit_tag_style_tag f %></p>

View File

@ -1,24 +0,0 @@
<p>
<label><%= l(:field_status) %></label>
<label class="block">
<%= radio_button_tag 'status', 1, custom_field.version_status.blank?, :id => 'custom_field_version_status_all',
:data => {:disables => '.custom_field_version_status input'} %>
<%= l(:label_all) %>
</label>
<label class="block">
<%= radio_button_tag 'status', 0, custom_field.version_status.present?, :id => 'custom_field_version_status_only',
:data => {:enables => '.custom_field_version_status input'} %>
<%= l(:label_only) %>:
</label>
<% Version::VERSION_STATUSES.each do |status| %>
<label class="block custom_field_version_status" style="padding-left:2em;">
<%= check_box_tag 'custom_field[version_status][]',
status,
custom_field.version_status.is_a?(Array) && custom_field.version_status.include?(status),
:id => nil %>
<%= l("version_status_#{status}") %>
</label>
<% end %>
<%= hidden_field_tag 'custom_field[version_status][]', '' %>
</p>
<p><%= edit_tag_style_tag f %></p>

View File

@ -6,8 +6,8 @@ api.array :custom_fields do
api.customized_type field.class.customized_class.name.underscore if field.class.customized_class
api.field_format field.field_format
api.regexp field.regexp
api.min_length field.min_length
api.max_length field.max_length
api.min_length (field.min_length == 0 ? nil : field.min_length)
api.max_length (field.max_length == 0 ? nil : field.max_length)
api.is_required field.is_required?
api.is_filter field.is_filter?
api.searchable field.searchable
@ -15,24 +15,23 @@ api.array :custom_fields do
api.default_value field.default_value
api.visible field.visible?
values = field.possible_values_options
if values.present?
if field.field_format == 'list'
api.array :possible_values do
values.each do |label, value|
field.possible_values.each do |v|
api.possible_value do
api.value value || label
api.value v
end
end
end
end
if field.is_a?(IssueCustomField)
api.array :trackers do
api.trackers do
field.trackers.each do |tracker|
api.tracker :id => tracker.id, :name => tracker.name
end
end
api.array :roles do
api.roles do
field.roles.each do |role|
api.role :id => role.id, :name => role.name
end

View File

@ -1,11 +1,3 @@
<div class="contextual">
<%= link_to l(:label_custom_field_new), new_custom_field_path, :class => 'icon icon-add' %>
</div>
<%= title l(:label_custom_field_plural) %>
<% if @custom_fields_by_type.present? %>
<%= render_custom_fields_tabs(@custom_fields_by_type.keys) %>
<% else %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% end %>
<%= render_tabs custom_fields_tabs %>

View File

@ -12,8 +12,7 @@ $('#custom_field_field_format').change(function(){
$.ajax({
url: '<%= new_custom_field_path(:format => 'js') %>',
type: 'get',
data: $('#custom_field_form').serialize(),
complete: toggleDisabledInit
data: $('#custom_field_form').serialize()
});
});
<% end %>

View File

@ -1,15 +0,0 @@
<%= title [l(:label_custom_field_plural), custom_fields_path],
l(:label_custom_field_new) %>
<% selected = 0 %>
<%= form_tag new_custom_field_path, :method => 'get' do %>
<div class="box">
<p><%= l(:label_custom_field_select_type) %>:</p>
<p>
<% custom_field_type_options.each do |name, type| %>
<label style="display:block;"><%= radio_button_tag 'type', type, 1==selected+=1 %> <%= name %></label>
<% end %>
</p>
</div>
<p><%= submit_tag l(:label_next).html_safe + " &#187;".html_safe, :name => nil %></p>
<% end %>

View File

@ -20,7 +20,7 @@
:url => { :action => 'edit_membership', :id => @group, :membership_id => membership },
:html => { :id => "member-#{membership.id}-roles-form", :style => 'display:none;'}) do %>
<p><% roles.each do |role| %>
<label><%= check_box_tag 'membership[role_ids][]', role.id, membership.roles.include?(role), :id => nil %> <%=h role %></label><br />
<label><%= check_box_tag 'membership[role_ids][]', role.id, membership.roles.include?(role) %> <%=h role %></label><br />
<% end %></p>
<p><%= submit_tag l(:button_change) %>
<%= link_to_function(
@ -56,7 +56,7 @@
<%= select_tag 'membership[project_id]', options_for_membership_project_select(@group, projects) %>
<p><%= l(:label_role_plural) %>:
<% roles.each do |role| %>
<label><%= check_box_tag 'membership[role_ids][]', role.id, false, :id => nil %> <%=h role %></label>
<label><%= check_box_tag 'membership[role_ids][]', role.id %> <%=h role %></label>
<% end %></p>
<p><%= submit_tag l(:button_add) %></p>
<% end %>

View File

@ -8,7 +8,7 @@
<tbody>
<% @group.users.sort.each do |user| %>
<tr id="user-<%= user.id %>" class="<%= cycle 'odd', 'even' %>">
<td class="name"><%= link_to_user user %></td>
<td class="user"><%= link_to_user user %></td>
<td class="buttons">
<%= delete_link group_user_path(@group, :user_id => user), :remote => true %>
</td>

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