##// END OF EJS Templates
Anonymous users can now be allowed to create, edit, comment issues, comment news and post messages in the forums....
Jean-Philippe Lang -
r906:987a5aa22114
parent child
Show More
@@ -0,0 +1,10
1 class AddUsersType < ActiveRecord::Migration
2 def self.up
3 add_column :users, :type, :string
4 User.update_all "type = 'User'"
5 end
6
7 def self.down
8 remove_column :users, :type
9 end
10 end
@@ -1,170 +1,166
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class ApplicationController < ActionController::Base
19 19 before_filter :user_setup, :check_if_login_required, :set_localization
20 20 filter_parameter_logging :password
21 21
22 22 REDMINE_SUPPORTED_SCM.each do |scm|
23 23 require_dependency "repository/#{scm.underscore}"
24 24 end
25 25
26 def logged_in_user
27 User.current.logged? ? User.current : nil
28 end
29
30 26 def current_role
31 27 @current_role ||= User.current.role_for_project(@project)
32 28 end
33 29
34 30 def user_setup
35 31 Setting.check_cache
36 32 if session[:user_id]
37 33 # existing session
38 34 User.current = User.find(session[:user_id])
39 35 elsif cookies[:autologin] && Setting.autologin?
40 36 # auto-login feature
41 37 User.current = User.find_by_autologin_key(cookies[:autologin])
42 38 elsif params[:key] && accept_key_auth_actions.include?(params[:action])
43 39 # RSS key authentication
44 40 User.current = User.find_by_rss_key(params[:key])
45 41 else
46 42 User.current = User.anonymous
47 43 end
48 44 end
49 45
50 46 # check if login is globally required to access the application
51 47 def check_if_login_required
52 48 # no check needed if user is already logged in
53 49 return true if User.current.logged?
54 50 require_login if Setting.login_required?
55 51 end
56 52
57 53 def set_localization
58 54 lang = begin
59 55 if !User.current.language.blank? and GLoc.valid_languages.include? User.current.language.to_sym
60 56 User.current.language
61 57 elsif request.env['HTTP_ACCEPT_LANGUAGE']
62 58 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.split('-').first
63 59 if accept_lang and !accept_lang.empty? and GLoc.valid_languages.include? accept_lang.to_sym
64 60 accept_lang
65 61 end
66 62 end
67 63 rescue
68 64 nil
69 65 end || Setting.default_language
70 66 set_language_if_valid(lang)
71 67 end
72 68
73 69 def require_login
74 70 if !User.current.logged?
75 71 store_location
76 72 redirect_to :controller => "account", :action => "login"
77 73 return false
78 74 end
79 75 true
80 76 end
81 77
82 78 def require_admin
83 79 return unless require_login
84 80 if !User.current.admin?
85 81 render_403
86 82 return false
87 83 end
88 84 true
89 85 end
90 86
91 87 # Authorize the user for the requested action
92 88 def authorize(ctrl = params[:controller], action = params[:action])
93 89 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project)
94 90 allowed ? true : (User.current.logged? ? render_403 : require_login)
95 91 end
96 92
97 93 # make sure that the user is a member of the project (or admin) if project is private
98 94 # used as a before_filter for actions that do not require any particular permission on the project
99 95 def check_project_privacy
100 96 unless @project.active?
101 97 @project = nil
102 98 render_404
103 99 return false
104 100 end
105 101 return true if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
106 102 User.current.logged? ? render_403 : require_login
107 103 end
108 104
109 105 # store current uri in session.
110 106 # return to this location by calling redirect_back_or_default
111 107 def store_location
112 108 session[:return_to_params] = params
113 109 end
114 110
115 111 # move to the last store_location call or to the passed default one
116 112 def redirect_back_or_default(default)
117 113 if session[:return_to_params].nil?
118 114 redirect_to default
119 115 else
120 116 redirect_to session[:return_to_params]
121 117 session[:return_to_params] = nil
122 118 end
123 119 end
124 120
125 121 def render_403
126 122 @project = nil
127 123 render :template => "common/403", :layout => !request.xhr?, :status => 403
128 124 return false
129 125 end
130 126
131 127 def render_404
132 128 render :template => "common/404", :layout => !request.xhr?, :status => 404
133 129 return false
134 130 end
135 131
136 132 def render_feed(items, options={})
137 133 @items = items || []
138 134 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
139 135 @title = options[:title] || Setting.app_title
140 136 render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
141 137 end
142 138
143 139 def self.accept_key_auth(*actions)
144 140 actions = actions.flatten.map(&:to_s)
145 141 write_inheritable_attribute('accept_key_auth_actions', actions)
146 142 end
147 143
148 144 def accept_key_auth_actions
149 145 self.class.read_inheritable_attribute('accept_key_auth_actions') || []
150 146 end
151 147
152 148 # qvalues http header parser
153 149 # code taken from webrick
154 150 def parse_qvalues(value)
155 151 tmp = []
156 152 if value
157 153 parts = value.split(/,\s*/)
158 154 parts.each {|part|
159 155 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
160 156 val = m[1]
161 157 q = (m[2] or 1).to_f
162 158 tmp.push([val, q])
163 159 end
164 160 }
165 161 tmp = tmp.sort_by{|val, q| -q}
166 162 tmp.collect!{|val, q| val}
167 163 end
168 164 return tmp
169 165 end
170 166 end
@@ -1,71 +1,71
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class DocumentsController < ApplicationController
19 19 layout 'base'
20 20 before_filter :find_project, :authorize
21 21
22 22 def show
23 23 @attachments = @document.attachments.find(:all, :order => "created_on DESC")
24 24 end
25 25
26 26 def edit
27 27 @categories = Enumeration::get_values('DCAT')
28 28 if request.post? and @document.update_attributes(params[:document])
29 29 flash[:notice] = l(:notice_successful_update)
30 30 redirect_to :action => 'show', :id => @document
31 31 end
32 32 end
33 33
34 34 def destroy
35 35 @document.destroy
36 36 redirect_to :controller => 'projects', :action => 'list_documents', :id => @project
37 37 end
38 38
39 39 def download
40 40 @attachment = @document.attachments.find(params[:attachment_id])
41 41 @attachment.increment_download
42 42 send_file @attachment.diskfile, :filename => @attachment.filename, :type => @attachment.content_type
43 43 rescue
44 44 render_404
45 45 end
46 46
47 47 def add_attachment
48 48 # Save the attachments
49 49 @attachments = []
50 50 params[:attachments].each { |file|
51 51 next unless file.size > 0
52 a = Attachment.create(:container => @document, :file => file, :author => logged_in_user)
52 a = Attachment.create(:container => @document, :file => file, :author => User.current)
53 53 @attachments << a unless a.new_record?
54 54 } if params[:attachments] and params[:attachments].is_a? Array
55 55 Mailer.deliver_attachments_added(@attachments) if !@attachments.empty? && Setting.notified_events.include?('document_added')
56 56 redirect_to :action => 'show', :id => @document
57 57 end
58 58
59 59 def destroy_attachment
60 60 @document.attachments.find(params[:attachment_id]).destroy
61 61 redirect_to :action => 'show', :id => @document
62 62 end
63 63
64 64 private
65 65 def find_project
66 66 @document = Document.find(params[:id])
67 67 @project = @document.project
68 68 rescue ActiveRecord::RecordNotFound
69 69 render_404
70 70 end
71 71 end
@@ -1,250 +1,249
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class IssuesController < ApplicationController
19 19 layout 'base'
20 20 before_filter :find_project, :authorize, :except => [:index, :changes, :preview]
21 21 before_filter :find_optional_project, :only => [:index, :changes]
22 22 accept_key_auth :index, :changes
23 23
24 24 cache_sweeper :issue_sweeper, :only => [ :edit, :change_status, :destroy ]
25 25
26 26 helper :projects
27 27 include ProjectsHelper
28 28 helper :custom_fields
29 29 include CustomFieldsHelper
30 30 helper :ifpdf
31 31 include IfpdfHelper
32 32 helper :issue_relations
33 33 include IssueRelationsHelper
34 34 helper :watchers
35 35 include WatchersHelper
36 36 helper :attachments
37 37 include AttachmentsHelper
38 38 helper :queries
39 39 helper :sort
40 40 include SortHelper
41 41 include IssuesHelper
42 42
43 43 def index
44 44 sort_init "#{Issue.table_name}.id", "desc"
45 45 sort_update
46 46 retrieve_query
47 47 if @query.valid?
48 48 limit = %w(pdf csv).include?(params[:format]) ? Setting.issues_export_limit.to_i : 25
49 49 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
50 50 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
51 51 @issues = Issue.find :all, :order => sort_clause,
52 52 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ],
53 53 :conditions => @query.statement,
54 54 :limit => limit,
55 55 :offset => @issue_pages.current.offset
56 56 respond_to do |format|
57 57 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
58 58 format.atom { render_feed(@issues, :title => l(:label_issue_plural)) }
59 59 format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
60 60 format.pdf { send_data(render(:template => 'issues/index.rfpdf', :layout => false), :type => 'application/pdf', :filename => 'export.pdf') }
61 61 end
62 62 else
63 63 # Send html if the query is not valid
64 64 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
65 65 end
66 66 end
67 67
68 68 def changes
69 69 sort_init "#{Issue.table_name}.id", "desc"
70 70 sort_update
71 71 retrieve_query
72 72 if @query.valid?
73 73 @changes = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
74 74 :conditions => @query.statement,
75 75 :limit => 25,
76 76 :order => "#{Journal.table_name}.created_on DESC"
77 77 end
78 78 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
79 79 render :layout => false, :content_type => 'application/atom+xml'
80 80 end
81 81
82 82 def show
83 83 @custom_values = @issue.custom_values.find(:all, :include => :custom_field, :order => "#{CustomField.table_name}.position")
84 84 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
85 @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
85 @status_options = @issue.status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)
86 86 respond_to do |format|
87 87 format.html { render :template => 'issues/show.rhtml' }
88 88 format.pdf { send_data(render(:template => 'issues/show.rfpdf', :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
89 89 end
90 90 end
91 91
92 92 def edit
93 93 @priorities = Enumeration::get_values('IPRI')
94 94 if request.get?
95 95 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
96 96 else
97 97 begin
98 @issue.init_journal(self.logged_in_user)
98 @issue.init_journal(User.current)
99 99 # Retrieve custom fields and values
100 100 if params["custom_fields"]
101 101 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
102 102 @issue.custom_values = @custom_values
103 103 end
104 104 @issue.attributes = params[:issue]
105 105 if @issue.save
106 106 flash[:notice] = l(:notice_successful_update)
107 107 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
108 108 end
109 109 rescue ActiveRecord::StaleObjectError
110 110 # Optimistic locking exception
111 111 flash[:error] = l(:notice_locking_conflict)
112 112 end
113 113 end
114 114 end
115 115
116 116 def add_note
117 117 journal = @issue.init_journal(User.current, params[:notes])
118 118 params[:attachments].each { |file|
119 119 next unless file.size > 0
120 a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
120 a = Attachment.create(:container => @issue, :file => file, :author => User.current)
121 121 journal.details << JournalDetail.new(:property => 'attachment',
122 122 :prop_key => a.id,
123 123 :value => a.filename) unless a.new_record?
124 124 } if params[:attachments] and params[:attachments].is_a? Array
125 125 if journal.save
126 126 flash[:notice] = l(:notice_successful_update)
127 127 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
128 128 redirect_to :action => 'show', :id => @issue
129 129 return
130 130 end
131 131 show
132 132 end
133 133
134 134 def change_status
135 @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
135 @status_options = @issue.status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)
136 136 @new_status = IssueStatus.find(params[:new_status_id])
137 137 if params[:confirm]
138 138 begin
139 journal = @issue.init_journal(self.logged_in_user, params[:notes])
139 journal = @issue.init_journal(User.current, params[:notes])
140 140 @issue.status = @new_status
141 141 if @issue.update_attributes(params[:issue])
142 142 # Save attachments
143 143 params[:attachments].each { |file|
144 144 next unless file.size > 0
145 a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
145 a = Attachment.create(:container => @issue, :file => file, :author => User.current)
146 146 journal.details << JournalDetail.new(:property => 'attachment',
147 147 :prop_key => a.id,
148 148 :value => a.filename) unless a.new_record?
149 149 } if params[:attachments] and params[:attachments].is_a? Array
150 150
151 151 # Log time
152 152 if current_role.allowed_to?(:log_time)
153 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => logged_in_user, :spent_on => Date.today)
153 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
154 154 @time_entry.attributes = params[:time_entry]
155 155 @time_entry.save
156 156 end
157 157
158 158 flash[:notice] = l(:notice_successful_update)
159 159 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
160 160 redirect_to :action => 'show', :id => @issue
161 161 end
162 162 rescue ActiveRecord::StaleObjectError
163 163 # Optimistic locking exception
164 164 flash[:error] = l(:notice_locking_conflict)
165 165 end
166 166 end
167 167 @assignable_to = @project.members.find(:all, :include => :user).collect{ |m| m.user }
168 168 @activities = Enumeration::get_values('ACTI')
169 169 end
170 170
171 171 def destroy
172 172 @issue.destroy
173 173 redirect_to :action => 'index', :project_id => @project
174 174 end
175 175
176 176 def destroy_attachment
177 177 a = @issue.attachments.find(params[:attachment_id])
178 178 a.destroy
179 journal = @issue.init_journal(self.logged_in_user)
179 journal = @issue.init_journal(User.current)
180 180 journal.details << JournalDetail.new(:property => 'attachment',
181 181 :prop_key => a.id,
182 182 :old_value => a.filename)
183 183 journal.save
184 184 redirect_to :action => 'show', :id => @issue
185 185 end
186 186
187 187 def context_menu
188 188 @priorities = Enumeration.get_values('IPRI').reverse
189 189 @statuses = IssueStatus.find(:all, :order => 'position')
190 190 @allowed_statuses = @issue.status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)
191 191 @assignables = @issue.assignable_users
192 192 @assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
193 193 @can = {:edit => User.current.allowed_to?(:edit_issues, @project),
194 194 :change_status => User.current.allowed_to?(:change_issue_status, @project),
195 195 :add => User.current.allowed_to?(:add_issues, @project),
196 196 :move => User.current.allowed_to?(:move_issues, @project),
197 197 :delete => User.current.allowed_to?(:delete_issues, @project)}
198 198 render :layout => false
199 199 end
200 200
201 201 def preview
202 202 issue = Issue.find_by_id(params[:id])
203 203 @attachements = issue.attachments if issue
204 204 @text = params[:issue][:description]
205 205 render :partial => 'common/preview'
206 206 end
207 207
208 208 private
209 209 def find_project
210 210 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
211 211 @project = @issue.project
212 212 rescue ActiveRecord::RecordNotFound
213 213 render_404
214 214 end
215 215
216 216 def find_optional_project
217 217 return true unless params[:project_id]
218 218 @project = Project.find(params[:project_id])
219 219 authorize
220 220 rescue ActiveRecord::RecordNotFound
221 221 render_404
222 222 end
223 223
224 224 # Retrieve query from session or build a new query
225 225 def retrieve_query
226 226 if params[:query_id]
227 227 @query = Query.find(params[:query_id], :conditions => {:project_id => (@project ? @project.id : nil)})
228 @query.executed_by = logged_in_user
229 228 session[:query] = @query
230 229 else
231 230 if params[:set_filter] or !session[:query] or session[:query].project != @project
232 231 # Give it a name, required to be valid
233 @query = Query.new(:name => "_", :executed_by => logged_in_user)
232 @query = Query.new(:name => "_")
234 233 @query.project = @project
235 234 if params[:fields] and params[:fields].is_a? Array
236 235 params[:fields].each do |field|
237 236 @query.add_filter(field, params[:operators][field], params[:values][field])
238 237 end
239 238 else
240 239 @query.available_filters.keys.each do |field|
241 240 @query.add_short_filter(field, params[field]) if params[field]
242 241 end
243 242 end
244 243 session[:query] = @query
245 244 else
246 245 @query = session[:query]
247 246 end
248 247 end
249 248 end
250 249 end
@@ -1,61 +1,61
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class MessagesController < ApplicationController
19 19 layout 'base'
20 20 before_filter :find_project, :authorize
21 21
22 22 verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show }
23 23
24 24 helper :attachments
25 25 include AttachmentsHelper
26 26
27 27 def show
28 28 @reply = Message.new(:subject => "RE: #{@message.subject}")
29 29 render :action => "show", :layout => false if request.xhr?
30 30 end
31 31
32 32 def new
33 33 @message = Message.new(params[:message])
34 @message.author = logged_in_user
34 @message.author = User.current
35 35 @message.board = @board
36 36 if request.post? && @message.save
37 37 params[:attachments].each { |file|
38 38 next unless file.size > 0
39 Attachment.create(:container => @message, :file => file, :author => logged_in_user)
39 Attachment.create(:container => @message, :file => file, :author => User.current)
40 40 } if params[:attachments] and params[:attachments].is_a? Array
41 41 redirect_to :action => 'show', :id => @message
42 42 end
43 43 end
44 44
45 45 def reply
46 46 @reply = Message.new(params[:reply])
47 @reply.author = logged_in_user
47 @reply.author = User.current
48 48 @reply.board = @board
49 49 @message.children << @reply
50 50 redirect_to :action => 'show', :id => @message
51 51 end
52 52
53 53 private
54 54 def find_project
55 55 @board = Board.find(params[:board_id], :include => :project)
56 56 @project = @board.project
57 57 @message = @board.topics.find(params[:id]) if params[:id]
58 58 rescue ActiveRecord::RecordNotFound
59 59 render_404
60 60 end
61 61 end
@@ -1,160 +1,160
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class MyController < ApplicationController
19 19 helper :issues
20 20
21 21 layout 'base'
22 22 before_filter :require_login
23 23
24 24 BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues,
25 25 'issuesreportedbyme' => :label_reported_issues,
26 26 'issueswatched' => :label_watched_issues,
27 27 'news' => :label_news_latest,
28 28 'calendar' => :label_calendar,
29 29 'documents' => :label_document_plural
30 30 }.freeze
31 31
32 32 DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'],
33 33 'right' => ['issuesreportedbyme']
34 34 }.freeze
35 35
36 36 verify :xhr => true,
37 37 :session => :page_layout,
38 38 :only => [:add_block, :remove_block, :order_blocks]
39 39
40 40 def index
41 41 page
42 42 render :action => 'page'
43 43 end
44 44
45 45 # Show user's page
46 46 def page
47 @user = self.logged_in_user
47 @user = User.current
48 48 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT
49 49 end
50 50
51 51 # Edit user's account
52 52 def account
53 53 @user = User.current
54 54 @pref = @user.pref
55 55 if request.post?
56 56 @user.attributes = params[:user]
57 57 @user.mail_notification = (params[:notification_option] == 'all')
58 58 @user.pref.attributes = params[:pref]
59 59 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
60 60 if @user.save
61 61 @user.pref.save
62 62 @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : [])
63 63 set_language_if_valid @user.language
64 64 flash[:notice] = l(:notice_account_updated)
65 65 redirect_to :action => 'account'
66 66 return
67 67 end
68 68 end
69 69 @notification_options = [[l(:label_user_mail_option_all), 'all'],
70 70 [l(:label_user_mail_option_none), 'none']]
71 71 # Only users that belong to more than 1 project can select projects for which they are notified
72 72 # Note that @user.membership.size would fail since AR ignores :include association option when doing a count
73 73 @notification_options.insert 1, [l(:label_user_mail_option_selected), 'selected'] if @user.memberships.length > 1
74 74 @notification_option = @user.mail_notification? ? 'all' : (@user.notified_projects_ids.empty? ? 'none' : 'selected')
75 75 end
76 76
77 77 # Manage user's password
78 78 def password
79 @user = self.logged_in_user
79 @user = User.current
80 80 flash[:error] = l(:notice_can_t_change_password) and redirect_to :action => 'account' and return if @user.auth_source_id
81 81 if request.post?
82 82 if @user.check_password?(params[:password])
83 83 @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
84 84 if @user.save
85 85 flash[:notice] = l(:notice_account_password_updated)
86 86 redirect_to :action => 'account'
87 87 end
88 88 else
89 89 flash[:error] = l(:notice_account_wrong_password)
90 90 end
91 91 end
92 92 end
93 93
94 94 # Create a new feeds key
95 95 def reset_rss_key
96 96 if request.post? && User.current.rss_token
97 97 User.current.rss_token.destroy
98 98 flash[:notice] = l(:notice_feeds_access_key_reseted)
99 99 end
100 100 redirect_to :action => 'account'
101 101 end
102 102
103 103 # User's page layout configuration
104 104 def page_layout
105 @user = self.logged_in_user
105 @user = User.current
106 106 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
107 107 session[:page_layout] = @blocks
108 108 %w(top left right).each {|f| session[:page_layout][f] ||= [] }
109 109 @block_options = []
110 110 BLOCKS.each {|k, v| @block_options << [l(v), k]}
111 111 end
112 112
113 113 # Add a block to user's page
114 114 # The block is added on top of the page
115 115 # params[:block] : id of the block to add
116 116 def add_block
117 117 block = params[:block]
118 118 render(:nothing => true) and return unless block && (BLOCKS.keys.include? block)
119 @user = self.logged_in_user
119 @user = User.current
120 120 # remove if already present in a group
121 121 %w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block }
122 122 # add it on top
123 123 session[:page_layout]['top'].unshift block
124 124 render :partial => "block", :locals => {:user => @user, :block_name => block}
125 125 end
126 126
127 127 # Remove a block to user's page
128 128 # params[:block] : id of the block to remove
129 129 def remove_block
130 130 block = params[:block]
131 131 # remove block in all groups
132 132 %w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block }
133 133 render :nothing => true
134 134 end
135 135
136 136 # Change blocks order on user's page
137 137 # params[:group] : group to order (top, left or right)
138 138 # params[:list-(top|left|right)] : array of block ids of the group
139 139 def order_blocks
140 140 group = params[:group]
141 141 group_items = params["list-#{group}"]
142 142 if group_items and group_items.is_a? Array
143 143 # remove group blocks if they are presents in other groups
144 144 %w(top left right).each {|f|
145 145 session[:page_layout][f] = (session[:page_layout][f] || []) - group_items
146 146 }
147 147 session[:page_layout][group] = group_items
148 148 end
149 149 render :nothing => true
150 150 end
151 151
152 152 # Save user's page layout
153 153 def page_layout_save
154 @user = self.logged_in_user
154 @user = User.current
155 155 @user.pref[:my_page_layout] = session[:page_layout] if session[:page_layout]
156 156 @user.pref.save
157 157 session[:page_layout] = nil
158 158 redirect_to :action => 'page'
159 159 end
160 160 end
@@ -1,82 +1,82
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class NewsController < ApplicationController
19 19 layout 'base'
20 20 before_filter :find_project, :authorize, :except => :index
21 21 before_filter :find_optional_project, :only => :index
22 22 accept_key_auth :index
23 23
24 24 def index
25 25 @news_pages, @newss = paginate :news,
26 26 :per_page => 10,
27 27 :conditions => (@project ? {:project_id => @project.id} : Project.visible_by(User.current)),
28 28 :include => [:author, :project],
29 29 :order => "#{News.table_name}.created_on DESC"
30 30 respond_to do |format|
31 31 format.html { render :layout => false if request.xhr? }
32 32 format.atom { render_feed(@newss, :title => (@project ? @project.name : Setting.app_title) + ": #{l(:label_news_plural)}") }
33 33 end
34 34 end
35 35
36 36 def show
37 37 end
38 38
39 39 def edit
40 40 if request.post? and @news.update_attributes(params[:news])
41 41 flash[:notice] = l(:notice_successful_update)
42 42 redirect_to :action => 'show', :id => @news
43 43 end
44 44 end
45 45
46 46 def add_comment
47 47 @comment = Comment.new(params[:comment])
48 @comment.author = logged_in_user
48 @comment.author = User.current
49 49 if @news.comments << @comment
50 50 flash[:notice] = l(:label_comment_added)
51 51 redirect_to :action => 'show', :id => @news
52 52 else
53 53 render :action => 'show'
54 54 end
55 55 end
56 56
57 57 def destroy_comment
58 58 @news.comments.find(params[:comment_id]).destroy
59 59 redirect_to :action => 'show', :id => @news
60 60 end
61 61
62 62 def destroy
63 63 @news.destroy
64 64 redirect_to :action => 'index', :project_id => @project
65 65 end
66 66
67 67 private
68 68 def find_project
69 69 @news = News.find(params[:id])
70 70 @project = @news.project
71 71 rescue ActiveRecord::RecordNotFound
72 72 render_404
73 73 end
74 74
75 75 def find_optional_project
76 76 return true unless params[:project_id]
77 77 @project = Project.find(params[:project_id])
78 78 authorize
79 79 rescue ActiveRecord::RecordNotFound
80 80 render_404
81 81 end
82 82 end
@@ -1,551 +1,550
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class ProjectsController < ApplicationController
19 19 layout 'base'
20 20 before_filter :find_project, :except => [ :index, :list, :add ]
21 21 before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy ]
22 22 before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
23 23 accept_key_auth :activity, :calendar
24 24
25 25 cache_sweeper :project_sweeper, :only => [ :add, :edit, :archive, :unarchive, :destroy ]
26 26 cache_sweeper :issue_sweeper, :only => [ :add_issue ]
27 27 cache_sweeper :version_sweeper, :only => [ :add_version ]
28 28
29 29 helper :sort
30 30 include SortHelper
31 31 helper :custom_fields
32 32 include CustomFieldsHelper
33 33 helper :ifpdf
34 34 include IfpdfHelper
35 35 helper :issues
36 36 helper IssuesHelper
37 37 helper :queries
38 38 include QueriesHelper
39 39 helper :repositories
40 40 include RepositoriesHelper
41 41 include ProjectsHelper
42 42
43 43 def index
44 44 list
45 45 render :action => 'list' unless request.xhr?
46 46 end
47 47
48 48 # Lists visible projects
49 49 def list
50 50 projects = Project.find :all,
51 :conditions => Project.visible_by(logged_in_user),
51 :conditions => Project.visible_by(User.current),
52 52 :include => :parent
53 53 @project_tree = projects.group_by {|p| p.parent || p}
54 54 @project_tree.each_key {|p| @project_tree[p] -= [p]}
55 55 end
56 56
57 57 # Add a new project
58 58 def add
59 59 @custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
60 60 @root_projects = Project.find(:all, :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}")
61 61 @project = Project.new(params[:project])
62 62 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
63 63 if request.get?
64 64 @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project) }
65 65 else
66 66 @project.custom_fields = CustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
67 67 @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) }
68 68 @project.custom_values = @custom_values
69 69 if @project.save
70 70 @project.enabled_module_names = params[:enabled_modules]
71 71 flash[:notice] = l(:notice_successful_create)
72 72 redirect_to :controller => 'admin', :action => 'projects'
73 73 end
74 74 end
75 75 end
76 76
77 77 # Show @project
78 78 def show
79 79 @custom_values = @project.custom_values.find(:all, :include => :custom_field, :order => "#{CustomField.table_name}.position")
80 80 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
81 81 @subprojects = @project.active_children
82 82 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
83 83 @trackers = Tracker.find(:all, :order => 'position')
84 84 @open_issues_by_tracker = Issue.count(:group => :tracker, :joins => "INNER JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{Issue.table_name}.status_id", :conditions => ["project_id=? and #{IssueStatus.table_name}.is_closed=?", @project.id, false])
85 85 @total_issues_by_tracker = Issue.count(:group => :tracker, :conditions => ["project_id=?", @project.id])
86 86 @total_hours = @project.time_entries.sum(:hours)
87 87 @key = User.current.rss_key
88 88 end
89 89
90 90 def settings
91 91 @root_projects = Project::find(:all, :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id])
92 92 @custom_fields = IssueCustomField.find(:all)
93 93 @issue_category ||= IssueCategory.new
94 94 @member ||= @project.members.new
95 95 @custom_values ||= ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
96 96 @repository ||= @project.repository
97 97 @wiki ||= @project.wiki
98 98 end
99 99
100 100 # Edit @project
101 101 def edit
102 102 if request.post?
103 103 @project.custom_fields = IssueCustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
104 104 if params[:custom_fields]
105 105 @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
106 106 @project.custom_values = @custom_values
107 107 end
108 108 @project.attributes = params[:project]
109 109 if @project.save
110 110 flash[:notice] = l(:notice_successful_update)
111 111 redirect_to :action => 'settings', :id => @project
112 112 else
113 113 settings
114 114 render :action => 'settings'
115 115 end
116 116 end
117 117 end
118 118
119 119 def modules
120 120 @project.enabled_module_names = params[:enabled_modules]
121 121 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
122 122 end
123 123
124 124 def archive
125 125 @project.archive if request.post? && @project.active?
126 126 redirect_to :controller => 'admin', :action => 'projects'
127 127 end
128 128
129 129 def unarchive
130 130 @project.unarchive if request.post? && !@project.active?
131 131 redirect_to :controller => 'admin', :action => 'projects'
132 132 end
133 133
134 134 # Delete @project
135 135 def destroy
136 136 @project_to_destroy = @project
137 137 if request.post? and params[:confirm]
138 138 @project_to_destroy.destroy
139 139 redirect_to :controller => 'admin', :action => 'projects'
140 140 end
141 141 # hide project in layout
142 142 @project = nil
143 143 end
144 144
145 145 # Add a new issue category to @project
146 146 def add_issue_category
147 147 @category = @project.issue_categories.build(params[:category])
148 148 if request.post? and @category.save
149 149 respond_to do |format|
150 150 format.html do
151 151 flash[:notice] = l(:notice_successful_create)
152 152 redirect_to :action => 'settings', :tab => 'categories', :id => @project
153 153 end
154 154 format.js do
155 155 # IE doesn't support the replace_html rjs method for select box options
156 156 render(:update) {|page| page.replace "issue_category_id",
157 157 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]')
158 158 }
159 159 end
160 160 end
161 161 end
162 162 end
163 163
164 164 # Add a new version to @project
165 165 def add_version
166 166 @version = @project.versions.build(params[:version])
167 167 if request.post? and @version.save
168 168 flash[:notice] = l(:notice_successful_create)
169 169 redirect_to :action => 'settings', :tab => 'versions', :id => @project
170 170 end
171 171 end
172 172
173 173 # Add a new document to @project
174 174 def add_document
175 175 @document = @project.documents.build(params[:document])
176 176 if request.post? and @document.save
177 177 # Save the attachments
178 178 params[:attachments].each { |a|
179 Attachment.create(:container => @document, :file => a, :author => logged_in_user) unless a.size == 0
179 Attachment.create(:container => @document, :file => a, :author => User.current) unless a.size == 0
180 180 } if params[:attachments] and params[:attachments].is_a? Array
181 181 flash[:notice] = l(:notice_successful_create)
182 182 Mailer.deliver_document_added(@document) if Setting.notified_events.include?('document_added')
183 183 redirect_to :action => 'list_documents', :id => @project
184 184 end
185 185 end
186 186
187 187 # Show documents list of @project
188 188 def list_documents
189 189 @sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category'
190 190 documents = @project.documents.find :all, :include => [:attachments, :category]
191 191 case @sort_by
192 192 when 'date'
193 193 @grouped = documents.group_by {|d| d.created_on.to_date }
194 194 when 'title'
195 195 @grouped = documents.group_by {|d| d.title.first.upcase}
196 196 when 'author'
197 197 @grouped = documents.select{|d| d.attachments.any?}.group_by {|d| d.attachments.last.author}
198 198 else
199 199 @grouped = documents.group_by(&:category)
200 200 end
201 201 render :layout => false if request.xhr?
202 202 end
203 203
204 204 # Add a new issue to @project
205 205 # The new issue will be created from an existing one if copy_from parameter is given
206 206 def add_issue
207 207 @issue = params[:copy_from] ? Issue.new.copy_from(params[:copy_from]) : Issue.new(params[:issue])
208 208 @issue.project = @project
209 209 @issue.author = User.current
210 210 @issue.tracker ||= Tracker.find(params[:tracker_id])
211 211
212 212 default_status = IssueStatus.default
213 213 unless default_status
214 214 flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
215 215 render :nothing => true, :layout => true
216 216 return
217 217 end
218 218 @issue.status = default_status
219 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker))if logged_in_user
219 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker))
220 220
221 221 if request.get?
222 222 @issue.start_date ||= Date.today
223 223 @custom_values = @issue.custom_values.empty? ?
224 224 @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) } :
225 225 @issue.custom_values
226 226 else
227 227 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
228 228 # Check that the user is allowed to apply the requested status
229 229 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
230 230 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
231 231 @issue.custom_values = @custom_values
232 232 if @issue.save
233 233 if params[:attachments] && params[:attachments].is_a?(Array)
234 234 # Save attachments
235 235 params[:attachments].each {|a| Attachment.create(:container => @issue, :file => a, :author => User.current) unless a.size == 0}
236 236 end
237 237 flash[:notice] = l(:notice_successful_create)
238 238 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
239 239 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
240 240 return
241 241 end
242 242 end
243 243 @priorities = Enumeration::get_values('IPRI')
244 244 end
245 245
246 246 # Bulk edit issues
247 247 def bulk_edit_issues
248 248 if request.post?
249 249 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
250 250 priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
251 251 assigned_to = params[:assigned_to_id].blank? ? nil : User.find_by_id(params[:assigned_to_id])
252 252 category = params[:category_id].blank? ? nil : @project.issue_categories.find_by_id(params[:category_id])
253 253 fixed_version = params[:fixed_version_id].blank? ? nil : @project.versions.find_by_id(params[:fixed_version_id])
254 254 issues = @project.issues.find_all_by_id(params[:issue_ids])
255 255 unsaved_issue_ids = []
256 256 issues.each do |issue|
257 257 journal = issue.init_journal(User.current, params[:notes])
258 258 issue.priority = priority if priority
259 259 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
260 260 issue.category = category if category
261 261 issue.fixed_version = fixed_version if fixed_version
262 262 issue.start_date = params[:start_date] unless params[:start_date].blank?
263 263 issue.due_date = params[:due_date] unless params[:due_date].blank?
264 264 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
265 265 # Don't save any change to the issue if the user is not authorized to apply the requested status
266 266 if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
267 267 # Send notification for each issue (if changed)
268 268 Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
269 269 else
270 270 # Keep unsaved issue ids to display them in flash error
271 271 unsaved_issue_ids << issue.id
272 272 end
273 273 end
274 274 if unsaved_issue_ids.empty?
275 275 flash[:notice] = l(:notice_successful_update) unless issues.empty?
276 276 else
277 277 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, issues.size, '#' + unsaved_issue_ids.join(', #'))
278 278 end
279 279 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
280 280 return
281 281 end
282 282 if current_role && User.current.allowed_to?(:change_issue_status, @project)
283 283 # Find potential statuses the user could be allowed to switch issues to
284 284 @available_statuses = Workflow.find(:all, :include => :new_status,
285 285 :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq
286 286 end
287 287 render :update do |page|
288 288 page.hide 'query_form'
289 289 page.replace_html 'bulk-edit', :partial => 'issues/bulk_edit_form'
290 290 end
291 291 end
292 292
293 293 def move_issues
294 294 @issues = @project.issues.find(params[:issue_ids]) if params[:issue_ids]
295 295 redirect_to :controller => 'issues', :action => 'index', :project_id => @project and return unless @issues
296 296 @projects = []
297 297 # find projects to which the user is allowed to move the issue
298 298 if User.current.admin?
299 299 # admin is allowed to move issues to any active (visible) project
300 300 @projects = Project.find(:all, :conditions => Project.visible_by(User.current), :order => 'name')
301 301 else
302 302 User.current.memberships.each {|m| @projects << m.project if m.role.allowed_to?(:move_issues)}
303 303 end
304 304 # issue can be moved to any tracker
305 305 @trackers = Tracker.find(:all)
306 306 if request.post? && params[:new_project_id] && @projects.collect(&:id).include?(params[:new_project_id].to_i) && params[:new_tracker_id]
307 307 new_project = Project.find_by_id(params[:new_project_id])
308 308 new_tracker = params[:new_tracker_id].blank? ? nil : Tracker.find_by_id(params[:new_tracker_id])
309 309 unsaved_issue_ids = []
310 310 @issues.each do |issue|
311 311 unsaved_issue_ids << issue.id unless issue.move_to(new_project, new_tracker)
312 312 end
313 313 if unsaved_issue_ids.empty?
314 314 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
315 315 else
316 316 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
317 317 end
318 318 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
319 319 end
320 320 end
321 321
322 322 # Add a news to @project
323 323 def add_news
324 @news = News.new(:project => @project)
324 @news = News.new(:project => @project, :author => User.current)
325 325 if request.post?
326 326 @news.attributes = params[:news]
327 @news.author_id = self.logged_in_user.id if self.logged_in_user
328 327 if @news.save
329 328 flash[:notice] = l(:notice_successful_create)
330 329 Mailer.deliver_news_added(@news) if Setting.notified_events.include?('news_added')
331 330 redirect_to :controller => 'news', :action => 'index', :project_id => @project
332 331 end
333 332 end
334 333 end
335 334
336 335 def add_file
337 336 if request.post?
338 337 @version = @project.versions.find_by_id(params[:version_id])
339 338 # Save the attachments
340 339 @attachments = []
341 340 params[:attachments].each { |file|
342 341 next unless file.size > 0
343 a = Attachment.create(:container => @version, :file => file, :author => logged_in_user)
342 a = Attachment.create(:container => @version, :file => file, :author => User.current)
344 343 @attachments << a unless a.new_record?
345 344 } if params[:attachments] and params[:attachments].is_a? Array
346 345 Mailer.deliver_attachments_added(@attachments) if !@attachments.empty? && Setting.notified_events.include?('file_added')
347 346 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
348 347 end
349 348 @versions = @project.versions.sort
350 349 end
351 350
352 351 def list_files
353 352 @versions = @project.versions.sort
354 353 end
355 354
356 355 # Show changelog for @project
357 356 def changelog
358 357 @trackers = Tracker.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
359 358 retrieve_selected_tracker_ids(@trackers)
360 359 @versions = @project.versions.sort
361 360 end
362 361
363 362 def roadmap
364 363 @trackers = Tracker.find(:all, :conditions => ["is_in_roadmap=?", true], :order => 'position')
365 364 retrieve_selected_tracker_ids(@trackers)
366 365 @versions = @project.versions.sort
367 366 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
368 367 end
369 368
370 369 def activity
371 370 if params[:year] and params[:year].to_i > 1900
372 371 @year = params[:year].to_i
373 372 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
374 373 @month = params[:month].to_i
375 374 end
376 375 end
377 376 @year ||= Date.today.year
378 377 @month ||= Date.today.month
379 378
380 379 case params[:format]
381 380 when 'atom'
382 381 # 30 last days
383 382 @date_from = Date.today - 30
384 383 @date_to = Date.today + 1
385 384 else
386 385 # current month
387 386 @date_from = Date.civil(@year, @month, 1)
388 387 @date_to = @date_from >> 1
389 388 end
390 389
391 390 @event_types = %w(issues news files documents changesets wiki_pages messages)
392 391 @event_types.delete('wiki_pages') unless @project.wiki
393 392 @event_types.delete('changesets') unless @project.repository
394 393 @event_types.delete('messages') unless @project.boards.any?
395 394 # only show what the user is allowed to view
396 395 @event_types = @event_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)}
397 396
398 397 @scope = @event_types.select {|t| params["show_#{t}"]}
399 398 # default events if none is specified in parameters
400 399 @scope = (@event_types - %w(wiki_pages messages))if @scope.empty?
401 400
402 401 @events = []
403 402
404 403 if @scope.include?('issues')
405 404 @events += @project.issues.find(:all, :include => [:author, :tracker], :conditions => ["#{Issue.table_name}.created_on>=? and #{Issue.table_name}.created_on<=?", @date_from, @date_to] )
406 405 @events += @project.issues_status_changes(@date_from, @date_to)
407 406 end
408 407
409 408 if @scope.include?('news')
410 409 @events += @project.news.find(:all, :conditions => ["#{News.table_name}.created_on>=? and #{News.table_name}.created_on<=?", @date_from, @date_to], :include => :author )
411 410 end
412 411
413 412 if @scope.include?('files')
414 413 @events += Attachment.find(:all, :select => "#{Attachment.table_name}.*", :joins => "LEFT JOIN #{Version.table_name} ON #{Version.table_name}.id = #{Attachment.table_name}.container_id", :conditions => ["#{Attachment.table_name}.container_type='Version' and #{Version.table_name}.project_id=? and #{Attachment.table_name}.created_on>=? and #{Attachment.table_name}.created_on<=?", @project.id, @date_from, @date_to], :include => :author )
415 414 end
416 415
417 416 if @scope.include?('documents')
418 417 @events += @project.documents.find(:all, :conditions => ["#{Document.table_name}.created_on>=? and #{Document.table_name}.created_on<=?", @date_from, @date_to] )
419 418 @events += Attachment.find(:all, :select => "attachments.*", :joins => "LEFT JOIN #{Document.table_name} ON #{Document.table_name}.id = #{Attachment.table_name}.container_id", :conditions => ["#{Attachment.table_name}.container_type='Document' and #{Document.table_name}.project_id=? and #{Attachment.table_name}.created_on>=? and #{Attachment.table_name}.created_on<=?", @project.id, @date_from, @date_to], :include => :author )
420 419 end
421 420
422 421 if @scope.include?('wiki_pages')
423 422 select = "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
424 423 "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
425 424 "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
426 425 "#{WikiContent.versioned_table_name}.id"
427 426 joins = "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
428 427 "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id "
429 428 conditions = ["#{Wiki.table_name}.project_id = ? AND #{WikiContent.versioned_table_name}.updated_on BETWEEN ? AND ?",
430 429 @project.id, @date_from, @date_to]
431 430
432 431 @events += WikiContent.versioned_class.find(:all, :select => select, :joins => joins, :conditions => conditions)
433 432 end
434 433
435 434 if @scope.include?('changesets')
436 435 @events += @project.repository.changesets.find(:all, :conditions => ["#{Changeset.table_name}.committed_on BETWEEN ? AND ?", @date_from, @date_to])
437 436 end
438 437
439 438 if @scope.include?('messages')
440 439 @events += Message.find(:all,
441 440 :include => [:board, :author],
442 441 :conditions => ["#{Board.table_name}.project_id=? AND #{Message.table_name}.parent_id IS NULL AND #{Message.table_name}.created_on BETWEEN ? AND ?", @project.id, @date_from, @date_to])
443 442 end
444 443
445 444 @events_by_day = @events.group_by(&:event_date)
446 445
447 446 respond_to do |format|
448 447 format.html { render :layout => false if request.xhr? }
449 448 format.atom { render_feed(@events, :title => "#{@project.name}: #{l(:label_activity)}") }
450 449 end
451 450 end
452 451
453 452 def calendar
454 453 @trackers = Tracker.find(:all, :order => 'position')
455 454 retrieve_selected_tracker_ids(@trackers)
456 455
457 456 if params[:year] and params[:year].to_i > 1900
458 457 @year = params[:year].to_i
459 458 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
460 459 @month = params[:month].to_i
461 460 end
462 461 end
463 462 @year ||= Date.today.year
464 463 @month ||= Date.today.month
465 464 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
466 465
467 466 events = []
468 467 @project.issues_with_subprojects(params[:with_subprojects]) do
469 468 events += Issue.find(:all,
470 469 :include => [:tracker, :status, :assigned_to, :priority, :project],
471 470 :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]
472 471 ) unless @selected_tracker_ids.empty?
473 472 end
474 473 events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
475 474 @calendar.events = events
476 475
477 476 render :layout => false if request.xhr?
478 477 end
479 478
480 479 def gantt
481 480 @trackers = Tracker.find(:all, :order => 'position')
482 481 retrieve_selected_tracker_ids(@trackers)
483 482
484 483 if params[:year] and params[:year].to_i >0
485 484 @year_from = params[:year].to_i
486 485 if params[:month] and params[:month].to_i >=1 and params[:month].to_i <= 12
487 486 @month_from = params[:month].to_i
488 487 else
489 488 @month_from = 1
490 489 end
491 490 else
492 491 @month_from ||= Date.today.month
493 492 @year_from ||= Date.today.year
494 493 end
495 494
496 495 zoom = (params[:zoom] || User.current.pref[:gantt_zoom]).to_i
497 496 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
498 497 months = (params[:months] || User.current.pref[:gantt_months]).to_i
499 498 @months = (months > 0 && months < 25) ? months : 6
500 499
501 500 # Save gantt paramters as user preference (zoom and months count)
502 501 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || @months != User.current.pref[:gantt_months]))
503 502 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
504 503 User.current.preference.save
505 504 end
506 505
507 506 @date_from = Date.civil(@year_from, @month_from, 1)
508 507 @date_to = (@date_from >> @months) - 1
509 508
510 509 @events = []
511 510 @project.issues_with_subprojects(params[:with_subprojects]) do
512 511 @events += Issue.find(:all,
513 512 :order => "start_date, due_date",
514 513 :include => [:tracker, :status, :assigned_to, :priority, :project],
515 514 :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]
516 515 ) unless @selected_tracker_ids.empty?
517 516 end
518 517 @events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
519 518 @events.sort! {|x,y| x.start_date <=> y.start_date }
520 519
521 520 if params[:format]=='pdf'
522 521 @options_for_rfpdf ||= {}
523 522 @options_for_rfpdf[:file_name] = "#{@project.identifier}-gantt.pdf"
524 523 render :template => "projects/gantt.rfpdf", :layout => false
525 524 elsif params[:format]=='png' && respond_to?('gantt_image')
526 525 image = gantt_image(@events, @date_from, @months, @zoom)
527 526 image.format = 'PNG'
528 527 send_data(image.to_blob, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png")
529 528 else
530 529 render :template => "projects/gantt.rhtml"
531 530 end
532 531 end
533 532
534 533 private
535 534 # Find project of id params[:id]
536 535 # if not found, redirect to project list
537 536 # Used as a before_filter
538 537 def find_project
539 538 @project = Project.find(params[:id])
540 539 rescue ActiveRecord::RecordNotFound
541 540 render_404
542 541 end
543 542
544 543 def retrieve_selected_tracker_ids(selectable_trackers)
545 544 if ids = params[:tracker_ids]
546 545 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
547 546 else
548 547 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
549 548 end
550 549 end
551 550 end
@@ -1,83 +1,81
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class QueriesController < ApplicationController
19 19 layout 'base'
20 20 before_filter :find_project, :authorize
21 21
22 22 def index
23 23 @queries = @project.queries.find(:all,
24 24 :order => "name ASC",
25 :conditions => ["is_public = ? or user_id = ?", true, (logged_in_user ? logged_in_user.id : 0)])
25 :conditions => ["is_public = ? or user_id = ?", true, (User.current.logged? ? User.current.id : 0)])
26 26 end
27 27
28 28 def new
29 29 @query = Query.new(params[:query])
30 30 @query.project = @project
31 @query.user = logged_in_user
32 @query.executed_by = logged_in_user
31 @query.user = User.current
33 32 @query.is_public = false unless current_role.allowed_to?(:manage_public_queries)
34 33 @query.column_names = nil if params[:default_columns]
35 34
36 35 params[:fields].each do |field|
37 36 @query.add_filter(field, params[:operators][field], params[:values][field])
38 37 end if params[:fields]
39 38
40 39 if request.post? && params[:confirm] && @query.save
41 40 flash[:notice] = l(:notice_successful_create)
42 41 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
43 42 return
44 43 end
45 44 render :layout => false if request.xhr?
46 45 end
47 46
48 47 def edit
49 48 if request.post?
50 49 @query.filters = {}
51 50 params[:fields].each do |field|
52 51 @query.add_filter(field, params[:operators][field], params[:values][field])
53 52 end if params[:fields]
54 53 @query.attributes = params[:query]
55 54 @query.is_public = false unless current_role.allowed_to?(:manage_public_queries)
56 55 @query.column_names = nil if params[:default_columns]
57 56
58 57 if @query.save
59 58 flash[:notice] = l(:notice_successful_update)
60 59 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
61 60 end
62 61 end
63 62 end
64 63
65 64 def destroy
66 65 @query.destroy if request.post?
67 66 redirect_to :controller => 'queries', :project_id => @project
68 67 end
69 68
70 69 private
71 70 def find_project
72 71 if params[:id]
73 72 @query = Query.find(params[:id])
74 @query.executed_by = logged_in_user
75 73 @project = @query.project
76 render_403 unless @query.editable_by?(logged_in_user)
74 render_403 unless @query.editable_by?(User.current)
77 75 else
78 76 @project = Project.find(params[:project_id])
79 77 end
80 78 rescue ActiveRecord::RecordNotFound
81 79 render_404
82 80 end
83 81 end
@@ -1,109 +1,109
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class SearchController < ApplicationController
19 19 layout 'base'
20 20
21 21 helper :messages
22 22 include MessagesHelper
23 23
24 24 def index
25 25 @question = params[:q] || ""
26 26 @question.strip!
27 27 @all_words = params[:all_words] || (params[:submit] ? false : true)
28 28 @titles_only = !params[:titles_only].nil?
29 29
30 30 offset = nil
31 31 begin; offset = params[:offset].to_time if params[:offset]; rescue; end
32 32
33 33 # quick jump to an issue
34 if @question.match(/^#?(\d+)$/) && Issue.find_by_id($1, :include => :project, :conditions => Project.visible_by(logged_in_user))
34 if @question.match(/^#?(\d+)$/) && Issue.find_by_id($1, :include => :project, :conditions => Project.visible_by(User.current))
35 35 redirect_to :controller => "issues", :action => "show", :id => $1
36 36 return
37 37 end
38 38
39 39 if params[:id]
40 40 find_project
41 41 return unless check_project_privacy
42 42 end
43 43
44 44 if @project
45 45 # only show what the user is allowed to view
46 46 @object_types = %w(issues news documents changesets wiki_pages messages)
47 47 @object_types = @object_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)}
48 48
49 49 @scope = @object_types.select {|t| params[t]}
50 50 @scope = @object_types if @scope.empty?
51 51 else
52 52 @object_types = @scope = %w(projects)
53 53 end
54 54
55 55 # tokens must be at least 3 character long
56 56 @tokens = @question.split.uniq.select {|w| w.length > 2 }
57 57
58 58 if !@tokens.empty?
59 59 # no more than 5 tokens to search for
60 60 @tokens.slice! 5..-1 if @tokens.size > 5
61 61 # strings used in sql like statement
62 62 like_tokens = @tokens.collect {|w| "%#{w.downcase}%"}
63 63 @results = []
64 64 limit = 10
65 65 if @project
66 66 @scope.each do |s|
67 67 @results += s.singularize.camelcase.constantize.search(like_tokens, @project,
68 68 :all_words => @all_words,
69 69 :titles_only => @titles_only,
70 70 :limit => (limit+1),
71 71 :offset => offset,
72 72 :before => params[:previous].nil?)
73 73 end
74 74 @results = @results.sort {|a,b| b.event_datetime <=> a.event_datetime}
75 75 if params[:previous].nil?
76 76 @pagination_previous_date = @results[0].event_datetime if offset && @results[0]
77 77 if @results.size > limit
78 78 @pagination_next_date = @results[limit-1].event_datetime
79 79 @results = @results[0, limit]
80 80 end
81 81 else
82 82 @pagination_next_date = @results[-1].event_datetime if offset && @results[-1]
83 83 if @results.size > limit
84 84 @pagination_previous_date = @results[-(limit)].event_datetime
85 85 @results = @results[-(limit), limit]
86 86 end
87 87 end
88 88 else
89 89 operator = @all_words ? ' AND ' : ' OR '
90 Project.with_scope(:find => {:conditions => Project.visible_by(logged_in_user)}) do
90 Project.with_scope(:find => {:conditions => Project.visible_by(User.current)}) do
91 91 @results += Project.find(:all, :limit => limit, :conditions => [ (["(LOWER(name) like ? OR LOWER(description) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort] ) if @scope.include? 'projects'
92 92 end
93 93 # if only one project is found, user is redirected to its overview
94 94 redirect_to :controller => 'projects', :action => 'show', :id => @results.first and return if @results.size == 1
95 95 end
96 96 @question = @tokens.join(" ")
97 97 else
98 98 @question = ""
99 99 end
100 100 render :layout => false if request.xhr?
101 101 end
102 102
103 103 private
104 104 def find_project
105 105 @project = Project.find(params[:id])
106 106 rescue ActiveRecord::RecordNotFound
107 107 render_404
108 108 end
109 109 end
@@ -1,172 +1,172
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class TimelogController < ApplicationController
19 19 layout 'base'
20 20 before_filter :find_project, :authorize
21 21
22 22 helper :sort
23 23 include SortHelper
24 24 helper :issues
25 25
26 26 def report
27 27 @available_criterias = { 'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
28 28 :values => @project.versions,
29 29 :label => :label_version},
30 30 'category' => {:sql => "#{Issue.table_name}.category_id",
31 31 :values => @project.issue_categories,
32 32 :label => :field_category},
33 33 'member' => {:sql => "#{TimeEntry.table_name}.user_id",
34 34 :values => @project.users,
35 35 :label => :label_member},
36 36 'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
37 37 :values => Tracker.find(:all),
38 38 :label => :label_tracker},
39 39 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
40 40 :values => Enumeration::get_values('ACTI'),
41 41 :label => :label_activity}
42 42 }
43 43
44 44 @criterias = params[:criterias] || []
45 45 @criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
46 46 @criterias.uniq!
47 47
48 48 @columns = (params[:period] && %w(year month week).include?(params[:period])) ? params[:period] : 'month'
49 49
50 50 if params[:date_from]
51 51 begin; @date_from = params[:date_from].to_date; rescue; end
52 52 end
53 53 if params[:date_to]
54 54 begin; @date_to = params[:date_to].to_date; rescue; end
55 55 end
56 56 @date_from ||= Date.civil(Date.today.year, 1, 1)
57 57 @date_to ||= Date.civil(Date.today.year, Date.today.month+1, 1) - 1
58 58
59 59 unless @criterias.empty?
60 60 sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
61 61 sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
62 62
63 63 sql = "SELECT #{sql_select}, tyear, tmonth, tweek, SUM(hours) AS hours"
64 64 sql << " FROM #{TimeEntry.table_name} LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
65 65 sql << " WHERE #{TimeEntry.table_name}.project_id = %s" % @project.id
66 66 sql << " AND spent_on BETWEEN '%s' AND '%s'" % [ActiveRecord::Base.connection.quoted_date(@date_from.to_time), ActiveRecord::Base.connection.quoted_date(@date_to.to_time)]
67 67 sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek"
68 68
69 69 @hours = ActiveRecord::Base.connection.select_all(sql)
70 70
71 71 @hours.each do |row|
72 72 case @columns
73 73 when 'year'
74 74 row['year'] = row['tyear']
75 75 when 'month'
76 76 row['month'] = "#{row['tyear']}-#{row['tmonth']}"
77 77 when 'week'
78 78 row['week'] = "#{row['tyear']}-#{row['tweek']}"
79 79 end
80 80 end
81 81 end
82 82
83 83 @periods = []
84 84 date_from = @date_from
85 85 # 100 columns max
86 86 while date_from < @date_to && @periods.length < 100
87 87 case @columns
88 88 when 'year'
89 89 @periods << "#{date_from.year}"
90 90 date_from = date_from >> 12
91 91 when 'month'
92 92 @periods << "#{date_from.year}-#{date_from.month}"
93 93 date_from = date_from >> 1
94 94 when 'week'
95 95 @periods << "#{date_from.year}-#{date_from.cweek}"
96 96 date_from = date_from + 7
97 97 end
98 98 end
99 99
100 100 render :layout => false if request.xhr?
101 101 end
102 102
103 103 def details
104 104 sort_init 'spent_on', 'desc'
105 105 sort_update
106 106
107 107 @entries = (@issue ? @issue : @project).time_entries.find(:all, :include => [:activity, :user, {:issue => [:tracker, :assigned_to, :priority]}], :order => sort_clause)
108 108
109 109 @total_hours = @entries.inject(0) { |sum,entry| sum + entry.hours }
110 @owner_id = logged_in_user ? logged_in_user.id : 0
110 @owner_id = User.current.id
111 111
112 112 send_csv and return if 'csv' == params[:export]
113 113 render :action => 'details', :layout => false if request.xhr?
114 114 end
115 115
116 116 def edit
117 render_404 and return if @time_entry && @time_entry.user != logged_in_user
118 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => logged_in_user, :spent_on => Date.today)
117 render_404 and return if @time_entry && @time_entry.user != User.current
118 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
119 119 @time_entry.attributes = params[:time_entry]
120 120 if request.post? and @time_entry.save
121 121 flash[:notice] = l(:notice_successful_update)
122 122 redirect_to :action => 'details', :project_id => @time_entry.project, :issue_id => @time_entry.issue
123 123 return
124 124 end
125 125 @activities = Enumeration::get_values('ACTI')
126 126 end
127 127
128 128 private
129 129 def find_project
130 130 if params[:id]
131 131 @time_entry = TimeEntry.find(params[:id])
132 132 @project = @time_entry.project
133 133 elsif params[:issue_id]
134 134 @issue = Issue.find(params[:issue_id])
135 135 @project = @issue.project
136 136 elsif params[:project_id]
137 137 @project = Project.find(params[:project_id])
138 138 else
139 139 render_404
140 140 return false
141 141 end
142 142 end
143 143
144 144 def send_csv
145 145 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
146 146 export = StringIO.new
147 147 CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
148 148 # csv header fields
149 149 headers = [l(:field_spent_on),
150 150 l(:field_user),
151 151 l(:field_activity),
152 152 l(:field_issue),
153 153 l(:field_hours),
154 154 l(:field_comments)
155 155 ]
156 156 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
157 157 # csv lines
158 158 @entries.each do |entry|
159 159 fields = [l_date(entry.spent_on),
160 160 entry.user.name,
161 161 entry.activity.name,
162 162 (entry.issue ? entry.issue.id : nil),
163 163 entry.hours,
164 164 entry.comments
165 165 ]
166 166 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
167 167 end
168 168 end
169 169 export.rewind
170 170 send_data(export.read, :type => 'text/csv; header=present', :filename => 'export.csv')
171 171 end
172 172 end
@@ -1,25 +1,25
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class WelcomeController < ApplicationController
19 19 layout 'base'
20 20
21 21 def index
22 @news = News.latest logged_in_user
23 @projects = Project.latest logged_in_user
22 @news = News.latest User.current
23 @projects = Project.latest User.current
24 24 end
25 25 end
@@ -1,180 +1,180
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'diff'
19 19
20 20 class WikiController < ApplicationController
21 21 layout 'base'
22 22 before_filter :find_wiki, :authorize
23 23
24 24 verify :method => :post, :only => [:destroy, :destroy_attachment], :redirect_to => { :action => :index }
25 25
26 26 helper :attachments
27 27 include AttachmentsHelper
28 28
29 29 # display a page (in editing mode if it doesn't exist)
30 30 def index
31 31 page_title = params[:page]
32 32 @page = @wiki.find_or_new_page(page_title)
33 33 if @page.new_record?
34 34 if User.current.allowed_to?(:edit_wiki_pages, @project)
35 35 edit
36 36 render :action => 'edit'
37 37 else
38 38 render_404
39 39 end
40 40 return
41 41 end
42 42 @content = @page.content_for_version(params[:version])
43 43 if params[:export] == 'html'
44 44 export = render_to_string :action => 'export', :layout => false
45 45 send_data(export, :type => 'text/html', :filename => "#{@page.title}.html")
46 46 return
47 47 elsif params[:export] == 'txt'
48 48 send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt")
49 49 return
50 50 end
51 51 render :action => 'show'
52 52 end
53 53
54 54 # edit an existing page or a new one
55 55 def edit
56 56 @page = @wiki.find_or_new_page(params[:page])
57 57 @page.content = WikiContent.new(:page => @page) if @page.new_record?
58 58
59 59 @content = @page.content_for_version(params[:version])
60 60 @content.text = "h1. #{@page.pretty_title}" if @content.text.blank?
61 61 # don't keep previous comment
62 62 @content.comments = nil
63 63 if request.post?
64 64 if !@page.new_record? && @content.text == params[:content][:text]
65 65 # don't save if text wasn't changed
66 66 redirect_to :action => 'index', :id => @project, :page => @page.title
67 67 return
68 68 end
69 69 #@content.text = params[:content][:text]
70 70 #@content.comments = params[:content][:comments]
71 71 @content.attributes = params[:content]
72 @content.author = logged_in_user
72 @content.author = User.current
73 73 # if page is new @page.save will also save content, but not if page isn't a new record
74 74 if (@page.new_record? ? @page.save : @content.save)
75 75 redirect_to :action => 'index', :id => @project, :page => @page.title
76 76 end
77 77 end
78 78 rescue ActiveRecord::StaleObjectError
79 79 # Optimistic locking exception
80 80 flash[:error] = l(:notice_locking_conflict)
81 81 end
82 82
83 83 # rename a page
84 84 def rename
85 85 @page = @wiki.find_page(params[:page])
86 86 @page.redirect_existing_links = true
87 87 # used to display the *original* title if some AR validation errors occur
88 88 @original_title = @page.pretty_title
89 89 if request.post? && @page.update_attributes(params[:wiki_page])
90 90 flash[:notice] = l(:notice_successful_update)
91 91 redirect_to :action => 'index', :id => @project, :page => @page.title
92 92 end
93 93 end
94 94
95 95 # show page history
96 96 def history
97 97 @page = @wiki.find_page(params[:page])
98 98
99 99 @version_count = @page.content.versions.count
100 100 @version_pages = Paginator.new self, @version_count, 25, params['p']
101 101 # don't load text
102 102 @versions = @page.content.versions.find :all,
103 103 :select => "id, author_id, comments, updated_on, version",
104 104 :order => 'version DESC',
105 105 :limit => @version_pages.items_per_page + 1,
106 106 :offset => @version_pages.current.offset
107 107
108 108 render :layout => false if request.xhr?
109 109 end
110 110
111 111 def diff
112 112 @page = @wiki.find_page(params[:page])
113 113 @diff = @page.diff(params[:version], params[:version_from])
114 114 render_404 unless @diff
115 115 end
116 116
117 117 # remove a wiki page and its history
118 118 def destroy
119 119 @page = @wiki.find_page(params[:page])
120 120 @page.destroy if @page
121 121 redirect_to :action => 'special', :id => @project, :page => 'Page_index'
122 122 end
123 123
124 124 # display special pages
125 125 def special
126 126 page_title = params[:page].downcase
127 127 case page_title
128 128 # show pages index, sorted by title
129 129 when 'page_index', 'date_index'
130 130 # eager load information about last updates, without loading text
131 131 @pages = @wiki.pages.find :all, :select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on",
132 132 :joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id",
133 133 :order => 'title'
134 134 @pages_by_date = @pages.group_by {|p| p.updated_on.to_date}
135 135 # export wiki to a single html file
136 136 when 'export'
137 137 @pages = @wiki.pages.find :all, :order => 'title'
138 138 export = render_to_string :action => 'export_multiple', :layout => false
139 139 send_data(export, :type => 'text/html', :filename => "wiki.html")
140 140 return
141 141 else
142 142 # requested special page doesn't exist, redirect to default page
143 143 redirect_to :action => 'index', :id => @project, :page => nil and return
144 144 end
145 145 render :action => "special_#{page_title}"
146 146 end
147 147
148 148 def preview
149 149 page = @wiki.find_page(params[:page])
150 150 @attachements = page.attachments if page
151 151 @text = params[:content][:text]
152 152 render :partial => 'common/preview'
153 153 end
154 154
155 155 def add_attachment
156 156 @page = @wiki.find_page(params[:page])
157 157 # Save the attachments
158 158 params[:attachments].each { |file|
159 159 next unless file.size > 0
160 a = Attachment.create(:container => @page, :file => file, :author => logged_in_user)
160 a = Attachment.create(:container => @page, :file => file, :author => User.current)
161 161 } if params[:attachments] and params[:attachments].is_a? Array
162 162 redirect_to :action => 'index', :page => @page.title
163 163 end
164 164
165 165 def destroy_attachment
166 166 @page = @wiki.find_page(params[:page])
167 167 @page.attachments.find(params[:attachment_id]).destroy
168 168 redirect_to :action => 'index', :page => @page.title
169 169 end
170 170
171 171 private
172 172
173 173 def find_wiki
174 174 @project = Project.find(params[:id])
175 175 @wiki = @project.wiki
176 176 render_404 unless @wiki
177 177 rescue ActiveRecord::RecordNotFound
178 178 render_404
179 179 end
180 180 end
@@ -1,110 +1,105
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require "digest/md5"
19 19
20 20 class Attachment < ActiveRecord::Base
21 21 belongs_to :container, :polymorphic => true
22 22 belongs_to :author, :class_name => "User", :foreign_key => "author_id"
23 23
24 validates_presence_of :container, :filename
24 validates_presence_of :container, :filename, :author
25 25 validates_length_of :filename, :maximum => 255
26 26 validates_length_of :disk_filename, :maximum => 255
27 27
28 28 acts_as_event :title => :filename,
29 29 :description => :filename,
30 30 :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id}}
31 31
32 32 cattr_accessor :storage_path
33 33 @@storage_path = "#{RAILS_ROOT}/files"
34 34
35 35 def validate
36 36 errors.add_to_base :too_long if self.filesize > Setting.attachment_max_size.to_i.kilobytes
37 37 end
38 38
39 39 def file=(incomming_file)
40 40 unless incomming_file.nil?
41 41 @temp_file = incomming_file
42 42 if @temp_file.size > 0
43 43 self.filename = sanitize_filename(@temp_file.original_filename)
44 44 self.disk_filename = DateTime.now.strftime("%y%m%d%H%M%S") + "_" + self.filename
45 45 self.content_type = @temp_file.content_type.chomp
46 46 self.filesize = @temp_file.size
47 47 end
48 48 end
49 49 end
50 50
51 51 def file
52 52 nil
53 53 end
54 54
55 55 # Copy temp file to its final location
56 56 def before_save
57 57 if @temp_file && (@temp_file.size > 0)
58 58 logger.debug("saving '#{self.diskfile}'")
59 59 File.open(diskfile, "wb") do |f|
60 60 f.write(@temp_file.read)
61 61 end
62 62 self.digest = Digest::MD5.hexdigest(File.read(diskfile))
63 63 end
64 64 # Don't save the content type if it's longer than the authorized length
65 65 if self.content_type && self.content_type.length > 255
66 66 self.content_type = nil
67 67 end
68 68 end
69 69
70 70 # Deletes file on the disk
71 71 def after_destroy
72 72 if self.filename?
73 73 File.delete(diskfile) if File.exist?(diskfile)
74 74 end
75 75 end
76 76
77 77 # Returns file's location on disk
78 78 def diskfile
79 79 "#{@@storage_path}/#{self.disk_filename}"
80 80 end
81 81
82 82 def increment_download
83 83 increment!(:downloads)
84 84 end
85
86 # returns last created projects
87 def self.most_downloaded
88 find(:all, :limit => 5, :order => "downloads DESC")
89 end
90 85
91 86 def project
92 87 container.is_a?(Project) ? container : container.project
93 88 end
94 89
95 90 def image?
96 91 self.filename =~ /\.(jpeg|jpg|gif|png)$/i
97 92 end
98 93
99 94 private
100 95 def sanitize_filename(value)
101 96 # get only the filename, not the whole path
102 97 just_filename = value.gsub(/^.*(\\|\/)/, '')
103 98 # NOTE: File.basename doesn't work right with Windows paths on Unix
104 99 # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
105 100
106 101 # Finally, replace all non alphanumeric, underscore or periods with underscore
107 102 @filename = just_filename.gsub(/[^\w\.\-]/,'_')
108 103 end
109 104
110 105 end
@@ -1,347 +1,344
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class QueryColumn
19 19 attr_accessor :name, :sortable
20 20 include GLoc
21 21
22 22 def initialize(name, options={})
23 23 self.name = name
24 24 self.sortable = options[:sortable]
25 25 end
26 26
27 27 def caption
28 28 set_language_if_valid(User.current.language)
29 29 l("field_#{name}")
30 30 end
31 31 end
32 32
33 33 class QueryCustomFieldColumn < QueryColumn
34 34
35 35 def initialize(custom_field)
36 36 self.name = "cf_#{custom_field.id}".to_sym
37 37 self.sortable = false
38 38 @cf = custom_field
39 39 end
40 40
41 41 def caption
42 42 @cf.name
43 43 end
44 44
45 45 def custom_field
46 46 @cf
47 47 end
48 48 end
49 49
50 50 class Query < ActiveRecord::Base
51 51 belongs_to :project
52 52 belongs_to :user
53 53 serialize :filters
54 54 serialize :column_names
55 55
56 56 attr_protected :project, :user
57 57 attr_accessor :executed_by
58 58
59 59 validates_presence_of :name, :on => :save
60 60 validates_length_of :name, :maximum => 255
61 61
62 62 @@operators = { "=" => :label_equals,
63 63 "!" => :label_not_equals,
64 64 "o" => :label_open_issues,
65 65 "c" => :label_closed_issues,
66 66 "!*" => :label_none,
67 67 "*" => :label_all,
68 68 ">=" => '>=',
69 69 "<=" => '<=',
70 70 "<t+" => :label_in_less_than,
71 71 ">t+" => :label_in_more_than,
72 72 "t+" => :label_in,
73 73 "t" => :label_today,
74 74 "w" => :label_this_week,
75 75 ">t-" => :label_less_than_ago,
76 76 "<t-" => :label_more_than_ago,
77 77 "t-" => :label_ago,
78 78 "~" => :label_contains,
79 79 "!~" => :label_not_contains }
80 80
81 81 cattr_reader :operators
82 82
83 83 @@operators_by_filter_type = { :list => [ "=", "!" ],
84 84 :list_status => [ "o", "=", "!", "c", "*" ],
85 85 :list_optional => [ "=", "!", "!*", "*" ],
86 86 :list_one_or_more => [ "*", "=" ],
87 87 :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
88 88 :date_past => [ ">t-", "<t-", "t-", "t", "w" ],
89 89 :string => [ "=", "~", "!", "!~" ],
90 90 :text => [ "~", "!~" ],
91 91 :integer => [ "=", ">=", "<=" ] }
92 92
93 93 cattr_reader :operators_by_filter_type
94 94
95 95 @@available_columns = [
96 96 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position"),
97 97 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position"),
98 98 QueryColumn.new(:priority, :sortable => "#{Enumeration.table_name}.position"),
99 99 QueryColumn.new(:subject),
100 100 QueryColumn.new(:assigned_to, :sortable => "#{User.table_name}.lastname"),
101 101 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on"),
102 102 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name"),
103 103 QueryColumn.new(:fixed_version),
104 104 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
105 105 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
106 106 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
107 107 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio"),
108 108 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on"),
109 109 ]
110 110 cattr_reader :available_columns
111 111
112 112 def initialize(attributes = nil)
113 113 super attributes
114 114 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
115 end
116
117 def executed_by=(user)
118 @executed_by = user
119 set_language_if_valid(user.language) if user
115 @executed_by = User.current.logged? ? User.current : nil
116 set_language_if_valid(executed_by.language) if executed_by
120 117 end
121 118
122 119 def validate
123 120 filters.each_key do |field|
124 121 errors.add label_for(field), :activerecord_error_blank unless
125 122 # filter requires one or more values
126 123 (values_for(field) and !values_for(field).first.empty?) or
127 124 # filter doesn't require any value
128 125 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
129 126 end if filters
130 127 end
131 128
132 129 def editable_by?(user)
133 130 return false unless user
134 131 return true if !is_public && self.user_id == user.id
135 132 is_public && user.allowed_to?(:manage_public_queries, project)
136 133 end
137 134
138 135 def available_filters
139 136 return @available_filters if @available_filters
140 137 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
141 138 "tracker_id" => { :type => :list, :order => 2, :values => Tracker.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
142 139 "priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI']).collect{|s| [s.name, s.id.to_s] } },
143 140 "subject" => { :type => :text, :order => 8 },
144 141 "created_on" => { :type => :date_past, :order => 9 },
145 142 "updated_on" => { :type => :date_past, :order => 10 },
146 143 "start_date" => { :type => :date, :order => 11 },
147 144 "due_date" => { :type => :date, :order => 12 },
148 145 "done_ratio" => { :type => :integer, :order => 13 }}
149 146
150 147 user_values = []
151 148 user_values << ["<< #{l(:label_me)} >>", "me"] if executed_by
152 149 if project
153 150 user_values += project.users.collect{|s| [s.name, s.id.to_s] }
154 151 elsif executed_by
155 152 # members of the user's projects
156 153 user_values += executed_by.projects.collect(&:users).flatten.uniq.sort.collect{|s| [s.name, s.id.to_s] }
157 154 end
158 155 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
159 156 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
160 157
161 158 if project
162 159 # project specific filters
163 160 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
164 161 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
165 162 unless @project.active_children.empty?
166 163 @available_filters["subproject_id"] = { :type => :list_one_or_more, :order => 13, :values => @project.active_children.collect{|s| [s.name, s.id.to_s] } }
167 164 end
168 165 @project.all_custom_fields.select(&:is_filter?).each do |field|
169 166 case field.field_format
170 167 when "string", "int"
171 168 options = { :type => :string, :order => 20 }
172 169 when "text"
173 170 options = { :type => :text, :order => 20 }
174 171 when "list"
175 172 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
176 173 when "date"
177 174 options = { :type => :date, :order => 20 }
178 175 when "bool"
179 176 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
180 177 end
181 178 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
182 179 end
183 180 # remove category filter if no category defined
184 181 @available_filters.delete "category_id" if @available_filters["category_id"][:values].empty?
185 182 end
186 183 @available_filters
187 184 end
188 185
189 186 def add_filter(field, operator, values)
190 187 # values must be an array
191 188 return unless values and values.is_a? Array # and !values.first.empty?
192 189 # check if field is defined as an available filter
193 190 if available_filters.has_key? field
194 191 filter_options = available_filters[field]
195 192 # check if operator is allowed for that filter
196 193 #if @@operators_by_filter_type[filter_options[:type]].include? operator
197 194 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
198 195 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
199 196 #end
200 197 filters[field] = {:operator => operator, :values => values }
201 198 end
202 199 end
203 200
204 201 def add_short_filter(field, expression)
205 202 return unless expression
206 203 parms = expression.scan(/^(o|c|\!|\*)?(.*)$/).first
207 204 add_filter field, (parms[0] || "="), [parms[1] || ""]
208 205 end
209 206
210 207 def has_filter?(field)
211 208 filters and filters[field]
212 209 end
213 210
214 211 def operator_for(field)
215 212 has_filter?(field) ? filters[field][:operator] : nil
216 213 end
217 214
218 215 def values_for(field)
219 216 has_filter?(field) ? filters[field][:values] : nil
220 217 end
221 218
222 219 def label_for(field)
223 220 label = @available_filters[field][:name] if @available_filters.has_key?(field)
224 221 label ||= field.gsub(/\_id$/, "")
225 222 end
226 223
227 224 def available_columns
228 225 return @available_columns if @available_columns
229 226 @available_columns = Query.available_columns
230 227 @available_columns += (project ?
231 228 project.custom_fields :
232 229 IssueCustomField.find(:all, :conditions => {:is_for_all => true})
233 230 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
234 231 end
235 232
236 233 def columns
237 234 if has_default_columns?
238 235 available_columns.select {|c| Setting.issue_list_default_columns.include?(c.name.to_s) }
239 236 else
240 237 # preserve the column_names order
241 238 column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
242 239 end
243 240 end
244 241
245 242 def column_names=(names)
246 243 names = names.select {|n| n.is_a?(Symbol) || !n.blank? } if names
247 244 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } if names
248 245 write_attribute(:column_names, names)
249 246 end
250 247
251 248 def has_column?(column)
252 249 column_names && column_names.include?(column.name)
253 250 end
254 251
255 252 def has_default_columns?
256 253 column_names.nil? || column_names.empty?
257 254 end
258 255
259 256 def statement
260 257 # project/subprojects clause
261 258 clause = ''
262 259 if project && has_filter?("subproject_id")
263 260 subproject_ids = []
264 261 if operator_for("subproject_id") == "="
265 262 subproject_ids = values_for("subproject_id").each(&:to_i)
266 263 else
267 264 subproject_ids = project.active_children.collect{|p| p.id}
268 265 end
269 266 clause << "#{Issue.table_name}.project_id IN (%d,%s)" % [project.id, subproject_ids.join(",")] if project
270 267 elsif project
271 268 clause << "#{Issue.table_name}.project_id=%d" % project.id
272 269 else
273 270 clause << Project.visible_by(executed_by)
274 271 end
275 272
276 273 # filters clauses
277 274 filters_clauses = []
278 275 filters.each_key do |field|
279 276 next if field == "subproject_id"
280 277 v = values_for(field).clone
281 278 next unless v and !v.empty?
282 279
283 280 sql = ''
284 281 if field =~ /^cf_(\d+)$/
285 282 # custom field
286 283 db_table = CustomValue.table_name
287 284 db_field = 'value'
288 285 sql << "#{Issue.table_name}.id IN (SELECT #{db_table}.customized_id FROM #{db_table} where #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} AND "
289 286 else
290 287 # regular field
291 288 db_table = Issue.table_name
292 289 db_field = field
293 290 sql << '('
294 291 end
295 292
296 293 # "me" value subsitution
297 294 if %w(assigned_to_id author_id).include?(field)
298 295 v.push(executed_by ? executed_by.id.to_s : "0") if v.delete("me")
299 296 end
300 297
301 298 case operator_for field
302 299 when "="
303 300 sql = sql + "#{db_table}.#{db_field} IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
304 301 when "!"
305 302 sql = sql + "#{db_table}.#{db_field} NOT IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
306 303 when "!*"
307 304 sql = sql + "#{db_table}.#{db_field} IS NULL"
308 305 when "*"
309 306 sql = sql + "#{db_table}.#{db_field} IS NOT NULL"
310 307 when ">="
311 308 sql = sql + "#{db_table}.#{db_field} >= #{v.first.to_i}"
312 309 when "<="
313 310 sql = sql + "#{db_table}.#{db_field} <= #{v.first.to_i}"
314 311 when "o"
315 312 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
316 313 when "c"
317 314 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
318 315 when ">t-"
319 316 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today + 1).to_time)]
320 317 when "<t-"
321 318 sql = sql + "#{db_table}.#{db_field} <= '%s'" % connection.quoted_date((Date.today - v.first.to_i).to_time)
322 319 when "t-"
323 320 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today - v.first.to_i + 1).to_time)]
324 321 when ">t+"
325 322 sql = sql + "#{db_table}.#{db_field} >= '%s'" % connection.quoted_date((Date.today + v.first.to_i).to_time)
326 323 when "<t+"
327 324 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)]
328 325 when "t+"
329 326 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today + v.first.to_i).to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)]
330 327 when "t"
331 328 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today+1).to_time)]
332 329 when "w"
333 330 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Time.now.at_beginning_of_week), connection.quoted_date(Time.now.next_week.yesterday)]
334 331 when "~"
335 332 sql = sql + "#{db_table}.#{db_field} LIKE '%#{connection.quote_string(v.first)}%'"
336 333 when "!~"
337 334 sql = sql + "#{db_table}.#{db_field} NOT LIKE '%#{connection.quote_string(v.first)}%'"
338 335 end
339 336 sql << ')'
340 337 filters_clauses << sql
341 338 end if filters and valid?
342 339
343 340 clause << ' AND ' unless clause.empty?
344 341 clause << filters_clauses.join(' AND ') unless filters_clauses.empty?
345 342 clause
346 343 end
347 344 end
@@ -1,246 +1,254
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require "digest/sha1"
19 19
20 20 class User < ActiveRecord::Base
21 21 # Account statuses
22 STATUS_ANONYMOUS = 0
22 23 STATUS_ACTIVE = 1
23 24 STATUS_REGISTERED = 2
24 25 STATUS_LOCKED = 3
25 26
26 27 has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name", :dependent => :delete_all
27 28 has_many :projects, :through => :memberships
28 29 has_many :custom_values, :dependent => :delete_all, :as => :customized
29 30 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
30 31 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
31 32 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
32 33 belongs_to :auth_source
33 34
34 35 attr_accessor :password, :password_confirmation
35 36 attr_accessor :last_before_login_on
36 37 # Prevents unauthorized assignments
37 38 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
38 39
39 validates_presence_of :login, :firstname, :lastname, :mail
40 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
40 41 validates_uniqueness_of :login, :mail
41 42 # Login must contain lettres, numbers, underscores only
42 validates_format_of :login, :with => /^[a-z0-9_\-@\.]+$/i
43 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
43 44 validates_length_of :login, :maximum => 30
44 45 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-]*$/i
45 46 validates_length_of :firstname, :lastname, :maximum => 30
46 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
47 validates_length_of :mail, :maximum => 60
47 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
48 validates_length_of :mail, :maximum => 60, :allow_nil => true
48 49 # Password length between 4 and 12
49 50 validates_length_of :password, :in => 4..12, :allow_nil => true
50 51 validates_confirmation_of :password, :allow_nil => true
51 52 validates_associated :custom_values, :on => :update
52 53
53 54 def before_create
54 55 self.mail_notification = false
55 56 true
56 57 end
57 58
58 59 def before_save
59 60 # update hashed_password if password was set
60 61 self.hashed_password = User.hash_password(self.password) if self.password
61 62 end
62 63
63 64 def self.active
64 65 with_scope :find => { :conditions => [ "status = ?", STATUS_ACTIVE ] } do
65 66 yield
66 67 end
67 68 end
68 69
69 70 def self.find_active(*args)
70 71 active do
71 72 find(*args)
72 73 end
73 74 end
74 75
75 76 # Returns the user that matches provided login and password, or nil
76 77 def self.try_to_login(login, password)
77 78 user = find(:first, :conditions => ["login=?", login])
78 79 if user
79 80 # user is already in local database
80 81 return nil if !user.active?
81 82 if user.auth_source
82 83 # user has an external authentication method
83 84 return nil unless user.auth_source.authenticate(login, password)
84 85 else
85 86 # authentication with local password
86 87 return nil unless User.hash_password(password) == user.hashed_password
87 88 end
88 89 else
89 90 # user is not yet registered, try to authenticate with available sources
90 91 attrs = AuthSource.authenticate(login, password)
91 92 if attrs
92 93 onthefly = new(*attrs)
93 94 onthefly.login = login
94 95 onthefly.language = Setting.default_language
95 96 if onthefly.save
96 97 user = find(:first, :conditions => ["login=?", login])
97 98 logger.info("User '#{user.login}' created on the fly.") if logger
98 99 end
99 100 end
100 101 end
101 102 user.update_attribute(:last_login_on, Time.now) if user
102 103 user
103 104
104 105 rescue => text
105 106 raise text
106 107 end
107 108
108 109 # Return user's full name for display
109 110 def name
110 111 "#{firstname} #{lastname}"
111 112 end
112 113
113 114 def active?
114 115 self.status == STATUS_ACTIVE
115 116 end
116 117
117 118 def registered?
118 119 self.status == STATUS_REGISTERED
119 120 end
120 121
121 122 def locked?
122 123 self.status == STATUS_LOCKED
123 124 end
124 125
125 126 def check_password?(clear_password)
126 127 User.hash_password(clear_password) == self.hashed_password
127 128 end
128 129
129 130 def pref
130 131 self.preference ||= UserPreference.new(:user => self)
131 132 end
132 133
133 134 def time_zone
134 135 self.pref.time_zone.nil? ? nil : TimeZone[self.pref.time_zone]
135 136 end
136 137
137 138 # Return user's RSS key (a 40 chars long string), used to access feeds
138 139 def rss_key
139 140 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
140 141 token.value
141 142 end
142 143
143 144 # Return an array of project ids for which the user has explicitly turned mail notifications on
144 145 def notified_projects_ids
145 146 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
146 147 end
147 148
148 149 def notified_project_ids=(ids)
149 150 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
150 151 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
151 152 @notified_projects_ids = nil
152 153 notified_projects_ids
153 154 end
154 155
155 156 def self.find_by_rss_key(key)
156 157 token = Token.find_by_value(key)
157 158 token && token.user.active? ? token.user : nil
158 159 end
159 160
160 161 def self.find_by_autologin_key(key)
161 162 token = Token.find_by_action_and_value('autologin', key)
162 163 token && (token.created_on > Setting.autologin.to_i.day.ago) && token.user.active? ? token.user : nil
163 164 end
164 165
165 166 def <=>(user)
166 167 lastname == user.lastname ? firstname <=> user.firstname : lastname <=> user.lastname
167 168 end
168 169
169 170 def to_s
170 171 name
171 172 end
172 173
173 174 def logged?
174 175 true
175 176 end
176 177
177 178 # Return user's role for project
178 179 def role_for_project(project)
179 180 # No role on archived projects
180 181 return nil unless project && project.active?
181 182 # Find project membership
182 183 membership = memberships.detect {|m| m.project_id == project.id}
183 184 if membership
184 185 membership.role
185 186 elsif logged?
186 187 Role.non_member
187 188 else
188 189 Role.anonymous
189 190 end
190 191 end
191 192
192 193 # Return true if the user is a member of project
193 194 def member_of?(project)
194 195 role_for_project(project).member?
195 196 end
196 197
197 198 # Return true if the user is allowed to do the specified action on project
198 199 # action can be:
199 200 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
200 201 # * a permission Symbol (eg. :edit_project)
201 202 def allowed_to?(action, project)
202 203 # No action allowed on archived projects
203 204 return false unless project.active?
204 205 # No action allowed on disabled modules
205 206 return false unless project.allows_to?(action)
206 207 # Admin users are authorized for anything else
207 208 return true if admin?
208 209
209 210 role = role_for_project(project)
210 211 return false unless role
211 212 role.allowed_to?(action) && (project.is_public? || role.member?)
212 213 end
213 214
214 215 def self.current=(user)
215 216 @current_user = user
216 217 end
217 218
218 219 def self.current
219 @current_user ||= AnonymousUser.new
220 @current_user ||= User.anonymous
220 221 end
221 222
222 223 def self.anonymous
223 AnonymousUser.new
224 return @anonymous_user if @anonymous_user
225 anonymous_user = AnonymousUser.find(:first)
226 if anonymous_user.nil?
227 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
228 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
229 end
230 @anonymous_user = anonymous_user
224 231 end
225 232
226 233 private
227 234 # Return password digest
228 235 def self.hash_password(clear_password)
229 236 Digest::SHA1.hexdigest(clear_password || "")
230 237 end
231 238 end
232 239
233 240 class AnonymousUser < User
234 def logged?
235 false
236 end
237 241
238 def time_zone
239 nil
242 def validate_on_create
243 # There should be only one AnonymousUser in the database
244 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
240 245 end
241 246
242 # Anonymous user has no RSS key
243 def rss_key
244 nil
245 end
247 # Overrides a few properties
248 def logged?; false end
249 def admin; false end
250 def name; 'Anonymous' end
251 def mail; nil end
252 def time_zone; nil end
253 def rss_key; nil end
246 254 end
@@ -1,43 +1,43
1 1 <div class="contextual">
2 2 <%= link_to_if_authorized l(:button_edit),
3 3 {:controller => 'news', :action => 'edit', :id => @news},
4 4 :class => 'icon icon-edit',
5 5 :accesskey => accesskey(:edit),
6 6 :onclick => 'Element.show("edit-news"); return false;' %>
7 7 <%= link_to_if_authorized l(:button_delete), {:controller => 'news', :action => 'destroy', :id => @news}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
8 8 </div>
9 9
10 10 <h2><%=h @news.title %></h2>
11 11
12 12 <div id="edit-news" style="display:none;">
13 13 <% labelled_tabular_form_for :news, @news, :url => { :action => "edit", :id => @news } do |f| %>
14 14 <%= render :partial => 'form', :locals => { :f => f } %>
15 15 <%= submit_tag l(:button_save) %>
16 16 <%= link_to l(:button_cancel), "#", :onclick => 'Element.hide("edit-news")' %>
17 17 <% end %>
18 18 </div>
19 19
20 20 <p><em><% unless @news.summary.empty? %><%=h @news.summary %><br /><% end %>
21 21 <span class="author"><%= authoring @news.created_on, @news.author %></span></em></p>
22 22 <%= textilizable(@news.description) %>
23 23 <br />
24 24
25 25 <div id="comments" style="margin-bottom:16px;">
26 26 <h3 class="icon22 icon22-comment"><%= l(:label_comment_plural) %></h3>
27 27 <% @news.comments.each do |comment| %>
28 28 <% next if comment.new_record? %>
29 <h4><%= format_time(comment.created_on) %> - <%= comment.author.name %></h4>
29 <h4><%= authoring comment.created_on, comment.author %></h4>
30 30 <div class="contextual">
31 31 <%= link_to_if_authorized l(:button_delete), {:controller => 'news', :action => 'destroy_comment', :id => @news, :comment_id => comment}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
32 32 </div>
33 33 <%= simple_format(auto_link(h comment.comments))%>
34 34 <% end if @news.comments_count > 0 %>
35 35 </div>
36 36
37 37 <% if authorize_for 'news', 'add_comment' %>
38 38 <p><%= toggle_link l(:label_comment_add), "add_comment_form", :focus => "comment_comments" %></p>
39 39 <% form_tag({:action => 'add_comment', :id => @news}, :id => "add_comment_form", :style => "display:none;") do %>
40 40 <%= text_area 'comment', 'comments', :cols => 60, :rows => 6 %>
41 41 <p><%= submit_tag l(:button_add) %></p>
42 42 <% end %>
43 43 <% end %>
@@ -1,103 +1,103
1 1 require 'redmine/access_control'
2 2 require 'redmine/menu_manager'
3 3 require 'redmine/mime_type'
4 4 require 'redmine/themes'
5 5 require 'redmine/plugin'
6 6
7 7 begin
8 8 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
9 9 rescue LoadError
10 10 # RMagick is not available
11 11 end
12 12
13 13 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs )
14 14
15 15 # Permissions
16 16 Redmine::AccessControl.map do |map|
17 17 map.permission :view_project, {:projects => [:show, :activity]}, :public => true
18 18 map.permission :search_project, {:search => :index}, :public => true
19 19 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
20 20 map.permission :select_project_modules, {:projects => :modules}, :require => :member
21 21 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy]}, :require => :member
22 22 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :destroy]}, :require => :member
23 23
24 24 map.project_module :issue_tracking do |map|
25 25 # Issue categories
26 26 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
27 27 # Issues
28 28 map.permission :view_issues, {:projects => [:changelog, :roadmap],
29 29 :issues => [:index, :changes, :show, :context_menu],
30 30 :queries => :index,
31 31 :reports => :issue_report}, :public => true
32 map.permission :add_issues, {:projects => :add_issue}, :require => :loggedin
32 map.permission :add_issues, {:projects => :add_issue}
33 33 map.permission :edit_issues, {:projects => :bulk_edit_issues,
34 :issues => [:edit, :destroy_attachment]}, :require => :loggedin
35 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}, :require => :loggedin
36 map.permission :add_issue_notes, {:issues => :add_note}, :require => :loggedin
34 :issues => [:edit, :destroy_attachment]}
35 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
36 map.permission :add_issue_notes, {:issues => :add_note}
37 37 map.permission :change_issue_status, {:issues => :change_status}, :require => :loggedin
38 38 map.permission :move_issues, {:projects => :move_issues}, :require => :loggedin
39 39 map.permission :delete_issues, {:issues => :destroy}, :require => :member
40 40 # Queries
41 41 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
42 42 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
43 43 # Gantt & calendar
44 44 map.permission :view_gantt, :projects => :gantt
45 45 map.permission :view_calendar, :projects => :calendar
46 46 end
47 47
48 48 map.project_module :time_tracking do |map|
49 49 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
50 50 map.permission :view_time_entries, :timelog => [:details, :report]
51 51 end
52 52
53 53 map.project_module :news do |map|
54 54 map.permission :manage_news, {:projects => :add_news, :news => [:edit, :destroy, :destroy_comment]}, :require => :member
55 55 map.permission :view_news, {:news => [:index, :show]}, :public => true
56 map.permission :comment_news, {:news => :add_comment}, :require => :loggedin
56 map.permission :comment_news, {:news => :add_comment}
57 57 end
58 58
59 59 map.project_module :documents do |map|
60 60 map.permission :manage_documents, {:projects => :add_document, :documents => [:edit, :destroy, :add_attachment, :destroy_attachment]}, :require => :loggedin
61 61 map.permission :view_documents, :projects => :list_documents, :documents => [:show, :download]
62 62 end
63 63
64 64 map.project_module :files do |map|
65 65 map.permission :manage_files, {:projects => :add_file, :versions => :destroy_file}, :require => :loggedin
66 66 map.permission :view_files, :projects => :list_files, :versions => :download
67 67 end
68 68
69 69 map.project_module :wiki do |map|
70 70 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
71 71 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
72 72 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
73 73 map.permission :view_wiki_pages, :wiki => [:index, :history, :diff, :special]
74 74 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment, :destroy_attachment]
75 75 end
76 76
77 77 map.project_module :repository do |map|
78 78 map.permission :manage_repository, {:repositories => [:edit, :destroy]}, :require => :member
79 79 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :changes, :diff, :stats, :graph]
80 80 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
81 81 end
82 82
83 83 map.project_module :boards do |map|
84 84 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
85 85 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
86 map.permission :add_messages, {:messages => [:new, :reply]}, :require => :loggedin
86 map.permission :add_messages, {:messages => [:new, :reply]}
87 87 end
88 88 end
89 89
90 90 # Project menu configuration
91 91 Redmine::MenuManager.map :project_menu do |menu|
92 92 menu.push :label_overview, :controller => 'projects', :action => 'show'
93 93 menu.push :label_activity, :controller => 'projects', :action => 'activity'
94 94 menu.push :label_roadmap, :controller => 'projects', :action => 'roadmap'
95 95 menu.push :label_issue_plural, { :controller => 'issues', :action => 'index' }, :param => :project_id
96 96 menu.push :label_news_plural, { :controller => 'news', :action => 'index' }, :param => :project_id
97 97 menu.push :label_document_plural, :controller => 'projects', :action => 'list_documents'
98 98 menu.push :label_wiki, { :controller => 'wiki', :action => 'index', :page => nil }, :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
99 99 menu.push :label_board_plural, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id, :if => Proc.new { |p| p.boards.any? }
100 100 menu.push :label_attachment_plural, :controller => 'projects', :action => 'list_files'
101 101 menu.push :label_repository, { :controller => 'repositories', :action => 'show' }, :if => Proc.new { |p| p.repository && !p.repository.new_record? }
102 102 menu.push :label_settings, :controller => 'projects', :action => 'settings'
103 103 end
@@ -1,132 +1,139
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.dirname(__FILE__) + '/../test_helper'
19 19
20 20 class UserTest < Test::Unit::TestCase
21 21 fixtures :users, :members, :projects
22 22
23 23 def setup
24 24 @admin = User.find(1)
25 25 @jsmith = User.find(2)
26 26 @dlopper = User.find(3)
27 27 end
28 28
29 29 def test_truth
30 30 assert_kind_of User, @jsmith
31 31 end
32 32
33 33 def test_create
34 34 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
35 35
36 36 user.login = "jsmith"
37 37 user.password, user.password_confirmation = "password", "password"
38 38 # login uniqueness
39 39 assert !user.save
40 40 assert_equal 1, user.errors.count
41 41
42 42 user.login = "newuser"
43 43 user.password, user.password_confirmation = "passwd", "password"
44 44 # password confirmation
45 45 assert !user.save
46 46 assert_equal 1, user.errors.count
47 47
48 48 user.password, user.password_confirmation = "password", "password"
49 49 assert user.save
50 50 end
51 51
52 52 def test_update
53 53 assert_equal "admin", @admin.login
54 54 @admin.login = "john"
55 55 assert @admin.save, @admin.errors.full_messages.join("; ")
56 56 @admin.reload
57 57 assert_equal "john", @admin.login
58 58 end
59 59
60 60 def test_validate
61 61 @admin.login = ""
62 62 assert !@admin.save
63 assert_equal 2, @admin.errors.count
63 assert_equal 1, @admin.errors.count
64 64 end
65 65
66 66 def test_password
67 67 user = User.try_to_login("admin", "admin")
68 68 assert_kind_of User, user
69 69 assert_equal "admin", user.login
70 70 user.password = "hello"
71 71 assert user.save
72 72
73 73 user = User.try_to_login("admin", "hello")
74 74 assert_kind_of User, user
75 75 assert_equal "admin", user.login
76 76 assert_equal User.hash_password("hello"), user.hashed_password
77 77 end
78 78
79 79 def test_lock
80 80 user = User.try_to_login("jsmith", "jsmith")
81 81 assert_equal @jsmith, user
82 82
83 83 @jsmith.status = User::STATUS_LOCKED
84 84 assert @jsmith.save
85 85
86 86 user = User.try_to_login("jsmith", "jsmith")
87 87 assert_equal nil, user
88 88 end
89 89
90 def test_create_anonymous
91 AnonymousUser.delete_all
92 anon = User.anonymous
93 assert !anon.new_record?
94 assert_kind_of AnonymousUser, anon
95 end
96
90 97 def test_rss_key
91 98 assert_nil @jsmith.rss_token
92 99 key = @jsmith.rss_key
93 100 assert_equal 40, key.length
94 101
95 102 @jsmith.reload
96 103 assert_equal key, @jsmith.rss_key
97 104 end
98 105
99 106 def test_role_for_project
100 107 # user with a role
101 108 role = @jsmith.role_for_project(Project.find(1))
102 109 assert_kind_of Role, role
103 110 assert_equal "Manager", role.name
104 111
105 112 # user with no role
106 113 assert !@dlopper.role_for_project(Project.find(2)).member?
107 114 end
108 115
109 116 def test_mail_notification_all
110 117 @jsmith.mail_notification = true
111 118 @jsmith.notified_project_ids = []
112 119 @jsmith.save
113 120 @jsmith.reload
114 121 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
115 122 end
116 123
117 124 def test_mail_notification_selected
118 125 @jsmith.mail_notification = false
119 126 @jsmith.notified_project_ids = [1]
120 127 @jsmith.save
121 128 @jsmith.reload
122 129 assert Project.find(1).recipients.include?(@jsmith.mail)
123 130 end
124 131
125 132 def test_mail_notification_none
126 133 @jsmith.mail_notification = false
127 134 @jsmith.notified_project_ids = []
128 135 @jsmith.save
129 136 @jsmith.reload
130 137 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
131 138 end
132 139 end
General Comments 0
You need to be logged in to leave comments. Login now