Compare commits

..
27 Commits
Author SHA1 Message Date
Jean-Philippe Lang 9c7b8c8192 tagged version 2.2.1
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/tags/2.2.1@11153 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-01-09 14:08:55 +00:00
Jean-Philippe Lang f26654b29f Merged r11146 from trunk: Changes for 2.2.1 release.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@11147 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-01-09 13:33:24 +00:00
Jean-Philippe Lang 5c62c1cf95 Merged r11126 from trunk (#12744).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@11145 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-01-09 13:31:05 +00:00
Jean-Philippe Lang 2d6adbd7ff Merged r11088 from trunk (#12691).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@11144 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-01-09 13:29:59 +00:00
Jean-Philippe Lang b0ccaffe1b Merged r11064 from trunk (#12652).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@11143 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-01-09 13:29:08 +00:00
Toshi MARUYAMA 2c37617973 Merged r11140 from trunk to 2.2-stable.
Upgrade to Rails 3.2.11.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@11142 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-01-08 23:57:13 +00:00
Toshi MARUYAMA 567eb70fdb Merged r11128 from trunk to 2.2-stable.
Test fails when run at the beginning of the day.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@11141 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-01-08 23:14:03 +00:00
Toshi MARUYAMA de63102925 Merged r11109 from trunk to 2.2-stable
Upgrade to Rails 3.2.10.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@11111 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-01-04 02:05:44 +00:00
Toshi MARUYAMA a8fcf9389e Merged r11091 from trunk (#12711, #11290)
fix incorrect min_x_value of lib/SVG/Graph/TimeSeries.rb by r10439.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@11093 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-01-03 00:15:32 +00:00
Toshi MARUYAMA 3e4b36de83 Merged r11051 from trunk to 2.2-stable (#12630)
Russian "x_hours" translation updated by Mikhail Velkin.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@11054 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-12-19 00:00:04 +00:00
Jean-Philippe Lang edd584d59e Merged r11041 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@11042 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-12-18 12:24:17 +00:00
Toshi MARUYAMA e2b27ab696 Merged r11036 from trunk to 2.2-stable (#12615, #12619)
Russian translation changed by Kirill Bezrukov.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@11040 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-12-18 00:04:49 +00:00
Toshi MARUYAMA 1968d039ee Merged r11035 from trunk to 2.2-stable (#12615)
Russian translation updated by Kirill Bezrukov.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@11037 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-12-17 23:24:31 +00:00
Toshi MARUYAMA fddc33cd45 Merged r11022 from trunk to 2.2-stable (#12614)
Dutch translation updated.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@11023 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-12-17 12:43:15 +00:00
Toshi MARUYAMA 29518ba0cf Merged r11015 from trunk to 2.2-stable (#12605, #12608)
Norwegian translation changed by Ketil Mehl.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@11018 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-12-16 07:34:34 +00:00
Toshi MARUYAMA acd27b9eb1 Merged r11014 from trunk to 2.2-stable (#12605)
Norwegian translation for 1.4-stable updated by Ketil Mehl.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@11017 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-12-16 07:34:09 +00:00
Toshi MARUYAMA 6445b5997c Merged r11012 from trunk to 2.2-stable (#12607)
Japanese translation for 2.2-stable updated.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@11016 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-12-16 07:33:39 +00:00
Jean-Philippe Lang 416d33973f Merged r10991 from trunk (#12568).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@11009 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-12-15 07:55:50 +00:00
Jean-Philippe Lang 82c7dc11d2 Merged r10992 from trunk (#12400).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@11006 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-12-15 07:52:39 +00:00
Jean-Philippe Lang d22f782d3f Merged r10956 from trunk (#12513).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@11004 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-12-15 07:51:12 +00:00
Jean-Philippe Lang 3717fdfa79 Merged r10975 from trunk (#12566).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@11002 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-12-15 07:49:31 +00:00
Toshi MARUYAMA 9d3d932703 Merged r10999 from trunk (#12602)
Korean translation updated by Jongwook Choi.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@11000 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-12-15 01:32:43 +00:00
Jean-Philippe Lang 3a379ce4b9 Merged r10948 into 2.2-stable (#3447).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@10952 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-12-08 08:41:59 +00:00
Toshi MARUYAMA daa77f0109 Merged r10946 from trunk to 2.2-stable (#12514)
Lithuanian translation updated by Egidijus Zideckas.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@10947 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-12-06 06:30:05 +00:00
Toshi MARUYAMA c0d66d626b Merged r10940 from trunk to 2.2-stable (#12494)
Bulgarian "button_submit" translation changed by Ivan Cenov.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@10942 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-12-04 05:43:41 +00:00
Jean-Philippe Lang bf02b76ca3 Sets stable branch.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@10908 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-12-02 18:01:48 +00:00
Jean-Philippe Lang 75e02ca486 branch 2.2-stable added
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.2-stable@10907 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-12-02 17:58:22 +00:00
999 changed files with 13339 additions and 37572 deletions

No files matched your search

-8
View File
@@ -1,7 +1,5 @@
/.project /.project
/.loadpath /.loadpath
/.powrc
/.rvmrc
/config/additional_environment.rb /config/additional_environment.rb
/config/configuration.yml /config/configuration.yml
/config/database.yml /config/database.yml
@@ -17,14 +15,8 @@
/lib/redmine/scm/adapters/mercurial/redminehelper.pyo /lib/redmine/scm/adapters/mercurial/redminehelper.pyo
/log/*.log* /log/*.log*
/log/mongrel_debug /log/mongrel_debug
/plugins/*
!/plugins/README
/public/dispatch.* /public/dispatch.*
/public/plugin_assets /public/plugin_assets
/public/themes/*
!/public/themes/alternate
!/public/themes/classic
!/public/themes/README
/tmp/* /tmp/*
/tmp/cache/* /tmp/cache/*
/tmp/pdf/* /tmp/pdf/*
-2
View File
@@ -2,8 +2,6 @@ syntax: glob
.project .project
.loadpath .loadpath
.powrc
.rvmrc
config/additional_environment.rb config/additional_environment.rb
config/configuration.yml config/configuration.yml
config/database.yml config/database.yml
-8
View File
@@ -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
+44 -49
View File
@@ -1,9 +1,9 @@
source 'https://rubygems.org' source 'http://rubygems.org'
gem "rails", "3.2.13" gem 'rails', '3.2.11'
gem "jquery-rails", "~> 2.0.2" gem "jquery-rails", "~> 2.0.2"
gem "i18n", "~> 0.6.0" gem "i18n", "~> 0.6.0"
gem "coderay", "~> 1.1.0" gem "coderay", "~> 1.0.6"
gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby] gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby]
gem "builder", "3.0.0" gem "builder", "3.0.0"
@@ -14,7 +14,7 @@ end
# Optional gem for OpenID authentication # Optional gem for OpenID authentication
group :openid do group :openid do
gem "ruby-openid", "~> 2.3.0", :require => "openid" gem "ruby-openid", "~> 2.1.4", :require => "openid"
gem "rack-openid" gem "rack-openid"
end end
@@ -28,47 +28,43 @@ platforms :mri, :mingw do
end end
end end
platforms :jruby do # Database gems
# jruby-openssl is bundled with JRuby 1.7.0 platforms :mri, :mingw do
gem "jruby-openssl" if Object.const_defined?(:JRUBY_VERSION) && JRUBY_VERSION < '1.7.0' group :postgresql do
gem "activerecord-jdbc-adapter", "~> 1.2.6" gem "pg", ">= 0.11.0"
end
group :sqlite do
gem "sqlite3"
end
end end
# Include database gems for the adapters found in the database platforms :mri_18, :mingw_18 do
# configuration file group :mysql do
require 'erb' gem "mysql", "~> 2.8.1"
require 'yaml'
database_file = File.join(File.dirname(__FILE__), "config/database.yml")
if File.exist?(database_file)
database_config = YAML::load(ERB.new(IO.read(database_file)).result)
adapters = database_config.values.map {|c| c['adapter']}.compact.uniq
if adapters.any?
adapters.each do |adapter|
case adapter
when 'mysql2'
gem "mysql2", "~> 0.3.11", :platforms => [:mri, :mingw]
gem "activerecord-jdbcmysql-adapter", :platforms => :jruby
when 'mysql'
gem "mysql", "~> 2.8.1", :platforms => [:mri, :mingw]
gem "activerecord-jdbcmysql-adapter", :platforms => :jruby
when /postgresql/
gem "pg", ">= 0.11.0", :platforms => [:mri, :mingw]
gem "activerecord-jdbcpostgresql-adapter", :platforms => :jruby
when /sqlite3/
gem "sqlite3", :platforms => [:mri, :mingw]
gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby
when /sqlserver/
gem "tiny_tds", "~> 0.5.1", :platforms => [:mri, :mingw]
gem "activerecord-sqlserver-adapter", :platforms => [:mri, :mingw]
else
warn("Unknown database adapter `#{adapter}` found in config/database.yml, use Gemfile.local to load your own database gems")
end end
end
platforms :mri_19, :mingw_19 do
group :mysql do
gem "mysql2", "~> 0.3.11"
end end
else end
warn("No adapter found in config/database.yml, please configure it first")
platforms :jruby do
gem "jruby-openssl"
group :mysql do
gem "activerecord-jdbcmysql-adapter"
end
group :postgresql do
gem "activerecord-jdbcpostgresql-adapter"
end
group :sqlite do
gem "activerecord-jdbcsqlite3-adapter"
end end
else
warn("Please configure your config/database.yml first")
end end
group :development do group :development do
@@ -77,13 +73,13 @@ group :development do
end end
group :test do group :test do
gem "shoulda", "~> 3.3.2" gem "shoulda", "~> 2.11"
gem "mocha", ">= 0.14", :require => 'mocha/api' # Shoulda does not work nice on Ruby 1.9.3 and JRuby 1.7.
if RUBY_VERSION >= '1.9.3' # It seems to need test-unit explicitely.
gem "capybara", "~> 2.1.0" platforms = [:mri_19]
gem "selenium-webdriver" platforms << :jruby if defined?(JRUBY_VERSION) && JRUBY_VERSION >= "1.7"
gem "database_cleaner" gem "test-unit", :platforms => platforms
end gem "mocha", "0.12.3"
end end
local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local") local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local")
@@ -95,6 +91,5 @@ end
# Load plugins' Gemfiles # Load plugins' Gemfiles
Dir.glob File.expand_path("../plugins/*/Gemfile", __FILE__) do |file| Dir.glob File.expand_path("../plugins/*/Gemfile", __FILE__) do |file|
puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v` 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)
instance_eval File.read(file), file
end end
+29 -76
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -20,14 +20,12 @@ class AccountController < ApplicationController
include CustomFieldsHelper include CustomFieldsHelper
# prevents login action to be filtered by check_if_login_required application scope filter # prevents login action to be filtered by check_if_login_required application scope filter
skip_before_filter :check_if_login_required, :check_password_change skip_before_filter :check_if_login_required
# Login request and validation # Login request and validation
def login def login
if request.get? if request.get?
if User.current.logged? logout_user
redirect_to home_url
end
else else
authenticate_user authenticate_user
end end
@@ -38,20 +36,15 @@ class AccountController < ApplicationController
# Log out current user and redirect to welcome page # Log out current user and redirect to welcome page
def logout def logout
if User.current.anonymous?
redirect_to home_url
elsif request.post?
logout_user logout_user
redirect_to home_url redirect_to home_url
end end
# display the logout form
end
# Lets user choose a new password # Lets user choose a new password
def lost_password def lost_password
(redirect_to(home_url); return) unless Setting.lost_password? redirect_to(home_url) && return unless Setting.lost_password?
if params[:token] if params[:token]
@token = Token.find_token("recovery", params[:token].to_s) @token = Token.find_by_action_and_value("recovery", params[:token].to_s)
if @token.nil? || @token.expired? if @token.nil? || @token.expired?
redirect_to home_url redirect_to home_url
return return
@@ -75,15 +68,11 @@ class AccountController < ApplicationController
else else
if request.post? if request.post?
user = User.find_by_mail(params[:mail].to_s) user = User.find_by_mail(params[:mail].to_s)
# user not found # user not found or not active
unless user unless user && user.active?
flash.now[:error] = l(:notice_account_unknown_email) flash.now[:error] = l(:notice_account_unknown_email)
return return
end end
unless user.active?
handle_inactive_user(user, lost_password_path)
return
end
# user cannot change its password # user cannot change its password
unless user.change_password_allowed? unless user.change_password_allowed?
flash.now[:error] = l(:notice_can_t_change_password) flash.now[:error] = l(:notice_can_t_change_password)
@@ -103,10 +92,10 @@ class AccountController < ApplicationController
# User self-registration # User self-registration
def register def register
(redirect_to(home_url); return) unless Setting.self_registration? || session[:auth_source_registration] redirect_to(home_url) && return unless Setting.self_registration? || session[:auth_source_registration]
if request.get? if request.get?
session[:auth_source_registration] = nil session[:auth_source_registration] = nil
@user = User.new(:language => current_language.to_s) @user = User.new(:language => Setting.default_language)
else else
user_params = params[:user] || {} user_params = params[:user] || {}
@user = User.new @user = User.new
@@ -121,7 +110,7 @@ class AccountController < ApplicationController
session[:auth_source_registration] = nil session[:auth_source_registration] = nil
self.logged_user = @user self.logged_user = @user
flash[:notice] = l(:notice_account_activated) flash[:notice] = l(:notice_account_activated)
redirect_to my_account_path redirect_to :controller => 'my', :action => 'account'
end end
else else
@user.login = params[:user][:login] @user.login = params[:user][:login]
@@ -143,11 +132,11 @@ class AccountController < ApplicationController
# Token based account activation # Token based account activation
def activate def activate
(redirect_to(home_url); return) unless Setting.self_registration? && params[:token].present? redirect_to(home_url) && return unless Setting.self_registration? && params[:token]
token = Token.find_token('register', params[:token].to_s) token = Token.find_by_action_and_value('register', params[:token])
(redirect_to(home_url); return) unless token and !token.expired? redirect_to(home_url) && return unless token and !token.expired?
user = token.user user = token.user
(redirect_to(home_url); return) unless user.registered? redirect_to(home_url) && return unless user.registered?
user.activate user.activate
if user.save if user.save
token.destroy token.destroy
@@ -156,19 +145,6 @@ class AccountController < ApplicationController
redirect_to signin_path redirect_to signin_path
end 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 private
def authenticate_user def authenticate_user
@@ -180,7 +156,7 @@ class AccountController < ApplicationController
end end
def password_authentication 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? if user.nil?
invalid_credentials invalid_credentials
@@ -188,31 +164,25 @@ class AccountController < ApplicationController
onthefly_creation_failed(user, {:login => user.login, :auth_source_id => user.auth_source_id }) onthefly_creation_failed(user, {:login => user.login, :auth_source_id => user.auth_source_id })
else else
# Valid user # Valid user
if user.active?
successful_authentication(user) successful_authentication(user)
else
handle_inactive_user(user)
end
end end
end end
def open_id_authenticate(openid_url) 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 => signin_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? if result.successful?
user = User.find_or_initialize_by_identity_url(identity_url) user = User.find_or_initialize_by_identity_url(identity_url)
if user.new_record? if user.new_record?
# Self-registration off # Self-registration off
(redirect_to(home_url); return) unless Setting.self_registration? redirect_to(home_url) && return unless Setting.self_registration?
# Create on the fly # Create on the fly
user.login = registration['nickname'] unless registration['nickname'].nil? user.login = registration['nickname'] unless registration['nickname'].nil?
user.mail = registration['email'] unless registration['email'].nil? user.mail = registration['email'] unless registration['email'].nil?
user.firstname, user.lastname = registration['fullname'].split(' ') unless registration['fullname'].nil? user.firstname, user.lastname = registration['fullname'].split(' ') unless registration['fullname'].nil?
user.random_password user.random_password
user.register user.register
case Setting.self_registration case Setting.self_registration
when '1' when '1'
register_by_email_activation(user) do register_by_email_activation(user) do
@@ -232,7 +202,7 @@ class AccountController < ApplicationController
if user.active? if user.active?
successful_authentication(user) successful_authentication(user)
else else
handle_inactive_user(user) account_pending
end end
end end
end end
@@ -248,11 +218,12 @@ class AccountController < ApplicationController
set_autologin_cookie(user) set_autologin_cookie(user)
end end
call_hook(:controller_account_success_authentication_after, {:user => user }) call_hook(:controller_account_success_authentication_after, {:user => user })
redirect_back_or_default my_page_path redirect_back_or_default :controller => 'my', :action => 'page'
end end
def set_autologin_cookie(user) def set_autologin_cookie(user)
token = Token.create(:user => user, :action => 'autologin') token = Token.create(:user => user, :action => 'autologin')
cookie_name = Redmine::Configuration['autologin_cookie_name'] || 'autologin'
cookie_options = { cookie_options = {
:value => token.value, :value => token.value,
:expires => 1.year.from_now, :expires => 1.year.from_now,
@@ -260,7 +231,7 @@ class AccountController < ApplicationController
:secure => (Redmine::Configuration['autologin_cookie_secure'] ? true : false), :secure => (Redmine::Configuration['autologin_cookie_secure'] ? true : false),
:httponly => true :httponly => true
} }
cookies[autologin_cookie_name] = cookie_options cookies[cookie_name] = cookie_options
end end
# Onthefly creation failed, display the registration form to fill/fix attributes # Onthefly creation failed, display the registration form to fill/fix attributes
@@ -282,7 +253,7 @@ class AccountController < ApplicationController
token = Token.new(:user => user, :action => "register") token = Token.new(:user => user, :action => "register")
if user.save and token.save if user.save and token.save
Mailer.register(token).deliver 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 redirect_to signin_path
else else
yield if block_given? yield if block_given?
@@ -299,7 +270,7 @@ class AccountController < ApplicationController
if user.save if user.save
self.logged_user = user self.logged_user = user
flash[:notice] = l(:notice_account_activated) flash[:notice] = l(:notice_account_activated)
redirect_to my_account_path redirect_to :controller => 'my', :action => 'account'
else else
yield if block_given? yield if block_given?
end end
@@ -312,32 +283,14 @@ class AccountController < ApplicationController
if user.save if user.save
# Sends an email to the administrators # Sends an email to the administrators
Mailer.account_activation_request(user).deliver Mailer.account_activation_request(user).deliver
account_pending(user) account_pending
else else
yield if block_given? yield if block_given?
end end
end end
def handle_inactive_user(user, redirect_path=signin_path) def account_pending
if user.registered? flash[:notice] = l(:notice_account_pending)
account_pending(user, redirect_path) redirect_to signin_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
end end
end end
+1 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+7 -6
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -32,9 +32,10 @@ class AdminController < ApplicationController
def projects def projects
@status = params[:status] || 1 @status = params[:status] || 1
scope = Project.status(@status).order('lft') scope = Project.status(@status)
scope = scope.like(params[:name]) if params[:name].present? scope = scope.like(params[:name]) if params[:name].present?
@projects = scope.all
@projects = scope.all(:order => 'lft')
render :action => "projects", :layout => false if request.xhr? render :action => "projects", :layout => false if request.xhr?
end end
@@ -54,7 +55,7 @@ class AdminController < ApplicationController
flash[:error] = l(:error_can_t_load_default_data, e.message) flash[:error] = l(:error_can_t_load_default_data, e.message)
end end
end end
redirect_to admin_path redirect_to :action => 'index'
end end
def test_email def test_email
@@ -65,10 +66,10 @@ class AdminController < ApplicationController
@test = Mailer.test_email(User.current).deliver @test = Mailer.test_email(User.current).deliver
flash[:notice] = l(:notice_email_sent, User.current.mail) flash[:notice] = l(:notice_email_sent, User.current.mail)
rescue Exception => e 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 end
ActionMailer::Base.raise_delivery_errors = raise_delivery_errors ActionMailer::Base.raise_delivery_errors = raise_delivery_errors
redirect_to settings_path(:tab => 'notifications') redirect_to :controller => 'settings', :action => 'edit', :tab => 'notifications'
end end
def info def info
+23 -48
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -22,9 +22,6 @@ class Unauthorized < Exception; end
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
include Redmine::I18n include Redmine::I18n
include Redmine::Pagination
include RoutesHelper
helper :routes
class_attribute :accept_api_auth_actions class_attribute :accept_api_auth_actions
class_attribute :accept_rss_auth_actions class_attribute :accept_rss_auth_actions
@@ -35,10 +32,10 @@ class ApplicationController < ActionController::Base
protect_from_forgery protect_from_forgery
def handle_unverified_request def handle_unverified_request
super super
cookies.delete(autologin_cookie_name) cookies.delete(:autologin)
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 ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
rescue_from ::Unauthorized, :with => :deny_access rescue_from ::Unauthorized, :with => :deny_access
@@ -78,9 +75,6 @@ class ApplicationController < ActionController::Base
session[:user_id] = user.id session[:user_id] = user.id
session[:ctime] = Time.now.utc.to_i session[:ctime] = Time.now.utc.to_i
session[:atime] = Time.now.utc.to_i session[:atime] = Time.now.utc.to_i
if user.must_change_password?
session[:pwd] = '1'
end
end end
def user_setup def user_setup
@@ -115,10 +109,6 @@ class ApplicationController < ActionController::Base
authenticate_with_http_basic do |username, password| authenticate_with_http_basic do |username, password|
user = User.try_to_login(username, password) || User.find_by_api_key(username) user = User.try_to_login(username, password) || User.find_by_api_key(username)
end end
if user && user.must_change_password?
render_error :message => 'You must change your password', :status => 403
return
end
end end
# Switch user if requested by an admin user # Switch user if requested by an admin user
if user && user.admin? && (username = api_switch_user_from_request) if user && user.admin? && (username = api_switch_user_from_request)
@@ -134,14 +124,10 @@ class ApplicationController < ActionController::Base
user user
end end
def autologin_cookie_name
Redmine::Configuration['autologin_cookie_name'].presence || 'autologin'
end
def try_to_autologin def try_to_autologin
if cookies[autologin_cookie_name] && Setting.autologin? if cookies[:autologin] && Setting.autologin?
# auto-login feature starts a new session # auto-login feature starts a new session
user = User.try_to_autologin(cookies[autologin_cookie_name]) user = User.try_to_autologin(cookies[:autologin])
if user if user
reset_session reset_session
start_user_session(user) start_user_session(user)
@@ -164,7 +150,7 @@ class ApplicationController < ActionController::Base
# Logs out current user # Logs out current user
def logout_user def logout_user
if User.current.logged? if User.current.logged?
cookies.delete(autologin_cookie_name) cookies.delete :autologin
Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin']) Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
self.logged_user = nil self.logged_user = nil
end end
@@ -177,16 +163,6 @@ class ApplicationController < ActionController::Base
require_login if Setting.login_required? require_login if Setting.login_required?
end 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 def set_localization
lang = nil lang = nil
if User.current.logged? if User.current.logged?
@@ -212,13 +188,7 @@ class ApplicationController < ActionController::Base
url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id]) url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
end end
respond_to do |format| respond_to do |format|
format.html { format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
if request.xhr?
head :unauthorized
else
redirect_to :controller => "account", :action => "login", :back_url => url
end
}
format.atom { 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.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' } format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
@@ -321,7 +291,7 @@ class ApplicationController < ActionController::Base
# Find issues with a single :id param or :ids array param # Find issues with a single :id param or :ids array param
# Raises a Unauthorized exception if one of the issues is not visible # Raises a Unauthorized exception if one of the issues is not visible
def find_issues 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 ActiveRecord::RecordNotFound if @issues.empty?
raise Unauthorized unless @issues.all?(&:visible?) raise Unauthorized unless @issues.all?(&:visible?)
@projects = @issues.collect(&:project).compact.uniq @projects = @issues.collect(&:project).compact.uniq
@@ -330,16 +300,6 @@ class ApplicationController < ActionController::Base
render_404 render_404
end end
def find_attachments
if (attachments = params[:attachments]).present?
att = attachments.values.collect do |attachment|
Attachment.find_by_token( attachment[:token] ) if attachment[:token].present?
end
att.compact!
end
@attachments = att || []
end
# make sure that the user is a member of the project (or admin) if project is private # make sure that the user is a member of the project (or admin) if project is private
# used as a before_filter for actions that do not require any particular permission on the project # used as a before_filter for actions that do not require any particular permission on the project
def check_project_privacy def check_project_privacy
@@ -578,6 +538,21 @@ class ApplicationController < ActionController::Base
flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present? flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
end 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... # Rescues an invalid query statement. Just in case...
def query_statement_invalid(exception) def query_statement_invalid(exception)
logger.error "Query::StatementInvalid: #{exception.message}" if logger logger.error "Query::StatementInvalid: #{exception.message}" if logger
+9 -24
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -85,17 +85,15 @@ class AttachmentsController < ApplicationController
@attachment = Attachment.new(:file => request.raw_post) @attachment = Attachment.new(:file => request.raw_post)
@attachment.author = User.current @attachment.author = User.current
@attachment.filename = params[:filename].presence || Redmine::Utils.random_hex(16) @attachment.filename = params[:filename].presence || Redmine::Utils.random_hex(16)
saved = @attachment.save
if @attachment.save
respond_to do |format| respond_to do |format|
format.js format.api { render :action => 'upload', :status => :created }
format.api { end
if saved else
render :action => 'upload', :status => :created respond_to do |format|
else format.api { render_validation_errors(@attachment) }
render_validation_errors(@attachment)
end end
}
end end
end end
@@ -103,17 +101,9 @@ class AttachmentsController < ApplicationController
if @attachment.container.respond_to?(:init_journal) if @attachment.container.respond_to?(:init_journal)
@attachment.container.init_journal(User.current) @attachment.container.init_journal(User.current)
end end
if @attachment.container
# Make sure association callbacks are called # Make sure association callbacks are called
@attachment.container.attachments.delete(@attachment) @attachment.container.attachments.delete(@attachment)
else redirect_to_referer_or project_path(@project)
@attachment.destroy
end
respond_to do |format|
format.html { redirect_to_referer_or project_path(@project) }
format.js
end
end end
private private
@@ -128,12 +118,7 @@ private
# Checks that the file exists and is readable # Checks that the file exists and is readable
def file_readable def file_readable
if @attachment.readable? @attachment.readable? ? true : render_404
true
else
logger.error "Cannot send attachment, #{@attachment.diskfile} does not exist or is unreadable."
render_404
end
end end
def read_authorize def read_authorize
+11 -31
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -20,77 +20,57 @@ class AuthSourcesController < ApplicationController
menu_item :ldap_authentication menu_item :ldap_authentication
before_filter :require_admin before_filter :require_admin
before_filter :find_auth_source, :only => [:edit, :update, :test_connection, :destroy]
def index def index
@auth_source_pages, @auth_sources = paginate AuthSource, :per_page => 25 @auth_source_pages, @auth_sources = paginate AuthSource, :per_page => 10
end end
def new def new
klass_name = params[:type] || 'AuthSourceLdap' klass_name = params[:type] || 'AuthSourceLdap'
@auth_source = AuthSource.new_subclass_instance(klass_name, params[:auth_source]) @auth_source = AuthSource.new_subclass_instance(klass_name, params[:auth_source])
render_404 unless @auth_source
end end
def create def create
@auth_source = AuthSource.new_subclass_instance(params[:type], params[:auth_source]) @auth_source = AuthSource.new_subclass_instance(params[:type], params[:auth_source])
if @auth_source.save if @auth_source.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to auth_sources_path redirect_to :action => 'index'
else else
render :action => 'new' render :action => 'new'
end end
end end
def edit def edit
@auth_source = AuthSource.find(params[:id])
end end
def update def update
@auth_source = AuthSource.find(params[:id])
if @auth_source.update_attributes(params[:auth_source]) if @auth_source.update_attributes(params[:auth_source])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to auth_sources_path redirect_to :action => 'index'
else else
render :action => 'edit' render :action => 'edit'
end end
end end
def test_connection def test_connection
@auth_source = AuthSource.find(params[:id])
begin begin
@auth_source.test_connection @auth_source.test_connection
flash[:notice] = l(:notice_successful_connection) flash[:notice] = l(:notice_successful_connection)
rescue Exception => e rescue Exception => e
flash[:error] = l(:error_unable_to_connect, e.message) flash[:error] = l(:error_unable_to_connect, e.message)
end end
redirect_to auth_sources_path redirect_to :action => 'index'
end end
def destroy def destroy
unless @auth_source.users.exists? @auth_source = AuthSource.find(params[:id])
unless @auth_source.users.find(:first)
@auth_source.destroy @auth_source.destroy
flash[:notice] = l(:notice_successful_delete) flash[:notice] = l(:notice_successful_delete)
end end
redirect_to auth_sources_path redirect_to :action => 'index'
end
def autocomplete_for_new_user
results = AuthSource.search(params[:term])
render :json => results.map {|result| {
'value' => result[:login],
'label' => "#{result[:login]} (#{result[:firstname]} #{result[:lastname]})",
'login' => result[:login].to_s,
'firstname' => result[:firstname].to_s,
'lastname' => result[:lastname].to_s,
'mail' => result[:mail].to_s,
'auth_source_id' => result[:auth_source_id].to_s
}}
end
private
def find_auth_source
@auth_source = AuthSource.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end end
end end
+4 -4
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -23,10 +23,10 @@ class AutoCompletesController < ApplicationController
q = (params[:q] || params[:term]).to_s.strip q = (params[:q] || params[:term]).to_s.strip
if q.present? if q.present?
scope = (params[:scope] == "all" || @project.nil? ? Issue : @project.issues).visible scope = (params[:scope] == "all" || @project.nil? ? Issue : @project.issues).visible
if q.match(/\A#?(\d+)\z/) if q.match(/^\d+$/)
@issues << scope.find_by_id($1.to_i) @issues << scope.find_by_id(q.to_i)
end end
@issues += scope.where("LOWER(#{Issue.table_name}.subject) LIKE LOWER(?)", "%#{q}%").order("#{Issue.table_name}.id DESC").limit(10).all @issues += scope.where("LOWER(#{Issue.table_name}.subject) LIKE ?", "%#{q.downcase}%").order("#{Issue.table_name}.id DESC").limit(10).all
@issues.compact! @issues.compact!
end end
render :layout => false render :layout => false
+12 -18
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -25,7 +25,7 @@ class BoardsController < ApplicationController
helper :watchers helper :watchers
def index 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 # show the board if there is only one
if @boards.size == 1 if @boards.size == 1
@board = @boards.first @board = @boards.first
@@ -39,27 +39,21 @@ class BoardsController < ApplicationController
sort_init 'updated_on', 'desc' sort_init 'updated_on', 'desc'
sort_update 'created_on' => "#{Message.table_name}.created_on", sort_update 'created_on' => "#{Message.table_name}.created_on",
'replies' => "#{Message.table_name}.replies_count", 'replies' => "#{Message.table_name}.replies_count",
'updated_on' => "COALESCE(last_replies_messages.created_on, #{Message.table_name}.created_on)" 'updated_on' => "#{Message.table_name}.updated_on"
@topic_count = @board.topics.count @topic_count = @board.topics.count
@topic_pages = Paginator.new @topic_count, per_page_option, params['page'] @topic_pages = Paginator.new self, @topic_count, per_page_option, params['page']
@topics = @board.topics. @topics = @board.topics.reorder("#{Message.table_name}.sticky DESC").order(sort_clause).all(
reorder("#{Message.table_name}.sticky DESC"). :include => [:author, {:last_reply => :author}],
includes(:last_reply). :limit => @topic_pages.items_per_page,
limit(@topic_pages.per_page). :offset => @topic_pages.current.offset)
offset(@topic_pages.offset).
order(sort_clause).
preload(:author, {:last_reply => :author}).
all
@message = Message.new(:board => @board) @message = Message.new(:board => @board)
render :action => 'show', :layout => !request.xhr? render :action => 'show', :layout => !request.xhr?
} }
format.atom { format.atom {
@messages = @board.messages. @messages = @board.messages.find :all, :order => 'created_on DESC',
reorder('created_on DESC'). :include => [:author, :board],
includes(:author, :board). :limit => Setting.feeds_limit.to_i
limit(Setting.feeds_limit.to_i).
all
render_feed(@messages, :title => "#{@project}: #{@board}") render_feed(@messages, :title => "#{@project}: #{@board}")
} }
end end
@@ -100,7 +94,7 @@ class BoardsController < ApplicationController
private private
def redirect_to_settings_in_projects def redirect_to_settings_in_projects
redirect_to settings_project_path(@project, :tab => 'boards') redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
end end
def find_board_if_available def find_board_if_available
+1 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+3 -3
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -32,12 +32,12 @@ class CommentsController < ApplicationController
flash[:notice] = l(:label_comment_added) flash[:notice] = l(:label_comment_added)
end end
redirect_to news_path(@news) redirect_to :controller => 'news', :action => 'show', :id => @news
end end
def destroy def destroy
@news.comments.find(params[:comment_id]).destroy @news.comments.find(params[:comment_id]).destroy
redirect_to news_path(@news) redirect_to :controller => 'news', :action => 'show', :id => @news
end end
private private
+6 -6
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -19,15 +19,16 @@ class ContextMenusController < ApplicationController
helper :watchers helper :watchers
helper :issues helper :issues
before_filter :find_issues, :only => :issues
def issues def issues
@issues = Issue.visible.all(:conditions => {:id => params[:ids]}, :include => :project)
if (@issues.size == 1) if (@issues.size == 1)
@issue = @issues.first @issue = @issues.first
end end
@issue_ids = @issues.map(&:id).sort @issue_ids = @issues.map(&:id).sort
@allowed_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&) @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), @can = {:edit => User.current.allowed_to?(:edit_issues, @projects),
:log_time => (@project && User.current.allowed_to?(:log_time, @project)), :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
@@ -71,9 +72,8 @@ class ContextMenusController < ApplicationController
end end
def time_entries def time_entries
@time_entries = TimeEntry.where(:id => params[:ids]).preload(:project).to_a @time_entries = TimeEntry.all(
(render_404; return) unless @time_entries.present? :conditions => {:id => params[:ids]}, :include => :project)
@projects = @time_entries.collect(&:project).compact.uniq @projects = @time_entries.collect(&:project).compact.uniq
@project = @projects.first if @projects.size == 1 @project = @projects.first if @projects.size == 1
@activities = TimeEntryActivity.shared.active @activities = TimeEntryActivity.shared.active
+8 -19
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -21,28 +21,20 @@ class CustomFieldsController < ApplicationController
before_filter :require_admin before_filter :require_admin
before_filter :build_new_custom_field, :only => [:new, :create] before_filter :build_new_custom_field, :only => [:new, :create]
before_filter :find_custom_field, :only => [:edit, :update, :destroy] before_filter :find_custom_field, :only => [:edit, :update, :destroy]
accept_api_auth :index
def index def index
respond_to do |format| @custom_fields_by_type = CustomField.find(:all).group_by {|f| f.class.name }
format.html {
@custom_fields_by_type = CustomField.all.group_by {|f| f.class.name }
@tab = params[:tab] || 'IssueCustomField' @tab = params[:tab] || 'IssueCustomField'
}
format.api {
@custom_fields = CustomField.all
}
end
end end
def new def new
end end
def create def create
if @custom_field.save if request.post? and @custom_field.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
call_hook(:controller_custom_fields_new_after_save, :params => params, :custom_field => @custom_field) call_hook(:controller_custom_fields_new_after_save, :params => params, :custom_field => @custom_field)
redirect_to custom_fields_path(:tab => @custom_field.class.name) redirect_to :action => 'index', :tab => @custom_field.class.name
else else
render :action => 'new' render :action => 'new'
end end
@@ -52,22 +44,21 @@ class CustomFieldsController < ApplicationController
end end
def update def update
if @custom_field.update_attributes(params[:custom_field]) if request.put? and @custom_field.update_attributes(params[:custom_field])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
call_hook(:controller_custom_fields_edit_after_save, :params => params, :custom_field => @custom_field) call_hook(:controller_custom_fields_edit_after_save, :params => params, :custom_field => @custom_field)
redirect_to custom_fields_path(:tab => @custom_field.class.name) redirect_to :action => 'index', :tab => @custom_field.class.name
else else
render :action => 'edit' render :action => 'edit'
end end
end end
def destroy def destroy
begin
@custom_field.destroy @custom_field.destroy
redirect_to :action => 'index', :tab => @custom_field.class.name
rescue rescue
flash[:error] = l(:error_can_not_delete_custom_field) flash[:error] = l(:error_can_not_delete_custom_field)
end redirect_to :action => 'index'
redirect_to custom_fields_path(:tab => @custom_field.class.name)
end end
private private
@@ -76,8 +67,6 @@ class CustomFieldsController < ApplicationController
@custom_field = CustomField.new_subclass_instance(params[:type], params[:custom_field]) @custom_field = CustomField.new_subclass_instance(params[:type], params[:custom_field])
if @custom_field.nil? if @custom_field.nil?
render_404 render_404
else
@custom_field.default_value = nil
end end
end end
+7 -7
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -27,7 +27,7 @@ class DocumentsController < ApplicationController
def index def index
@sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category' @sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category'
documents = @project.documents.includes(:attachments, :category).all documents = @project.documents.find :all, :include => [:attachments, :category]
case @sort_by case @sort_by
when 'date' when 'date'
@grouped = documents.group_by {|d| d.updated_on.to_date } @grouped = documents.group_by {|d| d.updated_on.to_date }
@@ -43,7 +43,7 @@ class DocumentsController < ApplicationController
end end
def show def show
@attachments = @document.attachments.all @attachments = @document.attachments.find(:all, :order => "created_on DESC")
end end
def new def new
@@ -58,7 +58,7 @@ class DocumentsController < ApplicationController
if @document.save if @document.save
render_attachment_warning_if_needed(@document) render_attachment_warning_if_needed(@document)
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to project_documents_path(@project) redirect_to :action => 'index', :project_id => @project
else else
render :action => 'new' render :action => 'new'
end end
@@ -71,7 +71,7 @@ class DocumentsController < ApplicationController
@document.safe_attributes = params[:document] @document.safe_attributes = params[:document]
if request.put? and @document.save if request.put? and @document.save
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to document_path(@document) redirect_to :action => 'show', :id => @document
else else
render :action => 'edit' render :action => 'edit'
end end
@@ -79,7 +79,7 @@ class DocumentsController < ApplicationController
def destroy def destroy
@document.destroy if request.delete? @document.destroy if request.delete?
redirect_to project_documents_path(@project) redirect_to :controller => 'documents', :action => 'index', :project_id => @project
end end
def add_attachment def add_attachment
@@ -89,6 +89,6 @@ class DocumentsController < ApplicationController
if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added') if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added')
Mailer.attachments_added(attachments[:files]).deliver Mailer.attachments_added(attachments[:files]).deliver
end end
redirect_to document_path(@document) redirect_to :action => 'show', :id => @document
end end
end end
+9 -7
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -46,7 +46,7 @@ class EnumerationsController < ApplicationController
def create def create
if request.post? && @enumeration.save if request.post? && @enumeration.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to enumerations_path redirect_to :action => 'index'
else else
render :action => 'new' render :action => 'new'
end end
@@ -58,7 +58,7 @@ class EnumerationsController < ApplicationController
def update def update
if request.put? && @enumeration.update_attributes(params[:enumeration]) if request.put? && @enumeration.update_attributes(params[:enumeration])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to enumerations_path redirect_to :action => 'index'
else else
render :action => 'edit' render :action => 'edit'
end end
@@ -68,14 +68,16 @@ class EnumerationsController < ApplicationController
if !@enumeration.in_use? if !@enumeration.in_use?
# No associated objects # No associated objects
@enumeration.destroy @enumeration.destroy
redirect_to enumerations_path redirect_to :action => 'index'
return return
elsif params[:reassign_to_id].present? && (reassign_to = @enumeration.class.find_by_id(params[:reassign_to_id].to_i)) elsif params[:reassign_to_id]
if reassign_to = @enumeration.class.find_by_id(params[:reassign_to_id])
@enumeration.destroy(reassign_to) @enumeration.destroy(reassign_to)
redirect_to enumerations_path redirect_to :action => 'index'
return return
end end
@enumerations = @enumeration.class.system.all - [@enumeration] end
@enumerations = @enumeration.class.all - [@enumeration]
end end
private private
+3 -3
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -31,8 +31,8 @@ class FilesController < ApplicationController
'size' => "#{Attachment.table_name}.filesize", 'size' => "#{Attachment.table_name}.filesize",
'downloads' => "#{Attachment.table_name}.downloads" 'downloads' => "#{Attachment.table_name}.downloads"
@containers = [ Project.includes(:attachments).reorder(sort_clause).find(@project.id)] @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
@containers += @project.versions.includes(:attachments).reorder(sort_clause).all.sort.reverse @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
render :layout => !request.xhr? render :layout => !request.xhr?
end end
+1 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+8 -9
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -84,7 +84,7 @@ class GroupsController < ApplicationController
@group.destroy @group.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to(groups_path) } format.html { redirect_to(groups_url) }
format.api { render_api_ok } format.api { render_api_ok }
end end
end end
@@ -93,7 +93,7 @@ class GroupsController < ApplicationController
@users = User.find_all_by_id(params[:user_id] || params[:user_ids]) @users = User.find_all_by_id(params[:user_id] || params[:user_ids])
@group.users << @users if request.post? @group.users << @users if request.post?
respond_to do |format| respond_to do |format|
format.html { redirect_to edit_group_path(@group, :tab => 'users') } format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' }
format.js format.js
format.api { render_api_ok } format.api { render_api_ok }
end end
@@ -102,23 +102,22 @@ class GroupsController < ApplicationController
def remove_user def remove_user
@group.users.delete(User.find(params[:user_id])) if request.delete? @group.users.delete(User.find(params[:user_id])) if request.delete?
respond_to do |format| respond_to do |format|
format.html { redirect_to edit_group_path(@group, :tab => 'users') } format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' }
format.js format.js
format.api { render_api_ok } format.api { render_api_ok }
end end
end end
def autocomplete_for_user def autocomplete_for_user
respond_to do |format| @users = User.active.not_in_group(@group).like(params[:q]).all(:limit => 100)
format.js render :layout => false
end
end end
def edit_membership def edit_membership
@membership = Member.edit_membership(params[:membership_id], params[:membership], @group) @membership = Member.edit_membership(params[:membership_id], params[:membership], @group)
@membership.save if request.post? @membership.save if request.post?
respond_to do |format| respond_to do |format|
format.html { redirect_to edit_group_path(@group, :tab => 'memberships') } format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' }
format.js format.js
end end
end end
@@ -126,7 +125,7 @@ class GroupsController < ApplicationController
def destroy_membership def destroy_membership
Member.find(params[:membership_id]).destroy if request.post? Member.find(params[:membership_id]).destroy if request.post?
respond_to do |format| respond_to do |format|
format.html { redirect_to edit_group_path(@group, :tab => 'memberships') } format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' }
format.js format.js
end end
end end
+7 -12
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -26,14 +26,14 @@ class IssueCategoriesController < ApplicationController
def index def index
respond_to do |format| respond_to do |format|
format.html { redirect_to_settings_in_projects } format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project }
format.api { @categories = @project.issue_categories.all } format.api { @categories = @project.issue_categories.all }
end end
end end
def show def show
respond_to do |format| respond_to do |format|
format.html { redirect_to_settings_in_projects } format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project }
format.api format.api
end end
end end
@@ -55,7 +55,7 @@ class IssueCategoriesController < ApplicationController
respond_to do |format| respond_to do |format|
format.html do format.html do
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to_settings_in_projects redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
end end
format.js format.js
format.api { render :action => 'show', :status => :created, :location => issue_category_path(@category) } format.api { render :action => 'show', :status => :created, :location => issue_category_path(@category) }
@@ -78,7 +78,7 @@ class IssueCategoriesController < ApplicationController
respond_to do |format| respond_to do |format|
format.html { format.html {
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to_settings_in_projects redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
} }
format.api { render_api_ok } format.api { render_api_ok }
end end
@@ -99,7 +99,7 @@ class IssueCategoriesController < ApplicationController
end end
@category.destroy(reassign_to) @category.destroy(reassign_to)
respond_to do |format| respond_to do |format|
format.html { redirect_to_settings_in_projects } format.html { redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories' }
format.api { render_api_ok } format.api { render_api_ok }
end end
return return
@@ -107,12 +107,7 @@ class IssueCategoriesController < ApplicationController
@categories = @project.issue_categories - [@category] @categories = @project.issue_categories - [@category]
end end
private private
def redirect_to_settings_in_projects
redirect_to settings_project_path(@project, :tab => 'categories')
end
# Wrap ApplicationController's find_model_object method to set # Wrap ApplicationController's find_model_object method to set
# @category instead of just @issue_category # @category instead of just @issue_category
def find_model_object def find_model_object
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -48,9 +48,9 @@ class IssueRelationsController < ApplicationController
saved = @relation.save saved = @relation.save
respond_to do |format| respond_to do |format|
format.html { redirect_to issue_path(@issue) } format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
format.js { format.js {
@relations = @issue.reload.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? } @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
} }
format.api { format.api {
if saved if saved
@@ -67,7 +67,7 @@ class IssueRelationsController < ApplicationController
@relation.destroy @relation.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to issue_path(@relation.issue_from) } format.html { redirect_to issue_path } # TODO : does this really work since @issue is always nil? What is it useful to?
format.js format.js
format.api { render_api_ok } format.api { render_api_ok }
end end
+9 -9
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -25,7 +25,7 @@ class IssueStatusesController < ApplicationController
def index def index
respond_to do |format| respond_to do |format|
format.html { format.html {
@issue_status_pages, @issue_statuses = paginate IssueStatus.sorted, :per_page => 25 @issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 25, :order => "position"
render :action => "index", :layout => false if request.xhr? render :action => "index", :layout => false if request.xhr?
} }
format.api { format.api {
@@ -40,9 +40,9 @@ class IssueStatusesController < ApplicationController
def create def create
@issue_status = IssueStatus.new(params[:issue_status]) @issue_status = IssueStatus.new(params[:issue_status])
if @issue_status.save if request.post? && @issue_status.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to issue_statuses_path redirect_to :action => 'index'
else else
render :action => 'new' render :action => 'new'
end end
@@ -54,9 +54,9 @@ class IssueStatusesController < ApplicationController
def update def update
@issue_status = IssueStatus.find(params[:id]) @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) flash[:notice] = l(:notice_successful_update)
redirect_to issue_statuses_path redirect_to :action => 'index'
else else
render :action => 'edit' render :action => 'edit'
end end
@@ -64,10 +64,10 @@ class IssueStatusesController < ApplicationController
def destroy def destroy
IssueStatus.find(params[:id]).destroy IssueStatus.find(params[:id]).destroy
redirect_to issue_statuses_path redirect_to :action => 'index'
rescue rescue
flash[:error] = l(:error_unable_delete_issue_status) flash[:error] = l(:error_unable_delete_issue_status)
redirect_to issue_statuses_path redirect_to :action => 'index'
end end
def update_issue_done_ratio def update_issue_done_ratio
@@ -76,6 +76,6 @@ class IssueStatusesController < ApplicationController
else else
flash[:error] = l(:error_issue_done_ratios_not_updated) flash[:error] = l(:error_issue_done_ratios_not_updated)
end end
redirect_to issue_statuses_path redirect_to :action => 'index'
end end
end end
+30 -76
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -21,11 +21,11 @@ class IssuesController < ApplicationController
before_filter :find_issue, :only => [:show, :edit, :update] before_filter :find_issue, :only => [:show, :edit, :update]
before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy] before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
before_filter :find_project, :only => [:new, :create, :update_form] before_filter :find_project, :only => [:new, :create]
before_filter :authorize, :except => [:index] before_filter :authorize, :except => [:index]
before_filter :find_optional_project, :only => [:index] before_filter :find_optional_project, :only => [:index]
before_filter :check_for_default_issue_status, :only => [:new, :create] before_filter :check_for_default_issue_status, :only => [:new, :create]
before_filter :build_new_issue_from_params, :only => [:new, :create, :update_form] before_filter :build_new_issue_from_params, :only => [:new, :create]
accept_rss_auth :index, :show accept_rss_auth :index, :show
accept_api_auth :index, :show, :create, :update, :destroy accept_api_auth :index, :show, :create, :update, :destroy
@@ -71,8 +71,8 @@ class IssuesController < ApplicationController
end end
@issue_count = @query.issue_count @issue_count = @query.issue_count
@issue_pages = Paginator.new @issue_count, @limit, params['page'] @issue_pages = Paginator.new self, @issue_count, @limit, params['page']
@offset ||= @issue_pages.offset @offset ||= @issue_pages.current.offset
@issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version], @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
:order => sort_clause, :order => sort_clause,
:offset => @offset, :offset => @offset,
@@ -85,8 +85,8 @@ class IssuesController < ApplicationController
Issue.load_visible_relations(@issues) if include_in_api_response?('relations') Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
} }
format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") } format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
format.csv { send_data(query_to_csv(@issues, @query, params), :type => 'text/csv; header=present', :filename => 'issues.csv') } format.csv { send_data(issues_to_csv(@issues, @project, @query, params), :type => 'text/csv; header=present', :filename => 'export.csv') }
format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'issues.pdf') } format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
end end
else else
respond_to do |format| respond_to do |format|
@@ -103,9 +103,6 @@ class IssuesController < ApplicationController
@journals = @issue.journals.includes(:user, :details).reorder("#{Journal.table_name}.id ASC").all @journals = @issue.journals.includes(:user, :details).reorder("#{Journal.table_name}.id ASC").all
@journals.each_with_index {|j,i| j.indice = i+1} @journals.each_with_index {|j,i| j.indice = i+1}
@journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project) @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? @journals.reverse! if User.current.wants_comments_in_reverse_order?
@changesets = @issue.changesets.visible.all @changesets = @issue.changesets.visible.all
@@ -116,8 +113,6 @@ class IssuesController < ApplicationController
@edit_allowed = User.current.allowed_to?(:edit_issues, @project) @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
@priorities = IssuePriority.active @priorities = IssuePriority.active
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
@relation = IssueRelation.new
respond_to do |format| respond_to do |format|
format.html { format.html {
retrieve_previous_and_next_issue_ids retrieve_previous_and_next_issue_ids
@@ -137,6 +132,7 @@ class IssuesController < ApplicationController
def new def new
respond_to do |format| respond_to do |format|
format.html { render :action => 'new', :layout => !request.xhr? } format.html { render :action => 'new', :layout => !request.xhr? }
format.js { render :partial => 'update_form' }
end end
end end
@@ -149,12 +145,8 @@ class IssuesController < ApplicationController
format.html { format.html {
render_attachment_warning_if_needed(@issue) render_attachment_warning_if_needed(@issue)
flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject)) flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject))
if params[:continue] redirect_to(params[:continue] ? { :action => 'new', :project_id => @issue.project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} { :action => 'show', :id => @issue })
redirect_to new_project_issue_path(@issue.project, :issue => attrs)
else
redirect_to issue_path(@issue)
end
} }
format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) } format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
end end
@@ -181,7 +173,7 @@ class IssuesController < ApplicationController
@issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads])) @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
saved = false saved = false
begin begin
saved = save_issue_with_child_records saved = @issue.save_issue_with_child_records(params, @time_entry)
rescue ActiveRecord::StaleObjectError rescue ActiveRecord::StaleObjectError
@conflict = true @conflict = true
if params[:last_journal_id] if params[:last_journal_id]
@@ -195,7 +187,7 @@ class IssuesController < ApplicationController
flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record? flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
respond_to do |format| respond_to do |format|
format.html { redirect_back_or_default issue_path(@issue) } format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
format.api { render_api_ok } format.api { render_api_ok }
end end
else else
@@ -206,11 +198,6 @@ class IssuesController < ApplicationController
end end
end end
# Updates the issue form when changing the project, status or tracker
# on issue creation/update
def update_form
end
# Bulk edit/copy a set of issues # Bulk edit/copy a set of issues
def bulk_edit def bulk_edit
@issues.sort! @issues.sort!
@@ -233,7 +220,7 @@ class IssuesController < ApplicationController
else else
@available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&) @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
end 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(:&) @assignables = target_projects.map(&:assignable_users).reduce(:&)
@trackers = target_projects.map(&:trackers).reduce(:&) @trackers = target_projects.map(&:trackers).reduce(:&)
@versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&) @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
@@ -244,9 +231,7 @@ class IssuesController < ApplicationController
end end
@safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&) @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
render :layout => false if request.xhr?
@issue_params = params[:issue] || {}
@issue_params[:custom_field_values] ||= {}
end end
def bulk_update def bulk_update
@@ -254,8 +239,8 @@ class IssuesController < ApplicationController
@copy = params[:copy].present? @copy = params[:copy].present?
attributes = parse_params_for_bulk_issue_attributes(params) attributes = parse_params_for_bulk_issue_attributes(params)
unsaved_issues = [] unsaved_issue_ids = []
saved_issues = [] moved_issues = []
if @copy && params[:copy_subtasks].present? if @copy && params[:copy_subtasks].present?
# Descendant issues will be copied with the parent task # Descendant issues will be copied with the parent task
@@ -263,48 +248,39 @@ class IssuesController < ApplicationController
@issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}} @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
end end
@issues.each do |orig_issue| @issues.each do |issue|
orig_issue.reload issue.reload
if @copy if @copy
issue = orig_issue.copy({}, issue = issue.copy({},
:attachments => params[:copy_attachments].present?, :attachments => params[:copy_attachments].present?,
:subtasks => params[:copy_subtasks].present? :subtasks => params[:copy_subtasks].present?
) )
else
issue = orig_issue
end end
journal = issue.init_journal(User.current, params[:notes]) journal = issue.init_journal(User.current, params[:notes])
issue.safe_attributes = attributes issue.safe_attributes = attributes
call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue }) call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
if issue.save if issue.save
saved_issues << issue moved_issues << issue
else else
unsaved_issues << orig_issue # Keep unsaved issue ids to display them in flash error
unsaved_issue_ids << issue.id
end end
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 params[:follow]
if @issues.size == 1 && saved_issues.size == 1 if @issues.size == 1 && moved_issues.size == 1
redirect_to issue_path(saved_issues.first) redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
elsif saved_issues.map(&:project).uniq.size == 1 elsif moved_issues.map(&:project).uniq.size == 1
redirect_to project_issues_path(saved_issues.map(&:project).first) redirect_to :controller => 'issues', :action => 'index', :project_id => moved_issues.map(&:project).first
end end
else else
redirect_back_or_default _project_issues_path(@project) redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
end
else
@saved_issues = @issues
@unsaved_issues = unsaved_issues
@issues = Issue.visible.find_all_by_id(@unsaved_issues.map(&:id))
bulk_edit
render :action => 'bulk_edit'
end end
end end
def destroy 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 if @hours > 0
case params[:todo] case params[:todo]
when 'destroy' when 'destroy'
@@ -332,7 +308,7 @@ class IssuesController < ApplicationController
end end
end end
respond_to do |format| respond_to do |format|
format.html { redirect_back_or_default _project_issues_path(@project) } format.html { redirect_back_or_default(:action => 'index', :project_id => @project) }
format.api { render_api_ok } format.api { render_api_ok }
end end
end end
@@ -452,26 +428,4 @@ class IssuesController < ApplicationController
end end
attributes attributes
end 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 end
+2 -2
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -80,7 +80,7 @@ class JournalsController < ApplicationController
@journal.destroy if @journal.details.empty? && @journal.notes.blank? @journal.destroy if @journal.details.empty? && @journal.notes.blank?
call_hook(:controller_journals_edit_post, { :journal => @journal, :params => params}) call_hook(:controller_journals_edit_post, { :journal => @journal, :params => params})
respond_to do |format| respond_to do |format|
format.html { redirect_to issue_path(@journal.journalized) } format.html { redirect_to :controller => 'issues', :action => 'show', :id => @journal.journalized_id }
format.js { render :action => 'update' } format.js { render :action => 'update' }
end end
else else
+1 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+8 -15
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -26,8 +26,8 @@ class MembersController < ApplicationController
def index def index
@offset, @limit = api_offset_and_limit @offset, @limit = api_offset_and_limit
@member_count = @project.member_principals.count @member_count = @project.member_principals.count
@member_pages = Paginator.new @member_count, @limit, params['page'] @member_pages = Paginator.new self, @member_count, @limit, params['page']
@offset ||= @member_pages.offset @offset ||= @member_pages.current.offset
@members = @project.member_principals.all( @members = @project.member_principals.all(
:order => "#{Member.table_name}.id", :order => "#{Member.table_name}.id",
:limit => @limit, :limit => @limit,
@@ -63,7 +63,7 @@ class MembersController < ApplicationController
end end
respond_to do |format| respond_to do |format|
format.html { redirect_to_settings_in_projects } format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
format.js { @members = members } format.js { @members = members }
format.api { format.api {
@member = members.first @member = members.first
@@ -82,7 +82,7 @@ class MembersController < ApplicationController
end end
saved = @member.save saved = @member.save
respond_to do |format| respond_to do |format|
format.html { redirect_to_settings_in_projects } format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
format.js format.js
format.api { format.api {
if saved if saved
@@ -99,7 +99,7 @@ class MembersController < ApplicationController
@member.destroy @member.destroy
end end
respond_to do |format| respond_to do |format|
format.html { redirect_to_settings_in_projects } format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
format.js format.js
format.api { format.api {
if @member.destroyed? if @member.destroyed?
@@ -112,14 +112,7 @@ class MembersController < ApplicationController
end end
def autocomplete def autocomplete
respond_to do |format| @principals = Principal.active.not_member_of(@project).like(params[:q]).all(:limit => 100)
format.js render :layout => false
end
end
private
def redirect_to_settings_in_projects
redirect_to settings_project_path(@project, :tab => 'members')
end end
end end
+9 -12
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -19,7 +19,6 @@ class MessagesController < ApplicationController
menu_item :boards menu_item :boards
default_search_scope :messages default_search_scope :messages
before_filter :find_board, :only => [:new, :preview] before_filter :find_board, :only => [:new, :preview]
before_filter :find_attachments, :only => [:preview]
before_filter :find_message, :except => [:new, :preview] before_filter :find_message, :except => [:new, :preview]
before_filter :authorize, :except => [:preview, :edit, :destroy] before_filter :authorize, :except => [:preview, :edit, :destroy]
@@ -35,18 +34,16 @@ class MessagesController < ApplicationController
page = params[:page] page = params[:page]
# Find the page of the requested reply # Find the page of the requested reply
if params[:r] && page.nil? 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 page = 1 + offset / REPLIES_PER_PAGE
end end
@reply_count = @topic.children.count @reply_count = @topic.children.count
@reply_pages = Paginator.new @reply_count, REPLIES_PER_PAGE, page @reply_pages = Paginator.new self, @reply_count, REPLIES_PER_PAGE, page
@replies = @topic.children. @replies = @topic.children.find(:all, :include => [:author, :attachments, {:board => :project}],
includes(:author, :attachments, {:board => :project}). :order => "#{Message.table_name}.created_on ASC",
reorder("#{Message.table_name}.created_on ASC"). :limit => @reply_pages.items_per_page,
limit(@reply_pages.per_page). :offset => @reply_pages.current.offset)
offset(@reply_pages.offset).
all
@reply = Message.new(:subject => "RE: #{@message.subject}") @reply = Message.new(:subject => "RE: #{@message.subject}")
render :action => "show", :layout => false if request.xhr? render :action => "show", :layout => false if request.xhr?
@@ -118,6 +115,7 @@ class MessagesController < ApplicationController
def preview def preview
message = @board.messages.find_by_id(params[:id]) message = @board.messages.find_by_id(params[:id])
@attachements = message.attachments if message
@text = (params[:message] || params[:reply])[:content] @text = (params[:message] || params[:reply])[:content]
@previewed = message @previewed = message
render :partial => 'common/preview' render :partial => 'common/preview'
@@ -125,7 +123,7 @@ class MessagesController < ApplicationController
private private
def find_message def find_message
return unless find_board find_board
@message = @board.messages.find(params[:id], :include => :parent) @message = @board.messages.find(params[:id], :include => :parent)
@topic = @message.root @topic = @message.root
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
@@ -137,6 +135,5 @@ private
@project = @board.project @project = @board.project
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
nil
end end
end end
+15 -19
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -17,8 +17,6 @@
class MyController < ApplicationController class MyController < ApplicationController
before_filter :require_login 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 :issues
helper :users helper :users
@@ -55,11 +53,13 @@ class MyController < ApplicationController
if request.post? if request.post?
@user.safe_attributes = params[:user] @user.safe_attributes = params[:user]
@user.pref.attributes = params[:pref] @user.pref.attributes = params[:pref]
@user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
if @user.save if @user.save
@user.pref.save @user.pref.save
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
set_language_if_valid @user.language set_language_if_valid @user.language
flash[:notice] = l(:notice_account_updated) flash[:notice] = l(:notice_account_updated)
redirect_to my_account_path redirect_to :action => 'account'
return return
end end
end end
@@ -69,7 +69,7 @@ class MyController < ApplicationController
def destroy def destroy
@user = User.current @user = User.current
unless @user.own_account_deletable? unless @user.own_account_deletable?
redirect_to my_account_path redirect_to :action => 'account'
return return
end end
@@ -88,21 +88,18 @@ class MyController < ApplicationController
@user = User.current @user = User.current
unless @user.change_password_allowed? unless @user.change_password_allowed?
flash[:error] = l(:notice_can_t_change_password) flash[:error] = l(:notice_can_t_change_password)
redirect_to my_account_path redirect_to :action => 'account'
return return
end end
if request.post? if request.post?
if !@user.check_password?(params[:password]) 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
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation] @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
@user.must_change_passwd = false
if @user.save if @user.save
flash[:notice] = l(:notice_account_password_updated) flash[:notice] = l(:notice_account_password_updated)
redirect_to my_account_path redirect_to :action => 'account'
end end
else
flash[:error] = l(:notice_account_wrong_password)
end end
end end
end end
@@ -117,7 +114,7 @@ class MyController < ApplicationController
User.current.rss_key User.current.rss_key
flash[:notice] = l(:notice_feeds_access_key_reseted) flash[:notice] = l(:notice_feeds_access_key_reseted)
end end
redirect_to my_account_path redirect_to :action => 'account'
end end
# Create a new API key # Create a new API key
@@ -130,7 +127,7 @@ class MyController < ApplicationController
User.current.api_key User.current.api_key
flash[:notice] = l(:notice_api_access_key_reseted) flash[:notice] = l(:notice_api_access_key_reseted)
end end
redirect_to my_account_path redirect_to :action => 'account'
end end
# User's page layout configuration # User's page layout configuration
@@ -150,7 +147,7 @@ class MyController < ApplicationController
# params[:block] : id of the block to add # params[:block] : id of the block to add
def add_block def add_block
block = params[:block].to_s.underscore block = params[:block].to_s.underscore
if block.present? && BLOCKS.key?(block) (render :nothing => true; return) unless block && (BLOCKS.keys.include? block)
@user = User.current @user = User.current
layout = @user.pref[:my_page_layout] || {} layout = @user.pref[:my_page_layout] || {}
# remove if already present in a group # remove if already present in a group
@@ -159,8 +156,7 @@ class MyController < ApplicationController
layout['top'].unshift block layout['top'].unshift block
@user.pref[:my_page_layout] = layout @user.pref[:my_page_layout] = layout
@user.pref.save @user.pref.save
end redirect_to :action => 'page_layout'
redirect_to my_page_layout_path
end end
# Remove a block to user's page # Remove a block to user's page
@@ -173,7 +169,7 @@ class MyController < ApplicationController
%w(top left right).each {|f| (layout[f] ||= []).delete block } %w(top left right).each {|f| (layout[f] ||= []).delete block }
@user.pref[:my_page_layout] = layout @user.pref[:my_page_layout] = layout
@user.pref.save @user.pref.save
redirect_to my_page_layout_path redirect_to :action => 'page_layout'
end end
# Change blocks order on user's page # Change blocks order on user's page
+6 -6
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -40,8 +40,8 @@ class NewsController < ApplicationController
scope = @project ? @project.news.visible : News.visible scope = @project ? @project.news.visible : News.visible
@news_count = scope.count @news_count = scope.count
@news_pages = Paginator.new @news_count, @limit, params['page'] @news_pages = Paginator.new self, @news_count, @limit, params['page']
@offset ||= @news_pages.offset @offset ||= @news_pages.current.offset
@newss = scope.all(:include => [:author, :project], @newss = scope.all(:include => [:author, :project],
:order => "#{News.table_name}.created_on DESC", :order => "#{News.table_name}.created_on DESC",
:offset => @offset, :offset => @offset,
@@ -73,7 +73,7 @@ class NewsController < ApplicationController
if @news.save if @news.save
render_attachment_warning_if_needed(@news) render_attachment_warning_if_needed(@news)
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to project_news_index_path(@project) redirect_to :controller => 'news', :action => 'index', :project_id => @project
else else
render :action => 'new' render :action => 'new'
end end
@@ -88,7 +88,7 @@ class NewsController < ApplicationController
if @news.save if @news.save
render_attachment_warning_if_needed(@news) render_attachment_warning_if_needed(@news)
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to news_path(@news) redirect_to :action => 'show', :id => @news
else else
render :action => 'edit' render :action => 'edit'
end end
@@ -96,7 +96,7 @@ class NewsController < ApplicationController
def destroy def destroy
@news.destroy @news.destroy
redirect_to project_news_index_path(@project) redirect_to :action => 'index', :project_id => @project
end end
private private
+4 -2
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -16,11 +16,12 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class PreviewsController < ApplicationController class PreviewsController < ApplicationController
before_filter :find_project, :find_attachments before_filter :find_project
def issue def issue
@issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank? @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
if @issue if @issue
@attachements = @issue.attachments
@description = params[:issue] && params[:issue][:description] @description = params[:issue] && params[:issue][:description]
if @description && @description.gsub(/(\r?\n|\n\r?)/, "\n") == @issue.description.to_s.gsub(/(\r?\n|\n\r?)/, "\n") if @description && @description.gsub(/(\r?\n|\n\r?)/, "\n") == @issue.description.to_s.gsub(/(\r?\n|\n\r?)/, "\n")
@description = nil @description = nil
@@ -36,6 +37,7 @@ class PreviewsController < ApplicationController
def news def news
if params[:id].present? && news = News.visible.find_by_id(params[:id]) if params[:id].present? && news = News.visible.find_by_id(params[:id])
@previewed = news @previewed = news
@attachments = news.attachments
end end
@text = (params[:news] ? params[:news][:description] : nil) @text = (params[:news] ? params[:news][:description] : nil)
render :partial => 'common/preview' render :partial => 'common/preview'
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -29,7 +29,7 @@ class ProjectEnumerationsController < ApplicationController
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
end end
redirect_to settings_project_path(@project, :tab => 'activities') redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
end end
def destroy def destroy
@@ -37,6 +37,7 @@ class ProjectEnumerationsController < ApplicationController
time_entry_activity.destroy(time_entry_activity.parent) time_entry_activity.destroy(time_entry_activity.parent)
end end
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to settings_project_path(@project, :tab => 'activities') redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
end end
end end
+28 -26
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -43,7 +43,6 @@ class ProjectsController < ApplicationController
helper :repositories helper :repositories
include RepositoriesHelper include RepositoriesHelper
include ProjectsHelper include ProjectsHelper
helper :members
# Lists visible projects # Lists visible projects
def index def index
@@ -58,31 +57,32 @@ class ProjectsController < ApplicationController
format.api { format.api {
@offset, @limit = api_offset_and_limit @offset, @limit = api_offset_and_limit
@project_count = Project.visible.count @project_count = Project.visible.count
@projects = Project.visible.offset(@offset).limit(@limit).order('lft').all @projects = Project.visible.all(:offset => @offset, :limit => @limit, :order => 'lft')
} }
format.atom { format.atom {
projects = Project.visible.order('created_on DESC').limit(Setting.feeds_limit.to_i).all projects = Project.visible.find(:all, :order => 'created_on DESC',
:limit => Setting.feeds_limit.to_i)
render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}") render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
} }
end end
end end
def new def new
@issue_custom_fields = IssueCustomField.sorted.all @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.sorted.all @trackers = Tracker.sorted.all
@project = Project.new @project = Project.new
@project.safe_attributes = params[:project] @project.safe_attributes = params[:project]
end end
def create def create
@issue_custom_fields = IssueCustomField.sorted.all @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.sorted.all @trackers = Tracker.sorted.all
@project = Project.new @project = Project.new
@project.safe_attributes = params[:project] @project.safe_attributes = params[:project]
if validate_parent_id && @project.save if validate_parent_id && @project.save
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') @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? unless User.current.admin?
r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first 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]) m = Member.new(:user => User.current, :roles => [r])
@@ -91,12 +91,10 @@ class ProjectsController < ApplicationController
respond_to do |format| respond_to do |format|
format.html { format.html {
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
if params[:continue] redirect_to(params[:continue] ?
attrs = {:parent_id => @project.parent_id}.reject {|k,v| v.nil?} {:controller => 'projects', :action => 'new', :project => {:parent_id => @project.parent_id}.reject {|k,v| v.nil?}} :
redirect_to new_project_path(attrs) {:controller => 'projects', :action => 'settings', :id => @project}
else )
redirect_to settings_project_path(@project)
end
} }
format.api { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) } format.api { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
end end
@@ -106,11 +104,15 @@ class ProjectsController < ApplicationController
format.api { render_validation_errors(@project) } format.api { render_validation_errors(@project) }
end end
end end
end end
def copy def copy
@issue_custom_fields = IssueCustomField.sorted.all @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.sorted.all @trackers = Tracker.sorted.all
@root_projects = Project.find(:all,
:conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
:order => 'name')
@source_project = Project.find(params[:id]) @source_project = Project.find(params[:id])
if request.get? if request.get?
@project = Project.copy_from(@source_project) @project = Project.copy_from(@source_project)
@@ -122,13 +124,13 @@ class ProjectsController < ApplicationController
if validate_parent_id && @project.copy(@source_project, :only => params[:only]) if validate_parent_id && @project.copy(@source_project, :only => params[:only])
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to settings_project_path(@project) redirect_to :controller => 'projects', :action => 'settings', :id => @project
elsif !@project.new_record? elsif !@project.new_record?
# Project was created # Project was created
# But some objects were not copied due to validation failures # But some objects were not copied due to validation failures
# (eg. issues from disabled trackers) # (eg. issues from disabled trackers)
# TODO: inform about that # TODO: inform about that
redirect_to settings_project_path(@project) redirect_to :controller => 'projects', :action => 'settings', :id => @project
end end
end end
end end
@@ -139,14 +141,14 @@ class ProjectsController < ApplicationController
# Show @project # Show @project
def show def show
if params[:jump]
# try to redirect to the requested menu item # try to redirect to the requested menu item
if params[:jump] && redirect_to_project_menu_item(@project, params[:jump]) redirect_to_project_menu_item(@project, params[:jump]) && return
return
end end
@users_by_role = @project.users_by_role @users_by_role = @project.users_by_role
@subprojects = @project.children.visible.all @subprojects = @project.children.visible.all
@news = @project.news.limit(5).includes(:author, :project).reorder("#{News.table_name}.created_on DESC").all @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
@trackers = @project.rolled_up_trackers @trackers = @project.rolled_up_trackers
cond = @project.project_condition(Setting.display_subprojects_issues?) cond = @project.project_condition(Setting.display_subprojects_issues?)
@@ -155,7 +157,7 @@ class ProjectsController < ApplicationController
@total_issues_by_tracker = Issue.visible.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) 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 end
@key = User.current.rss_key @key = User.current.rss_key
@@ -167,7 +169,7 @@ class ProjectsController < ApplicationController
end end
def settings def settings
@issue_custom_fields = IssueCustomField.sorted.all @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@issue_category ||= IssueCategory.new @issue_category ||= IssueCategory.new
@member ||= @project.members.new @member ||= @project.members.new
@trackers = Tracker.sorted.all @trackers = Tracker.sorted.all
@@ -184,7 +186,7 @@ class ProjectsController < ApplicationController
respond_to do |format| respond_to do |format|
format.html { format.html {
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to settings_project_path(@project) redirect_to :action => 'settings', :id => @project
} }
format.api { render_api_ok } format.api { render_api_ok }
end end
@@ -202,7 +204,7 @@ class ProjectsController < ApplicationController
def modules def modules
@project.enabled_module_names = params[:enabled_module_names] @project.enabled_module_names = params[:enabled_module_names]
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to settings_project_path(@project, :tab => 'modules') redirect_to :action => 'settings', :id => @project, :tab => 'modules'
end end
def archive def archive
@@ -211,12 +213,12 @@ class ProjectsController < ApplicationController
flash[:error] = l(:error_can_not_archive_project) flash[:error] = l(:error_can_not_archive_project)
end end
end end
redirect_to admin_projects_path(:status => params[:status]) redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
end end
def unarchive def unarchive
@project.unarchive if request.post? && !@project.active? @project.unarchive if request.post? && !@project.active?
redirect_to admin_projects_path(:status => params[:status]) redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
end end
def close def close
@@ -235,7 +237,7 @@ class ProjectsController < ApplicationController
if api_request? || params[:confirm] if api_request? || params[:confirm]
@project_to_destroy.destroy @project_to_destroy.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to admin_projects_path } format.html { redirect_to :controller => 'admin', :action => 'projects' }
format.api { render_api_ok } format.api { render_api_ok }
end end
end end
+17 -28
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -32,34 +32,35 @@ class QueriesController < ApplicationController
@limit = per_page_option @limit = per_page_option
end end
@query_count = IssueQuery.visible.count @query_count = Query.visible.count
@query_pages = Paginator.new @query_count, @limit, params['page'] @query_pages = Paginator.new self, @query_count, @limit, params['page']
@queries = IssueQuery.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name") @queries = Query.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name")
respond_to do |format| respond_to do |format|
format.html { render :nothing => true }
format.api format.api
end end
end end
def new def new
@query = IssueQuery.new @query = Query.new
@query.user = User.current @query.user = User.current
@query.project = @project @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) build_query_from_params
end end
def create def create
@query = IssueQuery.new(params[:query]) @query = Query.new(params[:query])
@query.user = User.current @query.user = User.current
@query.project = params[:query_is_for_all] ? nil : @project @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) build_query_from_params
@query.column_names = nil if params[:default_columns] @query.column_names = nil if params[:default_columns]
if @query.save if @query.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to_issues(:query_id => @query) redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
else else
render :action => 'new', :layout => !request.xhr? render :action => 'new', :layout => !request.xhr?
end end
@@ -71,13 +72,13 @@ class QueriesController < ApplicationController
def update def update
@query.attributes = params[:query] @query.attributes = params[:query]
@query.project = nil if params[:query_is_for_all] @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) build_query_from_params
@query.column_names = nil if params[:default_columns] @query.column_names = nil if params[:default_columns]
if @query.save if @query.save
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to_issues(:query_id => @query) redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
else else
render :action => 'edit' render :action => 'edit'
end end
@@ -85,12 +86,12 @@ class QueriesController < ApplicationController
def destroy def destroy
@query.destroy @query.destroy
redirect_to_issues(:set_filter => 1) redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1
end end
private private
def find_query def find_query
@query = IssueQuery.find(params[:id]) @query = Query.find(params[:id])
@project = @query.project @project = @query.project
render_403 unless @query.editable_by?(User.current) render_403 unless @query.editable_by?(User.current)
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
@@ -103,16 +104,4 @@ private
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end 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 end
+2 -2
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -90,6 +90,6 @@ class ReportsController < ApplicationController
private private
def find_issue_statuses def find_issue_statuses
@statuses = IssueStatus.sorted.all @statuses = IssueStatus.find(:all, :order => 'position')
end end
end end
+16 -20
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -111,7 +111,7 @@ class RepositoriesController < ApplicationController
end end
def show 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) @entries = @repository.entries(@path, @rev)
@changeset = @repository.find_changeset_by_name(@rev) @changeset = @repository.find_changeset_by_name(@rev)
@@ -138,14 +138,13 @@ class RepositoriesController < ApplicationController
def revisions def revisions
@changeset_count = @repository.changesets.count @changeset_count = @repository.changesets.count
@changeset_pages = Paginator.new @changeset_count, @changeset_pages = Paginator.new self, @changeset_count,
per_page_option, per_page_option,
params['page'] params['page']
@changesets = @repository.changesets. @changesets = @repository.changesets.find(:all,
limit(@changeset_pages.per_page). :limit => @changeset_pages.items_per_page,
offset(@changeset_pages.offset). :offset => @changeset_pages.current.offset,
includes(:user, :repository, :parents). :include => [:user, :repository, :parents])
all
respond_to do |format| respond_to do |format|
format.html { render :layout => false if request.xhr? } format.html { render :layout => false if request.xhr? }
@@ -352,18 +351,15 @@ class RepositoriesController < ApplicationController
@date_to = Date.today @date_to = Date.today
@date_from = @date_to << 11 @date_from = @date_to << 11
@date_from = Date.civil(@date_from.year, @date_from.month, 1) @date_from = Date.civil(@date_from.year, @date_from.month, 1)
commits_by_day = Changeset. commits_by_day = Changeset.count(
where("repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to). :all, :group => :commit_date,
group(:commit_date). :conditions => ["repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
count
commits_by_month = [0] * 12 commits_by_month = [0] * 12
commits_by_day.each {|c| commits_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last } commits_by_day.each {|c| commits_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
changes_by_day = Change. changes_by_day = Change.count(
joins(:changeset). :all, :group => :commit_date, :include => :changeset,
where("#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to). :conditions => ["#{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_month = [0] * 12 changes_by_month = [0] * 12
changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last } changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
@@ -396,10 +392,10 @@ class RepositoriesController < ApplicationController
end end
def graph_commits_per_author(repository) 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} 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} h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
fields = commits_by_author.collect {|r| r.first} fields = commits_by_author.collect {|r| r.first}
@@ -414,7 +410,7 @@ class RepositoriesController < ApplicationController
fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') } fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
graph = SVG::Graph::BarHorizontal.new( graph = SVG::Graph::BarHorizontal.new(
:height => 30 * commits_data.length, :height => 400,
:width => 800, :width => 800,
:fields => fields, :fields => fields,
:stack => :side, :stack => :side,
+7 -7
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -26,7 +26,7 @@ class RolesController < ApplicationController
def index def index
respond_to do |format| respond_to do |format|
format.html { format.html {
@role_pages, @roles = paginate Role.sorted, :per_page => 25 @role_pages, @roles = paginate :roles, :per_page => 25, :order => 'builtin, position'
render :action => "index", :layout => false if request.xhr? render :action => "index", :layout => false if request.xhr?
} }
format.api { format.api {
@@ -58,7 +58,7 @@ class RolesController < ApplicationController
@role.workflow_rules.copy(copy_from) @role.workflow_rules.copy(copy_from)
end end
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to roles_path redirect_to :action => 'index'
else else
@roles = Role.sorted.all @roles = Role.sorted.all
render :action => 'new' render :action => 'new'
@@ -71,7 +71,7 @@ class RolesController < ApplicationController
def update def update
if request.put? and @role.update_attributes(params[:role]) if request.put? and @role.update_attributes(params[:role])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to roles_path redirect_to :action => 'index'
else else
render :action => 'edit' render :action => 'edit'
end end
@@ -79,10 +79,10 @@ class RolesController < ApplicationController
def destroy def destroy
@role.destroy @role.destroy
redirect_to roles_path redirect_to :action => 'index'
rescue rescue
flash[:error] = l(:error_can_not_remove_role) flash[:error] = l(:error_can_not_remove_role)
redirect_to roles_path redirect_to :action => 'index'
end end
def permissions def permissions
@@ -94,7 +94,7 @@ class RolesController < ApplicationController
role.save role.save
end end
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to roles_path redirect_to :action => 'index'
end end
end end
+6 -3
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,6 +18,9 @@
class SearchController < ApplicationController class SearchController < ApplicationController
before_filter :find_optional_project before_filter :find_optional_project
helper :messages
include MessagesHelper
def index def index
@question = params[:q] || "" @question = params[:q] || ""
@question.strip! @question.strip!
@@ -40,8 +43,8 @@ class SearchController < ApplicationController
begin; offset = params[:offset].to_time if params[:offset]; rescue; end begin; offset = params[:offset].to_time if params[:offset]; rescue; end
# quick jump to an issue # quick jump to an issue
if (m = @question.match(/^#?(\d+)$/)) && (issue = Issue.visible.find_by_id(m[1].to_i)) if @question.match(/^#?(\d+)$/) && Issue.visible.find_by_id($1.to_i)
redirect_to issue_path(issue) redirect_to :controller => "issues", :action => "show", :id => $1
return return
end end
+6 -14
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -19,8 +19,6 @@ class SettingsController < ApplicationController
layout 'admin' layout 'admin'
menu_item :plugins, :only => :plugin menu_item :plugins, :only => :plugin
helper :queries
before_filter :require_admin before_filter :require_admin
def index def index
@@ -33,10 +31,12 @@ class SettingsController < ApplicationController
if request.post? && params[:settings] && params[:settings].is_a?(Hash) if request.post? && params[:settings] && params[:settings].is_a?(Hash)
settings = (params[:settings] || {}).dup.symbolize_keys settings = (params[:settings] || {}).dup.symbolize_keys
settings.each do |name, value| 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 end
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to settings_path(:tab => params[:tab]) redirect_to :action => 'edit', :tab => params[:tab]
else else
@options = {} @options = {}
user_format = User::USER_FORMATS.collect{|key, value| [key, value[:setting_order]]}.sort{|a, b| a[1] <=> b[1]} user_format = User::USER_FORMATS.collect{|key, value| [key, value[:setting_order]]}.sort{|a, b| a[1] <=> b[1]}
@@ -46,24 +46,16 @@ class SettingsController < ApplicationController
@guessed_host_and_path = request.host_with_port.dup @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? @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 Redmine::Themes.rescan
end end
end end
def plugin def plugin
@plugin = Redmine::Plugin.find(params[:id]) @plugin = Redmine::Plugin.find(params[:id])
unless @plugin.configurable?
render_404
return
end
if request.post? if request.post?
Setting.send "plugin_#{@plugin.id}=", params[:settings] Setting.send "plugin_#{@plugin.id}=", params[:settings]
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to plugin_settings_path(@plugin) redirect_to :action => 'plugin', :id => @plugin.id
else else
@partial = @plugin.settings[:partial] @partial = @plugin.settings[:partial]
@settings = Setting.send "plugin_#{@plugin.id}" @settings = Setting.send "plugin_#{@plugin.id}"
+6 -2
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -19,7 +19,11 @@ class SysController < ActionController::Base
before_filter :check_enabled before_filter :check_enabled
def projects 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 # extra_info attribute from repository breaks activeresource client
render :xml => p.to_xml( render :xml => p.to_xml(
:only => [:id, :identifier, :name, :is_public, :status], :only => [:id, :identifier, :name, :is_public, :status],
+92 -42
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -30,31 +30,42 @@ class TimelogController < ApplicationController
accept_rss_auth :index accept_rss_auth :index
accept_api_auth :index, :show, :create, :update, :destroy accept_api_auth :index, :show, :create, :update, :destroy
rescue_from Query::StatementInvalid, :with => :query_statement_invalid
helper :sort helper :sort
include SortHelper include SortHelper
helper :issues helper :issues
include TimelogHelper include TimelogHelper
helper :custom_fields helper :custom_fields
include CustomFieldsHelper include CustomFieldsHelper
helper :queries
include QueriesHelper
def index def index
@query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_') sort_init 'spent_on', 'desc'
sort_update 'spent_on' => ['spent_on', "#{TimeEntry.table_name}.created_on"],
'user' => 'user_id',
'activity' => 'activity_id',
'project' => "#{Project.table_name}.name",
'issue' => 'issue_id',
'hours' => 'hours'
sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria) retrieve_date_range
sort_update(@query.sortable_columns)
scope = time_entry_scope(:order => sort_clause). scope = TimeEntry.visible.spent_between(@from, @to)
includes(:project, :activity, :user, :issue). if @issue
preload(:issue => [:project, :tracker, :status, :assigned_to, :priority]) scope = scope.on_issue(@issue)
elsif @project
scope = scope.on_project(@project, Setting.display_subprojects_issues?)
end
respond_to do |format| respond_to do |format|
format.html { format.html {
# Paginate results
@entry_count = scope.count @entry_count = scope.count
@entry_pages = Paginator.new @entry_count, per_page_option, params['page'] @entry_pages = Paginator.new self, @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}],
:order => sort_clause,
:limit => @entry_pages.items_per_page,
:offset => @entry_pages.current.offset
)
@total_hours = scope.sum(:hours).to_f @total_hours = scope.sum(:hours).to_f
render :layout => !request.xhr? render :layout => !request.xhr?
@@ -62,25 +73,35 @@ class TimelogController < ApplicationController
format.api { format.api {
@entry_count = scope.count @entry_count = scope.count
@offset, @limit = api_offset_and_limit @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}],
:order => sort_clause,
:limit => @limit,
:offset => @offset
)
} }
format.atom { format.atom {
entries = scope.limit(Setting.feeds_limit.to_i).reorder("#{TimeEntry.table_name}.created_on DESC").all entries = scope.all(
:include => [:project, :activity, :user, {:issue => :tracker}],
:order => "#{TimeEntry.table_name}.created_on DESC",
:limit => Setting.feeds_limit.to_i
)
render_feed(entries, :title => l(:label_spent_time)) render_feed(entries, :title => l(:label_spent_time))
} }
format.csv { format.csv {
# Export all entries # Export all entries
@entries = scope.all @entries = scope.all(
send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'timelog.csv') :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
:order => sort_clause
)
send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv')
} }
end end
end end
def report def report
@query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_') retrieve_date_range
scope = time_entry_scope @report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], @from, @to)
@report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], scope)
respond_to do |format| respond_to do |format|
format.html { render :layout => !request.xhr? } format.html { render :layout => !request.xhr? }
@@ -113,24 +134,16 @@ class TimelogController < ApplicationController
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
if params[:continue] if params[:continue]
if params[:project_id] if params[:project_id]
options = { redirect_to :action => 'new', :project_id => @time_entry.project, :issue_id => @time_entry.issue,
:time_entry => {:issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id}, :time_entry => {:issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
:back_url => params[:back_url] :back_url => params[:back_url]
}
if @time_entry.issue
redirect_to new_project_issue_time_entry_path(@time_entry.project, @time_entry.issue, options)
else else
redirect_to new_project_time_entry_path(@time_entry.project, options) redirect_to :action => 'new',
end
else
options = {
:time_entry => {:project_id => @time_entry.project_id, :issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id}, :time_entry => {:project_id => @time_entry.project_id, :issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
:back_url => params[:back_url] :back_url => params[:back_url]
}
redirect_to new_time_entry_path(options)
end end
else else
redirect_back_or_default project_time_entries_path(@time_entry.project) redirect_back_or_default :action => 'index', :project_id => @time_entry.project
end end
} }
format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) } format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
@@ -156,7 +169,7 @@ class TimelogController < ApplicationController
respond_to do |format| respond_to do |format|
format.html { format.html {
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_back_or_default project_time_entries_path(@time_entry.project) redirect_back_or_default :action => 'index', :project_id => @time_entry.project
} }
format.api { render_api_ok } format.api { render_api_ok }
end end
@@ -182,13 +195,12 @@ class TimelogController < ApplicationController
time_entry.safe_attributes = attributes time_entry.safe_attributes = attributes
call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry }) call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
unless time_entry.save 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 # Keep unsaved time_entry ids to display them in flash error
unsaved_time_entry_ids << time_entry.id unsaved_time_entry_ids << time_entry.id
end end
end end
set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids) set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
redirect_back_or_default project_time_entries_path(@projects.first) redirect_back_or_default({:controller => 'timelog', :action => 'index', :project_id => @projects.first})
end end
def destroy def destroy
@@ -207,7 +219,7 @@ class TimelogController < ApplicationController
else else
flash[:error] = l(:notice_unable_delete_time_entry) flash[:error] = l(:notice_unable_delete_time_entry)
end end
redirect_back_or_default project_time_entries_path(@projects.first) redirect_back_or_default(:action => 'index', :project_id => @projects.first)
} }
format.api { format.api {
if destroyed if destroyed
@@ -279,13 +291,51 @@ private
end end
end end
# Returns the TimeEntry scope for index and report actions # Retrieves the date range based on predefined ranges or specific from/to param dates
def time_entry_scope(options={}) def retrieve_date_range
scope = @query.results_scope(options) @free_period = false
if @issue @from, @to = nil, nil
scope = scope.on_issue(@issue)
if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
case params[:period].to_s
when 'today'
@from = @to = Date.today
when 'yesterday'
@from = @to = Date.today - 1
when 'current_week'
@from = Date.today - (Date.today.cwday - 1)%7
@to = @from + 6
when 'last_week'
@from = Date.today - 7 - (Date.today.cwday - 1)%7
@to = @from + 6
when 'last_2_weeks'
@from = Date.today - 14 - (Date.today.cwday - 1)%7
@to = @from + 13
when '7_days'
@from = Date.today - 7
@to = Date.today
when 'current_month'
@from = Date.civil(Date.today.year, Date.today.month, 1)
@to = (@from >> 1) - 1
when 'last_month'
@from = Date.civil(Date.today.year, Date.today.month, 1) << 1
@to = (@from >> 1) - 1
when '30_days'
@from = Date.today - 30
@to = Date.today
when 'current_year'
@from = Date.civil(Date.today.year, 1, 1)
@to = Date.civil(Date.today.year, 12, 31)
end end
scope elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
@free_period = true
else
# default
end
@from, @to = @to, @from if @from && @to && @from > @to
end end
def parse_params_for_bulk_time_entry_attributes(params) def parse_params_for_bulk_time_entry_attributes(params)
+11 -11
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -25,7 +25,7 @@ class TrackersController < ApplicationController
def index def index
respond_to do |format| respond_to do |format|
format.html { format.html {
@tracker_pages, @trackers = paginate Tracker.sorted, :per_page => 25 @tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position'
render :action => "index", :layout => false if request.xhr? render :action => "index", :layout => false if request.xhr?
} }
format.api { format.api {
@@ -36,19 +36,19 @@ class TrackersController < ApplicationController
def new def new
@tracker ||= Tracker.new(params[:tracker]) @tracker ||= Tracker.new(params[:tracker])
@trackers = Tracker.sorted.all @trackers = Tracker.find :all, :order => 'position'
@projects = Project.all @projects = Project.find(:all)
end end
def create def create
@tracker = Tracker.new(params[:tracker]) @tracker = Tracker.new(params[:tracker])
if @tracker.save if request.post? and @tracker.save
# workflow copy # workflow copy
if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from])) if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from]))
@tracker.workflow_rules.copy(copy_from) @tracker.workflow_rules.copy(copy_from)
end end
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to trackers_path redirect_to :action => 'index'
return return
end end
new new
@@ -57,14 +57,14 @@ class TrackersController < ApplicationController
def edit def edit
@tracker ||= Tracker.find(params[:id]) @tracker ||= Tracker.find(params[:id])
@projects = Project.all @projects = Project.find(:all)
end end
def update def update
@tracker = Tracker.find(params[:id]) @tracker = Tracker.find(params[:id])
if @tracker.update_attributes(params[:tracker]) if request.put? and @tracker.update_attributes(params[:tracker])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to trackers_path redirect_to :action => 'index'
return return
end end
edit edit
@@ -78,7 +78,7 @@ class TrackersController < ApplicationController
else else
@tracker.destroy @tracker.destroy
end end
redirect_to trackers_path redirect_to :action => 'index'
end end
def fields def fields
@@ -92,7 +92,7 @@ class TrackersController < ApplicationController
end end
end end
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to fields_trackers_path redirect_to :action => 'fields'
return return
end end
@trackers = Tracker.sorted.all @trackers = Tracker.sorted.all
+26 -22
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -45,9 +45,12 @@ class UsersController < ApplicationController
scope = scope.in_group(params[:group_id]) if params[:group_id].present? scope = scope.in_group(params[:group_id]) if params[:group_id].present?
@user_count = scope.count @user_count = scope.count
@user_pages = Paginator.new @user_count, @limit, params['page'] @user_pages = Paginator.new self, @user_count, @limit, params['page']
@offset ||= @user_pages.offset @offset ||= @user_pages.current.offset
@users = scope.order(sort_clause).limit(@limit).offset(@offset).all @users = scope.find :all,
:order => sort_clause,
:limit => @limit,
:offset => @offset
respond_to do |format| respond_to do |format|
format.html { format.html {
@@ -60,7 +63,7 @@ class UsersController < ApplicationController
def show def show
# show projects based on current user visibility # 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 = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
@events_by_day = events.group_by(&:event_date) @events_by_day = events.group_by(&:event_date)
@@ -80,8 +83,7 @@ class UsersController < ApplicationController
def new def new
@user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
@user.safe_attributes = params[:user] @auth_sources = AuthSource.find(:all)
@auth_sources = AuthSource.all
end end
def create def create
@@ -93,24 +95,24 @@ class UsersController < ApplicationController
if @user.save if @user.save
@user.pref.attributes = params[:pref] @user.pref.attributes = params[:pref]
@user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
@user.pref.save @user.pref.save
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
Mailer.account_information(@user, @user.password).deliver if params[:send_information] Mailer.account_information(@user, params[:user][:password]).deliver if params[:send_information]
respond_to do |format| respond_to do |format|
format.html { format.html {
flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user))) flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user)))
if params[:continue] redirect_to(params[:continue] ?
attrs = params[:user].slice(:generate_password) {:controller => 'users', :action => 'new'} :
redirect_to new_user_path(:user => attrs) {:controller => 'users', :action => 'edit', :id => @user}
else )
redirect_to edit_user_path(@user)
end
} }
format.api { render :action => 'show', :status => :created, :location => user_url(@user) } format.api { render :action => 'show', :status => :created, :location => user_url(@user) }
end end
else else
@auth_sources = AuthSource.all @auth_sources = AuthSource.find(:all)
# Clear password input # Clear password input
@user.password = @user.password_confirmation = nil @user.password = @user.password_confirmation = nil
@@ -122,7 +124,7 @@ class UsersController < ApplicationController
end end
def edit def edit
@auth_sources = AuthSource.all @auth_sources = AuthSource.find(:all)
@membership ||= Member.new @membership ||= Member.new
end end
@@ -137,14 +139,16 @@ class UsersController < ApplicationController
was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE]) was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
# TODO: Similar to My#account # TODO: Similar to My#account
@user.pref.attributes = params[:pref] @user.pref.attributes = params[:pref]
@user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
if @user.save if @user.save
@user.pref.save @user.pref.save
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
if was_activated if was_activated
Mailer.account_activated(@user).deliver Mailer.account_activated(@user).deliver
elsif @user.active? && params[:send_information] && @user.password.present? && @user.auth_source_id.nil? elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil?
Mailer.account_information(@user, @user.password).deliver Mailer.account_information(@user, params[:user][:password]).deliver
end end
respond_to do |format| respond_to do |format|
@@ -155,7 +159,7 @@ class UsersController < ApplicationController
format.api { render_api_ok } format.api { render_api_ok }
end end
else else
@auth_sources = AuthSource.all @auth_sources = AuthSource.find(:all)
@membership ||= Member.new @membership ||= Member.new
# Clear password input # Clear password input
@user.password = @user.password_confirmation = nil @user.password = @user.password_confirmation = nil
@@ -170,7 +174,7 @@ class UsersController < ApplicationController
def destroy def destroy
@user.destroy @user.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_back_or_default(users_path) } format.html { redirect_back_or_default(users_url) }
format.api { render_api_ok } format.api { render_api_ok }
end end
end end
@@ -179,7 +183,7 @@ class UsersController < ApplicationController
@membership = Member.edit_membership(params[:membership_id], params[:membership], @user) @membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
@membership.save @membership.save
respond_to do |format| respond_to do |format|
format.html { redirect_to edit_user_path(@user, :tab => 'memberships') } format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
format.js format.js
end end
end end
@@ -190,7 +194,7 @@ class UsersController < ApplicationController
@membership.destroy @membership.destroy
end end
respond_to do |format| respond_to do |format|
format.html { redirect_to edit_user_path(@user, :tab => 'memberships') } format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
format.js format.js
end end
end end
+15 -16
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -31,7 +31,7 @@ class VersionsController < ApplicationController
def index def index
respond_to do |format| respond_to do |format|
format.html { format.html {
@trackers = @project.trackers.sorted.all @trackers = @project.trackers.find(:all, :order => 'position')
retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?}) retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?})
@with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1') @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id] project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
@@ -46,11 +46,11 @@ class VersionsController < ApplicationController
@issues_by_version = {} @issues_by_version = {}
if @selected_tracker_ids.any? && @versions.any? if @selected_tracker_ids.any? && @versions.any?
issues = Issue.visible. issues = Issue.visible.all(
includes(:project, :tracker). :include => [:project, :status, :tracker, :priority, :fixed_version],
preload(:status, :priority, :fixed_version). :conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)},
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"
order("#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id") )
@issues_by_version = issues.group_by(&:fixed_version) @issues_by_version = issues.group_by(&:fixed_version)
end end
@versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?} @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?}
@@ -64,10 +64,9 @@ class VersionsController < ApplicationController
def show def show
respond_to do |format| respond_to do |format|
format.html { format.html {
@issues = @version.fixed_issues.visible. @issues = @version.fixed_issues.visible.find(:all,
includes(:status, :tracker, :priority). :include => [:status, :tracker, :priority],
reorder("#{Tracker.table_name}.position, #{Issue.table_name}.id"). :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id")
all
} }
format.api format.api
end end
@@ -96,7 +95,7 @@ class VersionsController < ApplicationController
respond_to do |format| respond_to do |format|
format.html do format.html do
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_back_or_default settings_project_path(@project, :tab => 'versions') redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
end end
format.js format.js
format.api do format.api do
@@ -125,7 +124,7 @@ class VersionsController < ApplicationController
respond_to do |format| respond_to do |format|
format.html { format.html {
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_back_or_default settings_project_path(@project, :tab => 'versions') redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
} }
format.api { render_api_ok } format.api { render_api_ok }
end end
@@ -142,21 +141,21 @@ class VersionsController < ApplicationController
if request.put? if request.put?
@project.close_completed_versions @project.close_completed_versions
end end
redirect_to settings_project_path(@project, :tab => 'versions') redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
end end
def destroy def destroy
if @version.fixed_issues.empty? if @version.fixed_issues.empty?
@version.destroy @version.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_back_or_default settings_project_path(@project, :tab => 'versions') } format.html { redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project }
format.api { render_api_ok } format.api { render_api_ok }
end end
else else
respond_to do |format| respond_to do |format|
format.html { format.html {
flash[:error] = l(:notice_unable_delete_version) flash[:error] = l(:notice_unable_delete_version)
redirect_to settings_project_path(@project, :tab => 'versions') redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
} }
format.api { head :unprocessable_entity } format.api { head :unprocessable_entity }
end end
+20 -34
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -16,36 +16,35 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class WatchersController < ApplicationController class WatchersController < ApplicationController
before_filter :require_login, :find_watchables, :only => [:watch, :unwatch] before_filter :find_project
before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch]
before_filter :authorize, :only => [:new, :destroy]
def watch def watch
set_watcher(@watchables, User.current, true) if @watched.respond_to?(:visible?) && !@watched.visible?(User.current)
render_403
else
set_watcher(User.current, true)
end
end end
def unwatch def unwatch
set_watcher(@watchables, User.current, false) set_watcher(User.current, false)
end end
before_filter :find_project, :authorize, :only => [:new, :create, :append, :destroy, :autocomplete_for_user]
accept_api_auth :create, :destroy
def new def new
end end
def create def create
user_ids = [] if params[:watcher].is_a?(Hash) && request.post?
if params[:watcher].is_a?(Hash) user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
user_ids << (params[:watcher][:user_ids] || params[:watcher][:user_id]) user_ids.each do |user_id|
else
user_ids << params[:user_id]
end
user_ids.flatten.compact.uniq.each do |user_id|
Watcher.create(:watchable => @watched, :user_id => user_id) Watcher.create(:watchable => @watched, :user_id => user_id)
end end
end
respond_to do |format| respond_to do |format|
format.html { redirect_to_referer_or {render :text => 'Watcher added.', :layout => true}} format.html { redirect_to_referer_or {render :text => 'Watcher added.', :layout => true}}
format.js format.js
format.api { render_api_ok }
end end
end end
@@ -57,24 +56,22 @@ class WatchersController < ApplicationController
end end
def destroy def destroy
@watched.set_watcher(User.find(params[:user_id]), false) @watched.set_watcher(User.find(params[:user_id]), false) if request.post?
respond_to do |format| respond_to do |format|
format.html { redirect_to :back } format.html { redirect_to :back }
format.js format.js
format.api { render_api_ok }
end end
end end
def autocomplete_for_user def autocomplete_for_user
@users = User.active.sorted.like(params[:q]).limit(100).all @users = User.active.like(params[:q]).find(:all, :limit => 100)
if @watched if @watched
@users -= @watched.watcher_users @users -= @watched.watcher_users
end end
render :layout => false render :layout => false
end end
private private
def find_project def find_project
if params[:object_type] && params[:object_id] if params[:object_type] && params[:object_id]
klass = Object.const_get(params[:object_type].camelcase) klass = Object.const_get(params[:object_type].camelcase)
@@ -88,22 +85,11 @@ class WatchersController < ApplicationController
render_404 render_404
end end
def find_watchables def set_watcher(user, watching)
klass = Object.const_get(params[:object_type].camelcase) rescue nil @watched.set_watcher(user, watching)
if klass && klass.respond_to?('watched_by')
@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
def set_watcher(watchables, user, watching)
watchables.each do |watchable|
watchable.set_watcher(user, watching)
end
respond_to do |format| respond_to do |format|
format.html { redirect_to_referer_or {render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true}} format.html { redirect_to_referer_or {render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true}}
format.js { render :partial => 'set_watcher', :locals => {:user => user, :watched => watchables} } format.js { render :partial => 'set_watcher', :locals => {:user => user, :watched => @watched} }
end end
end end
end end
+1 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+29 -31
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # 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 # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'diff'
# The WikiController follows the Rails REST controller pattern but with # The WikiController follows the Rails REST controller pattern but with
# a few differences # a few differences
# #
@@ -35,7 +37,6 @@ class WikiController < ApplicationController
before_filter :find_existing_or_new_page, :only => [:show, :edit, :update] before_filter :find_existing_or_new_page, :only => [:show, :edit, :update]
before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy, :destroy_version] before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy, :destroy_version]
accept_api_auth :index, :show, :update, :destroy accept_api_auth :index, :show, :update, :destroy
before_filter :find_attachments, :only => [:preview]
helper :attachments helper :attachments
include AttachmentsHelper include AttachmentsHelper
@@ -62,12 +63,7 @@ class WikiController < ApplicationController
# display a page (in editing mode if it doesn't exist) # display a page (in editing mode if it doesn't exist)
def show def show
if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project) if @page.new_record?
deny_access
return
end
@content = @page.content_for_version(params[:version])
if @content.nil?
if User.current.allowed_to?(:edit_wiki_pages, @project) && editable? && !api_request? if User.current.allowed_to?(:edit_wiki_pages, @project) && editable? && !api_request?
edit edit
render :action => 'edit' render :action => 'edit'
@@ -76,6 +72,11 @@ class WikiController < ApplicationController
end end
return return
end 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 User.current.allowed_to?(:export_wiki_pages, @project)
if params[:format] == 'pdf' if params[:format] == 'pdf'
send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf") send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf")
@@ -104,19 +105,19 @@ class WikiController < ApplicationController
def edit def edit
return render_403 unless editable? return render_403 unless editable?
if @page.new_record? if @page.new_record?
@page.content = WikiContent.new(:page => @page)
if params[:parent].present? if params[:parent].present?
@page.parent = @page.wiki.find_page(params[:parent].to_s) @page.parent = @page.wiki.find_page(params[:parent].to_s)
end end
end end
@content = @page.content_for_version(params[:version]) @content = @page.content_for_version(params[:version])
@content ||= WikiContent.new(:page => @page)
@content.text = initial_page_content(@page) if @content.text.blank? @content.text = initial_page_content(@page) if @content.text.blank?
# don't keep previous comment # don't keep previous comment
@content.comments = nil @content.comments = nil
# To prevent StaleObjectError exception when reverting to a previous version # 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 @text = @content.text
if params[:section].present? && Redmine::WikiFormatting.supports_section_edit? if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
@@ -130,9 +131,10 @@ class WikiController < ApplicationController
def update def update
return render_403 unless editable? return render_403 unless editable?
was_new_page = @page.new_record? was_new_page = @page.new_record?
@page.content = WikiContent.new(:page => @page) if @page.new_record?
@page.safe_attributes = params[:wiki_page] @page.safe_attributes = params[:wiki_page]
@content = @page.content || WikiContent.new(:page => @page) @content = @page.content
content_params = params[:content] content_params = params[:content]
if content_params.nil? && params[:wiki_page].is_a?(Hash) if content_params.nil? && params[:wiki_page].is_a?(Hash)
content_params = params[:wiki_page].slice(:text, :comments, :version) content_params = params[:wiki_page].slice(:text, :comments, :version)
@@ -144,26 +146,23 @@ class WikiController < ApplicationController
if params[:section].present? && Redmine::WikiFormatting.supports_section_edit? if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
@section = params[:section].to_i @section = params[:section].to_i
@section_hash = params[:section_hash] @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 else
@content.version = content_params[:version] if content_params[:version] @content.version = content_params[:version] if content_params[:version]
@content.text = @text @content.text = @text
end end
@content.author = User.current @content.author = User.current
if @page.save_with_content(@content) if @page.save_with_content
attachments = Attachment.attach_files(@page, params[:attachments]) attachments = Attachment.attach_files(@page, params[:attachments])
render_attachment_warning_if_needed(@page) render_attachment_warning_if_needed(@page)
call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page}) call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
respond_to do |format| respond_to do |format|
format.html { format.html { redirect_to :action => 'show', :project_id => @project, :id => @page.title }
anchor = @section ? "section-#{@section}" : nil
redirect_to project_wiki_page_path(@project, @page.title, :anchor => anchor)
}
format.api { format.api {
if was_new_page if was_new_page
render :action => 'show', :status => :created, :location => project_wiki_page_path(@project, @page.title) render :action => 'show', :status => :created, :location => url_for(:controller => 'wiki', :action => 'show', :project_id => @project, :id => @page.title)
else else
render_api_ok render_api_ok
end end
@@ -200,26 +199,25 @@ class WikiController < ApplicationController
@original_title = @page.pretty_title @original_title = @page.pretty_title
if request.post? && @page.update_attributes(params[:wiki_page]) if request.post? && @page.update_attributes(params[:wiki_page])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to project_wiki_page_path(@project, @page.title) redirect_to :action => 'show', :project_id => @project, :id => @page.title
end end
end end
def protect def protect
@page.update_attribute :protected, params[:protected] @page.update_attribute :protected, params[:protected]
redirect_to project_wiki_page_path(@project, @page.title) redirect_to :action => 'show', :project_id => @project, :id => @page.title
end end
# show page history # show page history
def history def history
@version_count = @page.content.versions.count @version_count = @page.content.versions.count
@version_pages = Paginator.new @version_count, per_page_option, params['page'] @version_pages = Paginator.new self, @version_count, per_page_option, params['page']
# don't load text # don't load text
@versions = @page.content.versions. @versions = @page.content.versions.find :all,
select("id, author_id, comments, updated_on, version"). :select => "id, author_id, comments, updated_on, version",
reorder('version DESC'). :order => 'version DESC',
limit(@version_pages.per_page + 1). :limit => @version_pages.items_per_page + 1,
offset(@version_pages.offset). :offset => @version_pages.current.offset
all
render :layout => false if request.xhr? render :layout => false if request.xhr?
end end
@@ -262,7 +260,7 @@ class WikiController < ApplicationController
end end
@page.destroy @page.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to project_wiki_index_path(@project) } format.html { redirect_to :action => 'index', :project_id => @project }
format.api { render_api_ok } format.api { render_api_ok }
end end
end end
@@ -272,7 +270,7 @@ class WikiController < ApplicationController
@content = @page.content_for_version(params[:version]) @content = @page.content_for_version(params[:version])
@content.destroy @content.destroy
redirect_to_referer_or history_project_wiki_page_path(@project, @page.title) redirect_to_referer_or :action => 'history', :id => @page.title, :project_id => @project
end end
# Export wiki to a single pdf or html file # Export wiki to a single pdf or html file
@@ -294,7 +292,7 @@ class WikiController < ApplicationController
# page is nil when previewing a new page # page is nil when previewing a new page
return render_403 unless page.nil? || editable?(page) return render_403 unless page.nil? || editable?(page)
if page if page
@attachments += page.attachments @attachements = page.attachments
@previewed = page.content @previewed = page.content
end end
@text = params[:content][:text] @text = params[:content][:text]
@@ -351,6 +349,6 @@ private
end end
def load_pages_for_index 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.order("#{WikiPage.table_name}.title").includes(:wiki => :project).includes(:parent).all
end end
end end
+2 -2
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -30,7 +30,7 @@ class WikisController < ApplicationController
def destroy def destroy
if request.post? && params[:confirm] && @project.wiki if request.post? && params[:confirm] && @project.wiki
@project.wiki.destroy @project.wiki.destroy
redirect_to settings_project_path(@project, :tab => 'wiki') redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'wiki'
end end
end end
end end
+5 -5
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -38,7 +38,7 @@ class WorkflowsController < ApplicationController
} }
} }
if @role.save if @role.save
redirect_to workflows_edit_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only]) redirect_to :action => 'edit', :role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only]
return return
end end
end end
@@ -64,7 +64,7 @@ class WorkflowsController < ApplicationController
if request.post? && @role && @tracker if request.post? && @role && @tracker
WorkflowPermission.replace_permissions(@tracker, @role, params[:permissions] || {}) 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]) redirect_to :action => 'permissions', :role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only]
return return
end end
@@ -106,12 +106,12 @@ class WorkflowsController < ApplicationController
if request.post? if request.post?
if params[:source_tracker_id].blank? || params[:source_role_id].blank? || (@source_tracker.nil? && @source_role.nil?) 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) flash.now[:error] = l(:error_workflow_copy_source)
elsif @target_trackers.blank? || @target_roles.blank? elsif @target_trackers.nil? || @target_roles.nil?
flash.now[:error] = l(:error_workflow_copy_target) flash.now[:error] = l(:error_workflow_copy_target)
else else
WorkflowRule.copy(@source_tracker, @source_role, @target_trackers, @target_roles) WorkflowRule.copy(@source_tracker, @source_role, @target_trackers, @target_roles)
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to workflows_copy_path(:source_tracker_id => @source_tracker, :source_role_id => @source_role) redirect_to :action => 'copy', :source_tracker_id => @source_tracker, :source_role_id => @source_role
end end
end end
end end
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+106 -95
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -24,7 +24,6 @@ module ApplicationHelper
include Redmine::WikiFormatting::Macros::Definitions include Redmine::WikiFormatting::Macros::Definitions
include Redmine::I18n include Redmine::I18n
include GravatarHelper::PublicMethods include GravatarHelper::PublicMethods
include Redmine::Pagination::Helper
extend Forwardable extend Forwardable
def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
@@ -91,10 +90,14 @@ module ApplicationHelper
# * :download - Force download (default: false) # * :download - Force download (default: false)
def link_to_attachment(attachment, options={}) def link_to_attachment(attachment, options={})
text = options.delete(:text) || attachment.filename text = options.delete(:text) || attachment.filename
route_method = options.delete(:download) ? :download_named_attachment_path : :named_attachment_path action = options.delete(:download) ? 'download' : 'show'
html_options = options.slice!(:only_path) opt_only_path = {}
url = send(route_method, attachment, attachment.filename, options) opt_only_path[:only_path] = (options[:only_path] == false ? false : true)
link_to text, url, html_options options.delete(:only_path)
link_to(h(text),
{:controller => 'attachments', :action => action,
:id => attachment, :filename => attachment.filename}.merge(opt_only_path),
options)
end end
# Generates a link to a SCM revision # Generates a link to a SCM revision
@@ -116,11 +119,13 @@ module ApplicationHelper
# Generates a link to a message # Generates a link to a message
def link_to_message(message, options={}, html_options = nil) def link_to_message(message, options={}, html_options = nil)
link_to( link_to(
truncate(message.subject, :length => 60), h(truncate(message.subject, :length => 60)),
board_message_path(message.board_id, message.parent_id || message.id, { { :controller => 'messages', :action => 'show',
:board_id => message.board_id,
:id => (message.parent_id || message.id),
:r => (message.parent_id && message.id), :r => (message.parent_id && message.id),
:anchor => (message.parent_id ? "message-#{message.id}" : nil) :anchor => (message.parent_id ? "message-#{message.id}" : nil)
}.merge(options)), }.merge(options),
html_options html_options
) )
end end
@@ -129,29 +134,16 @@ module ApplicationHelper
# Examples: # Examples:
# #
# link_to_project(project) # => link to the specified project overview # link_to_project(project) # => link to the specified project overview
# link_to_project(project, :action=>'settings') # => link to project settings
# link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
# link_to_project(project, {}, :class => "project") # => html options with default url (project overview) # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
# #
def link_to_project(project, options={}, html_options = nil) def link_to_project(project, options={}, html_options = nil)
if project.archived? if project.archived?
h(project.name) h(project)
elsif options.key?(:action) else
ActiveSupport::Deprecation.warn "#link_to_project with :action option is deprecated and will be removed in Redmine 3.0."
url = {:controller => 'projects', :action => 'show', :id => project}.merge(options) url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
link_to project.name, url, html_options link_to(h(project), url, html_options)
else
link_to project.name, project_path(project, options), html_options
end
end
# Generates a link to a project settings if active
def link_to_project_settings(project, options={}, html_options=nil)
if project.active?
link_to project.name, settings_project_path(project, options), html_options
elsif project.archived?
h(project.name)
else
link_to project.name, project_path(project, options), html_options
end end
end end
@@ -160,8 +152,8 @@ module ApplicationHelper
end end
def thumbnail_tag(attachment) def thumbnail_tag(attachment)
link_to image_tag(thumbnail_path(attachment)), link_to image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment)),
named_attachment_path(attachment, attachment.filename), {:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename},
:title => attachment.filename :title => attachment.filename
end end
@@ -316,8 +308,8 @@ module ApplicationHelper
def principals_check_box_tags(name, principals) def principals_check_box_tags(name, principals)
s = '' s = ''
principals.each do |principal| principals.sort.each do |principal|
s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } #{h principal}</label>\n" s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
end end
s.html_safe s.html_safe
end end
@@ -330,7 +322,7 @@ module ApplicationHelper
end end
groups = '' groups = ''
collection.sort.each do |element| 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>) (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
end end
unless groups.empty? unless groups.empty?
@@ -343,15 +335,11 @@ module ApplicationHelper
def options_for_membership_project_select(principal, projects) def options_for_membership_project_select(principal, projects)
options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---") options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
options << project_tree_options_for_select(projects) do |p| options << project_tree_options_for_select(projects) do |p|
{:disabled => principal.projects.to_a.include?(p)} {:disabled => principal.projects.include?(p)}
end end
options options
end 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 # Truncates and returns the string as a single line
def truncate_single_line(string, *args) def truncate_single_line(string, *args)
truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
@@ -384,7 +372,7 @@ module ApplicationHelper
if @project if @project
link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time)) link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
else else
content_tag('abbr', text, :title => format_time(time)) content_tag('acronym', text, :title => format_time(time))
end end
end end
@@ -403,6 +391,59 @@ module ApplicationHelper
str.blank? ? nil : str str.blank? ? nil : str
end end
def pagination_links_full(paginator, count=nil, options={})
page_param = options.delete(:page_param) || :page
per_page_links = options.delete(:per_page_links)
url_param = params.dup
html = ''
if paginator.current.previous
# \xc2\xab(utf-8) = &#171;
html << link_to_content_update(
"\xc2\xab " + l(:label_previous),
url_param.merge(page_param => paginator.current.previous)) + ' '
end
html << (pagination_links_each(paginator, options) do |n|
link_to_content_update(n.to_s, url_param.merge(page_param => n))
end || '')
if paginator.current.next
# \xc2\xbb(utf-8) = &#187;
html << ' ' + link_to_content_update(
(l(:label_next) + " \xc2\xbb"),
url_param.merge(page_param => paginator.current.next))
end
unless count.nil?
html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
if per_page_links != false && links = per_page_links(paginator.items_per_page, count)
html << " | #{links}"
end
end
html.html_safe
end
def per_page_links(selected=nil, item_count=nil)
values = Setting.per_page_options_array
if item_count && values.any?
if item_count > values.first
max = values.detect {|value| value >= item_count} || item_count
else
max = item_count
end
values = values.select {|value| value <= max || value == selected}
end
if values.empty? || (values.size == 1 && values.first == selected)
return nil
end
links = values.collect do |n|
n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
end
l(:label_display_per_page, links.join(', '))
end
def reorder_links(name, url, method = :post) def reorder_links(name, url, method = :post)
link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
url.merge({"#{name}[move_to]" => 'highest'}), url.merge({"#{name}[move_to]" => 'highest'}),
@@ -449,31 +490,12 @@ module ApplicationHelper
end end
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(' &#187; ').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) def html_title(*args)
if args.empty? if args.empty?
title = @html_title || [] title = @html_title || []
title << @project.name if @project title << @project.name if @project
title << Setting.app_title unless Setting.app_title == title.last title << Setting.app_title unless Setting.app_title == title.last
title.reject(&:blank?).join(' - ') title.select {|t| !t.blank? }.join(' - ')
else else
@html_title ||= [] @html_title ||= []
@html_title += args @html_title += args
@@ -488,18 +510,13 @@ module ApplicationHelper
css << 'theme-' + theme.name css << 'theme-' + theme.name
end end
css << 'project-' + @project.identifier if @project && @project.identifier.present?
css << 'controller-' + controller_name css << 'controller-' + controller_name
css << 'action-' + action_name css << 'action-' + action_name
css.join(' ') css.join(' ')
end end
def accesskey(s) def accesskey(s)
@used_accesskeys ||= [] Redmine::AccessKeys.key_for s
key = Redmine::AccessKeys.key_for(s)
return nil if @used_accesskeys.include?(key)
@used_accesskeys << key
key
end end
# Formats text according to system settings. # Formats text according to system settings.
@@ -580,14 +597,14 @@ module ApplicationHelper
def parse_inline_attachments(text, project, obj, attr, only_path, options) def parse_inline_attachments(text, project, obj, attr, only_path, options)
# when using an image link, try to use an attachment, if possible # when using an image link, try to use an attachment, if possible
attachments = options[:attachments] || [] if options[:attachments] || (obj && obj.respond_to?(:attachments))
attachments += obj.attachments if obj.respond_to?(:attachments) attachments = options[:attachments] || obj.attachments
if attachments.present?
text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m| text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
filename, ext, alt, alttext = $1.downcase, $2, $3, $4 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
# search for the picture in attachments # search for the picture in attachments
if found = Attachment.latest_attach(attachments, filename) if found = Attachment.latest_attach(attachments, filename)
image_url = download_named_attachment_path(found, found.filename, :only_path => only_path) image_url = url_for :only_path => only_path, :controller => 'attachments',
:action => 'download', :id => found
desc = found.description.to_s.gsub('"', '') desc = found.description.to_s.gsub('"', '')
if !desc.blank? && alttext.blank? if !desc.blank? && alttext.blank?
alt = " title=\"#{desc}\" alt=\"#{desc}\"" alt = " title=\"#{desc}\" alt=\"#{desc}\""
@@ -616,9 +633,9 @@ module ApplicationHelper
esc, all, page, title = $1, $2, $3, $5 esc, all, page, title = $1, $2, $3, $5
if esc.nil? if esc.nil?
if page =~ /^([^\:]+)\:(.*)$/ if page =~ /^([^\:]+)\:(.*)$/
identifier, page = $1, $2 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier) page = $2
title ||= identifier if page.blank? title ||= $1 if page.blank?
end end
if link_project && link_project.wiki if link_project && link_project.wiki
@@ -680,20 +697,16 @@ module ApplicationHelper
# export:some/file -> Force the download of the file # export:some/file -> Force the download of the file
# Forum messages: # Forum messages:
# message#1218 -> Link to message with id 1218 # 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: # Links can refer other objects from other projects, using project identifier:
# identifier:r52 # identifier:r52
# identifier:document:"Some document" # identifier:document:"Some document"
# identifier:version:1.0.0 # identifier:version:1.0.0
# identifier:source:some/file # identifier:source:some/file
def parse_redmine_links(text, default_project, obj, attr, only_path, options) def parse_redmine_links(text, project, obj, attr, only_path, options)
text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m| text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17 leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
link = nil link = nil
project = default_project
if project_identifier if project_identifier
project = Project.visible.find_by_identifier(project_identifier) project = Project.visible.find_by_identifier(project_identifier)
end end
@@ -719,7 +732,7 @@ module ApplicationHelper
when nil when nil
if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status) if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status)
anchor = comment_id ? "note-#{comment_id}" : nil 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, :class => issue.css_classes,
:title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
end end
@@ -779,21 +792,21 @@ module ApplicationHelper
when 'commit', 'source', 'export' when 'commit', 'source', 'export'
if project if project
repository = nil repository = nil
if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$} if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
repo_prefix, repo_identifier, name = $1, $2, $3 repo_prefix, repo_identifier, name = $1, $2, $3
repository = project.repositories.detect {|repo| repo.identifier == repo_identifier} repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
else else
repository = project.repository repository = project.repository
end end
if prefix == 'commit' if prefix == 'commit'
if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first) if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
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}, 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', :class => 'changeset',
:title => truncate_single_line(changeset.comments, :length => 100) :title => truncate_single_line(h(changeset.comments), :length => 100)
end end
else else
if repository && User.current.allowed_to?(:browse_repository, project) if repository && User.current.allowed_to?(:browse_repository, project)
name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$} name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
path, rev, anchor = $1, $3, $5 path, rev, anchor = $1, $3, $5
link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param, link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param,
:path => to_path_param(path), :path => to_path_param(path),
@@ -806,11 +819,12 @@ module ApplicationHelper
end end
when 'attachment' when 'attachment'
attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
if attachments && attachment = Attachment.latest_attach(attachments, name) if attachments && attachment = attachments.detect {|a| a.filename == name }
link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment') link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
:class => 'attachment'
end end
when 'project' when 'project'
if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
link = link_to_project(p, {:only_path => only_path}, :class => 'project') link = link_to_project(p, {:only_path => only_path}, :class => 'project')
end end
end end
@@ -831,8 +845,7 @@ module ApplicationHelper
content_tag('div', content_tag('div',
link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)), link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
:class => 'contextual', :class => 'contextual',
:title => l(:button_edit_section), :title => l(:button_edit_section)) + heading.html_safe
:id => "section-#{@current_section}") + heading.html_safe
else else
heading heading
end end
@@ -1063,8 +1076,8 @@ module ApplicationHelper
(pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) + (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[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) (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 content_tag('p', legend, :class => 'pourcent').html_safe
end end
def checked_image(checked=true) def checked_image(checked=true)
@@ -1096,7 +1109,6 @@ module ApplicationHelper
def include_calendar_headers_tags def include_calendar_headers_tags
unless @calendar_headers_tags_included unless @calendar_headers_tags_included
tags = javascript_include_tag("datepicker")
@calendar_headers_tags_included = true @calendar_headers_tags_included = true
content_for :header_tags do content_for :header_tags do
start_of_week = Setting.start_of_week start_of_week = Setting.start_of_week
@@ -1104,13 +1116,12 @@ module ApplicationHelper
# Redmine uses 1..7 (monday..sunday) in settings and locales # Redmine uses 1..7 (monday..sunday) in settings and locales
# JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
start_of_week = start_of_week.to_i % 7 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}, " + "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
"showOn: 'button', buttonImageOnly: true, buttonImage: '" + "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
path_to_image('/images/calendar.png') + path_to_image('/images/calendar.png') +
"', showButtonPanel: true, showWeek: true, showOtherMonths: true, " + "', showButtonPanel: true};")
"selectOtherMonths: true, changeMonth: true, changeYear: true, " +
"beforeShow: beforeShowDatePicker};")
jquery_locale = l('jquery.locale', :default => current_language.to_s) jquery_locale = l('jquery.locale', :default => current_language.to_s)
unless jquery_locale == 'en' unless jquery_locale == 'en'
tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js") tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
@@ -1214,7 +1225,7 @@ module ApplicationHelper
def sanitize_anchor_name(anchor) def sanitize_anchor_name(anchor)
if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java' if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-') anchor.gsub(%r{[^\p{Word}\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
else else
# TODO: remove when ruby1.8 is no longer supported # TODO: remove when ruby1.8 is no longer supported
anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
@@ -1223,7 +1234,7 @@ module ApplicationHelper
# Returns the javascript tags that are included in the html layout head # Returns the javascript tags that are included in the html layout head
def javascript_heads def javascript_heads
tags = javascript_include_tag('jquery-1.8.3-ui-1.9.2-ujs-2.0.3', 'application') tags = javascript_include_tag('jquery-1.7.2-ui-1.8.21-ujs-2.0.3', 'application')
unless User.current.pref.warn_on_leaving_unsaved == '0' unless User.current.pref.warn_on_leaving_unsaved == '0'
tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });") tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
end end
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+2 -2
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -36,7 +36,7 @@ module ContextMenusHelper
def bulk_update_custom_field_context_menu_link(field, text, value) def bulk_update_custom_field_context_menu_link(field, text, value)
context_menu_link h(text), context_menu_link h(text),
bulk_update_issues_path(:ids => @issue_ids, :issue => {'custom_field_values' => {field.id => value}}, :back_url => @back), {:controller => 'issues', :action => 'bulk_update', :ids => @issue_ids, :issue => {'custom_field_values' => {field.id => value}}, :back_url => @back},
:method => :post, :method => :post,
:selected => (@issue && @issue.custom_field_value(field) == value) :selected => (@issue && @issue.custom_field_value(field) == value)
end end
+9 -42
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -19,29 +19,8 @@
module CustomFieldsHelper 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 custom_fields_tabs def custom_fields_tabs
CUSTOM_FIELDS_TABS CustomField::CUSTOM_FIELDS_TABS
end end
# Return custom field html tag corresponding to its format # Return custom field html tag corresponding to its format
@@ -98,44 +77,32 @@ module CustomFieldsHelper
custom_field_label_tag(name, custom_value, options) + custom_field_tag(name, custom_value) custom_field_label_tag(name, custom_value, options) + custom_field_tag(name, custom_value)
end end
def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil, value='') def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil)
field_name = "#{name}[custom_field_values][#{custom_field.id}]" field_name = "#{name}[custom_field_values][#{custom_field.id}]"
field_name << "[]" if custom_field.multiple? field_name << "[]" if custom_field.multiple?
field_id = "#{name}_custom_field_values_#{custom_field.id}" field_id = "#{name}_custom_field_values_#{custom_field.id}"
tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"} tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"}
unset_tag = ''
unless custom_field.is_required?
unset_tag = content_tag('label',
check_box_tag(field_name, '__none__', (value == '__none__'), :id => nil, :data => {:disables => "##{field_id}"}) + l(:button_clear),
:class => 'inline'
)
end
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format) field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
case field_format.try(:edit_as) case field_format.try(:edit_as)
when "date" when "date"
text_field_tag(field_name, value, tag_options.merge(:size => 10)) + text_field_tag(field_name, '', tag_options.merge(:size => 10)) +
calendar_for(field_id) + calendar_for(field_id)
unset_tag
when "text" when "text"
text_area_tag(field_name, value, tag_options.merge(:rows => 3)) + text_area_tag(field_name, '', tag_options.merge(:rows => 3))
'<br />'.html_safe +
unset_tag
when "bool" when "bool"
select_tag(field_name, options_for_select([[l(:label_no_change_option), ''], select_tag(field_name, options_for_select([[l(:label_no_change_option), ''],
[l(:general_text_yes), '1'], [l(:general_text_yes), '1'],
[l(:general_text_no), '0']], value), tag_options) [l(:general_text_no), '0']]), tag_options)
when "list" when "list"
options = [] options = []
options << [l(:label_no_change_option), ''] unless custom_field.multiple? options << [l(:label_no_change_option), ''] unless custom_field.multiple?
options << [l(:label_none), '__none__'] unless custom_field.is_required? options << [l(:label_none), '__none__'] unless custom_field.is_required?
options += custom_field.possible_values_options(projects) options += custom_field.possible_values_options(projects)
select_tag(field_name, options_for_select(options, value), tag_options.merge(:multiple => custom_field.multiple?)) select_tag(field_name, options_for_select(options), tag_options.merge(:multiple => custom_field.multiple?))
else else
text_field_tag(field_name, value, tag_options) + text_field_tag(field_name, '', tag_options)
unset_tag
end end
end end
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+5 -5
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -24,19 +24,19 @@ module GanttHelper
when :in when :in
if gantt.zoom < 4 if gantt.zoom < 4
link_to_content_update l(:text_zoom_in), link_to_content_update l(:text_zoom_in),
params.merge(gantt.params.merge(:zoom => (gantt.zoom + 1))), params.merge(gantt.params.merge(:zoom => (gantt.zoom+1))),
:class => 'icon icon-zoom-in' :class => 'icon icon-zoom-in'
else else
content_tag(:span, l(:text_zoom_in), :class => 'icon icon-zoom-in').html_safe content_tag('span', l(:text_zoom_in), :class => 'icon icon-zoom-in').html_safe
end end
when :out when :out
if gantt.zoom > 1 if gantt.zoom > 1
link_to_content_update l(:text_zoom_out), link_to_content_update l(:text_zoom_out),
params.merge(gantt.params.merge(:zoom => (gantt.zoom - 1))), params.merge(gantt.params.merge(:zoom => (gantt.zoom-1))),
:class => 'icon icon-zoom-out' :class => 'icon icon-zoom-out'
else else
content_tag(:span, l(:text_zoom_out), :class => 'icon icon-zoom-out').html_safe content_tag('span', l(:text_zoom_out), :class => 'icon icon-zoom-out').html_safe
end end
end end
end end
+1 -16
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -24,19 +24,4 @@ module GroupsHelper
{:name => 'memberships', :partial => 'groups/memberships', :label => :label_project_plural} {:name => 'memberships', :partial => 'groups/memberships', :label => :label_project_plural}
] ]
end end
def render_principals_for_new_group_users(group)
scope = User.active.sorted.not_in_group(group).like(params[:q])
principal_count = scope.count
principal_pages = Redmine::Pagination::Paginator.new principal_count, 100, params['page']
principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).all
s = content_tag('div', principals_check_box_tags('user_ids[]', principals), :id => 'principals')
links = pagination_links_full(principal_pages, principal_count, :per_page_links => false) {|text, parameters, options|
link_to text, autocomplete_for_user_group_path(group, parameters.merge(:q => params[:q], :format => 'js')), :remote => true
}
s + content_tag('p', links, :class => 'pagination')
end
end end
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+67 -85
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -94,20 +94,6 @@ module IssuesHelper
s.html_safe s.html_safe
end 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 # Returns a link for adding a new subtask to the given issue
def link_to_new_subtask(issue) def link_to_new_subtask(issue)
attrs = { attrs = {
@@ -160,13 +146,12 @@ module IssuesHelper
end end
def render_custom_fields_rows(issue) def render_custom_fields_rows(issue)
values = issue.visible_custom_field_values return if issue.custom_field_values.empty?
return if values.empty?
ordered_values = [] ordered_values = []
half = (values.size / 2.0).ceil half = (issue.custom_field_values.size / 2.0).ceil
half.times do |i| half.times do |i|
ordered_values << values[i] ordered_values << issue.custom_field_values[i]
ordered_values << values[i + half] ordered_values << issue.custom_field_values[i + half]
end end
s = "<tr>\n" s = "<tr>\n"
n = 0 n = 0
@@ -199,60 +184,36 @@ module IssuesHelper
def sidebar_queries def sidebar_queries
unless @sidebar_queries unless @sidebar_queries
@sidebar_queries = IssueQuery.visible. @sidebar_queries = Query.visible.all(
order("#{Query.table_name}.name ASC"). :order => "#{Query.table_name}.name ASC",
# Project specific queries and global queries # Project specific queries and global queries
where(@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id]). :conditions => (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id])
all )
end end
@sidebar_queries @sidebar_queries
end end
def query_links(title, queries) def query_links(title, queries)
return '' if queries.empty?
# links to #index on issues/show # links to #index on issues/show
url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params
content_tag('h3', title) + "\n" + content_tag('h3', h(title)) +
content_tag('ul',
queries.collect {|query| queries.collect {|query|
css = 'query' css = 'query'
css << ' selected' if query == @query css << ' selected' if query == @query
content_tag('li', link_to(query.name, url_params.merge(:query_id => query), :class => css)) link_to(h(query.name), url_params.merge(:query_id => query), :class => css)
}.join("\n").html_safe, }.join('<br />').html_safe
:class => 'queries'
) + "\n"
end end
def render_sidebar_queries def render_sidebar_queries
out = ''.html_safe out = ''.html_safe
out << query_links(l(:label_my_queries), sidebar_queries.select(&:is_private?)) queries = sidebar_queries.select {|q| !q.is_public?}
out << query_links(l(:label_query_plural), sidebar_queries.reject(&:is_private?)) 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 out
end end
def email_issue_attributes(issue, user)
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)}"
end
items
end
def render_email_issue_attributes(issue, user, html=false)
items = email_issue_attributes(issue, user)
if html
content_tag('ul', items.map{|s| content_tag('li', s)}.join("\n").html_safe)
else
items.map{|s| "* #{s}"}.join("\n")
end
end
# Returns the textual representation of a journal details # Returns the textual representation of a journal details
# as an array of strings # as an array of strings
def details_to_strings(details, no_html=false, options={}) def details_to_strings(details, no_html=false, options={})
@@ -261,23 +222,23 @@ module IssuesHelper
values_by_field = {} values_by_field = {}
details.each do |detail| details.each do |detail|
if detail.property == 'cf' if detail.property == 'cf'
field = detail.custom_field field_id = detail.prop_key
field = CustomField.find_by_id(field_id)
if field && field.multiple? if field && field.multiple?
values_by_field[field] ||= {:added => [], :deleted => []} values_by_field[field_id] ||= {:added => [], :deleted => []}
if detail.old_value if detail.old_value
values_by_field[field][:deleted] << detail.old_value values_by_field[field_id][:deleted] << detail.old_value
end end
if detail.value if detail.value
values_by_field[field][:added] << detail.value values_by_field[field_id][:added] << detail.value
end end
next next
end end
end end
strings << show_detail(detail, no_html, options) strings << show_detail(detail, no_html, options)
end end
values_by_field.each do |field, changes| values_by_field.each do |field_id, changes|
detail = JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s) detail = JournalDetail.new(:property => 'cf', :prop_key => field_id)
detail.instance_variable_set "@custom_field", field
if changes[:added].any? if changes[:added].any?
detail.value = changes[:added] detail.value = changes[:added]
strings << show_detail(detail, no_html, options) strings << show_detail(detail, no_html, options)
@@ -320,7 +281,7 @@ module IssuesHelper
old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank? old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank?
end end
when 'cf' when 'cf'
custom_field = detail.custom_field custom_field = CustomField.find_by_id(detail.prop_key)
if custom_field if custom_field
multiple = custom_field.multiple? multiple = custom_field.multiple?
label = custom_field.name label = custom_field.name
@@ -329,17 +290,6 @@ module IssuesHelper
end end
when 'attachment' when 'attachment'
label = l(:label_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))
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))
end
label = l(detail.prop_key.to_sym)
end end
call_hook(:helper_issues_show_detail_after_setting, call_hook(:helper_issues_show_detail_after_setting,
{:detail => detail, :label => label, :value => value, :old_value => old_value }) {:detail => detail, :label => label, :value => value, :old_value => old_value })
@@ -351,9 +301,7 @@ module IssuesHelper
unless no_html unless no_html
label = content_tag('strong', label) label = content_tag('strong', label)
old_value = content_tag("i", h(old_value)) if detail.old_value 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) if detail.old_value and detail.value.blank?
old_value = content_tag("del", old_value)
end
if detail.property == 'attachment' && !value.blank? && atta = Attachment.find_by_id(detail.prop_key) if detail.property == 'attachment' && !value.blank? && atta = Attachment.find_by_id(detail.prop_key)
# Link to the attachment if it has not been removed # Link to the attachment if it has not been removed
value = link_to_attachment(atta, :download => true, :only_path => options[:only_path]) value = link_to_attachment(atta, :download => true, :only_path => options[:only_path])
@@ -389,7 +337,7 @@ module IssuesHelper
else else
l(:text_journal_set_to, :label => label, :value => value).html_safe l(:text_journal_set_to, :label => label, :value => value).html_safe
end end
when 'attachment', 'relation' when 'attachment'
l(:text_journal_added, :label => label, :value => value).html_safe l(:text_journal_added, :label => label, :value => value).html_safe
end end
else else
@@ -399,16 +347,10 @@ module IssuesHelper
# Find the name of an associated record stored in the field attribute # Find the name of an associated record stored in the field attribute
def find_name_by_reflection(field, id) def find_name_by_reflection(field, id)
unless id.present?
return nil
end
association = Issue.reflect_on_association(field.to_sym) association = Issue.reflect_on_association(field.to_sym)
if association if association
record = association.class_name.constantize.find_by_id(id) record = association.class_name.constantize.find_by_id(id)
if record return record.name if record
record.name.force_encoding('UTF-8') if record.name.respond_to?(:force_encoding)
return record.name
end
end end
end end
@@ -425,4 +367,44 @@ module IssuesHelper
end end
end end
end end
def issues_to_csv(issues, project, query, options={})
decimal_separator = l(:general_csv_decimal_separator)
encoding = l(:general_csv_encoding)
columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns)
if options[:description]
if description = query.available_columns.detect {|q| q.name == :description}
columns << description
end
end
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
# csv header fields
csv << [ "#" ] + columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) }
# csv lines
issues.each do |issue|
col_values = columns.collect do |column|
s = if column.is_a?(QueryCustomFieldColumn)
cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id}
show_value(cv)
else
value = column.value(issue)
if value.is_a?(Date)
format_date(value)
elsif value.is_a?(Time)
format_time(value)
elsif value.is_a?(Float)
("%.2f" % value).gsub('.', decimal_separator)
else
value
end
end
s.to_s
end
csv << [ issue.id.to_s ] + col_values.collect {|c| Redmine::CodesetUtil.from_utf8(c.to_s, encoding) }
end
end
export
end
end end
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -15
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,18 +18,4 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module MembersHelper module MembersHelper
def render_principals_for_new_members(project)
scope = Principal.active.sorted.not_member_of(project).like(params[:q])
principal_count = scope.count
principal_pages = Redmine::Pagination::Paginator.new principal_count, 100, params['page']
principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).all
s = content_tag('div', principals_check_box_tags('membership[user_ids][]', principals), :id => 'principals')
links = pagination_links_full(principal_pages, principal_count, :per_page_links => false) {|text, parameters, options|
link_to text, autocomplete_project_memberships_path(project, parameters.merge(:q => params[:q], :format => 'js')), :remote => true
}
s + content_tag('p', links, :class => 'pagination')
end
end end
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -51
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,54 +18,4 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module MyHelper module MyHelper
def calendar_items(startdt, enddt)
Issue.visible.
where(:project_id => User.current.projects.map(&:id)).
where("(start_date>=? and start_date<=?) or (due_date>=? and due_date<=?)", startdt, enddt, startdt, enddt).
includes(:project, :tracker, :priority, :assigned_to).
all
end
def documents_items
Document.visible.order("#{Document.table_name}.created_on DESC").limit(10).all
end
def issuesassignedtome_items
Issue.visible.open.
where(:assigned_to_id => ([User.current.id] + User.current.group_ids)).
limit(10).
includes(:status, :project, :tracker, :priority).
order("#{IssuePriority.table_name}.position DESC, #{Issue.table_name}.updated_on DESC").
all
end
def issuesreportedbyme_items
Issue.visible.
where(:author_id => User.current.id).
limit(10).
includes(:status, :project, :tracker).
order("#{Issue.table_name}.updated_on DESC").
all
end
def issueswatched_items
Issue.visible.on_active_project.watched_by(User.current.id).recently_updated.limit(10).all
end
def news_items
News.visible.
where(:project_id => User.current.projects.map(&:id)).
limit(10).
includes(:project, :author).
order("#{News.table_name}.created_on DESC").
all
end
def timelog_items
TimeEntry.
where("#{TimeEntry.table_name}.user_id = ? AND #{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", User.current.id, Date.today - 6, Date.today).
includes(:activity, :project, {:issue => [:tracker, :status]}).
order("#{TimeEntry.table_name}.spent_on DESC, #{Project.table_name}.name ASC, #{Tracker.table_name}.position ASC, #{Issue.table_name}.id ASC").
all
end
end end
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+4 -5
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -46,7 +46,7 @@ module ProjectsHelper
end end
options = '' options = ''
options << "<option value=''>&nbsp;</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) 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') content_tag('select', options.html_safe, :name => 'project[parent_id]', :id => 'project_parent_id')
end end
@@ -69,11 +69,10 @@ module ProjectsHelper
grouped[version.project.name] << [version.name, version.id] grouped[version.project.name] << [version.name, version.id]
end end
selected = selected.is_a?(Version) ? selected.id : selected
if grouped.keys.size > 1 if grouped.keys.size > 1
grouped_options_for_select(grouped, selected) grouped_options_for_select(grouped, selected && selected.id)
else else
options_for_select((grouped.values.first || []), selected) options_for_select((grouped.values.first || []), selected && selected.id)
end end
end end
+50 -101
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -24,35 +24,32 @@ module QueriesHelper
def filters_options(query) def filters_options(query)
options = [[]] options = [[]]
options += query.available_filters.map do |field, field_options| sorted_options = query.available_filters.sort do |a, b|
ord = 0
if !(a[1][:order] == 20 && b[1][:order] == 20)
ord = a[1][:order] <=> b[1][:order]
else
cn = (CustomField::CUSTOM_FIELDS_NAMES.index(a[1][:field].class.name) <=>
CustomField::CUSTOM_FIELDS_NAMES.index(b[1][:field].class.name))
if cn != 0
ord = cn
else
f = (a[1][:field] <=> b[1][:field])
if f != 0
ord = f
else
# assigned_to or author
ord = (a[0] <=> b[0])
end
end
end
ord
end
options += sorted_options.map do |field, field_options|
[field_options[:name], field] [field_options[:name], field]
end end
end end
def query_filters_hidden_tags(query)
tags = ''.html_safe
query.filters.each do |field, options|
tags << hidden_field_tag("f[]", field, :id => nil)
tags << hidden_field_tag("op[#{field}]", options[:operator], :id => nil)
options[:values].each do |value|
tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
end
end
tags
end
def query_columns_hidden_tags(query)
tags = ''.html_safe
query.columns.each do |column|
tags << hidden_field_tag("c[]", column.name, :id => nil)
end
tags
end
def query_hidden_tags(query)
query_filters_hidden_tags(query) + query_columns_hidden_tags(query)
end
def available_block_columns_tags(query) def available_block_columns_tags(query)
tags = ''.html_safe tags = ''.html_safe
query.available_block_columns.each do |column| query.available_block_columns.each do |column|
@@ -61,19 +58,6 @@ module QueriesHelper
tags tags
end end
def query_available_inline_columns_options(query)
(query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
end
def query_selected_inline_columns_options(query)
(query.inline_columns & query.available_inline_columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
end
def render_query_columns_selection(query, options={})
tag_name = (options[:name] || 'c') + '[]'
render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name}
end
def column_header(column) def column_header(column)
column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption, column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
:default_order => column.default_order) : :default_order => column.default_order) :
@@ -103,16 +87,14 @@ module QueriesHelper
format_time(value) format_time(value)
when 'Date' when 'Date'
format_date(value) format_date(value)
when 'Fixnum' when 'Fixnum', 'Float'
if column.name == :id if column.name == :done_ratio
link_to value, issue_path(issue)
elsif column.name == :done_ratio
progress_bar(value, :width => '80px') progress_bar(value, :width => '80px')
else elsif column.name == :spent_hours
value.to_s
end
when 'Float'
sprintf "%.2f", value sprintf "%.2f", value
else
h(value.to_s)
end
when 'User' when 'User'
link_to_user value link_to_user value
when 'Project' when 'Project'
@@ -124,7 +106,7 @@ module QueriesHelper
when 'FalseClass' when 'FalseClass'
l(:general_text_No) l(:general_text_No)
when 'Issue' when 'Issue'
value.visible? ? link_to_issue(value) : "##{value.id}" link_to_issue(value, :subject => false)
when 'IssueRelation' when 'IssueRelation'
other = value.other_issue(issue) other = value.other_issue(issue)
content_tag('span', content_tag('span',
@@ -135,72 +117,26 @@ module QueriesHelper
end end
end end
def csv_content(column, issue)
value = column.value(issue)
if value.is_a?(Array)
value.collect {|v| csv_value(column, issue, v)}.compact.join(', ')
else
csv_value(column, issue, value)
end
end
def csv_value(column, issue, 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.to_s
end
end
def query_to_csv(items, query, options={})
encoding = l(:general_csv_encoding)
columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns)
query.available_block_columns.each do |column|
if options[column.name].present?
columns << column
end
end
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
# csv header fields
csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) }
# csv lines
items.each do |item|
csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(csv_content(c, item), encoding) }
end
end
export
end
# Retrieve query from session or build a new query # Retrieve query from session or build a new query
def retrieve_query def retrieve_query
if !params[:query_id].blank? if !params[:query_id].blank?
cond = "project_id IS NULL" cond = "project_id IS NULL"
cond << " OR project_id = #{@project.id}" if @project cond << " OR project_id = #{@project.id}" if @project
@query = IssueQuery.where(cond).find(params[:query_id]) @query = Query.find(params[:query_id], :conditions => cond)
raise ::Unauthorized unless @query.visible? raise ::Unauthorized unless @query.visible?
@query.project = @project @query.project = @project
session[:query] = {:id => @query.id, :project_id => @query.project_id} session[:query] = {:id => @query.id, :project_id => @query.project_id}
sort_clear sort_clear
elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil) elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
# Give it a name, required to be valid # Give it a name, required to be valid
@query = IssueQuery.new(:name => "_") @query = Query.new(:name => "_")
@query.project = @project @query.project = @project
@query.build_from_params(params) build_query_from_params
session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names} session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
else else
# retrieve from session # retrieve from session
@query = nil @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
@query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id] @query ||= Query.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
@query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
@query.project = @project @query.project = @project
end end
end end
@@ -208,10 +144,10 @@ module QueriesHelper
def retrieve_query_from_session def retrieve_query_from_session
if session[:query] if session[:query]
if session[:query][:id] if session[:query][:id]
@query = IssueQuery.find_by_id(session[:query][:id]) @query = Query.find_by_id(session[:query][:id])
return unless @query return unless @query
else else
@query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names]) @query = Query.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
end end
if session[:query].has_key?(:project_id) if session[:query].has_key?(:project_id)
@query.project_id = session[:query][:project_id] @query.project_id = session[:query][:project_id]
@@ -221,4 +157,17 @@ module QueriesHelper
@query @query
end end
end end
def build_query_from_params
if params[:fields] || params[:f]
@query.filters = {}
@query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v])
else
@query.available_filters.keys.each do |field|
@query.add_short_filter(field, params[field]) if params[field]
end
end
@query.group_by = params[:group_by] || (params[:query] && params[:query][:group_by])
@query.column_names = params[:c] || (params[:query] && params[:query][:column_names])
end
end end
+2 -7
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -24,7 +24,7 @@ module ReportsHelper
data.each { |row| data.each { |row|
match = 1 match = 1
criteria.each { |k, v| criteria.each { |k, v|
match = 0 unless (row[k].to_s == v.to_s) || (k == 'closed' && (v == 0 ? ['f', false] : ['t', true]).include?(row[k])) match = 0 unless (row[k].to_s == v.to_s) || (k == 'closed' && row[k] == (v == 0 ? "f" : "t"))
} unless criteria.nil? } unless criteria.nil?
a = a + row["total"].to_i if match == 1 a = a + row["total"].to_i if match == 1
} unless data.nil? } unless data.nil?
@@ -35,9 +35,4 @@ module ReportsHelper
a = aggregate data, criteria a = aggregate data, criteria
a > 0 ? link_to(h(a), *args) : '-' a > 0 ? link_to(h(a), *args) : '-'
end end
def aggregate_path(project, field, row, options={})
parameters = {:set_filter => 1, :subproject_id => '!*', field => row.id}.merge(options)
project_issues_path(row.is_a?(Project) ? row : project, parameters)
end
end end
+2 -2
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -43,7 +43,7 @@ module RepositoriesHelper
end end
def render_changeset_changes def render_changeset_changes
changes = @changeset.filechanges.limit(1000).reorder('path').all.collect do |change| changes = @changeset.filechanges.find(:all, :limit => 1000, :order => 'path').collect do |change|
case change.action case change.action
when 'A' when 'A'
# Detects moved/copied files # Detects moved/copied files
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+2 -2
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -53,7 +53,7 @@ module SettingsHelper
check_box_tag( check_box_tag(
"settings[#{setting}][]", "settings[#{setting}][]",
value, value,
setting_values.include?(value), Setting.send(setting).include?(value),
:id => nil :id => nil
) + text.to_s, ) + text.to_s,
:class => (options[:inline] ? 'inline' : 'block') :class => (options[:inline] ? 'inline' : 'block')
+2 -3
View File
@@ -80,13 +80,12 @@ module SortHelper
@criteria.collect {|k,o| k + (o ? '' : ':desc')}.join(',') @criteria.collect {|k,o| k + (o ? '' : ':desc')}.join(',')
end end
# Returns an array of SQL fragments used to sort the list
def to_sql def to_sql
sql = @criteria.collect do |k,o| sql = @criteria.collect do |k,o|
if s = @available_criteria[k] if s = @available_criteria[k]
(o ? s.to_a : s.to_a.collect {|c| append_desc(c)}) (o ? s.to_a : s.to_a.collect {|c| append_desc(c)}).join(', ')
end end
end.flatten.compact end.compact.join(', ')
sql.blank? ? nil : sql sql.blank? ? nil : sql
end end
+46 -3
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -86,6 +86,49 @@ module TimelogHelper
value) value)
end end
def entries_to_csv(entries)
decimal_separator = l(:general_csv_decimal_separator)
custom_fields = TimeEntryCustomField.find(:all)
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
# csv header fields
headers = [l(:field_spent_on),
l(:field_user),
l(:field_activity),
l(:field_project),
l(:field_issue),
l(:field_tracker),
l(:field_subject),
l(:field_hours),
l(:field_comments)
]
# Export custom fields
headers += custom_fields.collect(&:name)
csv << headers.collect {|c| Redmine::CodesetUtil.from_utf8(
c.to_s,
l(:general_csv_encoding) ) }
# csv lines
entries.each do |entry|
fields = [format_date(entry.spent_on),
entry.user,
entry.activity,
entry.project,
(entry.issue ? entry.issue.id : nil),
(entry.issue ? entry.issue.tracker : nil),
(entry.issue ? entry.issue.subject : nil),
entry.hours.to_s.gsub('.', decimal_separator),
entry.comments
]
fields += custom_fields.collect {|f| show_value(entry.custom_field_values.detect {|v| v.custom_field_id == f.id}) }
csv << fields.collect {|c| Redmine::CodesetUtil.from_utf8(
c.to_s,
l(:general_csv_encoding) ) }
end
end
export
end
def format_criteria_value(criteria_options, value) def format_criteria_value(criteria_options, value)
if value.blank? if value.blank?
"[#{l(:label_none)}]" "[#{l(:label_none)}]"
@@ -107,14 +150,14 @@ module TimelogHelper
# Column headers # Column headers
headers = report.criteria.collect {|criteria| l(report.available_criteria[criteria][:label]) } headers = report.criteria.collect {|criteria| l(report.available_criteria[criteria][:label]) }
headers += report.periods headers += report.periods
headers << l(:label_total_time) headers << l(:label_total)
csv << headers.collect {|c| Redmine::CodesetUtil.from_utf8( csv << headers.collect {|c| Redmine::CodesetUtil.from_utf8(
c.to_s, c.to_s,
l(:general_csv_encoding) ) } l(:general_csv_encoding) ) }
# Content # Content
report_criteria_to_csv(csv, report.available_criteria, report.columns, report.criteria, report.periods, report.hours) report_criteria_to_csv(csv, report.available_criteria, report.columns, report.criteria, report.periods, report.hours)
# Total row # Total row
str_total = Redmine::CodesetUtil.from_utf8(l(:label_total_time), l(:general_csv_encoding)) str_total = Redmine::CodesetUtil.from_utf8(l(:label_total), l(:general_csv_encoding))
row = [ str_total ] + [''] * (report.criteria.size - 1) row = [ str_total ] + [''] * (report.criteria.size - 1)
total = 0 total = 0
report.periods.each do |period| report.periods.each do |period|
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+6 -3
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # 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]} h = Hash.new {|k,v| k[v] = [0, 0]}
begin begin
# Total issue count # 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 # 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 rescue ActiveRecord::RecordNotFound
# When grouping by an association, Rails throws this exception if there's no result (bug) # When grouping by an association, Rails throws this exception if there's no result (bug)
end end
+16 -23
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -20,31 +20,24 @@
module WatchersHelper module WatchersHelper
def watcher_tag(object, user, options={}) def watcher_tag(object, user, options={})
ActiveSupport::Deprecation.warn "#watcher_tag is deprecated and will be removed in Redmine 3.0. Use #watcher_link instead." content_tag("span", watcher_link(object, user), :class => watcher_css(object))
watcher_link(object, user)
end end
def watcher_link(objects, user) def watcher_link(object, user)
return '' unless user && user.logged? return '' unless user && user.logged? && object.respond_to?('watched_by?')
objects = Array.wrap(objects) watched = object.watched_by?(user)
url = {:controller => 'watchers',
:action => (watched ? 'unwatch' : 'watch'),
:object_type => object.class.to_s.underscore,
:object_id => object.id}
link_to((watched ? l(:button_unwatch) : l(:button_watch)), url,
:remote => true, :method => 'post', :class => (watched ? 'icon icon-fav' : 'icon icon-fav-off'))
watched = Watcher.any_watched?(objects, 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(
:object_type => objects.first.class.to_s.underscore,
:object_id => (objects.size == 1 ? objects.first.id : objects.map(&:id).sort)
)
method = watched ? 'delete' : 'post'
link_to text, url, :remote => true, :method => method, :class => css
end end
# Returns the css class used to identify watch links for a given +object+ # Returns the css class used to identify watch links for a given +object+
def watcher_css(objects) def watcher_css(object)
objects = Array.wrap(objects) "#{object.class.to_s.underscore}-#{object.id}-watcher"
id = (objects.size == 1 ? objects.first.id : 'bulk')
"#{objects.first.class.to_s.underscore}-#{id}-watcher"
end end
# Returns a comma separated list of users watching the given object # Returns a comma separated list of users watching the given object
@@ -63,11 +56,11 @@ module WatchersHelper
:user_id => user} :user_id => user}
s << ' ' s << ' '
s << link_to(image_tag('delete.png'), url, s << link_to(image_tag('delete.png'), url,
:remote => true, :method => 'delete', :class => "delete") :remote => true, :method => 'post', :style => "vertical-align: middle", :class => "delete")
end end
content << content_tag('li', s, :class => "user-#{user.id}") content << content_tag('li', s)
end end
content.present? ? content_tag('ul', content, :class => 'watchers') : content content.present? ? content_tag('ul', content) : content
end end
def watchers_checkboxes(object, users, checked=nil) def watchers_checkboxes(object, users, checked=nil)
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+3 -12
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -22,20 +22,11 @@ module WorkflowsHelper
field.is_a?(CustomField) ? field.is_required? : %w(project_id tracker_id subject priority_id is_private).include?(field) field.is_a?(CustomField) ? field.is_required? : %w(project_id tracker_id subject priority_id is_private).include?(field)
end end
def field_permission_tag(permissions, status, field, role) def field_permission_tag(permissions, status, field)
name = field.is_a?(CustomField) ? field.id.to_s : field name = field.is_a?(CustomField) ? field.id.to_s : field
options = [["", ""], [l(:label_readonly), "readonly"]] options = [["", ""], [l(:label_readonly), "readonly"]]
options << [l(:label_required), "required"] unless field_required?(field) options << [l(:label_required), "required"] unless field_required?(field)
html_options = {}
selected = permissions[status.id][name]
hidden = field.is_a?(CustomField) && !field.visible? && !role.custom_fields.to_a.include?(field) select_tag("permissions[#{name}][#{status.id}]", options_for_select(options, permissions[status.id][name]))
if hidden
options[0][0] = l(:label_hidden)
selected = ''
html_options[:disabled] = true
end
select_tag("permissions[#{name}][#{status.id}]", options_for_select(options, selected), html_options)
end end
end end
+11 -50
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -16,7 +16,6 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require "digest/md5" require "digest/md5"
require "fileutils"
class Attachment < ActiveRecord::Base class Attachment < ActiveRecord::Base
belongs_to :container, :polymorphic => true belongs_to :container, :polymorphic => true
@@ -93,6 +92,9 @@ class Attachment < ActiveRecord::Base
def filename=(arg) def filename=(arg)
write_attribute :filename, sanitize_filename(arg.to_s) write_attribute :filename, sanitize_filename(arg.to_s)
if new_record? && disk_filename.blank?
self.disk_filename = Attachment.disk_filename(filename)
end
filename filename
end end
@@ -100,13 +102,7 @@ class Attachment < ActiveRecord::Base
# and computes its MD5 hash # and computes its MD5 hash
def files_to_final_location def files_to_final_location
if @temp_file && (@temp_file.size > 0) if @temp_file && (@temp_file.size > 0)
self.disk_directory = target_directory logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)")
self.disk_filename = Attachment.disk_filename(filename, disk_directory)
logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)") if logger
path = File.dirname(diskfile)
unless File.directory?(path)
FileUtils.mkdir_p(path)
end
md5 = Digest::MD5.new md5 = Digest::MD5.new
File.open(diskfile, "wb") do |f| File.open(diskfile, "wb") do |f|
if @temp_file.respond_to?(:read) if @temp_file.respond_to?(:read)
@@ -138,7 +134,7 @@ class Attachment < ActiveRecord::Base
# Returns file's location on disk # Returns file's location on disk
def diskfile def diskfile
File.join(self.class.storage_path, disk_directory.to_s, disk_filename.to_s) File.join(self.class.storage_path, disk_filename.to_s)
end end
def title def title
@@ -158,19 +154,11 @@ class Attachment < ActiveRecord::Base
end end
def visible?(user=User.current) def visible?(user=User.current)
if container_id
container && container.attachments_visible?(user) container && container.attachments_visible?(user)
else
author == user
end
end end
def deletable?(user=User.current) def deletable?(user=User.current)
if container_id
container && container.attachments_deletable?(user) container && container.attachments_deletable?(user)
else
author == user
end
end end
def image? def image?
@@ -263,26 +251,6 @@ class Attachment < ActiveRecord::Base
Attachment.where("created_on < ? AND (container_type IS NULL OR container_type = '')", Time.now - age).destroy_all Attachment.where("created_on < ? AND (container_type IS NULL OR container_type = '')", Time.now - age).destroy_all
end end
# Moves an existing attachment to its target directory
def move_to_target_directory!
if !new_record? & readable?
src = diskfile
self.disk_directory = target_directory
dest = diskfile
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
def self.move_from_root_to_target_directory
Attachment.where("disk_directory IS NULL OR disk_directory = ''").find_each do |attachment|
attachment.move_to_target_directory!
end
end
private private
# Physically deletes the file from the file system # Physically deletes the file from the file system
@@ -294,21 +262,14 @@ class Attachment < ActiveRecord::Base
def sanitize_filename(value) def sanitize_filename(value)
# get only the filename, not the whole path # get only the filename, not the whole path
just_filename = value.gsub(/\A.*(\\|\/)/m, '') just_filename = value.gsub(/^.*(\\|\/)/, '')
# Finally, replace invalid characters with underscore # Finally, replace invalid characters with underscore
@filename = just_filename.gsub(/[\/\?\%\*\:\|\"\'<>\n\r]+/, '_') @filename = just_filename.gsub(/[\/\?\%\*\:\|\"\'<>]+/, '_')
end end
# Returns the subdirectory in which the attachment will be saved # Returns an ASCII or hashed filename
def target_directory def self.disk_filename(filename)
time = created_on || DateTime.now
time.strftime("%Y/%m")
end
# Returns an ASCII or hashed filename that do not
# exists yet in the given subdirectory
def self.disk_filename(filename, directory=nil)
timestamp = DateTime.now.strftime("%y%m%d%H%M%S") timestamp = DateTime.now.strftime("%y%m%d%H%M%S")
ascii = '' ascii = ''
if filename =~ %r{^[a-zA-Z0-9_\.\-]*$} if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
@@ -318,7 +279,7 @@ class Attachment < ActiveRecord::Base
# keep the extension if any # keep the extension if any
ascii << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$} ascii << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
end end
while File.exist?(File.join(storage_path, directory.to_s, "#{timestamp}_#{ascii}")) while File.exist?(File.join(@@storage_path, "#{timestamp}_#{ascii}"))
timestamp.succ! timestamp.succ!
end end
"#{timestamp}_#{ascii}" "#{timestamp}_#{ascii}"
+1 -19
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -48,24 +48,6 @@ class AuthSource < ActiveRecord::Base
write_ciphered_attribute(:account_password, arg) write_ciphered_attribute(:account_password, arg)
end end
def searchable?
false
end
def self.search(q)
results = []
AuthSource.all.each do |source|
begin
if source.searchable?
results += source.search(q)
end
rescue AuthSourceException => e
logger.error "Error while searching users in #{source.name}: #{e.message}"
end
end
results
end
def allow_password_changes? def allow_password_changes?
self.class.allow_password_changes? self.class.allow_password_changes?
end end
+9 -36
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -15,6 +15,7 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'iconv'
require 'net/ldap' require 'net/ldap'
require 'net/ldap/dn' require 'net/ldap/dn'
require 'timeout' require 'timeout'
@@ -63,32 +64,6 @@ class AuthSourceLdap < AuthSource
"LDAP" "LDAP"
end end
# Returns true if this source can be searched for users
def searchable?
!account.to_s.include?("$login") && %w(login firstname lastname mail).all? {|a| send("attr_#{a}?")}
end
# Searches the source for users and returns an array of results
def search(q)
q = q.to_s.strip
return [] unless searchable? && q.present?
results = []
search_filter = base_filter & Net::LDAP::Filter.begins(self.attr_login, q)
ldap_con = initialize_ldap_con(self.account, self.account_password)
ldap_con.search(:base => self.base_dn,
:filter => search_filter,
:attributes => ['dn', self.attr_login, self.attr_firstname, self.attr_lastname, self.attr_mail],
:size => 10) do |entry|
attrs = get_user_attributes_from_ldap_entry(entry)
attrs[:login] = AuthSourceLdap.get_attr(entry, self.attr_login)
results << attrs
end
results
rescue Net::LDAP::LdapError => e
raise AuthSourceException.new(e.message)
end
private private
def with_timeout(&block) def with_timeout(&block)
@@ -109,14 +84,6 @@ class AuthSourceLdap < AuthSource
nil nil
end end
def base_filter
filter = Net::LDAP::Filter.eq("objectClass", "*")
if f = ldap_filter
filter = filter & f
end
filter
end
def validate_filter def validate_filter
if filter.present? && ldap_filter.nil? if filter.present? && ldap_filter.nil?
errors.add(:filter, :invalid) errors.add(:filter, :invalid)
@@ -173,8 +140,14 @@ class AuthSourceLdap < AuthSource
else else
ldap_con = initialize_ldap_con(self.account, self.account_password) ldap_con = initialize_ldap_con(self.account, self.account_password)
end end
login_filter = Net::LDAP::Filter.eq( self.attr_login, login )
object_filter = Net::LDAP::Filter.eq( "objectClass", "*" )
attrs = {} attrs = {}
search_filter = base_filter & Net::LDAP::Filter.eq(self.attr_login, login)
search_filter = object_filter & login_filter
if f = ldap_filter
search_filter = search_filter & f
end
ldap_con.search( :base => self.base_dn, ldap_con.search( :base => self.base_dn,
:filter => search_filter, :filter => search_filter,
+3 -4
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -30,9 +30,8 @@ class Board < ActiveRecord::Base
validates_length_of :description, :maximum => 255 validates_length_of :description, :maximum => 255
validate :validate_board validate :validate_board
scope :visible, lambda {|*args| scope :visible, lambda {|*args| { :include => :project,
includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_messages, *args)) :conditions => Project.allowed_to_condition(args.shift || User.current, :view_messages, *args) } }
}
safe_attributes 'name', 'description', 'parent_id', 'move_to' safe_attributes 'name', 'description', 'parent_id', 'move_to'
+1 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+21 -23
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # 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 # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'iconv'
class Changeset < ActiveRecord::Base class Changeset < ActiveRecord::Base
belongs_to :repository belongs_to :repository
belongs_to :user belongs_to :user
@@ -47,9 +49,9 @@ class Changeset < ActiveRecord::Base
validates_uniqueness_of :revision, :scope => :repository_id validates_uniqueness_of :revision, :scope => :repository_id
validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
scope :visible, lambda {|*args| scope :visible,
includes(:repository => :project).where(Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args)) lambda {|*args| { :include => {:repository => :project},
} :conditions => Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args) } }
after_create :scan_for_issues after_create :scan_for_issues
before_create :before_create_cs before_create :before_create_cs
@@ -118,28 +120,25 @@ class Changeset < ActiveRecord::Base
ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip) ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
ref_keywords_any = ref_keywords.delete('*') ref_keywords_any = ref_keywords.delete('*')
# keywords used to fix issues # 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("|") kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
referenced_issues = [] referenced_issues = []
comments.scan(/([\s\(\[,-]|^)((#{kw_regexp})[\s:]+)?(#\d+(\s+@#{TIMELOG_RE})?([\s,;&]+#\d+(\s+@#{TIMELOG_RE})?)*)(?=[[:punct:]]|\s|<|$)/i) do |match| 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 next unless action.present? || ref_keywords_any
refs.scan(/#(\d+)(\s+@#{TIMELOG_RE})?/).each do |m| refs.scan(/#(\d+)(\s+@#{TIMELOG_RE})?/).each do |m|
issue, hours = find_referenced_issue_by_id(m[0].to_i), m[2] issue, hours = find_referenced_issue_by_id(m[0].to_i), m[2]
if issue if issue
referenced_issues << issue referenced_issues << issue
# Don't update issues or log time when importing old commits fix_issue(issue) if fix_keywords.include?(action.to_s.downcase)
unless repository.created_on && committed_on && committed_on < repository.created_on
fix_issue(issue, action) if fix_keywords.include?(action)
log_time(issue, hours) if hours && Setting.commit_logtime_enabled? log_time(issue, hours) if hours && Setting.commit_logtime_enabled?
end end
end end
end end
end
referenced_issues.uniq! referenced_issues.uniq!
self.issues = referenced_issues unless referenced_issues.empty? self.issues = referenced_issues unless referenced_issues.empty?
@@ -213,26 +212,25 @@ class Changeset < ActiveRecord::Base
private private
# Updates the +issue+ according to +action+ def fix_issue(issue)
def fix_issue(issue, action) 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) # the issue may have been updated by the closure of another one (eg. duplicate)
issue.reload issue.reload
# don't change the status is the issue is closed # don't change the status is the issue is closed
return if issue.status && issue.status.is_closed? return if issue.status && issue.status.is_closed?
journal = issue.init_journal(user || User.anonymous, journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag(issue.project)))
ll(Setting.default_language, issue.status = status
:text_status_changed_by_changeset, unless Setting.commit_fix_done_ratio.blank?
text_tag(issue.project))) issue.done_ratio = Setting.commit_fix_done_ratio.to_i
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)
end end
Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update, 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 unless issue.save
logger.warn("Issue ##{issue.id} could not be saved by changeset #{id}: #{issue.errors.full_messages}") if logger logger.warn("Issue ##{issue.id} could not be saved by changeset #{id}: #{issue.errors.full_messages}") if logger
end end
+1 -12
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # 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 validates_presence_of :commented, :author, :comments
after_create :send_notification
safe_attributes 'comments' 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 end
@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -17,17 +15,10 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module ActivitiesHelper class CommentObserver < ActiveRecord::Observer
def sort_activity_events(events) def after_create(comment)
events_by_group = events.group_by(&:event_group) if comment.commented.is_a?(News) && Setting.notified_events.include?('news_comment_added')
sorted_events = [] Mailer.news_comment_added(comment).deliver
events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each do |event|
if group_events = events_by_group.delete(event.event_group)
group_events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each_with_index do |e, i|
sorted_events << [e, i > 0]
end end
end end
end
sorted_events
end
end end
+39 -101
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -19,7 +19,6 @@ class CustomField < ActiveRecord::Base
include Redmine::SubclassFactory include Redmine::SubclassFactory
has_many :custom_values, :dependent => :delete_all 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}\'' acts_as_list :scope => 'type = \'#{self.class}\''
serialize :possible_values serialize :possible_values
@@ -27,35 +26,32 @@ class CustomField < ActiveRecord::Base
validates_uniqueness_of :name, :scope => :type validates_uniqueness_of :name, :scope => :type
validates_length_of :name, :maximum => 30 validates_length_of :name, :maximum => 30
validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats
validate :validate_custom_field validate :validate_custom_field
before_validation :set_searchable before_validation :set_searchable
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") } CUSTOM_FIELDS_TABS = [
scope :visible, lambda {|*args| {:name => 'IssueCustomField', :partial => 'custom_fields/index',
user = args.shift || User.current :label => :label_issue_plural},
if user.admin? {:name => 'TimeEntryCustomField', :partial => 'custom_fields/index',
# nop :label => :label_spent_time},
elsif user.memberships.any? {:name => 'ProjectCustomField', :partial => 'custom_fields/index',
where("#{table_name}.visible = ? OR #{table_name}.id IN (SELECT DISTINCT cfr.custom_field_id FROM #{Member.table_name} m" + :label => :label_project_plural},
" INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" + {:name => 'VersionCustomField', :partial => 'custom_fields/index',
" INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" + :label => :label_version_plural},
" WHERE m.user_id = ?)", {:name => 'UserCustomField', :partial => 'custom_fields/index',
true, user.id) :label => :label_user_plural},
else {:name => 'GroupCustomField', :partial => 'custom_fields/index',
where(:visible => true) :label => :label_group_plural},
end {: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 visible_by?(project, user=User.current) CUSTOM_FIELDS_NAMES = CUSTOM_FIELDS_TABS.collect{|v| v[:name]}
visible? || user.admin?
end
def field_format=(arg) def field_format=(arg)
# cannot change format of a saved custom field # cannot change format of a saved custom field
@@ -123,10 +119,8 @@ class CustomField < ActiveRecord::Base
values.each do |value| values.each do |value|
value.force_encoding('UTF-8') if value.respond_to?(:force_encoding) value.force_encoding('UTF-8') if value.respond_to?(:force_encoding)
end end
values
else
[]
end end
values || []
end end
end end
@@ -184,12 +178,18 @@ class CustomField < ActiveRecord::Base
case field_format case field_format
when 'string', 'text', 'list', 'date', 'bool' when 'string', 'text', 'list', 'date', 'bool'
# COALESCE is here to make sure that blank and NULL values are sorted equally # COALESCE is here to make sure that blank and NULL values are sorted equally
"COALESCE(#{join_alias}.value, '')" "COALESCE((SELECT cv_sort.value FROM #{CustomValue.table_name} cv_sort" +
" WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" +
" AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" +
" AND cv_sort.custom_field_id=#{id} LIMIT 1), '')"
when 'int', 'float' when 'int', 'float'
# Make the database cast values into numeric # Make the database cast values into numeric
# Postgresql will raise an error if a value can not be casted! # Postgresql will raise an error if a value can not be casted!
# CustomValue validations should ensure that it doesn't occur # 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))" "(SELECT CAST(cv_sort.value AS decimal(60,3)) FROM #{CustomValue.table_name} cv_sort" +
" WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" +
" AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" +
" AND cv_sort.custom_field_id=#{id} AND cv_sort.value <> '' AND cv_sort.value IS NOT NULL LIMIT 1)"
when 'user', 'version' when 'user', 'version'
value_class.fields_for_order_statement(value_join_alias) value_class.fields_for_order_statement(value_join_alias)
else else
@@ -205,7 +205,10 @@ class CustomField < ActiveRecord::Base
when 'list', 'date', 'bool', 'int' when 'list', 'date', 'bool', 'int'
order_statement order_statement
when 'user', 'version' when 'user', 'version'
"COALESCE(#{join_alias}.value, '')" "COALESCE((SELECT cv_sort.value FROM #{CustomValue.table_name} cv_sort" +
" WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" +
" AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" +
" AND cv_sort.custom_field_id=#{id} LIMIT 1), '')"
else else
nil nil
end end
@@ -218,35 +221,13 @@ class CustomField < ActiveRecord::Base
" ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" + " 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}.customized_id = #{self.class.customized_class.table_name}.id" +
" AND #{join_alias}.custom_field_id = #{id}" + " AND #{join_alias}.custom_field_id = #{id}" +
" AND (#{visibility_by_project_condition})" +
" AND #{join_alias}.value <> ''" + " AND #{join_alias}.value <> ''" +
" AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" + " 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" + " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
" AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" + " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
" AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" + " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" +
" LEFT OUTER JOIN #{value_class.table_name} #{value_join_alias}" + " 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" " ON CAST(#{join_alias}.value as decimal(60,0)) = #{value_join_alias}.id"
when 'int', 'float'
"LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
" ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
" AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
" AND #{join_alias}.custom_field_id = #{id}" +
" AND (#{visibility_by_project_condition})" +
" AND #{join_alias}.value <> ''" +
" AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
" WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
" AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
" AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)"
when 'string', 'text', 'list', 'date', 'bool'
"LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
" ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
" AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
" AND #{join_alias}.custom_field_id = #{id}" +
" AND (#{visibility_by_project_condition})" +
" AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
" WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
" AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
" AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)"
else else
nil nil
end end
@@ -260,33 +241,6 @@ class CustomField < ActiveRecord::Base
join_alias + "_" + field_format join_alias + "_" + field_format
end end
def visibility_by_project_condition(project_key=nil, user=User.current)
if visible? || user.admin?
"1=1"
elsif user.anonymous?
"1=0"
else
project_key ||= "#{self.class.customized_class.table_name}.project_id"
"#{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})"
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})"
end
end
def <=>(field) def <=>(field)
position <=> field.position position <=> field.position
end end
@@ -303,12 +257,12 @@ class CustomField < ActiveRecord::Base
def self.customized_class def self.customized_class
self.name =~ /^(.+)CustomField$/ self.name =~ /^(.+)CustomField$/
$1.constantize rescue nil begin; $1.constantize; rescue nil; end
end end
# to move in project_custom_field # to move in project_custom_field
def self.for_all def self.for_all
where(:is_for_all => true).order('position').all find(:all, :conditions => ["is_for_all=?", true], :order => 'position')
end end
def type_name def type_name
@@ -369,20 +323,4 @@ class CustomField < ActiveRecord::Base
end end
errs errs
end 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
if !new_record? && multiple_was && !multiple
ids = custom_values.
where("EXISTS(SELECT 1 FROM #{CustomValue.table_name} cve WHERE cve.custom_field_id = #{CustomValue.table_name}.custom_field_id" +
" AND cve.customized_type = #{CustomValue.table_name}.customized_type AND cve.customized_id = #{CustomValue.table_name}.customized_id" +
" AND cve.id > #{CustomValue.table_name}.id)").
pluck(:id)
if ids.any?
custom_values.where(:id => ids).delete_all
end
end
end
end end
+1 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2012 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
Loaded 100 of 999 files, more files were not shown because too many files have changed in this diff. Show more