##// END OF EJS Templates
Moves @layout 'base'@ to ApplicationController....
Jean-Philippe Lang -
r1726:2fdf4426cd8f
parent child
Show More
@@ -1,191 +1,190
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class AccountController < ApplicationController
18 class AccountController < ApplicationController
19 layout 'base'
20 helper :custom_fields
19 helper :custom_fields
21 include CustomFieldsHelper
20 include CustomFieldsHelper
22
21
23 # prevents login action to be filtered by check_if_login_required application scope filter
22 # prevents login action to be filtered by check_if_login_required application scope filter
24 skip_before_filter :check_if_login_required, :only => [:login, :lost_password, :register, :activate]
23 skip_before_filter :check_if_login_required, :only => [:login, :lost_password, :register, :activate]
25
24
26 # Show user's account
25 # Show user's account
27 def show
26 def show
28 @user = User.find_active(params[:id])
27 @user = User.find_active(params[:id])
29 @custom_values = @user.custom_values.find(:all, :include => :custom_field)
28 @custom_values = @user.custom_values.find(:all, :include => :custom_field)
30
29
31 # show only public projects and private projects that the logged in user is also a member of
30 # show only public projects and private projects that the logged in user is also a member of
32 @memberships = @user.memberships.select do |membership|
31 @memberships = @user.memberships.select do |membership|
33 membership.project.is_public? || (User.current.member_of?(membership.project))
32 membership.project.is_public? || (User.current.member_of?(membership.project))
34 end
33 end
35 rescue ActiveRecord::RecordNotFound
34 rescue ActiveRecord::RecordNotFound
36 render_404
35 render_404
37 end
36 end
38
37
39 # Login request and validation
38 # Login request and validation
40 def login
39 def login
41 if request.get?
40 if request.get?
42 # Logout user
41 # Logout user
43 self.logged_user = nil
42 self.logged_user = nil
44 else
43 else
45 # Authenticate user
44 # Authenticate user
46 user = User.try_to_login(params[:username], params[:password])
45 user = User.try_to_login(params[:username], params[:password])
47 if user.nil?
46 if user.nil?
48 # Invalid credentials
47 # Invalid credentials
49 flash.now[:error] = l(:notice_account_invalid_creditentials)
48 flash.now[:error] = l(:notice_account_invalid_creditentials)
50 elsif user.new_record?
49 elsif user.new_record?
51 # Onthefly creation failed, display the registration form to fill/fix attributes
50 # Onthefly creation failed, display the registration form to fill/fix attributes
52 @user = user
51 @user = user
53 session[:auth_source_registration] = {:login => user.login, :auth_source_id => user.auth_source_id }
52 session[:auth_source_registration] = {:login => user.login, :auth_source_id => user.auth_source_id }
54 render :action => 'register'
53 render :action => 'register'
55 else
54 else
56 # Valid user
55 # Valid user
57 self.logged_user = user
56 self.logged_user = user
58 # generate a key and set cookie if autologin
57 # generate a key and set cookie if autologin
59 if params[:autologin] && Setting.autologin?
58 if params[:autologin] && Setting.autologin?
60 token = Token.create(:user => user, :action => 'autologin')
59 token = Token.create(:user => user, :action => 'autologin')
61 cookies[:autologin] = { :value => token.value, :expires => 1.year.from_now }
60 cookies[:autologin] = { :value => token.value, :expires => 1.year.from_now }
62 end
61 end
63 redirect_back_or_default :controller => 'my', :action => 'page'
62 redirect_back_or_default :controller => 'my', :action => 'page'
64 end
63 end
65 end
64 end
66 end
65 end
67
66
68 # Log out current user and redirect to welcome page
67 # Log out current user and redirect to welcome page
69 def logout
68 def logout
70 cookies.delete :autologin
69 cookies.delete :autologin
71 Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin']) if User.current.logged?
70 Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin']) if User.current.logged?
72 self.logged_user = nil
71 self.logged_user = nil
73 redirect_to home_url
72 redirect_to home_url
74 end
73 end
75
74
76 # Enable user to choose a new password
75 # Enable user to choose a new password
77 def lost_password
76 def lost_password
78 redirect_to(home_url) && return unless Setting.lost_password?
77 redirect_to(home_url) && return unless Setting.lost_password?
79 if params[:token]
78 if params[:token]
80 @token = Token.find_by_action_and_value("recovery", params[:token])
79 @token = Token.find_by_action_and_value("recovery", params[:token])
81 redirect_to(home_url) && return unless @token and !@token.expired?
80 redirect_to(home_url) && return unless @token and !@token.expired?
82 @user = @token.user
81 @user = @token.user
83 if request.post?
82 if request.post?
84 @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
83 @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
85 if @user.save
84 if @user.save
86 @token.destroy
85 @token.destroy
87 flash[:notice] = l(:notice_account_password_updated)
86 flash[:notice] = l(:notice_account_password_updated)
88 redirect_to :action => 'login'
87 redirect_to :action => 'login'
89 return
88 return
90 end
89 end
91 end
90 end
92 render :template => "account/password_recovery"
91 render :template => "account/password_recovery"
93 return
92 return
94 else
93 else
95 if request.post?
94 if request.post?
96 user = User.find_by_mail(params[:mail])
95 user = User.find_by_mail(params[:mail])
97 # user not found in db
96 # user not found in db
98 flash.now[:error] = l(:notice_account_unknown_email) and return unless user
97 flash.now[:error] = l(:notice_account_unknown_email) and return unless user
99 # user uses an external authentification
98 # user uses an external authentification
100 flash.now[:error] = l(:notice_can_t_change_password) and return if user.auth_source_id
99 flash.now[:error] = l(:notice_can_t_change_password) and return if user.auth_source_id
101 # create a new token for password recovery
100 # create a new token for password recovery
102 token = Token.new(:user => user, :action => "recovery")
101 token = Token.new(:user => user, :action => "recovery")
103 if token.save
102 if token.save
104 Mailer.deliver_lost_password(token)
103 Mailer.deliver_lost_password(token)
105 flash[:notice] = l(:notice_account_lost_email_sent)
104 flash[:notice] = l(:notice_account_lost_email_sent)
106 redirect_to :action => 'login'
105 redirect_to :action => 'login'
107 return
106 return
108 end
107 end
109 end
108 end
110 end
109 end
111 end
110 end
112
111
113 # User self-registration
112 # User self-registration
114 def register
113 def register
115 redirect_to(home_url) && return unless Setting.self_registration? || session[:auth_source_registration]
114 redirect_to(home_url) && return unless Setting.self_registration? || session[:auth_source_registration]
116 if request.get?
115 if request.get?
117 session[:auth_source_registration] = nil
116 session[:auth_source_registration] = nil
118 @user = User.new(:language => Setting.default_language)
117 @user = User.new(:language => Setting.default_language)
119 else
118 else
120 @user = User.new(params[:user])
119 @user = User.new(params[:user])
121 @user.admin = false
120 @user.admin = false
122 @user.status = User::STATUS_REGISTERED
121 @user.status = User::STATUS_REGISTERED
123 if session[:auth_source_registration]
122 if session[:auth_source_registration]
124 @user.status = User::STATUS_ACTIVE
123 @user.status = User::STATUS_ACTIVE
125 @user.login = session[:auth_source_registration][:login]
124 @user.login = session[:auth_source_registration][:login]
126 @user.auth_source_id = session[:auth_source_registration][:auth_source_id]
125 @user.auth_source_id = session[:auth_source_registration][:auth_source_id]
127 if @user.save
126 if @user.save
128 session[:auth_source_registration] = nil
127 session[:auth_source_registration] = nil
129 self.logged_user = @user
128 self.logged_user = @user
130 flash[:notice] = l(:notice_account_activated)
129 flash[:notice] = l(:notice_account_activated)
131 redirect_to :controller => 'my', :action => 'account'
130 redirect_to :controller => 'my', :action => 'account'
132 end
131 end
133 else
132 else
134 @user.login = params[:user][:login]
133 @user.login = params[:user][:login]
135 @user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
134 @user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
136 case Setting.self_registration
135 case Setting.self_registration
137 when '1'
136 when '1'
138 # Email activation
137 # Email activation
139 token = Token.new(:user => @user, :action => "register")
138 token = Token.new(:user => @user, :action => "register")
140 if @user.save and token.save
139 if @user.save and token.save
141 Mailer.deliver_register(token)
140 Mailer.deliver_register(token)
142 flash[:notice] = l(:notice_account_register_done)
141 flash[:notice] = l(:notice_account_register_done)
143 redirect_to :action => 'login'
142 redirect_to :action => 'login'
144 end
143 end
145 when '3'
144 when '3'
146 # Automatic activation
145 # Automatic activation
147 @user.status = User::STATUS_ACTIVE
146 @user.status = User::STATUS_ACTIVE
148 if @user.save
147 if @user.save
149 self.logged_user = @user
148 self.logged_user = @user
150 flash[:notice] = l(:notice_account_activated)
149 flash[:notice] = l(:notice_account_activated)
151 redirect_to :controller => 'my', :action => 'account'
150 redirect_to :controller => 'my', :action => 'account'
152 end
151 end
153 else
152 else
154 # Manual activation by the administrator
153 # Manual activation by the administrator
155 if @user.save
154 if @user.save
156 # Sends an email to the administrators
155 # Sends an email to the administrators
157 Mailer.deliver_account_activation_request(@user)
156 Mailer.deliver_account_activation_request(@user)
158 flash[:notice] = l(:notice_account_pending)
157 flash[:notice] = l(:notice_account_pending)
159 redirect_to :action => 'login'
158 redirect_to :action => 'login'
160 end
159 end
161 end
160 end
162 end
161 end
163 end
162 end
164 end
163 end
165
164
166 # Token based account activation
165 # Token based account activation
167 def activate
166 def activate
168 redirect_to(home_url) && return unless Setting.self_registration? && params[:token]
167 redirect_to(home_url) && return unless Setting.self_registration? && params[:token]
169 token = Token.find_by_action_and_value('register', params[:token])
168 token = Token.find_by_action_and_value('register', params[:token])
170 redirect_to(home_url) && return unless token and !token.expired?
169 redirect_to(home_url) && return unless token and !token.expired?
171 user = token.user
170 user = token.user
172 redirect_to(home_url) && return unless user.status == User::STATUS_REGISTERED
171 redirect_to(home_url) && return unless user.status == User::STATUS_REGISTERED
173 user.status = User::STATUS_ACTIVE
172 user.status = User::STATUS_ACTIVE
174 if user.save
173 if user.save
175 token.destroy
174 token.destroy
176 flash[:notice] = l(:notice_account_activated)
175 flash[:notice] = l(:notice_account_activated)
177 end
176 end
178 redirect_to :action => 'login'
177 redirect_to :action => 'login'
179 end
178 end
180
179
181 private
180 private
182 def logged_user=(user)
181 def logged_user=(user)
183 if user && user.is_a?(User)
182 if user && user.is_a?(User)
184 User.current = user
183 User.current = user
185 session[:user_id] = user.id
184 session[:user_id] = user.id
186 else
185 else
187 User.current = User.anonymous
186 User.current = User.anonymous
188 session[:user_id] = nil
187 session[:user_id] = nil
189 end
188 end
190 end
189 end
191 end
190 end
@@ -1,86 +1,85
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class AdminController < ApplicationController
18 class AdminController < ApplicationController
19 layout 'base'
20 before_filter :require_admin
19 before_filter :require_admin
21
20
22 helper :sort
21 helper :sort
23 include SortHelper
22 include SortHelper
24
23
25 def index
24 def index
26 @no_configuration_data = Redmine::DefaultData::Loader::no_data?
25 @no_configuration_data = Redmine::DefaultData::Loader::no_data?
27 end
26 end
28
27
29 def projects
28 def projects
30 sort_init 'name', 'asc'
29 sort_init 'name', 'asc'
31 sort_update
30 sort_update
32
31
33 @status = params[:status] ? params[:status].to_i : 0
32 @status = params[:status] ? params[:status].to_i : 0
34 conditions = nil
33 conditions = nil
35 conditions = ["status=?", @status] unless @status == 0
34 conditions = ["status=?", @status] unless @status == 0
36
35
37 @project_count = Project.count(:conditions => conditions)
36 @project_count = Project.count(:conditions => conditions)
38 @project_pages = Paginator.new self, @project_count,
37 @project_pages = Paginator.new self, @project_count,
39 per_page_option,
38 per_page_option,
40 params['page']
39 params['page']
41 @projects = Project.find :all, :order => sort_clause,
40 @projects = Project.find :all, :order => sort_clause,
42 :conditions => conditions,
41 :conditions => conditions,
43 :limit => @project_pages.items_per_page,
42 :limit => @project_pages.items_per_page,
44 :offset => @project_pages.current.offset
43 :offset => @project_pages.current.offset
45
44
46 render :action => "projects", :layout => false if request.xhr?
45 render :action => "projects", :layout => false if request.xhr?
47 end
46 end
48
47
49 # Loads the default configuration
48 # Loads the default configuration
50 # (roles, trackers, statuses, workflow, enumerations)
49 # (roles, trackers, statuses, workflow, enumerations)
51 def default_configuration
50 def default_configuration
52 if request.post?
51 if request.post?
53 begin
52 begin
54 Redmine::DefaultData::Loader::load(params[:lang])
53 Redmine::DefaultData::Loader::load(params[:lang])
55 flash[:notice] = l(:notice_default_data_loaded)
54 flash[:notice] = l(:notice_default_data_loaded)
56 rescue Exception => e
55 rescue Exception => e
57 flash[:error] = l(:error_can_t_load_default_data, e.message)
56 flash[:error] = l(:error_can_t_load_default_data, e.message)
58 end
57 end
59 end
58 end
60 redirect_to :action => 'index'
59 redirect_to :action => 'index'
61 end
60 end
62
61
63 def test_email
62 def test_email
64 raise_delivery_errors = ActionMailer::Base.raise_delivery_errors
63 raise_delivery_errors = ActionMailer::Base.raise_delivery_errors
65 # Force ActionMailer to raise delivery errors so we can catch it
64 # Force ActionMailer to raise delivery errors so we can catch it
66 ActionMailer::Base.raise_delivery_errors = true
65 ActionMailer::Base.raise_delivery_errors = true
67 begin
66 begin
68 @test = Mailer.deliver_test(User.current)
67 @test = Mailer.deliver_test(User.current)
69 flash[:notice] = l(:notice_email_sent, User.current.mail)
68 flash[:notice] = l(:notice_email_sent, User.current.mail)
70 rescue Exception => e
69 rescue Exception => e
71 flash[:error] = l(:notice_email_error, e.message)
70 flash[:error] = l(:notice_email_error, e.message)
72 end
71 end
73 ActionMailer::Base.raise_delivery_errors = raise_delivery_errors
72 ActionMailer::Base.raise_delivery_errors = raise_delivery_errors
74 redirect_to :controller => 'settings', :action => 'edit', :tab => 'notifications'
73 redirect_to :controller => 'settings', :action => 'edit', :tab => 'notifications'
75 end
74 end
76
75
77 def info
76 def info
78 @db_adapter_name = ActiveRecord::Base.connection.adapter_name
77 @db_adapter_name = ActiveRecord::Base.connection.adapter_name
79 @flags = {
78 @flags = {
80 :default_admin_changed => User.find(:first, :conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?,
79 :default_admin_changed => User.find(:first, :conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?,
81 :file_repository_writable => File.writable?(Attachment.storage_path),
80 :file_repository_writable => File.writable?(Attachment.storage_path),
82 :rmagick_available => Object.const_defined?(:Magick)
81 :rmagick_available => Object.const_defined?(:Magick)
83 }
82 }
84 @plugins = Redmine::Plugin.registered_plugins
83 @plugins = Redmine::Plugin.registered_plugins
85 end
84 end
86 end
85 end
@@ -1,219 +1,221
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'uri'
18 require 'uri'
19
19
20 class ApplicationController < ActionController::Base
20 class ApplicationController < ActionController::Base
21 layout 'base'
22
21 before_filter :user_setup, :check_if_login_required, :set_localization
23 before_filter :user_setup, :check_if_login_required, :set_localization
22 filter_parameter_logging :password
24 filter_parameter_logging :password
23
25
24 include Redmine::MenuManager::MenuController
26 include Redmine::MenuManager::MenuController
25 helper Redmine::MenuManager::MenuHelper
27 helper Redmine::MenuManager::MenuHelper
26
28
27 REDMINE_SUPPORTED_SCM.each do |scm|
29 REDMINE_SUPPORTED_SCM.each do |scm|
28 require_dependency "repository/#{scm.underscore}"
30 require_dependency "repository/#{scm.underscore}"
29 end
31 end
30
32
31 def current_role
33 def current_role
32 @current_role ||= User.current.role_for_project(@project)
34 @current_role ||= User.current.role_for_project(@project)
33 end
35 end
34
36
35 def user_setup
37 def user_setup
36 # Check the settings cache for each request
38 # Check the settings cache for each request
37 Setting.check_cache
39 Setting.check_cache
38 # Find the current user
40 # Find the current user
39 User.current = find_current_user
41 User.current = find_current_user
40 end
42 end
41
43
42 # Returns the current user or nil if no user is logged in
44 # Returns the current user or nil if no user is logged in
43 def find_current_user
45 def find_current_user
44 if session[:user_id]
46 if session[:user_id]
45 # existing session
47 # existing session
46 (User.find_active(session[:user_id]) rescue nil)
48 (User.find_active(session[:user_id]) rescue nil)
47 elsif cookies[:autologin] && Setting.autologin?
49 elsif cookies[:autologin] && Setting.autologin?
48 # auto-login feature
50 # auto-login feature
49 User.find_by_autologin_key(cookies[:autologin])
51 User.find_by_autologin_key(cookies[:autologin])
50 elsif params[:key] && accept_key_auth_actions.include?(params[:action])
52 elsif params[:key] && accept_key_auth_actions.include?(params[:action])
51 # RSS key authentication
53 # RSS key authentication
52 User.find_by_rss_key(params[:key])
54 User.find_by_rss_key(params[:key])
53 end
55 end
54 end
56 end
55
57
56 # check if login is globally required to access the application
58 # check if login is globally required to access the application
57 def check_if_login_required
59 def check_if_login_required
58 # no check needed if user is already logged in
60 # no check needed if user is already logged in
59 return true if User.current.logged?
61 return true if User.current.logged?
60 require_login if Setting.login_required?
62 require_login if Setting.login_required?
61 end
63 end
62
64
63 def set_localization
65 def set_localization
64 User.current.language = nil unless User.current.logged?
66 User.current.language = nil unless User.current.logged?
65 lang = begin
67 lang = begin
66 if !User.current.language.blank? && GLoc.valid_language?(User.current.language)
68 if !User.current.language.blank? && GLoc.valid_language?(User.current.language)
67 User.current.language
69 User.current.language
68 elsif request.env['HTTP_ACCEPT_LANGUAGE']
70 elsif request.env['HTTP_ACCEPT_LANGUAGE']
69 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.downcase
71 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.downcase
70 if !accept_lang.blank? && (GLoc.valid_language?(accept_lang) || GLoc.valid_language?(accept_lang = accept_lang.split('-').first))
72 if !accept_lang.blank? && (GLoc.valid_language?(accept_lang) || GLoc.valid_language?(accept_lang = accept_lang.split('-').first))
71 User.current.language = accept_lang
73 User.current.language = accept_lang
72 end
74 end
73 end
75 end
74 rescue
76 rescue
75 nil
77 nil
76 end || Setting.default_language
78 end || Setting.default_language
77 set_language_if_valid(lang)
79 set_language_if_valid(lang)
78 end
80 end
79
81
80 def require_login
82 def require_login
81 if !User.current.logged?
83 if !User.current.logged?
82 redirect_to :controller => "account", :action => "login", :back_url => request.request_uri
84 redirect_to :controller => "account", :action => "login", :back_url => request.request_uri
83 return false
85 return false
84 end
86 end
85 true
87 true
86 end
88 end
87
89
88 def require_admin
90 def require_admin
89 return unless require_login
91 return unless require_login
90 if !User.current.admin?
92 if !User.current.admin?
91 render_403
93 render_403
92 return false
94 return false
93 end
95 end
94 true
96 true
95 end
97 end
96
98
97 # Authorize the user for the requested action
99 # Authorize the user for the requested action
98 def authorize(ctrl = params[:controller], action = params[:action])
100 def authorize(ctrl = params[:controller], action = params[:action])
99 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project)
101 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project)
100 allowed ? true : (User.current.logged? ? render_403 : require_login)
102 allowed ? true : (User.current.logged? ? render_403 : require_login)
101 end
103 end
102
104
103 # make sure that the user is a member of the project (or admin) if project is private
105 # make sure that the user is a member of the project (or admin) if project is private
104 # used as a before_filter for actions that do not require any particular permission on the project
106 # used as a before_filter for actions that do not require any particular permission on the project
105 def check_project_privacy
107 def check_project_privacy
106 if @project && @project.active?
108 if @project && @project.active?
107 if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
109 if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
108 true
110 true
109 else
111 else
110 User.current.logged? ? render_403 : require_login
112 User.current.logged? ? render_403 : require_login
111 end
113 end
112 else
114 else
113 @project = nil
115 @project = nil
114 render_404
116 render_404
115 false
117 false
116 end
118 end
117 end
119 end
118
120
119 def redirect_back_or_default(default)
121 def redirect_back_or_default(default)
120 back_url = params[:back_url]
122 back_url = params[:back_url]
121 if !back_url.blank?
123 if !back_url.blank?
122 uri = URI.parse(back_url)
124 uri = URI.parse(back_url)
123 # do not redirect user to another host
125 # do not redirect user to another host
124 if uri.relative? || (uri.host == request.host)
126 if uri.relative? || (uri.host == request.host)
125 redirect_to(back_url) and return
127 redirect_to(back_url) and return
126 end
128 end
127 end
129 end
128 redirect_to default
130 redirect_to default
129 end
131 end
130
132
131 def render_403
133 def render_403
132 @project = nil
134 @project = nil
133 render :template => "common/403", :layout => !request.xhr?, :status => 403
135 render :template => "common/403", :layout => !request.xhr?, :status => 403
134 return false
136 return false
135 end
137 end
136
138
137 def render_404
139 def render_404
138 render :template => "common/404", :layout => !request.xhr?, :status => 404
140 render :template => "common/404", :layout => !request.xhr?, :status => 404
139 return false
141 return false
140 end
142 end
141
143
142 def render_error(msg)
144 def render_error(msg)
143 flash.now[:error] = msg
145 flash.now[:error] = msg
144 render :nothing => true, :layout => !request.xhr?, :status => 500
146 render :nothing => true, :layout => !request.xhr?, :status => 500
145 end
147 end
146
148
147 def render_feed(items, options={})
149 def render_feed(items, options={})
148 @items = items || []
150 @items = items || []
149 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
151 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
150 @items = @items.slice(0, Setting.feeds_limit.to_i)
152 @items = @items.slice(0, Setting.feeds_limit.to_i)
151 @title = options[:title] || Setting.app_title
153 @title = options[:title] || Setting.app_title
152 render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
154 render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
153 end
155 end
154
156
155 def self.accept_key_auth(*actions)
157 def self.accept_key_auth(*actions)
156 actions = actions.flatten.map(&:to_s)
158 actions = actions.flatten.map(&:to_s)
157 write_inheritable_attribute('accept_key_auth_actions', actions)
159 write_inheritable_attribute('accept_key_auth_actions', actions)
158 end
160 end
159
161
160 def accept_key_auth_actions
162 def accept_key_auth_actions
161 self.class.read_inheritable_attribute('accept_key_auth_actions') || []
163 self.class.read_inheritable_attribute('accept_key_auth_actions') || []
162 end
164 end
163
165
164 # TODO: move to model
166 # TODO: move to model
165 def attach_files(obj, attachments)
167 def attach_files(obj, attachments)
166 attached = []
168 attached = []
167 if attachments && attachments.is_a?(Hash)
169 if attachments && attachments.is_a?(Hash)
168 attachments.each_value do |attachment|
170 attachments.each_value do |attachment|
169 file = attachment['file']
171 file = attachment['file']
170 next unless file && file.size > 0
172 next unless file && file.size > 0
171 a = Attachment.create(:container => obj,
173 a = Attachment.create(:container => obj,
172 :file => file,
174 :file => file,
173 :description => attachment['description'].to_s.strip,
175 :description => attachment['description'].to_s.strip,
174 :author => User.current)
176 :author => User.current)
175 attached << a unless a.new_record?
177 attached << a unless a.new_record?
176 end
178 end
177 end
179 end
178 attached
180 attached
179 end
181 end
180
182
181 # Returns the number of objects that should be displayed
183 # Returns the number of objects that should be displayed
182 # on the paginated list
184 # on the paginated list
183 def per_page_option
185 def per_page_option
184 per_page = nil
186 per_page = nil
185 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
187 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
186 per_page = params[:per_page].to_s.to_i
188 per_page = params[:per_page].to_s.to_i
187 session[:per_page] = per_page
189 session[:per_page] = per_page
188 elsif session[:per_page]
190 elsif session[:per_page]
189 per_page = session[:per_page]
191 per_page = session[:per_page]
190 else
192 else
191 per_page = Setting.per_page_options_array.first || 25
193 per_page = Setting.per_page_options_array.first || 25
192 end
194 end
193 per_page
195 per_page
194 end
196 end
195
197
196 # qvalues http header parser
198 # qvalues http header parser
197 # code taken from webrick
199 # code taken from webrick
198 def parse_qvalues(value)
200 def parse_qvalues(value)
199 tmp = []
201 tmp = []
200 if value
202 if value
201 parts = value.split(/,\s*/)
203 parts = value.split(/,\s*/)
202 parts.each {|part|
204 parts.each {|part|
203 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
205 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
204 val = m[1]
206 val = m[1]
205 q = (m[2] or 1).to_f
207 q = (m[2] or 1).to_f
206 tmp.push([val, q])
208 tmp.push([val, q])
207 end
209 end
208 }
210 }
209 tmp = tmp.sort_by{|val, q| -q}
211 tmp = tmp.sort_by{|val, q| -q}
210 tmp.collect!{|val, q| val}
212 tmp.collect!{|val, q| val}
211 end
213 end
212 return tmp
214 return tmp
213 end
215 end
214
216
215 # Returns a string that can be used as filename value in Content-Disposition header
217 # Returns a string that can be used as filename value in Content-Disposition header
216 def filename_for_content_disposition(name)
218 def filename_for_content_disposition(name)
217 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
219 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
218 end
220 end
219 end
221 end
@@ -1,56 +1,55
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class AttachmentsController < ApplicationController
18 class AttachmentsController < ApplicationController
19 layout 'base'
20 before_filter :find_project
19 before_filter :find_project
21
20
22 def show
21 def show
23 if @attachment.is_diff?
22 if @attachment.is_diff?
24 @diff = File.new(@attachment.diskfile, "rb").read
23 @diff = File.new(@attachment.diskfile, "rb").read
25 render :action => 'diff'
24 render :action => 'diff'
26 elsif @attachment.is_text?
25 elsif @attachment.is_text?
27 @content = File.new(@attachment.diskfile, "rb").read
26 @content = File.new(@attachment.diskfile, "rb").read
28 render :action => 'file'
27 render :action => 'file'
29 elsif
28 elsif
30 download
29 download
31 end
30 end
32 end
31 end
33
32
34 def download
33 def download
35 @attachment.increment_download if @attachment.container.is_a?(Version)
34 @attachment.increment_download if @attachment.container.is_a?(Version)
36
35
37 # images are sent inline
36 # images are sent inline
38 send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename),
37 send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename),
39 :type => @attachment.content_type,
38 :type => @attachment.content_type,
40 :disposition => (@attachment.image? ? 'inline' : 'attachment')
39 :disposition => (@attachment.image? ? 'inline' : 'attachment')
41 end
40 end
42
41
43 private
42 private
44 def find_project
43 def find_project
45 @attachment = Attachment.find(params[:id])
44 @attachment = Attachment.find(params[:id])
46 # Show 404 if the filename in the url is wrong
45 # Show 404 if the filename in the url is wrong
47 raise ActiveRecord::RecordNotFound if params[:filename] && params[:filename] != @attachment.filename
46 raise ActiveRecord::RecordNotFound if params[:filename] && params[:filename] != @attachment.filename
48
47
49 @project = @attachment.project
48 @project = @attachment.project
50 permission = @attachment.container.is_a?(Version) ? :view_files : "view_#{@attachment.container.class.name.underscore.pluralize}".to_sym
49 permission = @attachment.container.is_a?(Version) ? :view_files : "view_#{@attachment.container.class.name.underscore.pluralize}".to_sym
51 allowed = User.current.allowed_to?(permission, @project)
50 allowed = User.current.allowed_to?(permission, @project)
52 allowed ? true : (User.current.logged? ? render_403 : require_login)
51 allowed ? true : (User.current.logged? ? render_403 : require_login)
53 rescue ActiveRecord::RecordNotFound
52 rescue ActiveRecord::RecordNotFound
54 render_404
53 render_404
55 end
54 end
56 end
55 end
@@ -1,83 +1,82
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class AuthSourcesController < ApplicationController
18 class AuthSourcesController < ApplicationController
19 layout 'base'
20 before_filter :require_admin
19 before_filter :require_admin
21
20
22 def index
21 def index
23 list
22 list
24 render :action => 'list' unless request.xhr?
23 render :action => 'list' unless request.xhr?
25 end
24 end
26
25
27 # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
26 # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
28 verify :method => :post, :only => [ :destroy, :create, :update ],
27 verify :method => :post, :only => [ :destroy, :create, :update ],
29 :redirect_to => { :action => :list }
28 :redirect_to => { :action => :list }
30
29
31 def list
30 def list
32 @auth_source_pages, @auth_sources = paginate :auth_sources, :per_page => 10
31 @auth_source_pages, @auth_sources = paginate :auth_sources, :per_page => 10
33 render :action => "list", :layout => false if request.xhr?
32 render :action => "list", :layout => false if request.xhr?
34 end
33 end
35
34
36 def new
35 def new
37 @auth_source = AuthSourceLdap.new
36 @auth_source = AuthSourceLdap.new
38 end
37 end
39
38
40 def create
39 def create
41 @auth_source = AuthSourceLdap.new(params[:auth_source])
40 @auth_source = AuthSourceLdap.new(params[:auth_source])
42 if @auth_source.save
41 if @auth_source.save
43 flash[:notice] = l(:notice_successful_create)
42 flash[:notice] = l(:notice_successful_create)
44 redirect_to :action => 'list'
43 redirect_to :action => 'list'
45 else
44 else
46 render :action => 'new'
45 render :action => 'new'
47 end
46 end
48 end
47 end
49
48
50 def edit
49 def edit
51 @auth_source = AuthSource.find(params[:id])
50 @auth_source = AuthSource.find(params[:id])
52 end
51 end
53
52
54 def update
53 def update
55 @auth_source = AuthSource.find(params[:id])
54 @auth_source = AuthSource.find(params[:id])
56 if @auth_source.update_attributes(params[:auth_source])
55 if @auth_source.update_attributes(params[:auth_source])
57 flash[:notice] = l(:notice_successful_update)
56 flash[:notice] = l(:notice_successful_update)
58 redirect_to :action => 'list'
57 redirect_to :action => 'list'
59 else
58 else
60 render :action => 'edit'
59 render :action => 'edit'
61 end
60 end
62 end
61 end
63
62
64 def test_connection
63 def test_connection
65 @auth_method = AuthSource.find(params[:id])
64 @auth_method = AuthSource.find(params[:id])
66 begin
65 begin
67 @auth_method.test_connection
66 @auth_method.test_connection
68 flash[:notice] = l(:notice_successful_connection)
67 flash[:notice] = l(:notice_successful_connection)
69 rescue => text
68 rescue => text
70 flash[:error] = "Unable to connect (#{text})"
69 flash[:error] = "Unable to connect (#{text})"
71 end
70 end
72 redirect_to :action => 'list'
71 redirect_to :action => 'list'
73 end
72 end
74
73
75 def destroy
74 def destroy
76 @auth_source = AuthSource.find(params[:id])
75 @auth_source = AuthSource.find(params[:id])
77 unless @auth_source.users.find(:first)
76 unless @auth_source.users.find(:first)
78 @auth_source.destroy
77 @auth_source.destroy
79 flash[:notice] = l(:notice_successful_delete)
78 flash[:notice] = l(:notice_successful_delete)
80 end
79 end
81 redirect_to :action => 'list'
80 redirect_to :action => 'list'
82 end
81 end
83 end
82 end
@@ -1,86 +1,85
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class BoardsController < ApplicationController
18 class BoardsController < ApplicationController
19 layout 'base'
20 before_filter :find_project, :authorize
19 before_filter :find_project, :authorize
21
20
22 helper :messages
21 helper :messages
23 include MessagesHelper
22 include MessagesHelper
24 helper :sort
23 helper :sort
25 include SortHelper
24 include SortHelper
26 helper :watchers
25 helper :watchers
27 include WatchersHelper
26 include WatchersHelper
28
27
29 def index
28 def index
30 @boards = @project.boards
29 @boards = @project.boards
31 # show the board if there is only one
30 # show the board if there is only one
32 if @boards.size == 1
31 if @boards.size == 1
33 @board = @boards.first
32 @board = @boards.first
34 show
33 show
35 end
34 end
36 end
35 end
37
36
38 def show
37 def show
39 sort_init "#{Message.table_name}.updated_on", "desc"
38 sort_init "#{Message.table_name}.updated_on", "desc"
40 sort_update
39 sort_update
41
40
42 @topic_count = @board.topics.count
41 @topic_count = @board.topics.count
43 @topic_pages = Paginator.new self, @topic_count, per_page_option, params['page']
42 @topic_pages = Paginator.new self, @topic_count, per_page_option, params['page']
44 @topics = @board.topics.find :all, :order => "#{Message.table_name}.sticky DESC, #{sort_clause}",
43 @topics = @board.topics.find :all, :order => "#{Message.table_name}.sticky DESC, #{sort_clause}",
45 :include => [:author, {:last_reply => :author}],
44 :include => [:author, {:last_reply => :author}],
46 :limit => @topic_pages.items_per_page,
45 :limit => @topic_pages.items_per_page,
47 :offset => @topic_pages.current.offset
46 :offset => @topic_pages.current.offset
48 render :action => 'show', :layout => !request.xhr?
47 render :action => 'show', :layout => !request.xhr?
49 end
48 end
50
49
51 verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :index }
50 verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :index }
52
51
53 def new
52 def new
54 @board = Board.new(params[:board])
53 @board = Board.new(params[:board])
55 @board.project = @project
54 @board.project = @project
56 if request.post? && @board.save
55 if request.post? && @board.save
57 flash[:notice] = l(:notice_successful_create)
56 flash[:notice] = l(:notice_successful_create)
58 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
57 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
59 end
58 end
60 end
59 end
61
60
62 def edit
61 def edit
63 if request.post? && @board.update_attributes(params[:board])
62 if request.post? && @board.update_attributes(params[:board])
64 case params[:position]
63 case params[:position]
65 when 'highest'; @board.move_to_top
64 when 'highest'; @board.move_to_top
66 when 'higher'; @board.move_higher
65 when 'higher'; @board.move_higher
67 when 'lower'; @board.move_lower
66 when 'lower'; @board.move_lower
68 when 'lowest'; @board.move_to_bottom
67 when 'lowest'; @board.move_to_bottom
69 end if params[:position]
68 end if params[:position]
70 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
69 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
71 end
70 end
72 end
71 end
73
72
74 def destroy
73 def destroy
75 @board.destroy
74 @board.destroy
76 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
75 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
77 end
76 end
78
77
79 private
78 private
80 def find_project
79 def find_project
81 @project = Project.find(params[:project_id])
80 @project = Project.find(params[:project_id])
82 @board = @project.boards.find(params[:id]) if params[:id]
81 @board = @project.boards.find(params[:id]) if params[:id]
83 rescue ActiveRecord::RecordNotFound
82 rescue ActiveRecord::RecordNotFound
84 render_404
83 render_404
85 end
84 end
86 end
85 end
@@ -1,89 +1,88
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class CustomFieldsController < ApplicationController
18 class CustomFieldsController < ApplicationController
19 layout 'base'
20 before_filter :require_admin
19 before_filter :require_admin
21
20
22 def index
21 def index
23 list
22 list
24 render :action => 'list' unless request.xhr?
23 render :action => 'list' unless request.xhr?
25 end
24 end
26
25
27 def list
26 def list
28 @custom_fields_by_type = CustomField.find(:all).group_by {|f| f.class.name }
27 @custom_fields_by_type = CustomField.find(:all).group_by {|f| f.class.name }
29 @tab = params[:tab] || 'IssueCustomField'
28 @tab = params[:tab] || 'IssueCustomField'
30 render :action => "list", :layout => false if request.xhr?
29 render :action => "list", :layout => false if request.xhr?
31 end
30 end
32
31
33 def new
32 def new
34 case params[:type]
33 case params[:type]
35 when "IssueCustomField"
34 when "IssueCustomField"
36 @custom_field = IssueCustomField.new(params[:custom_field])
35 @custom_field = IssueCustomField.new(params[:custom_field])
37 @custom_field.trackers = Tracker.find(params[:tracker_ids]) if params[:tracker_ids]
36 @custom_field.trackers = Tracker.find(params[:tracker_ids]) if params[:tracker_ids]
38 when "UserCustomField"
37 when "UserCustomField"
39 @custom_field = UserCustomField.new(params[:custom_field])
38 @custom_field = UserCustomField.new(params[:custom_field])
40 when "ProjectCustomField"
39 when "ProjectCustomField"
41 @custom_field = ProjectCustomField.new(params[:custom_field])
40 @custom_field = ProjectCustomField.new(params[:custom_field])
42 when "TimeEntryCustomField"
41 when "TimeEntryCustomField"
43 @custom_field = TimeEntryCustomField.new(params[:custom_field])
42 @custom_field = TimeEntryCustomField.new(params[:custom_field])
44 else
43 else
45 redirect_to :action => 'list'
44 redirect_to :action => 'list'
46 return
45 return
47 end
46 end
48 if request.post? and @custom_field.save
47 if request.post? and @custom_field.save
49 flash[:notice] = l(:notice_successful_create)
48 flash[:notice] = l(:notice_successful_create)
50 redirect_to :action => 'list', :tab => @custom_field.class.name
49 redirect_to :action => 'list', :tab => @custom_field.class.name
51 end
50 end
52 @trackers = Tracker.find(:all, :order => 'position')
51 @trackers = Tracker.find(:all, :order => 'position')
53 end
52 end
54
53
55 def edit
54 def edit
56 @custom_field = CustomField.find(params[:id])
55 @custom_field = CustomField.find(params[:id])
57 if request.post? and @custom_field.update_attributes(params[:custom_field])
56 if request.post? and @custom_field.update_attributes(params[:custom_field])
58 if @custom_field.is_a? IssueCustomField
57 if @custom_field.is_a? IssueCustomField
59 @custom_field.trackers = params[:tracker_ids] ? Tracker.find(params[:tracker_ids]) : []
58 @custom_field.trackers = params[:tracker_ids] ? Tracker.find(params[:tracker_ids]) : []
60 end
59 end
61 flash[:notice] = l(:notice_successful_update)
60 flash[:notice] = l(:notice_successful_update)
62 redirect_to :action => 'list', :tab => @custom_field.class.name
61 redirect_to :action => 'list', :tab => @custom_field.class.name
63 end
62 end
64 @trackers = Tracker.find(:all, :order => 'position')
63 @trackers = Tracker.find(:all, :order => 'position')
65 end
64 end
66
65
67 def move
66 def move
68 @custom_field = CustomField.find(params[:id])
67 @custom_field = CustomField.find(params[:id])
69 case params[:position]
68 case params[:position]
70 when 'highest'
69 when 'highest'
71 @custom_field.move_to_top
70 @custom_field.move_to_top
72 when 'higher'
71 when 'higher'
73 @custom_field.move_higher
72 @custom_field.move_higher
74 when 'lower'
73 when 'lower'
75 @custom_field.move_lower
74 @custom_field.move_lower
76 when 'lowest'
75 when 'lowest'
77 @custom_field.move_to_bottom
76 @custom_field.move_to_bottom
78 end if params[:position]
77 end if params[:position]
79 redirect_to :action => 'list', :tab => @custom_field.class.name
78 redirect_to :action => 'list', :tab => @custom_field.class.name
80 end
79 end
81
80
82 def destroy
81 def destroy
83 @custom_field = CustomField.find(params[:id]).destroy
82 @custom_field = CustomField.find(params[:id]).destroy
84 redirect_to :action => 'list', :tab => @custom_field.class.name
83 redirect_to :action => 'list', :tab => @custom_field.class.name
85 rescue
84 rescue
86 flash[:error] = "Unable to delete custom field"
85 flash[:error] = "Unable to delete custom field"
87 redirect_to :action => 'list'
86 redirect_to :action => 'list'
88 end
87 end
89 end
88 end
@@ -1,93 +1,92
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class DocumentsController < ApplicationController
18 class DocumentsController < ApplicationController
19 layout 'base'
20 before_filter :find_project, :only => [:index, :new]
19 before_filter :find_project, :only => [:index, :new]
21 before_filter :find_document, :except => [:index, :new]
20 before_filter :find_document, :except => [:index, :new]
22 before_filter :authorize
21 before_filter :authorize
23
22
24 helper :attachments
23 helper :attachments
25
24
26 def index
25 def index
27 @sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category'
26 @sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category'
28 documents = @project.documents.find :all, :include => [:attachments, :category]
27 documents = @project.documents.find :all, :include => [:attachments, :category]
29 case @sort_by
28 case @sort_by
30 when 'date'
29 when 'date'
31 @grouped = documents.group_by {|d| d.created_on.to_date }
30 @grouped = documents.group_by {|d| d.created_on.to_date }
32 when 'title'
31 when 'title'
33 @grouped = documents.group_by {|d| d.title.first.upcase}
32 @grouped = documents.group_by {|d| d.title.first.upcase}
34 when 'author'
33 when 'author'
35 @grouped = documents.select{|d| d.attachments.any?}.group_by {|d| d.attachments.last.author}
34 @grouped = documents.select{|d| d.attachments.any?}.group_by {|d| d.attachments.last.author}
36 else
35 else
37 @grouped = documents.group_by(&:category)
36 @grouped = documents.group_by(&:category)
38 end
37 end
39 render :layout => false if request.xhr?
38 render :layout => false if request.xhr?
40 end
39 end
41
40
42 def show
41 def show
43 @attachments = @document.attachments.find(:all, :order => "created_on DESC")
42 @attachments = @document.attachments.find(:all, :order => "created_on DESC")
44 end
43 end
45
44
46 def new
45 def new
47 @document = @project.documents.build(params[:document])
46 @document = @project.documents.build(params[:document])
48 if request.post? and @document.save
47 if request.post? and @document.save
49 attach_files(@document, params[:attachments])
48 attach_files(@document, params[:attachments])
50 flash[:notice] = l(:notice_successful_create)
49 flash[:notice] = l(:notice_successful_create)
51 Mailer.deliver_document_added(@document) if Setting.notified_events.include?('document_added')
50 Mailer.deliver_document_added(@document) if Setting.notified_events.include?('document_added')
52 redirect_to :action => 'index', :project_id => @project
51 redirect_to :action => 'index', :project_id => @project
53 end
52 end
54 end
53 end
55
54
56 def edit
55 def edit
57 @categories = Enumeration::get_values('DCAT')
56 @categories = Enumeration::get_values('DCAT')
58 if request.post? and @document.update_attributes(params[:document])
57 if request.post? and @document.update_attributes(params[:document])
59 flash[:notice] = l(:notice_successful_update)
58 flash[:notice] = l(:notice_successful_update)
60 redirect_to :action => 'show', :id => @document
59 redirect_to :action => 'show', :id => @document
61 end
60 end
62 end
61 end
63
62
64 def destroy
63 def destroy
65 @document.destroy
64 @document.destroy
66 redirect_to :controller => 'documents', :action => 'index', :project_id => @project
65 redirect_to :controller => 'documents', :action => 'index', :project_id => @project
67 end
66 end
68
67
69 def add_attachment
68 def add_attachment
70 attachments = attach_files(@document, params[:attachments])
69 attachments = attach_files(@document, params[:attachments])
71 Mailer.deliver_attachments_added(attachments) if !attachments.empty? && Setting.notified_events.include?('document_added')
70 Mailer.deliver_attachments_added(attachments) if !attachments.empty? && Setting.notified_events.include?('document_added')
72 redirect_to :action => 'show', :id => @document
71 redirect_to :action => 'show', :id => @document
73 end
72 end
74
73
75 def destroy_attachment
74 def destroy_attachment
76 @document.attachments.find(params[:attachment_id]).destroy
75 @document.attachments.find(params[:attachment_id]).destroy
77 redirect_to :action => 'show', :id => @document
76 redirect_to :action => 'show', :id => @document
78 end
77 end
79
78
80 private
79 private
81 def find_project
80 def find_project
82 @project = Project.find(params[:project_id])
81 @project = Project.find(params[:project_id])
83 rescue ActiveRecord::RecordNotFound
82 rescue ActiveRecord::RecordNotFound
84 render_404
83 render_404
85 end
84 end
86
85
87 def find_document
86 def find_document
88 @document = Document.find(params[:id])
87 @document = Document.find(params[:id])
89 @project = @document.project
88 @project = @document.project
90 rescue ActiveRecord::RecordNotFound
89 rescue ActiveRecord::RecordNotFound
91 render_404
90 render_404
92 end
91 end
93 end
92 end
@@ -1,94 +1,93
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class EnumerationsController < ApplicationController
18 class EnumerationsController < ApplicationController
19 layout 'base'
20 before_filter :require_admin
19 before_filter :require_admin
21
20
22 def index
21 def index
23 list
22 list
24 render :action => 'list'
23 render :action => 'list'
25 end
24 end
26
25
27 # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
26 # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
28 verify :method => :post, :only => [ :destroy, :create, :update ],
27 verify :method => :post, :only => [ :destroy, :create, :update ],
29 :redirect_to => { :action => :list }
28 :redirect_to => { :action => :list }
30
29
31 def list
30 def list
32 end
31 end
33
32
34 def new
33 def new
35 @enumeration = Enumeration.new(:opt => params[:opt])
34 @enumeration = Enumeration.new(:opt => params[:opt])
36 end
35 end
37
36
38 def create
37 def create
39 @enumeration = Enumeration.new(params[:enumeration])
38 @enumeration = Enumeration.new(params[:enumeration])
40 if @enumeration.save
39 if @enumeration.save
41 flash[:notice] = l(:notice_successful_create)
40 flash[:notice] = l(:notice_successful_create)
42 redirect_to :action => 'list', :opt => @enumeration.opt
41 redirect_to :action => 'list', :opt => @enumeration.opt
43 else
42 else
44 render :action => 'new'
43 render :action => 'new'
45 end
44 end
46 end
45 end
47
46
48 def edit
47 def edit
49 @enumeration = Enumeration.find(params[:id])
48 @enumeration = Enumeration.find(params[:id])
50 end
49 end
51
50
52 def update
51 def update
53 @enumeration = Enumeration.find(params[:id])
52 @enumeration = Enumeration.find(params[:id])
54 if @enumeration.update_attributes(params[:enumeration])
53 if @enumeration.update_attributes(params[:enumeration])
55 flash[:notice] = l(:notice_successful_update)
54 flash[:notice] = l(:notice_successful_update)
56 redirect_to :action => 'list', :opt => @enumeration.opt
55 redirect_to :action => 'list', :opt => @enumeration.opt
57 else
56 else
58 render :action => 'edit'
57 render :action => 'edit'
59 end
58 end
60 end
59 end
61
60
62 def move
61 def move
63 @enumeration = Enumeration.find(params[:id])
62 @enumeration = Enumeration.find(params[:id])
64 case params[:position]
63 case params[:position]
65 when 'highest'
64 when 'highest'
66 @enumeration.move_to_top
65 @enumeration.move_to_top
67 when 'higher'
66 when 'higher'
68 @enumeration.move_higher
67 @enumeration.move_higher
69 when 'lower'
68 when 'lower'
70 @enumeration.move_lower
69 @enumeration.move_lower
71 when 'lowest'
70 when 'lowest'
72 @enumeration.move_to_bottom
71 @enumeration.move_to_bottom
73 end if params[:position]
72 end if params[:position]
74 redirect_to :action => 'index'
73 redirect_to :action => 'index'
75 end
74 end
76
75
77 def destroy
76 def destroy
78 @enumeration = Enumeration.find(params[:id])
77 @enumeration = Enumeration.find(params[:id])
79 if !@enumeration.in_use?
78 if !@enumeration.in_use?
80 # No associated objects
79 # No associated objects
81 @enumeration.destroy
80 @enumeration.destroy
82 redirect_to :action => 'index'
81 redirect_to :action => 'index'
83 elsif params[:reassign_to_id]
82 elsif params[:reassign_to_id]
84 if reassign_to = Enumeration.find_by_opt_and_id(@enumeration.opt, params[:reassign_to_id])
83 if reassign_to = Enumeration.find_by_opt_and_id(@enumeration.opt, params[:reassign_to_id])
85 @enumeration.destroy(reassign_to)
84 @enumeration.destroy(reassign_to)
86 redirect_to :action => 'index'
85 redirect_to :action => 'index'
87 end
86 end
88 end
87 end
89 @enumerations = Enumeration.get_values(@enumeration.opt) - [@enumeration]
88 @enumerations = Enumeration.get_values(@enumeration.opt) - [@enumeration]
90 #rescue
89 #rescue
91 # flash[:error] = 'Unable to delete enumeration'
90 # flash[:error] = 'Unable to delete enumeration'
92 # redirect_to :action => 'index'
91 # redirect_to :action => 'index'
93 end
92 end
94 end
93 end
@@ -1,53 +1,52
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class IssueCategoriesController < ApplicationController
18 class IssueCategoriesController < ApplicationController
19 layout 'base'
20 menu_item :settings
19 menu_item :settings
21 before_filter :find_project, :authorize
20 before_filter :find_project, :authorize
22
21
23 verify :method => :post, :only => :destroy
22 verify :method => :post, :only => :destroy
24
23
25 def edit
24 def edit
26 if request.post? and @category.update_attributes(params[:category])
25 if request.post? and @category.update_attributes(params[:category])
27 flash[:notice] = l(:notice_successful_update)
26 flash[:notice] = l(:notice_successful_update)
28 redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
27 redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
29 end
28 end
30 end
29 end
31
30
32 def destroy
31 def destroy
33 @issue_count = @category.issues.size
32 @issue_count = @category.issues.size
34 if @issue_count == 0
33 if @issue_count == 0
35 # No issue assigned to this category
34 # No issue assigned to this category
36 @category.destroy
35 @category.destroy
37 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories'
36 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories'
38 elsif params[:todo]
37 elsif params[:todo]
39 reassign_to = @project.issue_categories.find_by_id(params[:reassign_to_id]) if params[:todo] == 'reassign'
38 reassign_to = @project.issue_categories.find_by_id(params[:reassign_to_id]) if params[:todo] == 'reassign'
40 @category.destroy(reassign_to)
39 @category.destroy(reassign_to)
41 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories'
40 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories'
42 end
41 end
43 @categories = @project.issue_categories - [@category]
42 @categories = @project.issue_categories - [@category]
44 end
43 end
45
44
46 private
45 private
47 def find_project
46 def find_project
48 @category = IssueCategory.find(params[:id])
47 @category = IssueCategory.find(params[:id])
49 @project = @category.project
48 @project = @category.project
50 rescue ActiveRecord::RecordNotFound
49 rescue ActiveRecord::RecordNotFound
51 render_404
50 render_404
52 end
51 end
53 end
52 end
@@ -1,59 +1,58
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class IssueRelationsController < ApplicationController
18 class IssueRelationsController < ApplicationController
19 layout 'base'
20 before_filter :find_project, :authorize
19 before_filter :find_project, :authorize
21
20
22 def new
21 def new
23 @relation = IssueRelation.new(params[:relation])
22 @relation = IssueRelation.new(params[:relation])
24 @relation.issue_from = @issue
23 @relation.issue_from = @issue
25 @relation.save if request.post?
24 @relation.save if request.post?
26 respond_to do |format|
25 respond_to do |format|
27 format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
26 format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
28 format.js do
27 format.js do
29 render :update do |page|
28 render :update do |page|
30 page.replace_html "relations", :partial => 'issues/relations'
29 page.replace_html "relations", :partial => 'issues/relations'
31 if @relation.errors.empty?
30 if @relation.errors.empty?
32 page << "$('relation_delay').value = ''"
31 page << "$('relation_delay').value = ''"
33 page << "$('relation_issue_to_id').value = ''"
32 page << "$('relation_issue_to_id').value = ''"
34 end
33 end
35 end
34 end
36 end
35 end
37 end
36 end
38 end
37 end
39
38
40 def destroy
39 def destroy
41 relation = IssueRelation.find(params[:id])
40 relation = IssueRelation.find(params[:id])
42 if request.post? && @issue.relations.include?(relation)
41 if request.post? && @issue.relations.include?(relation)
43 relation.destroy
42 relation.destroy
44 @issue.reload
43 @issue.reload
45 end
44 end
46 respond_to do |format|
45 respond_to do |format|
47 format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
46 format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
48 format.js { render(:update) {|page| page.replace_html "relations", :partial => 'issues/relations'} }
47 format.js { render(:update) {|page| page.replace_html "relations", :partial => 'issues/relations'} }
49 end
48 end
50 end
49 end
51
50
52 private
51 private
53 def find_project
52 def find_project
54 @issue = Issue.find(params[:issue_id])
53 @issue = Issue.find(params[:issue_id])
55 @project = @issue.project
54 @project = @issue.project
56 rescue ActiveRecord::RecordNotFound
55 rescue ActiveRecord::RecordNotFound
57 render_404
56 render_404
58 end
57 end
59 end
58 end
@@ -1,85 +1,84
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class IssueStatusesController < ApplicationController
18 class IssueStatusesController < ApplicationController
19 layout 'base'
20 before_filter :require_admin
19 before_filter :require_admin
21
20
22 verify :method => :post, :only => [ :destroy, :create, :update, :move ],
21 verify :method => :post, :only => [ :destroy, :create, :update, :move ],
23 :redirect_to => { :action => :list }
22 :redirect_to => { :action => :list }
24
23
25 def index
24 def index
26 list
25 list
27 render :action => 'list' unless request.xhr?
26 render :action => 'list' unless request.xhr?
28 end
27 end
29
28
30 def list
29 def list
31 @issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 25, :order => "position"
30 @issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 25, :order => "position"
32 render :action => "list", :layout => false if request.xhr?
31 render :action => "list", :layout => false if request.xhr?
33 end
32 end
34
33
35 def new
34 def new
36 @issue_status = IssueStatus.new
35 @issue_status = IssueStatus.new
37 end
36 end
38
37
39 def create
38 def create
40 @issue_status = IssueStatus.new(params[:issue_status])
39 @issue_status = IssueStatus.new(params[:issue_status])
41 if @issue_status.save
40 if @issue_status.save
42 flash[:notice] = l(:notice_successful_create)
41 flash[:notice] = l(:notice_successful_create)
43 redirect_to :action => 'list'
42 redirect_to :action => 'list'
44 else
43 else
45 render :action => 'new'
44 render :action => 'new'
46 end
45 end
47 end
46 end
48
47
49 def edit
48 def edit
50 @issue_status = IssueStatus.find(params[:id])
49 @issue_status = IssueStatus.find(params[:id])
51 end
50 end
52
51
53 def update
52 def update
54 @issue_status = IssueStatus.find(params[:id])
53 @issue_status = IssueStatus.find(params[:id])
55 if @issue_status.update_attributes(params[:issue_status])
54 if @issue_status.update_attributes(params[:issue_status])
56 flash[:notice] = l(:notice_successful_update)
55 flash[:notice] = l(:notice_successful_update)
57 redirect_to :action => 'list'
56 redirect_to :action => 'list'
58 else
57 else
59 render :action => 'edit'
58 render :action => 'edit'
60 end
59 end
61 end
60 end
62
61
63 def move
62 def move
64 @issue_status = IssueStatus.find(params[:id])
63 @issue_status = IssueStatus.find(params[:id])
65 case params[:position]
64 case params[:position]
66 when 'highest'
65 when 'highest'
67 @issue_status.move_to_top
66 @issue_status.move_to_top
68 when 'higher'
67 when 'higher'
69 @issue_status.move_higher
68 @issue_status.move_higher
70 when 'lower'
69 when 'lower'
71 @issue_status.move_lower
70 @issue_status.move_lower
72 when 'lowest'
71 when 'lowest'
73 @issue_status.move_to_bottom
72 @issue_status.move_to_bottom
74 end if params[:position]
73 end if params[:position]
75 redirect_to :action => 'list'
74 redirect_to :action => 'list'
76 end
75 end
77
76
78 def destroy
77 def destroy
79 IssueStatus.find(params[:id]).destroy
78 IssueStatus.find(params[:id]).destroy
80 redirect_to :action => 'list'
79 redirect_to :action => 'list'
81 rescue
80 rescue
82 flash[:error] = "Unable to delete issue status"
81 flash[:error] = "Unable to delete issue status"
83 redirect_to :action => 'list'
82 redirect_to :action => 'list'
84 end
83 end
85 end
84 end
@@ -1,430 +1,429
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class IssuesController < ApplicationController
18 class IssuesController < ApplicationController
19 layout 'base'
20 menu_item :new_issue, :only => :new
19 menu_item :new_issue, :only => :new
21
20
22 before_filter :find_issue, :only => [:show, :edit, :reply, :destroy_attachment]
21 before_filter :find_issue, :only => [:show, :edit, :reply, :destroy_attachment]
23 before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
22 before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
24 before_filter :find_project, :only => [:new, :update_form, :preview]
23 before_filter :find_project, :only => [:new, :update_form, :preview]
25 before_filter :authorize, :except => [:index, :changes, :preview, :update_form, :context_menu]
24 before_filter :authorize, :except => [:index, :changes, :preview, :update_form, :context_menu]
26 before_filter :find_optional_project, :only => [:index, :changes]
25 before_filter :find_optional_project, :only => [:index, :changes]
27 accept_key_auth :index, :changes
26 accept_key_auth :index, :changes
28
27
29 helper :journals
28 helper :journals
30 helper :projects
29 helper :projects
31 include ProjectsHelper
30 include ProjectsHelper
32 helper :custom_fields
31 helper :custom_fields
33 include CustomFieldsHelper
32 include CustomFieldsHelper
34 helper :ifpdf
33 helper :ifpdf
35 include IfpdfHelper
34 include IfpdfHelper
36 helper :issue_relations
35 helper :issue_relations
37 include IssueRelationsHelper
36 include IssueRelationsHelper
38 helper :watchers
37 helper :watchers
39 include WatchersHelper
38 include WatchersHelper
40 helper :attachments
39 helper :attachments
41 include AttachmentsHelper
40 include AttachmentsHelper
42 helper :queries
41 helper :queries
43 helper :sort
42 helper :sort
44 include SortHelper
43 include SortHelper
45 include IssuesHelper
44 include IssuesHelper
46 helper :timelog
45 helper :timelog
47
46
48 def index
47 def index
49 sort_init "#{Issue.table_name}.id", "desc"
48 sort_init "#{Issue.table_name}.id", "desc"
50 sort_update
49 sort_update
51 retrieve_query
50 retrieve_query
52 if @query.valid?
51 if @query.valid?
53 limit = per_page_option
52 limit = per_page_option
54 respond_to do |format|
53 respond_to do |format|
55 format.html { }
54 format.html { }
56 format.atom { }
55 format.atom { }
57 format.csv { limit = Setting.issues_export_limit.to_i }
56 format.csv { limit = Setting.issues_export_limit.to_i }
58 format.pdf { limit = Setting.issues_export_limit.to_i }
57 format.pdf { limit = Setting.issues_export_limit.to_i }
59 end
58 end
60 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
59 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
61 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
60 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
62 @issues = Issue.find :all, :order => sort_clause,
61 @issues = Issue.find :all, :order => sort_clause,
63 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ],
62 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ],
64 :conditions => @query.statement,
63 :conditions => @query.statement,
65 :limit => limit,
64 :limit => limit,
66 :offset => @issue_pages.current.offset
65 :offset => @issue_pages.current.offset
67 respond_to do |format|
66 respond_to do |format|
68 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
67 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
69 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
68 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
70 format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
69 format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
71 format.pdf { send_data(render(:template => 'issues/index.rfpdf', :layout => false), :type => 'application/pdf', :filename => 'export.pdf') }
70 format.pdf { send_data(render(:template => 'issues/index.rfpdf', :layout => false), :type => 'application/pdf', :filename => 'export.pdf') }
72 end
71 end
73 else
72 else
74 # Send html if the query is not valid
73 # Send html if the query is not valid
75 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
74 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
76 end
75 end
77 rescue ActiveRecord::RecordNotFound
76 rescue ActiveRecord::RecordNotFound
78 render_404
77 render_404
79 end
78 end
80
79
81 def changes
80 def changes
82 sort_init "#{Issue.table_name}.id", "desc"
81 sort_init "#{Issue.table_name}.id", "desc"
83 sort_update
82 sort_update
84 retrieve_query
83 retrieve_query
85 if @query.valid?
84 if @query.valid?
86 @journals = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
85 @journals = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
87 :conditions => @query.statement,
86 :conditions => @query.statement,
88 :limit => 25,
87 :limit => 25,
89 :order => "#{Journal.table_name}.created_on DESC"
88 :order => "#{Journal.table_name}.created_on DESC"
90 end
89 end
91 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
90 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
92 render :layout => false, :content_type => 'application/atom+xml'
91 render :layout => false, :content_type => 'application/atom+xml'
93 rescue ActiveRecord::RecordNotFound
92 rescue ActiveRecord::RecordNotFound
94 render_404
93 render_404
95 end
94 end
96
95
97 def show
96 def show
98 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
97 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
99 @journals.each_with_index {|j,i| j.indice = i+1}
98 @journals.each_with_index {|j,i| j.indice = i+1}
100 @journals.reverse! if User.current.wants_comments_in_reverse_order?
99 @journals.reverse! if User.current.wants_comments_in_reverse_order?
101 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
100 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
102 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
101 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
103 @priorities = Enumeration::get_values('IPRI')
102 @priorities = Enumeration::get_values('IPRI')
104 @time_entry = TimeEntry.new
103 @time_entry = TimeEntry.new
105 respond_to do |format|
104 respond_to do |format|
106 format.html { render :template => 'issues/show.rhtml' }
105 format.html { render :template => 'issues/show.rhtml' }
107 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
106 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
108 format.pdf { send_data(render(:template => 'issues/show.rfpdf', :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
107 format.pdf { send_data(render(:template => 'issues/show.rfpdf', :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
109 end
108 end
110 end
109 end
111
110
112 # Add a new issue
111 # Add a new issue
113 # The new issue will be created from an existing one if copy_from parameter is given
112 # The new issue will be created from an existing one if copy_from parameter is given
114 def new
113 def new
115 @issue = Issue.new
114 @issue = Issue.new
116 @issue.copy_from(params[:copy_from]) if params[:copy_from]
115 @issue.copy_from(params[:copy_from]) if params[:copy_from]
117 @issue.project = @project
116 @issue.project = @project
118 # Tracker must be set before custom field values
117 # Tracker must be set before custom field values
119 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
118 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
120 if @issue.tracker.nil?
119 if @issue.tracker.nil?
121 flash.now[:error] = 'No tracker is associated to this project. Please check the Project settings.'
120 flash.now[:error] = 'No tracker is associated to this project. Please check the Project settings.'
122 render :nothing => true, :layout => true
121 render :nothing => true, :layout => true
123 return
122 return
124 end
123 end
125 @issue.attributes = params[:issue]
124 @issue.attributes = params[:issue]
126 @issue.author = User.current
125 @issue.author = User.current
127
126
128 default_status = IssueStatus.default
127 default_status = IssueStatus.default
129 unless default_status
128 unless default_status
130 flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
129 flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
131 render :nothing => true, :layout => true
130 render :nothing => true, :layout => true
132 return
131 return
133 end
132 end
134 @issue.status = default_status
133 @issue.status = default_status
135 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)).uniq
134 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)).uniq
136
135
137 if request.get? || request.xhr?
136 if request.get? || request.xhr?
138 @issue.start_date ||= Date.today
137 @issue.start_date ||= Date.today
139 else
138 else
140 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
139 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
141 # Check that the user is allowed to apply the requested status
140 # Check that the user is allowed to apply the requested status
142 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
141 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
143 if @issue.save
142 if @issue.save
144 attach_files(@issue, params[:attachments])
143 attach_files(@issue, params[:attachments])
145 flash[:notice] = l(:notice_successful_create)
144 flash[:notice] = l(:notice_successful_create)
146 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
145 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
147 redirect_to :controller => 'issues', :action => 'show', :id => @issue
146 redirect_to :controller => 'issues', :action => 'show', :id => @issue
148 return
147 return
149 end
148 end
150 end
149 end
151 @priorities = Enumeration::get_values('IPRI')
150 @priorities = Enumeration::get_values('IPRI')
152 render :layout => !request.xhr?
151 render :layout => !request.xhr?
153 end
152 end
154
153
155 # Attributes that can be updated on workflow transition (without :edit permission)
154 # Attributes that can be updated on workflow transition (without :edit permission)
156 # TODO: make it configurable (at least per role)
155 # TODO: make it configurable (at least per role)
157 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
156 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
158
157
159 def edit
158 def edit
160 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
159 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
161 @priorities = Enumeration::get_values('IPRI')
160 @priorities = Enumeration::get_values('IPRI')
162 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
161 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
163 @time_entry = TimeEntry.new
162 @time_entry = TimeEntry.new
164
163
165 @notes = params[:notes]
164 @notes = params[:notes]
166 journal = @issue.init_journal(User.current, @notes)
165 journal = @issue.init_journal(User.current, @notes)
167 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
166 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
168 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
167 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
169 attrs = params[:issue].dup
168 attrs = params[:issue].dup
170 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
169 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
171 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
170 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
172 @issue.attributes = attrs
171 @issue.attributes = attrs
173 end
172 end
174
173
175 if request.post?
174 if request.post?
176 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
175 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
177 @time_entry.attributes = params[:time_entry]
176 @time_entry.attributes = params[:time_entry]
178 attachments = attach_files(@issue, params[:attachments])
177 attachments = attach_files(@issue, params[:attachments])
179 attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
178 attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
180 if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save
179 if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save
181 # Log spend time
180 # Log spend time
182 if current_role.allowed_to?(:log_time)
181 if current_role.allowed_to?(:log_time)
183 @time_entry.save
182 @time_entry.save
184 end
183 end
185 if !journal.new_record?
184 if !journal.new_record?
186 # Only send notification if something was actually changed
185 # Only send notification if something was actually changed
187 flash[:notice] = l(:notice_successful_update)
186 flash[:notice] = l(:notice_successful_update)
188 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
187 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
189 end
188 end
190 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
189 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
191 end
190 end
192 end
191 end
193 rescue ActiveRecord::StaleObjectError
192 rescue ActiveRecord::StaleObjectError
194 # Optimistic locking exception
193 # Optimistic locking exception
195 flash.now[:error] = l(:notice_locking_conflict)
194 flash.now[:error] = l(:notice_locking_conflict)
196 end
195 end
197
196
198 def reply
197 def reply
199 journal = Journal.find(params[:journal_id]) if params[:journal_id]
198 journal = Journal.find(params[:journal_id]) if params[:journal_id]
200 if journal
199 if journal
201 user = journal.user
200 user = journal.user
202 text = journal.notes
201 text = journal.notes
203 else
202 else
204 user = @issue.author
203 user = @issue.author
205 text = @issue.description
204 text = @issue.description
206 end
205 end
207 content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
206 content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
208 content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
207 content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
209 render(:update) { |page|
208 render(:update) { |page|
210 page.<< "$('notes').value = \"#{content}\";"
209 page.<< "$('notes').value = \"#{content}\";"
211 page.show 'update'
210 page.show 'update'
212 page << "Form.Element.focus('notes');"
211 page << "Form.Element.focus('notes');"
213 page << "Element.scrollTo('update');"
212 page << "Element.scrollTo('update');"
214 page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
213 page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
215 }
214 }
216 end
215 end
217
216
218 # Bulk edit a set of issues
217 # Bulk edit a set of issues
219 def bulk_edit
218 def bulk_edit
220 if request.post?
219 if request.post?
221 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
220 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
222 priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
221 priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
223 assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
222 assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
224 category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
223 category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
225 fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id])
224 fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id])
226
225
227 unsaved_issue_ids = []
226 unsaved_issue_ids = []
228 @issues.each do |issue|
227 @issues.each do |issue|
229 journal = issue.init_journal(User.current, params[:notes])
228 journal = issue.init_journal(User.current, params[:notes])
230 issue.priority = priority if priority
229 issue.priority = priority if priority
231 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
230 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
232 issue.category = category if category || params[:category_id] == 'none'
231 issue.category = category if category || params[:category_id] == 'none'
233 issue.fixed_version = fixed_version if fixed_version || params[:fixed_version_id] == 'none'
232 issue.fixed_version = fixed_version if fixed_version || params[:fixed_version_id] == 'none'
234 issue.start_date = params[:start_date] unless params[:start_date].blank?
233 issue.start_date = params[:start_date] unless params[:start_date].blank?
235 issue.due_date = params[:due_date] unless params[:due_date].blank?
234 issue.due_date = params[:due_date] unless params[:due_date].blank?
236 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
235 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
237 # Don't save any change to the issue if the user is not authorized to apply the requested status
236 # Don't save any change to the issue if the user is not authorized to apply the requested status
238 if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
237 if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
239 # Send notification for each issue (if changed)
238 # Send notification for each issue (if changed)
240 Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
239 Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
241 else
240 else
242 # Keep unsaved issue ids to display them in flash error
241 # Keep unsaved issue ids to display them in flash error
243 unsaved_issue_ids << issue.id
242 unsaved_issue_ids << issue.id
244 end
243 end
245 end
244 end
246 if unsaved_issue_ids.empty?
245 if unsaved_issue_ids.empty?
247 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
246 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
248 else
247 else
249 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
248 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
250 end
249 end
251 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
250 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
252 return
251 return
253 end
252 end
254 # Find potential statuses the user could be allowed to switch issues to
253 # Find potential statuses the user could be allowed to switch issues to
255 @available_statuses = Workflow.find(:all, :include => :new_status,
254 @available_statuses = Workflow.find(:all, :include => :new_status,
256 :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq
255 :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq
257 end
256 end
258
257
259 def move
258 def move
260 @allowed_projects = []
259 @allowed_projects = []
261 # find projects to which the user is allowed to move the issue
260 # find projects to which the user is allowed to move the issue
262 if User.current.admin?
261 if User.current.admin?
263 # admin is allowed to move issues to any active (visible) project
262 # admin is allowed to move issues to any active (visible) project
264 @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current), :order => 'name')
263 @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current), :order => 'name')
265 else
264 else
266 User.current.memberships.each {|m| @allowed_projects << m.project if m.role.allowed_to?(:move_issues)}
265 User.current.memberships.each {|m| @allowed_projects << m.project if m.role.allowed_to?(:move_issues)}
267 end
266 end
268 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
267 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
269 @target_project ||= @project
268 @target_project ||= @project
270 @trackers = @target_project.trackers
269 @trackers = @target_project.trackers
271 if request.post?
270 if request.post?
272 new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
271 new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
273 unsaved_issue_ids = []
272 unsaved_issue_ids = []
274 @issues.each do |issue|
273 @issues.each do |issue|
275 issue.init_journal(User.current)
274 issue.init_journal(User.current)
276 unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker)
275 unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker)
277 end
276 end
278 if unsaved_issue_ids.empty?
277 if unsaved_issue_ids.empty?
279 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
278 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
280 else
279 else
281 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
280 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
282 end
281 end
283 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
282 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
284 return
283 return
285 end
284 end
286 render :layout => false if request.xhr?
285 render :layout => false if request.xhr?
287 end
286 end
288
287
289 def destroy
288 def destroy
290 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
289 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
291 if @hours > 0
290 if @hours > 0
292 case params[:todo]
291 case params[:todo]
293 when 'destroy'
292 when 'destroy'
294 # nothing to do
293 # nothing to do
295 when 'nullify'
294 when 'nullify'
296 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
295 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
297 when 'reassign'
296 when 'reassign'
298 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
297 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
299 if reassign_to.nil?
298 if reassign_to.nil?
300 flash.now[:error] = l(:error_issue_not_found_in_project)
299 flash.now[:error] = l(:error_issue_not_found_in_project)
301 return
300 return
302 else
301 else
303 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
302 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
304 end
303 end
305 else
304 else
306 # display the destroy form
305 # display the destroy form
307 return
306 return
308 end
307 end
309 end
308 end
310 @issues.each(&:destroy)
309 @issues.each(&:destroy)
311 redirect_to :action => 'index', :project_id => @project
310 redirect_to :action => 'index', :project_id => @project
312 end
311 end
313
312
314 def destroy_attachment
313 def destroy_attachment
315 a = @issue.attachments.find(params[:attachment_id])
314 a = @issue.attachments.find(params[:attachment_id])
316 a.destroy
315 a.destroy
317 journal = @issue.init_journal(User.current)
316 journal = @issue.init_journal(User.current)
318 journal.details << JournalDetail.new(:property => 'attachment',
317 journal.details << JournalDetail.new(:property => 'attachment',
319 :prop_key => a.id,
318 :prop_key => a.id,
320 :old_value => a.filename)
319 :old_value => a.filename)
321 journal.save
320 journal.save
322 redirect_to :action => 'show', :id => @issue
321 redirect_to :action => 'show', :id => @issue
323 end
322 end
324
323
325 def context_menu
324 def context_menu
326 @issues = Issue.find_all_by_id(params[:ids], :include => :project)
325 @issues = Issue.find_all_by_id(params[:ids], :include => :project)
327 if (@issues.size == 1)
326 if (@issues.size == 1)
328 @issue = @issues.first
327 @issue = @issues.first
329 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
328 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
330 @assignables = @issue.assignable_users
329 @assignables = @issue.assignable_users
331 @assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
330 @assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
332 end
331 end
333 projects = @issues.collect(&:project).compact.uniq
332 projects = @issues.collect(&:project).compact.uniq
334 @project = projects.first if projects.size == 1
333 @project = projects.first if projects.size == 1
335
334
336 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
335 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
337 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
336 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
338 :update => (@issue && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && !@allowed_statuses.empty?))),
337 :update => (@issue && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && !@allowed_statuses.empty?))),
339 :move => (@project && User.current.allowed_to?(:move_issues, @project)),
338 :move => (@project && User.current.allowed_to?(:move_issues, @project)),
340 :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
339 :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
341 :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
340 :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
342 }
341 }
343
342
344 @priorities = Enumeration.get_values('IPRI').reverse
343 @priorities = Enumeration.get_values('IPRI').reverse
345 @statuses = IssueStatus.find(:all, :order => 'position')
344 @statuses = IssueStatus.find(:all, :order => 'position')
346 @back = request.env['HTTP_REFERER']
345 @back = request.env['HTTP_REFERER']
347
346
348 render :layout => false
347 render :layout => false
349 end
348 end
350
349
351 def update_form
350 def update_form
352 @issue = Issue.new(params[:issue])
351 @issue = Issue.new(params[:issue])
353 render :action => :new, :layout => false
352 render :action => :new, :layout => false
354 end
353 end
355
354
356 def preview
355 def preview
357 @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
356 @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
358 @attachements = @issue.attachments if @issue
357 @attachements = @issue.attachments if @issue
359 @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
358 @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
360 render :partial => 'common/preview'
359 render :partial => 'common/preview'
361 end
360 end
362
361
363 private
362 private
364 def find_issue
363 def find_issue
365 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
364 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
366 @project = @issue.project
365 @project = @issue.project
367 rescue ActiveRecord::RecordNotFound
366 rescue ActiveRecord::RecordNotFound
368 render_404
367 render_404
369 end
368 end
370
369
371 # Filter for bulk operations
370 # Filter for bulk operations
372 def find_issues
371 def find_issues
373 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
372 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
374 raise ActiveRecord::RecordNotFound if @issues.empty?
373 raise ActiveRecord::RecordNotFound if @issues.empty?
375 projects = @issues.collect(&:project).compact.uniq
374 projects = @issues.collect(&:project).compact.uniq
376 if projects.size == 1
375 if projects.size == 1
377 @project = projects.first
376 @project = projects.first
378 else
377 else
379 # TODO: let users bulk edit/move/destroy issues from different projects
378 # TODO: let users bulk edit/move/destroy issues from different projects
380 render_error 'Can not bulk edit/move/destroy issues from different projects' and return false
379 render_error 'Can not bulk edit/move/destroy issues from different projects' and return false
381 end
380 end
382 rescue ActiveRecord::RecordNotFound
381 rescue ActiveRecord::RecordNotFound
383 render_404
382 render_404
384 end
383 end
385
384
386 def find_project
385 def find_project
387 @project = Project.find(params[:project_id])
386 @project = Project.find(params[:project_id])
388 rescue ActiveRecord::RecordNotFound
387 rescue ActiveRecord::RecordNotFound
389 render_404
388 render_404
390 end
389 end
391
390
392 def find_optional_project
391 def find_optional_project
393 return true unless params[:project_id]
392 return true unless params[:project_id]
394 @project = Project.find(params[:project_id])
393 @project = Project.find(params[:project_id])
395 authorize
394 authorize
396 rescue ActiveRecord::RecordNotFound
395 rescue ActiveRecord::RecordNotFound
397 render_404
396 render_404
398 end
397 end
399
398
400 # Retrieve query from session or build a new query
399 # Retrieve query from session or build a new query
401 def retrieve_query
400 def retrieve_query
402 if !params[:query_id].blank?
401 if !params[:query_id].blank?
403 cond = "project_id IS NULL"
402 cond = "project_id IS NULL"
404 cond << " OR project_id = #{@project.id}" if @project
403 cond << " OR project_id = #{@project.id}" if @project
405 @query = Query.find(params[:query_id], :conditions => cond)
404 @query = Query.find(params[:query_id], :conditions => cond)
406 @query.project = @project
405 @query.project = @project
407 session[:query] = {:id => @query.id, :project_id => @query.project_id}
406 session[:query] = {:id => @query.id, :project_id => @query.project_id}
408 else
407 else
409 if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
408 if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
410 # Give it a name, required to be valid
409 # Give it a name, required to be valid
411 @query = Query.new(:name => "_")
410 @query = Query.new(:name => "_")
412 @query.project = @project
411 @query.project = @project
413 if params[:fields] and params[:fields].is_a? Array
412 if params[:fields] and params[:fields].is_a? Array
414 params[:fields].each do |field|
413 params[:fields].each do |field|
415 @query.add_filter(field, params[:operators][field], params[:values][field])
414 @query.add_filter(field, params[:operators][field], params[:values][field])
416 end
415 end
417 else
416 else
418 @query.available_filters.keys.each do |field|
417 @query.available_filters.keys.each do |field|
419 @query.add_short_filter(field, params[field]) if params[field]
418 @query.add_short_filter(field, params[field]) if params[field]
420 end
419 end
421 end
420 end
422 session[:query] = {:project_id => @query.project_id, :filters => @query.filters}
421 session[:query] = {:project_id => @query.project_id, :filters => @query.filters}
423 else
422 else
424 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
423 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
425 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters])
424 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters])
426 @query.project = @project
425 @query.project = @project
427 end
426 end
428 end
427 end
429 end
428 end
430 end
429 end
@@ -1,41 +1,40
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class JournalsController < ApplicationController
18 class JournalsController < ApplicationController
19 layout 'base'
20 before_filter :find_journal
19 before_filter :find_journal
21
20
22 def edit
21 def edit
23 if request.post?
22 if request.post?
24 @journal.update_attributes(:notes => params[:notes]) if params[:notes]
23 @journal.update_attributes(:notes => params[:notes]) if params[:notes]
25 @journal.destroy if @journal.details.empty? && @journal.notes.blank?
24 @journal.destroy if @journal.details.empty? && @journal.notes.blank?
26 respond_to do |format|
25 respond_to do |format|
27 format.html { redirect_to :controller => 'issues', :action => 'show', :id => @journal.journalized_id }
26 format.html { redirect_to :controller => 'issues', :action => 'show', :id => @journal.journalized_id }
28 format.js { render :action => 'update' }
27 format.js { render :action => 'update' }
29 end
28 end
30 end
29 end
31 end
30 end
32
31
33 private
32 private
34 def find_journal
33 def find_journal
35 @journal = Journal.find(params[:id])
34 @journal = Journal.find(params[:id])
36 render_403 and return false unless @journal.editable_by?(User.current)
35 render_403 and return false unless @journal.editable_by?(User.current)
37 @project = @journal.journalized.project
36 @project = @journal.journalized.project
38 rescue ActiveRecord::RecordNotFound
37 rescue ActiveRecord::RecordNotFound
39 render_404
38 render_404
40 end
39 end
41 end
40 end
@@ -1,62 +1,61
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class MembersController < ApplicationController
18 class MembersController < ApplicationController
19 layout 'base'
20 before_filter :find_member, :except => :new
19 before_filter :find_member, :except => :new
21 before_filter :find_project, :only => :new
20 before_filter :find_project, :only => :new
22 before_filter :authorize
21 before_filter :authorize
23
22
24 def new
23 def new
25 @project.members << Member.new(params[:member]) if request.post?
24 @project.members << Member.new(params[:member]) if request.post?
26 respond_to do |format|
25 respond_to do |format|
27 format.html { redirect_to :action => 'settings', :tab => 'members', :id => @project }
26 format.html { redirect_to :action => 'settings', :tab => 'members', :id => @project }
28 format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} }
27 format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} }
29 end
28 end
30 end
29 end
31
30
32 def edit
31 def edit
33 if request.post? and @member.update_attributes(params[:member])
32 if request.post? and @member.update_attributes(params[:member])
34 respond_to do |format|
33 respond_to do |format|
35 format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
34 format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
36 format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} }
35 format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} }
37 end
36 end
38 end
37 end
39 end
38 end
40
39
41 def destroy
40 def destroy
42 @member.destroy
41 @member.destroy
43 respond_to do |format|
42 respond_to do |format|
44 format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
43 format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
45 format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} }
44 format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} }
46 end
45 end
47 end
46 end
48
47
49 private
48 private
50 def find_project
49 def find_project
51 @project = Project.find(params[:id])
50 @project = Project.find(params[:id])
52 rescue ActiveRecord::RecordNotFound
51 rescue ActiveRecord::RecordNotFound
53 render_404
52 render_404
54 end
53 end
55
54
56 def find_member
55 def find_member
57 @member = Member.find(params[:id])
56 @member = Member.find(params[:id])
58 @project = @member.project
57 @project = @member.project
59 rescue ActiveRecord::RecordNotFound
58 rescue ActiveRecord::RecordNotFound
60 render_404
59 render_404
61 end
60 end
62 end
61 end
@@ -1,108 +1,107
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class MessagesController < ApplicationController
18 class MessagesController < ApplicationController
19 layout 'base'
20 menu_item :boards
19 menu_item :boards
21 before_filter :find_board, :only => [:new, :preview]
20 before_filter :find_board, :only => [:new, :preview]
22 before_filter :find_message, :except => [:new, :preview]
21 before_filter :find_message, :except => [:new, :preview]
23 before_filter :authorize, :except => :preview
22 before_filter :authorize, :except => :preview
24
23
25 verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show }
24 verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show }
26
25
27 helper :attachments
26 helper :attachments
28 include AttachmentsHelper
27 include AttachmentsHelper
29
28
30 # Show a topic and its replies
29 # Show a topic and its replies
31 def show
30 def show
32 @replies = @topic.children
31 @replies = @topic.children
33 @replies.reverse! if User.current.wants_comments_in_reverse_order?
32 @replies.reverse! if User.current.wants_comments_in_reverse_order?
34 @reply = Message.new(:subject => "RE: #{@message.subject}")
33 @reply = Message.new(:subject => "RE: #{@message.subject}")
35 render :action => "show", :layout => false if request.xhr?
34 render :action => "show", :layout => false if request.xhr?
36 end
35 end
37
36
38 # Create a new topic
37 # Create a new topic
39 def new
38 def new
40 @message = Message.new(params[:message])
39 @message = Message.new(params[:message])
41 @message.author = User.current
40 @message.author = User.current
42 @message.board = @board
41 @message.board = @board
43 if params[:message] && User.current.allowed_to?(:edit_messages, @project)
42 if params[:message] && User.current.allowed_to?(:edit_messages, @project)
44 @message.locked = params[:message]['locked']
43 @message.locked = params[:message]['locked']
45 @message.sticky = params[:message]['sticky']
44 @message.sticky = params[:message]['sticky']
46 end
45 end
47 if request.post? && @message.save
46 if request.post? && @message.save
48 attach_files(@message, params[:attachments])
47 attach_files(@message, params[:attachments])
49 redirect_to :action => 'show', :id => @message
48 redirect_to :action => 'show', :id => @message
50 end
49 end
51 end
50 end
52
51
53 # Reply to a topic
52 # Reply to a topic
54 def reply
53 def reply
55 @reply = Message.new(params[:reply])
54 @reply = Message.new(params[:reply])
56 @reply.author = User.current
55 @reply.author = User.current
57 @reply.board = @board
56 @reply.board = @board
58 @topic.children << @reply
57 @topic.children << @reply
59 if !@reply.new_record?
58 if !@reply.new_record?
60 attach_files(@reply, params[:attachments])
59 attach_files(@reply, params[:attachments])
61 end
60 end
62 redirect_to :action => 'show', :id => @topic
61 redirect_to :action => 'show', :id => @topic
63 end
62 end
64
63
65 # Edit a message
64 # Edit a message
66 def edit
65 def edit
67 if params[:message] && User.current.allowed_to?(:edit_messages, @project)
66 if params[:message] && User.current.allowed_to?(:edit_messages, @project)
68 @message.locked = params[:message]['locked']
67 @message.locked = params[:message]['locked']
69 @message.sticky = params[:message]['sticky']
68 @message.sticky = params[:message]['sticky']
70 end
69 end
71 if request.post? && @message.update_attributes(params[:message])
70 if request.post? && @message.update_attributes(params[:message])
72 attach_files(@message, params[:attachments])
71 attach_files(@message, params[:attachments])
73 flash[:notice] = l(:notice_successful_update)
72 flash[:notice] = l(:notice_successful_update)
74 redirect_to :action => 'show', :id => @topic
73 redirect_to :action => 'show', :id => @topic
75 end
74 end
76 end
75 end
77
76
78 # Delete a messages
77 # Delete a messages
79 def destroy
78 def destroy
80 @message.destroy
79 @message.destroy
81 redirect_to @message.parent.nil? ?
80 redirect_to @message.parent.nil? ?
82 { :controller => 'boards', :action => 'show', :project_id => @project, :id => @board } :
81 { :controller => 'boards', :action => 'show', :project_id => @project, :id => @board } :
83 { :action => 'show', :id => @message.parent }
82 { :action => 'show', :id => @message.parent }
84 end
83 end
85
84
86 def preview
85 def preview
87 message = @board.messages.find_by_id(params[:id])
86 message = @board.messages.find_by_id(params[:id])
88 @attachements = message.attachments if message
87 @attachements = message.attachments if message
89 @text = (params[:message] || params[:reply])[:content]
88 @text = (params[:message] || params[:reply])[:content]
90 render :partial => 'common/preview'
89 render :partial => 'common/preview'
91 end
90 end
92
91
93 private
92 private
94 def find_message
93 def find_message
95 find_board
94 find_board
96 @message = @board.messages.find(params[:id], :include => :parent)
95 @message = @board.messages.find(params[:id], :include => :parent)
97 @topic = @message.root
96 @topic = @message.root
98 rescue ActiveRecord::RecordNotFound
97 rescue ActiveRecord::RecordNotFound
99 render_404
98 render_404
100 end
99 end
101
100
102 def find_board
101 def find_board
103 @board = Board.find(params[:board_id], :include => :project)
102 @board = Board.find(params[:board_id], :include => :project)
104 @project = @board.project
103 @project = @board.project
105 rescue ActiveRecord::RecordNotFound
104 rescue ActiveRecord::RecordNotFound
106 render_404
105 render_404
107 end
106 end
108 end
107 end
@@ -1,161 +1,160
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class MyController < ApplicationController
18 class MyController < ApplicationController
19 helper :issues
20
21 layout 'base'
22 before_filter :require_login
19 before_filter :require_login
23
20
21 helper :issues
22
24 BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues,
23 BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues,
25 'issuesreportedbyme' => :label_reported_issues,
24 'issuesreportedbyme' => :label_reported_issues,
26 'issueswatched' => :label_watched_issues,
25 'issueswatched' => :label_watched_issues,
27 'news' => :label_news_latest,
26 'news' => :label_news_latest,
28 'calendar' => :label_calendar,
27 'calendar' => :label_calendar,
29 'documents' => :label_document_plural,
28 'documents' => :label_document_plural,
30 'timelog' => :label_spent_time
29 'timelog' => :label_spent_time
31 }.freeze
30 }.freeze
32
31
33 DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'],
32 DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'],
34 'right' => ['issuesreportedbyme']
33 'right' => ['issuesreportedbyme']
35 }.freeze
34 }.freeze
36
35
37 verify :xhr => true,
36 verify :xhr => true,
38 :session => :page_layout,
37 :session => :page_layout,
39 :only => [:add_block, :remove_block, :order_blocks]
38 :only => [:add_block, :remove_block, :order_blocks]
40
39
41 def index
40 def index
42 page
41 page
43 render :action => 'page'
42 render :action => 'page'
44 end
43 end
45
44
46 # Show user's page
45 # Show user's page
47 def page
46 def page
48 @user = User.current
47 @user = User.current
49 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT
48 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT
50 end
49 end
51
50
52 # Edit user's account
51 # Edit user's account
53 def account
52 def account
54 @user = User.current
53 @user = User.current
55 @pref = @user.pref
54 @pref = @user.pref
56 if request.post?
55 if request.post?
57 @user.attributes = params[:user]
56 @user.attributes = params[:user]
58 @user.mail_notification = (params[:notification_option] == 'all')
57 @user.mail_notification = (params[:notification_option] == 'all')
59 @user.pref.attributes = params[:pref]
58 @user.pref.attributes = params[:pref]
60 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
59 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
61 if @user.save
60 if @user.save
62 @user.pref.save
61 @user.pref.save
63 @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : [])
62 @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : [])
64 set_language_if_valid @user.language
63 set_language_if_valid @user.language
65 flash[:notice] = l(:notice_account_updated)
64 flash[:notice] = l(:notice_account_updated)
66 redirect_to :action => 'account'
65 redirect_to :action => 'account'
67 return
66 return
68 end
67 end
69 end
68 end
70 @notification_options = [[l(:label_user_mail_option_all), 'all'],
69 @notification_options = [[l(:label_user_mail_option_all), 'all'],
71 [l(:label_user_mail_option_none), 'none']]
70 [l(:label_user_mail_option_none), 'none']]
72 # Only users that belong to more than 1 project can select projects for which they are notified
71 # Only users that belong to more than 1 project can select projects for which they are notified
73 # Note that @user.membership.size would fail since AR ignores :include association option when doing a count
72 # Note that @user.membership.size would fail since AR ignores :include association option when doing a count
74 @notification_options.insert 1, [l(:label_user_mail_option_selected), 'selected'] if @user.memberships.length > 1
73 @notification_options.insert 1, [l(:label_user_mail_option_selected), 'selected'] if @user.memberships.length > 1
75 @notification_option = @user.mail_notification? ? 'all' : (@user.notified_projects_ids.empty? ? 'none' : 'selected')
74 @notification_option = @user.mail_notification? ? 'all' : (@user.notified_projects_ids.empty? ? 'none' : 'selected')
76 end
75 end
77
76
78 # Manage user's password
77 # Manage user's password
79 def password
78 def password
80 @user = User.current
79 @user = User.current
81 flash[:error] = l(:notice_can_t_change_password) and redirect_to :action => 'account' and return if @user.auth_source_id
80 flash[:error] = l(:notice_can_t_change_password) and redirect_to :action => 'account' and return if @user.auth_source_id
82 if request.post?
81 if request.post?
83 if @user.check_password?(params[:password])
82 if @user.check_password?(params[:password])
84 @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
83 @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
85 if @user.save
84 if @user.save
86 flash[:notice] = l(:notice_account_password_updated)
85 flash[:notice] = l(:notice_account_password_updated)
87 redirect_to :action => 'account'
86 redirect_to :action => 'account'
88 end
87 end
89 else
88 else
90 flash[:error] = l(:notice_account_wrong_password)
89 flash[:error] = l(:notice_account_wrong_password)
91 end
90 end
92 end
91 end
93 end
92 end
94
93
95 # Create a new feeds key
94 # Create a new feeds key
96 def reset_rss_key
95 def reset_rss_key
97 if request.post? && User.current.rss_token
96 if request.post? && User.current.rss_token
98 User.current.rss_token.destroy
97 User.current.rss_token.destroy
99 flash[:notice] = l(:notice_feeds_access_key_reseted)
98 flash[:notice] = l(:notice_feeds_access_key_reseted)
100 end
99 end
101 redirect_to :action => 'account'
100 redirect_to :action => 'account'
102 end
101 end
103
102
104 # User's page layout configuration
103 # User's page layout configuration
105 def page_layout
104 def page_layout
106 @user = User.current
105 @user = User.current
107 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
106 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
108 session[:page_layout] = @blocks
107 session[:page_layout] = @blocks
109 %w(top left right).each {|f| session[:page_layout][f] ||= [] }
108 %w(top left right).each {|f| session[:page_layout][f] ||= [] }
110 @block_options = []
109 @block_options = []
111 BLOCKS.each {|k, v| @block_options << [l(v), k]}
110 BLOCKS.each {|k, v| @block_options << [l(v), k]}
112 end
111 end
113
112
114 # Add a block to user's page
113 # Add a block to user's page
115 # The block is added on top of the page
114 # The block is added on top of the page
116 # params[:block] : id of the block to add
115 # params[:block] : id of the block to add
117 def add_block
116 def add_block
118 block = params[:block]
117 block = params[:block]
119 render(:nothing => true) and return unless block && (BLOCKS.keys.include? block)
118 render(:nothing => true) and return unless block && (BLOCKS.keys.include? block)
120 @user = User.current
119 @user = User.current
121 # remove if already present in a group
120 # remove if already present in a group
122 %w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block }
121 %w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block }
123 # add it on top
122 # add it on top
124 session[:page_layout]['top'].unshift block
123 session[:page_layout]['top'].unshift block
125 render :partial => "block", :locals => {:user => @user, :block_name => block}
124 render :partial => "block", :locals => {:user => @user, :block_name => block}
126 end
125 end
127
126
128 # Remove a block to user's page
127 # Remove a block to user's page
129 # params[:block] : id of the block to remove
128 # params[:block] : id of the block to remove
130 def remove_block
129 def remove_block
131 block = params[:block]
130 block = params[:block]
132 # remove block in all groups
131 # remove block in all groups
133 %w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block }
132 %w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block }
134 render :nothing => true
133 render :nothing => true
135 end
134 end
136
135
137 # Change blocks order on user's page
136 # Change blocks order on user's page
138 # params[:group] : group to order (top, left or right)
137 # params[:group] : group to order (top, left or right)
139 # params[:list-(top|left|right)] : array of block ids of the group
138 # params[:list-(top|left|right)] : array of block ids of the group
140 def order_blocks
139 def order_blocks
141 group = params[:group]
140 group = params[:group]
142 group_items = params["list-#{group}"]
141 group_items = params["list-#{group}"]
143 if group_items and group_items.is_a? Array
142 if group_items and group_items.is_a? Array
144 # remove group blocks if they are presents in other groups
143 # remove group blocks if they are presents in other groups
145 %w(top left right).each {|f|
144 %w(top left right).each {|f|
146 session[:page_layout][f] = (session[:page_layout][f] || []) - group_items
145 session[:page_layout][f] = (session[:page_layout][f] || []) - group_items
147 }
146 }
148 session[:page_layout][group] = group_items
147 session[:page_layout][group] = group_items
149 end
148 end
150 render :nothing => true
149 render :nothing => true
151 end
150 end
152
151
153 # Save user's page layout
152 # Save user's page layout
154 def page_layout_save
153 def page_layout_save
155 @user = User.current
154 @user = User.current
156 @user.pref[:my_page_layout] = session[:page_layout] if session[:page_layout]
155 @user.pref[:my_page_layout] = session[:page_layout] if session[:page_layout]
157 @user.pref.save
156 @user.pref.save
158 session[:page_layout] = nil
157 session[:page_layout] = nil
159 redirect_to :action => 'page'
158 redirect_to :action => 'page'
160 end
159 end
161 end
160 end
@@ -1,109 +1,108
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class NewsController < ApplicationController
18 class NewsController < ApplicationController
19 layout 'base'
20 before_filter :find_news, :except => [:new, :index, :preview]
19 before_filter :find_news, :except => [:new, :index, :preview]
21 before_filter :find_project, :only => [:new, :preview]
20 before_filter :find_project, :only => [:new, :preview]
22 before_filter :authorize, :except => [:index, :preview]
21 before_filter :authorize, :except => [:index, :preview]
23 before_filter :find_optional_project, :only => :index
22 before_filter :find_optional_project, :only => :index
24 accept_key_auth :index
23 accept_key_auth :index
25
24
26 def index
25 def index
27 @news_pages, @newss = paginate :news,
26 @news_pages, @newss = paginate :news,
28 :per_page => 10,
27 :per_page => 10,
29 :conditions => (@project ? {:project_id => @project.id} : Project.visible_by(User.current)),
28 :conditions => (@project ? {:project_id => @project.id} : Project.visible_by(User.current)),
30 :include => [:author, :project],
29 :include => [:author, :project],
31 :order => "#{News.table_name}.created_on DESC"
30 :order => "#{News.table_name}.created_on DESC"
32 respond_to do |format|
31 respond_to do |format|
33 format.html { render :layout => false if request.xhr? }
32 format.html { render :layout => false if request.xhr? }
34 format.atom { render_feed(@newss, :title => (@project ? @project.name : Setting.app_title) + ": #{l(:label_news_plural)}") }
33 format.atom { render_feed(@newss, :title => (@project ? @project.name : Setting.app_title) + ": #{l(:label_news_plural)}") }
35 end
34 end
36 end
35 end
37
36
38 def show
37 def show
39 @comments = @news.comments
38 @comments = @news.comments
40 @comments.reverse! if User.current.wants_comments_in_reverse_order?
39 @comments.reverse! if User.current.wants_comments_in_reverse_order?
41 end
40 end
42
41
43 def new
42 def new
44 @news = News.new(:project => @project, :author => User.current)
43 @news = News.new(:project => @project, :author => User.current)
45 if request.post?
44 if request.post?
46 @news.attributes = params[:news]
45 @news.attributes = params[:news]
47 if @news.save
46 if @news.save
48 flash[:notice] = l(:notice_successful_create)
47 flash[:notice] = l(:notice_successful_create)
49 Mailer.deliver_news_added(@news) if Setting.notified_events.include?('news_added')
48 Mailer.deliver_news_added(@news) if Setting.notified_events.include?('news_added')
50 redirect_to :controller => 'news', :action => 'index', :project_id => @project
49 redirect_to :controller => 'news', :action => 'index', :project_id => @project
51 end
50 end
52 end
51 end
53 end
52 end
54
53
55 def edit
54 def edit
56 if request.post? and @news.update_attributes(params[:news])
55 if request.post? and @news.update_attributes(params[:news])
57 flash[:notice] = l(:notice_successful_update)
56 flash[:notice] = l(:notice_successful_update)
58 redirect_to :action => 'show', :id => @news
57 redirect_to :action => 'show', :id => @news
59 end
58 end
60 end
59 end
61
60
62 def add_comment
61 def add_comment
63 @comment = Comment.new(params[:comment])
62 @comment = Comment.new(params[:comment])
64 @comment.author = User.current
63 @comment.author = User.current
65 if @news.comments << @comment
64 if @news.comments << @comment
66 flash[:notice] = l(:label_comment_added)
65 flash[:notice] = l(:label_comment_added)
67 redirect_to :action => 'show', :id => @news
66 redirect_to :action => 'show', :id => @news
68 else
67 else
69 render :action => 'show'
68 render :action => 'show'
70 end
69 end
71 end
70 end
72
71
73 def destroy_comment
72 def destroy_comment
74 @news.comments.find(params[:comment_id]).destroy
73 @news.comments.find(params[:comment_id]).destroy
75 redirect_to :action => 'show', :id => @news
74 redirect_to :action => 'show', :id => @news
76 end
75 end
77
76
78 def destroy
77 def destroy
79 @news.destroy
78 @news.destroy
80 redirect_to :action => 'index', :project_id => @project
79 redirect_to :action => 'index', :project_id => @project
81 end
80 end
82
81
83 def preview
82 def preview
84 @text = (params[:news] ? params[:news][:description] : nil)
83 @text = (params[:news] ? params[:news][:description] : nil)
85 render :partial => 'common/preview'
84 render :partial => 'common/preview'
86 end
85 end
87
86
88 private
87 private
89 def find_news
88 def find_news
90 @news = News.find(params[:id])
89 @news = News.find(params[:id])
91 @project = @news.project
90 @project = @news.project
92 rescue ActiveRecord::RecordNotFound
91 rescue ActiveRecord::RecordNotFound
93 render_404
92 render_404
94 end
93 end
95
94
96 def find_project
95 def find_project
97 @project = Project.find(params[:project_id])
96 @project = Project.find(params[:project_id])
98 rescue ActiveRecord::RecordNotFound
97 rescue ActiveRecord::RecordNotFound
99 render_404
98 render_404
100 end
99 end
101
100
102 def find_optional_project
101 def find_optional_project
103 return true unless params[:project_id]
102 return true unless params[:project_id]
104 @project = Project.find(params[:project_id])
103 @project = Project.find(params[:project_id])
105 authorize
104 authorize
106 rescue ActiveRecord::RecordNotFound
105 rescue ActiveRecord::RecordNotFound
107 render_404
106 render_404
108 end
107 end
109 end
108 end
@@ -1,365 +1,364
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class ProjectsController < ApplicationController
18 class ProjectsController < ApplicationController
19 layout 'base'
20 menu_item :overview
19 menu_item :overview
21 menu_item :activity, :only => :activity
20 menu_item :activity, :only => :activity
22 menu_item :roadmap, :only => :roadmap
21 menu_item :roadmap, :only => :roadmap
23 menu_item :files, :only => [:list_files, :add_file]
22 menu_item :files, :only => [:list_files, :add_file]
24 menu_item :settings, :only => :settings
23 menu_item :settings, :only => :settings
25 menu_item :issues, :only => [:changelog]
24 menu_item :issues, :only => [:changelog]
26
25
27 before_filter :find_project, :except => [ :index, :list, :add, :activity ]
26 before_filter :find_project, :except => [ :index, :list, :add, :activity ]
28 before_filter :find_optional_project, :only => :activity
27 before_filter :find_optional_project, :only => :activity
29 before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy, :activity ]
28 before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy, :activity ]
30 before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
29 before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
31 accept_key_auth :activity, :calendar
30 accept_key_auth :activity, :calendar
32
31
33 helper :sort
32 helper :sort
34 include SortHelper
33 include SortHelper
35 helper :custom_fields
34 helper :custom_fields
36 include CustomFieldsHelper
35 include CustomFieldsHelper
37 helper :ifpdf
36 helper :ifpdf
38 include IfpdfHelper
37 include IfpdfHelper
39 helper :issues
38 helper :issues
40 helper IssuesHelper
39 helper IssuesHelper
41 helper :queries
40 helper :queries
42 include QueriesHelper
41 include QueriesHelper
43 helper :repositories
42 helper :repositories
44 include RepositoriesHelper
43 include RepositoriesHelper
45 include ProjectsHelper
44 include ProjectsHelper
46
45
47 # Lists visible projects
46 # Lists visible projects
48 def index
47 def index
49 projects = Project.find :all,
48 projects = Project.find :all,
50 :conditions => Project.visible_by(User.current),
49 :conditions => Project.visible_by(User.current),
51 :include => :parent
50 :include => :parent
52 respond_to do |format|
51 respond_to do |format|
53 format.html {
52 format.html {
54 @project_tree = projects.group_by {|p| p.parent || p}
53 @project_tree = projects.group_by {|p| p.parent || p}
55 @project_tree.keys.each {|p| @project_tree[p] -= [p]}
54 @project_tree.keys.each {|p| @project_tree[p] -= [p]}
56 }
55 }
57 format.atom {
56 format.atom {
58 render_feed(projects.sort_by(&:created_on).reverse.slice(0, Setting.feeds_limit.to_i),
57 render_feed(projects.sort_by(&:created_on).reverse.slice(0, Setting.feeds_limit.to_i),
59 :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
58 :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
60 }
59 }
61 end
60 end
62 end
61 end
63
62
64 # Add a new project
63 # Add a new project
65 def add
64 def add
66 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
65 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
67 @trackers = Tracker.all
66 @trackers = Tracker.all
68 @root_projects = Project.find(:all,
67 @root_projects = Project.find(:all,
69 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
68 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
70 :order => 'name')
69 :order => 'name')
71 @project = Project.new(params[:project])
70 @project = Project.new(params[:project])
72 if request.get?
71 if request.get?
73 @project.trackers = Tracker.all
72 @project.trackers = Tracker.all
74 @project.is_public = Setting.default_projects_public?
73 @project.is_public = Setting.default_projects_public?
75 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
74 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
76 else
75 else
77 @project.enabled_module_names = params[:enabled_modules]
76 @project.enabled_module_names = params[:enabled_modules]
78 if @project.save
77 if @project.save
79 flash[:notice] = l(:notice_successful_create)
78 flash[:notice] = l(:notice_successful_create)
80 redirect_to :controller => 'admin', :action => 'projects'
79 redirect_to :controller => 'admin', :action => 'projects'
81 end
80 end
82 end
81 end
83 end
82 end
84
83
85 # Show @project
84 # Show @project
86 def show
85 def show
87 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
86 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
88 @subprojects = @project.children.find(:all, :conditions => Project.visible_by(User.current))
87 @subprojects = @project.children.find(:all, :conditions => Project.visible_by(User.current))
89 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
88 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
90 @trackers = @project.rolled_up_trackers
89 @trackers = @project.rolled_up_trackers
91
90
92 cond = @project.project_condition(Setting.display_subprojects_issues?)
91 cond = @project.project_condition(Setting.display_subprojects_issues?)
93 Issue.visible_by(User.current) do
92 Issue.visible_by(User.current) do
94 @open_issues_by_tracker = Issue.count(:group => :tracker,
93 @open_issues_by_tracker = Issue.count(:group => :tracker,
95 :include => [:project, :status, :tracker],
94 :include => [:project, :status, :tracker],
96 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
95 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
97 @total_issues_by_tracker = Issue.count(:group => :tracker,
96 @total_issues_by_tracker = Issue.count(:group => :tracker,
98 :include => [:project, :status, :tracker],
97 :include => [:project, :status, :tracker],
99 :conditions => cond)
98 :conditions => cond)
100 end
99 end
101 TimeEntry.visible_by(User.current) do
100 TimeEntry.visible_by(User.current) do
102 @total_hours = TimeEntry.sum(:hours,
101 @total_hours = TimeEntry.sum(:hours,
103 :include => :project,
102 :include => :project,
104 :conditions => cond).to_f
103 :conditions => cond).to_f
105 end
104 end
106 @key = User.current.rss_key
105 @key = User.current.rss_key
107 end
106 end
108
107
109 def settings
108 def settings
110 @root_projects = Project.find(:all,
109 @root_projects = Project.find(:all,
111 :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id],
110 :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id],
112 :order => 'name')
111 :order => 'name')
113 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
112 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
114 @issue_category ||= IssueCategory.new
113 @issue_category ||= IssueCategory.new
115 @member ||= @project.members.new
114 @member ||= @project.members.new
116 @trackers = Tracker.all
115 @trackers = Tracker.all
117 @repository ||= @project.repository
116 @repository ||= @project.repository
118 @wiki ||= @project.wiki
117 @wiki ||= @project.wiki
119 end
118 end
120
119
121 # Edit @project
120 # Edit @project
122 def edit
121 def edit
123 if request.post?
122 if request.post?
124 @project.attributes = params[:project]
123 @project.attributes = params[:project]
125 if @project.save
124 if @project.save
126 flash[:notice] = l(:notice_successful_update)
125 flash[:notice] = l(:notice_successful_update)
127 redirect_to :action => 'settings', :id => @project
126 redirect_to :action => 'settings', :id => @project
128 else
127 else
129 settings
128 settings
130 render :action => 'settings'
129 render :action => 'settings'
131 end
130 end
132 end
131 end
133 end
132 end
134
133
135 def modules
134 def modules
136 @project.enabled_module_names = params[:enabled_modules]
135 @project.enabled_module_names = params[:enabled_modules]
137 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
136 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
138 end
137 end
139
138
140 def archive
139 def archive
141 @project.archive if request.post? && @project.active?
140 @project.archive if request.post? && @project.active?
142 redirect_to :controller => 'admin', :action => 'projects'
141 redirect_to :controller => 'admin', :action => 'projects'
143 end
142 end
144
143
145 def unarchive
144 def unarchive
146 @project.unarchive if request.post? && !@project.active?
145 @project.unarchive if request.post? && !@project.active?
147 redirect_to :controller => 'admin', :action => 'projects'
146 redirect_to :controller => 'admin', :action => 'projects'
148 end
147 end
149
148
150 # Delete @project
149 # Delete @project
151 def destroy
150 def destroy
152 @project_to_destroy = @project
151 @project_to_destroy = @project
153 if request.post? and params[:confirm]
152 if request.post? and params[:confirm]
154 @project_to_destroy.destroy
153 @project_to_destroy.destroy
155 redirect_to :controller => 'admin', :action => 'projects'
154 redirect_to :controller => 'admin', :action => 'projects'
156 end
155 end
157 # hide project in layout
156 # hide project in layout
158 @project = nil
157 @project = nil
159 end
158 end
160
159
161 # Add a new issue category to @project
160 # Add a new issue category to @project
162 def add_issue_category
161 def add_issue_category
163 @category = @project.issue_categories.build(params[:category])
162 @category = @project.issue_categories.build(params[:category])
164 if request.post? and @category.save
163 if request.post? and @category.save
165 respond_to do |format|
164 respond_to do |format|
166 format.html do
165 format.html do
167 flash[:notice] = l(:notice_successful_create)
166 flash[:notice] = l(:notice_successful_create)
168 redirect_to :action => 'settings', :tab => 'categories', :id => @project
167 redirect_to :action => 'settings', :tab => 'categories', :id => @project
169 end
168 end
170 format.js do
169 format.js do
171 # IE doesn't support the replace_html rjs method for select box options
170 # IE doesn't support the replace_html rjs method for select box options
172 render(:update) {|page| page.replace "issue_category_id",
171 render(:update) {|page| page.replace "issue_category_id",
173 content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
172 content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
174 }
173 }
175 end
174 end
176 end
175 end
177 end
176 end
178 end
177 end
179
178
180 # Add a new version to @project
179 # Add a new version to @project
181 def add_version
180 def add_version
182 @version = @project.versions.build(params[:version])
181 @version = @project.versions.build(params[:version])
183 if request.post? and @version.save
182 if request.post? and @version.save
184 flash[:notice] = l(:notice_successful_create)
183 flash[:notice] = l(:notice_successful_create)
185 redirect_to :action => 'settings', :tab => 'versions', :id => @project
184 redirect_to :action => 'settings', :tab => 'versions', :id => @project
186 end
185 end
187 end
186 end
188
187
189 def add_file
188 def add_file
190 if request.post?
189 if request.post?
191 @version = @project.versions.find_by_id(params[:version_id])
190 @version = @project.versions.find_by_id(params[:version_id])
192 attachments = attach_files(@version, params[:attachments])
191 attachments = attach_files(@version, params[:attachments])
193 Mailer.deliver_attachments_added(attachments) if !attachments.empty? && Setting.notified_events.include?('file_added')
192 Mailer.deliver_attachments_added(attachments) if !attachments.empty? && Setting.notified_events.include?('file_added')
194 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
193 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
195 end
194 end
196 @versions = @project.versions.sort
195 @versions = @project.versions.sort
197 end
196 end
198
197
199 def list_files
198 def list_files
200 sort_init "#{Attachment.table_name}.filename", "asc"
199 sort_init "#{Attachment.table_name}.filename", "asc"
201 sort_update
200 sort_update
202 @versions = @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
201 @versions = @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
203 render :layout => !request.xhr?
202 render :layout => !request.xhr?
204 end
203 end
205
204
206 # Show changelog for @project
205 # Show changelog for @project
207 def changelog
206 def changelog
208 @trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
207 @trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
209 retrieve_selected_tracker_ids(@trackers)
208 retrieve_selected_tracker_ids(@trackers)
210 @versions = @project.versions.sort
209 @versions = @project.versions.sort
211 end
210 end
212
211
213 def roadmap
212 def roadmap
214 @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true])
213 @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true])
215 retrieve_selected_tracker_ids(@trackers)
214 retrieve_selected_tracker_ids(@trackers)
216 @versions = @project.versions.sort
215 @versions = @project.versions.sort
217 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
216 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
218 end
217 end
219
218
220 def activity
219 def activity
221 @days = Setting.activity_days_default.to_i
220 @days = Setting.activity_days_default.to_i
222
221
223 if params[:from]
222 if params[:from]
224 begin; @date_to = params[:from].to_date; rescue; end
223 begin; @date_to = params[:from].to_date; rescue; end
225 end
224 end
226
225
227 @date_to ||= Date.today + 1
226 @date_to ||= Date.today + 1
228 @date_from = @date_to - @days
227 @date_from = @date_to - @days
229 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
228 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
230
229
231 @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project, :with_subprojects => @with_subprojects)
230 @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project, :with_subprojects => @with_subprojects)
232 @activity.scope_select {|t| !params["show_#{t}"].nil?}
231 @activity.scope_select {|t| !params["show_#{t}"].nil?}
233 @activity.default_scope! if @activity.scope.empty?
232 @activity.default_scope! if @activity.scope.empty?
234
233
235 events = @activity.events(@date_from, @date_to)
234 events = @activity.events(@date_from, @date_to)
236
235
237 respond_to do |format|
236 respond_to do |format|
238 format.html {
237 format.html {
239 @events_by_day = events.group_by(&:event_date)
238 @events_by_day = events.group_by(&:event_date)
240 render :layout => false if request.xhr?
239 render :layout => false if request.xhr?
241 }
240 }
242 format.atom {
241 format.atom {
243 title = (@activity.scope.size == 1) ? l("label_#{@activity.scope.first.singularize}_plural") : l(:label_activity)
242 title = (@activity.scope.size == 1) ? l("label_#{@activity.scope.first.singularize}_plural") : l(:label_activity)
244 render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
243 render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
245 }
244 }
246 end
245 end
247 end
246 end
248
247
249 def calendar
248 def calendar
250 @trackers = @project.rolled_up_trackers
249 @trackers = @project.rolled_up_trackers
251 retrieve_selected_tracker_ids(@trackers)
250 retrieve_selected_tracker_ids(@trackers)
252
251
253 if params[:year] and params[:year].to_i > 1900
252 if params[:year] and params[:year].to_i > 1900
254 @year = params[:year].to_i
253 @year = params[:year].to_i
255 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
254 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
256 @month = params[:month].to_i
255 @month = params[:month].to_i
257 end
256 end
258 end
257 end
259 @year ||= Date.today.year
258 @year ||= Date.today.year
260 @month ||= Date.today.month
259 @month ||= Date.today.month
261 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
260 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
262 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
261 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
263 events = []
262 events = []
264 @project.issues_with_subprojects(@with_subprojects) do
263 @project.issues_with_subprojects(@with_subprojects) do
265 events += Issue.find(:all,
264 events += Issue.find(:all,
266 :include => [:tracker, :status, :assigned_to, :priority, :project],
265 :include => [:tracker, :status, :assigned_to, :priority, :project],
267 :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?)) AND #{Issue.table_name}.tracker_id IN (#{@selected_tracker_ids.join(',')})", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
266 :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?)) AND #{Issue.table_name}.tracker_id IN (#{@selected_tracker_ids.join(',')})", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
268 ) unless @selected_tracker_ids.empty?
267 ) unless @selected_tracker_ids.empty?
269 events += Version.find(:all, :include => :project,
268 events += Version.find(:all, :include => :project,
270 :conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
269 :conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
271 end
270 end
272 @calendar.events = events
271 @calendar.events = events
273
272
274 render :layout => false if request.xhr?
273 render :layout => false if request.xhr?
275 end
274 end
276
275
277 def gantt
276 def gantt
278 @trackers = @project.rolled_up_trackers
277 @trackers = @project.rolled_up_trackers
279 retrieve_selected_tracker_ids(@trackers)
278 retrieve_selected_tracker_ids(@trackers)
280
279
281 if params[:year] and params[:year].to_i >0
280 if params[:year] and params[:year].to_i >0
282 @year_from = params[:year].to_i
281 @year_from = params[:year].to_i
283 if params[:month] and params[:month].to_i >=1 and params[:month].to_i <= 12
282 if params[:month] and params[:month].to_i >=1 and params[:month].to_i <= 12
284 @month_from = params[:month].to_i
283 @month_from = params[:month].to_i
285 else
284 else
286 @month_from = 1
285 @month_from = 1
287 end
286 end
288 else
287 else
289 @month_from ||= Date.today.month
288 @month_from ||= Date.today.month
290 @year_from ||= Date.today.year
289 @year_from ||= Date.today.year
291 end
290 end
292
291
293 zoom = (params[:zoom] || User.current.pref[:gantt_zoom]).to_i
292 zoom = (params[:zoom] || User.current.pref[:gantt_zoom]).to_i
294 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
293 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
295 months = (params[:months] || User.current.pref[:gantt_months]).to_i
294 months = (params[:months] || User.current.pref[:gantt_months]).to_i
296 @months = (months > 0 && months < 25) ? months : 6
295 @months = (months > 0 && months < 25) ? months : 6
297
296
298 # Save gantt paramters as user preference (zoom and months count)
297 # Save gantt paramters as user preference (zoom and months count)
299 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || @months != User.current.pref[:gantt_months]))
298 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || @months != User.current.pref[:gantt_months]))
300 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
299 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
301 User.current.preference.save
300 User.current.preference.save
302 end
301 end
303
302
304 @date_from = Date.civil(@year_from, @month_from, 1)
303 @date_from = Date.civil(@year_from, @month_from, 1)
305 @date_to = (@date_from >> @months) - 1
304 @date_to = (@date_from >> @months) - 1
306 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
305 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
307
306
308 @events = []
307 @events = []
309 @project.issues_with_subprojects(@with_subprojects) do
308 @project.issues_with_subprojects(@with_subprojects) do
310 # Issues that have start and due dates
309 # Issues that have start and due dates
311 @events += Issue.find(:all,
310 @events += Issue.find(:all,
312 :order => "start_date, due_date",
311 :order => "start_date, due_date",
313 :include => [:tracker, :status, :assigned_to, :priority, :project],
312 :include => [:tracker, :status, :assigned_to, :priority, :project],
314 :conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}))", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to]
313 :conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}))", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to]
315 ) unless @selected_tracker_ids.empty?
314 ) unless @selected_tracker_ids.empty?
316 # Issues that don't have a due date but that are assigned to a version with a date
315 # Issues that don't have a due date but that are assigned to a version with a date
317 @events += Issue.find(:all,
316 @events += Issue.find(:all,
318 :order => "start_date, effective_date",
317 :order => "start_date, effective_date",
319 :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version],
318 :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version],
320 :conditions => ["(((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}))", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to]
319 :conditions => ["(((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}))", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to]
321 ) unless @selected_tracker_ids.empty?
320 ) unless @selected_tracker_ids.empty?
322 @events += Version.find(:all, :include => :project,
321 @events += Version.find(:all, :include => :project,
323 :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
322 :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
324 end
323 end
325 @events.sort! {|x,y| x.start_date <=> y.start_date }
324 @events.sort! {|x,y| x.start_date <=> y.start_date }
326
325
327 if params[:format]=='pdf'
326 if params[:format]=='pdf'
328 @options_for_rfpdf ||= {}
327 @options_for_rfpdf ||= {}
329 @options_for_rfpdf[:file_name] = "#{@project.identifier}-gantt.pdf"
328 @options_for_rfpdf[:file_name] = "#{@project.identifier}-gantt.pdf"
330 render :template => "projects/gantt.rfpdf", :layout => false
329 render :template => "projects/gantt.rfpdf", :layout => false
331 elsif params[:format]=='png' && respond_to?('gantt_image')
330 elsif params[:format]=='png' && respond_to?('gantt_image')
332 image = gantt_image(@events, @date_from, @months, @zoom)
331 image = gantt_image(@events, @date_from, @months, @zoom)
333 image.format = 'PNG'
332 image.format = 'PNG'
334 send_data(image.to_blob, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png")
333 send_data(image.to_blob, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png")
335 else
334 else
336 render :template => "projects/gantt.rhtml"
335 render :template => "projects/gantt.rhtml"
337 end
336 end
338 end
337 end
339
338
340 private
339 private
341 # Find project of id params[:id]
340 # Find project of id params[:id]
342 # if not found, redirect to project list
341 # if not found, redirect to project list
343 # Used as a before_filter
342 # Used as a before_filter
344 def find_project
343 def find_project
345 @project = Project.find(params[:id])
344 @project = Project.find(params[:id])
346 rescue ActiveRecord::RecordNotFound
345 rescue ActiveRecord::RecordNotFound
347 render_404
346 render_404
348 end
347 end
349
348
350 def find_optional_project
349 def find_optional_project
351 return true unless params[:id]
350 return true unless params[:id]
352 @project = Project.find(params[:id])
351 @project = Project.find(params[:id])
353 authorize
352 authorize
354 rescue ActiveRecord::RecordNotFound
353 rescue ActiveRecord::RecordNotFound
355 render_404
354 render_404
356 end
355 end
357
356
358 def retrieve_selected_tracker_ids(selectable_trackers)
357 def retrieve_selected_tracker_ids(selectable_trackers)
359 if ids = params[:tracker_ids]
358 if ids = params[:tracker_ids]
360 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
359 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
361 else
360 else
362 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
361 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
363 end
362 end
364 end
363 end
365 end
364 end
@@ -1,81 +1,80
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class QueriesController < ApplicationController
18 class QueriesController < ApplicationController
19 layout 'base'
20 menu_item :issues
19 menu_item :issues
21 before_filter :find_query, :except => :new
20 before_filter :find_query, :except => :new
22 before_filter :find_optional_project, :only => :new
21 before_filter :find_optional_project, :only => :new
23
22
24 def new
23 def new
25 @query = Query.new(params[:query])
24 @query = Query.new(params[:query])
26 @query.project = params[:query_is_for_all] ? nil : @project
25 @query.project = params[:query_is_for_all] ? nil : @project
27 @query.user = User.current
26 @query.user = User.current
28 @query.is_public = false unless (@query.project && current_role.allowed_to?(:manage_public_queries)) || User.current.admin?
27 @query.is_public = false unless (@query.project && current_role.allowed_to?(:manage_public_queries)) || User.current.admin?
29 @query.column_names = nil if params[:default_columns]
28 @query.column_names = nil if params[:default_columns]
30
29
31 params[:fields].each do |field|
30 params[:fields].each do |field|
32 @query.add_filter(field, params[:operators][field], params[:values][field])
31 @query.add_filter(field, params[:operators][field], params[:values][field])
33 end if params[:fields]
32 end if params[:fields]
34
33
35 if request.post? && params[:confirm] && @query.save
34 if request.post? && params[:confirm] && @query.save
36 flash[:notice] = l(:notice_successful_create)
35 flash[:notice] = l(:notice_successful_create)
37 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
36 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
38 return
37 return
39 end
38 end
40 render :layout => false if request.xhr?
39 render :layout => false if request.xhr?
41 end
40 end
42
41
43 def edit
42 def edit
44 if request.post?
43 if request.post?
45 @query.filters = {}
44 @query.filters = {}
46 params[:fields].each do |field|
45 params[:fields].each do |field|
47 @query.add_filter(field, params[:operators][field], params[:values][field])
46 @query.add_filter(field, params[:operators][field], params[:values][field])
48 end if params[:fields]
47 end if params[:fields]
49 @query.attributes = params[:query]
48 @query.attributes = params[:query]
50 @query.project = nil if params[:query_is_for_all]
49 @query.project = nil if params[:query_is_for_all]
51 @query.is_public = false unless (@query.project && current_role.allowed_to?(:manage_public_queries)) || User.current.admin?
50 @query.is_public = false unless (@query.project && current_role.allowed_to?(:manage_public_queries)) || User.current.admin?
52 @query.column_names = nil if params[:default_columns]
51 @query.column_names = nil if params[:default_columns]
53
52
54 if @query.save
53 if @query.save
55 flash[:notice] = l(:notice_successful_update)
54 flash[:notice] = l(:notice_successful_update)
56 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
55 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
57 end
56 end
58 end
57 end
59 end
58 end
60
59
61 def destroy
60 def destroy
62 @query.destroy if request.post?
61 @query.destroy if request.post?
63 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1
62 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1
64 end
63 end
65
64
66 private
65 private
67 def find_query
66 def find_query
68 @query = Query.find(params[:id])
67 @query = Query.find(params[:id])
69 @project = @query.project
68 @project = @query.project
70 render_403 unless @query.editable_by?(User.current)
69 render_403 unless @query.editable_by?(User.current)
71 rescue ActiveRecord::RecordNotFound
70 rescue ActiveRecord::RecordNotFound
72 render_404
71 render_404
73 end
72 end
74
73
75 def find_optional_project
74 def find_optional_project
76 @project = Project.find(params[:project_id]) if params[:project_id]
75 @project = Project.find(params[:project_id]) if params[:project_id]
77 User.current.allowed_to?(:save_queries, @project, :global => true)
76 User.current.allowed_to?(:save_queries, @project, :global => true)
78 rescue ActiveRecord::RecordNotFound
77 rescue ActiveRecord::RecordNotFound
79 render_404
78 render_404
80 end
79 end
81 end
80 end
@@ -1,237 +1,236
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class ReportsController < ApplicationController
18 class ReportsController < ApplicationController
19 layout 'base'
20 menu_item :issues
19 menu_item :issues
21 before_filter :find_project, :authorize
20 before_filter :find_project, :authorize
22
21
23 def issue_report
22 def issue_report
24 @statuses = IssueStatus.find(:all, :order => 'position')
23 @statuses = IssueStatus.find(:all, :order => 'position')
25
24
26 case params[:detail]
25 case params[:detail]
27 when "tracker"
26 when "tracker"
28 @field = "tracker_id"
27 @field = "tracker_id"
29 @rows = @project.trackers
28 @rows = @project.trackers
30 @data = issues_by_tracker
29 @data = issues_by_tracker
31 @report_title = l(:field_tracker)
30 @report_title = l(:field_tracker)
32 render :template => "reports/issue_report_details"
31 render :template => "reports/issue_report_details"
33 when "version"
32 when "version"
34 @field = "fixed_version_id"
33 @field = "fixed_version_id"
35 @rows = @project.versions.sort
34 @rows = @project.versions.sort
36 @data = issues_by_version
35 @data = issues_by_version
37 @report_title = l(:field_version)
36 @report_title = l(:field_version)
38 render :template => "reports/issue_report_details"
37 render :template => "reports/issue_report_details"
39 when "priority"
38 when "priority"
40 @field = "priority_id"
39 @field = "priority_id"
41 @rows = Enumeration::get_values('IPRI')
40 @rows = Enumeration::get_values('IPRI')
42 @data = issues_by_priority
41 @data = issues_by_priority
43 @report_title = l(:field_priority)
42 @report_title = l(:field_priority)
44 render :template => "reports/issue_report_details"
43 render :template => "reports/issue_report_details"
45 when "category"
44 when "category"
46 @field = "category_id"
45 @field = "category_id"
47 @rows = @project.issue_categories
46 @rows = @project.issue_categories
48 @data = issues_by_category
47 @data = issues_by_category
49 @report_title = l(:field_category)
48 @report_title = l(:field_category)
50 render :template => "reports/issue_report_details"
49 render :template => "reports/issue_report_details"
51 when "assigned_to"
50 when "assigned_to"
52 @field = "assigned_to_id"
51 @field = "assigned_to_id"
53 @rows = @project.members.collect { |m| m.user }
52 @rows = @project.members.collect { |m| m.user }
54 @data = issues_by_assigned_to
53 @data = issues_by_assigned_to
55 @report_title = l(:field_assigned_to)
54 @report_title = l(:field_assigned_to)
56 render :template => "reports/issue_report_details"
55 render :template => "reports/issue_report_details"
57 when "author"
56 when "author"
58 @field = "author_id"
57 @field = "author_id"
59 @rows = @project.members.collect { |m| m.user }
58 @rows = @project.members.collect { |m| m.user }
60 @data = issues_by_author
59 @data = issues_by_author
61 @report_title = l(:field_author)
60 @report_title = l(:field_author)
62 render :template => "reports/issue_report_details"
61 render :template => "reports/issue_report_details"
63 when "subproject"
62 when "subproject"
64 @field = "project_id"
63 @field = "project_id"
65 @rows = @project.active_children
64 @rows = @project.active_children
66 @data = issues_by_subproject
65 @data = issues_by_subproject
67 @report_title = l(:field_subproject)
66 @report_title = l(:field_subproject)
68 render :template => "reports/issue_report_details"
67 render :template => "reports/issue_report_details"
69 else
68 else
70 @trackers = @project.trackers
69 @trackers = @project.trackers
71 @versions = @project.versions.sort
70 @versions = @project.versions.sort
72 @priorities = Enumeration::get_values('IPRI')
71 @priorities = Enumeration::get_values('IPRI')
73 @categories = @project.issue_categories
72 @categories = @project.issue_categories
74 @assignees = @project.members.collect { |m| m.user }
73 @assignees = @project.members.collect { |m| m.user }
75 @authors = @project.members.collect { |m| m.user }
74 @authors = @project.members.collect { |m| m.user }
76 @subprojects = @project.active_children
75 @subprojects = @project.active_children
77 issues_by_tracker
76 issues_by_tracker
78 issues_by_version
77 issues_by_version
79 issues_by_priority
78 issues_by_priority
80 issues_by_category
79 issues_by_category
81 issues_by_assigned_to
80 issues_by_assigned_to
82 issues_by_author
81 issues_by_author
83 issues_by_subproject
82 issues_by_subproject
84
83
85 render :template => "reports/issue_report"
84 render :template => "reports/issue_report"
86 end
85 end
87 end
86 end
88
87
89 def delays
88 def delays
90 @trackers = Tracker.find(:all)
89 @trackers = Tracker.find(:all)
91 if request.get?
90 if request.get?
92 @selected_tracker_ids = @trackers.collect {|t| t.id.to_s }
91 @selected_tracker_ids = @trackers.collect {|t| t.id.to_s }
93 else
92 else
94 @selected_tracker_ids = params[:tracker_ids].collect { |id| id.to_i.to_s } if params[:tracker_ids] and params[:tracker_ids].is_a? Array
93 @selected_tracker_ids = params[:tracker_ids].collect { |id| id.to_i.to_s } if params[:tracker_ids] and params[:tracker_ids].is_a? Array
95 end
94 end
96 @selected_tracker_ids ||= []
95 @selected_tracker_ids ||= []
97 @raw =
96 @raw =
98 ActiveRecord::Base.connection.select_all("SELECT datediff( a.created_on, b.created_on ) as delay, count(a.id) as total
97 ActiveRecord::Base.connection.select_all("SELECT datediff( a.created_on, b.created_on ) as delay, count(a.id) as total
99 FROM issue_histories a, issue_histories b, issues i
98 FROM issue_histories a, issue_histories b, issues i
100 WHERE a.status_id =5
99 WHERE a.status_id =5
101 AND a.issue_id = b.issue_id
100 AND a.issue_id = b.issue_id
102 AND a.issue_id = i.id
101 AND a.issue_id = i.id
103 AND i.tracker_id in (#{@selected_tracker_ids.join(',')})
102 AND i.tracker_id in (#{@selected_tracker_ids.join(',')})
104 AND b.id = (
103 AND b.id = (
105 SELECT min( c.id )
104 SELECT min( c.id )
106 FROM issue_histories c
105 FROM issue_histories c
107 WHERE b.issue_id = c.issue_id )
106 WHERE b.issue_id = c.issue_id )
108 GROUP BY delay") unless @selected_tracker_ids.empty?
107 GROUP BY delay") unless @selected_tracker_ids.empty?
109 @raw ||=[]
108 @raw ||=[]
110
109
111 @x_from = 0
110 @x_from = 0
112 @x_to = 0
111 @x_to = 0
113 @y_from = 0
112 @y_from = 0
114 @y_to = 0
113 @y_to = 0
115 @sum_total = 0
114 @sum_total = 0
116 @sum_delay = 0
115 @sum_delay = 0
117 @raw.each do |r|
116 @raw.each do |r|
118 @x_to = [r['delay'].to_i, @x_to].max
117 @x_to = [r['delay'].to_i, @x_to].max
119 @y_to = [r['total'].to_i, @y_to].max
118 @y_to = [r['total'].to_i, @y_to].max
120 @sum_total = @sum_total + r['total'].to_i
119 @sum_total = @sum_total + r['total'].to_i
121 @sum_delay = @sum_delay + r['total'].to_i * r['delay'].to_i
120 @sum_delay = @sum_delay + r['total'].to_i * r['delay'].to_i
122 end
121 end
123 end
122 end
124
123
125 private
124 private
126 # Find project of id params[:id]
125 # Find project of id params[:id]
127 def find_project
126 def find_project
128 @project = Project.find(params[:id])
127 @project = Project.find(params[:id])
129 rescue ActiveRecord::RecordNotFound
128 rescue ActiveRecord::RecordNotFound
130 render_404
129 render_404
131 end
130 end
132
131
133 def issues_by_tracker
132 def issues_by_tracker
134 @issues_by_tracker ||=
133 @issues_by_tracker ||=
135 ActiveRecord::Base.connection.select_all("select s.id as status_id,
134 ActiveRecord::Base.connection.select_all("select s.id as status_id,
136 s.is_closed as closed,
135 s.is_closed as closed,
137 t.id as tracker_id,
136 t.id as tracker_id,
138 count(i.id) as total
137 count(i.id) as total
139 from
138 from
140 #{Issue.table_name} i, #{IssueStatus.table_name} s, #{Tracker.table_name} t
139 #{Issue.table_name} i, #{IssueStatus.table_name} s, #{Tracker.table_name} t
141 where
140 where
142 i.status_id=s.id
141 i.status_id=s.id
143 and i.tracker_id=t.id
142 and i.tracker_id=t.id
144 and i.project_id=#{@project.id}
143 and i.project_id=#{@project.id}
145 group by s.id, s.is_closed, t.id")
144 group by s.id, s.is_closed, t.id")
146 end
145 end
147
146
148 def issues_by_version
147 def issues_by_version
149 @issues_by_version ||=
148 @issues_by_version ||=
150 ActiveRecord::Base.connection.select_all("select s.id as status_id,
149 ActiveRecord::Base.connection.select_all("select s.id as status_id,
151 s.is_closed as closed,
150 s.is_closed as closed,
152 v.id as fixed_version_id,
151 v.id as fixed_version_id,
153 count(i.id) as total
152 count(i.id) as total
154 from
153 from
155 #{Issue.table_name} i, #{IssueStatus.table_name} s, #{Version.table_name} v
154 #{Issue.table_name} i, #{IssueStatus.table_name} s, #{Version.table_name} v
156 where
155 where
157 i.status_id=s.id
156 i.status_id=s.id
158 and i.fixed_version_id=v.id
157 and i.fixed_version_id=v.id
159 and i.project_id=#{@project.id}
158 and i.project_id=#{@project.id}
160 group by s.id, s.is_closed, v.id")
159 group by s.id, s.is_closed, v.id")
161 end
160 end
162
161
163 def issues_by_priority
162 def issues_by_priority
164 @issues_by_priority ||=
163 @issues_by_priority ||=
165 ActiveRecord::Base.connection.select_all("select s.id as status_id,
164 ActiveRecord::Base.connection.select_all("select s.id as status_id,
166 s.is_closed as closed,
165 s.is_closed as closed,
167 p.id as priority_id,
166 p.id as priority_id,
168 count(i.id) as total
167 count(i.id) as total
169 from
168 from
170 #{Issue.table_name} i, #{IssueStatus.table_name} s, #{Enumeration.table_name} p
169 #{Issue.table_name} i, #{IssueStatus.table_name} s, #{Enumeration.table_name} p
171 where
170 where
172 i.status_id=s.id
171 i.status_id=s.id
173 and i.priority_id=p.id
172 and i.priority_id=p.id
174 and i.project_id=#{@project.id}
173 and i.project_id=#{@project.id}
175 group by s.id, s.is_closed, p.id")
174 group by s.id, s.is_closed, p.id")
176 end
175 end
177
176
178 def issues_by_category
177 def issues_by_category
179 @issues_by_category ||=
178 @issues_by_category ||=
180 ActiveRecord::Base.connection.select_all("select s.id as status_id,
179 ActiveRecord::Base.connection.select_all("select s.id as status_id,
181 s.is_closed as closed,
180 s.is_closed as closed,
182 c.id as category_id,
181 c.id as category_id,
183 count(i.id) as total
182 count(i.id) as total
184 from
183 from
185 #{Issue.table_name} i, #{IssueStatus.table_name} s, #{IssueCategory.table_name} c
184 #{Issue.table_name} i, #{IssueStatus.table_name} s, #{IssueCategory.table_name} c
186 where
185 where
187 i.status_id=s.id
186 i.status_id=s.id
188 and i.category_id=c.id
187 and i.category_id=c.id
189 and i.project_id=#{@project.id}
188 and i.project_id=#{@project.id}
190 group by s.id, s.is_closed, c.id")
189 group by s.id, s.is_closed, c.id")
191 end
190 end
192
191
193 def issues_by_assigned_to
192 def issues_by_assigned_to
194 @issues_by_assigned_to ||=
193 @issues_by_assigned_to ||=
195 ActiveRecord::Base.connection.select_all("select s.id as status_id,
194 ActiveRecord::Base.connection.select_all("select s.id as status_id,
196 s.is_closed as closed,
195 s.is_closed as closed,
197 a.id as assigned_to_id,
196 a.id as assigned_to_id,
198 count(i.id) as total
197 count(i.id) as total
199 from
198 from
200 #{Issue.table_name} i, #{IssueStatus.table_name} s, #{User.table_name} a
199 #{Issue.table_name} i, #{IssueStatus.table_name} s, #{User.table_name} a
201 where
200 where
202 i.status_id=s.id
201 i.status_id=s.id
203 and i.assigned_to_id=a.id
202 and i.assigned_to_id=a.id
204 and i.project_id=#{@project.id}
203 and i.project_id=#{@project.id}
205 group by s.id, s.is_closed, a.id")
204 group by s.id, s.is_closed, a.id")
206 end
205 end
207
206
208 def issues_by_author
207 def issues_by_author
209 @issues_by_author ||=
208 @issues_by_author ||=
210 ActiveRecord::Base.connection.select_all("select s.id as status_id,
209 ActiveRecord::Base.connection.select_all("select s.id as status_id,
211 s.is_closed as closed,
210 s.is_closed as closed,
212 a.id as author_id,
211 a.id as author_id,
213 count(i.id) as total
212 count(i.id) as total
214 from
213 from
215 #{Issue.table_name} i, #{IssueStatus.table_name} s, #{User.table_name} a
214 #{Issue.table_name} i, #{IssueStatus.table_name} s, #{User.table_name} a
216 where
215 where
217 i.status_id=s.id
216 i.status_id=s.id
218 and i.author_id=a.id
217 and i.author_id=a.id
219 and i.project_id=#{@project.id}
218 and i.project_id=#{@project.id}
220 group by s.id, s.is_closed, a.id")
219 group by s.id, s.is_closed, a.id")
221 end
220 end
222
221
223 def issues_by_subproject
222 def issues_by_subproject
224 @issues_by_subproject ||=
223 @issues_by_subproject ||=
225 ActiveRecord::Base.connection.select_all("select s.id as status_id,
224 ActiveRecord::Base.connection.select_all("select s.id as status_id,
226 s.is_closed as closed,
225 s.is_closed as closed,
227 i.project_id as project_id,
226 i.project_id as project_id,
228 count(i.id) as total
227 count(i.id) as total
229 from
228 from
230 #{Issue.table_name} i, #{IssueStatus.table_name} s
229 #{Issue.table_name} i, #{IssueStatus.table_name} s
231 where
230 where
232 i.status_id=s.id
231 i.status_id=s.id
233 and i.project_id IN (#{@project.active_children.collect{|p| p.id}.join(',')})
232 and i.project_id IN (#{@project.active_children.collect{|p| p.id}.join(',')})
234 group by s.id, s.is_closed, i.project_id") if @project.active_children.any?
233 group by s.id, s.is_closed, i.project_id") if @project.active_children.any?
235 @issues_by_subproject ||= []
234 @issues_by_subproject ||= []
236 end
235 end
237 end
236 end
@@ -1,315 +1,314
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'SVG/Graph/Bar'
18 require 'SVG/Graph/Bar'
19 require 'SVG/Graph/BarHorizontal'
19 require 'SVG/Graph/BarHorizontal'
20 require 'digest/sha1'
20 require 'digest/sha1'
21
21
22 class ChangesetNotFound < Exception; end
22 class ChangesetNotFound < Exception; end
23 class InvalidRevisionParam < Exception; end
23 class InvalidRevisionParam < Exception; end
24
24
25 class RepositoriesController < ApplicationController
25 class RepositoriesController < ApplicationController
26 layout 'base'
27 menu_item :repository
26 menu_item :repository
28 before_filter :find_repository, :except => :edit
27 before_filter :find_repository, :except => :edit
29 before_filter :find_project, :only => :edit
28 before_filter :find_project, :only => :edit
30 before_filter :authorize
29 before_filter :authorize
31 accept_key_auth :revisions
30 accept_key_auth :revisions
32
31
33 rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
32 rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
34
33
35 def edit
34 def edit
36 @repository = @project.repository
35 @repository = @project.repository
37 if !@repository
36 if !@repository
38 @repository = Repository.factory(params[:repository_scm])
37 @repository = Repository.factory(params[:repository_scm])
39 @repository.project = @project if @repository
38 @repository.project = @project if @repository
40 end
39 end
41 if request.post? && @repository
40 if request.post? && @repository
42 @repository.attributes = params[:repository]
41 @repository.attributes = params[:repository]
43 @repository.save
42 @repository.save
44 end
43 end
45 render(:update) {|page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'}
44 render(:update) {|page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'}
46 end
45 end
47
46
48 def destroy
47 def destroy
49 @repository.destroy
48 @repository.destroy
50 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
49 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
51 end
50 end
52
51
53 def show
52 def show
54 # check if new revisions have been committed in the repository
53 # check if new revisions have been committed in the repository
55 @repository.fetch_changesets if Setting.autofetch_changesets?
54 @repository.fetch_changesets if Setting.autofetch_changesets?
56 # root entries
55 # root entries
57 @entries = @repository.entries('', @rev)
56 @entries = @repository.entries('', @rev)
58 # latest changesets
57 # latest changesets
59 @changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC")
58 @changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC")
60 show_error_not_found unless @entries || @changesets.any?
59 show_error_not_found unless @entries || @changesets.any?
61 end
60 end
62
61
63 def browse
62 def browse
64 @entries = @repository.entries(@path, @rev)
63 @entries = @repository.entries(@path, @rev)
65 if request.xhr?
64 if request.xhr?
66 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
65 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
67 else
66 else
68 show_error_not_found and return unless @entries
67 show_error_not_found and return unless @entries
69 @properties = @repository.properties(@path, @rev)
68 @properties = @repository.properties(@path, @rev)
70 render :action => 'browse'
69 render :action => 'browse'
71 end
70 end
72 end
71 end
73
72
74 def changes
73 def changes
75 @entry = @repository.entry(@path, @rev)
74 @entry = @repository.entry(@path, @rev)
76 show_error_not_found and return unless @entry
75 show_error_not_found and return unless @entry
77 @changesets = @repository.changesets_for_path(@path)
76 @changesets = @repository.changesets_for_path(@path)
78 @properties = @repository.properties(@path, @rev)
77 @properties = @repository.properties(@path, @rev)
79 end
78 end
80
79
81 def revisions
80 def revisions
82 @changeset_count = @repository.changesets.count
81 @changeset_count = @repository.changesets.count
83 @changeset_pages = Paginator.new self, @changeset_count,
82 @changeset_pages = Paginator.new self, @changeset_count,
84 per_page_option,
83 per_page_option,
85 params['page']
84 params['page']
86 @changesets = @repository.changesets.find(:all,
85 @changesets = @repository.changesets.find(:all,
87 :limit => @changeset_pages.items_per_page,
86 :limit => @changeset_pages.items_per_page,
88 :offset => @changeset_pages.current.offset)
87 :offset => @changeset_pages.current.offset)
89
88
90 respond_to do |format|
89 respond_to do |format|
91 format.html { render :layout => false if request.xhr? }
90 format.html { render :layout => false if request.xhr? }
92 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
91 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
93 end
92 end
94 end
93 end
95
94
96 def entry
95 def entry
97 @entry = @repository.entry(@path, @rev)
96 @entry = @repository.entry(@path, @rev)
98 show_error_not_found and return unless @entry
97 show_error_not_found and return unless @entry
99
98
100 # If the entry is a dir, show the browser
99 # If the entry is a dir, show the browser
101 browse and return if @entry.is_dir?
100 browse and return if @entry.is_dir?
102
101
103 @content = @repository.cat(@path, @rev)
102 @content = @repository.cat(@path, @rev)
104 show_error_not_found and return unless @content
103 show_error_not_found and return unless @content
105 if 'raw' == params[:format] || @content.is_binary_data?
104 if 'raw' == params[:format] || @content.is_binary_data?
106 # Force the download if it's a binary file
105 # Force the download if it's a binary file
107 send_data @content, :filename => @path.split('/').last
106 send_data @content, :filename => @path.split('/').last
108 else
107 else
109 # Prevent empty lines when displaying a file with Windows style eol
108 # Prevent empty lines when displaying a file with Windows style eol
110 @content.gsub!("\r\n", "\n")
109 @content.gsub!("\r\n", "\n")
111 end
110 end
112 end
111 end
113
112
114 def annotate
113 def annotate
115 @annotate = @repository.scm.annotate(@path, @rev)
114 @annotate = @repository.scm.annotate(@path, @rev)
116 render_error l(:error_scm_annotate) and return if @annotate.nil? || @annotate.empty?
115 render_error l(:error_scm_annotate) and return if @annotate.nil? || @annotate.empty?
117 end
116 end
118
117
119 def revision
118 def revision
120 @changeset = @repository.changesets.find_by_revision(@rev)
119 @changeset = @repository.changesets.find_by_revision(@rev)
121 raise ChangesetNotFound unless @changeset
120 raise ChangesetNotFound unless @changeset
122 @changes_count = @changeset.changes.size
121 @changes_count = @changeset.changes.size
123 @changes_pages = Paginator.new self, @changes_count, 150, params['page']
122 @changes_pages = Paginator.new self, @changes_count, 150, params['page']
124 @changes = @changeset.changes.find(:all,
123 @changes = @changeset.changes.find(:all,
125 :limit => @changes_pages.items_per_page,
124 :limit => @changes_pages.items_per_page,
126 :offset => @changes_pages.current.offset)
125 :offset => @changes_pages.current.offset)
127
126
128 respond_to do |format|
127 respond_to do |format|
129 format.html
128 format.html
130 format.js {render :layout => false}
129 format.js {render :layout => false}
131 end
130 end
132 rescue ChangesetNotFound
131 rescue ChangesetNotFound
133 show_error_not_found
132 show_error_not_found
134 end
133 end
135
134
136 def diff
135 def diff
137 if params[:format] == 'diff'
136 if params[:format] == 'diff'
138 @diff = @repository.diff(@path, @rev, @rev_to)
137 @diff = @repository.diff(@path, @rev, @rev_to)
139 show_error_not_found and return unless @diff
138 show_error_not_found and return unless @diff
140 filename = "changeset_r#{@rev}"
139 filename = "changeset_r#{@rev}"
141 filename << "_r#{@rev_to}" if @rev_to
140 filename << "_r#{@rev_to}" if @rev_to
142 send_data @diff.join, :filename => "#{filename}.diff",
141 send_data @diff.join, :filename => "#{filename}.diff",
143 :type => 'text/x-patch',
142 :type => 'text/x-patch',
144 :disposition => 'attachment'
143 :disposition => 'attachment'
145 else
144 else
146 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
145 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
147 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
146 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
148
147
149 # Save diff type as user preference
148 # Save diff type as user preference
150 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
149 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
151 User.current.pref[:diff_type] = @diff_type
150 User.current.pref[:diff_type] = @diff_type
152 User.current.preference.save
151 User.current.preference.save
153 end
152 end
154
153
155 @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
154 @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
156 unless read_fragment(@cache_key)
155 unless read_fragment(@cache_key)
157 @diff = @repository.diff(@path, @rev, @rev_to)
156 @diff = @repository.diff(@path, @rev, @rev_to)
158 show_error_not_found unless @diff
157 show_error_not_found unless @diff
159 end
158 end
160 end
159 end
161 end
160 end
162
161
163 def stats
162 def stats
164 end
163 end
165
164
166 def graph
165 def graph
167 data = nil
166 data = nil
168 case params[:graph]
167 case params[:graph]
169 when "commits_per_month"
168 when "commits_per_month"
170 data = graph_commits_per_month(@repository)
169 data = graph_commits_per_month(@repository)
171 when "commits_per_author"
170 when "commits_per_author"
172 data = graph_commits_per_author(@repository)
171 data = graph_commits_per_author(@repository)
173 end
172 end
174 if data
173 if data
175 headers["Content-Type"] = "image/svg+xml"
174 headers["Content-Type"] = "image/svg+xml"
176 send_data(data, :type => "image/svg+xml", :disposition => "inline")
175 send_data(data, :type => "image/svg+xml", :disposition => "inline")
177 else
176 else
178 render_404
177 render_404
179 end
178 end
180 end
179 end
181
180
182 private
181 private
183 def find_project
182 def find_project
184 @project = Project.find(params[:id])
183 @project = Project.find(params[:id])
185 rescue ActiveRecord::RecordNotFound
184 rescue ActiveRecord::RecordNotFound
186 render_404
185 render_404
187 end
186 end
188
187
189 REV_PARAM_RE = %r{^[a-f0-9]*$}
188 REV_PARAM_RE = %r{^[a-f0-9]*$}
190
189
191 def find_repository
190 def find_repository
192 @project = Project.find(params[:id])
191 @project = Project.find(params[:id])
193 @repository = @project.repository
192 @repository = @project.repository
194 render_404 and return false unless @repository
193 render_404 and return false unless @repository
195 @path = params[:path].join('/') unless params[:path].nil?
194 @path = params[:path].join('/') unless params[:path].nil?
196 @path ||= ''
195 @path ||= ''
197 @rev = params[:rev]
196 @rev = params[:rev]
198 @rev_to = params[:rev_to]
197 @rev_to = params[:rev_to]
199 raise InvalidRevisionParam unless @rev.to_s.match(REV_PARAM_RE) && @rev.to_s.match(REV_PARAM_RE)
198 raise InvalidRevisionParam unless @rev.to_s.match(REV_PARAM_RE) && @rev.to_s.match(REV_PARAM_RE)
200 rescue ActiveRecord::RecordNotFound
199 rescue ActiveRecord::RecordNotFound
201 render_404
200 render_404
202 rescue InvalidRevisionParam
201 rescue InvalidRevisionParam
203 show_error_not_found
202 show_error_not_found
204 end
203 end
205
204
206 def show_error_not_found
205 def show_error_not_found
207 render_error l(:error_scm_not_found)
206 render_error l(:error_scm_not_found)
208 end
207 end
209
208
210 # Handler for Redmine::Scm::Adapters::CommandFailed exception
209 # Handler for Redmine::Scm::Adapters::CommandFailed exception
211 def show_error_command_failed(exception)
210 def show_error_command_failed(exception)
212 render_error l(:error_scm_command_failed, exception.message)
211 render_error l(:error_scm_command_failed, exception.message)
213 end
212 end
214
213
215 def graph_commits_per_month(repository)
214 def graph_commits_per_month(repository)
216 @date_to = Date.today
215 @date_to = Date.today
217 @date_from = @date_to << 11
216 @date_from = @date_to << 11
218 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
217 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
219 commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
218 commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
220 commits_by_month = [0] * 12
219 commits_by_month = [0] * 12
221 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
220 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
222
221
223 changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
222 changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
224 changes_by_month = [0] * 12
223 changes_by_month = [0] * 12
225 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
224 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
226
225
227 fields = []
226 fields = []
228 month_names = l(:actionview_datehelper_select_month_names_abbr).split(',')
227 month_names = l(:actionview_datehelper_select_month_names_abbr).split(',')
229 12.times {|m| fields << month_names[((Date.today.month - 1 - m) % 12)]}
228 12.times {|m| fields << month_names[((Date.today.month - 1 - m) % 12)]}
230
229
231 graph = SVG::Graph::Bar.new(
230 graph = SVG::Graph::Bar.new(
232 :height => 300,
231 :height => 300,
233 :width => 800,
232 :width => 800,
234 :fields => fields.reverse,
233 :fields => fields.reverse,
235 :stack => :side,
234 :stack => :side,
236 :scale_integers => true,
235 :scale_integers => true,
237 :step_x_labels => 2,
236 :step_x_labels => 2,
238 :show_data_values => false,
237 :show_data_values => false,
239 :graph_title => l(:label_commits_per_month),
238 :graph_title => l(:label_commits_per_month),
240 :show_graph_title => true
239 :show_graph_title => true
241 )
240 )
242
241
243 graph.add_data(
242 graph.add_data(
244 :data => commits_by_month[0..11].reverse,
243 :data => commits_by_month[0..11].reverse,
245 :title => l(:label_revision_plural)
244 :title => l(:label_revision_plural)
246 )
245 )
247
246
248 graph.add_data(
247 graph.add_data(
249 :data => changes_by_month[0..11].reverse,
248 :data => changes_by_month[0..11].reverse,
250 :title => l(:label_change_plural)
249 :title => l(:label_change_plural)
251 )
250 )
252
251
253 graph.burn
252 graph.burn
254 end
253 end
255
254
256 def graph_commits_per_author(repository)
255 def graph_commits_per_author(repository)
257 commits_by_author = repository.changesets.count(:all, :group => :committer)
256 commits_by_author = repository.changesets.count(:all, :group => :committer)
258 commits_by_author.sort! {|x, y| x.last <=> y.last}
257 commits_by_author.sort! {|x, y| x.last <=> y.last}
259
258
260 changes_by_author = repository.changes.count(:all, :group => :committer)
259 changes_by_author = repository.changes.count(:all, :group => :committer)
261 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
260 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
262
261
263 fields = commits_by_author.collect {|r| r.first}
262 fields = commits_by_author.collect {|r| r.first}
264 commits_data = commits_by_author.collect {|r| r.last}
263 commits_data = commits_by_author.collect {|r| r.last}
265 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
264 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
266
265
267 fields = fields + [""]*(10 - fields.length) if fields.length<10
266 fields = fields + [""]*(10 - fields.length) if fields.length<10
268 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
267 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
269 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
268 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
270
269
271 # Remove email adress in usernames
270 # Remove email adress in usernames
272 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
271 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
273
272
274 graph = SVG::Graph::BarHorizontal.new(
273 graph = SVG::Graph::BarHorizontal.new(
275 :height => 400,
274 :height => 400,
276 :width => 800,
275 :width => 800,
277 :fields => fields,
276 :fields => fields,
278 :stack => :side,
277 :stack => :side,
279 :scale_integers => true,
278 :scale_integers => true,
280 :show_data_values => false,
279 :show_data_values => false,
281 :rotate_y_labels => false,
280 :rotate_y_labels => false,
282 :graph_title => l(:label_commits_per_author),
281 :graph_title => l(:label_commits_per_author),
283 :show_graph_title => true
282 :show_graph_title => true
284 )
283 )
285
284
286 graph.add_data(
285 graph.add_data(
287 :data => commits_data,
286 :data => commits_data,
288 :title => l(:label_revision_plural)
287 :title => l(:label_revision_plural)
289 )
288 )
290
289
291 graph.add_data(
290 graph.add_data(
292 :data => changes_data,
291 :data => changes_data,
293 :title => l(:label_change_plural)
292 :title => l(:label_change_plural)
294 )
293 )
295
294
296 graph.burn
295 graph.burn
297 end
296 end
298
297
299 end
298 end
300
299
301 class Date
300 class Date
302 def months_ago(date = Date.today)
301 def months_ago(date = Date.today)
303 (date.year - self.year)*12 + (date.month - self.month)
302 (date.year - self.year)*12 + (date.month - self.month)
304 end
303 end
305
304
306 def weeks_ago(date = Date.today)
305 def weeks_ago(date = Date.today)
307 (date.year - self.year)*52 + (date.cweek - self.cweek)
306 (date.year - self.year)*52 + (date.cweek - self.cweek)
308 end
307 end
309 end
308 end
310
309
311 class String
310 class String
312 def with_leading_slash
311 def with_leading_slash
313 starts_with?('/') ? self : "/#{self}"
312 starts_with?('/') ? self : "/#{self}"
314 end
313 end
315 end
314 end
@@ -1,116 +1,115
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class RolesController < ApplicationController
18 class RolesController < ApplicationController
19 layout 'base'
20 before_filter :require_admin
19 before_filter :require_admin
21
20
22 verify :method => :post, :only => [ :destroy, :move ],
21 verify :method => :post, :only => [ :destroy, :move ],
23 :redirect_to => { :action => :list }
22 :redirect_to => { :action => :list }
24
23
25 def index
24 def index
26 list
25 list
27 render :action => 'list' unless request.xhr?
26 render :action => 'list' unless request.xhr?
28 end
27 end
29
28
30 def list
29 def list
31 @role_pages, @roles = paginate :roles, :per_page => 25, :order => 'builtin, position'
30 @role_pages, @roles = paginate :roles, :per_page => 25, :order => 'builtin, position'
32 render :action => "list", :layout => false if request.xhr?
31 render :action => "list", :layout => false if request.xhr?
33 end
32 end
34
33
35 def new
34 def new
36 # Prefills the form with 'Non member' role permissions
35 # Prefills the form with 'Non member' role permissions
37 @role = Role.new(params[:role] || {:permissions => Role.non_member.permissions})
36 @role = Role.new(params[:role] || {:permissions => Role.non_member.permissions})
38 if request.post? && @role.save
37 if request.post? && @role.save
39 # workflow copy
38 # workflow copy
40 if !params[:copy_workflow_from].blank? && (copy_from = Role.find_by_id(params[:copy_workflow_from]))
39 if !params[:copy_workflow_from].blank? && (copy_from = Role.find_by_id(params[:copy_workflow_from]))
41 @role.workflows.copy(copy_from)
40 @role.workflows.copy(copy_from)
42 end
41 end
43 flash[:notice] = l(:notice_successful_create)
42 flash[:notice] = l(:notice_successful_create)
44 redirect_to :action => 'list'
43 redirect_to :action => 'list'
45 end
44 end
46 @permissions = @role.setable_permissions
45 @permissions = @role.setable_permissions
47 @roles = Role.find :all, :order => 'builtin, position'
46 @roles = Role.find :all, :order => 'builtin, position'
48 end
47 end
49
48
50 def edit
49 def edit
51 @role = Role.find(params[:id])
50 @role = Role.find(params[:id])
52 if request.post? and @role.update_attributes(params[:role])
51 if request.post? and @role.update_attributes(params[:role])
53 flash[:notice] = l(:notice_successful_update)
52 flash[:notice] = l(:notice_successful_update)
54 redirect_to :action => 'list'
53 redirect_to :action => 'list'
55 end
54 end
56 @permissions = @role.setable_permissions
55 @permissions = @role.setable_permissions
57 end
56 end
58
57
59 def destroy
58 def destroy
60 @role = Role.find(params[:id])
59 @role = Role.find(params[:id])
61 @role.destroy
60 @role.destroy
62 redirect_to :action => 'list'
61 redirect_to :action => 'list'
63 rescue
62 rescue
64 flash[:error] = 'This role is in use and can not be deleted.'
63 flash[:error] = 'This role is in use and can not be deleted.'
65 redirect_to :action => 'index'
64 redirect_to :action => 'index'
66 end
65 end
67
66
68 def move
67 def move
69 @role = Role.find(params[:id])
68 @role = Role.find(params[:id])
70 case params[:position]
69 case params[:position]
71 when 'highest'
70 when 'highest'
72 @role.move_to_top
71 @role.move_to_top
73 when 'higher'
72 when 'higher'
74 @role.move_higher
73 @role.move_higher
75 when 'lower'
74 when 'lower'
76 @role.move_lower
75 @role.move_lower
77 when 'lowest'
76 when 'lowest'
78 @role.move_to_bottom
77 @role.move_to_bottom
79 end if params[:position]
78 end if params[:position]
80 redirect_to :action => 'list'
79 redirect_to :action => 'list'
81 end
80 end
82
81
83 def workflow
82 def workflow
84 @role = Role.find_by_id(params[:role_id])
83 @role = Role.find_by_id(params[:role_id])
85 @tracker = Tracker.find_by_id(params[:tracker_id])
84 @tracker = Tracker.find_by_id(params[:tracker_id])
86
85
87 if request.post?
86 if request.post?
88 Workflow.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id])
87 Workflow.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id])
89 (params[:issue_status] || []).each { |old, news|
88 (params[:issue_status] || []).each { |old, news|
90 news.each { |new|
89 news.each { |new|
91 @role.workflows.build(:tracker_id => @tracker.id, :old_status_id => old, :new_status_id => new)
90 @role.workflows.build(:tracker_id => @tracker.id, :old_status_id => old, :new_status_id => new)
92 }
91 }
93 }
92 }
94 if @role.save
93 if @role.save
95 flash[:notice] = l(:notice_successful_update)
94 flash[:notice] = l(:notice_successful_update)
96 redirect_to :action => 'workflow', :role_id => @role, :tracker_id => @tracker
95 redirect_to :action => 'workflow', :role_id => @role, :tracker_id => @tracker
97 end
96 end
98 end
97 end
99 @roles = Role.find(:all, :order => 'builtin, position')
98 @roles = Role.find(:all, :order => 'builtin, position')
100 @trackers = Tracker.find(:all, :order => 'position')
99 @trackers = Tracker.find(:all, :order => 'position')
101 @statuses = IssueStatus.find(:all, :order => 'position')
100 @statuses = IssueStatus.find(:all, :order => 'position')
102 end
101 end
103
102
104 def report
103 def report
105 @roles = Role.find(:all, :order => 'builtin, position')
104 @roles = Role.find(:all, :order => 'builtin, position')
106 @permissions = Redmine::AccessControl.permissions.select { |p| !p.public? }
105 @permissions = Redmine::AccessControl.permissions.select { |p| !p.public? }
107 if request.post?
106 if request.post?
108 @roles.each do |role|
107 @roles.each do |role|
109 role.permissions = params[:permissions][role.id.to_s]
108 role.permissions = params[:permissions][role.id.to_s]
110 role.save
109 role.save
111 end
110 end
112 flash[:notice] = l(:notice_successful_update)
111 flash[:notice] = l(:notice_successful_update)
113 redirect_to :action => 'list'
112 redirect_to :action => 'list'
114 end
113 end
115 end
114 end
116 end
115 end
@@ -1,118 +1,116
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class SearchController < ApplicationController
18 class SearchController < ApplicationController
19 layout 'base'
20
21 before_filter :find_optional_project
19 before_filter :find_optional_project
22
20
23 helper :messages
21 helper :messages
24 include MessagesHelper
22 include MessagesHelper
25
23
26 def index
24 def index
27 @question = params[:q] || ""
25 @question = params[:q] || ""
28 @question.strip!
26 @question.strip!
29 @all_words = params[:all_words] || (params[:submit] ? false : true)
27 @all_words = params[:all_words] || (params[:submit] ? false : true)
30 @titles_only = !params[:titles_only].nil?
28 @titles_only = !params[:titles_only].nil?
31
29
32 projects_to_search =
30 projects_to_search =
33 case params[:scope]
31 case params[:scope]
34 when 'all'
32 when 'all'
35 nil
33 nil
36 when 'my_projects'
34 when 'my_projects'
37 User.current.memberships.collect(&:project)
35 User.current.memberships.collect(&:project)
38 when 'subprojects'
36 when 'subprojects'
39 @project ? ([ @project ] + @project.active_children) : nil
37 @project ? ([ @project ] + @project.active_children) : nil
40 else
38 else
41 @project
39 @project
42 end
40 end
43
41
44 offset = nil
42 offset = nil
45 begin; offset = params[:offset].to_time if params[:offset]; rescue; end
43 begin; offset = params[:offset].to_time if params[:offset]; rescue; end
46
44
47 # quick jump to an issue
45 # quick jump to an issue
48 if @question.match(/^#?(\d+)$/) && Issue.find_by_id($1, :include => :project, :conditions => Project.visible_by(User.current))
46 if @question.match(/^#?(\d+)$/) && Issue.find_by_id($1, :include => :project, :conditions => Project.visible_by(User.current))
49 redirect_to :controller => "issues", :action => "show", :id => $1
47 redirect_to :controller => "issues", :action => "show", :id => $1
50 return
48 return
51 end
49 end
52
50
53 @object_types = %w(issues news documents changesets wiki_pages messages projects)
51 @object_types = %w(issues news documents changesets wiki_pages messages projects)
54 if projects_to_search.is_a? Project
52 if projects_to_search.is_a? Project
55 # don't search projects
53 # don't search projects
56 @object_types.delete('projects')
54 @object_types.delete('projects')
57 # only show what the user is allowed to view
55 # only show what the user is allowed to view
58 @object_types = @object_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, projects_to_search)}
56 @object_types = @object_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, projects_to_search)}
59 end
57 end
60
58
61 @scope = @object_types.select {|t| params[t]}
59 @scope = @object_types.select {|t| params[t]}
62 @scope = @object_types if @scope.empty?
60 @scope = @object_types if @scope.empty?
63
61
64 # extract tokens from the question
62 # extract tokens from the question
65 # eg. hello "bye bye" => ["hello", "bye bye"]
63 # eg. hello "bye bye" => ["hello", "bye bye"]
66 @tokens = @question.scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}).collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')}
64 @tokens = @question.scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}).collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')}
67 # tokens must be at least 3 character long
65 # tokens must be at least 3 character long
68 @tokens = @tokens.uniq.select {|w| w.length > 2 }
66 @tokens = @tokens.uniq.select {|w| w.length > 2 }
69
67
70 if !@tokens.empty?
68 if !@tokens.empty?
71 # no more than 5 tokens to search for
69 # no more than 5 tokens to search for
72 @tokens.slice! 5..-1 if @tokens.size > 5
70 @tokens.slice! 5..-1 if @tokens.size > 5
73 # strings used in sql like statement
71 # strings used in sql like statement
74 like_tokens = @tokens.collect {|w| "%#{w.downcase}%"}
72 like_tokens = @tokens.collect {|w| "%#{w.downcase}%"}
75
73
76 @results = []
74 @results = []
77 @results_by_type = Hash.new {|h,k| h[k] = 0}
75 @results_by_type = Hash.new {|h,k| h[k] = 0}
78
76
79 limit = 10
77 limit = 10
80 @scope.each do |s|
78 @scope.each do |s|
81 r, c = s.singularize.camelcase.constantize.search(like_tokens, projects_to_search,
79 r, c = s.singularize.camelcase.constantize.search(like_tokens, projects_to_search,
82 :all_words => @all_words,
80 :all_words => @all_words,
83 :titles_only => @titles_only,
81 :titles_only => @titles_only,
84 :limit => (limit+1),
82 :limit => (limit+1),
85 :offset => offset,
83 :offset => offset,
86 :before => params[:previous].nil?)
84 :before => params[:previous].nil?)
87 @results += r
85 @results += r
88 @results_by_type[s] += c
86 @results_by_type[s] += c
89 end
87 end
90 @results = @results.sort {|a,b| b.event_datetime <=> a.event_datetime}
88 @results = @results.sort {|a,b| b.event_datetime <=> a.event_datetime}
91 if params[:previous].nil?
89 if params[:previous].nil?
92 @pagination_previous_date = @results[0].event_datetime if offset && @results[0]
90 @pagination_previous_date = @results[0].event_datetime if offset && @results[0]
93 if @results.size > limit
91 if @results.size > limit
94 @pagination_next_date = @results[limit-1].event_datetime
92 @pagination_next_date = @results[limit-1].event_datetime
95 @results = @results[0, limit]
93 @results = @results[0, limit]
96 end
94 end
97 else
95 else
98 @pagination_next_date = @results[-1].event_datetime if offset && @results[-1]
96 @pagination_next_date = @results[-1].event_datetime if offset && @results[-1]
99 if @results.size > limit
97 if @results.size > limit
100 @pagination_previous_date = @results[-(limit)].event_datetime
98 @pagination_previous_date = @results[-(limit)].event_datetime
101 @results = @results[-(limit), limit]
99 @results = @results[-(limit), limit]
102 end
100 end
103 end
101 end
104 else
102 else
105 @question = ""
103 @question = ""
106 end
104 end
107 render :layout => false if request.xhr?
105 render :layout => false if request.xhr?
108 end
106 end
109
107
110 private
108 private
111 def find_optional_project
109 def find_optional_project
112 return true unless params[:id]
110 return true unless params[:id]
113 @project = Project.find(params[:id])
111 @project = Project.find(params[:id])
114 check_project_privacy
112 check_project_privacy
115 rescue ActiveRecord::RecordNotFound
113 rescue ActiveRecord::RecordNotFound
116 render_404
114 render_404
117 end
115 end
118 end
116 end
@@ -1,56 +1,55
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class SettingsController < ApplicationController
18 class SettingsController < ApplicationController
19 layout 'base'
20 before_filter :require_admin
19 before_filter :require_admin
21
20
22 def index
21 def index
23 edit
22 edit
24 render :action => 'edit'
23 render :action => 'edit'
25 end
24 end
26
25
27 def edit
26 def edit
28 @notifiables = %w(issue_added issue_updated news_added document_added file_added message_posted)
27 @notifiables = %w(issue_added issue_updated news_added document_added file_added message_posted)
29 if request.post? && params[:settings] && params[:settings].is_a?(Hash)
28 if request.post? && params[:settings] && params[:settings].is_a?(Hash)
30 settings = (params[:settings] || {}).dup.symbolize_keys
29 settings = (params[:settings] || {}).dup.symbolize_keys
31 settings.each do |name, value|
30 settings.each do |name, value|
32 # remove blank values in array settings
31 # remove blank values in array settings
33 value.delete_if {|v| v.blank? } if value.is_a?(Array)
32 value.delete_if {|v| v.blank? } if value.is_a?(Array)
34 Setting[name] = value
33 Setting[name] = value
35 end
34 end
36 flash[:notice] = l(:notice_successful_update)
35 flash[:notice] = l(:notice_successful_update)
37 redirect_to :action => 'edit', :tab => params[:tab]
36 redirect_to :action => 'edit', :tab => params[:tab]
38 return
37 return
39 end
38 end
40 @options = {}
39 @options = {}
41 @options[:user_format] = User::USER_FORMATS.keys.collect {|f| [User.current.name(f), f.to_s] }
40 @options[:user_format] = User::USER_FORMATS.keys.collect {|f| [User.current.name(f), f.to_s] }
42 @deliveries = ActionMailer::Base.perform_deliveries
41 @deliveries = ActionMailer::Base.perform_deliveries
43 end
42 end
44
43
45 def plugin
44 def plugin
46 plugin_id = params[:id].to_sym
45 plugin_id = params[:id].to_sym
47 @plugin = Redmine::Plugin.registered_plugins[plugin_id]
46 @plugin = Redmine::Plugin.registered_plugins[plugin_id]
48 if request.post?
47 if request.post?
49 Setting["plugin_#{plugin_id}"] = params[:settings]
48 Setting["plugin_#{plugin_id}"] = params[:settings]
50 flash[:notice] = l(:notice_successful_update)
49 flash[:notice] = l(:notice_successful_update)
51 redirect_to :action => 'plugin', :id => params[:id]
50 redirect_to :action => 'plugin', :id => params[:id]
52 end
51 end
53 @partial = @plugin.settings[:partial]
52 @partial = @plugin.settings[:partial]
54 @settings = Setting["plugin_#{plugin_id}"]
53 @settings = Setting["plugin_#{plugin_id}"]
55 end
54 end
56 end
55 end
@@ -1,268 +1,267
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class TimelogController < ApplicationController
18 class TimelogController < ApplicationController
19 layout 'base'
20 menu_item :issues
19 menu_item :issues
21 before_filter :find_project, :authorize
20 before_filter :find_project, :authorize
22
21
23 verify :method => :post, :only => :destroy, :redirect_to => { :action => :details }
22 verify :method => :post, :only => :destroy, :redirect_to => { :action => :details }
24
23
25 helper :sort
24 helper :sort
26 include SortHelper
25 include SortHelper
27 helper :issues
26 helper :issues
28 include TimelogHelper
27 include TimelogHelper
29 helper :custom_fields
28 helper :custom_fields
30 include CustomFieldsHelper
29 include CustomFieldsHelper
31
30
32 def report
31 def report
33 @available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id",
32 @available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id",
34 :klass => Project,
33 :klass => Project,
35 :label => :label_project},
34 :label => :label_project},
36 'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
35 'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
37 :klass => Version,
36 :klass => Version,
38 :label => :label_version},
37 :label => :label_version},
39 'category' => {:sql => "#{Issue.table_name}.category_id",
38 'category' => {:sql => "#{Issue.table_name}.category_id",
40 :klass => IssueCategory,
39 :klass => IssueCategory,
41 :label => :field_category},
40 :label => :field_category},
42 'member' => {:sql => "#{TimeEntry.table_name}.user_id",
41 'member' => {:sql => "#{TimeEntry.table_name}.user_id",
43 :klass => User,
42 :klass => User,
44 :label => :label_member},
43 :label => :label_member},
45 'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
44 'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
46 :klass => Tracker,
45 :klass => Tracker,
47 :label => :label_tracker},
46 :label => :label_tracker},
48 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
47 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
49 :klass => Enumeration,
48 :klass => Enumeration,
50 :label => :label_activity},
49 :label => :label_activity},
51 'issue' => {:sql => "#{TimeEntry.table_name}.issue_id",
50 'issue' => {:sql => "#{TimeEntry.table_name}.issue_id",
52 :klass => Issue,
51 :klass => Issue,
53 :label => :label_issue}
52 :label => :label_issue}
54 }
53 }
55
54
56 # Add list and boolean custom fields as available criterias
55 # Add list and boolean custom fields as available criterias
57 @project.all_issue_custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
56 @project.all_issue_custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
58 @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = #{Issue.table_name}.id)",
57 @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = #{Issue.table_name}.id)",
59 :format => cf.field_format,
58 :format => cf.field_format,
60 :label => cf.name}
59 :label => cf.name}
61 end
60 end
62
61
63 # Add list and boolean time entry custom fields
62 # Add list and boolean time entry custom fields
64 TimeEntryCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
63 TimeEntryCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
65 @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'TimeEntry' AND c.customized_id = #{TimeEntry.table_name}.id)",
64 @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'TimeEntry' AND c.customized_id = #{TimeEntry.table_name}.id)",
66 :format => cf.field_format,
65 :format => cf.field_format,
67 :label => cf.name}
66 :label => cf.name}
68 end
67 end
69
68
70 @criterias = params[:criterias] || []
69 @criterias = params[:criterias] || []
71 @criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
70 @criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
72 @criterias.uniq!
71 @criterias.uniq!
73 @criterias = @criterias[0,3]
72 @criterias = @criterias[0,3]
74
73
75 @columns = (params[:columns] && %w(year month week day).include?(params[:columns])) ? params[:columns] : 'month'
74 @columns = (params[:columns] && %w(year month week day).include?(params[:columns])) ? params[:columns] : 'month'
76
75
77 retrieve_date_range
76 retrieve_date_range
78
77
79 unless @criterias.empty?
78 unless @criterias.empty?
80 sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
79 sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
81 sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
80 sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
82
81
83 sql = "SELECT #{sql_select}, tyear, tmonth, tweek, spent_on, SUM(hours) AS hours"
82 sql = "SELECT #{sql_select}, tyear, tmonth, tweek, spent_on, SUM(hours) AS hours"
84 sql << " FROM #{TimeEntry.table_name}"
83 sql << " FROM #{TimeEntry.table_name}"
85 sql << " LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
84 sql << " LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
86 sql << " LEFT JOIN #{Project.table_name} ON #{TimeEntry.table_name}.project_id = #{Project.table_name}.id"
85 sql << " LEFT JOIN #{Project.table_name} ON #{TimeEntry.table_name}.project_id = #{Project.table_name}.id"
87 sql << " WHERE (%s)" % @project.project_condition(Setting.display_subprojects_issues?)
86 sql << " WHERE (%s)" % @project.project_condition(Setting.display_subprojects_issues?)
88 sql << " AND (%s)" % Project.allowed_to_condition(User.current, :view_time_entries)
87 sql << " AND (%s)" % Project.allowed_to_condition(User.current, :view_time_entries)
89 sql << " AND spent_on BETWEEN '%s' AND '%s'" % [ActiveRecord::Base.connection.quoted_date(@from.to_time), ActiveRecord::Base.connection.quoted_date(@to.to_time)]
88 sql << " AND spent_on BETWEEN '%s' AND '%s'" % [ActiveRecord::Base.connection.quoted_date(@from.to_time), ActiveRecord::Base.connection.quoted_date(@to.to_time)]
90 sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek, spent_on"
89 sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek, spent_on"
91
90
92 @hours = ActiveRecord::Base.connection.select_all(sql)
91 @hours = ActiveRecord::Base.connection.select_all(sql)
93
92
94 @hours.each do |row|
93 @hours.each do |row|
95 case @columns
94 case @columns
96 when 'year'
95 when 'year'
97 row['year'] = row['tyear']
96 row['year'] = row['tyear']
98 when 'month'
97 when 'month'
99 row['month'] = "#{row['tyear']}-#{row['tmonth']}"
98 row['month'] = "#{row['tyear']}-#{row['tmonth']}"
100 when 'week'
99 when 'week'
101 row['week'] = "#{row['tyear']}-#{row['tweek']}"
100 row['week'] = "#{row['tyear']}-#{row['tweek']}"
102 when 'day'
101 when 'day'
103 row['day'] = "#{row['spent_on']}"
102 row['day'] = "#{row['spent_on']}"
104 end
103 end
105 end
104 end
106
105
107 @total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f}
106 @total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f}
108
107
109 @periods = []
108 @periods = []
110 # Date#at_beginning_of_ not supported in Rails 1.2.x
109 # Date#at_beginning_of_ not supported in Rails 1.2.x
111 date_from = @from.to_time
110 date_from = @from.to_time
112 # 100 columns max
111 # 100 columns max
113 while date_from <= @to.to_time && @periods.length < 100
112 while date_from <= @to.to_time && @periods.length < 100
114 case @columns
113 case @columns
115 when 'year'
114 when 'year'
116 @periods << "#{date_from.year}"
115 @periods << "#{date_from.year}"
117 date_from = (date_from + 1.year).at_beginning_of_year
116 date_from = (date_from + 1.year).at_beginning_of_year
118 when 'month'
117 when 'month'
119 @periods << "#{date_from.year}-#{date_from.month}"
118 @periods << "#{date_from.year}-#{date_from.month}"
120 date_from = (date_from + 1.month).at_beginning_of_month
119 date_from = (date_from + 1.month).at_beginning_of_month
121 when 'week'
120 when 'week'
122 @periods << "#{date_from.year}-#{date_from.to_date.cweek}"
121 @periods << "#{date_from.year}-#{date_from.to_date.cweek}"
123 date_from = (date_from + 7.day).at_beginning_of_week
122 date_from = (date_from + 7.day).at_beginning_of_week
124 when 'day'
123 when 'day'
125 @periods << "#{date_from.to_date}"
124 @periods << "#{date_from.to_date}"
126 date_from = date_from + 1.day
125 date_from = date_from + 1.day
127 end
126 end
128 end
127 end
129 end
128 end
130
129
131 respond_to do |format|
130 respond_to do |format|
132 format.html { render :layout => !request.xhr? }
131 format.html { render :layout => !request.xhr? }
133 format.csv { send_data(report_to_csv(@criterias, @periods, @hours).read, :type => 'text/csv; header=present', :filename => 'timelog.csv') }
132 format.csv { send_data(report_to_csv(@criterias, @periods, @hours).read, :type => 'text/csv; header=present', :filename => 'timelog.csv') }
134 end
133 end
135 end
134 end
136
135
137 def details
136 def details
138 sort_init 'spent_on', 'desc'
137 sort_init 'spent_on', 'desc'
139 sort_update
138 sort_update
140
139
141 cond = ARCondition.new
140 cond = ARCondition.new
142 cond << (@issue.nil? ? @project.project_condition(Setting.display_subprojects_issues?) :
141 cond << (@issue.nil? ? @project.project_condition(Setting.display_subprojects_issues?) :
143 ["#{TimeEntry.table_name}.issue_id = ?", @issue.id])
142 ["#{TimeEntry.table_name}.issue_id = ?", @issue.id])
144
143
145 retrieve_date_range
144 retrieve_date_range
146 cond << ['spent_on BETWEEN ? AND ?', @from, @to]
145 cond << ['spent_on BETWEEN ? AND ?', @from, @to]
147
146
148 TimeEntry.visible_by(User.current) do
147 TimeEntry.visible_by(User.current) do
149 respond_to do |format|
148 respond_to do |format|
150 format.html {
149 format.html {
151 # Paginate results
150 # Paginate results
152 @entry_count = TimeEntry.count(:include => :project, :conditions => cond.conditions)
151 @entry_count = TimeEntry.count(:include => :project, :conditions => cond.conditions)
153 @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
152 @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
154 @entries = TimeEntry.find(:all,
153 @entries = TimeEntry.find(:all,
155 :include => [:project, :activity, :user, {:issue => :tracker}],
154 :include => [:project, :activity, :user, {:issue => :tracker}],
156 :conditions => cond.conditions,
155 :conditions => cond.conditions,
157 :order => sort_clause,
156 :order => sort_clause,
158 :limit => @entry_pages.items_per_page,
157 :limit => @entry_pages.items_per_page,
159 :offset => @entry_pages.current.offset)
158 :offset => @entry_pages.current.offset)
160 @total_hours = TimeEntry.sum(:hours, :include => :project, :conditions => cond.conditions).to_f
159 @total_hours = TimeEntry.sum(:hours, :include => :project, :conditions => cond.conditions).to_f
161
160
162 render :layout => !request.xhr?
161 render :layout => !request.xhr?
163 }
162 }
164 format.atom {
163 format.atom {
165 entries = TimeEntry.find(:all,
164 entries = TimeEntry.find(:all,
166 :include => [:project, :activity, :user, {:issue => :tracker}],
165 :include => [:project, :activity, :user, {:issue => :tracker}],
167 :conditions => cond.conditions,
166 :conditions => cond.conditions,
168 :order => "#{TimeEntry.table_name}.created_on DESC",
167 :order => "#{TimeEntry.table_name}.created_on DESC",
169 :limit => Setting.feeds_limit.to_i)
168 :limit => Setting.feeds_limit.to_i)
170 render_feed(entries, :title => l(:label_spent_time))
169 render_feed(entries, :title => l(:label_spent_time))
171 }
170 }
172 format.csv {
171 format.csv {
173 # Export all entries
172 # Export all entries
174 @entries = TimeEntry.find(:all,
173 @entries = TimeEntry.find(:all,
175 :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
174 :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
176 :conditions => cond.conditions,
175 :conditions => cond.conditions,
177 :order => sort_clause)
176 :order => sort_clause)
178 send_data(entries_to_csv(@entries).read, :type => 'text/csv; header=present', :filename => 'timelog.csv')
177 send_data(entries_to_csv(@entries).read, :type => 'text/csv; header=present', :filename => 'timelog.csv')
179 }
178 }
180 end
179 end
181 end
180 end
182 end
181 end
183
182
184 def edit
183 def edit
185 render_403 and return if @time_entry && !@time_entry.editable_by?(User.current)
184 render_403 and return if @time_entry && !@time_entry.editable_by?(User.current)
186 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
185 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
187 @time_entry.attributes = params[:time_entry]
186 @time_entry.attributes = params[:time_entry]
188 if request.post? and @time_entry.save
187 if request.post? and @time_entry.save
189 flash[:notice] = l(:notice_successful_update)
188 flash[:notice] = l(:notice_successful_update)
190 redirect_to(params[:back_url].blank? ? {:action => 'details', :project_id => @time_entry.project} : params[:back_url])
189 redirect_to(params[:back_url].blank? ? {:action => 'details', :project_id => @time_entry.project} : params[:back_url])
191 return
190 return
192 end
191 end
193 end
192 end
194
193
195 def destroy
194 def destroy
196 render_404 and return unless @time_entry
195 render_404 and return unless @time_entry
197 render_403 and return unless @time_entry.editable_by?(User.current)
196 render_403 and return unless @time_entry.editable_by?(User.current)
198 @time_entry.destroy
197 @time_entry.destroy
199 flash[:notice] = l(:notice_successful_delete)
198 flash[:notice] = l(:notice_successful_delete)
200 redirect_to :back
199 redirect_to :back
201 rescue RedirectBackError
200 rescue RedirectBackError
202 redirect_to :action => 'details', :project_id => @time_entry.project
201 redirect_to :action => 'details', :project_id => @time_entry.project
203 end
202 end
204
203
205 private
204 private
206 def find_project
205 def find_project
207 if params[:id]
206 if params[:id]
208 @time_entry = TimeEntry.find(params[:id])
207 @time_entry = TimeEntry.find(params[:id])
209 @project = @time_entry.project
208 @project = @time_entry.project
210 elsif params[:issue_id]
209 elsif params[:issue_id]
211 @issue = Issue.find(params[:issue_id])
210 @issue = Issue.find(params[:issue_id])
212 @project = @issue.project
211 @project = @issue.project
213 elsif params[:project_id]
212 elsif params[:project_id]
214 @project = Project.find(params[:project_id])
213 @project = Project.find(params[:project_id])
215 else
214 else
216 render_404
215 render_404
217 return false
216 return false
218 end
217 end
219 rescue ActiveRecord::RecordNotFound
218 rescue ActiveRecord::RecordNotFound
220 render_404
219 render_404
221 end
220 end
222
221
223 # Retrieves the date range based on predefined ranges or specific from/to param dates
222 # Retrieves the date range based on predefined ranges or specific from/to param dates
224 def retrieve_date_range
223 def retrieve_date_range
225 @free_period = false
224 @free_period = false
226 @from, @to = nil, nil
225 @from, @to = nil, nil
227
226
228 if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
227 if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
229 case params[:period].to_s
228 case params[:period].to_s
230 when 'today'
229 when 'today'
231 @from = @to = Date.today
230 @from = @to = Date.today
232 when 'yesterday'
231 when 'yesterday'
233 @from = @to = Date.today - 1
232 @from = @to = Date.today - 1
234 when 'current_week'
233 when 'current_week'
235 @from = Date.today - (Date.today.cwday - 1)%7
234 @from = Date.today - (Date.today.cwday - 1)%7
236 @to = @from + 6
235 @to = @from + 6
237 when 'last_week'
236 when 'last_week'
238 @from = Date.today - 7 - (Date.today.cwday - 1)%7
237 @from = Date.today - 7 - (Date.today.cwday - 1)%7
239 @to = @from + 6
238 @to = @from + 6
240 when '7_days'
239 when '7_days'
241 @from = Date.today - 7
240 @from = Date.today - 7
242 @to = Date.today
241 @to = Date.today
243 when 'current_month'
242 when 'current_month'
244 @from = Date.civil(Date.today.year, Date.today.month, 1)
243 @from = Date.civil(Date.today.year, Date.today.month, 1)
245 @to = (@from >> 1) - 1
244 @to = (@from >> 1) - 1
246 when 'last_month'
245 when 'last_month'
247 @from = Date.civil(Date.today.year, Date.today.month, 1) << 1
246 @from = Date.civil(Date.today.year, Date.today.month, 1) << 1
248 @to = (@from >> 1) - 1
247 @to = (@from >> 1) - 1
249 when '30_days'
248 when '30_days'
250 @from = Date.today - 30
249 @from = Date.today - 30
251 @to = Date.today
250 @to = Date.today
252 when 'current_year'
251 when 'current_year'
253 @from = Date.civil(Date.today.year, 1, 1)
252 @from = Date.civil(Date.today.year, 1, 1)
254 @to = Date.civil(Date.today.year, 12, 31)
253 @to = Date.civil(Date.today.year, 12, 31)
255 end
254 end
256 elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
255 elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
257 begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
256 begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
258 begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
257 begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
259 @free_period = true
258 @free_period = true
260 else
259 else
261 # default
260 # default
262 end
261 end
263
262
264 @from, @to = @to, @from if @from && @to && @from > @to
263 @from, @to = @to, @from if @from && @to && @from > @to
265 @from ||= (TimeEntry.minimum(:spent_on, :include => :project, :conditions => @project.project_condition(Setting.display_subprojects_issues?)) || Date.today) - 1
264 @from ||= (TimeEntry.minimum(:spent_on, :include => :project, :conditions => @project.project_condition(Setting.display_subprojects_issues?)) || Date.today) - 1
266 @to ||= (TimeEntry.maximum(:spent_on, :include => :project, :conditions => @project.project_condition(Setting.display_subprojects_issues?)) || Date.today)
265 @to ||= (TimeEntry.maximum(:spent_on, :include => :project, :conditions => @project.project_condition(Setting.display_subprojects_issues?)) || Date.today)
267 end
266 end
268 end
267 end
@@ -1,80 +1,79
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class TrackersController < ApplicationController
18 class TrackersController < ApplicationController
19 layout 'base'
20 before_filter :require_admin
19 before_filter :require_admin
21
20
22 def index
21 def index
23 list
22 list
24 render :action => 'list' unless request.xhr?
23 render :action => 'list' unless request.xhr?
25 end
24 end
26
25
27 # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
26 # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
28 verify :method => :post, :only => [ :destroy, :move ], :redirect_to => { :action => :list }
27 verify :method => :post, :only => [ :destroy, :move ], :redirect_to => { :action => :list }
29
28
30 def list
29 def list
31 @tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position'
30 @tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position'
32 render :action => "list", :layout => false if request.xhr?
31 render :action => "list", :layout => false if request.xhr?
33 end
32 end
34
33
35 def new
34 def new
36 @tracker = Tracker.new(params[:tracker])
35 @tracker = Tracker.new(params[:tracker])
37 if request.post? and @tracker.save
36 if request.post? and @tracker.save
38 # workflow copy
37 # workflow copy
39 if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from]))
38 if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from]))
40 @tracker.workflows.copy(copy_from)
39 @tracker.workflows.copy(copy_from)
41 end
40 end
42 flash[:notice] = l(:notice_successful_create)
41 flash[:notice] = l(:notice_successful_create)
43 redirect_to :action => 'list'
42 redirect_to :action => 'list'
44 end
43 end
45 @trackers = Tracker.find :all, :order => 'position'
44 @trackers = Tracker.find :all, :order => 'position'
46 end
45 end
47
46
48 def edit
47 def edit
49 @tracker = Tracker.find(params[:id])
48 @tracker = Tracker.find(params[:id])
50 if request.post? and @tracker.update_attributes(params[:tracker])
49 if request.post? and @tracker.update_attributes(params[:tracker])
51 flash[:notice] = l(:notice_successful_update)
50 flash[:notice] = l(:notice_successful_update)
52 redirect_to :action => 'list'
51 redirect_to :action => 'list'
53 end
52 end
54 end
53 end
55
54
56 def move
55 def move
57 @tracker = Tracker.find(params[:id])
56 @tracker = Tracker.find(params[:id])
58 case params[:position]
57 case params[:position]
59 when 'highest'
58 when 'highest'
60 @tracker.move_to_top
59 @tracker.move_to_top
61 when 'higher'
60 when 'higher'
62 @tracker.move_higher
61 @tracker.move_higher
63 when 'lower'
62 when 'lower'
64 @tracker.move_lower
63 @tracker.move_lower
65 when 'lowest'
64 when 'lowest'
66 @tracker.move_to_bottom
65 @tracker.move_to_bottom
67 end if params[:position]
66 end if params[:position]
68 redirect_to :action => 'list'
67 redirect_to :action => 'list'
69 end
68 end
70
69
71 def destroy
70 def destroy
72 @tracker = Tracker.find(params[:id])
71 @tracker = Tracker.find(params[:id])
73 unless @tracker.issues.empty?
72 unless @tracker.issues.empty?
74 flash[:error] = "This tracker contains issues and can\'t be deleted."
73 flash[:error] = "This tracker contains issues and can\'t be deleted."
75 else
74 else
76 @tracker.destroy
75 @tracker.destroy
77 end
76 end
78 redirect_to :action => 'list'
77 redirect_to :action => 'list'
79 end
78 end
80 end
79 end
@@ -1,101 +1,100
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class UsersController < ApplicationController
18 class UsersController < ApplicationController
19 layout 'base'
20 before_filter :require_admin
19 before_filter :require_admin
21
20
22 helper :sort
21 helper :sort
23 include SortHelper
22 include SortHelper
24 helper :custom_fields
23 helper :custom_fields
25 include CustomFieldsHelper
24 include CustomFieldsHelper
26
25
27 def index
26 def index
28 list
27 list
29 render :action => 'list' unless request.xhr?
28 render :action => 'list' unless request.xhr?
30 end
29 end
31
30
32 def list
31 def list
33 sort_init 'login', 'asc'
32 sort_init 'login', 'asc'
34 sort_update
33 sort_update
35
34
36 @status = params[:status] ? params[:status].to_i : 1
35 @status = params[:status] ? params[:status].to_i : 1
37 conditions = "status <> 0"
36 conditions = "status <> 0"
38 conditions = ["status=?", @status] unless @status == 0
37 conditions = ["status=?", @status] unless @status == 0
39
38
40 @user_count = User.count(:conditions => conditions)
39 @user_count = User.count(:conditions => conditions)
41 @user_pages = Paginator.new self, @user_count,
40 @user_pages = Paginator.new self, @user_count,
42 per_page_option,
41 per_page_option,
43 params['page']
42 params['page']
44 @users = User.find :all,:order => sort_clause,
43 @users = User.find :all,:order => sort_clause,
45 :conditions => conditions,
44 :conditions => conditions,
46 :limit => @user_pages.items_per_page,
45 :limit => @user_pages.items_per_page,
47 :offset => @user_pages.current.offset
46 :offset => @user_pages.current.offset
48
47
49 render :action => "list", :layout => false if request.xhr?
48 render :action => "list", :layout => false if request.xhr?
50 end
49 end
51
50
52 def add
51 def add
53 if request.get?
52 if request.get?
54 @user = User.new(:language => Setting.default_language)
53 @user = User.new(:language => Setting.default_language)
55 else
54 else
56 @user = User.new(params[:user])
55 @user = User.new(params[:user])
57 @user.admin = params[:user][:admin] || false
56 @user.admin = params[:user][:admin] || false
58 @user.login = params[:user][:login]
57 @user.login = params[:user][:login]
59 @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless @user.auth_source_id
58 @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless @user.auth_source_id
60 if @user.save
59 if @user.save
61 Mailer.deliver_account_information(@user, params[:password]) if params[:send_information]
60 Mailer.deliver_account_information(@user, params[:password]) if params[:send_information]
62 flash[:notice] = l(:notice_successful_create)
61 flash[:notice] = l(:notice_successful_create)
63 redirect_to :action => 'list'
62 redirect_to :action => 'list'
64 end
63 end
65 end
64 end
66 @auth_sources = AuthSource.find(:all)
65 @auth_sources = AuthSource.find(:all)
67 end
66 end
68
67
69 def edit
68 def edit
70 @user = User.find(params[:id])
69 @user = User.find(params[:id])
71 if request.post?
70 if request.post?
72 @user.admin = params[:user][:admin] if params[:user][:admin]
71 @user.admin = params[:user][:admin] if params[:user][:admin]
73 @user.login = params[:user][:login] if params[:user][:login]
72 @user.login = params[:user][:login] if params[:user][:login]
74 @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless params[:password].nil? or params[:password].empty? or @user.auth_source_id
73 @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless params[:password].nil? or params[:password].empty? or @user.auth_source_id
75 if @user.update_attributes(params[:user])
74 if @user.update_attributes(params[:user])
76 flash[:notice] = l(:notice_successful_update)
75 flash[:notice] = l(:notice_successful_update)
77 # Give a string to redirect_to otherwise it would use status param as the response code
76 # Give a string to redirect_to otherwise it would use status param as the response code
78 redirect_to(url_for(:action => 'list', :status => params[:status], :page => params[:page]))
77 redirect_to(url_for(:action => 'list', :status => params[:status], :page => params[:page]))
79 end
78 end
80 end
79 end
81 @auth_sources = AuthSource.find(:all)
80 @auth_sources = AuthSource.find(:all)
82 @roles = Role.find_all_givable
81 @roles = Role.find_all_givable
83 @projects = Project.find(:all, :order => 'name', :conditions => "status=#{Project::STATUS_ACTIVE}") - @user.projects
82 @projects = Project.find(:all, :order => 'name', :conditions => "status=#{Project::STATUS_ACTIVE}") - @user.projects
84 @membership ||= Member.new
83 @membership ||= Member.new
85 @memberships = @user.memberships
84 @memberships = @user.memberships
86 end
85 end
87
86
88 def edit_membership
87 def edit_membership
89 @user = User.find(params[:id])
88 @user = User.find(params[:id])
90 @membership = params[:membership_id] ? Member.find(params[:membership_id]) : Member.new(:user => @user)
89 @membership = params[:membership_id] ? Member.find(params[:membership_id]) : Member.new(:user => @user)
91 @membership.attributes = params[:membership]
90 @membership.attributes = params[:membership]
92 @membership.save if request.post?
91 @membership.save if request.post?
93 redirect_to :action => 'edit', :id => @user, :tab => 'memberships'
92 redirect_to :action => 'edit', :id => @user, :tab => 'memberships'
94 end
93 end
95
94
96 def destroy_membership
95 def destroy_membership
97 @user = User.find(params[:id])
96 @user = User.find(params[:id])
98 Member.find(params[:membership_id]).destroy if request.post?
97 Member.find(params[:membership_id]).destroy if request.post?
99 redirect_to :action => 'edit', :id => @user, :tab => 'memberships'
98 redirect_to :action => 'edit', :id => @user, :tab => 'memberships'
100 end
99 end
101 end
100 end
@@ -1,61 +1,60
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class VersionsController < ApplicationController
18 class VersionsController < ApplicationController
19 layout 'base'
20 menu_item :roadmap
19 menu_item :roadmap
21 before_filter :find_project, :authorize
20 before_filter :find_project, :authorize
22
21
23 def show
22 def show
24 end
23 end
25
24
26 def edit
25 def edit
27 if request.post? and @version.update_attributes(params[:version])
26 if request.post? and @version.update_attributes(params[:version])
28 flash[:notice] = l(:notice_successful_update)
27 flash[:notice] = l(:notice_successful_update)
29 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
28 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
30 end
29 end
31 end
30 end
32
31
33 def destroy
32 def destroy
34 @version.destroy
33 @version.destroy
35 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
34 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
36 rescue
35 rescue
37 flash[:error] = "Unable to delete version"
36 flash[:error] = "Unable to delete version"
38 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
37 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
39 end
38 end
40
39
41 def destroy_file
40 def destroy_file
42 @version.attachments.find(params[:attachment_id]).destroy
41 @version.attachments.find(params[:attachment_id]).destroy
43 flash[:notice] = l(:notice_successful_delete)
42 flash[:notice] = l(:notice_successful_delete)
44 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
43 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
45 end
44 end
46
45
47 def status_by
46 def status_by
48 respond_to do |format|
47 respond_to do |format|
49 format.html { render :action => 'show' }
48 format.html { render :action => 'show' }
50 format.js { render(:update) {|page| page.replace_html 'status_by', render_issue_status_by(@version, params[:status_by])} }
49 format.js { render(:update) {|page| page.replace_html 'status_by', render_issue_status_by(@version, params[:status_by])} }
51 end
50 end
52 end
51 end
53
52
54 private
53 private
55 def find_project
54 def find_project
56 @version = Version.find(params[:id])
55 @version = Version.find(params[:id])
57 @project = @version.project
56 @project = @version.project
58 rescue ActiveRecord::RecordNotFound
57 rescue ActiveRecord::RecordNotFound
59 render_404
58 render_404
60 end
59 end
61 end
60 end
@@ -1,71 +1,70
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class WatchersController < ApplicationController
18 class WatchersController < ApplicationController
19 layout 'base'
20 before_filter :find_project
19 before_filter :find_project
21 before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch]
20 before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch]
22 before_filter :authorize, :only => :new
21 before_filter :authorize, :only => :new
23
22
24 verify :method => :post,
23 verify :method => :post,
25 :only => [ :watch, :unwatch ],
24 :only => [ :watch, :unwatch ],
26 :render => { :nothing => true, :status => :method_not_allowed }
25 :render => { :nothing => true, :status => :method_not_allowed }
27
26
28 def watch
27 def watch
29 set_watcher(User.current, true)
28 set_watcher(User.current, true)
30 end
29 end
31
30
32 def unwatch
31 def unwatch
33 set_watcher(User.current, false)
32 set_watcher(User.current, false)
34 end
33 end
35
34
36 def new
35 def new
37 @watcher = Watcher.new(params[:watcher])
36 @watcher = Watcher.new(params[:watcher])
38 @watcher.watchable = @watched
37 @watcher.watchable = @watched
39 @watcher.save if request.post?
38 @watcher.save if request.post?
40 respond_to do |format|
39 respond_to do |format|
41 format.html { redirect_to :back }
40 format.html { redirect_to :back }
42 format.js do
41 format.js do
43 render :update do |page|
42 render :update do |page|
44 page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched}
43 page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched}
45 end
44 end
46 end
45 end
47 end
46 end
48 rescue ::ActionController::RedirectBackError
47 rescue ::ActionController::RedirectBackError
49 render :text => 'Watcher added.', :layout => true
48 render :text => 'Watcher added.', :layout => true
50 end
49 end
51
50
52 private
51 private
53 def find_project
52 def find_project
54 klass = Object.const_get(params[:object_type].camelcase)
53 klass = Object.const_get(params[:object_type].camelcase)
55 return false unless klass.respond_to?('watched_by')
54 return false unless klass.respond_to?('watched_by')
56 @watched = klass.find(params[:object_id])
55 @watched = klass.find(params[:object_id])
57 @project = @watched.project
56 @project = @watched.project
58 rescue
57 rescue
59 render_404
58 render_404
60 end
59 end
61
60
62 def set_watcher(user, watching)
61 def set_watcher(user, watching)
63 @watched.set_watcher(user, watching)
62 @watched.set_watcher(user, watching)
64 respond_to do |format|
63 respond_to do |format|
65 format.html { redirect_to :back }
64 format.html { redirect_to :back }
66 format.js { render(:update) {|page| page.replace_html 'watcher', watcher_link(@watched, user)} }
65 format.js { render(:update) {|page| page.replace_html 'watcher', watcher_link(@watched, user)} }
67 end
66 end
68 rescue ::ActionController::RedirectBackError
67 rescue ::ActionController::RedirectBackError
69 render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true
68 render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true
70 end
69 end
71 end
70 end
@@ -1,25 +1,24
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class WelcomeController < ApplicationController
18 class WelcomeController < ApplicationController
19 layout 'base'
20
19
21 def index
20 def index
22 @news = News.latest User.current
21 @news = News.latest User.current
23 @projects = Project.latest User.current
22 @projects = Project.latest User.current
24 end
23 end
25 end
24 end
@@ -1,204 +1,203
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'diff'
18 require 'diff'
19
19
20 class WikiController < ApplicationController
20 class WikiController < ApplicationController
21 layout 'base'
22 before_filter :find_wiki, :authorize
21 before_filter :find_wiki, :authorize
23
22
24 verify :method => :post, :only => [:destroy, :destroy_attachment, :protect], :redirect_to => { :action => :index }
23 verify :method => :post, :only => [:destroy, :destroy_attachment, :protect], :redirect_to => { :action => :index }
25
24
26 helper :attachments
25 helper :attachments
27 include AttachmentsHelper
26 include AttachmentsHelper
28
27
29 # display a page (in editing mode if it doesn't exist)
28 # display a page (in editing mode if it doesn't exist)
30 def index
29 def index
31 page_title = params[:page]
30 page_title = params[:page]
32 @page = @wiki.find_or_new_page(page_title)
31 @page = @wiki.find_or_new_page(page_title)
33 if @page.new_record?
32 if @page.new_record?
34 if User.current.allowed_to?(:edit_wiki_pages, @project)
33 if User.current.allowed_to?(:edit_wiki_pages, @project)
35 edit
34 edit
36 render :action => 'edit'
35 render :action => 'edit'
37 else
36 else
38 render_404
37 render_404
39 end
38 end
40 return
39 return
41 end
40 end
42 @content = @page.content_for_version(params[:version])
41 @content = @page.content_for_version(params[:version])
43 if params[:export] == 'html'
42 if params[:export] == 'html'
44 export = render_to_string :action => 'export', :layout => false
43 export = render_to_string :action => 'export', :layout => false
45 send_data(export, :type => 'text/html', :filename => "#{@page.title}.html")
44 send_data(export, :type => 'text/html', :filename => "#{@page.title}.html")
46 return
45 return
47 elsif params[:export] == 'txt'
46 elsif params[:export] == 'txt'
48 send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt")
47 send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt")
49 return
48 return
50 end
49 end
51 @editable = editable?
50 @editable = editable?
52 render :action => 'show'
51 render :action => 'show'
53 end
52 end
54
53
55 # edit an existing page or a new one
54 # edit an existing page or a new one
56 def edit
55 def edit
57 @page = @wiki.find_or_new_page(params[:page])
56 @page = @wiki.find_or_new_page(params[:page])
58 return render_403 unless editable?
57 return render_403 unless editable?
59 @page.content = WikiContent.new(:page => @page) if @page.new_record?
58 @page.content = WikiContent.new(:page => @page) if @page.new_record?
60
59
61 @content = @page.content_for_version(params[:version])
60 @content = @page.content_for_version(params[:version])
62 @content.text = "h1. #{@page.pretty_title}" if @content.text.blank?
61 @content.text = "h1. #{@page.pretty_title}" if @content.text.blank?
63 # don't keep previous comment
62 # don't keep previous comment
64 @content.comments = nil
63 @content.comments = nil
65 if request.post?
64 if request.post?
66 if !@page.new_record? && @content.text == params[:content][:text]
65 if !@page.new_record? && @content.text == params[:content][:text]
67 # don't save if text wasn't changed
66 # don't save if text wasn't changed
68 redirect_to :action => 'index', :id => @project, :page => @page.title
67 redirect_to :action => 'index', :id => @project, :page => @page.title
69 return
68 return
70 end
69 end
71 #@content.text = params[:content][:text]
70 #@content.text = params[:content][:text]
72 #@content.comments = params[:content][:comments]
71 #@content.comments = params[:content][:comments]
73 @content.attributes = params[:content]
72 @content.attributes = params[:content]
74 @content.author = User.current
73 @content.author = User.current
75 # if page is new @page.save will also save content, but not if page isn't a new record
74 # if page is new @page.save will also save content, but not if page isn't a new record
76 if (@page.new_record? ? @page.save : @content.save)
75 if (@page.new_record? ? @page.save : @content.save)
77 redirect_to :action => 'index', :id => @project, :page => @page.title
76 redirect_to :action => 'index', :id => @project, :page => @page.title
78 end
77 end
79 end
78 end
80 rescue ActiveRecord::StaleObjectError
79 rescue ActiveRecord::StaleObjectError
81 # Optimistic locking exception
80 # Optimistic locking exception
82 flash[:error] = l(:notice_locking_conflict)
81 flash[:error] = l(:notice_locking_conflict)
83 end
82 end
84
83
85 # rename a page
84 # rename a page
86 def rename
85 def rename
87 @page = @wiki.find_page(params[:page])
86 @page = @wiki.find_page(params[:page])
88 return render_403 unless editable?
87 return render_403 unless editable?
89 @page.redirect_existing_links = true
88 @page.redirect_existing_links = true
90 # used to display the *original* title if some AR validation errors occur
89 # used to display the *original* title if some AR validation errors occur
91 @original_title = @page.pretty_title
90 @original_title = @page.pretty_title
92 if request.post? && @page.update_attributes(params[:wiki_page])
91 if request.post? && @page.update_attributes(params[:wiki_page])
93 flash[:notice] = l(:notice_successful_update)
92 flash[:notice] = l(:notice_successful_update)
94 redirect_to :action => 'index', :id => @project, :page => @page.title
93 redirect_to :action => 'index', :id => @project, :page => @page.title
95 end
94 end
96 end
95 end
97
96
98 def protect
97 def protect
99 page = @wiki.find_page(params[:page])
98 page = @wiki.find_page(params[:page])
100 page.update_attribute :protected, params[:protected]
99 page.update_attribute :protected, params[:protected]
101 redirect_to :action => 'index', :id => @project, :page => page.title
100 redirect_to :action => 'index', :id => @project, :page => page.title
102 end
101 end
103
102
104 # show page history
103 # show page history
105 def history
104 def history
106 @page = @wiki.find_page(params[:page])
105 @page = @wiki.find_page(params[:page])
107
106
108 @version_count = @page.content.versions.count
107 @version_count = @page.content.versions.count
109 @version_pages = Paginator.new self, @version_count, per_page_option, params['p']
108 @version_pages = Paginator.new self, @version_count, per_page_option, params['p']
110 # don't load text
109 # don't load text
111 @versions = @page.content.versions.find :all,
110 @versions = @page.content.versions.find :all,
112 :select => "id, author_id, comments, updated_on, version",
111 :select => "id, author_id, comments, updated_on, version",
113 :order => 'version DESC',
112 :order => 'version DESC',
114 :limit => @version_pages.items_per_page + 1,
113 :limit => @version_pages.items_per_page + 1,
115 :offset => @version_pages.current.offset
114 :offset => @version_pages.current.offset
116
115
117 render :layout => false if request.xhr?
116 render :layout => false if request.xhr?
118 end
117 end
119
118
120 def diff
119 def diff
121 @page = @wiki.find_page(params[:page])
120 @page = @wiki.find_page(params[:page])
122 @diff = @page.diff(params[:version], params[:version_from])
121 @diff = @page.diff(params[:version], params[:version_from])
123 render_404 unless @diff
122 render_404 unless @diff
124 end
123 end
125
124
126 def annotate
125 def annotate
127 @page = @wiki.find_page(params[:page])
126 @page = @wiki.find_page(params[:page])
128 @annotate = @page.annotate(params[:version])
127 @annotate = @page.annotate(params[:version])
129 end
128 end
130
129
131 # remove a wiki page and its history
130 # remove a wiki page and its history
132 def destroy
131 def destroy
133 @page = @wiki.find_page(params[:page])
132 @page = @wiki.find_page(params[:page])
134 return render_403 unless editable?
133 return render_403 unless editable?
135 @page.destroy if @page
134 @page.destroy if @page
136 redirect_to :action => 'special', :id => @project, :page => 'Page_index'
135 redirect_to :action => 'special', :id => @project, :page => 'Page_index'
137 end
136 end
138
137
139 # display special pages
138 # display special pages
140 def special
139 def special
141 page_title = params[:page].downcase
140 page_title = params[:page].downcase
142 case page_title
141 case page_title
143 # show pages index, sorted by title
142 # show pages index, sorted by title
144 when 'page_index', 'date_index'
143 when 'page_index', 'date_index'
145 # eager load information about last updates, without loading text
144 # eager load information about last updates, without loading text
146 @pages = @wiki.pages.find :all, :select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on",
145 @pages = @wiki.pages.find :all, :select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on",
147 :joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id",
146 :joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id",
148 :order => 'title'
147 :order => 'title'
149 @pages_by_date = @pages.group_by {|p| p.updated_on.to_date}
148 @pages_by_date = @pages.group_by {|p| p.updated_on.to_date}
150 @pages_by_parent_id = @pages.group_by(&:parent_id)
149 @pages_by_parent_id = @pages.group_by(&:parent_id)
151 # export wiki to a single html file
150 # export wiki to a single html file
152 when 'export'
151 when 'export'
153 @pages = @wiki.pages.find :all, :order => 'title'
152 @pages = @wiki.pages.find :all, :order => 'title'
154 export = render_to_string :action => 'export_multiple', :layout => false
153 export = render_to_string :action => 'export_multiple', :layout => false
155 send_data(export, :type => 'text/html', :filename => "wiki.html")
154 send_data(export, :type => 'text/html', :filename => "wiki.html")
156 return
155 return
157 else
156 else
158 # requested special page doesn't exist, redirect to default page
157 # requested special page doesn't exist, redirect to default page
159 redirect_to :action => 'index', :id => @project, :page => nil and return
158 redirect_to :action => 'index', :id => @project, :page => nil and return
160 end
159 end
161 render :action => "special_#{page_title}"
160 render :action => "special_#{page_title}"
162 end
161 end
163
162
164 def preview
163 def preview
165 page = @wiki.find_page(params[:page])
164 page = @wiki.find_page(params[:page])
166 # page is nil when previewing a new page
165 # page is nil when previewing a new page
167 return render_403 unless page.nil? || editable?(page)
166 return render_403 unless page.nil? || editable?(page)
168 if page
167 if page
169 @attachements = page.attachments
168 @attachements = page.attachments
170 @previewed = page.content
169 @previewed = page.content
171 end
170 end
172 @text = params[:content][:text]
171 @text = params[:content][:text]
173 render :partial => 'common/preview'
172 render :partial => 'common/preview'
174 end
173 end
175
174
176 def add_attachment
175 def add_attachment
177 @page = @wiki.find_page(params[:page])
176 @page = @wiki.find_page(params[:page])
178 return render_403 unless editable?
177 return render_403 unless editable?
179 attach_files(@page, params[:attachments])
178 attach_files(@page, params[:attachments])
180 redirect_to :action => 'index', :page => @page.title
179 redirect_to :action => 'index', :page => @page.title
181 end
180 end
182
181
183 def destroy_attachment
182 def destroy_attachment
184 @page = @wiki.find_page(params[:page])
183 @page = @wiki.find_page(params[:page])
185 return render_403 unless editable?
184 return render_403 unless editable?
186 @page.attachments.find(params[:attachment_id]).destroy
185 @page.attachments.find(params[:attachment_id]).destroy
187 redirect_to :action => 'index', :page => @page.title
186 redirect_to :action => 'index', :page => @page.title
188 end
187 end
189
188
190 private
189 private
191
190
192 def find_wiki
191 def find_wiki
193 @project = Project.find(params[:id])
192 @project = Project.find(params[:id])
194 @wiki = @project.wiki
193 @wiki = @project.wiki
195 render_404 unless @wiki
194 render_404 unless @wiki
196 rescue ActiveRecord::RecordNotFound
195 rescue ActiveRecord::RecordNotFound
197 render_404
196 render_404
198 end
197 end
199
198
200 # Returns true if the current user is allowed to edit the page, otherwise false
199 # Returns true if the current user is allowed to edit the page, otherwise false
201 def editable?(page = @page)
200 def editable?(page = @page)
202 page.editable_by?(User.current)
201 page.editable_by?(User.current)
203 end
202 end
204 end
203 end
@@ -1,45 +1,44
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class WikisController < ApplicationController
18 class WikisController < ApplicationController
19 layout 'base'
20 menu_item :settings
19 menu_item :settings
21 before_filter :find_project, :authorize
20 before_filter :find_project, :authorize
22
21
23 # Create or update a project's wiki
22 # Create or update a project's wiki
24 def edit
23 def edit
25 @wiki = @project.wiki || Wiki.new(:project => @project)
24 @wiki = @project.wiki || Wiki.new(:project => @project)
26 @wiki.attributes = params[:wiki]
25 @wiki.attributes = params[:wiki]
27 @wiki.save if request.post?
26 @wiki.save if request.post?
28 render(:update) {|page| page.replace_html "tab-content-wiki", :partial => 'projects/settings/wiki'}
27 render(:update) {|page| page.replace_html "tab-content-wiki", :partial => 'projects/settings/wiki'}
29 end
28 end
30
29
31 # Delete a project's wiki
30 # Delete a project's wiki
32 def destroy
31 def destroy
33 if request.post? && params[:confirm] && @project.wiki
32 if request.post? && params[:confirm] && @project.wiki
34 @project.wiki.destroy
33 @project.wiki.destroy
35 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'wiki'
34 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'wiki'
36 end
35 end
37 end
36 end
38
37
39 private
38 private
40 def find_project
39 def find_project
41 @project = Project.find(params[:id])
40 @project = Project.find(params[:id])
42 rescue ActiveRecord::RecordNotFound
41 rescue ActiveRecord::RecordNotFound
43 render_404
42 render_404
44 end
43 end
45 end
44 end
General Comments 0
You need to be logged in to leave comments. Login now