Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c7b8c8192 | ||
|
|
f26654b29f | ||
|
|
5c62c1cf95 | ||
|
|
2d6adbd7ff | ||
|
|
b0ccaffe1b | ||
|
|
2c37617973 | ||
|
|
567eb70fdb | ||
|
|
de63102925 | ||
|
|
a8fcf9389e | ||
|
|
3e4b36de83 | ||
|
|
edd584d59e | ||
|
|
e2b27ab696 | ||
|
|
1968d039ee | ||
|
|
fddc33cd45 | ||
|
|
29518ba0cf | ||
|
|
acd27b9eb1 | ||
|
|
6445b5997c | ||
|
|
416d33973f | ||
|
|
82c7dc11d2 | ||
|
|
d22f782d3f | ||
|
|
3717fdfa79 | ||
|
|
9d3d932703 | ||
|
|
3a379ce4b9 | ||
|
|
daa77f0109 | ||
|
|
c0d66d626b | ||
|
|
bf02b76ca3 | ||
|
|
75e02ca486 |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,7 +1,5 @@
|
||||
/.project
|
||||
/.loadpath
|
||||
/.powrc
|
||||
/.rvmrc
|
||||
/config/additional_environment.rb
|
||||
/config/configuration.yml
|
||||
/config/database.yml
|
||||
@@ -17,14 +15,8 @@
|
||||
/lib/redmine/scm/adapters/mercurial/redminehelper.pyo
|
||||
/log/*.log*
|
||||
/log/mongrel_debug
|
||||
/plugins/*
|
||||
!/plugins/README
|
||||
/public/dispatch.*
|
||||
/public/plugin_assets
|
||||
/public/themes/*
|
||||
!/public/themes/alternate
|
||||
!/public/themes/classic
|
||||
!/public/themes/README
|
||||
/tmp/*
|
||||
/tmp/cache/*
|
||||
/tmp/pdf/*
|
||||
|
||||
@@ -2,8 +2,6 @@ syntax: glob
|
||||
|
||||
.project
|
||||
.loadpath
|
||||
.powrc
|
||||
.rvmrc
|
||||
config/additional_environment.rb
|
||||
config/configuration.yml
|
||||
config/database.yml
|
||||
|
||||
@@ -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
|
||||
|
||||
97
Gemfile
97
Gemfile
@@ -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 "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 "builder", "3.0.0"
|
||||
|
||||
@@ -14,7 +14,7 @@ end
|
||||
|
||||
# Optional gem for OpenID authentication
|
||||
group :openid do
|
||||
gem "ruby-openid", "~> 2.3.0", :require => "openid"
|
||||
gem "ruby-openid", "~> 2.1.4", :require => "openid"
|
||||
gem "rack-openid"
|
||||
end
|
||||
|
||||
@@ -28,47 +28,43 @@ platforms :mri, :mingw do
|
||||
end
|
||||
end
|
||||
|
||||
platforms :jruby do
|
||||
# jruby-openssl is bundled with JRuby 1.7.0
|
||||
gem "jruby-openssl" if Object.const_defined?(:JRUBY_VERSION) && JRUBY_VERSION < '1.7.0'
|
||||
gem "activerecord-jdbc-adapter", "~> 1.2.6"
|
||||
# Database gems
|
||||
platforms :mri, :mingw do
|
||||
group :postgresql do
|
||||
gem "pg", ">= 0.11.0"
|
||||
end
|
||||
|
||||
group :sqlite do
|
||||
gem "sqlite3"
|
||||
end
|
||||
end
|
||||
|
||||
# Include database gems for the adapters found in the database
|
||||
# configuration file
|
||||
require 'erb'
|
||||
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
|
||||
else
|
||||
warn("No adapter found in config/database.yml, please configure it first")
|
||||
platforms :mri_18, :mingw_18 do
|
||||
group :mysql do
|
||||
gem "mysql", "~> 2.8.1"
|
||||
end
|
||||
end
|
||||
|
||||
platforms :mri_19, :mingw_19 do
|
||||
group :mysql do
|
||||
gem "mysql2", "~> 0.3.11"
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
else
|
||||
warn("Please configure your config/database.yml first")
|
||||
end
|
||||
|
||||
group :development do
|
||||
@@ -77,13 +73,13 @@ group :development do
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem "shoulda", "~> 3.3.2"
|
||||
gem "mocha", ">= 0.14", :require => 'mocha/api'
|
||||
if RUBY_VERSION >= '1.9.3'
|
||||
gem "capybara", "~> 2.1.0"
|
||||
gem "selenium-webdriver"
|
||||
gem "database_cleaner"
|
||||
end
|
||||
gem "shoulda", "~> 2.11"
|
||||
# Shoulda does not work nice on Ruby 1.9.3 and JRuby 1.7.
|
||||
# It seems to need test-unit explicitely.
|
||||
platforms = [:mri_19]
|
||||
platforms << :jruby if defined?(JRUBY_VERSION) && JRUBY_VERSION >= "1.7"
|
||||
gem "test-unit", :platforms => platforms
|
||||
gem "mocha", "0.12.3"
|
||||
end
|
||||
|
||||
local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local")
|
||||
@@ -95,6 +91,5 @@ end
|
||||
# Load plugins' Gemfiles
|
||||
Dir.glob File.expand_path("../plugins/*/Gemfile", __FILE__) do |file|
|
||||
puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v`
|
||||
#TODO: switch to "eval_gemfile file" when bundler >= 1.2.0 will be required (rails 4)
|
||||
instance_eval File.read(file), file
|
||||
instance_eval File.read(file)
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -20,14 +20,12 @@ class AccountController < ApplicationController
|
||||
include CustomFieldsHelper
|
||||
|
||||
# prevents login action to be filtered by check_if_login_required application scope filter
|
||||
skip_before_filter :check_if_login_required, :check_password_change
|
||||
skip_before_filter :check_if_login_required
|
||||
|
||||
# Login request and validation
|
||||
def login
|
||||
if request.get?
|
||||
if User.current.logged?
|
||||
redirect_to home_url
|
||||
end
|
||||
logout_user
|
||||
else
|
||||
authenticate_user
|
||||
end
|
||||
@@ -38,20 +36,15 @@ class AccountController < ApplicationController
|
||||
|
||||
# Log out current user and redirect to welcome page
|
||||
def logout
|
||||
if User.current.anonymous?
|
||||
redirect_to home_url
|
||||
elsif request.post?
|
||||
logout_user
|
||||
redirect_to home_url
|
||||
end
|
||||
# display the logout form
|
||||
logout_user
|
||||
redirect_to home_url
|
||||
end
|
||||
|
||||
# Lets user choose a new 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]
|
||||
@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?
|
||||
redirect_to home_url
|
||||
return
|
||||
@@ -75,15 +68,11 @@ class AccountController < ApplicationController
|
||||
else
|
||||
if request.post?
|
||||
user = User.find_by_mail(params[:mail].to_s)
|
||||
# user not found
|
||||
unless user
|
||||
# user not found or not active
|
||||
unless user && user.active?
|
||||
flash.now[:error] = l(:notice_account_unknown_email)
|
||||
return
|
||||
end
|
||||
unless user.active?
|
||||
handle_inactive_user(user, lost_password_path)
|
||||
return
|
||||
end
|
||||
# user cannot change its password
|
||||
unless user.change_password_allowed?
|
||||
flash.now[:error] = l(:notice_can_t_change_password)
|
||||
@@ -103,10 +92,10 @@ class AccountController < ApplicationController
|
||||
|
||||
# User self-registration
|
||||
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?
|
||||
session[:auth_source_registration] = nil
|
||||
@user = User.new(:language => current_language.to_s)
|
||||
@user = User.new(:language => Setting.default_language)
|
||||
else
|
||||
user_params = params[:user] || {}
|
||||
@user = User.new
|
||||
@@ -121,7 +110,7 @@ class AccountController < ApplicationController
|
||||
session[:auth_source_registration] = nil
|
||||
self.logged_user = @user
|
||||
flash[:notice] = l(:notice_account_activated)
|
||||
redirect_to my_account_path
|
||||
redirect_to :controller => 'my', :action => 'account'
|
||||
end
|
||||
else
|
||||
@user.login = params[:user][:login]
|
||||
@@ -143,11 +132,11 @@ class AccountController < ApplicationController
|
||||
|
||||
# Token based account activation
|
||||
def activate
|
||||
(redirect_to(home_url); return) unless Setting.self_registration? && params[:token].present?
|
||||
token = Token.find_token('register', params[:token].to_s)
|
||||
(redirect_to(home_url); return) unless token and !token.expired?
|
||||
redirect_to(home_url) && return unless Setting.self_registration? && params[:token]
|
||||
token = Token.find_by_action_and_value('register', params[:token])
|
||||
redirect_to(home_url) && return unless token and !token.expired?
|
||||
user = token.user
|
||||
(redirect_to(home_url); return) unless user.registered?
|
||||
redirect_to(home_url) && return unless user.registered?
|
||||
user.activate
|
||||
if user.save
|
||||
token.destroy
|
||||
@@ -156,19 +145,6 @@ class AccountController < ApplicationController
|
||||
redirect_to signin_path
|
||||
end
|
||||
|
||||
# Sends a new account activation email
|
||||
def activation_email
|
||||
if session[:registered_user_id] && Setting.self_registration == '1'
|
||||
user_id = session.delete(:registered_user_id).to_i
|
||||
user = User.find_by_id(user_id)
|
||||
if user && user.registered?
|
||||
register_by_email_activation(user)
|
||||
return
|
||||
end
|
||||
end
|
||||
redirect_to(home_url)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authenticate_user
|
||||
@@ -180,7 +156,7 @@ class AccountController < ApplicationController
|
||||
end
|
||||
|
||||
def password_authentication
|
||||
user = User.try_to_login(params[:username], params[:password], false)
|
||||
user = User.try_to_login(params[:username], params[:password])
|
||||
|
||||
if user.nil?
|
||||
invalid_credentials
|
||||
@@ -188,31 +164,25 @@ class AccountController < ApplicationController
|
||||
onthefly_creation_failed(user, {:login => user.login, :auth_source_id => user.auth_source_id })
|
||||
else
|
||||
# Valid user
|
||||
if user.active?
|
||||
successful_authentication(user)
|
||||
else
|
||||
handle_inactive_user(user)
|
||||
end
|
||||
successful_authentication(user)
|
||||
end
|
||||
end
|
||||
|
||||
def open_id_authenticate(openid_url)
|
||||
back_url = signin_url(:autologin => params[:autologin])
|
||||
authenticate_with_open_id(
|
||||
openid_url, :required => [:nickname, :fullname, :email],
|
||||
:return_to => back_url, :method => :post
|
||||
) do |result, identity_url, registration|
|
||||
authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => signin_url, :method => :post) do |result, identity_url, registration|
|
||||
if result.successful?
|
||||
user = User.find_or_initialize_by_identity_url(identity_url)
|
||||
if user.new_record?
|
||||
# Self-registration off
|
||||
(redirect_to(home_url); return) unless Setting.self_registration?
|
||||
redirect_to(home_url) && return unless Setting.self_registration?
|
||||
|
||||
# Create on the fly
|
||||
user.login = registration['nickname'] unless registration['nickname'].nil?
|
||||
user.mail = registration['email'] unless registration['email'].nil?
|
||||
user.firstname, user.lastname = registration['fullname'].split(' ') unless registration['fullname'].nil?
|
||||
user.random_password
|
||||
user.register
|
||||
|
||||
case Setting.self_registration
|
||||
when '1'
|
||||
register_by_email_activation(user) do
|
||||
@@ -232,7 +202,7 @@ class AccountController < ApplicationController
|
||||
if user.active?
|
||||
successful_authentication(user)
|
||||
else
|
||||
handle_inactive_user(user)
|
||||
account_pending
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -248,11 +218,12 @@ class AccountController < ApplicationController
|
||||
set_autologin_cookie(user)
|
||||
end
|
||||
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
|
||||
|
||||
def set_autologin_cookie(user)
|
||||
token = Token.create(:user => user, :action => 'autologin')
|
||||
cookie_name = Redmine::Configuration['autologin_cookie_name'] || 'autologin'
|
||||
cookie_options = {
|
||||
:value => token.value,
|
||||
:expires => 1.year.from_now,
|
||||
@@ -260,7 +231,7 @@ class AccountController < ApplicationController
|
||||
:secure => (Redmine::Configuration['autologin_cookie_secure'] ? true : false),
|
||||
:httponly => true
|
||||
}
|
||||
cookies[autologin_cookie_name] = cookie_options
|
||||
cookies[cookie_name] = cookie_options
|
||||
end
|
||||
|
||||
# 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")
|
||||
if user.save and token.save
|
||||
Mailer.register(token).deliver
|
||||
flash[:notice] = l(:notice_account_register_done, :email => user.mail)
|
||||
flash[:notice] = l(:notice_account_register_done)
|
||||
redirect_to signin_path
|
||||
else
|
||||
yield if block_given?
|
||||
@@ -299,7 +270,7 @@ class AccountController < ApplicationController
|
||||
if user.save
|
||||
self.logged_user = user
|
||||
flash[:notice] = l(:notice_account_activated)
|
||||
redirect_to my_account_path
|
||||
redirect_to :controller => 'my', :action => 'account'
|
||||
else
|
||||
yield if block_given?
|
||||
end
|
||||
@@ -312,32 +283,14 @@ class AccountController < ApplicationController
|
||||
if user.save
|
||||
# Sends an email to the administrators
|
||||
Mailer.account_activation_request(user).deliver
|
||||
account_pending(user)
|
||||
account_pending
|
||||
else
|
||||
yield if block_given?
|
||||
end
|
||||
end
|
||||
|
||||
def handle_inactive_user(user, redirect_path=signin_path)
|
||||
if user.registered?
|
||||
account_pending(user, redirect_path)
|
||||
else
|
||||
account_locked(user, redirect_path)
|
||||
end
|
||||
end
|
||||
|
||||
def account_pending(user, redirect_path=signin_path)
|
||||
if Setting.self_registration == '1'
|
||||
flash[:error] = l(:notice_account_not_activated_yet, :url => activation_email_path)
|
||||
session[:registered_user_id] = user.id
|
||||
else
|
||||
flash[:error] = l(:notice_account_pending)
|
||||
end
|
||||
redirect_to redirect_path
|
||||
end
|
||||
|
||||
def account_locked(user, redirect_path=signin_path)
|
||||
flash[:error] = l(:notice_account_locked)
|
||||
redirect_to redirect_path
|
||||
def account_pending
|
||||
flash[:notice] = l(:notice_account_pending)
|
||||
redirect_to signin_path
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -23,18 +23,19 @@ class AdminController < ApplicationController
|
||||
|
||||
before_filter :require_admin
|
||||
helper :sort
|
||||
include SortHelper
|
||||
include SortHelper
|
||||
|
||||
def index
|
||||
@no_configuration_data = Redmine::DefaultData::Loader::no_data?
|
||||
end
|
||||
|
||||
|
||||
def projects
|
||||
@status = params[:status] || 1
|
||||
|
||||
scope = Project.status(@status).order('lft')
|
||||
scope = Project.status(@status)
|
||||
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?
|
||||
end
|
||||
@@ -54,7 +55,7 @@ class AdminController < ApplicationController
|
||||
flash[:error] = l(:error_can_t_load_default_data, e.message)
|
||||
end
|
||||
end
|
||||
redirect_to admin_path
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
|
||||
def test_email
|
||||
@@ -65,10 +66,10 @@ class AdminController < ApplicationController
|
||||
@test = Mailer.test_email(User.current).deliver
|
||||
flash[:notice] = l(:notice_email_sent, User.current.mail)
|
||||
rescue Exception => e
|
||||
flash[:error] = l(:notice_email_error, Redmine::CodesetUtil.replace_invalid_utf8(e.message))
|
||||
flash[:error] = l(:notice_email_error, e.message)
|
||||
end
|
||||
ActionMailer::Base.raise_delivery_errors = raise_delivery_errors
|
||||
redirect_to settings_path(:tab => 'notifications')
|
||||
redirect_to :controller => 'settings', :action => 'edit', :tab => 'notifications'
|
||||
end
|
||||
|
||||
def info
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -22,9 +22,6 @@ class Unauthorized < Exception; end
|
||||
|
||||
class ApplicationController < ActionController::Base
|
||||
include Redmine::I18n
|
||||
include Redmine::Pagination
|
||||
include RoutesHelper
|
||||
helper :routes
|
||||
|
||||
class_attribute :accept_api_auth_actions
|
||||
class_attribute :accept_rss_auth_actions
|
||||
@@ -35,10 +32,10 @@ class ApplicationController < ActionController::Base
|
||||
protect_from_forgery
|
||||
def handle_unverified_request
|
||||
super
|
||||
cookies.delete(autologin_cookie_name)
|
||||
cookies.delete(:autologin)
|
||||
end
|
||||
|
||||
before_filter :session_expiration, :user_setup, :check_if_login_required, :check_password_change, :set_localization
|
||||
before_filter :session_expiration, :user_setup, :check_if_login_required, :set_localization
|
||||
|
||||
rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
|
||||
rescue_from ::Unauthorized, :with => :deny_access
|
||||
@@ -78,9 +75,6 @@ class ApplicationController < ActionController::Base
|
||||
session[:user_id] = user.id
|
||||
session[:ctime] = Time.now.utc.to_i
|
||||
session[:atime] = Time.now.utc.to_i
|
||||
if user.must_change_password?
|
||||
session[:pwd] = '1'
|
||||
end
|
||||
end
|
||||
|
||||
def user_setup
|
||||
@@ -115,10 +109,6 @@ class ApplicationController < ActionController::Base
|
||||
authenticate_with_http_basic do |username, password|
|
||||
user = User.try_to_login(username, password) || User.find_by_api_key(username)
|
||||
end
|
||||
if user && user.must_change_password?
|
||||
render_error :message => 'You must change your password', :status => 403
|
||||
return
|
||||
end
|
||||
end
|
||||
# Switch user if requested by an admin user
|
||||
if user && user.admin? && (username = api_switch_user_from_request)
|
||||
@@ -134,14 +124,10 @@ class ApplicationController < ActionController::Base
|
||||
user
|
||||
end
|
||||
|
||||
def autologin_cookie_name
|
||||
Redmine::Configuration['autologin_cookie_name'].presence || 'autologin'
|
||||
end
|
||||
|
||||
def try_to_autologin
|
||||
if cookies[autologin_cookie_name] && Setting.autologin?
|
||||
if cookies[:autologin] && Setting.autologin?
|
||||
# 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
|
||||
reset_session
|
||||
start_user_session(user)
|
||||
@@ -164,7 +150,7 @@ class ApplicationController < ActionController::Base
|
||||
# Logs out current user
|
||||
def logout_user
|
||||
if User.current.logged?
|
||||
cookies.delete(autologin_cookie_name)
|
||||
cookies.delete :autologin
|
||||
Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
|
||||
self.logged_user = nil
|
||||
end
|
||||
@@ -177,16 +163,6 @@ class ApplicationController < ActionController::Base
|
||||
require_login if Setting.login_required?
|
||||
end
|
||||
|
||||
def check_password_change
|
||||
if session[:pwd]
|
||||
if User.current.must_change_password?
|
||||
redirect_to my_password_path
|
||||
else
|
||||
session.delete(:pwd)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def set_localization
|
||||
lang = nil
|
||||
if User.current.logged?
|
||||
@@ -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])
|
||||
end
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
if request.xhr?
|
||||
head :unauthorized
|
||||
else
|
||||
redirect_to :controller => "account", :action => "login", :back_url => url
|
||||
end
|
||||
}
|
||||
format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
|
||||
format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
|
||||
format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
|
||||
format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
|
||||
@@ -321,7 +291,7 @@ class ApplicationController < ActionController::Base
|
||||
# Find issues with a single :id param or :ids array param
|
||||
# Raises a Unauthorized exception if one of the issues is not visible
|
||||
def find_issues
|
||||
@issues = Issue.where(:id => (params[:id] || params[:ids])).preload(:project, :status, :tracker, :priority, :author, :assigned_to, :relations_to).to_a
|
||||
@issues = Issue.find_all_by_id(params[:id] || params[:ids])
|
||||
raise ActiveRecord::RecordNotFound if @issues.empty?
|
||||
raise Unauthorized unless @issues.all?(&:visible?)
|
||||
@projects = @issues.collect(&:project).compact.uniq
|
||||
@@ -330,16 +300,6 @@ class ApplicationController < ActionController::Base
|
||||
render_404
|
||||
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
|
||||
# used as a before_filter for actions that do not require any particular permission on the project
|
||||
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?
|
||||
end
|
||||
|
||||
# Sets the `flash` notice or error based the number of issues that did not save
|
||||
#
|
||||
# @param [Array, Issue] issues all of the saved and unsaved Issues
|
||||
# @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved
|
||||
def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids)
|
||||
if unsaved_issue_ids.empty?
|
||||
flash[:notice] = l(:notice_successful_update) unless issues.empty?
|
||||
else
|
||||
flash[:error] = l(:notice_failed_to_save_issues,
|
||||
:count => unsaved_issue_ids.size,
|
||||
:total => issues.size,
|
||||
:ids => '#' + unsaved_issue_ids.join(', #'))
|
||||
end
|
||||
end
|
||||
|
||||
# Rescues an invalid query statement. Just in case...
|
||||
def query_statement_invalid(exception)
|
||||
logger.error "Query::StatementInvalid: #{exception.message}" if logger
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -85,17 +85,15 @@ class AttachmentsController < ApplicationController
|
||||
@attachment = Attachment.new(:file => request.raw_post)
|
||||
@attachment.author = User.current
|
||||
@attachment.filename = params[:filename].presence || Redmine::Utils.random_hex(16)
|
||||
saved = @attachment.save
|
||||
|
||||
respond_to do |format|
|
||||
format.js
|
||||
format.api {
|
||||
if saved
|
||||
render :action => 'upload', :status => :created
|
||||
else
|
||||
render_validation_errors(@attachment)
|
||||
end
|
||||
}
|
||||
if @attachment.save
|
||||
respond_to do |format|
|
||||
format.api { render :action => 'upload', :status => :created }
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.api { render_validation_errors(@attachment) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -103,17 +101,9 @@ class AttachmentsController < ApplicationController
|
||||
if @attachment.container.respond_to?(:init_journal)
|
||||
@attachment.container.init_journal(User.current)
|
||||
end
|
||||
if @attachment.container
|
||||
# Make sure association callbacks are called
|
||||
@attachment.container.attachments.delete(@attachment)
|
||||
else
|
||||
@attachment.destroy
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to_referer_or project_path(@project) }
|
||||
format.js
|
||||
end
|
||||
# Make sure association callbacks are called
|
||||
@attachment.container.attachments.delete(@attachment)
|
||||
redirect_to_referer_or project_path(@project)
|
||||
end
|
||||
|
||||
private
|
||||
@@ -128,12 +118,7 @@ private
|
||||
|
||||
# Checks that the file exists and is readable
|
||||
def file_readable
|
||||
if @attachment.readable?
|
||||
true
|
||||
else
|
||||
logger.error "Cannot send attachment, #{@attachment.diskfile} does not exist or is unreadable."
|
||||
render_404
|
||||
end
|
||||
@attachment.readable? ? true : render_404
|
||||
end
|
||||
|
||||
def read_authorize
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -20,77 +20,57 @@ class AuthSourcesController < ApplicationController
|
||||
menu_item :ldap_authentication
|
||||
|
||||
before_filter :require_admin
|
||||
before_filter :find_auth_source, :only => [:edit, :update, :test_connection, :destroy]
|
||||
|
||||
def index
|
||||
@auth_source_pages, @auth_sources = paginate AuthSource, :per_page => 25
|
||||
@auth_source_pages, @auth_sources = paginate AuthSource, :per_page => 10
|
||||
end
|
||||
|
||||
def new
|
||||
klass_name = params[:type] || 'AuthSourceLdap'
|
||||
@auth_source = AuthSource.new_subclass_instance(klass_name, params[:auth_source])
|
||||
render_404 unless @auth_source
|
||||
end
|
||||
|
||||
def create
|
||||
@auth_source = AuthSource.new_subclass_instance(params[:type], params[:auth_source])
|
||||
if @auth_source.save
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to auth_sources_path
|
||||
redirect_to :action => 'index'
|
||||
else
|
||||
render :action => 'new'
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@auth_source = AuthSource.find(params[:id])
|
||||
end
|
||||
|
||||
def update
|
||||
@auth_source = AuthSource.find(params[:id])
|
||||
if @auth_source.update_attributes(params[:auth_source])
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to auth_sources_path
|
||||
redirect_to :action => 'index'
|
||||
else
|
||||
render :action => 'edit'
|
||||
end
|
||||
end
|
||||
|
||||
def test_connection
|
||||
@auth_source = AuthSource.find(params[:id])
|
||||
begin
|
||||
@auth_source.test_connection
|
||||
flash[:notice] = l(:notice_successful_connection)
|
||||
rescue Exception => e
|
||||
flash[:error] = l(:error_unable_to_connect, e.message)
|
||||
end
|
||||
redirect_to auth_sources_path
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
|
||||
def destroy
|
||||
unless @auth_source.users.exists?
|
||||
@auth_source = AuthSource.find(params[:id])
|
||||
unless @auth_source.users.find(:first)
|
||||
@auth_source.destroy
|
||||
flash[:notice] = l(:notice_successful_delete)
|
||||
end
|
||||
redirect_to auth_sources_path
|
||||
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
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# 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
|
||||
if q.present?
|
||||
scope = (params[:scope] == "all" || @project.nil? ? Issue : @project.issues).visible
|
||||
if q.match(/\A#?(\d+)\z/)
|
||||
@issues << scope.find_by_id($1.to_i)
|
||||
if q.match(/^\d+$/)
|
||||
@issues << scope.find_by_id(q.to_i)
|
||||
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!
|
||||
end
|
||||
render :layout => false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -25,7 +25,7 @@ class BoardsController < ApplicationController
|
||||
helper :watchers
|
||||
|
||||
def index
|
||||
@boards = @project.boards.includes(:project, :last_message => :author).all
|
||||
@boards = @project.boards.includes(:last_message => :author).all
|
||||
# show the board if there is only one
|
||||
if @boards.size == 1
|
||||
@board = @boards.first
|
||||
@@ -37,29 +37,23 @@ class BoardsController < ApplicationController
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
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",
|
||||
'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_pages = Paginator.new @topic_count, per_page_option, params['page']
|
||||
@topics = @board.topics.
|
||||
reorder("#{Message.table_name}.sticky DESC").
|
||||
includes(:last_reply).
|
||||
limit(@topic_pages.per_page).
|
||||
offset(@topic_pages.offset).
|
||||
order(sort_clause).
|
||||
preload(:author, {:last_reply => :author}).
|
||||
all
|
||||
@topic_pages = Paginator.new self, @topic_count, per_page_option, params['page']
|
||||
@topics = @board.topics.reorder("#{Message.table_name}.sticky DESC").order(sort_clause).all(
|
||||
:include => [:author, {:last_reply => :author}],
|
||||
:limit => @topic_pages.items_per_page,
|
||||
:offset => @topic_pages.current.offset)
|
||||
@message = Message.new(:board => @board)
|
||||
render :action => 'show', :layout => !request.xhr?
|
||||
}
|
||||
format.atom {
|
||||
@messages = @board.messages.
|
||||
reorder('created_on DESC').
|
||||
includes(:author, :board).
|
||||
limit(Setting.feeds_limit.to_i).
|
||||
all
|
||||
@messages = @board.messages.find :all, :order => 'created_on DESC',
|
||||
:include => [:author, :board],
|
||||
:limit => Setting.feeds_limit.to_i
|
||||
render_feed(@messages, :title => "#{@project}: #{@board}")
|
||||
}
|
||||
end
|
||||
@@ -100,7 +94,7 @@ class BoardsController < ApplicationController
|
||||
|
||||
private
|
||||
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
|
||||
|
||||
def find_board_if_available
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -32,12 +32,12 @@ class CommentsController < ApplicationController
|
||||
flash[:notice] = l(:label_comment_added)
|
||||
end
|
||||
|
||||
redirect_to news_path(@news)
|
||||
redirect_to :controller => 'news', :action => 'show', :id => @news
|
||||
end
|
||||
|
||||
def destroy
|
||||
@news.comments.find(params[:comment_id]).destroy
|
||||
redirect_to news_path(@news)
|
||||
redirect_to :controller => 'news', :action => 'show', :id => @news
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -19,15 +19,16 @@ class ContextMenusController < ApplicationController
|
||||
helper :watchers
|
||||
helper :issues
|
||||
|
||||
before_filter :find_issues, :only => :issues
|
||||
|
||||
def issues
|
||||
@issues = Issue.visible.all(:conditions => {:id => params[:ids]}, :include => :project)
|
||||
if (@issues.size == 1)
|
||||
@issue = @issues.first
|
||||
end
|
||||
@issue_ids = @issues.map(&:id).sort
|
||||
|
||||
@allowed_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
|
||||
@projects = @issues.collect(&:project).compact.uniq
|
||||
@project = @projects.first if @projects.size == 1
|
||||
|
||||
@can = {:edit => User.current.allowed_to?(:edit_issues, @projects),
|
||||
:log_time => (@project && User.current.allowed_to?(:log_time, @project)),
|
||||
@@ -71,9 +72,8 @@ class ContextMenusController < ApplicationController
|
||||
end
|
||||
|
||||
def time_entries
|
||||
@time_entries = TimeEntry.where(:id => params[:ids]).preload(:project).to_a
|
||||
(render_404; return) unless @time_entries.present?
|
||||
|
||||
@time_entries = TimeEntry.all(
|
||||
:conditions => {:id => params[:ids]}, :include => :project)
|
||||
@projects = @time_entries.collect(&:project).compact.uniq
|
||||
@project = @projects.first if @projects.size == 1
|
||||
@activities = TimeEntryActivity.shared.active
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -21,28 +21,20 @@ class CustomFieldsController < ApplicationController
|
||||
before_filter :require_admin
|
||||
before_filter :build_new_custom_field, :only => [:new, :create]
|
||||
before_filter :find_custom_field, :only => [:edit, :update, :destroy]
|
||||
accept_api_auth :index
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
@custom_fields_by_type = CustomField.all.group_by {|f| f.class.name }
|
||||
@tab = params[:tab] || 'IssueCustomField'
|
||||
}
|
||||
format.api {
|
||||
@custom_fields = CustomField.all
|
||||
}
|
||||
end
|
||||
@custom_fields_by_type = CustomField.find(:all).group_by {|f| f.class.name }
|
||||
@tab = params[:tab] || 'IssueCustomField'
|
||||
end
|
||||
|
||||
def new
|
||||
end
|
||||
|
||||
def create
|
||||
if @custom_field.save
|
||||
if request.post? and @custom_field.save
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
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
|
||||
render :action => 'new'
|
||||
end
|
||||
@@ -52,22 +44,21 @@ class CustomFieldsController < ApplicationController
|
||||
end
|
||||
|
||||
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)
|
||||
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
|
||||
render :action => 'edit'
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
begin
|
||||
@custom_field.destroy
|
||||
rescue
|
||||
flash[:error] = l(:error_can_not_delete_custom_field)
|
||||
end
|
||||
redirect_to custom_fields_path(:tab => @custom_field.class.name)
|
||||
@custom_field.destroy
|
||||
redirect_to :action => 'index', :tab => @custom_field.class.name
|
||||
rescue
|
||||
flash[:error] = l(:error_can_not_delete_custom_field)
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
|
||||
private
|
||||
@@ -76,8 +67,6 @@ class CustomFieldsController < ApplicationController
|
||||
@custom_field = CustomField.new_subclass_instance(params[:type], params[:custom_field])
|
||||
if @custom_field.nil?
|
||||
render_404
|
||||
else
|
||||
@custom_field.default_value = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -27,7 +27,7 @@ class DocumentsController < ApplicationController
|
||||
|
||||
def index
|
||||
@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
|
||||
when 'date'
|
||||
@grouped = documents.group_by {|d| d.updated_on.to_date }
|
||||
@@ -43,7 +43,7 @@ class DocumentsController < ApplicationController
|
||||
end
|
||||
|
||||
def show
|
||||
@attachments = @document.attachments.all
|
||||
@attachments = @document.attachments.find(:all, :order => "created_on DESC")
|
||||
end
|
||||
|
||||
def new
|
||||
@@ -58,7 +58,7 @@ class DocumentsController < ApplicationController
|
||||
if @document.save
|
||||
render_attachment_warning_if_needed(@document)
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to project_documents_path(@project)
|
||||
redirect_to :action => 'index', :project_id => @project
|
||||
else
|
||||
render :action => 'new'
|
||||
end
|
||||
@@ -71,7 +71,7 @@ class DocumentsController < ApplicationController
|
||||
@document.safe_attributes = params[:document]
|
||||
if request.put? and @document.save
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to document_path(@document)
|
||||
redirect_to :action => 'show', :id => @document
|
||||
else
|
||||
render :action => 'edit'
|
||||
end
|
||||
@@ -79,7 +79,7 @@ class DocumentsController < ApplicationController
|
||||
|
||||
def destroy
|
||||
@document.destroy if request.delete?
|
||||
redirect_to project_documents_path(@project)
|
||||
redirect_to :controller => 'documents', :action => 'index', :project_id => @project
|
||||
end
|
||||
|
||||
def add_attachment
|
||||
@@ -89,6 +89,6 @@ class DocumentsController < ApplicationController
|
||||
if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added')
|
||||
Mailer.attachments_added(attachments[:files]).deliver
|
||||
end
|
||||
redirect_to document_path(@document)
|
||||
redirect_to :action => 'show', :id => @document
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -46,7 +46,7 @@ class EnumerationsController < ApplicationController
|
||||
def create
|
||||
if request.post? && @enumeration.save
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to enumerations_path
|
||||
redirect_to :action => 'index'
|
||||
else
|
||||
render :action => 'new'
|
||||
end
|
||||
@@ -58,7 +58,7 @@ class EnumerationsController < ApplicationController
|
||||
def update
|
||||
if request.put? && @enumeration.update_attributes(params[:enumeration])
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to enumerations_path
|
||||
redirect_to :action => 'index'
|
||||
else
|
||||
render :action => 'edit'
|
||||
end
|
||||
@@ -68,14 +68,16 @@ class EnumerationsController < ApplicationController
|
||||
if !@enumeration.in_use?
|
||||
# No associated objects
|
||||
@enumeration.destroy
|
||||
redirect_to enumerations_path
|
||||
return
|
||||
elsif params[:reassign_to_id].present? && (reassign_to = @enumeration.class.find_by_id(params[:reassign_to_id].to_i))
|
||||
@enumeration.destroy(reassign_to)
|
||||
redirect_to enumerations_path
|
||||
redirect_to :action => 'index'
|
||||
return
|
||||
elsif params[:reassign_to_id]
|
||||
if reassign_to = @enumeration.class.find_by_id(params[:reassign_to_id])
|
||||
@enumeration.destroy(reassign_to)
|
||||
redirect_to :action => 'index'
|
||||
return
|
||||
end
|
||||
end
|
||||
@enumerations = @enumeration.class.system.all - [@enumeration]
|
||||
@enumerations = @enumeration.class.all - [@enumeration]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -31,8 +31,8 @@ class FilesController < ApplicationController
|
||||
'size' => "#{Attachment.table_name}.filesize",
|
||||
'downloads' => "#{Attachment.table_name}.downloads"
|
||||
|
||||
@containers = [ Project.includes(:attachments).reorder(sort_clause).find(@project.id)]
|
||||
@containers += @project.versions.includes(:attachments).reorder(sort_clause).all.sort.reverse
|
||||
@containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
|
||||
@containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
|
||||
render :layout => !request.xhr?
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -84,7 +84,7 @@ class GroupsController < ApplicationController
|
||||
@group.destroy
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to(groups_path) }
|
||||
format.html { redirect_to(groups_url) }
|
||||
format.api { render_api_ok }
|
||||
end
|
||||
end
|
||||
@@ -93,7 +93,7 @@ class GroupsController < ApplicationController
|
||||
@users = User.find_all_by_id(params[:user_id] || params[:user_ids])
|
||||
@group.users << @users if request.post?
|
||||
respond_to do |format|
|
||||
format.html { redirect_to edit_group_path(@group, :tab => 'users') }
|
||||
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' }
|
||||
format.js
|
||||
format.api { render_api_ok }
|
||||
end
|
||||
@@ -102,23 +102,22 @@ class GroupsController < ApplicationController
|
||||
def remove_user
|
||||
@group.users.delete(User.find(params[:user_id])) if request.delete?
|
||||
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.api { render_api_ok }
|
||||
end
|
||||
end
|
||||
|
||||
def autocomplete_for_user
|
||||
respond_to do |format|
|
||||
format.js
|
||||
end
|
||||
@users = User.active.not_in_group(@group).like(params[:q]).all(:limit => 100)
|
||||
render :layout => false
|
||||
end
|
||||
|
||||
def edit_membership
|
||||
@membership = Member.edit_membership(params[:membership_id], params[:membership], @group)
|
||||
@membership.save if request.post?
|
||||
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
|
||||
end
|
||||
end
|
||||
@@ -126,7 +125,7 @@ class GroupsController < ApplicationController
|
||||
def destroy_membership
|
||||
Member.find(params[:membership_id]).destroy if request.post?
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -26,14 +26,14 @@ class IssueCategoriesController < ApplicationController
|
||||
|
||||
def index
|
||||
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 }
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
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
|
||||
end
|
||||
end
|
||||
@@ -55,7 +55,7 @@ class IssueCategoriesController < ApplicationController
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to_settings_in_projects
|
||||
redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
|
||||
end
|
||||
format.js
|
||||
format.api { render :action => 'show', :status => :created, :location => issue_category_path(@category) }
|
||||
@@ -78,7 +78,7 @@ class IssueCategoriesController < ApplicationController
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
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 }
|
||||
end
|
||||
@@ -99,7 +99,7 @@ class IssueCategoriesController < ApplicationController
|
||||
end
|
||||
@category.destroy(reassign_to)
|
||||
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 }
|
||||
end
|
||||
return
|
||||
@@ -107,12 +107,7 @@ class IssueCategoriesController < ApplicationController
|
||||
@categories = @project.issue_categories - [@category]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def redirect_to_settings_in_projects
|
||||
redirect_to settings_project_path(@project, :tab => 'categories')
|
||||
end
|
||||
|
||||
private
|
||||
# Wrap ApplicationController's find_model_object method to set
|
||||
# @category instead of just @issue_category
|
||||
def find_model_object
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -48,9 +48,9 @@ class IssueRelationsController < ApplicationController
|
||||
saved = @relation.save
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to issue_path(@issue) }
|
||||
format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
|
||||
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 {
|
||||
if saved
|
||||
@@ -67,7 +67,7 @@ class IssueRelationsController < ApplicationController
|
||||
@relation.destroy
|
||||
|
||||
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.api { render_api_ok }
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -25,7 +25,7 @@ class IssueStatusesController < ApplicationController
|
||||
def index
|
||||
respond_to do |format|
|
||||
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?
|
||||
}
|
||||
format.api {
|
||||
@@ -40,9 +40,9 @@ class IssueStatusesController < ApplicationController
|
||||
|
||||
def create
|
||||
@issue_status = IssueStatus.new(params[:issue_status])
|
||||
if @issue_status.save
|
||||
if request.post? && @issue_status.save
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to issue_statuses_path
|
||||
redirect_to :action => 'index'
|
||||
else
|
||||
render :action => 'new'
|
||||
end
|
||||
@@ -54,9 +54,9 @@ class IssueStatusesController < ApplicationController
|
||||
|
||||
def update
|
||||
@issue_status = IssueStatus.find(params[:id])
|
||||
if @issue_status.update_attributes(params[:issue_status])
|
||||
if request.put? && @issue_status.update_attributes(params[:issue_status])
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to issue_statuses_path
|
||||
redirect_to :action => 'index'
|
||||
else
|
||||
render :action => 'edit'
|
||||
end
|
||||
@@ -64,11 +64,11 @@ class IssueStatusesController < ApplicationController
|
||||
|
||||
def destroy
|
||||
IssueStatus.find(params[:id]).destroy
|
||||
redirect_to issue_statuses_path
|
||||
redirect_to :action => 'index'
|
||||
rescue
|
||||
flash[:error] = l(:error_unable_delete_issue_status)
|
||||
redirect_to issue_statuses_path
|
||||
end
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
|
||||
def update_issue_done_ratio
|
||||
if request.post? && IssueStatus.update_issue_done_ratios
|
||||
@@ -76,6 +76,6 @@ class IssueStatusesController < ApplicationController
|
||||
else
|
||||
flash[:error] = l(:error_issue_done_ratios_not_updated)
|
||||
end
|
||||
redirect_to issue_statuses_path
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# 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_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 :find_optional_project, :only => [:index]
|
||||
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_api_auth :index, :show, :create, :update, :destroy
|
||||
|
||||
@@ -71,8 +71,8 @@ class IssuesController < ApplicationController
|
||||
end
|
||||
|
||||
@issue_count = @query.issue_count
|
||||
@issue_pages = Paginator.new @issue_count, @limit, params['page']
|
||||
@offset ||= @issue_pages.offset
|
||||
@issue_pages = Paginator.new self, @issue_count, @limit, params['page']
|
||||
@offset ||= @issue_pages.current.offset
|
||||
@issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
|
||||
:order => sort_clause,
|
||||
:offset => @offset,
|
||||
@@ -85,8 +85,8 @@ class IssuesController < ApplicationController
|
||||
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.csv { send_data(query_to_csv(@issues, @query, params), :type => 'text/csv; header=present', :filename => 'issues.csv') }
|
||||
format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'issues.pdf') }
|
||||
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 => 'export.pdf') }
|
||||
end
|
||||
else
|
||||
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.each_with_index {|j,i| j.indice = i+1}
|
||||
@journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
|
||||
Journal.preload_journals_details_custom_fields(@journals)
|
||||
# TODO: use #select! when ruby1.8 support is dropped
|
||||
@journals.reject! {|journal| !journal.notes? && journal.visible_details.empty?}
|
||||
@journals.reverse! if User.current.wants_comments_in_reverse_order?
|
||||
|
||||
@changesets = @issue.changesets.visible.all
|
||||
@@ -116,8 +113,6 @@ class IssuesController < ApplicationController
|
||||
@edit_allowed = User.current.allowed_to?(:edit_issues, @project)
|
||||
@priorities = IssuePriority.active
|
||||
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
|
||||
@relation = IssueRelation.new
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
retrieve_previous_and_next_issue_ids
|
||||
@@ -137,6 +132,7 @@ class IssuesController < ApplicationController
|
||||
def new
|
||||
respond_to do |format|
|
||||
format.html { render :action => 'new', :layout => !request.xhr? }
|
||||
format.js { render :partial => 'update_form' }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -149,12 +145,8 @@ class IssuesController < ApplicationController
|
||||
format.html {
|
||||
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))
|
||||
if params[:continue]
|
||||
attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?}
|
||||
redirect_to new_project_issue_path(@issue.project, :issue => attrs)
|
||||
else
|
||||
redirect_to issue_path(@issue)
|
||||
end
|
||||
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?} } :
|
||||
{ :action => 'show', :id => @issue })
|
||||
}
|
||||
format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
|
||||
end
|
||||
@@ -181,7 +173,7 @@ class IssuesController < ApplicationController
|
||||
@issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
|
||||
saved = false
|
||||
begin
|
||||
saved = save_issue_with_child_records
|
||||
saved = @issue.save_issue_with_child_records(params, @time_entry)
|
||||
rescue ActiveRecord::StaleObjectError
|
||||
@conflict = true
|
||||
if params[:last_journal_id]
|
||||
@@ -195,7 +187,7 @@ class IssuesController < ApplicationController
|
||||
flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
|
||||
|
||||
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 }
|
||||
end
|
||||
else
|
||||
@@ -206,11 +198,6 @@ class IssuesController < ApplicationController
|
||||
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
|
||||
def bulk_edit
|
||||
@issues.sort!
|
||||
@@ -233,7 +220,7 @@ class IssuesController < ApplicationController
|
||||
else
|
||||
@available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
|
||||
end
|
||||
@custom_fields = target_projects.map{|p|p.all_issue_custom_fields.visible}.reduce(:&)
|
||||
@custom_fields = target_projects.map{|p|p.all_issue_custom_fields}.reduce(:&)
|
||||
@assignables = target_projects.map(&:assignable_users).reduce(:&)
|
||||
@trackers = target_projects.map(&:trackers).reduce(:&)
|
||||
@versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
|
||||
@@ -244,9 +231,7 @@ class IssuesController < ApplicationController
|
||||
end
|
||||
|
||||
@safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
|
||||
|
||||
@issue_params = params[:issue] || {}
|
||||
@issue_params[:custom_field_values] ||= {}
|
||||
render :layout => false if request.xhr?
|
||||
end
|
||||
|
||||
def bulk_update
|
||||
@@ -254,8 +239,8 @@ class IssuesController < ApplicationController
|
||||
@copy = params[:copy].present?
|
||||
attributes = parse_params_for_bulk_issue_attributes(params)
|
||||
|
||||
unsaved_issues = []
|
||||
saved_issues = []
|
||||
unsaved_issue_ids = []
|
||||
moved_issues = []
|
||||
|
||||
if @copy && params[:copy_subtasks].present?
|
||||
# Descendant issues will be copied with the parent task
|
||||
@@ -263,48 +248,39 @@ class IssuesController < ApplicationController
|
||||
@issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
|
||||
end
|
||||
|
||||
@issues.each do |orig_issue|
|
||||
orig_issue.reload
|
||||
@issues.each do |issue|
|
||||
issue.reload
|
||||
if @copy
|
||||
issue = orig_issue.copy({},
|
||||
issue = issue.copy({},
|
||||
:attachments => params[:copy_attachments].present?,
|
||||
:subtasks => params[:copy_subtasks].present?
|
||||
)
|
||||
else
|
||||
issue = orig_issue
|
||||
end
|
||||
journal = issue.init_journal(User.current, params[:notes])
|
||||
issue.safe_attributes = attributes
|
||||
call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
|
||||
if issue.save
|
||||
saved_issues << issue
|
||||
moved_issues << issue
|
||||
else
|
||||
unsaved_issues << orig_issue
|
||||
# Keep unsaved issue ids to display them in flash error
|
||||
unsaved_issue_ids << issue.id
|
||||
end
|
||||
end
|
||||
set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
|
||||
|
||||
if unsaved_issues.empty?
|
||||
flash[:notice] = l(:notice_successful_update) unless saved_issues.empty?
|
||||
if params[:follow]
|
||||
if @issues.size == 1 && saved_issues.size == 1
|
||||
redirect_to issue_path(saved_issues.first)
|
||||
elsif saved_issues.map(&:project).uniq.size == 1
|
||||
redirect_to project_issues_path(saved_issues.map(&:project).first)
|
||||
end
|
||||
else
|
||||
redirect_back_or_default _project_issues_path(@project)
|
||||
if params[:follow]
|
||||
if @issues.size == 1 && moved_issues.size == 1
|
||||
redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
|
||||
elsif moved_issues.map(&:project).uniq.size == 1
|
||||
redirect_to :controller => 'issues', :action => 'index', :project_id => moved_issues.map(&:project).first
|
||||
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'
|
||||
redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@hours = TimeEntry.where(:issue_id => @issues.map(&:id)).sum(:hours).to_f
|
||||
@hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
|
||||
if @hours > 0
|
||||
case params[:todo]
|
||||
when 'destroy'
|
||||
@@ -332,7 +308,7 @@ class IssuesController < ApplicationController
|
||||
end
|
||||
end
|
||||
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 }
|
||||
end
|
||||
end
|
||||
@@ -452,26 +428,4 @@ class IssuesController < ApplicationController
|
||||
end
|
||||
attributes
|
||||
end
|
||||
|
||||
# Saves @issue and a time_entry from the parameters
|
||||
def save_issue_with_child_records
|
||||
Issue.transaction do
|
||||
if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, @issue.project)
|
||||
time_entry = @time_entry || TimeEntry.new
|
||||
time_entry.project = @issue.project
|
||||
time_entry.issue = @issue
|
||||
time_entry.user = User.current
|
||||
time_entry.spent_on = User.current.today
|
||||
time_entry.attributes = params[:time_entry]
|
||||
@issue.time_entries << time_entry
|
||||
end
|
||||
|
||||
call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
|
||||
if @issue.save
|
||||
call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
|
||||
else
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -80,7 +80,7 @@ class JournalsController < ApplicationController
|
||||
@journal.destroy if @journal.details.empty? && @journal.notes.blank?
|
||||
call_hook(:controller_journals_edit_post, { :journal => @journal, :params => params})
|
||||
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' }
|
||||
end
|
||||
else
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -26,8 +26,8 @@ class MembersController < ApplicationController
|
||||
def index
|
||||
@offset, @limit = api_offset_and_limit
|
||||
@member_count = @project.member_principals.count
|
||||
@member_pages = Paginator.new @member_count, @limit, params['page']
|
||||
@offset ||= @member_pages.offset
|
||||
@member_pages = Paginator.new self, @member_count, @limit, params['page']
|
||||
@offset ||= @member_pages.current.offset
|
||||
@members = @project.member_principals.all(
|
||||
:order => "#{Member.table_name}.id",
|
||||
:limit => @limit,
|
||||
@@ -63,7 +63,7 @@ class MembersController < ApplicationController
|
||||
end
|
||||
|
||||
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.api {
|
||||
@member = members.first
|
||||
@@ -82,7 +82,7 @@ class MembersController < ApplicationController
|
||||
end
|
||||
saved = @member.save
|
||||
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.api {
|
||||
if saved
|
||||
@@ -99,7 +99,7 @@ class MembersController < ApplicationController
|
||||
@member.destroy
|
||||
end
|
||||
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.api {
|
||||
if @member.destroyed?
|
||||
@@ -112,14 +112,7 @@ class MembersController < ApplicationController
|
||||
end
|
||||
|
||||
def autocomplete
|
||||
respond_to do |format|
|
||||
format.js
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def redirect_to_settings_in_projects
|
||||
redirect_to settings_project_path(@project, :tab => 'members')
|
||||
@principals = Principal.active.not_member_of(@project).like(params[:q]).all(:limit => 100)
|
||||
render :layout => false
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -19,7 +19,6 @@ class MessagesController < ApplicationController
|
||||
menu_item :boards
|
||||
default_search_scope :messages
|
||||
before_filter :find_board, :only => [:new, :preview]
|
||||
before_filter :find_attachments, :only => [:preview]
|
||||
before_filter :find_message, :except => [:new, :preview]
|
||||
before_filter :authorize, :except => [:preview, :edit, :destroy]
|
||||
|
||||
@@ -35,18 +34,16 @@ class MessagesController < ApplicationController
|
||||
page = params[:page]
|
||||
# Find the page of the requested reply
|
||||
if params[:r] && page.nil?
|
||||
offset = @topic.children.where("#{Message.table_name}.id < ?", params[:r].to_i).count
|
||||
offset = @topic.children.count(:conditions => ["#{Message.table_name}.id < ?", params[:r].to_i])
|
||||
page = 1 + offset / REPLIES_PER_PAGE
|
||||
end
|
||||
|
||||
@reply_count = @topic.children.count
|
||||
@reply_pages = Paginator.new @reply_count, REPLIES_PER_PAGE, page
|
||||
@replies = @topic.children.
|
||||
includes(:author, :attachments, {:board => :project}).
|
||||
reorder("#{Message.table_name}.created_on ASC").
|
||||
limit(@reply_pages.per_page).
|
||||
offset(@reply_pages.offset).
|
||||
all
|
||||
@reply_pages = Paginator.new self, @reply_count, REPLIES_PER_PAGE, page
|
||||
@replies = @topic.children.find(:all, :include => [:author, :attachments, {:board => :project}],
|
||||
:order => "#{Message.table_name}.created_on ASC",
|
||||
:limit => @reply_pages.items_per_page,
|
||||
:offset => @reply_pages.current.offset)
|
||||
|
||||
@reply = Message.new(:subject => "RE: #{@message.subject}")
|
||||
render :action => "show", :layout => false if request.xhr?
|
||||
@@ -118,6 +115,7 @@ class MessagesController < ApplicationController
|
||||
|
||||
def preview
|
||||
message = @board.messages.find_by_id(params[:id])
|
||||
@attachements = message.attachments if message
|
||||
@text = (params[:message] || params[:reply])[:content]
|
||||
@previewed = message
|
||||
render :partial => 'common/preview'
|
||||
@@ -125,7 +123,7 @@ class MessagesController < ApplicationController
|
||||
|
||||
private
|
||||
def find_message
|
||||
return unless find_board
|
||||
find_board
|
||||
@message = @board.messages.find(params[:id], :include => :parent)
|
||||
@topic = @message.root
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
@@ -137,6 +135,5 @@ private
|
||||
@project = @board.project
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -17,8 +17,6 @@
|
||||
|
||||
class MyController < ApplicationController
|
||||
before_filter :require_login
|
||||
# let user change user's password when user has to
|
||||
skip_before_filter :check_password_change, :only => :password
|
||||
|
||||
helper :issues
|
||||
helper :users
|
||||
@@ -55,11 +53,13 @@ class MyController < ApplicationController
|
||||
if request.post?
|
||||
@user.safe_attributes = params[:user]
|
||||
@user.pref.attributes = params[:pref]
|
||||
@user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
|
||||
if @user.save
|
||||
@user.pref.save
|
||||
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
|
||||
set_language_if_valid @user.language
|
||||
flash[:notice] = l(:notice_account_updated)
|
||||
redirect_to my_account_path
|
||||
redirect_to :action => 'account'
|
||||
return
|
||||
end
|
||||
end
|
||||
@@ -69,7 +69,7 @@ class MyController < ApplicationController
|
||||
def destroy
|
||||
@user = User.current
|
||||
unless @user.own_account_deletable?
|
||||
redirect_to my_account_path
|
||||
redirect_to :action => 'account'
|
||||
return
|
||||
end
|
||||
|
||||
@@ -88,21 +88,18 @@ class MyController < ApplicationController
|
||||
@user = User.current
|
||||
unless @user.change_password_allowed?
|
||||
flash[:error] = l(:notice_can_t_change_password)
|
||||
redirect_to my_account_path
|
||||
redirect_to :action => 'account'
|
||||
return
|
||||
end
|
||||
if request.post?
|
||||
if !@user.check_password?(params[:password])
|
||||
flash.now[:error] = l(:notice_account_wrong_password)
|
||||
elsif params[:password] == params[:new_password]
|
||||
flash.now[:error] = l(:notice_new_password_must_be_different)
|
||||
else
|
||||
if @user.check_password?(params[:password])
|
||||
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
|
||||
@user.must_change_passwd = false
|
||||
if @user.save
|
||||
flash[:notice] = l(:notice_account_password_updated)
|
||||
redirect_to my_account_path
|
||||
redirect_to :action => 'account'
|
||||
end
|
||||
else
|
||||
flash[:error] = l(:notice_account_wrong_password)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -117,7 +114,7 @@ class MyController < ApplicationController
|
||||
User.current.rss_key
|
||||
flash[:notice] = l(:notice_feeds_access_key_reseted)
|
||||
end
|
||||
redirect_to my_account_path
|
||||
redirect_to :action => 'account'
|
||||
end
|
||||
|
||||
# Create a new API key
|
||||
@@ -130,7 +127,7 @@ class MyController < ApplicationController
|
||||
User.current.api_key
|
||||
flash[:notice] = l(:notice_api_access_key_reseted)
|
||||
end
|
||||
redirect_to my_account_path
|
||||
redirect_to :action => 'account'
|
||||
end
|
||||
|
||||
# User's page layout configuration
|
||||
@@ -150,17 +147,16 @@ class MyController < ApplicationController
|
||||
# params[:block] : id of the block to add
|
||||
def add_block
|
||||
block = params[:block].to_s.underscore
|
||||
if block.present? && BLOCKS.key?(block)
|
||||
@user = User.current
|
||||
layout = @user.pref[:my_page_layout] || {}
|
||||
# remove if already present in a group
|
||||
%w(top left right).each {|f| (layout[f] ||= []).delete block }
|
||||
# add it on top
|
||||
layout['top'].unshift block
|
||||
@user.pref[:my_page_layout] = layout
|
||||
@user.pref.save
|
||||
end
|
||||
redirect_to my_page_layout_path
|
||||
(render :nothing => true; return) unless block && (BLOCKS.keys.include? block)
|
||||
@user = User.current
|
||||
layout = @user.pref[:my_page_layout] || {}
|
||||
# remove if already present in a group
|
||||
%w(top left right).each {|f| (layout[f] ||= []).delete block }
|
||||
# add it on top
|
||||
layout['top'].unshift block
|
||||
@user.pref[:my_page_layout] = layout
|
||||
@user.pref.save
|
||||
redirect_to :action => 'page_layout'
|
||||
end
|
||||
|
||||
# Remove a block to user's page
|
||||
@@ -173,7 +169,7 @@ class MyController < ApplicationController
|
||||
%w(top left right).each {|f| (layout[f] ||= []).delete block }
|
||||
@user.pref[:my_page_layout] = layout
|
||||
@user.pref.save
|
||||
redirect_to my_page_layout_path
|
||||
redirect_to :action => 'page_layout'
|
||||
end
|
||||
|
||||
# Change blocks order on user's page
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# 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
|
||||
|
||||
@news_count = scope.count
|
||||
@news_pages = Paginator.new @news_count, @limit, params['page']
|
||||
@offset ||= @news_pages.offset
|
||||
@news_pages = Paginator.new self, @news_count, @limit, params['page']
|
||||
@offset ||= @news_pages.current.offset
|
||||
@newss = scope.all(:include => [:author, :project],
|
||||
:order => "#{News.table_name}.created_on DESC",
|
||||
:offset => @offset,
|
||||
@@ -73,7 +73,7 @@ class NewsController < ApplicationController
|
||||
if @news.save
|
||||
render_attachment_warning_if_needed(@news)
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to project_news_index_path(@project)
|
||||
redirect_to :controller => 'news', :action => 'index', :project_id => @project
|
||||
else
|
||||
render :action => 'new'
|
||||
end
|
||||
@@ -88,7 +88,7 @@ class NewsController < ApplicationController
|
||||
if @news.save
|
||||
render_attachment_warning_if_needed(@news)
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to news_path(@news)
|
||||
redirect_to :action => 'show', :id => @news
|
||||
else
|
||||
render :action => 'edit'
|
||||
end
|
||||
@@ -96,7 +96,7 @@ class NewsController < ApplicationController
|
||||
|
||||
def destroy
|
||||
@news.destroy
|
||||
redirect_to project_news_index_path(@project)
|
||||
redirect_to :action => 'index', :project_id => @project
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# 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.
|
||||
|
||||
class PreviewsController < ApplicationController
|
||||
before_filter :find_project, :find_attachments
|
||||
before_filter :find_project
|
||||
|
||||
def issue
|
||||
@issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
|
||||
if @issue
|
||||
@attachements = @issue.attachments
|
||||
@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")
|
||||
@description = nil
|
||||
@@ -36,6 +37,7 @@ class PreviewsController < ApplicationController
|
||||
def news
|
||||
if params[:id].present? && news = News.visible.find_by_id(params[:id])
|
||||
@previewed = news
|
||||
@attachments = news.attachments
|
||||
end
|
||||
@text = (params[:news] ? params[:news][:description] : nil)
|
||||
render :partial => 'common/preview'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -29,7 +29,7 @@ class ProjectEnumerationsController < ApplicationController
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
end
|
||||
|
||||
redirect_to settings_project_path(@project, :tab => 'activities')
|
||||
redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
|
||||
end
|
||||
|
||||
def destroy
|
||||
@@ -37,6 +37,7 @@ class ProjectEnumerationsController < ApplicationController
|
||||
time_entry_activity.destroy(time_entry_activity.parent)
|
||||
end
|
||||
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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -43,7 +43,6 @@ class ProjectsController < ApplicationController
|
||||
helper :repositories
|
||||
include RepositoriesHelper
|
||||
include ProjectsHelper
|
||||
helper :members
|
||||
|
||||
# Lists visible projects
|
||||
def index
|
||||
@@ -58,31 +57,32 @@ class ProjectsController < ApplicationController
|
||||
format.api {
|
||||
@offset, @limit = api_offset_and_limit
|
||||
@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 {
|
||||
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)}")
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def new
|
||||
@issue_custom_fields = IssueCustomField.sorted.all
|
||||
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
|
||||
@trackers = Tracker.sorted.all
|
||||
@project = Project.new
|
||||
@project.safe_attributes = params[:project]
|
||||
end
|
||||
|
||||
def create
|
||||
@issue_custom_fields = IssueCustomField.sorted.all
|
||||
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
|
||||
@trackers = Tracker.sorted.all
|
||||
@project = Project.new
|
||||
@project.safe_attributes = params[:project]
|
||||
|
||||
if validate_parent_id && @project.save
|
||||
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
|
||||
# Add current user as a project member if current user is not admin
|
||||
# Add current user as a project member if he is not admin
|
||||
unless User.current.admin?
|
||||
r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
|
||||
m = Member.new(:user => User.current, :roles => [r])
|
||||
@@ -91,12 +91,10 @@ class ProjectsController < ApplicationController
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
if params[:continue]
|
||||
attrs = {:parent_id => @project.parent_id}.reject {|k,v| v.nil?}
|
||||
redirect_to new_project_path(attrs)
|
||||
else
|
||||
redirect_to settings_project_path(@project)
|
||||
end
|
||||
redirect_to(params[:continue] ?
|
||||
{:controller => 'projects', :action => 'new', :project => {:parent_id => @project.parent_id}.reject {|k,v| v.nil?}} :
|
||||
{:controller => 'projects', :action => 'settings', :id => @project}
|
||||
)
|
||||
}
|
||||
format.api { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
|
||||
end
|
||||
@@ -106,11 +104,15 @@ class ProjectsController < ApplicationController
|
||||
format.api { render_validation_errors(@project) }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def copy
|
||||
@issue_custom_fields = IssueCustomField.sorted.all
|
||||
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
|
||||
@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])
|
||||
if request.get?
|
||||
@project = Project.copy_from(@source_project)
|
||||
@@ -122,13 +124,13 @@ class ProjectsController < ApplicationController
|
||||
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')
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to settings_project_path(@project)
|
||||
redirect_to :controller => 'projects', :action => 'settings', :id => @project
|
||||
elsif !@project.new_record?
|
||||
# Project was created
|
||||
# But some objects were not copied due to validation failures
|
||||
# (eg. issues from disabled trackers)
|
||||
# TODO: inform about that
|
||||
redirect_to settings_project_path(@project)
|
||||
redirect_to :controller => 'projects', :action => 'settings', :id => @project
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -136,17 +138,17 @@ class ProjectsController < ApplicationController
|
||||
# source_project not found
|
||||
render_404
|
||||
end
|
||||
|
||||
|
||||
# Show @project
|
||||
def show
|
||||
# try to redirect to the requested menu item
|
||||
if params[:jump] && redirect_to_project_menu_item(@project, params[:jump])
|
||||
return
|
||||
if params[:jump]
|
||||
# try to redirect to the requested menu item
|
||||
redirect_to_project_menu_item(@project, params[:jump]) && return
|
||||
end
|
||||
|
||||
@users_by_role = @project.users_by_role
|
||||
@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
|
||||
|
||||
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)
|
||||
|
||||
if User.current.allowed_to?(:view_time_entries, @project)
|
||||
@total_hours = TimeEntry.visible.where(cond).sum(:hours).to_f
|
||||
@total_hours = TimeEntry.visible.sum(:hours, :include => :project, :conditions => cond).to_f
|
||||
end
|
||||
|
||||
@key = User.current.rss_key
|
||||
@@ -167,7 +169,7 @@ class ProjectsController < ApplicationController
|
||||
end
|
||||
|
||||
def settings
|
||||
@issue_custom_fields = IssueCustomField.sorted.all
|
||||
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
|
||||
@issue_category ||= IssueCategory.new
|
||||
@member ||= @project.members.new
|
||||
@trackers = Tracker.sorted.all
|
||||
@@ -184,7 +186,7 @@ class ProjectsController < ApplicationController
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to settings_project_path(@project)
|
||||
redirect_to :action => 'settings', :id => @project
|
||||
}
|
||||
format.api { render_api_ok }
|
||||
end
|
||||
@@ -202,7 +204,7 @@ class ProjectsController < ApplicationController
|
||||
def modules
|
||||
@project.enabled_module_names = params[:enabled_module_names]
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to settings_project_path(@project, :tab => 'modules')
|
||||
redirect_to :action => 'settings', :id => @project, :tab => 'modules'
|
||||
end
|
||||
|
||||
def archive
|
||||
@@ -211,12 +213,12 @@ class ProjectsController < ApplicationController
|
||||
flash[:error] = l(:error_can_not_archive_project)
|
||||
end
|
||||
end
|
||||
redirect_to admin_projects_path(:status => params[:status])
|
||||
redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
|
||||
end
|
||||
|
||||
def unarchive
|
||||
@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
|
||||
|
||||
def close
|
||||
@@ -235,7 +237,7 @@ class ProjectsController < ApplicationController
|
||||
if api_request? || params[:confirm]
|
||||
@project_to_destroy.destroy
|
||||
respond_to do |format|
|
||||
format.html { redirect_to admin_projects_path }
|
||||
format.html { redirect_to :controller => 'admin', :action => 'projects' }
|
||||
format.api { render_api_ok }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -32,34 +32,35 @@ class QueriesController < ApplicationController
|
||||
@limit = per_page_option
|
||||
end
|
||||
|
||||
@query_count = IssueQuery.visible.count
|
||||
@query_pages = Paginator.new @query_count, @limit, params['page']
|
||||
@queries = IssueQuery.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name")
|
||||
@query_count = Query.visible.count
|
||||
@query_pages = Paginator.new self, @query_count, @limit, params['page']
|
||||
@queries = Query.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name")
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render :nothing => true }
|
||||
format.api
|
||||
end
|
||||
end
|
||||
|
||||
def new
|
||||
@query = IssueQuery.new
|
||||
@query = Query.new
|
||||
@query.user = User.current
|
||||
@query.project = @project
|
||||
@query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
|
||||
@query.build_from_params(params)
|
||||
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
|
||||
build_query_from_params
|
||||
end
|
||||
|
||||
def create
|
||||
@query = IssueQuery.new(params[:query])
|
||||
@query = Query.new(params[:query])
|
||||
@query.user = User.current
|
||||
@query.project = params[:query_is_for_all] ? nil : @project
|
||||
@query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
|
||||
@query.build_from_params(params)
|
||||
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
|
||||
build_query_from_params
|
||||
@query.column_names = nil if params[:default_columns]
|
||||
|
||||
if @query.save
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to_issues(:query_id => @query)
|
||||
redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
|
||||
else
|
||||
render :action => 'new', :layout => !request.xhr?
|
||||
end
|
||||
@@ -71,13 +72,13 @@ class QueriesController < ApplicationController
|
||||
def update
|
||||
@query.attributes = params[:query]
|
||||
@query.project = nil if params[:query_is_for_all]
|
||||
@query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
|
||||
@query.build_from_params(params)
|
||||
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
|
||||
build_query_from_params
|
||||
@query.column_names = nil if params[:default_columns]
|
||||
|
||||
if @query.save
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to_issues(:query_id => @query)
|
||||
redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
|
||||
else
|
||||
render :action => 'edit'
|
||||
end
|
||||
@@ -85,12 +86,12 @@ class QueriesController < ApplicationController
|
||||
|
||||
def destroy
|
||||
@query.destroy
|
||||
redirect_to_issues(:set_filter => 1)
|
||||
redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1
|
||||
end
|
||||
|
||||
private
|
||||
def find_query
|
||||
@query = IssueQuery.find(params[:id])
|
||||
@query = Query.find(params[:id])
|
||||
@project = @query.project
|
||||
render_403 unless @query.editable_by?(User.current)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
@@ -103,16 +104,4 @@ private
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def redirect_to_issues(options)
|
||||
if params[:gantt]
|
||||
if @project
|
||||
redirect_to project_gantt_path(@project, options)
|
||||
else
|
||||
redirect_to issues_gantt_path(options)
|
||||
end
|
||||
else
|
||||
redirect_to _project_issues_path(@project, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -90,6 +90,6 @@ class ReportsController < ApplicationController
|
||||
private
|
||||
|
||||
def find_issue_statuses
|
||||
@statuses = IssueStatus.sorted.all
|
||||
@statuses = IssueStatus.find(:all, :order => 'position')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -111,7 +111,7 @@ class RepositoriesController < ApplicationController
|
||||
end
|
||||
|
||||
def show
|
||||
@repository.fetch_changesets if @project.active? && Setting.autofetch_changesets? && @path.empty?
|
||||
@repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty?
|
||||
|
||||
@entries = @repository.entries(@path, @rev)
|
||||
@changeset = @repository.find_changeset_by_name(@rev)
|
||||
@@ -138,14 +138,13 @@ class RepositoriesController < ApplicationController
|
||||
|
||||
def revisions
|
||||
@changeset_count = @repository.changesets.count
|
||||
@changeset_pages = Paginator.new @changeset_count,
|
||||
@changeset_pages = Paginator.new self, @changeset_count,
|
||||
per_page_option,
|
||||
params['page']
|
||||
@changesets = @repository.changesets.
|
||||
limit(@changeset_pages.per_page).
|
||||
offset(@changeset_pages.offset).
|
||||
includes(:user, :repository, :parents).
|
||||
all
|
||||
@changesets = @repository.changesets.find(:all,
|
||||
:limit => @changeset_pages.items_per_page,
|
||||
:offset => @changeset_pages.current.offset,
|
||||
:include => [:user, :repository, :parents])
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render :layout => false if request.xhr? }
|
||||
@@ -352,18 +351,15 @@ class RepositoriesController < ApplicationController
|
||||
@date_to = Date.today
|
||||
@date_from = @date_to << 11
|
||||
@date_from = Date.civil(@date_from.year, @date_from.month, 1)
|
||||
commits_by_day = Changeset.
|
||||
where("repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to).
|
||||
group(:commit_date).
|
||||
count
|
||||
commits_by_day = Changeset.count(
|
||||
:all, :group => :commit_date,
|
||||
:conditions => ["repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
|
||||
commits_by_month = [0] * 12
|
||||
commits_by_day.each {|c| commits_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
|
||||
|
||||
changes_by_day = Change.
|
||||
joins(:changeset).
|
||||
where("#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to).
|
||||
group(:commit_date).
|
||||
count
|
||||
changes_by_day = Change.count(
|
||||
:all, :group => :commit_date, :include => :changeset,
|
||||
:conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
|
||||
changes_by_month = [0] * 12
|
||||
changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
|
||||
|
||||
@@ -396,10 +392,10 @@ class RepositoriesController < ApplicationController
|
||||
end
|
||||
|
||||
def graph_commits_per_author(repository)
|
||||
commits_by_author = Changeset.where("repository_id = ?", repository.id).group(:committer).count
|
||||
commits_by_author = Changeset.count(:all, :group => :committer, :conditions => ["repository_id = ?", repository.id])
|
||||
commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}
|
||||
|
||||
changes_by_author = Change.joins(:changeset).where("#{Changeset.table_name}.repository_id = ?", repository.id).group(:committer).count
|
||||
changes_by_author = Change.count(:all, :group => :committer, :include => :changeset, :conditions => ["#{Changeset.table_name}.repository_id = ?", repository.id])
|
||||
h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
|
||||
|
||||
fields = commits_by_author.collect {|r| r.first}
|
||||
@@ -414,7 +410,7 @@ class RepositoriesController < ApplicationController
|
||||
fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
|
||||
|
||||
graph = SVG::Graph::BarHorizontal.new(
|
||||
:height => 30 * commits_data.length,
|
||||
:height => 400,
|
||||
:width => 800,
|
||||
:fields => fields,
|
||||
:stack => :side,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -26,7 +26,7 @@ class RolesController < ApplicationController
|
||||
def index
|
||||
respond_to do |format|
|
||||
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?
|
||||
}
|
||||
format.api {
|
||||
@@ -58,7 +58,7 @@ class RolesController < ApplicationController
|
||||
@role.workflow_rules.copy(copy_from)
|
||||
end
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to roles_path
|
||||
redirect_to :action => 'index'
|
||||
else
|
||||
@roles = Role.sorted.all
|
||||
render :action => 'new'
|
||||
@@ -71,7 +71,7 @@ class RolesController < ApplicationController
|
||||
def update
|
||||
if request.put? and @role.update_attributes(params[:role])
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to roles_path
|
||||
redirect_to :action => 'index'
|
||||
else
|
||||
render :action => 'edit'
|
||||
end
|
||||
@@ -79,10 +79,10 @@ class RolesController < ApplicationController
|
||||
|
||||
def destroy
|
||||
@role.destroy
|
||||
redirect_to roles_path
|
||||
redirect_to :action => 'index'
|
||||
rescue
|
||||
flash[:error] = l(:error_can_not_remove_role)
|
||||
redirect_to roles_path
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
|
||||
def permissions
|
||||
@@ -94,7 +94,7 @@ class RolesController < ApplicationController
|
||||
role.save
|
||||
end
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to roles_path
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -18,6 +18,9 @@
|
||||
class SearchController < ApplicationController
|
||||
before_filter :find_optional_project
|
||||
|
||||
helper :messages
|
||||
include MessagesHelper
|
||||
|
||||
def index
|
||||
@question = params[:q] || ""
|
||||
@question.strip!
|
||||
@@ -40,8 +43,8 @@ class SearchController < ApplicationController
|
||||
begin; offset = params[:offset].to_time if params[:offset]; rescue; end
|
||||
|
||||
# quick jump to an issue
|
||||
if (m = @question.match(/^#?(\d+)$/)) && (issue = Issue.visible.find_by_id(m[1].to_i))
|
||||
redirect_to issue_path(issue)
|
||||
if @question.match(/^#?(\d+)$/) && Issue.visible.find_by_id($1.to_i)
|
||||
redirect_to :controller => "issues", :action => "show", :id => $1
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -19,8 +19,6 @@ class SettingsController < ApplicationController
|
||||
layout 'admin'
|
||||
menu_item :plugins, :only => :plugin
|
||||
|
||||
helper :queries
|
||||
|
||||
before_filter :require_admin
|
||||
|
||||
def index
|
||||
@@ -33,10 +31,12 @@ class SettingsController < ApplicationController
|
||||
if request.post? && params[:settings] && params[:settings].is_a?(Hash)
|
||||
settings = (params[:settings] || {}).dup.symbolize_keys
|
||||
settings.each do |name, value|
|
||||
Setting.set_from_params name, value
|
||||
# remove blank values in array settings
|
||||
value.delete_if {|v| v.blank? } if value.is_a?(Array)
|
||||
Setting[name] = value
|
||||
end
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to settings_path(:tab => params[:tab])
|
||||
redirect_to :action => 'edit', :tab => params[:tab]
|
||||
else
|
||||
@options = {}
|
||||
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 << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank?
|
||||
|
||||
@commit_update_keywords = Setting.commit_update_keywords.dup
|
||||
@commit_update_keywords = [{}] unless @commit_update_keywords.is_a?(Array) && @commit_update_keywords.any?
|
||||
|
||||
Redmine::Themes.rescan
|
||||
end
|
||||
end
|
||||
|
||||
def plugin
|
||||
@plugin = Redmine::Plugin.find(params[:id])
|
||||
unless @plugin.configurable?
|
||||
render_404
|
||||
return
|
||||
end
|
||||
|
||||
if request.post?
|
||||
Setting.send "plugin_#{@plugin.id}=", params[:settings]
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to plugin_settings_path(@plugin)
|
||||
redirect_to :action => 'plugin', :id => @plugin.id
|
||||
else
|
||||
@partial = @plugin.settings[:partial]
|
||||
@settings = Setting.send "plugin_#{@plugin.id}"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -19,7 +19,11 @@ class SysController < ActionController::Base
|
||||
before_filter :check_enabled
|
||||
|
||||
def projects
|
||||
p = Project.active.has_module(:repository).order("#{Project.table_name}.identifier").preload(:repository).all
|
||||
p = Project.active.has_module(:repository).find(
|
||||
:all,
|
||||
:include => :repository,
|
||||
:order => "#{Project.table_name}.identifier"
|
||||
)
|
||||
# extra_info attribute from repository breaks activeresource client
|
||||
render :xml => p.to_xml(
|
||||
:only => [:id, :identifier, :name, :is_public, :status],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -30,31 +30,42 @@ class TimelogController < ApplicationController
|
||||
accept_rss_auth :index
|
||||
accept_api_auth :index, :show, :create, :update, :destroy
|
||||
|
||||
rescue_from Query::StatementInvalid, :with => :query_statement_invalid
|
||||
|
||||
helper :sort
|
||||
include SortHelper
|
||||
helper :issues
|
||||
include TimelogHelper
|
||||
helper :custom_fields
|
||||
include CustomFieldsHelper
|
||||
helper :queries
|
||||
include QueriesHelper
|
||||
|
||||
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)
|
||||
sort_update(@query.sortable_columns)
|
||||
scope = time_entry_scope(:order => sort_clause).
|
||||
includes(:project, :activity, :user, :issue).
|
||||
preload(:issue => [:project, :tracker, :status, :assigned_to, :priority])
|
||||
retrieve_date_range
|
||||
|
||||
scope = TimeEntry.visible.spent_between(@from, @to)
|
||||
if @issue
|
||||
scope = scope.on_issue(@issue)
|
||||
elsif @project
|
||||
scope = scope.on_project(@project, Setting.display_subprojects_issues?)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
# Paginate results
|
||||
@entry_count = scope.count
|
||||
@entry_pages = Paginator.new @entry_count, per_page_option, params['page']
|
||||
@entries = scope.offset(@entry_pages.offset).limit(@entry_pages.per_page).all
|
||||
@entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
|
||||
@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
|
||||
|
||||
render :layout => !request.xhr?
|
||||
@@ -62,25 +73,35 @@ class TimelogController < ApplicationController
|
||||
format.api {
|
||||
@entry_count = scope.count
|
||||
@offset, @limit = api_offset_and_limit
|
||||
@entries = scope.offset(@offset).limit(@limit).preload(:custom_values => :custom_field).all
|
||||
@entries = scope.all(
|
||||
:include => [:project, :activity, :user, {:issue => :tracker}],
|
||||
:order => sort_clause,
|
||||
:limit => @limit,
|
||||
:offset => @offset
|
||||
)
|
||||
}
|
||||
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))
|
||||
}
|
||||
format.csv {
|
||||
# Export all entries
|
||||
@entries = scope.all
|
||||
send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'timelog.csv')
|
||||
@entries = scope.all(
|
||||
: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
|
||||
|
||||
def report
|
||||
@query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
|
||||
scope = time_entry_scope
|
||||
|
||||
@report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], scope)
|
||||
retrieve_date_range
|
||||
@report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], @from, @to)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render :layout => !request.xhr? }
|
||||
@@ -113,24 +134,16 @@ class TimelogController < ApplicationController
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
if params[:continue]
|
||||
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},
|
||||
: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
|
||||
redirect_to new_project_time_entry_path(@time_entry.project, options)
|
||||
end
|
||||
else
|
||||
options = {
|
||||
redirect_to :action => 'new',
|
||||
: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]
|
||||
}
|
||||
redirect_to new_time_entry_path(options)
|
||||
end
|
||||
else
|
||||
redirect_back_or_default project_time_entries_path(@time_entry.project)
|
||||
redirect_back_or_default :action => 'index', :project_id => @time_entry.project
|
||||
end
|
||||
}
|
||||
format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
|
||||
@@ -156,7 +169,7 @@ class TimelogController < ApplicationController
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
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 }
|
||||
end
|
||||
@@ -182,13 +195,12 @@ class TimelogController < ApplicationController
|
||||
time_entry.safe_attributes = attributes
|
||||
call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
|
||||
unless time_entry.save
|
||||
logger.info "time entry could not be updated: #{time_entry.errors.full_messages}" if logger && logger.info
|
||||
# Keep unsaved time_entry ids to display them in flash error
|
||||
unsaved_time_entry_ids << time_entry.id
|
||||
end
|
||||
end
|
||||
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
|
||||
|
||||
def destroy
|
||||
@@ -207,7 +219,7 @@ class TimelogController < ApplicationController
|
||||
else
|
||||
flash[:error] = l(:notice_unable_delete_time_entry)
|
||||
end
|
||||
redirect_back_or_default project_time_entries_path(@projects.first)
|
||||
redirect_back_or_default(:action => 'index', :project_id => @projects.first)
|
||||
}
|
||||
format.api {
|
||||
if destroyed
|
||||
@@ -279,13 +291,51 @@ private
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the TimeEntry scope for index and report actions
|
||||
def time_entry_scope(options={})
|
||||
scope = @query.results_scope(options)
|
||||
if @issue
|
||||
scope = scope.on_issue(@issue)
|
||||
# Retrieves the date range based on predefined ranges or specific from/to param dates
|
||||
def retrieve_date_range
|
||||
@free_period = false
|
||||
@from, @to = nil, nil
|
||||
|
||||
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
|
||||
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
|
||||
scope
|
||||
|
||||
@from, @to = @to, @from if @from && @to && @from > @to
|
||||
end
|
||||
|
||||
def parse_params_for_bulk_time_entry_attributes(params)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -25,7 +25,7 @@ class TrackersController < ApplicationController
|
||||
def index
|
||||
respond_to do |format|
|
||||
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?
|
||||
}
|
||||
format.api {
|
||||
@@ -36,19 +36,19 @@ class TrackersController < ApplicationController
|
||||
|
||||
def new
|
||||
@tracker ||= Tracker.new(params[:tracker])
|
||||
@trackers = Tracker.sorted.all
|
||||
@projects = Project.all
|
||||
@trackers = Tracker.find :all, :order => 'position'
|
||||
@projects = Project.find(:all)
|
||||
end
|
||||
|
||||
def create
|
||||
@tracker = Tracker.new(params[:tracker])
|
||||
if @tracker.save
|
||||
if request.post? and @tracker.save
|
||||
# workflow copy
|
||||
if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from]))
|
||||
@tracker.workflow_rules.copy(copy_from)
|
||||
end
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to trackers_path
|
||||
redirect_to :action => 'index'
|
||||
return
|
||||
end
|
||||
new
|
||||
@@ -57,14 +57,14 @@ class TrackersController < ApplicationController
|
||||
|
||||
def edit
|
||||
@tracker ||= Tracker.find(params[:id])
|
||||
@projects = Project.all
|
||||
@projects = Project.find(:all)
|
||||
end
|
||||
|
||||
def update
|
||||
@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)
|
||||
redirect_to trackers_path
|
||||
redirect_to :action => 'index'
|
||||
return
|
||||
end
|
||||
edit
|
||||
@@ -78,7 +78,7 @@ class TrackersController < ApplicationController
|
||||
else
|
||||
@tracker.destroy
|
||||
end
|
||||
redirect_to trackers_path
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
|
||||
def fields
|
||||
@@ -92,7 +92,7 @@ class TrackersController < ApplicationController
|
||||
end
|
||||
end
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to fields_trackers_path
|
||||
redirect_to :action => 'fields'
|
||||
return
|
||||
end
|
||||
@trackers = Tracker.sorted.all
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# 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?
|
||||
|
||||
@user_count = scope.count
|
||||
@user_pages = Paginator.new @user_count, @limit, params['page']
|
||||
@offset ||= @user_pages.offset
|
||||
@users = scope.order(sort_clause).limit(@limit).offset(@offset).all
|
||||
@user_pages = Paginator.new self, @user_count, @limit, params['page']
|
||||
@offset ||= @user_pages.current.offset
|
||||
@users = scope.find :all,
|
||||
:order => sort_clause,
|
||||
:limit => @limit,
|
||||
:offset => @offset
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
@@ -55,12 +58,12 @@ class UsersController < ApplicationController
|
||||
render :layout => !request.xhr?
|
||||
}
|
||||
format.api
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
# show projects based on current user visibility
|
||||
@memberships = @user.memberships.where(Project.visible_condition(User.current)).all
|
||||
@memberships = @user.memberships.all(:conditions => Project.visible_condition(User.current))
|
||||
|
||||
events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
|
||||
@events_by_day = events.group_by(&:event_date)
|
||||
@@ -80,8 +83,7 @@ class UsersController < ApplicationController
|
||||
|
||||
def new
|
||||
@user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
|
||||
@user.safe_attributes = params[:user]
|
||||
@auth_sources = AuthSource.all
|
||||
@auth_sources = AuthSource.find(:all)
|
||||
end
|
||||
|
||||
def create
|
||||
@@ -93,24 +95,24 @@ class UsersController < ApplicationController
|
||||
|
||||
if @user.save
|
||||
@user.pref.attributes = params[:pref]
|
||||
@user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
|
||||
@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|
|
||||
format.html {
|
||||
flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user)))
|
||||
if params[:continue]
|
||||
attrs = params[:user].slice(:generate_password)
|
||||
redirect_to new_user_path(:user => attrs)
|
||||
else
|
||||
redirect_to edit_user_path(@user)
|
||||
end
|
||||
redirect_to(params[:continue] ?
|
||||
{:controller => 'users', :action => 'new'} :
|
||||
{:controller => 'users', :action => 'edit', :id => @user}
|
||||
)
|
||||
}
|
||||
format.api { render :action => 'show', :status => :created, :location => user_url(@user) }
|
||||
end
|
||||
else
|
||||
@auth_sources = AuthSource.all
|
||||
@auth_sources = AuthSource.find(:all)
|
||||
# Clear password input
|
||||
@user.password = @user.password_confirmation = nil
|
||||
|
||||
@@ -122,7 +124,7 @@ class UsersController < ApplicationController
|
||||
end
|
||||
|
||||
def edit
|
||||
@auth_sources = AuthSource.all
|
||||
@auth_sources = AuthSource.find(:all)
|
||||
@membership ||= Member.new
|
||||
end
|
||||
|
||||
@@ -137,14 +139,16 @@ class UsersController < ApplicationController
|
||||
was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
|
||||
# TODO: Similar to My#account
|
||||
@user.pref.attributes = params[:pref]
|
||||
@user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
|
||||
|
||||
if @user.save
|
||||
@user.pref.save
|
||||
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
|
||||
|
||||
if was_activated
|
||||
Mailer.account_activated(@user).deliver
|
||||
elsif @user.active? && params[:send_information] && @user.password.present? && @user.auth_source_id.nil?
|
||||
Mailer.account_information(@user, @user.password).deliver
|
||||
elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil?
|
||||
Mailer.account_information(@user, params[:user][:password]).deliver
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
@@ -155,7 +159,7 @@ class UsersController < ApplicationController
|
||||
format.api { render_api_ok }
|
||||
end
|
||||
else
|
||||
@auth_sources = AuthSource.all
|
||||
@auth_sources = AuthSource.find(:all)
|
||||
@membership ||= Member.new
|
||||
# Clear password input
|
||||
@user.password = @user.password_confirmation = nil
|
||||
@@ -170,7 +174,7 @@ class UsersController < ApplicationController
|
||||
def destroy
|
||||
@user.destroy
|
||||
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 }
|
||||
end
|
||||
end
|
||||
@@ -179,7 +183,7 @@ class UsersController < ApplicationController
|
||||
@membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
|
||||
@membership.save
|
||||
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
|
||||
end
|
||||
end
|
||||
@@ -190,7 +194,7 @@ class UsersController < ApplicationController
|
||||
@membership.destroy
|
||||
end
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -31,7 +31,7 @@ class VersionsController < ApplicationController
|
||||
def index
|
||||
respond_to do |format|
|
||||
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?})
|
||||
@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]
|
||||
@@ -46,11 +46,11 @@ class VersionsController < ApplicationController
|
||||
|
||||
@issues_by_version = {}
|
||||
if @selected_tracker_ids.any? && @versions.any?
|
||||
issues = Issue.visible.
|
||||
includes(:project, :tracker).
|
||||
preload(:status, :priority, :fixed_version).
|
||||
where(:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)).
|
||||
order("#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
|
||||
issues = Issue.visible.all(
|
||||
:include => [:project, :status, :tracker, :priority, :fixed_version],
|
||||
:conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)},
|
||||
:order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id"
|
||||
)
|
||||
@issues_by_version = issues.group_by(&:fixed_version)
|
||||
end
|
||||
@versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?}
|
||||
@@ -64,10 +64,9 @@ class VersionsController < ApplicationController
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
@issues = @version.fixed_issues.visible.
|
||||
includes(:status, :tracker, :priority).
|
||||
reorder("#{Tracker.table_name}.position, #{Issue.table_name}.id").
|
||||
all
|
||||
@issues = @version.fixed_issues.visible.find(:all,
|
||||
:include => [:status, :tracker, :priority],
|
||||
:order => "#{Tracker.table_name}.position, #{Issue.table_name}.id")
|
||||
}
|
||||
format.api
|
||||
end
|
||||
@@ -96,7 +95,7 @@ class VersionsController < ApplicationController
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
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
|
||||
format.js
|
||||
format.api do
|
||||
@@ -125,7 +124,7 @@ class VersionsController < ApplicationController
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
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 }
|
||||
end
|
||||
@@ -142,21 +141,21 @@ class VersionsController < ApplicationController
|
||||
if request.put?
|
||||
@project.close_completed_versions
|
||||
end
|
||||
redirect_to settings_project_path(@project, :tab => 'versions')
|
||||
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
|
||||
end
|
||||
|
||||
def destroy
|
||||
if @version.fixed_issues.empty?
|
||||
@version.destroy
|
||||
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 }
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
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 }
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# 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.
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
def unwatch
|
||||
set_watcher(@watchables, User.current, false)
|
||||
set_watcher(User.current, false)
|
||||
end
|
||||
|
||||
before_filter :find_project, :authorize, :only => [:new, :create, :append, :destroy, :autocomplete_for_user]
|
||||
accept_api_auth :create, :destroy
|
||||
|
||||
def new
|
||||
end
|
||||
|
||||
def create
|
||||
user_ids = []
|
||||
if params[:watcher].is_a?(Hash)
|
||||
user_ids << (params[:watcher][:user_ids] || params[:watcher][: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)
|
||||
if params[:watcher].is_a?(Hash) && request.post?
|
||||
user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
|
||||
user_ids.each do |user_id|
|
||||
Watcher.create(:watchable => @watched, :user_id => user_id)
|
||||
end
|
||||
end
|
||||
respond_to do |format|
|
||||
format.html { redirect_to_referer_or {render :text => 'Watcher added.', :layout => true}}
|
||||
format.js
|
||||
format.api { render_api_ok }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -57,24 +56,22 @@ class WatchersController < ApplicationController
|
||||
end
|
||||
|
||||
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|
|
||||
format.html { redirect_to :back }
|
||||
format.js
|
||||
format.api { render_api_ok }
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
@users -= @watched.watcher_users
|
||||
end
|
||||
render :layout => false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
private
|
||||
def find_project
|
||||
if params[:object_type] && params[:object_id]
|
||||
klass = Object.const_get(params[:object_type].camelcase)
|
||||
@@ -88,22 +85,11 @@ class WatchersController < ApplicationController
|
||||
render_404
|
||||
end
|
||||
|
||||
def find_watchables
|
||||
klass = Object.const_get(params[:object_type].camelcase) rescue nil
|
||||
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
|
||||
def set_watcher(user, watching)
|
||||
@watched.set_watcher(user, watching)
|
||||
respond_to do |format|
|
||||
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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -15,6 +15,8 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
require 'diff'
|
||||
|
||||
# The WikiController follows the Rails REST controller pattern but with
|
||||
# a few differences
|
||||
#
|
||||
@@ -35,7 +37,6 @@ class WikiController < ApplicationController
|
||||
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]
|
||||
accept_api_auth :index, :show, :update, :destroy
|
||||
before_filter :find_attachments, :only => [:preview]
|
||||
|
||||
helper :attachments
|
||||
include AttachmentsHelper
|
||||
@@ -62,12 +63,7 @@ class WikiController < ApplicationController
|
||||
|
||||
# display a page (in editing mode if it doesn't exist)
|
||||
def show
|
||||
if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project)
|
||||
deny_access
|
||||
return
|
||||
end
|
||||
@content = @page.content_for_version(params[:version])
|
||||
if @content.nil?
|
||||
if @page.new_record?
|
||||
if User.current.allowed_to?(:edit_wiki_pages, @project) && editable? && !api_request?
|
||||
edit
|
||||
render :action => 'edit'
|
||||
@@ -76,6 +72,11 @@ class WikiController < ApplicationController
|
||||
end
|
||||
return
|
||||
end
|
||||
if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project)
|
||||
deny_access
|
||||
return
|
||||
end
|
||||
@content = @page.content_for_version(params[:version])
|
||||
if User.current.allowed_to?(:export_wiki_pages, @project)
|
||||
if params[:format] == 'pdf'
|
||||
send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf")
|
||||
@@ -104,19 +105,19 @@ class WikiController < ApplicationController
|
||||
def edit
|
||||
return render_403 unless editable?
|
||||
if @page.new_record?
|
||||
@page.content = WikiContent.new(:page => @page)
|
||||
if params[:parent].present?
|
||||
@page.parent = @page.wiki.find_page(params[:parent].to_s)
|
||||
end
|
||||
end
|
||||
|
||||
@content = @page.content_for_version(params[:version])
|
||||
@content ||= WikiContent.new(:page => @page)
|
||||
@content.text = initial_page_content(@page) if @content.text.blank?
|
||||
# don't keep previous comment
|
||||
@content.comments = nil
|
||||
|
||||
# To prevent StaleObjectError exception when reverting to a previous version
|
||||
@content.version = @page.content.version if @page.content
|
||||
@content.version = @page.content.version
|
||||
|
||||
@text = @content.text
|
||||
if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
|
||||
@@ -130,9 +131,10 @@ class WikiController < ApplicationController
|
||||
def update
|
||||
return render_403 unless editable?
|
||||
was_new_page = @page.new_record?
|
||||
@page.content = WikiContent.new(:page => @page) if @page.new_record?
|
||||
@page.safe_attributes = params[:wiki_page]
|
||||
|
||||
@content = @page.content || WikiContent.new(:page => @page)
|
||||
@content = @page.content
|
||||
content_params = params[:content]
|
||||
if content_params.nil? && params[:wiki_page].is_a?(Hash)
|
||||
content_params = params[:wiki_page].slice(:text, :comments, :version)
|
||||
@@ -144,26 +146,23 @@ class WikiController < ApplicationController
|
||||
if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
|
||||
@section = params[:section].to_i
|
||||
@section_hash = params[:section_hash]
|
||||
@content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(@section, @text, @section_hash)
|
||||
@content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(params[:section].to_i, @text, @section_hash)
|
||||
else
|
||||
@content.version = content_params[:version] if content_params[:version]
|
||||
@content.text = @text
|
||||
end
|
||||
@content.author = User.current
|
||||
|
||||
if @page.save_with_content(@content)
|
||||
if @page.save_with_content
|
||||
attachments = Attachment.attach_files(@page, params[:attachments])
|
||||
render_attachment_warning_if_needed(@page)
|
||||
call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
anchor = @section ? "section-#{@section}" : nil
|
||||
redirect_to project_wiki_page_path(@project, @page.title, :anchor => anchor)
|
||||
}
|
||||
format.html { redirect_to :action => 'show', :project_id => @project, :id => @page.title }
|
||||
format.api {
|
||||
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
|
||||
render_api_ok
|
||||
end
|
||||
@@ -200,26 +199,25 @@ class WikiController < ApplicationController
|
||||
@original_title = @page.pretty_title
|
||||
if request.post? && @page.update_attributes(params[:wiki_page])
|
||||
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
|
||||
|
||||
def protect
|
||||
@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
|
||||
|
||||
# show page history
|
||||
def history
|
||||
@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
|
||||
@versions = @page.content.versions.
|
||||
select("id, author_id, comments, updated_on, version").
|
||||
reorder('version DESC').
|
||||
limit(@version_pages.per_page + 1).
|
||||
offset(@version_pages.offset).
|
||||
all
|
||||
@versions = @page.content.versions.find :all,
|
||||
:select => "id, author_id, comments, updated_on, version",
|
||||
:order => 'version DESC',
|
||||
:limit => @version_pages.items_per_page + 1,
|
||||
:offset => @version_pages.current.offset
|
||||
|
||||
render :layout => false if request.xhr?
|
||||
end
|
||||
@@ -262,7 +260,7 @@ class WikiController < ApplicationController
|
||||
end
|
||||
@page.destroy
|
||||
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 }
|
||||
end
|
||||
end
|
||||
@@ -272,7 +270,7 @@ class WikiController < ApplicationController
|
||||
|
||||
@content = @page.content_for_version(params[:version])
|
||||
@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
|
||||
|
||||
# Export wiki to a single pdf or html file
|
||||
@@ -294,7 +292,7 @@ class WikiController < ApplicationController
|
||||
# page is nil when previewing a new page
|
||||
return render_403 unless page.nil? || editable?(page)
|
||||
if page
|
||||
@attachments += page.attachments
|
||||
@attachements = page.attachments
|
||||
@previewed = page.content
|
||||
end
|
||||
@text = params[:content][:text]
|
||||
@@ -351,6 +349,6 @@ private
|
||||
end
|
||||
|
||||
def load_pages_for_index
|
||||
@pages = @wiki.pages.with_updated_on.reorder("#{WikiPage.table_name}.title").includes(:wiki => :project).includes(:parent).all
|
||||
@pages = @wiki.pages.with_updated_on.order("#{WikiPage.table_name}.title").includes(:wiki => :project).includes(:parent).all
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -30,7 +30,7 @@ class WikisController < ApplicationController
|
||||
def destroy
|
||||
if request.post? && params[:confirm] && @project.wiki
|
||||
@project.wiki.destroy
|
||||
redirect_to settings_project_path(@project, :tab => 'wiki')
|
||||
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'wiki'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -38,7 +38,7 @@ class WorkflowsController < ApplicationController
|
||||
}
|
||||
}
|
||||
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
|
||||
end
|
||||
end
|
||||
@@ -64,7 +64,7 @@ class WorkflowsController < ApplicationController
|
||||
|
||||
if request.post? && @role && @tracker
|
||||
WorkflowPermission.replace_permissions(@tracker, @role, params[:permissions] || {})
|
||||
redirect_to workflows_permissions_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only])
|
||||
redirect_to :action => 'permissions', :role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only]
|
||||
return
|
||||
end
|
||||
|
||||
@@ -106,12 +106,12 @@ class WorkflowsController < ApplicationController
|
||||
if request.post?
|
||||
if params[:source_tracker_id].blank? || params[:source_role_id].blank? || (@source_tracker.nil? && @source_role.nil?)
|
||||
flash.now[:error] = l(:error_workflow_copy_source)
|
||||
elsif @target_trackers.blank? || @target_roles.blank?
|
||||
elsif @target_trackers.nil? || @target_roles.nil?
|
||||
flash.now[:error] = l(:error_workflow_copy_target)
|
||||
else
|
||||
WorkflowRule.copy(@source_tracker, @source_role, @target_trackers, @target_roles)
|
||||
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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -24,7 +24,6 @@ module ApplicationHelper
|
||||
include Redmine::WikiFormatting::Macros::Definitions
|
||||
include Redmine::I18n
|
||||
include GravatarHelper::PublicMethods
|
||||
include Redmine::Pagination::Helper
|
||||
|
||||
extend Forwardable
|
||||
def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
|
||||
@@ -91,10 +90,14 @@ module ApplicationHelper
|
||||
# * :download - Force download (default: false)
|
||||
def link_to_attachment(attachment, options={})
|
||||
text = options.delete(:text) || attachment.filename
|
||||
route_method = options.delete(:download) ? :download_named_attachment_path : :named_attachment_path
|
||||
html_options = options.slice!(:only_path)
|
||||
url = send(route_method, attachment, attachment.filename, options)
|
||||
link_to text, url, html_options
|
||||
action = options.delete(:download) ? 'download' : 'show'
|
||||
opt_only_path = {}
|
||||
opt_only_path[:only_path] = (options[:only_path] == false ? false : true)
|
||||
options.delete(:only_path)
|
||||
link_to(h(text),
|
||||
{:controller => 'attachments', :action => action,
|
||||
:id => attachment, :filename => attachment.filename}.merge(opt_only_path),
|
||||
options)
|
||||
end
|
||||
|
||||
# Generates a link to a SCM revision
|
||||
@@ -116,11 +119,13 @@ module ApplicationHelper
|
||||
# Generates a link to a message
|
||||
def link_to_message(message, options={}, html_options = nil)
|
||||
link_to(
|
||||
truncate(message.subject, :length => 60),
|
||||
board_message_path(message.board_id, message.parent_id || message.id, {
|
||||
h(truncate(message.subject, :length => 60)),
|
||||
{ :controller => 'messages', :action => 'show',
|
||||
:board_id => message.board_id,
|
||||
:id => (message.parent_id || message.id),
|
||||
:r => (message.parent_id && message.id),
|
||||
:anchor => (message.parent_id ? "message-#{message.id}" : nil)
|
||||
}.merge(options)),
|
||||
}.merge(options),
|
||||
html_options
|
||||
)
|
||||
end
|
||||
@@ -129,29 +134,16 @@ module ApplicationHelper
|
||||
# Examples:
|
||||
#
|
||||
# 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, {}, :class => "project") # => html options with default url (project overview)
|
||||
#
|
||||
def link_to_project(project, options={}, html_options = nil)
|
||||
if project.archived?
|
||||
h(project.name)
|
||||
elsif options.key?(:action)
|
||||
ActiveSupport::Deprecation.warn "#link_to_project with :action option is deprecated and will be removed in Redmine 3.0."
|
||||
h(project)
|
||||
else
|
||||
url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
|
||||
link_to project.name, 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
|
||||
link_to(h(project), url, html_options)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -160,8 +152,8 @@ module ApplicationHelper
|
||||
end
|
||||
|
||||
def thumbnail_tag(attachment)
|
||||
link_to image_tag(thumbnail_path(attachment)),
|
||||
named_attachment_path(attachment, attachment.filename),
|
||||
link_to image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment)),
|
||||
{:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename},
|
||||
:title => attachment.filename
|
||||
end
|
||||
|
||||
@@ -195,7 +187,7 @@ module ApplicationHelper
|
||||
|
||||
def format_version_name(version)
|
||||
if version.project == @project
|
||||
h(version)
|
||||
h(version)
|
||||
else
|
||||
h("#{version.project} - #{version}")
|
||||
end
|
||||
@@ -316,8 +308,8 @@ module ApplicationHelper
|
||||
|
||||
def principals_check_box_tags(name, principals)
|
||||
s = ''
|
||||
principals.each do |principal|
|
||||
s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } #{h principal}</label>\n"
|
||||
principals.sort.each do |principal|
|
||||
s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
|
||||
end
|
||||
s.html_safe
|
||||
end
|
||||
@@ -330,7 +322,7 @@ module ApplicationHelper
|
||||
end
|
||||
groups = ''
|
||||
collection.sort.each do |element|
|
||||
selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) || element.id.to_s == selected
|
||||
selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
|
||||
(element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
|
||||
end
|
||||
unless groups.empty?
|
||||
@@ -343,15 +335,11 @@ module ApplicationHelper
|
||||
def options_for_membership_project_select(principal, projects)
|
||||
options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
|
||||
options << project_tree_options_for_select(projects) do |p|
|
||||
{:disabled => principal.projects.to_a.include?(p)}
|
||||
{:disabled => principal.projects.include?(p)}
|
||||
end
|
||||
options
|
||||
end
|
||||
|
||||
def option_tag(name, text, value, selected=nil, options={})
|
||||
content_tag 'option', value, options.merge(:value => value, :selected => (value == selected))
|
||||
end
|
||||
|
||||
# Truncates and returns the string as a single line
|
||||
def truncate_single_line(string, *args)
|
||||
truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
|
||||
@@ -384,7 +372,7 @@ module ApplicationHelper
|
||||
if @project
|
||||
link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
|
||||
else
|
||||
content_tag('abbr', text, :title => format_time(time))
|
||||
content_tag('acronym', text, :title => format_time(time))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -403,6 +391,59 @@ module ApplicationHelper
|
||||
str.blank? ? nil : str
|
||||
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) = «
|
||||
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) = »
|
||||
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)
|
||||
link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
|
||||
url.merge({"#{name}[move_to]" => 'highest'}),
|
||||
@@ -449,31 +490,12 @@ module ApplicationHelper
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a h2 tag and sets the html title with the given arguments
|
||||
def title(*args)
|
||||
strings = args.map do |arg|
|
||||
if arg.is_a?(Array) && arg.size >= 2
|
||||
link_to(*arg)
|
||||
else
|
||||
h(arg.to_s)
|
||||
end
|
||||
end
|
||||
html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
|
||||
content_tag('h2', strings.join(' » ').html_safe)
|
||||
end
|
||||
|
||||
# Sets the html title
|
||||
# Returns the html title when called without arguments
|
||||
# Current project name and app_title and automatically appended
|
||||
# Exemples:
|
||||
# html_title 'Foo', 'Bar'
|
||||
# html_title # => 'Foo - Bar - My Project - Redmine'
|
||||
def html_title(*args)
|
||||
if args.empty?
|
||||
title = @html_title || []
|
||||
title << @project.name if @project
|
||||
title << Setting.app_title unless Setting.app_title == title.last
|
||||
title.reject(&:blank?).join(' - ')
|
||||
title.select {|t| !t.blank? }.join(' - ')
|
||||
else
|
||||
@html_title ||= []
|
||||
@html_title += args
|
||||
@@ -488,18 +510,13 @@ module ApplicationHelper
|
||||
css << 'theme-' + theme.name
|
||||
end
|
||||
|
||||
css << 'project-' + @project.identifier if @project && @project.identifier.present?
|
||||
css << 'controller-' + controller_name
|
||||
css << 'action-' + action_name
|
||||
css.join(' ')
|
||||
end
|
||||
|
||||
def accesskey(s)
|
||||
@used_accesskeys ||= []
|
||||
key = Redmine::AccessKeys.key_for(s)
|
||||
return nil if @used_accesskeys.include?(key)
|
||||
@used_accesskeys << key
|
||||
key
|
||||
Redmine::AccessKeys.key_for s
|
||||
end
|
||||
|
||||
# Formats text according to system settings.
|
||||
@@ -580,14 +597,14 @@ module ApplicationHelper
|
||||
|
||||
def parse_inline_attachments(text, project, obj, attr, only_path, options)
|
||||
# when using an image link, try to use an attachment, if possible
|
||||
attachments = options[:attachments] || []
|
||||
attachments += obj.attachments if obj.respond_to?(:attachments)
|
||||
if attachments.present?
|
||||
if options[:attachments] || (obj && obj.respond_to?(:attachments))
|
||||
attachments = options[:attachments] || obj.attachments
|
||||
text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
|
||||
filename, ext, alt, alttext = $1.downcase, $2, $3, $4
|
||||
# search for the picture in attachments
|
||||
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('"', '')
|
||||
if !desc.blank? && alttext.blank?
|
||||
alt = " title=\"#{desc}\" alt=\"#{desc}\""
|
||||
@@ -616,9 +633,9 @@ module ApplicationHelper
|
||||
esc, all, page, title = $1, $2, $3, $5
|
||||
if esc.nil?
|
||||
if page =~ /^([^\:]+)\:(.*)$/
|
||||
identifier, page = $1, $2
|
||||
link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
|
||||
title ||= identifier if page.blank?
|
||||
link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
|
||||
page = $2
|
||||
title ||= $1 if page.blank?
|
||||
end
|
||||
|
||||
if link_project && link_project.wiki
|
||||
@@ -639,7 +656,7 @@ module ApplicationHelper
|
||||
else
|
||||
wiki_page_id = page.present? ? Wiki.titleize(page) : nil
|
||||
parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
|
||||
url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
|
||||
url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
|
||||
:id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
|
||||
end
|
||||
end
|
||||
@@ -680,20 +697,16 @@ module ApplicationHelper
|
||||
# export:some/file -> Force the download of the file
|
||||
# Forum messages:
|
||||
# message#1218 -> Link to message with id 1218
|
||||
# Projects:
|
||||
# project:someproject -> Link to project named "someproject"
|
||||
# project#3 -> Link to project with id 3
|
||||
#
|
||||
# Links can refer other objects from other projects, using project identifier:
|
||||
# identifier:r52
|
||||
# identifier:document:"Some document"
|
||||
# identifier:version:1.0.0
|
||||
# identifier:source:some/file
|
||||
def parse_redmine_links(text, default_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|
|
||||
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|
|
||||
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
|
||||
project = default_project
|
||||
if project_identifier
|
||||
project = Project.visible.find_by_identifier(project_identifier)
|
||||
end
|
||||
@@ -719,7 +732,7 @@ module ApplicationHelper
|
||||
when nil
|
||||
if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status)
|
||||
anchor = comment_id ? "note-#{comment_id}" : nil
|
||||
link = link_to(h("##{oid}#{comment_suffix}"), {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
|
||||
link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
|
||||
:class => issue.css_classes,
|
||||
:title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
|
||||
end
|
||||
@@ -779,21 +792,21 @@ module ApplicationHelper
|
||||
when 'commit', 'source', 'export'
|
||||
if project
|
||||
repository = nil
|
||||
if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
|
||||
if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
|
||||
repo_prefix, repo_identifier, name = $1, $2, $3
|
||||
repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
|
||||
else
|
||||
repository = project.repository
|
||||
end
|
||||
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},
|
||||
:class => 'changeset',
|
||||
:title => truncate_single_line(changeset.comments, :length => 100)
|
||||
:title => truncate_single_line(h(changeset.comments), :length => 100)
|
||||
end
|
||||
else
|
||||
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
|
||||
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),
|
||||
@@ -806,11 +819,12 @@ module ApplicationHelper
|
||||
end
|
||||
when 'attachment'
|
||||
attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
|
||||
if attachments && attachment = Attachment.latest_attach(attachments, name)
|
||||
link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
|
||||
if attachments && attachment = attachments.detect {|a| a.filename == name }
|
||||
link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
|
||||
:class => 'attachment'
|
||||
end
|
||||
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')
|
||||
end
|
||||
end
|
||||
@@ -831,8 +845,7 @@ module ApplicationHelper
|
||||
content_tag('div',
|
||||
link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
|
||||
:class => 'contextual',
|
||||
:title => l(:button_edit_section),
|
||||
:id => "section-#{@current_section}") + heading.html_safe
|
||||
:title => l(:button_edit_section)) + heading.html_safe
|
||||
else
|
||||
heading
|
||||
end
|
||||
@@ -1003,7 +1016,7 @@ module ApplicationHelper
|
||||
html << "</ul></div>\n"
|
||||
end
|
||||
html.html_safe
|
||||
end
|
||||
end
|
||||
|
||||
def delete_link(url, options={})
|
||||
options = {
|
||||
@@ -1017,8 +1030,8 @@ module ApplicationHelper
|
||||
|
||||
def preview_link(url, form, target='preview', options={})
|
||||
content_tag 'a', l(:label_preview), {
|
||||
:href => "#",
|
||||
:onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
|
||||
:href => "#",
|
||||
:onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
|
||||
:accesskey => accesskey(:preview)
|
||||
}.merge(options)
|
||||
end
|
||||
@@ -1063,8 +1076,8 @@ module ApplicationHelper
|
||||
(pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
|
||||
(pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
|
||||
(pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
|
||||
), :class => 'progress progress-#{pcts[0]}', :style => "width: #{width};").html_safe +
|
||||
content_tag('p', legend, :class => 'percent').html_safe
|
||||
), :class => 'progress', :style => "width: #{width};").html_safe +
|
||||
content_tag('p', legend, :class => 'pourcent').html_safe
|
||||
end
|
||||
|
||||
def checked_image(checked=true)
|
||||
@@ -1096,7 +1109,6 @@ module ApplicationHelper
|
||||
|
||||
def include_calendar_headers_tags
|
||||
unless @calendar_headers_tags_included
|
||||
tags = javascript_include_tag("datepicker")
|
||||
@calendar_headers_tags_included = true
|
||||
content_for :header_tags do
|
||||
start_of_week = Setting.start_of_week
|
||||
@@ -1104,16 +1116,15 @@ module ApplicationHelper
|
||||
# Redmine uses 1..7 (monday..sunday) in settings and locales
|
||||
# JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
|
||||
start_of_week = start_of_week.to_i % 7
|
||||
tags << javascript_tag(
|
||||
|
||||
tags = javascript_tag(
|
||||
"var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
|
||||
"showOn: 'button', buttonImageOnly: true, buttonImage: '" +
|
||||
"showOn: 'button', buttonImageOnly: true, buttonImage: '" +
|
||||
path_to_image('/images/calendar.png') +
|
||||
"', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
|
||||
"selectOtherMonths: true, changeMonth: true, changeYear: true, " +
|
||||
"beforeShow: beforeShowDatePicker};")
|
||||
"', showButtonPanel: true};")
|
||||
jquery_locale = l('jquery.locale', :default => current_language.to_s)
|
||||
unless jquery_locale == 'en'
|
||||
tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
|
||||
tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
|
||||
end
|
||||
tags
|
||||
end
|
||||
@@ -1214,7 +1225,7 @@ module ApplicationHelper
|
||||
|
||||
def sanitize_anchor_name(anchor)
|
||||
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
|
||||
# TODO: remove when ruby1.8 is no longer supported
|
||||
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
|
||||
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'
|
||||
tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
|
||||
end
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -36,7 +36,7 @@ module ContextMenusHelper
|
||||
|
||||
def bulk_update_custom_field_context_menu_link(field, text, value)
|
||||
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,
|
||||
:selected => (@issue && @issue.custom_field_value(field) == value)
|
||||
end
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -19,33 +19,12 @@
|
||||
|
||||
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
|
||||
CUSTOM_FIELDS_TABS
|
||||
CustomField::CUSTOM_FIELDS_TABS
|
||||
end
|
||||
|
||||
# Return custom field html tag corresponding to its format
|
||||
def custom_field_tag(name, custom_value)
|
||||
def custom_field_tag(name, custom_value)
|
||||
custom_field = custom_value.custom_field
|
||||
field_name = "#{name}[custom_field_values][#{custom_field.id}]"
|
||||
field_name << "[]" if custom_field.multiple?
|
||||
@@ -98,44 +77,32 @@ module CustomFieldsHelper
|
||||
custom_field_label_tag(name, custom_value, options) + custom_field_tag(name, custom_value)
|
||||
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 << "[]" if custom_field.multiple?
|
||||
field_id = "#{name}_custom_field_values_#{custom_field.id}"
|
||||
|
||||
tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"}
|
||||
|
||||
unset_tag = ''
|
||||
unless custom_field.is_required?
|
||||
unset_tag = content_tag('label',
|
||||
check_box_tag(field_name, '__none__', (value == '__none__'), :id => nil, :data => {:disables => "##{field_id}"}) + l(:button_clear),
|
||||
:class => 'inline'
|
||||
)
|
||||
end
|
||||
|
||||
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
|
||||
case field_format.try(:edit_as)
|
||||
when "date"
|
||||
text_field_tag(field_name, value, tag_options.merge(:size => 10)) +
|
||||
calendar_for(field_id) +
|
||||
unset_tag
|
||||
text_field_tag(field_name, '', tag_options.merge(:size => 10)) +
|
||||
calendar_for(field_id)
|
||||
when "text"
|
||||
text_area_tag(field_name, value, tag_options.merge(:rows => 3)) +
|
||||
'<br />'.html_safe +
|
||||
unset_tag
|
||||
text_area_tag(field_name, '', tag_options.merge(:rows => 3))
|
||||
when "bool"
|
||||
select_tag(field_name, options_for_select([[l(:label_no_change_option), ''],
|
||||
[l(:general_text_yes), '1'],
|
||||
[l(:general_text_no), '0']], value), tag_options)
|
||||
[l(:general_text_no), '0']]), tag_options)
|
||||
when "list"
|
||||
options = []
|
||||
options << [l(:label_no_change_option), ''] unless custom_field.multiple?
|
||||
options << [l(:label_none), '__none__'] unless custom_field.is_required?
|
||||
options += custom_field.possible_values_options(projects)
|
||||
select_tag(field_name, options_for_select(options, value), tag_options.merge(:multiple => custom_field.multiple?))
|
||||
select_tag(field_name, options_for_select(options), tag_options.merge(:multiple => custom_field.multiple?))
|
||||
else
|
||||
text_field_tag(field_name, value, tag_options) +
|
||||
unset_tag
|
||||
text_field_tag(field_name, '', tag_options)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -24,19 +24,19 @@ module GanttHelper
|
||||
when :in
|
||||
if gantt.zoom < 4
|
||||
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'
|
||||
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
|
||||
|
||||
when :out
|
||||
if gantt.zoom > 1
|
||||
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'
|
||||
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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# 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
|
||||
# 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}
|
||||
]
|
||||
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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -94,20 +94,6 @@ module IssuesHelper
|
||||
s.html_safe
|
||||
end
|
||||
|
||||
# Returns an array of error messages for bulk edited issues
|
||||
def bulk_edit_error_messages(issues)
|
||||
messages = {}
|
||||
issues.each do |issue|
|
||||
issue.errors.full_messages.each do |message|
|
||||
messages[message] ||= []
|
||||
messages[message] << issue
|
||||
end
|
||||
end
|
||||
messages.map { |message, issues|
|
||||
"#{message}: " + issues.map {|i| "##{i.id}"}.join(', ')
|
||||
}
|
||||
end
|
||||
|
||||
# Returns a link for adding a new subtask to the given issue
|
||||
def link_to_new_subtask(issue)
|
||||
attrs = {
|
||||
@@ -160,13 +146,12 @@ module IssuesHelper
|
||||
end
|
||||
|
||||
def render_custom_fields_rows(issue)
|
||||
values = issue.visible_custom_field_values
|
||||
return if values.empty?
|
||||
return if issue.custom_field_values.empty?
|
||||
ordered_values = []
|
||||
half = (values.size / 2.0).ceil
|
||||
half = (issue.custom_field_values.size / 2.0).ceil
|
||||
half.times do |i|
|
||||
ordered_values << values[i]
|
||||
ordered_values << values[i + half]
|
||||
ordered_values << issue.custom_field_values[i]
|
||||
ordered_values << issue.custom_field_values[i + half]
|
||||
end
|
||||
s = "<tr>\n"
|
||||
n = 0
|
||||
@@ -199,60 +184,36 @@ module IssuesHelper
|
||||
|
||||
def sidebar_queries
|
||||
unless @sidebar_queries
|
||||
@sidebar_queries = IssueQuery.visible.
|
||||
order("#{Query.table_name}.name ASC").
|
||||
@sidebar_queries = Query.visible.all(
|
||||
:order => "#{Query.table_name}.name ASC",
|
||||
# Project specific queries and global queries
|
||||
where(@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id]).
|
||||
all
|
||||
:conditions => (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id])
|
||||
)
|
||||
end
|
||||
@sidebar_queries
|
||||
end
|
||||
|
||||
def query_links(title, queries)
|
||||
return '' if queries.empty?
|
||||
# links to #index on issues/show
|
||||
url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params
|
||||
|
||||
content_tag('h3', title) + "\n" +
|
||||
content_tag('ul',
|
||||
queries.collect {|query|
|
||||
css = 'query'
|
||||
css << ' selected' if query == @query
|
||||
content_tag('li', link_to(query.name, url_params.merge(:query_id => query), :class => css))
|
||||
}.join("\n").html_safe,
|
||||
:class => 'queries'
|
||||
) + "\n"
|
||||
content_tag('h3', h(title)) +
|
||||
queries.collect {|query|
|
||||
css = 'query'
|
||||
css << ' selected' if query == @query
|
||||
link_to(h(query.name), url_params.merge(:query_id => query), :class => css)
|
||||
}.join('<br />').html_safe
|
||||
end
|
||||
|
||||
def render_sidebar_queries
|
||||
out = ''.html_safe
|
||||
out << query_links(l(:label_my_queries), sidebar_queries.select(&:is_private?))
|
||||
out << query_links(l(:label_query_plural), sidebar_queries.reject(&:is_private?))
|
||||
queries = sidebar_queries.select {|q| !q.is_public?}
|
||||
out << query_links(l(:label_my_queries), queries) if queries.any?
|
||||
queries = sidebar_queries.select {|q| q.is_public?}
|
||||
out << query_links(l(:label_query_plural), queries) if queries.any?
|
||||
out
|
||||
end
|
||||
|
||||
def email_issue_attributes(issue, user)
|
||||
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
|
||||
# as an array of strings
|
||||
def details_to_strings(details, no_html=false, options={})
|
||||
@@ -261,23 +222,23 @@ module IssuesHelper
|
||||
values_by_field = {}
|
||||
details.each do |detail|
|
||||
if detail.property == 'cf'
|
||||
field = detail.custom_field
|
||||
field_id = detail.prop_key
|
||||
field = CustomField.find_by_id(field_id)
|
||||
if field && field.multiple?
|
||||
values_by_field[field] ||= {:added => [], :deleted => []}
|
||||
values_by_field[field_id] ||= {:added => [], :deleted => []}
|
||||
if detail.old_value
|
||||
values_by_field[field][:deleted] << detail.old_value
|
||||
values_by_field[field_id][:deleted] << detail.old_value
|
||||
end
|
||||
if detail.value
|
||||
values_by_field[field][:added] << detail.value
|
||||
values_by_field[field_id][:added] << detail.value
|
||||
end
|
||||
next
|
||||
end
|
||||
end
|
||||
strings << show_detail(detail, no_html, options)
|
||||
end
|
||||
values_by_field.each do |field, changes|
|
||||
detail = JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s)
|
||||
detail.instance_variable_set "@custom_field", field
|
||||
values_by_field.each do |field_id, changes|
|
||||
detail = JournalDetail.new(:property => 'cf', :prop_key => field_id)
|
||||
if changes[:added].any?
|
||||
detail.value = changes[:added]
|
||||
strings << show_detail(detail, no_html, options)
|
||||
@@ -320,7 +281,7 @@ module IssuesHelper
|
||||
old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank?
|
||||
end
|
||||
when 'cf'
|
||||
custom_field = detail.custom_field
|
||||
custom_field = CustomField.find_by_id(detail.prop_key)
|
||||
if custom_field
|
||||
multiple = custom_field.multiple?
|
||||
label = custom_field.name
|
||||
@@ -329,17 +290,6 @@ module IssuesHelper
|
||||
end
|
||||
when 'attachment'
|
||||
label = l(:label_attachment)
|
||||
when 'relation'
|
||||
if detail.value && !detail.old_value
|
||||
rel_issue = Issue.visible.find_by_id(detail.value)
|
||||
value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.value}" :
|
||||
(no_html ? rel_issue : link_to_issue(rel_issue))
|
||||
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
|
||||
call_hook(:helper_issues_show_detail_after_setting,
|
||||
{:detail => detail, :label => label, :value => value, :old_value => old_value })
|
||||
@@ -351,9 +301,7 @@ module IssuesHelper
|
||||
unless no_html
|
||||
label = content_tag('strong', label)
|
||||
old_value = content_tag("i", h(old_value)) if detail.old_value
|
||||
if detail.old_value && detail.value.blank? && detail.property != 'relation'
|
||||
old_value = content_tag("del", old_value)
|
||||
end
|
||||
old_value = content_tag("del", old_value) if detail.old_value and detail.value.blank?
|
||||
if detail.property == 'attachment' && !value.blank? && atta = Attachment.find_by_id(detail.prop_key)
|
||||
# Link to the attachment if it has not been removed
|
||||
value = link_to_attachment(atta, :download => true, :only_path => options[:only_path])
|
||||
@@ -389,7 +337,7 @@ module IssuesHelper
|
||||
else
|
||||
l(:text_journal_set_to, :label => label, :value => value).html_safe
|
||||
end
|
||||
when 'attachment', 'relation'
|
||||
when 'attachment'
|
||||
l(:text_journal_added, :label => label, :value => value).html_safe
|
||||
end
|
||||
else
|
||||
@@ -399,16 +347,10 @@ module IssuesHelper
|
||||
|
||||
# Find the name of an associated record stored in the field attribute
|
||||
def find_name_by_reflection(field, id)
|
||||
unless id.present?
|
||||
return nil
|
||||
end
|
||||
association = Issue.reflect_on_association(field.to_sym)
|
||||
if association
|
||||
record = association.class_name.constantize.find_by_id(id)
|
||||
if record
|
||||
record.name.force_encoding('UTF-8') if record.name.respond_to?(:force_encoding)
|
||||
return record.name
|
||||
end
|
||||
return record.name if record
|
||||
end
|
||||
end
|
||||
|
||||
@@ -425,4 +367,44 @@ module IssuesHelper
|
||||
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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -18,18 +18,4 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -18,54 +18,4 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -46,7 +46,7 @@ module ProjectsHelper
|
||||
end
|
||||
|
||||
options = ''
|
||||
options << "<option value=''> </option>" if project.allowed_parents.include?(nil)
|
||||
options << "<option value=''></option>" if project.allowed_parents.include?(nil)
|
||||
options << project_tree_options_for_select(project.allowed_parents.compact, :selected => selected)
|
||||
content_tag('select', options.html_safe, :name => 'project[parent_id]', :id => 'project_parent_id')
|
||||
end
|
||||
@@ -69,11 +69,10 @@ module ProjectsHelper
|
||||
grouped[version.project.name] << [version.name, version.id]
|
||||
end
|
||||
|
||||
selected = selected.is_a?(Version) ? selected.id : selected
|
||||
if grouped.keys.size > 1
|
||||
grouped_options_for_select(grouped, selected)
|
||||
grouped_options_for_select(grouped, selected && selected.id)
|
||||
else
|
||||
options_for_select((grouped.values.first || []), selected)
|
||||
options_for_select((grouped.values.first || []), selected && selected.id)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -24,35 +24,32 @@ module QueriesHelper
|
||||
|
||||
def filters_options(query)
|
||||
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]
|
||||
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)
|
||||
tags = ''.html_safe
|
||||
query.available_block_columns.each do |column|
|
||||
@@ -61,19 +58,6 @@ module QueriesHelper
|
||||
tags
|
||||
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)
|
||||
column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
|
||||
:default_order => column.default_order) :
|
||||
@@ -103,16 +87,14 @@ module QueriesHelper
|
||||
format_time(value)
|
||||
when 'Date'
|
||||
format_date(value)
|
||||
when 'Fixnum'
|
||||
if column.name == :id
|
||||
link_to value, issue_path(issue)
|
||||
elsif column.name == :done_ratio
|
||||
when 'Fixnum', 'Float'
|
||||
if column.name == :done_ratio
|
||||
progress_bar(value, :width => '80px')
|
||||
elsif column.name == :spent_hours
|
||||
sprintf "%.2f", value
|
||||
else
|
||||
value.to_s
|
||||
h(value.to_s)
|
||||
end
|
||||
when 'Float'
|
||||
sprintf "%.2f", value
|
||||
when 'User'
|
||||
link_to_user value
|
||||
when 'Project'
|
||||
@@ -124,7 +106,7 @@ module QueriesHelper
|
||||
when 'FalseClass'
|
||||
l(:general_text_No)
|
||||
when 'Issue'
|
||||
value.visible? ? link_to_issue(value) : "##{value.id}"
|
||||
link_to_issue(value, :subject => false)
|
||||
when 'IssueRelation'
|
||||
other = value.other_issue(issue)
|
||||
content_tag('span',
|
||||
@@ -135,72 +117,26 @@ module QueriesHelper
|
||||
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
|
||||
def retrieve_query
|
||||
if !params[:query_id].blank?
|
||||
cond = "project_id IS NULL"
|
||||
cond << " OR project_id = #{@project.id}" if @project
|
||||
@query = IssueQuery.where(cond).find(params[:query_id])
|
||||
@query = Query.find(params[:query_id], :conditions => cond)
|
||||
raise ::Unauthorized unless @query.visible?
|
||||
@query.project = @project
|
||||
session[:query] = {:id => @query.id, :project_id => @query.project_id}
|
||||
sort_clear
|
||||
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
|
||||
@query = IssueQuery.new(:name => "_")
|
||||
@query = Query.new(:name => "_")
|
||||
@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}
|
||||
else
|
||||
# retrieve from session
|
||||
@query = nil
|
||||
@query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id]
|
||||
@query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
|
||||
@query = Query.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.project = @project
|
||||
end
|
||||
end
|
||||
@@ -208,10 +144,10 @@ module QueriesHelper
|
||||
def retrieve_query_from_session
|
||||
if session[:query]
|
||||
if session[:query][:id]
|
||||
@query = IssueQuery.find_by_id(session[:query][:id])
|
||||
@query = Query.find_by_id(session[:query][:id])
|
||||
return unless @query
|
||||
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
|
||||
if session[:query].has_key?(:project_id)
|
||||
@query.project_id = session[:query][:project_id]
|
||||
@@ -221,4 +157,17 @@ module QueriesHelper
|
||||
@query
|
||||
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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -24,7 +24,7 @@ module ReportsHelper
|
||||
data.each { |row|
|
||||
match = 1
|
||||
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?
|
||||
a = a + row["total"].to_i if match == 1
|
||||
} unless data.nil?
|
||||
@@ -35,9 +35,4 @@ module ReportsHelper
|
||||
a = aggregate data, criteria
|
||||
a > 0 ? link_to(h(a), *args) : '-'
|
||||
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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -43,7 +43,7 @@ module RepositoriesHelper
|
||||
end
|
||||
|
||||
def render_changeset_changes
|
||||
changes = @changeset.filechanges.limit(1000).reorder('path').all.collect do |change|
|
||||
changes = @changeset.filechanges.find(:all, :limit => 1000, :order => 'path').collect do |change|
|
||||
case change.action
|
||||
when 'A'
|
||||
# Detects moved/copied files
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -53,7 +53,7 @@ module SettingsHelper
|
||||
check_box_tag(
|
||||
"settings[#{setting}][]",
|
||||
value,
|
||||
setting_values.include?(value),
|
||||
Setting.send(setting).include?(value),
|
||||
:id => nil
|
||||
) + text.to_s,
|
||||
:class => (options[:inline] ? 'inline' : 'block')
|
||||
|
||||
@@ -80,13 +80,12 @@ module SortHelper
|
||||
@criteria.collect {|k,o| k + (o ? '' : ':desc')}.join(',')
|
||||
end
|
||||
|
||||
# Returns an array of SQL fragments used to sort the list
|
||||
def to_sql
|
||||
sql = @criteria.collect do |k,o|
|
||||
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.flatten.compact
|
||||
end.compact.join(', ')
|
||||
sql.blank? ? nil : sql
|
||||
end
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -56,10 +56,10 @@ module TimelogHelper
|
||||
end
|
||||
|
||||
def select_hours(data, criteria, value)
|
||||
if value.to_s.empty?
|
||||
data.select {|row| row[criteria].blank? }
|
||||
if value.to_s.empty?
|
||||
data.select {|row| row[criteria].blank? }
|
||||
else
|
||||
data.select {|row| row[criteria].to_s == value.to_s}
|
||||
data.select {|row| row[criteria].to_s == value.to_s}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -86,6 +86,49 @@ module TimelogHelper
|
||||
value)
|
||||
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)
|
||||
if value.blank?
|
||||
"[#{l(:label_none)}]"
|
||||
@@ -107,14 +150,14 @@ module TimelogHelper
|
||||
# Column headers
|
||||
headers = report.criteria.collect {|criteria| l(report.available_criteria[criteria][:label]) }
|
||||
headers += report.periods
|
||||
headers << l(:label_total_time)
|
||||
headers << l(:label_total)
|
||||
csv << headers.collect {|c| Redmine::CodesetUtil.from_utf8(
|
||||
c.to_s,
|
||||
l(:general_csv_encoding) ) }
|
||||
# Content
|
||||
report_criteria_to_csv(csv, report.available_criteria, report.columns, report.criteria, report.periods, report.hours)
|
||||
# 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)
|
||||
total = 0
|
||||
report.periods.each do |period|
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -35,9 +35,12 @@ module VersionsHelper
|
||||
h = Hash.new {|k,v| k[v] = [0, 0]}
|
||||
begin
|
||||
# Total issue count
|
||||
Issue.where(:fixed_version_id => version.id).group(criteria).count.each {|c,s| h[c][0] = s}
|
||||
Issue.count(:group => criteria,
|
||||
:conditions => ["#{Issue.table_name}.fixed_version_id = ?", version.id]).each {|c,s| h[c][0] = s}
|
||||
# Open issues count
|
||||
Issue.open.where(:fixed_version_id => version.id).group(criteria).count.each {|c,s| h[c][1] = s}
|
||||
Issue.count(:group => criteria,
|
||||
:include => :status,
|
||||
:conditions => ["#{Issue.table_name}.fixed_version_id = ? AND #{IssueStatus.table_name}.is_closed = ?", version.id, false]).each {|c,s| h[c][1] = s}
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
# When grouping by an association, Rails throws this exception if there's no result (bug)
|
||||
end
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -20,31 +20,24 @@
|
||||
module WatchersHelper
|
||||
|
||||
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."
|
||||
watcher_link(object, user)
|
||||
content_tag("span", watcher_link(object, user), :class => watcher_css(object))
|
||||
end
|
||||
|
||||
def watcher_link(objects, user)
|
||||
return '' unless user && user.logged?
|
||||
objects = Array.wrap(objects)
|
||||
def watcher_link(object, user)
|
||||
return '' unless user && user.logged? && object.respond_to?('watched_by?')
|
||||
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
|
||||
|
||||
# Returns the css class used to identify watch links for a given +object+
|
||||
def watcher_css(objects)
|
||||
objects = Array.wrap(objects)
|
||||
id = (objects.size == 1 ? objects.first.id : 'bulk')
|
||||
"#{objects.first.class.to_s.underscore}-#{id}-watcher"
|
||||
def watcher_css(object)
|
||||
"#{object.class.to_s.underscore}-#{object.id}-watcher"
|
||||
end
|
||||
|
||||
# Returns a comma separated list of users watching the given object
|
||||
@@ -63,11 +56,11 @@ module WatchersHelper
|
||||
:user_id => user}
|
||||
s << ' '
|
||||
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
|
||||
content << content_tag('li', s, :class => "user-#{user.id}")
|
||||
content << content_tag('li', s)
|
||||
end
|
||||
content.present? ? content_tag('ul', content, :class => 'watchers') : content
|
||||
content.present? ? content_tag('ul', content) : content
|
||||
end
|
||||
|
||||
def watchers_checkboxes(object, users, checked=nil)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -22,20 +22,11 @@ module WorkflowsHelper
|
||||
field.is_a?(CustomField) ? field.is_required? : %w(project_id tracker_id subject priority_id is_private).include?(field)
|
||||
end
|
||||
|
||||
def field_permission_tag(permissions, status, field, role)
|
||||
def field_permission_tag(permissions, status, field)
|
||||
name = field.is_a?(CustomField) ? field.id.to_s : field
|
||||
options = [["", ""], [l(:label_readonly), "readonly"]]
|
||||
options << [l(:label_required), "required"] unless field_required?(field)
|
||||
html_options = {}
|
||||
selected = permissions[status.id][name]
|
||||
|
||||
hidden = field.is_a?(CustomField) && !field.visible? && !role.custom_fields.to_a.include?(field)
|
||||
if hidden
|
||||
options[0][0] = l(:label_hidden)
|
||||
selected = ''
|
||||
html_options[:disabled] = true
|
||||
end
|
||||
|
||||
select_tag("permissions[#{name}][#{status.id}]", options_for_select(options, selected), html_options)
|
||||
select_tag("permissions[#{name}][#{status.id}]", options_for_select(options, permissions[status.id][name]))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -16,7 +16,6 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
require "digest/md5"
|
||||
require "fileutils"
|
||||
|
||||
class Attachment < ActiveRecord::Base
|
||||
belongs_to :container, :polymorphic => true
|
||||
@@ -93,6 +92,9 @@ class Attachment < ActiveRecord::Base
|
||||
|
||||
def filename=(arg)
|
||||
write_attribute :filename, sanitize_filename(arg.to_s)
|
||||
if new_record? && disk_filename.blank?
|
||||
self.disk_filename = Attachment.disk_filename(filename)
|
||||
end
|
||||
filename
|
||||
end
|
||||
|
||||
@@ -100,13 +102,7 @@ class Attachment < ActiveRecord::Base
|
||||
# and computes its MD5 hash
|
||||
def files_to_final_location
|
||||
if @temp_file && (@temp_file.size > 0)
|
||||
self.disk_directory = target_directory
|
||||
self.disk_filename = Attachment.disk_filename(filename, disk_directory)
|
||||
logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)") if logger
|
||||
path = File.dirname(diskfile)
|
||||
unless File.directory?(path)
|
||||
FileUtils.mkdir_p(path)
|
||||
end
|
||||
logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)")
|
||||
md5 = Digest::MD5.new
|
||||
File.open(diskfile, "wb") do |f|
|
||||
if @temp_file.respond_to?(:read)
|
||||
@@ -138,7 +134,7 @@ class Attachment < ActiveRecord::Base
|
||||
|
||||
# Returns file's location on disk
|
||||
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
|
||||
|
||||
def title
|
||||
@@ -158,19 +154,11 @@ class Attachment < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def visible?(user=User.current)
|
||||
if container_id
|
||||
container && container.attachments_visible?(user)
|
||||
else
|
||||
author == user
|
||||
end
|
||||
container && container.attachments_visible?(user)
|
||||
end
|
||||
|
||||
def deletable?(user=User.current)
|
||||
if container_id
|
||||
container && container.attachments_deletable?(user)
|
||||
else
|
||||
author == user
|
||||
end
|
||||
container && container.attachments_deletable?(user)
|
||||
end
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
# Physically deletes the file from the file system
|
||||
@@ -294,21 +262,14 @@ class Attachment < ActiveRecord::Base
|
||||
|
||||
def sanitize_filename(value)
|
||||
# get only the filename, not the whole path
|
||||
just_filename = value.gsub(/\A.*(\\|\/)/m, '')
|
||||
just_filename = value.gsub(/^.*(\\|\/)/, '')
|
||||
|
||||
# Finally, replace invalid characters with underscore
|
||||
@filename = just_filename.gsub(/[\/\?\%\*\:\|\"\'<>\n\r]+/, '_')
|
||||
@filename = just_filename.gsub(/[\/\?\%\*\:\|\"\'<>]+/, '_')
|
||||
end
|
||||
|
||||
# Returns the subdirectory in which the attachment will be saved
|
||||
def target_directory
|
||||
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)
|
||||
# Returns an ASCII or hashed filename
|
||||
def self.disk_filename(filename)
|
||||
timestamp = DateTime.now.strftime("%y%m%d%H%M%S")
|
||||
ascii = ''
|
||||
if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
|
||||
@@ -318,7 +279,7 @@ class Attachment < ActiveRecord::Base
|
||||
# keep the extension if any
|
||||
ascii << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
|
||||
end
|
||||
while File.exist?(File.join(storage_path, directory.to_s, "#{timestamp}_#{ascii}"))
|
||||
while File.exist?(File.join(@@storage_path, "#{timestamp}_#{ascii}"))
|
||||
timestamp.succ!
|
||||
end
|
||||
"#{timestamp}_#{ascii}"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# 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)
|
||||
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?
|
||||
self.class.allow_password_changes?
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# 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
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
require 'iconv'
|
||||
require 'net/ldap'
|
||||
require 'net/ldap/dn'
|
||||
require 'timeout'
|
||||
@@ -63,32 +64,6 @@ class AuthSourceLdap < AuthSource
|
||||
"LDAP"
|
||||
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
|
||||
|
||||
def with_timeout(&block)
|
||||
@@ -109,14 +84,6 @@ class AuthSourceLdap < AuthSource
|
||||
nil
|
||||
end
|
||||
|
||||
def base_filter
|
||||
filter = Net::LDAP::Filter.eq("objectClass", "*")
|
||||
if f = ldap_filter
|
||||
filter = filter & f
|
||||
end
|
||||
filter
|
||||
end
|
||||
|
||||
def validate_filter
|
||||
if filter.present? && ldap_filter.nil?
|
||||
errors.add(:filter, :invalid)
|
||||
@@ -173,8 +140,14 @@ class AuthSourceLdap < AuthSource
|
||||
else
|
||||
ldap_con = initialize_ldap_con(self.account, self.account_password)
|
||||
end
|
||||
login_filter = Net::LDAP::Filter.eq( self.attr_login, login )
|
||||
object_filter = Net::LDAP::Filter.eq( "objectClass", "*" )
|
||||
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,
|
||||
:filter => search_filter,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# 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
|
||||
validate :validate_board
|
||||
|
||||
scope :visible, lambda {|*args|
|
||||
includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_messages, *args))
|
||||
}
|
||||
scope :visible, lambda {|*args| { :include => :project,
|
||||
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_messages, *args) } }
|
||||
|
||||
safe_attributes 'name', 'description', 'parent_id', 'move_to'
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -15,6 +15,8 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
require 'iconv'
|
||||
|
||||
class Changeset < ActiveRecord::Base
|
||||
belongs_to :repository
|
||||
belongs_to :user
|
||||
@@ -47,9 +49,9 @@ class Changeset < ActiveRecord::Base
|
||||
validates_uniqueness_of :revision, :scope => :repository_id
|
||||
validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
|
||||
|
||||
scope :visible, lambda {|*args|
|
||||
includes(:repository => :project).where(Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args))
|
||||
}
|
||||
scope :visible,
|
||||
lambda {|*args| { :include => {:repository => :project},
|
||||
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args) } }
|
||||
|
||||
after_create :scan_for_issues
|
||||
before_create :before_create_cs
|
||||
@@ -118,25 +120,22 @@ class Changeset < ActiveRecord::Base
|
||||
ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
|
||||
ref_keywords_any = ref_keywords.delete('*')
|
||||
# keywords used to fix issues
|
||||
fix_keywords = Setting.commit_update_keywords_array.map {|r| r['keywords']}.flatten.compact
|
||||
fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
|
||||
|
||||
kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
|
||||
|
||||
referenced_issues = []
|
||||
|
||||
comments.scan(/([\s\(\[,-]|^)((#{kw_regexp})[\s:]+)?(#\d+(\s+@#{TIMELOG_RE})?([\s,;&]+#\d+(\s+@#{TIMELOG_RE})?)*)(?=[[:punct:]]|\s|<|$)/i) do |match|
|
||||
action, refs = match[2].to_s.downcase, match[3]
|
||||
action, refs = match[2], match[3]
|
||||
next unless action.present? || ref_keywords_any
|
||||
|
||||
refs.scan(/#(\d+)(\s+@#{TIMELOG_RE})?/).each do |m|
|
||||
issue, hours = find_referenced_issue_by_id(m[0].to_i), m[2]
|
||||
if issue
|
||||
referenced_issues << issue
|
||||
# Don't update issues or log time when importing old commits
|
||||
unless repository.created_on && committed_on && committed_on < repository.created_on
|
||||
fix_issue(issue, action) if fix_keywords.include?(action)
|
||||
log_time(issue, hours) if hours && Setting.commit_logtime_enabled?
|
||||
end
|
||||
fix_issue(issue) if fix_keywords.include?(action.to_s.downcase)
|
||||
log_time(issue, hours) if hours && Setting.commit_logtime_enabled?
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -213,26 +212,25 @@ class Changeset < ActiveRecord::Base
|
||||
|
||||
private
|
||||
|
||||
# Updates the +issue+ according to +action+
|
||||
def fix_issue(issue, action)
|
||||
def fix_issue(issue)
|
||||
status = IssueStatus.find_by_id(Setting.commit_fix_status_id.to_i)
|
||||
if status.nil?
|
||||
logger.warn("No status matches commit_fix_status_id setting (#{Setting.commit_fix_status_id})") if logger
|
||||
return issue
|
||||
end
|
||||
|
||||
# the issue may have been updated by the closure of another one (eg. duplicate)
|
||||
issue.reload
|
||||
# don't change the status is the issue is closed
|
||||
return if issue.status && issue.status.is_closed?
|
||||
|
||||
journal = issue.init_journal(user || User.anonymous,
|
||||
ll(Setting.default_language,
|
||||
:text_status_changed_by_changeset,
|
||||
text_tag(issue.project)))
|
||||
rule = Setting.commit_update_keywords_array.detect do |rule|
|
||||
rule['keywords'].include?(action) &&
|
||||
(rule['if_tracker_id'].blank? || rule['if_tracker_id'] == issue.tracker_id.to_s)
|
||||
end
|
||||
if rule
|
||||
issue.assign_attributes rule.slice(*Issue.attribute_names)
|
||||
journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag(issue.project)))
|
||||
issue.status = status
|
||||
unless Setting.commit_fix_done_ratio.blank?
|
||||
issue.done_ratio = Setting.commit_fix_done_ratio.to_i
|
||||
end
|
||||
Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
|
||||
{ :changeset => self, :issue => issue, :action => action })
|
||||
{ :changeset => self, :issue => issue })
|
||||
unless issue.save
|
||||
logger.warn("Issue ##{issue.id} could not be saved by changeset #{id}: #{issue.errors.full_messages}") if logger
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -22,16 +22,5 @@ class Comment < ActiveRecord::Base
|
||||
|
||||
validates_presence_of :commented, :author, :comments
|
||||
|
||||
after_create :send_notification
|
||||
|
||||
safe_attributes 'comments'
|
||||
|
||||
private
|
||||
|
||||
def send_notification
|
||||
mailer_method = "#{commented.class.name.underscore}_comment_added"
|
||||
if Setting.notified_events.include?(mailer_method)
|
||||
Mailer.send(mailer_method, self).deliver
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# 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
|
||||
# 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
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
module ActivitiesHelper
|
||||
def sort_activity_events(events)
|
||||
events_by_group = events.group_by(&:event_group)
|
||||
sorted_events = []
|
||||
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
|
||||
class CommentObserver < ActiveRecord::Observer
|
||||
def after_create(comment)
|
||||
if comment.commented.is_a?(News) && Setting.notified_events.include?('news_comment_added')
|
||||
Mailer.news_comment_added(comment).deliver
|
||||
end
|
||||
sorted_events
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -19,7 +19,6 @@ class CustomField < ActiveRecord::Base
|
||||
include Redmine::SubclassFactory
|
||||
|
||||
has_many :custom_values, :dependent => :delete_all
|
||||
has_and_belongs_to_many :roles, :join_table => "#{table_name_prefix}custom_fields_roles#{table_name_suffix}", :foreign_key => "custom_field_id"
|
||||
acts_as_list :scope => 'type = \'#{self.class}\''
|
||||
serialize :possible_values
|
||||
|
||||
@@ -27,35 +26,32 @@ class CustomField < ActiveRecord::Base
|
||||
validates_uniqueness_of :name, :scope => :type
|
||||
validates_length_of :name, :maximum => 30
|
||||
validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats
|
||||
|
||||
validate :validate_custom_field
|
||||
|
||||
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") }
|
||||
scope :visible, lambda {|*args|
|
||||
user = args.shift || User.current
|
||||
if user.admin?
|
||||
# nop
|
||||
elsif user.memberships.any?
|
||||
where("#{table_name}.visible = ? OR #{table_name}.id IN (SELECT DISTINCT cfr.custom_field_id FROM #{Member.table_name} m" +
|
||||
" INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
|
||||
" INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
|
||||
" WHERE m.user_id = ?)",
|
||||
true, user.id)
|
||||
else
|
||||
where(:visible => true)
|
||||
end
|
||||
}
|
||||
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 visible_by?(project, user=User.current)
|
||||
visible? || user.admin?
|
||||
end
|
||||
CUSTOM_FIELDS_NAMES = CUSTOM_FIELDS_TABS.collect{|v| v[:name]}
|
||||
|
||||
def field_format=(arg)
|
||||
# cannot change format of a saved custom field
|
||||
@@ -123,10 +119,8 @@ class CustomField < ActiveRecord::Base
|
||||
values.each do |value|
|
||||
value.force_encoding('UTF-8') if value.respond_to?(:force_encoding)
|
||||
end
|
||||
values
|
||||
else
|
||||
[]
|
||||
end
|
||||
values || []
|
||||
end
|
||||
end
|
||||
|
||||
@@ -175,7 +169,7 @@ class CustomField < ActiveRecord::Base
|
||||
keyword
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Returns a ORDER BY clause that can used to sort customized
|
||||
# objects by their value of the custom field.
|
||||
# Returns nil if the custom field can not be used for sorting.
|
||||
@@ -184,12 +178,18 @@ class CustomField < ActiveRecord::Base
|
||||
case field_format
|
||||
when 'string', 'text', 'list', 'date', 'bool'
|
||||
# COALESCE is here to make sure that blank and NULL values are sorted equally
|
||||
"COALESCE(#{join_alias}.value, '')"
|
||||
"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'
|
||||
# Make the database cast values into numeric
|
||||
# Postgresql will raise an error if a value can not be casted!
|
||||
# CustomValue validations should ensure that it doesn't occur
|
||||
"CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,3))"
|
||||
"(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'
|
||||
value_class.fields_for_order_statement(value_join_alias)
|
||||
else
|
||||
@@ -199,13 +199,16 @@ class CustomField < ActiveRecord::Base
|
||||
|
||||
# Returns a GROUP BY clause that can used to group by custom value
|
||||
# Returns nil if the custom field can not be used for grouping.
|
||||
def group_statement
|
||||
def group_statement
|
||||
return nil if multiple?
|
||||
case field_format
|
||||
when 'list', 'date', 'bool', 'int'
|
||||
order_statement
|
||||
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
|
||||
nil
|
||||
end
|
||||
@@ -218,35 +221,13 @@ class CustomField < ActiveRecord::Base
|
||||
" ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
|
||||
" AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
|
||||
" AND #{join_alias}.custom_field_id = #{id}" +
|
||||
" AND (#{visibility_by_project_condition})" +
|
||||
" AND #{join_alias}.value <> ''" +
|
||||
" AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
|
||||
" WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
|
||||
" AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
|
||||
" AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" +
|
||||
" LEFT OUTER JOIN #{value_class.table_name} #{value_join_alias}" +
|
||||
" ON CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,0)) = #{value_join_alias}.id"
|
||||
when 'int', 'float'
|
||||
"LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
|
||||
" ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
|
||||
" AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
|
||||
" AND #{join_alias}.custom_field_id = #{id}" +
|
||||
" AND (#{visibility_by_project_condition})" +
|
||||
" AND #{join_alias}.value <> ''" +
|
||||
" AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
|
||||
" WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
|
||||
" AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
|
||||
" AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)"
|
||||
when 'string', 'text', 'list', 'date', 'bool'
|
||||
"LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
|
||||
" ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
|
||||
" AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
|
||||
" AND #{join_alias}.custom_field_id = #{id}" +
|
||||
" AND (#{visibility_by_project_condition})" +
|
||||
" AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
|
||||
" WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
|
||||
" AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
|
||||
" AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)"
|
||||
" ON CAST(#{join_alias}.value as decimal(60,0)) = #{value_join_alias}.id"
|
||||
else
|
||||
nil
|
||||
end
|
||||
@@ -260,33 +241,6 @@ class CustomField < ActiveRecord::Base
|
||||
join_alias + "_" + field_format
|
||||
end
|
||||
|
||||
def visibility_by_project_condition(project_key=nil, user=User.current)
|
||||
if visible? || user.admin?
|
||||
"1=1"
|
||||
elsif user.anonymous?
|
||||
"1=0"
|
||||
else
|
||||
project_key ||= "#{self.class.customized_class.table_name}.project_id"
|
||||
"#{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)
|
||||
position <=> field.position
|
||||
end
|
||||
@@ -303,12 +257,12 @@ class CustomField < ActiveRecord::Base
|
||||
|
||||
def self.customized_class
|
||||
self.name =~ /^(.+)CustomField$/
|
||||
$1.constantize rescue nil
|
||||
begin; $1.constantize; rescue nil; end
|
||||
end
|
||||
|
||||
# to move in project_custom_field
|
||||
def self.for_all
|
||||
where(:is_for_all => true).order('position').all
|
||||
find(:all, :conditions => ["is_for_all=?", true], :order => 'position')
|
||||
end
|
||||
|
||||
def type_name
|
||||
@@ -369,20 +323,4 @@ class CustomField < ActiveRecord::Base
|
||||
end
|
||||
errs
|
||||
end
|
||||
|
||||
# Removes multiple values for the custom field after setting the multiple attribute to false
|
||||
# We kepp the value with the highest id for each customized object
|
||||
def handle_multiplicity_change
|
||||
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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user