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