Compare commits
141 Commits
Author | SHA1 | Date |
---|---|---|
Jean-Philippe Lang | 071bc7bc46 | |
Jean-Philippe Lang | 2c44829509 | |
Jean-Philippe Lang | d259dd2dd5 | |
Jean-Philippe Lang | ddc016d81d | |
Jean-Philippe Lang | e7c82e3934 | |
Jean-Philippe Lang | e503f41f6f | |
Jean-Philippe Lang | cd67243de5 | |
Toshi MARUYAMA | b0c3b7a574 | |
Toshi MARUYAMA | 9a67caf248 | |
Toshi MARUYAMA | 67ee8653bd | |
Jean-Philippe Lang | 36978279c3 | |
Toshi MARUYAMA | d155392b3c | |
Jean-Philippe Lang | dc98cec17f | |
Jean-Philippe Lang | e1e006f09e | |
Jean-Philippe Lang | e8757fec2b | |
Jean-Philippe Lang | 591922c365 | |
Jean-Philippe Lang | 5eeca35317 | |
Jean-Philippe Lang | 2de51892ee | |
Toshi MARUYAMA | 3fe5e3bb6f | |
Toshi MARUYAMA | bbf3ffe0aa | |
Jean-Philippe Lang | b724eb4fec | |
Jean-Philippe Lang | 493119e795 | |
Toshi MARUYAMA | f1b6b4ef33 | |
Toshi MARUYAMA | ce002ee3df | |
Toshi MARUYAMA | eb7862445c | |
Toshi MARUYAMA | b31b5328e4 | |
Jean-Philippe Lang | 8efcf60319 | |
Jean-Philippe Lang | e07bc81c5e | |
Jean-Philippe Lang | 5c6349a7ca | |
Jean-Philippe Lang | 37fbdb1457 | |
Jean-Philippe Lang | a13acb7851 | |
Jean-Philippe Lang | 01887f1e67 | |
Jean-Philippe Lang | 3d9f274140 | |
Jean-Philippe Lang | 7375dff6d6 | |
Jean-Philippe Lang | 9874a61ec3 | |
Toshi MARUYAMA | b5998b59ab | |
Toshi MARUYAMA | c590cded4b | |
Toshi MARUYAMA | 5b8af329e2 | |
Toshi MARUYAMA | 0badc79162 | |
Toshi MARUYAMA | 5e57cbfdc4 | |
Toshi MARUYAMA | 489527ada7 | |
Toshi MARUYAMA | 6ce685a63c | |
Toshi MARUYAMA | 8045385990 | |
Toshi MARUYAMA | 3a4c46089f | |
Toshi MARUYAMA | 53d45273a9 | |
Jean-Philippe Lang | cc23ab9652 | |
Jean-Philippe Lang | ea33a66c6b | |
Jean-Philippe Lang | a8c27df9fe | |
Jean-Philippe Lang | af632568e3 | |
Jean-Philippe Lang | 2c408ca6a5 | |
Jean-Philippe Lang | 2a6dadf787 | |
Toshi MARUYAMA | 958f1accec | |
Toshi MARUYAMA | 77a2cbfa6a | |
Toshi MARUYAMA | 73b4b614ee | |
Jean-Philippe Lang | 1a71316b37 | |
Jean-Philippe Lang | 5fa087a792 | |
Jean-Philippe Lang | 425a0fe988 | |
Jean-Philippe Lang | 606762dcad | |
Jean-Philippe Lang | 07dc1e34ff | |
Jean-Philippe Lang | 1afe67bf74 | |
Jean-Philippe Lang | f9f4591cff | |
Jean-Philippe Lang | aa4fc1b58a | |
Toshi MARUYAMA | 8c17237638 | |
Toshi MARUYAMA | 06ab582e5d | |
Toshi MARUYAMA | af66c42d9e | |
Toshi MARUYAMA | 655c50849d | |
Toshi MARUYAMA | bd5b7428d8 | |
Toshi MARUYAMA | 4ed30a4495 | |
Jean-Philippe Lang | ca39b05420 | |
Etienne Massip | e189641e8c | |
Toshi MARUYAMA | 102dd4c11b | |
Toshi MARUYAMA | c1d853d957 | |
Toshi MARUYAMA | 225f99377a | |
Toshi MARUYAMA | d8cf4c57d3 | |
Toshi MARUYAMA | 4b5fa08f66 | |
Toshi MARUYAMA | 4380c0af73 | |
Jean-Philippe Lang | 61a32a5002 | |
Jean-Philippe Lang | 5745a2a2e3 | |
Jean-Philippe Lang | f98f9b9ae1 | |
Toshi MARUYAMA | cfec2018e3 | |
Toshi MARUYAMA | 0083420829 | |
Toshi MARUYAMA | 338d7ea91d | |
Toshi MARUYAMA | 73fb7e3427 | |
Toshi MARUYAMA | edb7f2d2c5 | |
Toshi MARUYAMA | 594d9e9da2 | |
Toshi MARUYAMA | 7ebc62387a | |
Toshi MARUYAMA | 3269c42cdc | |
Toshi MARUYAMA | 9436318987 | |
Jean-Philippe Lang | 94ecabbaf9 | |
Toshi MARUYAMA | 063c9a2a83 | |
Toshi MARUYAMA | 376e8d4aa3 | |
Toshi MARUYAMA | 20114bd8e0 | |
Jean-Philippe Lang | cfdd85173f | |
Toshi MARUYAMA | 5de8e9f04c | |
Toshi MARUYAMA | 79db8fd3e0 | |
Toshi MARUYAMA | 98b7900c5c | |
Jean-Philippe Lang | 23c28c1ef7 | |
Jean-Philippe Lang | 974863e8f4 | |
Jean-Philippe Lang | 58af20746b | |
Toshi MARUYAMA | efcd602444 | |
Jean-Philippe Lang | 20cd146e93 | |
Jean-Philippe Lang | 8a97dfdeab | |
Jean-Philippe Lang | b0b7f4d7d6 | |
Jean-Philippe Lang | 53680edb2d | |
Jean-Philippe Lang | ddf0307718 | |
Jean-Philippe Lang | 0ab90145fe | |
Jean-Philippe Lang | f4def66c58 | |
Jean-Philippe Lang | 4413e0e52e | |
Jean-Philippe Lang | b2e1080007 | |
Jean-Philippe Lang | 8245eaa9f3 | |
Jean-Philippe Lang | 83430dacd9 | |
Jean-Philippe Lang | 998a29cbaf | |
Jean-Philippe Lang | 511099e9ca | |
Jean-Philippe Lang | a18db94c06 | |
Toshi MARUYAMA | fe3a4cdbd1 | |
Toshi MARUYAMA | 67bb69d68e | |
Toshi MARUYAMA | 639b6f5c85 | |
Toshi MARUYAMA | b783bbf3bb | |
Toshi MARUYAMA | a4b6928a26 | |
Toshi MARUYAMA | 0e92038047 | |
Toshi MARUYAMA | 35b17d3bdc | |
Toshi MARUYAMA | 8672114648 | |
Toshi MARUYAMA | 699fa9ac3f | |
Toshi MARUYAMA | a5a1bd5a35 | |
Toshi MARUYAMA | 4100d3beeb | |
Jean-Philippe Lang | 58ebb87ae6 | |
Jean-Philippe Lang | 33ef9fbe29 | |
Jean-Philippe Lang | b0fa5e7305 | |
Jean-Philippe Lang | 5bb2f5e211 | |
Toshi MARUYAMA | 2293a5d3f4 | |
Toshi MARUYAMA | d14cd42a78 | |
Toshi MARUYAMA | 2a53538616 | |
Toshi MARUYAMA | d1f63717dd | |
Toshi MARUYAMA | d22b085d1d | |
Toshi MARUYAMA | 15751a6931 | |
Toshi MARUYAMA | 052cf73dfd | |
Jean-Philippe Lang | a4bee12e5a | |
Jean-Philippe Lang | 92507382b4 | |
Jean-Philippe Lang | e6d63a4e0d | |
Jean-Philippe Lang | 346085c5fc | |
Jean-Philippe Lang | dcfc9170e6 |
|
@ -1,7 +1,5 @@
|
|||
/.project
|
||||
/.loadpath
|
||||
/.powrc
|
||||
/.rvmrc
|
||||
/config/additional_environment.rb
|
||||
/config/configuration.yml
|
||||
/config/database.yml
|
||||
|
@ -17,14 +15,8 @@
|
|||
/lib/redmine/scm/adapters/mercurial/redminehelper.pyo
|
||||
/log/*.log*
|
||||
/log/mongrel_debug
|
||||
/plugins/*
|
||||
!/plugins/README
|
||||
/public/dispatch.*
|
||||
/public/plugin_assets
|
||||
/public/themes/*
|
||||
!/public/themes/alternate
|
||||
!/public/themes/classic
|
||||
!/public/themes/README
|
||||
/tmp/*
|
||||
/tmp/cache/*
|
||||
/tmp/pdf/*
|
||||
|
|
|
@ -2,8 +2,6 @@ syntax: glob
|
|||
|
||||
.project
|
||||
.loadpath
|
||||
.powrc
|
||||
.rvmrc
|
||||
config/additional_environment.rb
|
||||
config/configuration.yml
|
||||
config/database.yml
|
||||
|
|
38
.travis.yml
38
.travis.yml
|
@ -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
|
|
@ -1,8 +0,0 @@
|
|||
|
||||
**Do not send a pull requst to this github repository**.
|
||||
|
||||
For more detail, please see [official website] wiki [Contribute].
|
||||
|
||||
[official website]: http://www.redmine.org
|
||||
[Contribute]: http://www.redmine.org/projects/redmine/wiki/Contribute
|
||||
|
31
Gemfile
31
Gemfile
|
@ -1,13 +1,11 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gem "rails", "3.2.18"
|
||||
gem "rake", "~> 10.1.1"
|
||||
gem "rails", "3.2.13"
|
||||
gem "jquery-rails", "~> 2.0.2"
|
||||
gem "coderay", "~> 1.1.0"
|
||||
gem "i18n", "~> 0.6.0"
|
||||
gem "coderay", "~> 1.0.9"
|
||||
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
|
||||
|
@ -16,30 +14,24 @@ end
|
|||
|
||||
# Optional gem for OpenID authentication
|
||||
group :openid do
|
||||
gem "ruby-openid", "~> 2.3.0", :require => "openid"
|
||||
gem "ruby-openid", "~> 2.2.3", :require => "openid"
|
||||
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
|
||||
# jruby-openssl is bundled with JRuby 1.7.0
|
||||
gem "jruby-openssl" if Object.const_defined?(:JRUBY_VERSION) && JRUBY_VERSION < '1.7.0'
|
||||
gem "activerecord-jdbc-adapter", "~> 1.3.2"
|
||||
gem "activerecord-jdbc-adapter", "1.2.5"
|
||||
end
|
||||
|
||||
# Include database gems for the adapters found in the database
|
||||
|
@ -86,11 +78,9 @@ end
|
|||
|
||||
group :test do
|
||||
gem "shoulda", "~> 3.3.2"
|
||||
gem "mocha", "~> 1.0.0", :require => 'mocha/api'
|
||||
if RUBY_VERSION >= '1.9.3'
|
||||
gem "capybara", "~> 2.1.0"
|
||||
gem "selenium-webdriver"
|
||||
end
|
||||
gem "mocha", "~> 0.13.3"
|
||||
gem 'capybara', '~> 2.0.0'
|
||||
gem 'nokogiri', '< 1.6.0'
|
||||
end
|
||||
|
||||
local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local")
|
||||
|
@ -102,6 +92,5 @@ end
|
|||
# Load plugins' Gemfiles
|
||||
Dir.glob File.expand_path("../plugins/*/Gemfile", __FILE__) do |file|
|
||||
puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v`
|
||||
#TODO: switch to "eval_gemfile file" when bundler >= 1.2.0 will be required (rails 4)
|
||||
instance_eval File.read(file), file
|
||||
instance_eval File.read(file)
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -20,21 +20,13 @@ class AccountController < ApplicationController
|
|||
include CustomFieldsHelper
|
||||
|
||||
# prevents login action to be filtered by check_if_login_required application scope filter
|
||||
skip_before_filter :check_if_login_required, :check_password_change
|
||||
|
||||
# Overrides ApplicationController#verify_authenticity_token to disable
|
||||
# token verification on openid callbacks
|
||||
def verify_authenticity_token
|
||||
unless using_open_id?
|
||||
super
|
||||
end
|
||||
end
|
||||
skip_before_filter :check_if_login_required
|
||||
|
||||
# Login request and validation
|
||||
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
|
||||
|
@ -83,15 +75,11 @@ class AccountController < ApplicationController
|
|||
else
|
||||
if request.post?
|
||||
user = User.find_by_mail(params[:mail].to_s)
|
||||
# user not found
|
||||
unless user
|
||||
# user not found or not active
|
||||
unless user && user.active?
|
||||
flash.now[:error] = l(:notice_account_unknown_email)
|
||||
return
|
||||
end
|
||||
unless user.active?
|
||||
handle_inactive_user(user, lost_password_path)
|
||||
return
|
||||
end
|
||||
# user cannot change its password
|
||||
unless user.change_password_allowed?
|
||||
flash.now[:error] = l(:notice_can_t_change_password)
|
||||
|
@ -164,19 +152,6 @@ class AccountController < ApplicationController
|
|||
redirect_to signin_path
|
||||
end
|
||||
|
||||
# Sends a new account activation email
|
||||
def activation_email
|
||||
if session[:registered_user_id] && Setting.self_registration == '1'
|
||||
user_id = session.delete(:registered_user_id).to_i
|
||||
user = User.find_by_id(user_id)
|
||||
if user && user.registered?
|
||||
register_by_email_activation(user)
|
||||
return
|
||||
end
|
||||
end
|
||||
redirect_to(home_url)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authenticate_user
|
||||
|
@ -188,7 +163,7 @@ class AccountController < ApplicationController
|
|||
end
|
||||
|
||||
def password_authentication
|
||||
user = User.try_to_login(params[:username], params[:password], false)
|
||||
user = User.try_to_login(params[:username], params[:password])
|
||||
|
||||
if user.nil?
|
||||
invalid_credentials
|
||||
|
@ -196,31 +171,27 @@ class AccountController < ApplicationController
|
|||
onthefly_creation_failed(user, {:login => user.login, :auth_source_id => user.auth_source_id })
|
||||
else
|
||||
# Valid user
|
||||
if user.active?
|
||||
successful_authentication(user)
|
||||
else
|
||||
handle_inactive_user(user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def open_id_authenticate(openid_url)
|
||||
back_url = signin_url(:autologin => params[:autologin])
|
||||
authenticate_with_open_id(
|
||||
openid_url, :required => [:nickname, :fullname, :email],
|
||||
:return_to => back_url, :method => :post
|
||||
) do |result, identity_url, registration|
|
||||
|
||||
authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => back_url, :method => :post) do |result, identity_url, registration|
|
||||
if result.successful?
|
||||
user = User.find_or_initialize_by_identity_url(identity_url)
|
||||
if user.new_record?
|
||||
# Self-registration off
|
||||
(redirect_to(home_url); return) unless Setting.self_registration?
|
||||
|
||||
# Create on the fly
|
||||
user.login = registration['nickname'] unless registration['nickname'].nil?
|
||||
user.mail = registration['email'] unless registration['email'].nil?
|
||||
user.firstname, user.lastname = registration['fullname'].split(' ') unless registration['fullname'].nil?
|
||||
user.random_password
|
||||
user.register
|
||||
|
||||
case Setting.self_registration
|
||||
when '1'
|
||||
register_by_email_activation(user) do
|
||||
|
@ -240,7 +211,7 @@ class AccountController < ApplicationController
|
|||
if user.active?
|
||||
successful_authentication(user)
|
||||
else
|
||||
handle_inactive_user(user)
|
||||
account_pending
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -290,7 +261,7 @@ class AccountController < ApplicationController
|
|||
token = Token.new(:user => user, :action => "register")
|
||||
if user.save and token.save
|
||||
Mailer.register(token).deliver
|
||||
flash[:notice] = l(:notice_account_register_done, :email => user.mail)
|
||||
flash[:notice] = l(:notice_account_register_done)
|
||||
redirect_to signin_path
|
||||
else
|
||||
yield if block_given?
|
||||
|
@ -320,32 +291,14 @@ class AccountController < ApplicationController
|
|||
if user.save
|
||||
# Sends an email to the administrators
|
||||
Mailer.account_activation_request(user).deliver
|
||||
account_pending(user)
|
||||
account_pending
|
||||
else
|
||||
yield if block_given?
|
||||
end
|
||||
end
|
||||
|
||||
def handle_inactive_user(user, redirect_path=signin_path)
|
||||
if user.registered?
|
||||
account_pending(user, redirect_path)
|
||||
else
|
||||
account_locked(user, redirect_path)
|
||||
end
|
||||
end
|
||||
|
||||
def account_pending(user, redirect_path=signin_path)
|
||||
if Setting.self_registration == '1'
|
||||
flash[:error] = l(:notice_account_not_activated_yet, :url => activation_email_path)
|
||||
session[:registered_user_id] = user.id
|
||||
else
|
||||
flash[:error] = l(:notice_account_pending)
|
||||
end
|
||||
redirect_to redirect_path
|
||||
end
|
||||
|
||||
def account_locked(user, redirect_path=signin_path)
|
||||
flash[:error] = l(:notice_account_locked)
|
||||
redirect_to redirect_path
|
||||
def account_pending
|
||||
flash[:notice] = l(:notice_account_pending)
|
||||
redirect_to signin_path
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -65,7 +65,7 @@ class AdminController < ApplicationController
|
|||
@test = Mailer.test_email(User.current).deliver
|
||||
flash[:notice] = l(:notice_email_sent, User.current.mail)
|
||||
rescue Exception => e
|
||||
flash[:error] = l(:notice_email_error, Redmine::CodesetUtil.replace_invalid_utf8(e.message))
|
||||
flash[:error] = l(:notice_email_error, e.message)
|
||||
end
|
||||
ActionMailer::Base.raise_delivery_errors = raise_delivery_errors
|
||||
redirect_to settings_path(:tab => 'notifications')
|
||||
|
@ -77,8 +77,7 @@ class AdminController < ApplicationController
|
|||
[:text_default_administrator_account_changed, User.default_admin_account_changed?],
|
||||
[:text_file_repository_writable, File.writable?(Attachment.storage_path)],
|
||||
[:text_plugin_assets_writable, File.writable?(Redmine::Plugin.public_directory)],
|
||||
[:text_rmagick_available, Object.const_defined?(:Magick)],
|
||||
[:text_convert_available, Redmine::Thumbnail.convert_available?]
|
||||
[:text_rmagick_available, Object.const_defined?(:Magick)]
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -33,24 +33,14 @@ class ApplicationController < ActionController::Base
|
|||
layout 'base'
|
||||
|
||||
protect_from_forgery
|
||||
|
||||
def verify_authenticity_token
|
||||
unless api_request?
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def handle_unverified_request
|
||||
unless api_request?
|
||||
super
|
||||
cookies.delete(autologin_cookie_name)
|
||||
self.logged_user = nil
|
||||
render_error :status => 422, :message => "Invalid form authenticity token."
|
||||
end
|
||||
end
|
||||
|
||||
before_filter :session_expiration, :user_setup, :check_if_login_required, :check_password_change, :set_localization
|
||||
before_filter :session_expiration, :user_setup, :check_if_login_required, :set_localization
|
||||
|
||||
rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
|
||||
rescue_from ::Unauthorized, :with => :deny_access
|
||||
rescue_from ::ActionView::MissingTemplate, :with => :missing_template
|
||||
|
||||
|
@ -88,9 +78,6 @@ class ApplicationController < ActionController::Base
|
|||
session[:user_id] = user.id
|
||||
session[:ctime] = Time.now.utc.to_i
|
||||
session[:atime] = Time.now.utc.to_i
|
||||
if user.must_change_password?
|
||||
session[:pwd] = '1'
|
||||
end
|
||||
end
|
||||
|
||||
def user_setup
|
||||
|
@ -120,15 +107,11 @@ 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)
|
||||
end
|
||||
if user && user.must_change_password?
|
||||
render_error :message => 'You must change your password', :status => 403
|
||||
return
|
||||
end
|
||||
end
|
||||
# Switch user if requested by an admin user
|
||||
if user && user.admin? && (username = api_switch_user_from_request)
|
||||
|
@ -187,22 +170,12 @@ class ApplicationController < ActionController::Base
|
|||
require_login if Setting.login_required?
|
||||
end
|
||||
|
||||
def check_password_change
|
||||
if session[:pwd]
|
||||
if User.current.must_change_password?
|
||||
redirect_to my_password_path
|
||||
else
|
||||
session.delete(:pwd)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def set_localization
|
||||
lang = nil
|
||||
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
|
||||
|
@ -222,13 +195,7 @@ class ApplicationController < ActionController::Base
|
|||
url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
|
||||
end
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
if request.xhr?
|
||||
head :unauthorized
|
||||
else
|
||||
redirect_to :controller => "account", :action => "login", :back_url => url
|
||||
end
|
||||
}
|
||||
format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
|
||||
format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
|
||||
format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
|
||||
format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
|
||||
|
@ -331,7 +298,7 @@ class ApplicationController < ActionController::Base
|
|||
# Find issues with a single :id param or :ids array param
|
||||
# Raises a Unauthorized exception if one of the issues is not visible
|
||||
def find_issues
|
||||
@issues = Issue.where(:id => (params[:id] || params[:ids])).preload(:project, :status, :tracker, :priority, :author, :assigned_to, :relations_to).to_a
|
||||
@issues = Issue.find_all_by_id(params[:id] || params[:ids])
|
||||
raise ActiveRecord::RecordNotFound if @issues.empty?
|
||||
raise Unauthorized unless @issues.all?(&:visible?)
|
||||
@projects = @issues.collect(&:project).compact.uniq
|
||||
|
@ -374,13 +341,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 +355,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
|
||||
|
@ -463,6 +427,13 @@ class ApplicationController < ActionController::Base
|
|||
request.xhr? ? false : 'base'
|
||||
end
|
||||
|
||||
def invalid_authenticity_token
|
||||
if api_request?
|
||||
logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
|
||||
end
|
||||
render_error "Invalid form authenticity token."
|
||||
end
|
||||
|
||||
def render_feed(items, options={})
|
||||
@items = items || []
|
||||
@items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
|
||||
|
@ -558,7 +529,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?
|
||||
|
@ -584,6 +555,21 @@ class ApplicationController < ActionController::Base
|
|||
flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
|
||||
end
|
||||
|
||||
# Sets the `flash` notice or error based the number of issues that did not save
|
||||
#
|
||||
# @param [Array, Issue] issues all of the saved and unsaved Issues
|
||||
# @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved
|
||||
def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids)
|
||||
if unsaved_issue_ids.empty?
|
||||
flash[:notice] = l(:notice_successful_update) unless issues.empty?
|
||||
else
|
||||
flash[:error] = l(:notice_failed_to_save_issues,
|
||||
:count => unsaved_issue_ids.size,
|
||||
:total => issues.size,
|
||||
:ids => '#' + unsaved_issue_ids.join(', #'))
|
||||
end
|
||||
end
|
||||
|
||||
# Rescues an invalid query statement. Just in case...
|
||||
def query_statement_invalid(exception)
|
||||
logger.error "Query::StatementInvalid: #{exception.message}" if logger
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -25,7 +25,7 @@ class BoardsController < ApplicationController
|
|||
helper :watchers
|
||||
|
||||
def index
|
||||
@boards = @project.boards.includes(:project, :last_message => :author).all
|
||||
@boards = @project.boards.includes(:last_message => :author).all
|
||||
# show the board if there is only one
|
||||
if @boards.size == 1
|
||||
@board = @boards.first
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -19,15 +19,17 @@ class ContextMenusController < ApplicationController
|
|||
helper :watchers
|
||||
helper :issues
|
||||
|
||||
before_filter :find_issues, :only => :issues
|
||||
|
||||
def issues
|
||||
@issues = Issue.visible.all(:conditions => {:id => params[:ids]}, :include => :project)
|
||||
(render_404; return) unless @issues.present?
|
||||
if (@issues.size == 1)
|
||||
@issue = @issues.first
|
||||
end
|
||||
@issue_ids = @issues.map(&:id).sort
|
||||
|
||||
@allowed_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
|
||||
@projects = @issues.collect(&:project).compact.uniq
|
||||
@project = @projects.first if @projects.size == 1
|
||||
|
||||
@can = {:edit => User.current.allowed_to?(:edit_issues, @projects),
|
||||
:log_time => (@project && User.current.allowed_to?(:log_time, @project)),
|
||||
|
@ -55,10 +57,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
|
||||
|
@ -69,7 +73,8 @@ class ContextMenusController < ApplicationController
|
|||
end
|
||||
|
||||
def time_entries
|
||||
@time_entries = TimeEntry.where(:id => params[:ids]).preload(:project).to_a
|
||||
@time_entries = TimeEntry.all(
|
||||
:conditions => {:id => params[:ids]}, :include => :project)
|
||||
(render_404; return) unless @time_entries.present?
|
||||
|
||||
@projects = @time_entries.collect(&:project).compact.uniq
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -21,22 +21,13 @@ class CustomFieldsController < ApplicationController
|
|||
before_filter :require_admin
|
||||
before_filter :build_new_custom_field, :only => [:new, :create]
|
||||
before_filter :find_custom_field, :only => [:edit, :update, :destroy]
|
||||
accept_api_auth :index
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
@custom_fields_by_type = CustomField.all.group_by {|f| f.class.name }
|
||||
}
|
||||
format.api {
|
||||
@custom_fields = CustomField.all
|
||||
}
|
||||
end
|
||||
@tab = params[:tab] || 'IssueCustomField'
|
||||
end
|
||||
|
||||
def new
|
||||
@custom_field.field_format = 'string' if @custom_field.field_format.blank?
|
||||
@custom_field.default_value = nil
|
||||
end
|
||||
|
||||
def create
|
||||
|
@ -76,7 +67,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
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -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') }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -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
|
||||
|
@ -40,7 +40,7 @@ class IssueStatusesController < ApplicationController
|
|||
|
||||
def create
|
||||
@issue_status = IssueStatus.new(params[:issue_status])
|
||||
if @issue_status.save
|
||||
if request.post? && @issue_status.save
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to issue_statuses_path
|
||||
else
|
||||
|
@ -54,7 +54,7 @@ class IssueStatusesController < ApplicationController
|
|||
|
||||
def update
|
||||
@issue_status = IssueStatus.find(params[:id])
|
||||
if @issue_status.update_attributes(params[:issue_status])
|
||||
if request.put? && @issue_status.update_attributes(params[:issue_status])
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to issue_statuses_path
|
||||
else
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -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
|
||||
|
@ -107,9 +103,6 @@ class IssuesController < ApplicationController
|
|||
@journals = @issue.journals.includes(:user, :details).reorder("#{Journal.table_name}.id ASC").all
|
||||
@journals.each_with_index {|j,i| j.indice = i+1}
|
||||
@journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
|
||||
Journal.preload_journals_details_custom_fields(@journals)
|
||||
# TODO: use #select! when ruby1.8 support is dropped
|
||||
@journals.reject! {|journal| !journal.notes? && journal.visible_details.empty?}
|
||||
@journals.reverse! if User.current.wants_comments_in_reverse_order?
|
||||
|
||||
@changesets = @issue.changesets.visible.all
|
||||
|
@ -120,8 +113,6 @@ class IssuesController < ApplicationController
|
|||
@edit_allowed = User.current.allowed_to?(:edit_issues, @project)
|
||||
@priorities = IssuePriority.active
|
||||
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
|
||||
@relation = IssueRelation.new
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
retrieve_previous_and_next_issue_ids
|
||||
|
@ -185,7 +176,7 @@ class IssuesController < ApplicationController
|
|||
@issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
|
||||
saved = false
|
||||
begin
|
||||
saved = save_issue_with_child_records
|
||||
saved = @issue.save_issue_with_child_records(params, @time_entry)
|
||||
rescue ActiveRecord::StaleObjectError
|
||||
@conflict = true
|
||||
if params[:last_journal_id]
|
||||
|
@ -237,7 +228,7 @@ class IssuesController < ApplicationController
|
|||
else
|
||||
@available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
|
||||
end
|
||||
@custom_fields = target_projects.map{|p|p.all_issue_custom_fields.visible}.reduce(:&)
|
||||
@custom_fields = target_projects.map{|p|p.all_issue_custom_fields}.reduce(:&)
|
||||
@assignables = target_projects.map(&:assignable_users).reduce(:&)
|
||||
@trackers = target_projects.map(&:trackers).reduce(:&)
|
||||
@versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
|
||||
|
@ -248,9 +239,7 @@ class IssuesController < ApplicationController
|
|||
end
|
||||
|
||||
@safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
|
||||
|
||||
@issue_params = params[:issue] || {}
|
||||
@issue_params[:custom_field_values] ||= {}
|
||||
render :layout => false if request.xhr?
|
||||
end
|
||||
|
||||
def bulk_update
|
||||
|
@ -258,8 +247,8 @@ class IssuesController < ApplicationController
|
|||
@copy = params[:copy].present?
|
||||
attributes = parse_params_for_bulk_issue_attributes(params)
|
||||
|
||||
unsaved_issues = []
|
||||
saved_issues = []
|
||||
unsaved_issue_ids = []
|
||||
moved_issues = []
|
||||
|
||||
if @copy && params[:copy_subtasks].present?
|
||||
# Descendant issues will be copied with the parent task
|
||||
|
@ -267,62 +256,52 @@ class IssuesController < ApplicationController
|
|||
@issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
|
||||
end
|
||||
|
||||
@issues.each do |orig_issue|
|
||||
orig_issue.reload
|
||||
@issues.each do |issue|
|
||||
issue.reload
|
||||
if @copy
|
||||
issue = orig_issue.copy({},
|
||||
issue = issue.copy({},
|
||||
:attachments => params[:copy_attachments].present?,
|
||||
:subtasks => params[:copy_subtasks].present?
|
||||
)
|
||||
else
|
||||
issue = orig_issue
|
||||
end
|
||||
journal = issue.init_journal(User.current, params[:notes])
|
||||
issue.safe_attributes = attributes
|
||||
call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
|
||||
if issue.save
|
||||
saved_issues << issue
|
||||
moved_issues << issue
|
||||
else
|
||||
unsaved_issues << orig_issue
|
||||
# Keep unsaved issue ids to display them in flash error
|
||||
unsaved_issue_ids << issue.id
|
||||
end
|
||||
end
|
||||
set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
|
||||
|
||||
if unsaved_issues.empty?
|
||||
flash[:notice] = l(:notice_successful_update) unless saved_issues.empty?
|
||||
if params[:follow]
|
||||
if @issues.size == 1 && saved_issues.size == 1
|
||||
redirect_to issue_path(saved_issues.first)
|
||||
elsif saved_issues.map(&:project).uniq.size == 1
|
||||
redirect_to project_issues_path(saved_issues.map(&:project).first)
|
||||
if @issues.size == 1 && moved_issues.size == 1
|
||||
redirect_to issue_path(moved_issues.first)
|
||||
elsif moved_issues.map(&:project).uniq.size == 1
|
||||
redirect_to project_issues_path(moved_issues.map(&:project).first)
|
||||
end
|
||||
else
|
||||
redirect_back_or_default _project_issues_path(@project)
|
||||
end
|
||||
else
|
||||
@saved_issues = @issues
|
||||
@unsaved_issues = unsaved_issues
|
||||
@issues = Issue.visible.where(:id => @unsaved_issues.map(&:id)).all
|
||||
bulk_edit
|
||||
render :action => 'bulk_edit'
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@hours = TimeEntry.where(:issue_id => @issues.map(&:id)).sum(:hours).to_f
|
||||
@hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
|
||||
if @hours > 0
|
||||
case params[:todo]
|
||||
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
|
||||
|
@ -431,11 +410,8 @@ class IssuesController < ApplicationController
|
|||
@issue.safe_attributes = params[:issue]
|
||||
|
||||
@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
|
||||
@allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
|
||||
@available_watchers = (@issue.project.users.sort + @issue.watcher_users).uniq
|
||||
end
|
||||
|
||||
def check_for_default_issue_status
|
||||
|
@ -460,26 +436,4 @@ class IssuesController < ApplicationController
|
|||
end
|
||||
attributes
|
||||
end
|
||||
|
||||
# Saves @issue and a time_entry from the parameters
|
||||
def save_issue_with_child_records
|
||||
Issue.transaction do
|
||||
if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, @issue.project)
|
||||
time_entry = @time_entry || TimeEntry.new
|
||||
time_entry.project = @issue.project
|
||||
time_entry.issue = @issue
|
||||
time_entry.user = User.current
|
||||
time_entry.spent_on = User.current.today
|
||||
time_entry.attributes = params[:time_entry]
|
||||
@issue.time_entries << time_entry
|
||||
end
|
||||
|
||||
call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
|
||||
if @issue.save
|
||||
call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
|
||||
else
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -35,7 +35,7 @@ class MessagesController < ApplicationController
|
|||
page = params[:page]
|
||||
# Find the page of the requested reply
|
||||
if params[:r] && page.nil?
|
||||
offset = @topic.children.where("#{Message.table_name}.id < ?", params[:r].to_i).count
|
||||
offset = @topic.children.count(:conditions => ["#{Message.table_name}.id < ?", params[:r].to_i])
|
||||
page = 1 + offset / REPLIES_PER_PAGE
|
||||
end
|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -17,8 +17,6 @@
|
|||
|
||||
class MyController < ApplicationController
|
||||
before_filter :require_login
|
||||
# let user change user's password when user has to
|
||||
skip_before_filter :check_password_change, :only => :password
|
||||
|
||||
helper :issues
|
||||
helper :users
|
||||
|
@ -57,6 +55,7 @@ class MyController < ApplicationController
|
|||
@user.pref.attributes = params[:pref]
|
||||
if @user.save
|
||||
@user.pref.save
|
||||
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
|
||||
set_language_if_valid @user.language
|
||||
flash[:notice] = l(:notice_account_updated)
|
||||
redirect_to my_account_path
|
||||
|
@ -92,17 +91,14 @@ class MyController < ApplicationController
|
|||
return
|
||||
end
|
||||
if request.post?
|
||||
if !@user.check_password?(params[:password])
|
||||
flash.now[:error] = l(:notice_account_wrong_password)
|
||||
elsif params[:password] == params[:new_password]
|
||||
flash.now[:error] = l(:notice_new_password_must_be_different)
|
||||
else
|
||||
if @user.check_password?(params[:password])
|
||||
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
|
||||
@user.must_change_passwd = false
|
||||
if @user.save
|
||||
flash[:notice] = l(:notice_account_password_updated)
|
||||
redirect_to my_account_path
|
||||
end
|
||||
else
|
||||
flash[:error] = l(:notice_account_wrong_password)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -139,7 +135,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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -82,7 +82,7 @@ class ProjectsController < ApplicationController
|
|||
|
||||
if validate_parent_id && @project.save
|
||||
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
|
||||
# Add current user as a project member if current user is not admin
|
||||
# Add current user as a project member if he is not admin
|
||||
unless User.current.admin?
|
||||
r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
|
||||
m = Member.new(:user => User.current, :roles => [r])
|
||||
|
@ -151,11 +151,11 @@ 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
|
||||
@total_hours = TimeEntry.visible.sum(:hours, :include => :project, :conditions => cond).to_f
|
||||
end
|
||||
|
||||
@key = User.current.rss_key
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -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
|
||||
|
@ -47,7 +45,7 @@ class QueriesController < ApplicationController
|
|||
@query = IssueQuery.new
|
||||
@query.user = User.current
|
||||
@query.project = @project
|
||||
@query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
|
||||
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
|
||||
@query.build_from_params(params)
|
||||
end
|
||||
|
||||
|
@ -55,13 +53,13 @@ class QueriesController < ApplicationController
|
|||
@query = IssueQuery.new(params[:query])
|
||||
@query.user = User.current
|
||||
@query.project = params[:query_is_for_all] ? nil : @project
|
||||
@query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
|
||||
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
|
||||
@query.build_from_params(params)
|
||||
@query.column_names = nil if params[:default_columns]
|
||||
|
||||
if @query.save
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to_issues(:query_id => @query)
|
||||
redirect_to _project_issues_path(@project, :query_id => @query)
|
||||
else
|
||||
render :action => 'new', :layout => !request.xhr?
|
||||
end
|
||||
|
@ -73,13 +71,13 @@ class QueriesController < ApplicationController
|
|||
def update
|
||||
@query.attributes = params[:query]
|
||||
@query.project = nil if params[:query_is_for_all]
|
||||
@query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
|
||||
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
|
||||
@query.build_from_params(params)
|
||||
@query.column_names = nil if params[:default_columns]
|
||||
|
||||
if @query.save
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to_issues(:query_id => @query)
|
||||
redirect_to _project_issues_path(@project, :query_id => @query)
|
||||
else
|
||||
render :action => 'edit'
|
||||
end
|
||||
|
@ -87,7 +85,7 @@ class QueriesController < ApplicationController
|
|||
|
||||
def destroy
|
||||
@query.destroy
|
||||
redirect_to_issues(:set_filter => 1)
|
||||
redirect_to _project_issues_path(@project, :set_filter => 1)
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -105,16 +103,4 @@ private
|
|||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def redirect_to_issues(options)
|
||||
if params[:gantt]
|
||||
if @project
|
||||
redirect_to project_gantt_path(@project, options)
|
||||
else
|
||||
redirect_to issues_gantt_path(options)
|
||||
end
|
||||
else
|
||||
redirect_to _project_issues_path(@project, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -18,7 +18,7 @@
|
|||
require 'SVG/Graph/Bar'
|
||||
require 'SVG/Graph/BarHorizontal'
|
||||
require 'digest/sha1'
|
||||
require 'redmine/scm/adapters'
|
||||
require 'redmine/scm/adapters/abstract_adapter'
|
||||
|
||||
class ChangesetNotFound < Exception; end
|
||||
class InvalidRevisionParam < Exception; end
|
||||
|
@ -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)
|
||||
|
@ -111,7 +111,7 @@ class RepositoriesController < ApplicationController
|
|||
end
|
||||
|
||||
def show
|
||||
@repository.fetch_changesets if @project.active? && Setting.autofetch_changesets? && @path.empty?
|
||||
@repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty?
|
||||
|
||||
@entries = @repository.entries(@path, @rev)
|
||||
@changeset = @repository.find_changeset_by_name(@rev)
|
||||
|
@ -229,8 +229,7 @@ class RepositoriesController < ApplicationController
|
|||
# Adds a related issue to a changeset
|
||||
# POST /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues
|
||||
def add_related_issue
|
||||
issue_id = params[:issue_id].to_s.sub(/^#/,'')
|
||||
@issue = @changeset.find_referenced_issue_by_id(issue_id)
|
||||
@issue = @changeset.find_referenced_issue_by_id(params[:issue_id])
|
||||
if @issue && (!@issue.visible? || @changeset.issues.include?(@issue))
|
||||
@issue = nil
|
||||
end
|
||||
|
@ -353,18 +352,15 @@ class RepositoriesController < ApplicationController
|
|||
@date_to = Date.today
|
||||
@date_from = @date_to << 11
|
||||
@date_from = Date.civil(@date_from.year, @date_from.month, 1)
|
||||
commits_by_day = Changeset.
|
||||
where("repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to).
|
||||
group(:commit_date).
|
||||
count
|
||||
commits_by_day = Changeset.count(
|
||||
:all, :group => :commit_date,
|
||||
:conditions => ["repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
|
||||
commits_by_month = [0] * 12
|
||||
commits_by_day.each {|c| commits_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
|
||||
|
||||
changes_by_day = Change.
|
||||
joins(:changeset).
|
||||
where("#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to).
|
||||
group(:commit_date).
|
||||
count
|
||||
changes_by_day = Change.count(
|
||||
:all, :group => :commit_date, :include => :changeset,
|
||||
:conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
|
||||
changes_by_month = [0] * 12
|
||||
changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
|
||||
|
||||
|
@ -397,10 +393,10 @@ class RepositoriesController < ApplicationController
|
|||
end
|
||||
|
||||
def graph_commits_per_author(repository)
|
||||
commits_by_author = Changeset.where("repository_id = ?", repository.id).group(:committer).count
|
||||
commits_by_author = Changeset.count(:all, :group => :committer, :conditions => ["repository_id = ?", repository.id])
|
||||
commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}
|
||||
|
||||
changes_by_author = Change.joins(:changeset).where("#{Changeset.table_name}.repository_id = ?", repository.id).group(:committer).count
|
||||
changes_by_author = Change.count(:all, :group => :committer, :include => :changeset, :conditions => ["#{Changeset.table_name}.repository_id = ?", repository.id])
|
||||
h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
|
||||
|
||||
fields = commits_by_author.collect {|r| r.first}
|
||||
|
@ -411,11 +407,11 @@ 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(
|
||||
:height => 30 * commits_data.length,
|
||||
:height => 400,
|
||||
:width => 800,
|
||||
:fields => fields,
|
||||
:stack => :side,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -33,7 +33,9 @@ class SettingsController < ApplicationController
|
|||
if request.post? && params[:settings] && params[:settings].is_a?(Hash)
|
||||
settings = (params[:settings] || {}).dup.symbolize_keys
|
||||
settings.each do |name, value|
|
||||
Setting.set_from_params name, value
|
||||
# remove blank values in array settings
|
||||
value.delete_if {|v| v.blank? } if value.is_a?(Array)
|
||||
Setting[name] = value
|
||||
end
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to settings_path(:tab => params[:tab])
|
||||
|
@ -46,9 +48,6 @@ class SettingsController < ApplicationController
|
|||
@guessed_host_and_path = request.host_with_port.dup
|
||||
@guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank?
|
||||
|
||||
@commit_update_keywords = Setting.commit_update_keywords.dup
|
||||
@commit_update_keywords = [{}] unless @commit_update_keywords.is_a?(Array) && @commit_update_keywords.any?
|
||||
|
||||
Redmine::Themes.rescan
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -19,7 +19,11 @@ class SysController < ActionController::Base
|
|||
before_filter :check_enabled
|
||||
|
||||
def projects
|
||||
p = Project.active.has_module(:repository).order("#{Project.table_name}.identifier").preload(:repository).all
|
||||
p = Project.active.has_module(:repository).find(
|
||||
:all,
|
||||
:include => :repository,
|
||||
:order => "#{Project.table_name}.identifier"
|
||||
)
|
||||
# extra_info attribute from repository breaks activeresource client
|
||||
render :xml => p.to_xml(
|
||||
:only => [:id, :identifier, :name, :is_public, :status],
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -46,15 +46,18 @@ class TimelogController < ApplicationController
|
|||
|
||||
sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria)
|
||||
sort_update(@query.sortable_columns)
|
||||
scope = time_entry_scope(:order => sort_clause).
|
||||
includes(:project, :user, :issue).
|
||||
preload(:issue => [:project, :tracker, :status, :assigned_to, :priority])
|
||||
scope = time_entry_scope(:order => sort_clause)
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
# Paginate results
|
||||
@entry_count = scope.count
|
||||
@entry_pages = Paginator.new @entry_count, per_page_option, params['page']
|
||||
@entries = scope.offset(@entry_pages.offset).limit(@entry_pages.per_page).all
|
||||
@entries = scope.all(
|
||||
:include => [:project, :activity, :user, {:issue => :tracker}],
|
||||
:limit => @entry_pages.per_page,
|
||||
:offset => @entry_pages.offset
|
||||
)
|
||||
@total_hours = scope.sum(:hours).to_f
|
||||
|
||||
render :layout => !request.xhr?
|
||||
|
@ -62,15 +65,24 @@ class TimelogController < ApplicationController
|
|||
format.api {
|
||||
@entry_count = scope.count
|
||||
@offset, @limit = api_offset_and_limit
|
||||
@entries = scope.offset(@offset).limit(@limit).preload(:custom_values => :custom_field).all
|
||||
@entries = scope.all(
|
||||
:include => [:project, :activity, :user, {:issue => :tracker}],
|
||||
:limit => @limit,
|
||||
:offset => @offset
|
||||
)
|
||||
}
|
||||
format.atom {
|
||||
entries = scope.limit(Setting.feeds_limit.to_i).reorder("#{TimeEntry.table_name}.created_on DESC").all
|
||||
entries = scope.reorder("#{TimeEntry.table_name}.created_on DESC").all(
|
||||
:include => [:project, :activity, :user, {:issue => :tracker}],
|
||||
:limit => Setting.feeds_limit.to_i
|
||||
)
|
||||
render_feed(entries, :title => l(:label_spent_time))
|
||||
}
|
||||
format.csv {
|
||||
# Export all entries
|
||||
@entries = scope.all
|
||||
@entries = scope.all(
|
||||
:include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}]
|
||||
)
|
||||
send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'timelog.csv')
|
||||
}
|
||||
end
|
||||
|
@ -182,7 +194,6 @@ class TimelogController < ApplicationController
|
|||
time_entry.safe_attributes = attributes
|
||||
call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
|
||||
unless time_entry.save
|
||||
logger.info "time entry could not be updated: #{time_entry.errors.full_messages}" if logger && logger.info
|
||||
# Keep unsaved time_entry ids to display them in flash error
|
||||
unsaved_time_entry_ids << time_entry.id
|
||||
end
|
||||
|
@ -232,7 +243,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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -60,7 +60,7 @@ class UsersController < ApplicationController
|
|||
|
||||
def show
|
||||
# show projects based on current user visibility
|
||||
@memberships = @user.memberships.where(Project.visible_condition(User.current)).all
|
||||
@memberships = @user.memberships.all(:conditions => Project.visible_condition(User.current))
|
||||
|
||||
events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
|
||||
@events_by_day = events.group_by(&:event_date)
|
||||
|
@ -80,7 +80,6 @@ class UsersController < ApplicationController
|
|||
|
||||
def new
|
||||
@user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
|
||||
@user.safe_attributes = params[:user]
|
||||
@auth_sources = AuthSource.all
|
||||
end
|
||||
|
||||
|
@ -90,17 +89,19 @@ 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
|
||||
Mailer.account_information(@user, @user.password).deliver if params[:send_information]
|
||||
@user.pref.attributes = params[:pref]
|
||||
@user.pref.save
|
||||
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
|
||||
|
||||
Mailer.account_information(@user, params[:user][:password]).deliver if params[:send_information]
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user)))
|
||||
if params[:continue]
|
||||
attrs = params[:user].slice(:generate_password)
|
||||
redirect_to new_user_path(:user => attrs)
|
||||
redirect_to new_user_path
|
||||
else
|
||||
redirect_to edit_user_path(@user)
|
||||
end
|
||||
|
@ -138,11 +139,12 @@ class UsersController < ApplicationController
|
|||
|
||||
if @user.save
|
||||
@user.pref.save
|
||||
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
|
||||
|
||||
if was_activated
|
||||
Mailer.account_activated(@user).deliver
|
||||
elsif @user.active? && params[:send_information] && @user.password.present? && @user.auth_source_id.nil?
|
||||
Mailer.account_information(@user, @user.password).deliver
|
||||
elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil?
|
||||
Mailer.account_information(@user, params[:user][:password]).deliver
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -46,11 +46,11 @@ class VersionsController < ApplicationController
|
|||
|
||||
@issues_by_version = {}
|
||||
if @selected_tracker_ids.any? && @versions.any?
|
||||
issues = Issue.visible.
|
||||
includes(:project, :tracker).
|
||||
preload(:status, :priority, :fixed_version).
|
||||
where(:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)).
|
||||
order("#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
|
||||
issues = Issue.visible.all(
|
||||
:include => [:project, :status, :tracker, :priority, :fixed_version],
|
||||
:conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)},
|
||||
:order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id"
|
||||
)
|
||||
@issues_by_version = issues.group_by(&:fixed_version)
|
||||
end
|
||||
@versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -15,6 +15,8 @@
|
|||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
require 'diff'
|
||||
|
||||
# The WikiController follows the Rails REST controller pattern but with
|
||||
# a few differences
|
||||
#
|
||||
|
@ -62,12 +64,7 @@ class WikiController < ApplicationController
|
|||
|
||||
# display a page (in editing mode if it doesn't exist)
|
||||
def show
|
||||
if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project)
|
||||
deny_access
|
||||
return
|
||||
end
|
||||
@content = @page.content_for_version(params[:version])
|
||||
if @content.nil?
|
||||
if @page.new_record?
|
||||
if User.current.allowed_to?(:edit_wiki_pages, @project) && editable? && !api_request?
|
||||
edit
|
||||
render :action => 'edit'
|
||||
|
@ -76,6 +73,11 @@ class WikiController < ApplicationController
|
|||
end
|
||||
return
|
||||
end
|
||||
if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project)
|
||||
deny_access
|
||||
return
|
||||
end
|
||||
@content = @page.content_for_version(params[:version])
|
||||
if User.current.allowed_to?(:export_wiki_pages, @project)
|
||||
if params[:format] == 'pdf'
|
||||
send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf")
|
||||
|
@ -104,19 +106,19 @@ class WikiController < ApplicationController
|
|||
def edit
|
||||
return render_403 unless editable?
|
||||
if @page.new_record?
|
||||
@page.content = WikiContent.new(:page => @page)
|
||||
if params[:parent].present?
|
||||
@page.parent = @page.wiki.find_page(params[:parent].to_s)
|
||||
end
|
||||
end
|
||||
|
||||
@content = @page.content_for_version(params[:version])
|
||||
@content ||= WikiContent.new(:page => @page)
|
||||
@content.text = initial_page_content(@page) if @content.text.blank?
|
||||
# don't keep previous comment
|
||||
@content.comments = nil
|
||||
|
||||
# To prevent StaleObjectError exception when reverting to a previous version
|
||||
@content.version = @page.content.version if @page.content
|
||||
@content.version = @page.content.version
|
||||
|
||||
@text = @content.text
|
||||
if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
|
||||
|
@ -130,9 +132,10 @@ class WikiController < ApplicationController
|
|||
def update
|
||||
return render_403 unless editable?
|
||||
was_new_page = @page.new_record?
|
||||
@page.content = WikiContent.new(:page => @page) if @page.new_record?
|
||||
@page.safe_attributes = params[:wiki_page]
|
||||
|
||||
@content = @page.content || WikiContent.new(:page => @page)
|
||||
@content = @page.content
|
||||
content_params = params[:content]
|
||||
if content_params.nil? && params[:wiki_page].is_a?(Hash)
|
||||
content_params = params[:wiki_page].slice(:text, :comments, :version)
|
||||
|
@ -144,23 +147,20 @@ class WikiController < ApplicationController
|
|||
if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
|
||||
@section = params[:section].to_i
|
||||
@section_hash = params[:section_hash]
|
||||
@content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(@section, @text, @section_hash)
|
||||
@content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(params[:section].to_i, @text, @section_hash)
|
||||
else
|
||||
@content.version = content_params[:version] if content_params[:version]
|
||||
@content.text = @text
|
||||
end
|
||||
@content.author = User.current
|
||||
|
||||
if @page.save_with_content(@content)
|
||||
if @page.save_with_content
|
||||
attachments = Attachment.attach_files(@page, params[:attachments])
|
||||
render_attachment_warning_if_needed(@page)
|
||||
call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
anchor = @section ? "section-#{@section}" : nil
|
||||
redirect_to project_wiki_page_path(@project, @page.title, :anchor => anchor)
|
||||
}
|
||||
format.html { redirect_to project_wiki_page_path(@project, @page.title) }
|
||||
format.api {
|
||||
if was_new_page
|
||||
render :action => 'show', :status => :created, :location => project_wiki_page_path(@project, @page.title)
|
||||
|
@ -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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -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
|
||||
end
|
||||
WorkflowTransition.replace_transitions(@trackers, @roles, transitions)
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to_referer_or workflows_edit_path
|
||||
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
|
||||
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?
|
||||
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
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -72,16 +72,14 @@ 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), :class => issue.css_classes, :title => title
|
||||
s << h(": #{subject}") if subject
|
||||
s = h("#{issue.project} - ") + s if options[:project]
|
||||
s
|
||||
|
@ -118,7 +116,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 +155,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 +181,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 +189,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 +266,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
|
||||
|
@ -380,7 +330,7 @@ module ApplicationHelper
|
|||
end
|
||||
groups = ''
|
||||
collection.sort.each do |element|
|
||||
selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) || element.id.to_s == selected
|
||||
selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
|
||||
(element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
|
||||
end
|
||||
unless groups.empty?
|
||||
|
@ -398,23 +348,11 @@ module ApplicationHelper
|
|||
options
|
||||
end
|
||||
|
||||
def option_tag(name, text, value, selected=nil, options={})
|
||||
content_tag 'option', value, options.merge(:value => value, :selected => (value == selected))
|
||||
end
|
||||
|
||||
# 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
|
||||
|
@ -442,7 +380,7 @@ module ApplicationHelper
|
|||
if @project
|
||||
link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
|
||||
else
|
||||
content_tag('abbr', text, :title => format_time(time))
|
||||
content_tag('acronym', text, :title => format_time(time))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -507,31 +445,12 @@ module ApplicationHelper
|
|||
end
|
||||
end
|
||||
|
||||
# Returns a h2 tag and sets the html title with the given arguments
|
||||
def title(*args)
|
||||
strings = args.map do |arg|
|
||||
if arg.is_a?(Array) && arg.size >= 2
|
||||
link_to(*arg)
|
||||
else
|
||||
h(arg.to_s)
|
||||
end
|
||||
end
|
||||
html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
|
||||
content_tag('h2', strings.join(' » ').html_safe)
|
||||
end
|
||||
|
||||
# Sets the html title
|
||||
# Returns the html title when called without arguments
|
||||
# Current project name and app_title and automatically appended
|
||||
# Exemples:
|
||||
# html_title 'Foo', 'Bar'
|
||||
# html_title # => 'Foo - Bar - My Project - Redmine'
|
||||
def html_title(*args)
|
||||
if args.empty?
|
||||
title = @html_title || []
|
||||
title << @project.name if @project
|
||||
title << Setting.app_title unless Setting.app_title == title.last
|
||||
title.reject(&:blank?).join(' - ')
|
||||
title.select {|t| !t.blank? }.join(' - ')
|
||||
else
|
||||
@html_title ||= []
|
||||
@html_title += args
|
||||
|
@ -546,7 +465,6 @@ module ApplicationHelper
|
|||
css << 'theme-' + theme.name
|
||||
end
|
||||
|
||||
css << 'project-' + @project.identifier if @project && @project.identifier.present?
|
||||
css << 'controller-' + controller_name
|
||||
css << 'action-' + action_name
|
||||
css.join(' ')
|
||||
|
@ -738,9 +656,6 @@ module ApplicationHelper
|
|||
# export:some/file -> Force the download of the file
|
||||
# Forum messages:
|
||||
# message#1218 -> Link to message with id 1218
|
||||
# Projects:
|
||||
# project:someproject -> Link to project named "someproject"
|
||||
# project#3 -> Link to project with id 3
|
||||
#
|
||||
# Links can refer other objects from other projects, using project identifier:
|
||||
# identifier:r52
|
||||
|
@ -765,30 +680,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},
|
||||
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))
|
||||
: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},
|
||||
link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
|
||||
:class => issue.css_classes,
|
||||
:title => "#{issue.subject.truncate(100)} (#{issue.status.name})")
|
||||
:title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
|
||||
end
|
||||
when 'document'
|
||||
if document = Document.visible.find_by_id(oid)
|
||||
|
@ -801,7 +707,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 +728,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 +762,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 +778,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
|
||||
|
@ -900,8 +804,7 @@ module ApplicationHelper
|
|||
content_tag('div',
|
||||
link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
|
||||
:class => 'contextual',
|
||||
:title => l(:button_edit_section),
|
||||
:id => "section-#{@current_section}") + heading.html_safe
|
||||
:title => l(:button_edit_section)) + heading.html_safe
|
||||
else
|
||||
heading
|
||||
end
|
||||
|
@ -982,20 +885,19 @@ module ApplicationHelper
|
|||
end
|
||||
end
|
||||
|
||||
TOC_RE = /<p>\{\{((<|<)|(>|>))?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
|
||||
|
@ -1133,7 +1035,7 @@ module ApplicationHelper
|
|||
(pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
|
||||
(pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
|
||||
(pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
|
||||
), :class => "progress progress-#{pcts[0]}", :style => "width: #{width};").html_safe +
|
||||
), :class => 'progress', :style => "width: #{width};").html_safe +
|
||||
content_tag('p', legend, :class => 'percent').html_safe
|
||||
end
|
||||
|
||||
|
@ -1166,7 +1068,6 @@ module ApplicationHelper
|
|||
|
||||
def include_calendar_headers_tags
|
||||
unless @calendar_headers_tags_included
|
||||
tags = javascript_include_tag("datepicker")
|
||||
@calendar_headers_tags_included = true
|
||||
content_for :header_tags do
|
||||
start_of_week = Setting.start_of_week
|
||||
|
@ -1174,13 +1075,12 @@ module ApplicationHelper
|
|||
# Redmine uses 1..7 (monday..sunday) in settings and locales
|
||||
# JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
|
||||
start_of_week = start_of_week.to_i % 7
|
||||
tags << javascript_tag(
|
||||
|
||||
tags = javascript_tag(
|
||||
"var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
|
||||
"showOn: 'button', buttonImageOnly: true, buttonImage: '" +
|
||||
path_to_image('/images/calendar.png') +
|
||||
"', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
|
||||
"selectOtherMonths: true, changeMonth: true, changeYear: true, " +
|
||||
"beforeShow: beforeShowDatePicker};")
|
||||
"', showButtonPanel: true, showWeek: true, showOtherMonths: true, selectOtherMonths: true};")
|
||||
jquery_locale = l('jquery.locale', :default => current_language.to_s)
|
||||
unless jquery_locale == 'en'
|
||||
tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
|
||||
|
@ -1243,13 +1143,18 @@ module ApplicationHelper
|
|||
super sources, options
|
||||
end
|
||||
|
||||
# TODO: remove this in 2.5.0
|
||||
def content_for(name, content = nil, &block)
|
||||
@has_content ||= {}
|
||||
@has_content[name] = true
|
||||
super(name, content, &block)
|
||||
end
|
||||
|
||||
def has_content?(name)
|
||||
content_for?(name)
|
||||
(@has_content && @has_content[name]) || false
|
||||
end
|
||||
|
||||
def sidebar_content?
|
||||
content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
|
||||
has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
|
||||
end
|
||||
|
||||
def view_layouts_base_sidebar_hook_response
|
||||
|
@ -1296,21 +1201,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 +1223,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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -19,69 +19,55 @@
|
|||
|
||||
module CustomFieldsHelper
|
||||
|
||||
CUSTOM_FIELDS_TABS = [
|
||||
{:name => 'IssueCustomField', :partial => 'custom_fields/index',
|
||||
:label => :label_issue_plural},
|
||||
{:name => 'TimeEntryCustomField', :partial => 'custom_fields/index',
|
||||
:label => :label_spent_time},
|
||||
{:name => 'ProjectCustomField', :partial => 'custom_fields/index',
|
||||
:label => :label_project_plural},
|
||||
{:name => 'VersionCustomField', :partial => 'custom_fields/index',
|
||||
:label => :label_version_plural},
|
||||
{:name => 'UserCustomField', :partial => 'custom_fields/index',
|
||||
:label => :label_user_plural},
|
||||
{:name => 'GroupCustomField', :partial => 'custom_fields/index',
|
||||
:label => :label_group_plural},
|
||||
{:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index',
|
||||
:label => TimeEntryActivity::OptionName},
|
||||
{:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index',
|
||||
:label => IssuePriority::OptionName},
|
||||
{:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index',
|
||||
: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
|
||||
CustomField::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 +77,53 @@ 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)
|
||||
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, '', tag_options.merge(:size => 10)) +
|
||||
calendar_for(field_id)
|
||||
when "text"
|
||||
text_area_tag(field_name, '', tag_options.merge(:rows => 3))
|
||||
when "bool"
|
||||
select_tag(field_name, options_for_select([[l(:label_no_change_option), ''],
|
||||
[l(:general_text_yes), '1'],
|
||||
[l(:general_text_no), '0']]), 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), tag_options.merge(:multiple => custom_field.multiple?))
|
||||
else
|
||||
text_field_tag(field_name, '', tag_options)
|
||||
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 +146,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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -94,20 +94,6 @@ module IssuesHelper
|
|||
s.html_safe
|
||||
end
|
||||
|
||||
# Returns an array of error messages for bulk edited issues
|
||||
def bulk_edit_error_messages(issues)
|
||||
messages = {}
|
||||
issues.each do |issue|
|
||||
issue.errors.full_messages.each do |message|
|
||||
messages[message] ||= []
|
||||
messages[message] << issue
|
||||
end
|
||||
end
|
||||
messages.map { |message, issues|
|
||||
"#{message}: " + issues.map {|i| "##{i.id}"}.join(', ')
|
||||
}
|
||||
end
|
||||
|
||||
# Returns a link for adding a new subtask to the given issue
|
||||
def link_to_new_subtask(issue)
|
||||
attrs = {
|
||||
|
@ -160,20 +146,18 @@ module IssuesHelper
|
|||
end
|
||||
|
||||
def render_custom_fields_rows(issue)
|
||||
values = issue.visible_custom_field_values
|
||||
return if values.empty?
|
||||
return if issue.custom_field_values.empty?
|
||||
ordered_values = []
|
||||
half = (values.size / 2.0).ceil
|
||||
half = (issue.custom_field_values.size / 2.0).ceil
|
||||
half.times do |i|
|
||||
ordered_values << values[i]
|
||||
ordered_values << values[i + half]
|
||||
ordered_values << issue.custom_field_values[i]
|
||||
ordered_values << issue.custom_field_values[i + half]
|
||||
end
|
||||
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"
|
||||
|
@ -200,53 +184,51 @@ module IssuesHelper
|
|||
|
||||
def sidebar_queries
|
||||
unless @sidebar_queries
|
||||
@sidebar_queries = IssueQuery.visible.
|
||||
order("#{Query.table_name}.name ASC").
|
||||
@sidebar_queries = IssueQuery.visible.all(
|
||||
:order => "#{Query.table_name}.name ASC",
|
||||
# Project specific queries and global queries
|
||||
where(@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id]).
|
||||
all
|
||||
:conditions => (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id])
|
||||
)
|
||||
end
|
||||
@sidebar_queries
|
||||
end
|
||||
|
||||
def query_links(title, queries)
|
||||
return '' if queries.empty?
|
||||
# links to #index on issues/show
|
||||
url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params
|
||||
|
||||
content_tag('h3', title) + "\n" +
|
||||
content_tag('ul',
|
||||
content_tag('h3', h(title)) +
|
||||
queries.collect {|query|
|
||||
css = 'query'
|
||||
css << ' selected' if query == @query
|
||||
content_tag('li', link_to(query.name, url_params.merge(:query_id => query), :class => css))
|
||||
}.join("\n").html_safe,
|
||||
:class => 'queries'
|
||||
) + "\n"
|
||||
link_to(h(query.name), url_params.merge(:query_id => query), :class => css)
|
||||
}.join('<br />').html_safe
|
||||
end
|
||||
|
||||
def render_sidebar_queries
|
||||
out = ''.html_safe
|
||||
out << query_links(l(:label_my_queries), sidebar_queries.select(&:is_private?))
|
||||
out << query_links(l(:label_query_plural), sidebar_queries.reject(&:is_private?))
|
||||
queries = sidebar_queries.select {|q| !q.is_public?}
|
||||
out << query_links(l(:label_my_queries), queries) if queries.any?
|
||||
queries = sidebar_queries.select {|q| q.is_public?}
|
||||
out << query_links(l(:label_query_plural), queries) if queries.any?
|
||||
out
|
||||
end
|
||||
|
||||
def email_issue_attributes(issue, user)
|
||||
def email_issue_attributes(issue)
|
||||
items = []
|
||||
%w(author status priority assigned_to category fixed_version).each do |attribute|
|
||||
unless issue.disabled_core_fields.include?(attribute+"_id")
|
||||
items << "#{l("field_#{attribute}")}: #{issue.send attribute}"
|
||||
end
|
||||
end
|
||||
issue.visible_custom_field_values(user).each do |value|
|
||||
items << "#{value.custom_field.name}: #{show_value(value, false)}"
|
||||
issue.custom_field_values.each do |value|
|
||||
items << "#{value.custom_field.name}: #{show_value(value)}"
|
||||
end
|
||||
items
|
||||
end
|
||||
|
||||
def render_email_issue_attributes(issue, user, html=false)
|
||||
items = email_issue_attributes(issue, user)
|
||||
def render_email_issue_attributes(issue, html=false)
|
||||
items = email_issue_attributes(issue)
|
||||
if html
|
||||
content_tag('ul', items.map{|s| content_tag('li', s)}.join("\n").html_safe)
|
||||
else
|
||||
|
@ -262,23 +244,23 @@ module IssuesHelper
|
|||
values_by_field = {}
|
||||
details.each do |detail|
|
||||
if detail.property == 'cf'
|
||||
field = detail.custom_field
|
||||
field_id = detail.prop_key
|
||||
field = CustomField.find_by_id(field_id)
|
||||
if field && field.multiple?
|
||||
values_by_field[field] ||= {:added => [], :deleted => []}
|
||||
values_by_field[field_id] ||= {:added => [], :deleted => []}
|
||||
if detail.old_value
|
||||
values_by_field[field][:deleted] << detail.old_value
|
||||
values_by_field[field_id][:deleted] << detail.old_value
|
||||
end
|
||||
if detail.value
|
||||
values_by_field[field][:added] << detail.value
|
||||
values_by_field[field_id][:added] << detail.value
|
||||
end
|
||||
next
|
||||
end
|
||||
end
|
||||
strings << show_detail(detail, no_html, options)
|
||||
end
|
||||
values_by_field.each do |field, changes|
|
||||
detail = JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s)
|
||||
detail.instance_variable_set "@custom_field", field
|
||||
values_by_field.each do |field_id, changes|
|
||||
detail = JournalDetail.new(:property => 'cf', :prop_key => field_id)
|
||||
if changes[:added].any?
|
||||
detail.value = changes[:added]
|
||||
strings << show_detail(detail, no_html, options)
|
||||
|
@ -321,27 +303,15 @@ module IssuesHelper
|
|||
old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank?
|
||||
end
|
||||
when 'cf'
|
||||
custom_field = detail.custom_field
|
||||
custom_field = CustomField.find_by_id(detail.prop_key)
|
||||
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)
|
||||
when 'relation'
|
||||
if detail.value && !detail.old_value
|
||||
rel_issue = Issue.visible.find_by_id(detail.value)
|
||||
value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.value}" :
|
||||
(no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path]))
|
||||
elsif detail.old_value && !detail.value
|
||||
rel_issue = Issue.visible.find_by_id(detail.old_value)
|
||||
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
|
||||
end
|
||||
call_hook(:helper_issues_show_detail_after_setting,
|
||||
{:detail => detail, :label => label, :value => value, :old_value => old_value })
|
||||
|
@ -353,9 +323,7 @@ module IssuesHelper
|
|||
unless no_html
|
||||
label = content_tag('strong', label)
|
||||
old_value = content_tag("i", h(old_value)) if detail.old_value
|
||||
if detail.old_value && detail.value.blank? && detail.property != 'relation'
|
||||
old_value = content_tag("del", old_value)
|
||||
end
|
||||
old_value = content_tag("del", old_value) if detail.old_value and detail.value.blank?
|
||||
if detail.property == 'attachment' && !value.blank? && atta = Attachment.find_by_id(detail.prop_key)
|
||||
# Link to the attachment if it has not been removed
|
||||
value = link_to_attachment(atta, :download => true, :only_path => options[:only_path])
|
||||
|
@ -391,7 +359,7 @@ module IssuesHelper
|
|||
else
|
||||
l(:text_journal_set_to, :label => label, :value => value).html_safe
|
||||
end
|
||||
when 'attachment', 'relation'
|
||||
when 'attachment'
|
||||
l(:text_journal_added, :label => label, :value => value).html_safe
|
||||
end
|
||||
else
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -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
|
||||
|
@ -46,26 +46,11 @@ module ProjectsHelper
|
|||
end
|
||||
|
||||
options = ''
|
||||
options << "<option value=''> </option>" if project.allowed_parents.include?(nil)
|
||||
options << "<option value=''></option>" if project.allowed_parents.include?(nil)
|
||||
options << project_tree_options_for_select(project.allowed_parents.compact, :selected => selected)
|
||||
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|
|
||||
|
@ -84,11 +69,10 @@ module ProjectsHelper
|
|||
grouped[version.project.name] << [version.name, version.id]
|
||||
end
|
||||
|
||||
selected = selected.is_a?(Version) ? selected.id : selected
|
||||
if grouped.keys.size > 1
|
||||
grouped_options_for_select(grouped, selected)
|
||||
grouped_options_for_select(grouped, selected && selected.id)
|
||||
else
|
||||
options_for_select((grouped.values.first || []), selected)
|
||||
options_for_select((grouped.values.first || []), selected && selected.id)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -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
|
||||
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") : ''
|
||||
when :done_ratio
|
||||
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')
|
||||
when :relations
|
||||
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,18 @@ module QueriesHelper
|
|||
end
|
||||
|
||||
def csv_value(column, issue, value)
|
||||
format_object(value, false) do |value|
|
||||
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}"
|
||||
else
|
||||
value
|
||||
end
|
||||
value.to_s
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -159,7 +185,7 @@ module QueriesHelper
|
|||
if !params[:query_id].blank?
|
||||
cond = "project_id IS NULL"
|
||||
cond << " OR project_id = #{@project.id}" if @project
|
||||
@query = IssueQuery.where(cond).find(params[:query_id])
|
||||
@query = IssueQuery.find(params[:query_id], :conditions => cond)
|
||||
raise ::Unauthorized unless @query.visible?
|
||||
@query.project = @project
|
||||
session[:query] = {:id => @query.id, :project_id => @query.project_id}
|
||||
|
@ -172,7 +198,6 @@ module QueriesHelper
|
|||
session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
|
||||
else
|
||||
# retrieve from session
|
||||
@query = nil
|
||||
@query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id]
|
||||
@query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
|
||||
@query.project = @project
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -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][]',
|
||||
return content_tag(:label,
|
||||
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)
|
||||
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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -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
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -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'],
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -35,9 +35,12 @@ module VersionsHelper
|
|||
h = Hash.new {|k,v| k[v] = [0, 0]}
|
||||
begin
|
||||
# Total issue count
|
||||
Issue.where(:fixed_version_id => version.id).group(criteria).count.each {|c,s| h[c][0] = s}
|
||||
Issue.count(:group => criteria,
|
||||
:conditions => ["#{Issue.table_name}.fixed_version_id = ?", version.id]).each {|c,s| h[c][0] = s}
|
||||
# Open issues count
|
||||
Issue.open.where(:fixed_version_id => version.id).group(criteria).count.each {|c,s| h[c][1] = s}
|
||||
Issue.count(:group => criteria,
|
||||
:include => :status,
|
||||
:conditions => ["#{Issue.table_name}.fixed_version_id = ? AND #{IssueStatus.table_name}.is_closed = ?", version.id, false]).each {|c,s| h[c][1] = s}
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
# When grouping by an association, Rails throws this exception if there's no result (bug)
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -27,9 +27,8 @@ 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)
|
||||
watched = objects.any? {|object| object.watched_by?(user)}
|
||||
css = [watcher_css(objects), watched ? 'icon icon-fav' : 'icon icon-fav-off'].join(' ')
|
||||
text = watched ? l(:button_unwatch) : l(:button_watch)
|
||||
url = watch_path(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -18,78 +18,15 @@
|
|||
# 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)
|
||||
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)}
|
||||
|
||||
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, permissions[status.id][name]))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -102,7 +102,7 @@ class Attachment < ActiveRecord::Base
|
|||
if @temp_file && (@temp_file.size > 0)
|
||||
self.disk_directory = target_directory
|
||||
self.disk_filename = Attachment.disk_filename(filename, disk_directory)
|
||||
logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)") if logger
|
||||
logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)")
|
||||
path = File.dirname(diskfile)
|
||||
unless File.directory?(path)
|
||||
FileUtils.mkdir_p(path)
|
||||
|
@ -265,26 +265,15 @@ class Attachment < ActiveRecord::Base
|
|||
|
||||
# Moves an existing attachment to its target directory
|
||||
def move_to_target_directory!
|
||||
return unless !new_record? & readable?
|
||||
|
||||
if !new_record? & readable?
|
||||
src = diskfile
|
||||
self.disk_directory = target_directory
|
||||
dest = diskfile
|
||||
|
||||
return if src == dest
|
||||
|
||||
if !FileUtils.mkdir_p(File.dirname(dest))
|
||||
logger.error "Could not create directory #{File.dirname(dest)}" if logger
|
||||
return
|
||||
end
|
||||
|
||||
if !FileUtils.mv(src, dest)
|
||||
logger.error "Could not move attachment from #{src} to #{dest}" if logger
|
||||
return
|
||||
end
|
||||
|
||||
if src != dest && FileUtils.mkdir_p(File.dirname(dest)) && FileUtils.mv(src, dest)
|
||||
update_column :disk_directory, disk_directory
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Moves existing attachments that are stored at the root of the files
|
||||
# directory (ie. created before Redmine 2.3) to their target subdirectories
|
||||
|
@ -305,10 +294,10 @@ class Attachment < ActiveRecord::Base
|
|||
|
||||
def sanitize_filename(value)
|
||||
# get only the filename, not the whole path
|
||||
just_filename = value.gsub(/\A.*(\\|\/)/m, '')
|
||||
just_filename = value.gsub(/^.*(\\|\/)/, '')
|
||||
|
||||
# Finally, replace invalid characters with underscore
|
||||
@filename = just_filename.gsub(/[\/\?\%\*\:\|\"\'<>\n\r]+/, '_')
|
||||
@filename = just_filename.gsub(/[\/\?\%\*\:\|\"\'<>]+/, '_')
|
||||
end
|
||||
|
||||
# Returns the subdirectory in which the attachment will be saved
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -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)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -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)," +
|
||||
" 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)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -118,28 +118,25 @@ class Changeset < ActiveRecord::Base
|
|||
ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
|
||||
ref_keywords_any = ref_keywords.delete('*')
|
||||
# keywords used to fix issues
|
||||
fix_keywords = Setting.commit_update_keywords_array.map {|r| r['keywords']}.flatten.compact
|
||||
fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
|
||||
|
||||
kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
|
||||
|
||||
referenced_issues = []
|
||||
|
||||
comments.scan(/([\s\(\[,-]|^)((#{kw_regexp})[\s:]+)?(#\d+(\s+@#{TIMELOG_RE})?([\s,;&]+#\d+(\s+@#{TIMELOG_RE})?)*)(?=[[:punct:]]|\s|<|$)/i) do |match|
|
||||
action, refs = match[2].to_s.downcase, match[3]
|
||||
action, refs = match[2], match[3]
|
||||
next unless action.present? || ref_keywords_any
|
||||
|
||||
refs.scan(/#(\d+)(\s+@#{TIMELOG_RE})?/).each do |m|
|
||||
issue, hours = find_referenced_issue_by_id(m[0].to_i), m[2]
|
||||
if issue
|
||||
referenced_issues << issue
|
||||
# Don't update issues or log time when importing old commits
|
||||
unless repository.created_on && committed_on && committed_on < repository.created_on
|
||||
fix_issue(issue, action) if fix_keywords.include?(action)
|
||||
fix_issue(issue) if fix_keywords.include?(action.to_s.downcase)
|
||||
log_time(issue, hours) if hours && Setting.commit_logtime_enabled?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
referenced_issues.uniq!
|
||||
self.issues = referenced_issues unless referenced_issues.empty?
|
||||
|
@ -154,14 +151,13 @@ class Changeset < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def text_tag(ref_project=nil)
|
||||
repo = ""
|
||||
if repository && repository.identifier.present?
|
||||
repo = "#{repository.identifier}|"
|
||||
end
|
||||
tag = if scmid?
|
||||
"commit:#{repo}#{scmid}"
|
||||
"commit:#{scmid}"
|
||||
else
|
||||
"#{repo}r#{revision}"
|
||||
"r#{revision}"
|
||||
end
|
||||
if repository && repository.identifier.present?
|
||||
tag = "#{repository.identifier}|#{tag}"
|
||||
end
|
||||
if ref_project && project && ref_project != project
|
||||
tag = "#{project.identifier}:#{tag}"
|
||||
|
@ -198,7 +194,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
|
||||
|
@ -214,26 +210,25 @@ class Changeset < ActiveRecord::Base
|
|||
|
||||
private
|
||||
|
||||
# Updates the +issue+ according to +action+
|
||||
def fix_issue(issue, action)
|
||||
def fix_issue(issue)
|
||||
status = IssueStatus.find_by_id(Setting.commit_fix_status_id.to_i)
|
||||
if status.nil?
|
||||
logger.warn("No status matches commit_fix_status_id setting (#{Setting.commit_fix_status_id})") if logger
|
||||
return issue
|
||||
end
|
||||
|
||||
# the issue may have been updated by the closure of another one (eg. duplicate)
|
||||
issue.reload
|
||||
# don't change the status is the issue is closed
|
||||
return if issue.status && issue.status.is_closed?
|
||||
|
||||
journal = issue.init_journal(user || User.anonymous,
|
||||
ll(Setting.default_language,
|
||||
:text_status_changed_by_changeset,
|
||||
text_tag(issue.project)))
|
||||
rule = Setting.commit_update_keywords_array.detect do |rule|
|
||||
rule['keywords'].include?(action) &&
|
||||
(rule['if_tracker_id'].blank? || rule['if_tracker_id'] == issue.tracker_id.to_s)
|
||||
end
|
||||
if rule
|
||||
issue.assign_attributes rule.slice(*Issue.attribute_names)
|
||||
journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag(issue.project)))
|
||||
issue.status = status
|
||||
unless Setting.commit_fix_done_ratio.blank?
|
||||
issue.done_ratio = Setting.commit_fix_done_ratio.to_i
|
||||
end
|
||||
Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
|
||||
{ :changeset => self, :issue => issue, :action => action })
|
||||
{ :changeset => self, :issue => issue })
|
||||
unless issue.save
|
||||
logger.warn("Issue ##{issue.id} could not be saved by changeset #{id}: #{issue.errors.full_messages}") if logger
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -22,16 +22,5 @@ class Comment < ActiveRecord::Base
|
|||
|
||||
validates_presence_of :commented, :author, :comments
|
||||
|
||||
after_create :send_notification
|
||||
|
||||
safe_attributes 'comments'
|
||||
|
||||
private
|
||||
|
||||
def send_notification
|
||||
mailer_method = "#{commented.class.name.underscore}_comment_added"
|
||||
if Setting.notified_events.include?(mailer_method)
|
||||
Mailer.send(mailer_method, self).deliver
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -15,27 +15,10 @@
|
|||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
require File.expand_path('../../../test_helper', __FILE__)
|
||||
|
||||
class Redmine::ApiTest::ApiTest < Redmine::ApiTest::Base
|
||||
fixtures :users
|
||||
|
||||
def setup
|
||||
Setting.rest_api_enabled = '1'
|
||||
class CommentObserver < ActiveRecord::Observer
|
||||
def after_create(comment)
|
||||
if comment.commented.is_a?(News) && Setting.notified_events.include?('news_comment_added')
|
||||
Mailer.news_comment_added(comment).deliver
|
||||
end
|
||||
|
||||
def test_api_should_work_with_protect_from_forgery
|
||||
ActionController::Base.allow_forgery_protection = true
|
||||
assert_difference('User.count') do
|
||||
post '/users.xml', {
|
||||
:user => {
|
||||
:login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname',
|
||||
:mail => 'foo@example.net', :password => 'secret123'}
|
||||
},
|
||||
credentials('admin')
|
||||
assert_response 201
|
||||
end
|
||||
ensure
|
||||
ActionController::Base.allow_forgery_protection = false
|
||||
end
|
||||
end
|
|
@ -1,5 +1,5 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2014 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@ -19,71 +19,60 @@ class CustomField < ActiveRecord::Base
|
|||
include Redmine::SubclassFactory
|
||||
|
||||
has_many :custom_values, :dependent => :delete_all
|
||||
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 }
|
||||
validate :validate_custom_field
|
||||
validates_inclusion_of :field_format, :in => 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
|
||||
field.roles.clear
|
||||
end
|
||||
end
|
||||
|
||||
scope :sorted, lambda { order("#{table_name}.position ASC") }
|
||||
scope :visible, lambda {|*args|
|
||||
user = args.shift || User.current
|
||||
if user.admin?
|
||||
# nop
|
||||
elsif user.memberships.any?
|
||||
where("#{table_name}.visible = ? OR #{table_name}.id IN (SELECT DISTINCT cfr.custom_field_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 = ?)",
|
||||
true, user.id)
|
||||
else
|
||||
where(:visible => true)
|
||||
end
|
||||
}
|
||||
|
||||
def visible_by?(project, user=User.current)
|
||||
visible? || user.admin?
|
||||
end
|
||||
CUSTOM_FIELDS_TABS = [
|
||||
{:name => 'IssueCustomField', :partial => 'custom_fields/index',
|
||||
:label => :label_issue_plural},
|
||||
{:name => 'TimeEntryCustomField', :partial => 'custom_fields/index',
|
||||
:label => :label_spent_time},
|
||||
{:name => 'ProjectCustomField', :partial => 'custom_fields/index',
|
||||
:label => :label_project_plural},
|
||||
{:name => 'VersionCustomField', :partial => 'custom_fields/index',
|
||||
:label => :label_version_plural},
|
||||
{:name => 'UserCustomField', :partial => 'custom_fields/index',
|
||||
:label => :label_user_plural},
|
||||
{:name => 'GroupCustomField', :partial => 'custom_fields/index',
|
||||
:label => :label_group_plural},
|
||||
{:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index',
|
||||
:label => TimeEntryActivity::OptionName},
|
||||
{:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index',
|
||||
:label => IssuePriority::OptionName},
|
||||
{:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index',
|
||||
:label => DocumentCategory::OptionName}
|
||||
]
|
||||
|
||||
def format
|
||||
@format ||= Redmine::FieldFormat.find(field_format)
|
||||
end
|
||||
CUSTOM_FIELDS_NAMES = CUSTOM_FIELDS_TABS.collect{|v| v[:name]}
|
||||
|
||||
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 +83,78 @@ 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)
|
||||
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
|
||||
|
||||
def possible_values_options(object=nil)
|
||||
if object.is_a?(Array)
|
||||
object.map {|o| format.possible_values_options(self, o)}.reduce(:&) || []
|
||||
elsif obj.is_a?(Array)
|
||||
obj.collect {|o| possible_values_options(o)}.reduce(:&)
|
||||
else
|
||||
format.possible_values_options(self, object) || []
|
||||
[]
|
||||
end
|
||||
when 'bool'
|
||||
[[l(:general_text_Yes), '1'], [l(:general_text_No), '0']]
|
||||
else
|
||||
possible_values || []
|
||||
end
|
||||
end
|
||||
|
||||
def possible_values
|
||||
values = read_attribute(:possible_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
|
||||
values || []
|
||||
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,46 +178,80 @@ 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)
|
||||
end
|
||||
|
||||
def visibility_by_project_condition(project_key=nil, user=User.current, id_column=nil)
|
||||
if visible? || user.admin?
|
||||
"1=1"
|
||||
elsif user.anonymous?
|
||||
"1=0"
|
||||
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 #{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 #{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 #{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
|
||||
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})"
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def self.visibility_condition
|
||||
if user.admin?
|
||||
"1=1"
|
||||
elsif user.anonymous?
|
||||
"#{table_name}.visible"
|
||||
else
|
||||
"#{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})"
|
||||
def join_alias
|
||||
"cf_#{id}"
|
||||
end
|
||||
|
||||
def value_join_alias
|
||||
join_alias + "_" + field_format
|
||||
end
|
||||
|
||||
def <=>(field)
|
||||
|
@ -208,12 +260,17 @@ 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
|
||||
self.name =~ /^(.+)CustomField$/
|
||||
$1.constantize rescue nil
|
||||
begin; $1.constantize; rescue nil; end
|
||||
end
|
||||
|
||||
# to move in project_custom_field
|
||||
|
@ -227,8 +284,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 +293,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 +314,29 @@ class CustomField < ActiveRecord::Base
|
|||
|
||||
protected
|
||||
|
||||
# Returns the error message for the given value regarding its format
|
||||
def validate_field_value_format(value)
|
||||
errs = []
|
||||
if value.present?
|
||||
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 +353,3 @@ class CustomField < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
require_dependency 'redmine/field_format'
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue