##// END OF EJS Templates
Files module: makes version field non required (#1053)....
Jean-Philippe Lang -
r2115:66ff4cb7de55
parent child
Show More
@@ -1,285 +1,289
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 menu_item :overview
19 menu_item :overview
20 menu_item :activity, :only => :activity
20 menu_item :activity, :only => :activity
21 menu_item :roadmap, :only => :roadmap
21 menu_item :roadmap, :only => :roadmap
22 menu_item :files, :only => [:list_files, :add_file]
22 menu_item :files, :only => [:list_files, :add_file]
23 menu_item :settings, :only => :settings
23 menu_item :settings, :only => :settings
24 menu_item :issues, :only => [:changelog]
24 menu_item :issues, :only => [:changelog]
25
25
26 before_filter :find_project, :except => [ :index, :list, :add, :activity ]
26 before_filter :find_project, :except => [ :index, :list, :add, :activity ]
27 before_filter :find_optional_project, :only => :activity
27 before_filter :find_optional_project, :only => :activity
28 before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy, :activity ]
28 before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy, :activity ]
29 before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
29 before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
30 accept_key_auth :activity
30 accept_key_auth :activity
31
31
32 helper :sort
32 helper :sort
33 include SortHelper
33 include SortHelper
34 helper :custom_fields
34 helper :custom_fields
35 include CustomFieldsHelper
35 include CustomFieldsHelper
36 helper :ifpdf
36 helper :ifpdf
37 include IfpdfHelper
37 include IfpdfHelper
38 helper :issues
38 helper :issues
39 helper IssuesHelper
39 helper IssuesHelper
40 helper :queries
40 helper :queries
41 include QueriesHelper
41 include QueriesHelper
42 helper :repositories
42 helper :repositories
43 include RepositoriesHelper
43 include RepositoriesHelper
44 include ProjectsHelper
44 include ProjectsHelper
45
45
46 # Lists visible projects
46 # Lists visible projects
47 def index
47 def index
48 projects = Project.find :all,
48 projects = Project.find :all,
49 :conditions => Project.visible_by(User.current),
49 :conditions => Project.visible_by(User.current),
50 :include => :parent
50 :include => :parent
51 respond_to do |format|
51 respond_to do |format|
52 format.html {
52 format.html {
53 @project_tree = projects.group_by {|p| p.parent || p}
53 @project_tree = projects.group_by {|p| p.parent || p}
54 @project_tree.keys.each {|p| @project_tree[p] -= [p]}
54 @project_tree.keys.each {|p| @project_tree[p] -= [p]}
55 }
55 }
56 format.atom {
56 format.atom {
57 render_feed(projects.sort_by(&:created_on).reverse.slice(0, Setting.feeds_limit.to_i),
57 render_feed(projects.sort_by(&:created_on).reverse.slice(0, Setting.feeds_limit.to_i),
58 :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
58 :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
59 }
59 }
60 end
60 end
61 end
61 end
62
62
63 # Add a new project
63 # Add a new project
64 def add
64 def add
65 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
65 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
66 @trackers = Tracker.all
66 @trackers = Tracker.all
67 @root_projects = Project.find(:all,
67 @root_projects = Project.find(:all,
68 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
68 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
69 :order => 'name')
69 :order => 'name')
70 @project = Project.new(params[:project])
70 @project = Project.new(params[:project])
71 if request.get?
71 if request.get?
72 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
72 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
73 @project.trackers = Tracker.all
73 @project.trackers = Tracker.all
74 @project.is_public = Setting.default_projects_public?
74 @project.is_public = Setting.default_projects_public?
75 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
75 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
76 else
76 else
77 @project.enabled_module_names = params[:enabled_modules]
77 @project.enabled_module_names = params[:enabled_modules]
78 if @project.save
78 if @project.save
79 flash[:notice] = l(:notice_successful_create)
79 flash[:notice] = l(:notice_successful_create)
80 redirect_to :controller => 'admin', :action => 'projects'
80 redirect_to :controller => 'admin', :action => 'projects'
81 end
81 end
82 end
82 end
83 end
83 end
84
84
85 # Show @project
85 # Show @project
86 def show
86 def show
87 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
87 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
88 @subprojects = @project.children.find(:all, :conditions => Project.visible_by(User.current))
88 @subprojects = @project.children.find(:all, :conditions => Project.visible_by(User.current))
89 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
89 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
90 @trackers = @project.rolled_up_trackers
90 @trackers = @project.rolled_up_trackers
91
91
92 cond = @project.project_condition(Setting.display_subprojects_issues?)
92 cond = @project.project_condition(Setting.display_subprojects_issues?)
93 Issue.visible_by(User.current) do
93 Issue.visible_by(User.current) do
94 @open_issues_by_tracker = Issue.count(:group => :tracker,
94 @open_issues_by_tracker = Issue.count(:group => :tracker,
95 :include => [:project, :status, :tracker],
95 :include => [:project, :status, :tracker],
96 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
96 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
97 @total_issues_by_tracker = Issue.count(:group => :tracker,
97 @total_issues_by_tracker = Issue.count(:group => :tracker,
98 :include => [:project, :status, :tracker],
98 :include => [:project, :status, :tracker],
99 :conditions => cond)
99 :conditions => cond)
100 end
100 end
101 TimeEntry.visible_by(User.current) do
101 TimeEntry.visible_by(User.current) do
102 @total_hours = TimeEntry.sum(:hours,
102 @total_hours = TimeEntry.sum(:hours,
103 :include => :project,
103 :include => :project,
104 :conditions => cond).to_f
104 :conditions => cond).to_f
105 end
105 end
106 @key = User.current.rss_key
106 @key = User.current.rss_key
107 end
107 end
108
108
109 def settings
109 def settings
110 @root_projects = Project.find(:all,
110 @root_projects = Project.find(:all,
111 :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id],
111 :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id],
112 :order => 'name')
112 :order => 'name')
113 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
113 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
114 @issue_category ||= IssueCategory.new
114 @issue_category ||= IssueCategory.new
115 @member ||= @project.members.new
115 @member ||= @project.members.new
116 @trackers = Tracker.all
116 @trackers = Tracker.all
117 @repository ||= @project.repository
117 @repository ||= @project.repository
118 @wiki ||= @project.wiki
118 @wiki ||= @project.wiki
119 end
119 end
120
120
121 # Edit @project
121 # Edit @project
122 def edit
122 def edit
123 if request.post?
123 if request.post?
124 @project.attributes = params[:project]
124 @project.attributes = params[:project]
125 if @project.save
125 if @project.save
126 flash[:notice] = l(:notice_successful_update)
126 flash[:notice] = l(:notice_successful_update)
127 redirect_to :action => 'settings', :id => @project
127 redirect_to :action => 'settings', :id => @project
128 else
128 else
129 settings
129 settings
130 render :action => 'settings'
130 render :action => 'settings'
131 end
131 end
132 end
132 end
133 end
133 end
134
134
135 def modules
135 def modules
136 @project.enabled_module_names = params[:enabled_modules]
136 @project.enabled_module_names = params[:enabled_modules]
137 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
137 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
138 end
138 end
139
139
140 def archive
140 def archive
141 @project.archive if request.post? && @project.active?
141 @project.archive if request.post? && @project.active?
142 redirect_to :controller => 'admin', :action => 'projects'
142 redirect_to :controller => 'admin', :action => 'projects'
143 end
143 end
144
144
145 def unarchive
145 def unarchive
146 @project.unarchive if request.post? && !@project.active?
146 @project.unarchive if request.post? && !@project.active?
147 redirect_to :controller => 'admin', :action => 'projects'
147 redirect_to :controller => 'admin', :action => 'projects'
148 end
148 end
149
149
150 # Delete @project
150 # Delete @project
151 def destroy
151 def destroy
152 @project_to_destroy = @project
152 @project_to_destroy = @project
153 if request.post? and params[:confirm]
153 if request.post? and params[:confirm]
154 @project_to_destroy.destroy
154 @project_to_destroy.destroy
155 redirect_to :controller => 'admin', :action => 'projects'
155 redirect_to :controller => 'admin', :action => 'projects'
156 end
156 end
157 # hide project in layout
157 # hide project in layout
158 @project = nil
158 @project = nil
159 end
159 end
160
160
161 # Add a new issue category to @project
161 # Add a new issue category to @project
162 def add_issue_category
162 def add_issue_category
163 @category = @project.issue_categories.build(params[:category])
163 @category = @project.issue_categories.build(params[:category])
164 if request.post? and @category.save
164 if request.post? and @category.save
165 respond_to do |format|
165 respond_to do |format|
166 format.html do
166 format.html do
167 flash[:notice] = l(:notice_successful_create)
167 flash[:notice] = l(:notice_successful_create)
168 redirect_to :action => 'settings', :tab => 'categories', :id => @project
168 redirect_to :action => 'settings', :tab => 'categories', :id => @project
169 end
169 end
170 format.js do
170 format.js do
171 # IE doesn't support the replace_html rjs method for select box options
171 # IE doesn't support the replace_html rjs method for select box options
172 render(:update) {|page| page.replace "issue_category_id",
172 render(:update) {|page| page.replace "issue_category_id",
173 content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
173 content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
174 }
174 }
175 end
175 end
176 end
176 end
177 end
177 end
178 end
178 end
179
179
180 # Add a new version to @project
180 # Add a new version to @project
181 def add_version
181 def add_version
182 @version = @project.versions.build(params[:version])
182 @version = @project.versions.build(params[:version])
183 if request.post? and @version.save
183 if request.post? and @version.save
184 flash[:notice] = l(:notice_successful_create)
184 flash[:notice] = l(:notice_successful_create)
185 redirect_to :action => 'settings', :tab => 'versions', :id => @project
185 redirect_to :action => 'settings', :tab => 'versions', :id => @project
186 end
186 end
187 end
187 end
188
188
189 def add_file
189 def add_file
190 if request.post?
190 if request.post?
191 @version = @project.versions.find_by_id(params[:version_id])
191 container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
192 attachments = attach_files(@version, params[:attachments])
192 attachments = attach_files(container, params[:attachments])
193 Mailer.deliver_attachments_added(attachments) if !attachments.empty? && Setting.notified_events.include?('file_added')
193 if !attachments.empty? && Setting.notified_events.include?('file_added')
194 Mailer.deliver_attachments_added(attachments)
195 end
194 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
196 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
197 return
195 end
198 end
196 @versions = @project.versions.sort
199 @versions = @project.versions.sort
197 end
200 end
198
201
199 def list_files
202 def list_files
200 sort_init "#{Attachment.table_name}.filename", "asc"
203 sort_init "#{Attachment.table_name}.filename", "asc"
201 sort_update
204 sort_update
202 @versions = @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
205 @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
206 @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
203 render :layout => !request.xhr?
207 render :layout => !request.xhr?
204 end
208 end
205
209
206 # Show changelog for @project
210 # Show changelog for @project
207 def changelog
211 def changelog
208 @trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
212 @trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
209 retrieve_selected_tracker_ids(@trackers)
213 retrieve_selected_tracker_ids(@trackers)
210 @versions = @project.versions.sort
214 @versions = @project.versions.sort
211 end
215 end
212
216
213 def roadmap
217 def roadmap
214 @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true])
218 @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true])
215 retrieve_selected_tracker_ids(@trackers)
219 retrieve_selected_tracker_ids(@trackers)
216 @versions = @project.versions.sort
220 @versions = @project.versions.sort
217 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
221 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
218 end
222 end
219
223
220 def activity
224 def activity
221 @days = Setting.activity_days_default.to_i
225 @days = Setting.activity_days_default.to_i
222
226
223 if params[:from]
227 if params[:from]
224 begin; @date_to = params[:from].to_date + 1; rescue; end
228 begin; @date_to = params[:from].to_date + 1; rescue; end
225 end
229 end
226
230
227 @date_to ||= Date.today + 1
231 @date_to ||= Date.today + 1
228 @date_from = @date_to - @days
232 @date_from = @date_to - @days
229 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
233 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
230 @author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id]))
234 @author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id]))
231
235
232 @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project,
236 @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project,
233 :with_subprojects => @with_subprojects,
237 :with_subprojects => @with_subprojects,
234 :author => @author)
238 :author => @author)
235 @activity.scope_select {|t| !params["show_#{t}"].nil?}
239 @activity.scope_select {|t| !params["show_#{t}"].nil?}
236 @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
240 @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
237
241
238 events = @activity.events(@date_from, @date_to)
242 events = @activity.events(@date_from, @date_to)
239
243
240 respond_to do |format|
244 respond_to do |format|
241 format.html {
245 format.html {
242 @events_by_day = events.group_by(&:event_date)
246 @events_by_day = events.group_by(&:event_date)
243 render :layout => false if request.xhr?
247 render :layout => false if request.xhr?
244 }
248 }
245 format.atom {
249 format.atom {
246 title = l(:label_activity)
250 title = l(:label_activity)
247 if @author
251 if @author
248 title = @author.name
252 title = @author.name
249 elsif @activity.scope.size == 1
253 elsif @activity.scope.size == 1
250 title = l("label_#{@activity.scope.first.singularize}_plural")
254 title = l("label_#{@activity.scope.first.singularize}_plural")
251 end
255 end
252 render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
256 render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
253 }
257 }
254 end
258 end
255
259
256 rescue ActiveRecord::RecordNotFound
260 rescue ActiveRecord::RecordNotFound
257 render_404
261 render_404
258 end
262 end
259
263
260 private
264 private
261 # Find project of id params[:id]
265 # Find project of id params[:id]
262 # if not found, redirect to project list
266 # if not found, redirect to project list
263 # Used as a before_filter
267 # Used as a before_filter
264 def find_project
268 def find_project
265 @project = Project.find(params[:id])
269 @project = Project.find(params[:id])
266 rescue ActiveRecord::RecordNotFound
270 rescue ActiveRecord::RecordNotFound
267 render_404
271 render_404
268 end
272 end
269
273
270 def find_optional_project
274 def find_optional_project
271 return true unless params[:id]
275 return true unless params[:id]
272 @project = Project.find(params[:id])
276 @project = Project.find(params[:id])
273 authorize
277 authorize
274 rescue ActiveRecord::RecordNotFound
278 rescue ActiveRecord::RecordNotFound
275 render_404
279 render_404
276 end
280 end
277
281
278 def retrieve_selected_tracker_ids(selectable_trackers)
282 def retrieve_selected_tracker_ids(selectable_trackers)
279 if ids = params[:tracker_ids]
283 if ids = params[:tracker_ids]
280 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
284 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
281 else
285 else
282 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
286 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
283 end
287 end
284 end
288 end
285 end
289 end
@@ -1,247 +1,250
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 Mailer < ActionMailer::Base
18 class Mailer < ActionMailer::Base
19 helper :application
19 helper :application
20 helper :issues
20 helper :issues
21 helper :custom_fields
21 helper :custom_fields
22
22
23 include ActionController::UrlWriter
23 include ActionController::UrlWriter
24
24
25 def issue_add(issue)
25 def issue_add(issue)
26 redmine_headers 'Project' => issue.project.identifier,
26 redmine_headers 'Project' => issue.project.identifier,
27 'Issue-Id' => issue.id,
27 'Issue-Id' => issue.id,
28 'Issue-Author' => issue.author.login
28 'Issue-Author' => issue.author.login
29 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
29 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
30 recipients issue.recipients
30 recipients issue.recipients
31 subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
31 subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
32 body :issue => issue,
32 body :issue => issue,
33 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
33 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
34 end
34 end
35
35
36 def issue_edit(journal)
36 def issue_edit(journal)
37 issue = journal.journalized
37 issue = journal.journalized
38 redmine_headers 'Project' => issue.project.identifier,
38 redmine_headers 'Project' => issue.project.identifier,
39 'Issue-Id' => issue.id,
39 'Issue-Id' => issue.id,
40 'Issue-Author' => issue.author.login
40 'Issue-Author' => issue.author.login
41 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
41 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
42 recipients issue.recipients
42 recipients issue.recipients
43 # Watchers in cc
43 # Watchers in cc
44 cc(issue.watcher_recipients - @recipients)
44 cc(issue.watcher_recipients - @recipients)
45 s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
45 s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
46 s << "(#{issue.status.name}) " if journal.new_value_for('status_id')
46 s << "(#{issue.status.name}) " if journal.new_value_for('status_id')
47 s << issue.subject
47 s << issue.subject
48 subject s
48 subject s
49 body :issue => issue,
49 body :issue => issue,
50 :journal => journal,
50 :journal => journal,
51 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
51 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
52 end
52 end
53
53
54 def reminder(user, issues, days)
54 def reminder(user, issues, days)
55 set_language_if_valid user.language
55 set_language_if_valid user.language
56 recipients user.mail
56 recipients user.mail
57 subject l(:mail_subject_reminder, issues.size)
57 subject l(:mail_subject_reminder, issues.size)
58 body :issues => issues,
58 body :issues => issues,
59 :days => days,
59 :days => days,
60 :issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort_key => 'issues.due_date', :sort_order => 'asc')
60 :issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort_key => 'issues.due_date', :sort_order => 'asc')
61 end
61 end
62
62
63 def document_added(document)
63 def document_added(document)
64 redmine_headers 'Project' => document.project.identifier
64 redmine_headers 'Project' => document.project.identifier
65 recipients document.project.recipients
65 recipients document.project.recipients
66 subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
66 subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
67 body :document => document,
67 body :document => document,
68 :document_url => url_for(:controller => 'documents', :action => 'show', :id => document)
68 :document_url => url_for(:controller => 'documents', :action => 'show', :id => document)
69 end
69 end
70
70
71 def attachments_added(attachments)
71 def attachments_added(attachments)
72 container = attachments.first.container
72 container = attachments.first.container
73 added_to = ''
73 added_to = ''
74 added_to_url = ''
74 added_to_url = ''
75 case container.class.name
75 case container.class.name
76 when 'Project'
77 added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container)
78 added_to = "#{l(:label_project)}: #{container}"
76 when 'Version'
79 when 'Version'
77 added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container.project_id)
80 added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container.project_id)
78 added_to = "#{l(:label_version)}: #{container.name}"
81 added_to = "#{l(:label_version)}: #{container.name}"
79 when 'Document'
82 when 'Document'
80 added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
83 added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
81 added_to = "#{l(:label_document)}: #{container.title}"
84 added_to = "#{l(:label_document)}: #{container.title}"
82 end
85 end
83 redmine_headers 'Project' => container.project.identifier
86 redmine_headers 'Project' => container.project.identifier
84 recipients container.project.recipients
87 recipients container.project.recipients
85 subject "[#{container.project.name}] #{l(:label_attachment_new)}"
88 subject "[#{container.project.name}] #{l(:label_attachment_new)}"
86 body :attachments => attachments,
89 body :attachments => attachments,
87 :added_to => added_to,
90 :added_to => added_to,
88 :added_to_url => added_to_url
91 :added_to_url => added_to_url
89 end
92 end
90
93
91 def news_added(news)
94 def news_added(news)
92 redmine_headers 'Project' => news.project.identifier
95 redmine_headers 'Project' => news.project.identifier
93 recipients news.project.recipients
96 recipients news.project.recipients
94 subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
97 subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
95 body :news => news,
98 body :news => news,
96 :news_url => url_for(:controller => 'news', :action => 'show', :id => news)
99 :news_url => url_for(:controller => 'news', :action => 'show', :id => news)
97 end
100 end
98
101
99 def message_posted(message, recipients)
102 def message_posted(message, recipients)
100 redmine_headers 'Project' => message.project.identifier,
103 redmine_headers 'Project' => message.project.identifier,
101 'Topic-Id' => (message.parent_id || message.id)
104 'Topic-Id' => (message.parent_id || message.id)
102 recipients(recipients)
105 recipients(recipients)
103 subject "[#{message.board.project.name} - #{message.board.name}] #{message.subject}"
106 subject "[#{message.board.project.name} - #{message.board.name}] #{message.subject}"
104 body :message => message,
107 body :message => message,
105 :message_url => url_for(:controller => 'messages', :action => 'show', :board_id => message.board_id, :id => message.root)
108 :message_url => url_for(:controller => 'messages', :action => 'show', :board_id => message.board_id, :id => message.root)
106 end
109 end
107
110
108 def account_information(user, password)
111 def account_information(user, password)
109 set_language_if_valid user.language
112 set_language_if_valid user.language
110 recipients user.mail
113 recipients user.mail
111 subject l(:mail_subject_register, Setting.app_title)
114 subject l(:mail_subject_register, Setting.app_title)
112 body :user => user,
115 body :user => user,
113 :password => password,
116 :password => password,
114 :login_url => url_for(:controller => 'account', :action => 'login')
117 :login_url => url_for(:controller => 'account', :action => 'login')
115 end
118 end
116
119
117 def account_activation_request(user)
120 def account_activation_request(user)
118 # Send the email to all active administrators
121 # Send the email to all active administrators
119 recipients User.active.find(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact
122 recipients User.active.find(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact
120 subject l(:mail_subject_account_activation_request, Setting.app_title)
123 subject l(:mail_subject_account_activation_request, Setting.app_title)
121 body :user => user,
124 body :user => user,
122 :url => url_for(:controller => 'users', :action => 'index', :status => User::STATUS_REGISTERED, :sort_key => 'created_on', :sort_order => 'desc')
125 :url => url_for(:controller => 'users', :action => 'index', :status => User::STATUS_REGISTERED, :sort_key => 'created_on', :sort_order => 'desc')
123 end
126 end
124
127
125 def lost_password(token)
128 def lost_password(token)
126 set_language_if_valid(token.user.language)
129 set_language_if_valid(token.user.language)
127 recipients token.user.mail
130 recipients token.user.mail
128 subject l(:mail_subject_lost_password, Setting.app_title)
131 subject l(:mail_subject_lost_password, Setting.app_title)
129 body :token => token,
132 body :token => token,
130 :url => url_for(:controller => 'account', :action => 'lost_password', :token => token.value)
133 :url => url_for(:controller => 'account', :action => 'lost_password', :token => token.value)
131 end
134 end
132
135
133 def register(token)
136 def register(token)
134 set_language_if_valid(token.user.language)
137 set_language_if_valid(token.user.language)
135 recipients token.user.mail
138 recipients token.user.mail
136 subject l(:mail_subject_register, Setting.app_title)
139 subject l(:mail_subject_register, Setting.app_title)
137 body :token => token,
140 body :token => token,
138 :url => url_for(:controller => 'account', :action => 'activate', :token => token.value)
141 :url => url_for(:controller => 'account', :action => 'activate', :token => token.value)
139 end
142 end
140
143
141 def test(user)
144 def test(user)
142 set_language_if_valid(user.language)
145 set_language_if_valid(user.language)
143 recipients user.mail
146 recipients user.mail
144 subject 'Redmine test'
147 subject 'Redmine test'
145 body :url => url_for(:controller => 'welcome')
148 body :url => url_for(:controller => 'welcome')
146 end
149 end
147
150
148 # Overrides default deliver! method to prevent from sending an email
151 # Overrides default deliver! method to prevent from sending an email
149 # with no recipient, cc or bcc
152 # with no recipient, cc or bcc
150 def deliver!(mail = @mail)
153 def deliver!(mail = @mail)
151 return false if (recipients.nil? || recipients.empty?) &&
154 return false if (recipients.nil? || recipients.empty?) &&
152 (cc.nil? || cc.empty?) &&
155 (cc.nil? || cc.empty?) &&
153 (bcc.nil? || bcc.empty?)
156 (bcc.nil? || bcc.empty?)
154 super
157 super
155 end
158 end
156
159
157 # Sends reminders to issue assignees
160 # Sends reminders to issue assignees
158 # Available options:
161 # Available options:
159 # * :days => how many days in the future to remind about (defaults to 7)
162 # * :days => how many days in the future to remind about (defaults to 7)
160 # * :tracker => id of tracker for filtering issues (defaults to all trackers)
163 # * :tracker => id of tracker for filtering issues (defaults to all trackers)
161 # * :project => id or identifier of project to process (defaults to all projects)
164 # * :project => id or identifier of project to process (defaults to all projects)
162 def self.reminders(options={})
165 def self.reminders(options={})
163 days = options[:days] || 7
166 days = options[:days] || 7
164 project = options[:project] ? Project.find(options[:project]) : nil
167 project = options[:project] ? Project.find(options[:project]) : nil
165 tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil
168 tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil
166
169
167 s = ARCondition.new ["#{IssueStatus.table_name}.is_closed = ? AND #{Issue.table_name}.due_date <= ?", false, days.day.from_now.to_date]
170 s = ARCondition.new ["#{IssueStatus.table_name}.is_closed = ? AND #{Issue.table_name}.due_date <= ?", false, days.day.from_now.to_date]
168 s << "#{Issue.table_name}.assigned_to_id IS NOT NULL"
171 s << "#{Issue.table_name}.assigned_to_id IS NOT NULL"
169 s << "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}"
172 s << "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}"
170 s << "#{Issue.table_name}.project_id = #{project.id}" if project
173 s << "#{Issue.table_name}.project_id = #{project.id}" if project
171 s << "#{Issue.table_name}.tracker_id = #{tracker.id}" if tracker
174 s << "#{Issue.table_name}.tracker_id = #{tracker.id}" if tracker
172
175
173 issues_by_assignee = Issue.find(:all, :include => [:status, :assigned_to, :project, :tracker],
176 issues_by_assignee = Issue.find(:all, :include => [:status, :assigned_to, :project, :tracker],
174 :conditions => s.conditions
177 :conditions => s.conditions
175 ).group_by(&:assigned_to)
178 ).group_by(&:assigned_to)
176 issues_by_assignee.each do |assignee, issues|
179 issues_by_assignee.each do |assignee, issues|
177 deliver_reminder(assignee, issues, days) unless assignee.nil?
180 deliver_reminder(assignee, issues, days) unless assignee.nil?
178 end
181 end
179 end
182 end
180
183
181 private
184 private
182 def initialize_defaults(method_name)
185 def initialize_defaults(method_name)
183 super
186 super
184 set_language_if_valid Setting.default_language
187 set_language_if_valid Setting.default_language
185 from Setting.mail_from
188 from Setting.mail_from
186
189
187 # URL options
190 # URL options
188 h = Setting.host_name
191 h = Setting.host_name
189 h = h.to_s.gsub(%r{\/.*$}, '') unless ActionController::AbstractRequest.relative_url_root.blank?
192 h = h.to_s.gsub(%r{\/.*$}, '') unless ActionController::AbstractRequest.relative_url_root.blank?
190 default_url_options[:host] = h
193 default_url_options[:host] = h
191 default_url_options[:protocol] = Setting.protocol
194 default_url_options[:protocol] = Setting.protocol
192
195
193 # Common headers
196 # Common headers
194 headers 'X-Mailer' => 'Redmine',
197 headers 'X-Mailer' => 'Redmine',
195 'X-Redmine-Host' => Setting.host_name,
198 'X-Redmine-Host' => Setting.host_name,
196 'X-Redmine-Site' => Setting.app_title
199 'X-Redmine-Site' => Setting.app_title
197 end
200 end
198
201
199 # Appends a Redmine header field (name is prepended with 'X-Redmine-')
202 # Appends a Redmine header field (name is prepended with 'X-Redmine-')
200 def redmine_headers(h)
203 def redmine_headers(h)
201 h.each { |k,v| headers["X-Redmine-#{k}"] = v }
204 h.each { |k,v| headers["X-Redmine-#{k}"] = v }
202 end
205 end
203
206
204 # Overrides the create_mail method
207 # Overrides the create_mail method
205 def create_mail
208 def create_mail
206 # Removes the current user from the recipients and cc
209 # Removes the current user from the recipients and cc
207 # if he doesn't want to receive notifications about what he does
210 # if he doesn't want to receive notifications about what he does
208 if User.current.pref[:no_self_notified]
211 if User.current.pref[:no_self_notified]
209 recipients.delete(User.current.mail) if recipients
212 recipients.delete(User.current.mail) if recipients
210 cc.delete(User.current.mail) if cc
213 cc.delete(User.current.mail) if cc
211 end
214 end
212 # Blind carbon copy recipients
215 # Blind carbon copy recipients
213 if Setting.bcc_recipients?
216 if Setting.bcc_recipients?
214 bcc([recipients, cc].flatten.compact.uniq)
217 bcc([recipients, cc].flatten.compact.uniq)
215 recipients []
218 recipients []
216 cc []
219 cc []
217 end
220 end
218 super
221 super
219 end
222 end
220
223
221 # Renders a message with the corresponding layout
224 # Renders a message with the corresponding layout
222 def render_message(method_name, body)
225 def render_message(method_name, body)
223 layout = method_name.match(%r{text\.html\.(rhtml|rxml)}) ? 'layout.text.html.rhtml' : 'layout.text.plain.rhtml'
226 layout = method_name.match(%r{text\.html\.(rhtml|rxml)}) ? 'layout.text.html.rhtml' : 'layout.text.plain.rhtml'
224 body[:content_for_layout] = render(:file => method_name, :body => body)
227 body[:content_for_layout] = render(:file => method_name, :body => body)
225 ActionView::Base.new(template_root, body, self).render(:file => "mailer/#{layout}", :use_full_path => true)
228 ActionView::Base.new(template_root, body, self).render(:file => "mailer/#{layout}", :use_full_path => true)
226 end
229 end
227
230
228 # for the case of plain text only
231 # for the case of plain text only
229 def body(*params)
232 def body(*params)
230 value = super(*params)
233 value = super(*params)
231 if Setting.plain_text_mail?
234 if Setting.plain_text_mail?
232 templates = Dir.glob("#{template_path}/#{@template}.text.plain.{rhtml,erb}")
235 templates = Dir.glob("#{template_path}/#{@template}.text.plain.{rhtml,erb}")
233 unless String === @body or templates.empty?
236 unless String === @body or templates.empty?
234 template = File.basename(templates.first)
237 template = File.basename(templates.first)
235 @body[:content_for_layout] = render(:file => template, :body => @body)
238 @body[:content_for_layout] = render(:file => template, :body => @body)
236 @body = ActionView::Base.new(template_root, @body, self).render(:file => "mailer/layout.text.plain.rhtml", :use_full_path => true)
239 @body = ActionView::Base.new(template_root, @body, self).render(:file => "mailer/layout.text.plain.rhtml", :use_full_path => true)
237 return @body
240 return @body
238 end
241 end
239 end
242 end
240 return value
243 return value
241 end
244 end
242
245
243 # Makes partial rendering work with Rails 1.2 (retro-compatibility)
246 # Makes partial rendering work with Rails 1.2 (retro-compatibility)
244 def self.controller_path
247 def self.controller_path
245 ''
248 ''
246 end unless respond_to?('controller_path')
249 end unless respond_to?('controller_path')
247 end
250 end
@@ -1,274 +1,276
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 Project < ActiveRecord::Base
18 class Project < ActiveRecord::Base
19 # Project statuses
19 # Project statuses
20 STATUS_ACTIVE = 1
20 STATUS_ACTIVE = 1
21 STATUS_ARCHIVED = 9
21 STATUS_ARCHIVED = 9
22
22
23 has_many :members, :include => :user, :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}"
23 has_many :members, :include => :user, :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}"
24 has_many :users, :through => :members
24 has_many :users, :through => :members
25 has_many :enabled_modules, :dependent => :delete_all
25 has_many :enabled_modules, :dependent => :delete_all
26 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
26 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
27 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
27 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
28 has_many :issue_changes, :through => :issues, :source => :journals
28 has_many :issue_changes, :through => :issues, :source => :journals
29 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
29 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
30 has_many :time_entries, :dependent => :delete_all
30 has_many :time_entries, :dependent => :delete_all
31 has_many :queries, :dependent => :delete_all
31 has_many :queries, :dependent => :delete_all
32 has_many :documents, :dependent => :destroy
32 has_many :documents, :dependent => :destroy
33 has_many :news, :dependent => :delete_all, :include => :author
33 has_many :news, :dependent => :delete_all, :include => :author
34 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
34 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
35 has_many :boards, :dependent => :destroy, :order => "position ASC"
35 has_many :boards, :dependent => :destroy, :order => "position ASC"
36 has_one :repository, :dependent => :destroy
36 has_one :repository, :dependent => :destroy
37 has_many :changesets, :through => :repository
37 has_many :changesets, :through => :repository
38 has_one :wiki, :dependent => :destroy
38 has_one :wiki, :dependent => :destroy
39 # Custom field for the project issues
39 # Custom field for the project issues
40 has_and_belongs_to_many :issue_custom_fields,
40 has_and_belongs_to_many :issue_custom_fields,
41 :class_name => 'IssueCustomField',
41 :class_name => 'IssueCustomField',
42 :order => "#{CustomField.table_name}.position",
42 :order => "#{CustomField.table_name}.position",
43 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
43 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
44 :association_foreign_key => 'custom_field_id'
44 :association_foreign_key => 'custom_field_id'
45
45
46 acts_as_tree :order => "name", :counter_cache => true
46 acts_as_tree :order => "name", :counter_cache => true
47 acts_as_attachable :view_permission => :view_files,
48 :delete_permission => :manage_files
47
49
48 acts_as_customizable
50 acts_as_customizable
49 acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil
51 acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil
50 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
52 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
51 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}},
53 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}},
52 :author => nil
54 :author => nil
53
55
54 attr_protected :status, :enabled_module_names
56 attr_protected :status, :enabled_module_names
55
57
56 validates_presence_of :name, :identifier
58 validates_presence_of :name, :identifier
57 validates_uniqueness_of :name, :identifier
59 validates_uniqueness_of :name, :identifier
58 validates_associated :repository, :wiki
60 validates_associated :repository, :wiki
59 validates_length_of :name, :maximum => 30
61 validates_length_of :name, :maximum => 30
60 validates_length_of :homepage, :maximum => 255
62 validates_length_of :homepage, :maximum => 255
61 validates_length_of :identifier, :in => 3..20
63 validates_length_of :identifier, :in => 3..20
62 validates_format_of :identifier, :with => /^[a-z0-9\-]*$/
64 validates_format_of :identifier, :with => /^[a-z0-9\-]*$/
63
65
64 before_destroy :delete_all_members
66 before_destroy :delete_all_members
65
67
66 named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } }
68 named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } }
67
69
68 def identifier=(identifier)
70 def identifier=(identifier)
69 super unless identifier_frozen?
71 super unless identifier_frozen?
70 end
72 end
71
73
72 def identifier_frozen?
74 def identifier_frozen?
73 errors[:identifier].nil? && !(new_record? || identifier.blank?)
75 errors[:identifier].nil? && !(new_record? || identifier.blank?)
74 end
76 end
75
77
76 def issues_with_subprojects(include_subprojects=false)
78 def issues_with_subprojects(include_subprojects=false)
77 conditions = nil
79 conditions = nil
78 if include_subprojects
80 if include_subprojects
79 ids = [id] + child_ids
81 ids = [id] + child_ids
80 conditions = ["#{Project.table_name}.id IN (#{ids.join(',')}) AND #{Project.visible_by}"]
82 conditions = ["#{Project.table_name}.id IN (#{ids.join(',')}) AND #{Project.visible_by}"]
81 end
83 end
82 conditions ||= ["#{Project.table_name}.id = ?", id]
84 conditions ||= ["#{Project.table_name}.id = ?", id]
83 # Quick and dirty fix for Rails 2 compatibility
85 # Quick and dirty fix for Rails 2 compatibility
84 Issue.send(:with_scope, :find => { :conditions => conditions }) do
86 Issue.send(:with_scope, :find => { :conditions => conditions }) do
85 Version.send(:with_scope, :find => { :conditions => conditions }) do
87 Version.send(:with_scope, :find => { :conditions => conditions }) do
86 yield
88 yield
87 end
89 end
88 end
90 end
89 end
91 end
90
92
91 # returns latest created projects
93 # returns latest created projects
92 # non public projects will be returned only if user is a member of those
94 # non public projects will be returned only if user is a member of those
93 def self.latest(user=nil, count=5)
95 def self.latest(user=nil, count=5)
94 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
96 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
95 end
97 end
96
98
97 def self.visible_by(user=nil)
99 def self.visible_by(user=nil)
98 user ||= User.current
100 user ||= User.current
99 if user && user.admin?
101 if user && user.admin?
100 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
102 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
101 elsif user && user.memberships.any?
103 elsif user && user.memberships.any?
102 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = #{connection.quoted_true} or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))"
104 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = #{connection.quoted_true} or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))"
103 else
105 else
104 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
106 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
105 end
107 end
106 end
108 end
107
109
108 def self.allowed_to_condition(user, permission, options={})
110 def self.allowed_to_condition(user, permission, options={})
109 statements = []
111 statements = []
110 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
112 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
111 if perm = Redmine::AccessControl.permission(permission)
113 if perm = Redmine::AccessControl.permission(permission)
112 unless perm.project_module.nil?
114 unless perm.project_module.nil?
113 # If the permission belongs to a project module, make sure the module is enabled
115 # If the permission belongs to a project module, make sure the module is enabled
114 base_statement << " AND EXISTS (SELECT em.id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}' AND em.project_id=#{Project.table_name}.id)"
116 base_statement << " AND EXISTS (SELECT em.id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}' AND em.project_id=#{Project.table_name}.id)"
115 end
117 end
116 end
118 end
117 if options[:project]
119 if options[:project]
118 project_statement = "#{Project.table_name}.id = #{options[:project].id}"
120 project_statement = "#{Project.table_name}.id = #{options[:project].id}"
119 project_statement << " OR #{Project.table_name}.parent_id = #{options[:project].id}" if options[:with_subprojects]
121 project_statement << " OR #{Project.table_name}.parent_id = #{options[:project].id}" if options[:with_subprojects]
120 base_statement = "(#{project_statement}) AND (#{base_statement})"
122 base_statement = "(#{project_statement}) AND (#{base_statement})"
121 end
123 end
122 if user.admin?
124 if user.admin?
123 # no restriction
125 # no restriction
124 else
126 else
125 statements << "1=0"
127 statements << "1=0"
126 if user.logged?
128 if user.logged?
127 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" if Role.non_member.allowed_to?(permission)
129 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" if Role.non_member.allowed_to?(permission)
128 allowed_project_ids = user.memberships.select {|m| m.role.allowed_to?(permission)}.collect {|m| m.project_id}
130 allowed_project_ids = user.memberships.select {|m| m.role.allowed_to?(permission)}.collect {|m| m.project_id}
129 statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
131 statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
130 elsif Role.anonymous.allowed_to?(permission)
132 elsif Role.anonymous.allowed_to?(permission)
131 # anonymous user allowed on public project
133 # anonymous user allowed on public project
132 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
134 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
133 else
135 else
134 # anonymous user is not authorized
136 # anonymous user is not authorized
135 end
137 end
136 end
138 end
137 statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
139 statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
138 end
140 end
139
141
140 def project_condition(with_subprojects)
142 def project_condition(with_subprojects)
141 cond = "#{Project.table_name}.id = #{id}"
143 cond = "#{Project.table_name}.id = #{id}"
142 cond = "(#{cond} OR #{Project.table_name}.parent_id = #{id})" if with_subprojects
144 cond = "(#{cond} OR #{Project.table_name}.parent_id = #{id})" if with_subprojects
143 cond
145 cond
144 end
146 end
145
147
146 def self.find(*args)
148 def self.find(*args)
147 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
149 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
148 project = find_by_identifier(*args)
150 project = find_by_identifier(*args)
149 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
151 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
150 project
152 project
151 else
153 else
152 super
154 super
153 end
155 end
154 end
156 end
155
157
156 def to_param
158 def to_param
157 # id is used for projects with a numeric identifier (compatibility)
159 # id is used for projects with a numeric identifier (compatibility)
158 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
160 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
159 end
161 end
160
162
161 def active?
163 def active?
162 self.status == STATUS_ACTIVE
164 self.status == STATUS_ACTIVE
163 end
165 end
164
166
165 def archive
167 def archive
166 # Archive subprojects if any
168 # Archive subprojects if any
167 children.each do |subproject|
169 children.each do |subproject|
168 subproject.archive
170 subproject.archive
169 end
171 end
170 update_attribute :status, STATUS_ARCHIVED
172 update_attribute :status, STATUS_ARCHIVED
171 end
173 end
172
174
173 def unarchive
175 def unarchive
174 return false if parent && !parent.active?
176 return false if parent && !parent.active?
175 update_attribute :status, STATUS_ACTIVE
177 update_attribute :status, STATUS_ACTIVE
176 end
178 end
177
179
178 def active_children
180 def active_children
179 children.select {|child| child.active?}
181 children.select {|child| child.active?}
180 end
182 end
181
183
182 # Returns an array of the trackers used by the project and its sub projects
184 # Returns an array of the trackers used by the project and its sub projects
183 def rolled_up_trackers
185 def rolled_up_trackers
184 @rolled_up_trackers ||=
186 @rolled_up_trackers ||=
185 Tracker.find(:all, :include => :projects,
187 Tracker.find(:all, :include => :projects,
186 :select => "DISTINCT #{Tracker.table_name}.*",
188 :select => "DISTINCT #{Tracker.table_name}.*",
187 :conditions => ["#{Project.table_name}.id = ? OR #{Project.table_name}.parent_id = ?", id, id],
189 :conditions => ["#{Project.table_name}.id = ? OR #{Project.table_name}.parent_id = ?", id, id],
188 :order => "#{Tracker.table_name}.position")
190 :order => "#{Tracker.table_name}.position")
189 end
191 end
190
192
191 # Deletes all project's members
193 # Deletes all project's members
192 def delete_all_members
194 def delete_all_members
193 Member.delete_all(['project_id = ?', id])
195 Member.delete_all(['project_id = ?', id])
194 end
196 end
195
197
196 # Users issues can be assigned to
198 # Users issues can be assigned to
197 def assignable_users
199 def assignable_users
198 members.select {|m| m.role.assignable?}.collect {|m| m.user}.sort
200 members.select {|m| m.role.assignable?}.collect {|m| m.user}.sort
199 end
201 end
200
202
201 # Returns the mail adresses of users that should be always notified on project events
203 # Returns the mail adresses of users that should be always notified on project events
202 def recipients
204 def recipients
203 members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user.mail}
205 members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user.mail}
204 end
206 end
205
207
206 # Returns an array of all custom fields enabled for project issues
208 # Returns an array of all custom fields enabled for project issues
207 # (explictly associated custom fields and custom fields enabled for all projects)
209 # (explictly associated custom fields and custom fields enabled for all projects)
208 def all_issue_custom_fields
210 def all_issue_custom_fields
209 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
211 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
210 end
212 end
211
213
212 def project
214 def project
213 self
215 self
214 end
216 end
215
217
216 def <=>(project)
218 def <=>(project)
217 name.downcase <=> project.name.downcase
219 name.downcase <=> project.name.downcase
218 end
220 end
219
221
220 def to_s
222 def to_s
221 name
223 name
222 end
224 end
223
225
224 # Returns a short description of the projects (first lines)
226 # Returns a short description of the projects (first lines)
225 def short_description(length = 255)
227 def short_description(length = 255)
226 description.gsub(/^(.{#{length}}[^\n]*).*$/m, '\1').strip if description
228 description.gsub(/^(.{#{length}}[^\n]*).*$/m, '\1').strip if description
227 end
229 end
228
230
229 def allows_to?(action)
231 def allows_to?(action)
230 if action.is_a? Hash
232 if action.is_a? Hash
231 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
233 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
232 else
234 else
233 allowed_permissions.include? action
235 allowed_permissions.include? action
234 end
236 end
235 end
237 end
236
238
237 def module_enabled?(module_name)
239 def module_enabled?(module_name)
238 module_name = module_name.to_s
240 module_name = module_name.to_s
239 enabled_modules.detect {|m| m.name == module_name}
241 enabled_modules.detect {|m| m.name == module_name}
240 end
242 end
241
243
242 def enabled_module_names=(module_names)
244 def enabled_module_names=(module_names)
243 enabled_modules.clear
245 enabled_modules.clear
244 module_names = [] unless module_names && module_names.is_a?(Array)
246 module_names = [] unless module_names && module_names.is_a?(Array)
245 module_names.each do |name|
247 module_names.each do |name|
246 enabled_modules << EnabledModule.new(:name => name.to_s)
248 enabled_modules << EnabledModule.new(:name => name.to_s)
247 end
249 end
248 end
250 end
249
251
250 # Returns an auto-generated project identifier based on the last identifier used
252 # Returns an auto-generated project identifier based on the last identifier used
251 def self.next_identifier
253 def self.next_identifier
252 p = Project.find(:first, :order => 'created_on DESC')
254 p = Project.find(:first, :order => 'created_on DESC')
253 p.nil? ? nil : p.identifier.to_s.succ
255 p.nil? ? nil : p.identifier.to_s.succ
254 end
256 end
255
257
256 protected
258 protected
257 def validate
259 def validate
258 errors.add(parent_id, " must be a root project") if parent and parent.parent
260 errors.add(parent_id, " must be a root project") if parent and parent.parent
259 errors.add_to_base("A project with subprojects can't be a subproject") if parent and children.size > 0
261 errors.add_to_base("A project with subprojects can't be a subproject") if parent and children.size > 0
260 errors.add(:identifier, :activerecord_error_invalid) if !identifier.blank? && identifier.match(/^\d*$/)
262 errors.add(:identifier, :activerecord_error_invalid) if !identifier.blank? && identifier.match(/^\d*$/)
261 end
263 end
262
264
263 private
265 private
264 def allowed_permissions
266 def allowed_permissions
265 @allowed_permissions ||= begin
267 @allowed_permissions ||= begin
266 module_names = enabled_modules.collect {|m| m.name}
268 module_names = enabled_modules.collect {|m| m.name}
267 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
269 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
268 end
270 end
269 end
271 end
270
272
271 def allowed_actions
273 def allowed_actions
272 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
274 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
273 end
275 end
274 end
276 end
@@ -1,13 +1,16
1 <h2><%=l(:label_attachment_new)%></h2>
1 <h2><%=l(:label_attachment_new)%></h2>
2
2
3 <%= error_messages_for 'attachment' %>
3 <%= error_messages_for 'attachment' %>
4 <div class="box">
4 <div class="box">
5 <% form_tag({ :action => 'add_file', :id => @project }, :multipart => true, :class => "tabular") do %>
5 <% form_tag({ :action => 'add_file', :id => @project }, :multipart => true, :class => "tabular") do %>
6
6
7 <p><label for="version_id"><%=l(:field_version)%> <span class="required">*</span></label>
7 <% if @versions.any? %>
8 <%= select_tag "version_id", options_from_collection_for_select(@versions, "id", "name") %></p>
8 <p><label for="version_id"><%=l(:field_version)%></label>
9 <%= select_tag "version_id", content_tag('option', '') +
10 options_from_collection_for_select(@versions, "id", "name") %></p>
11 <% end %>
9
12
10 <p><label><%=l(:label_attachment_plural)%></label><%= render :partial => 'attachments/form' %></p>
13 <p><label><%=l(:label_attachment_plural)%></label><%= render :partial => 'attachments/form' %></p>
11 </div>
14 </div>
12 <%= submit_tag l(:button_add) %>
15 <%= submit_tag l(:button_add) %>
13 <% end %>
16 <% end %>
@@ -1,45 +1,42
1 <div class="contextual">
1 <div class="contextual">
2 <%= link_to_if_authorized l(:label_attachment_new), {:controller => 'projects', :action => 'add_file', :id => @project}, :class => 'icon icon-add' %>
2 <%= link_to_if_authorized l(:label_attachment_new), {:controller => 'projects', :action => 'add_file', :id => @project}, :class => 'icon icon-add' %>
3 </div>
3 </div>
4
4
5 <h2><%=l(:label_attachment_plural)%></h2>
5 <h2><%=l(:label_attachment_plural)%></h2>
6
6
7 <% delete_allowed = User.current.allowed_to?(:manage_files, @project) %>
7 <% delete_allowed = User.current.allowed_to?(:manage_files, @project) %>
8
8
9 <table class="list">
9 <table class="list">
10 <thead><tr>
10 <thead><tr>
11 <th><%=l(:field_version)%></th>
12 <%= sort_header_tag("#{Attachment.table_name}.filename", :caption => l(:field_filename)) %>
11 <%= sort_header_tag("#{Attachment.table_name}.filename", :caption => l(:field_filename)) %>
13 <%= sort_header_tag("#{Attachment.table_name}.created_on", :caption => l(:label_date), :default_order => 'desc') %>
12 <%= sort_header_tag("#{Attachment.table_name}.created_on", :caption => l(:label_date), :default_order => 'desc') %>
14 <%= sort_header_tag("#{Attachment.table_name}.filesize", :caption => l(:field_filesize), :default_order => 'desc') %>
13 <%= sort_header_tag("#{Attachment.table_name}.filesize", :caption => l(:field_filesize), :default_order => 'desc') %>
15 <%= sort_header_tag("#{Attachment.table_name}.downloads", :caption => l(:label_downloads_abbr), :default_order => 'desc') %>
14 <%= sort_header_tag("#{Attachment.table_name}.downloads", :caption => l(:label_downloads_abbr), :default_order => 'desc') %>
16 <th>MD5</th>
15 <th>MD5</th>
17 <% if delete_allowed %><th></th><% end %>
16 <th></th>
18 </tr></thead>
17 </tr></thead>
19 <tbody>
18 <tbody>
20 <% for version in @versions %>
19 <% @containers.each do |container| %>
21 <% unless version.attachments.empty? %>
20 <% next if container.attachments.empty? -%>
22 <tr><th colspan="7" align="left"><span class="icon icon-package"><b><%= version.name %></b></span></th></tr>
21 <% if container.is_a?(Version) -%>
23 <% for file in version.attachments %>
22 <tr><th colspan="6" align="left"><span class="icon icon-package"><b><%=h container %></b></span></th></tr>
23 <% end -%>
24 <% container.attachments.each do |file| %>
24 <tr class="<%= cycle("odd", "even") %>">
25 <tr class="<%= cycle("odd", "even") %>">
25 <td></td>
26 <td><%= link_to_attachment file, :download => true, :title => file.description %></td>
26 <td><%= link_to_attachment file, :download => true, :title => file.description %></td>
27 <td align="center"><%= format_time(file.created_on) %></td>
27 <td align="center"><%= format_time(file.created_on) %></td>
28 <td align="center"><%= number_to_human_size(file.filesize) %></td>
28 <td align="center"><%= number_to_human_size(file.filesize) %></td>
29 <td align="center"><%= file.downloads %></td>
29 <td align="center"><%= file.downloads %></td>
30 <td align="center"><small><%= file.digest %></small></td>
30 <td align="center"><small><%= file.digest %></small></td>
31 <% if delete_allowed %>
32 <td align="center">
31 <td align="center">
33 <%= link_to image_tag('delete.png'), {:controller => 'attachments', :action => 'destroy', :id => file},
32 <%= link_to(image_tag('delete.png'), {:controller => 'attachments', :action => 'destroy', :id => file},
34 :confirm => l(:text_are_you_sure), :method => :post %>
33 :confirm => l(:text_are_you_sure), :method => :post) if delete_allowed %>
35 </td>
34 </td>
36 <% end %>
37 </tr>
35 </tr>
38 <% end
36 <% end
39 reset_cycle %>
37 reset_cycle %>
40 <% end %>
38 <% end %>
41 <% end %>
42 </tbody>
39 </tbody>
43 </table>
40 </table>
44
41
45 <% html_title(l(:label_attachment_plural)) -%>
42 <% html_title(l(:label_attachment_plural)) -%>
@@ -1,88 +1,112
1 ---
1 ---
2 attachments_001:
2 attachments_001:
3 created_on: 2006-07-19 21:07:27 +02:00
3 created_on: 2006-07-19 21:07:27 +02:00
4 downloads: 0
4 downloads: 0
5 content_type: text/plain
5 content_type: text/plain
6 disk_filename: 060719210727_error281.txt
6 disk_filename: 060719210727_error281.txt
7 container_id: 3
7 container_id: 3
8 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
8 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
9 id: 1
9 id: 1
10 container_type: Issue
10 container_type: Issue
11 filesize: 28
11 filesize: 28
12 filename: error281.txt
12 filename: error281.txt
13 author_id: 2
13 author_id: 2
14 attachments_002:
14 attachments_002:
15 created_on: 2006-07-19 21:07:27 +02:00
15 created_on: 2006-07-19 21:07:27 +02:00
16 downloads: 0
16 downloads: 0
17 content_type: text/plain
17 content_type: text/plain
18 disk_filename: 060719210727_document.txt
18 disk_filename: 060719210727_document.txt
19 container_id: 1
19 container_id: 1
20 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
20 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
21 id: 2
21 id: 2
22 container_type: Document
22 container_type: Document
23 filesize: 28
23 filesize: 28
24 filename: document.txt
24 filename: document.txt
25 author_id: 2
25 author_id: 2
26 attachments_003:
26 attachments_003:
27 created_on: 2006-07-19 21:07:27 +02:00
27 created_on: 2006-07-19 21:07:27 +02:00
28 downloads: 0
28 downloads: 0
29 content_type: image/gif
29 content_type: image/gif
30 disk_filename: 060719210727_logo.gif
30 disk_filename: 060719210727_logo.gif
31 container_id: 4
31 container_id: 4
32 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
32 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
33 id: 3
33 id: 3
34 container_type: WikiPage
34 container_type: WikiPage
35 filesize: 280
35 filesize: 280
36 filename: logo.gif
36 filename: logo.gif
37 description: This is a logo
37 description: This is a logo
38 author_id: 2
38 author_id: 2
39 attachments_004:
39 attachments_004:
40 created_on: 2006-07-19 21:07:27 +02:00
40 created_on: 2006-07-19 21:07:27 +02:00
41 container_type: Issue
41 container_type: Issue
42 container_id: 3
42 container_id: 3
43 downloads: 0
43 downloads: 0
44 disk_filename: 060719210727_source.rb
44 disk_filename: 060719210727_source.rb
45 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
45 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
46 id: 4
46 id: 4
47 filesize: 153
47 filesize: 153
48 filename: source.rb
48 filename: source.rb
49 author_id: 2
49 author_id: 2
50 description: This is a Ruby source file
50 description: This is a Ruby source file
51 content_type: application/x-ruby
51 content_type: application/x-ruby
52 attachments_005:
52 attachments_005:
53 created_on: 2006-07-19 21:07:27 +02:00
53 created_on: 2006-07-19 21:07:27 +02:00
54 container_type: Issue
54 container_type: Issue
55 container_id: 3
55 container_id: 3
56 downloads: 0
56 downloads: 0
57 disk_filename: 060719210727_changeset.diff
57 disk_filename: 060719210727_changeset.diff
58 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
58 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
59 id: 5
59 id: 5
60 filesize: 687
60 filesize: 687
61 filename: changeset.diff
61 filename: changeset.diff
62 author_id: 2
62 author_id: 2
63 content_type: text/x-diff
63 content_type: text/x-diff
64 attachments_006:
64 attachments_006:
65 created_on: 2006-07-19 21:07:27 +02:00
65 created_on: 2006-07-19 21:07:27 +02:00
66 container_type: Issue
66 container_type: Issue
67 container_id: 3
67 container_id: 3
68 downloads: 0
68 downloads: 0
69 disk_filename: 060719210727_archive.zip
69 disk_filename: 060719210727_archive.zip
70 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
70 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
71 id: 6
71 id: 6
72 filesize: 157
72 filesize: 157
73 filename: archive.zip
73 filename: archive.zip
74 author_id: 2
74 author_id: 2
75 content_type: application/octet-stream
75 content_type: application/octet-stream
76 attachments_007:
76 attachments_007:
77 created_on: 2006-07-19 21:07:27 +02:00
77 created_on: 2006-07-19 21:07:27 +02:00
78 container_type: Issue
78 container_type: Issue
79 container_id: 4
79 container_id: 4
80 downloads: 0
80 downloads: 0
81 disk_filename: 060719210727_archive.zip
81 disk_filename: 060719210727_archive.zip
82 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
82 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
83 id: 7
83 id: 7
84 filesize: 157
84 filesize: 157
85 filename: archive.zip
85 filename: archive.zip
86 author_id: 1
86 author_id: 1
87 content_type: application/octet-stream
87 content_type: application/octet-stream
88 attachments_008:
89 created_on: 2006-07-19 21:07:27 +02:00
90 container_type: Project
91 container_id: 1
92 downloads: 0
93 disk_filename: 060719210727_project_file.zip
94 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
95 id: 8
96 filesize: 320
97 filename: project_file.zip
98 author_id: 2
99 content_type: application/octet-stream
100 attachments_009:
101 created_on: 2006-07-19 21:07:27 +02:00
102 container_type: Version
103 container_id: 1
104 downloads: 0
105 disk_filename: 060719210727_version_file.zip
106 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
107 id: 9
108 filesize: 452
109 filename: version_file.zip
110 author_id: 2
111 content_type: application/octet-stream
88 No newline at end of file
112
@@ -1,108 +1,125
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'attachments_controller'
19 require 'attachments_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class AttachmentsController; def rescue_action(e) raise e end; end
22 class AttachmentsController; def rescue_action(e) raise e end; end
23
23
24
24
25 class AttachmentsControllerTest < Test::Unit::TestCase
25 class AttachmentsControllerTest < Test::Unit::TestCase
26 fixtures :users, :projects, :roles, :members, :enabled_modules, :issues, :attachments
26 fixtures :users, :projects, :roles, :members, :enabled_modules, :issues, :attachments
27
27
28 def setup
28 def setup
29 @controller = AttachmentsController.new
29 @controller = AttachmentsController.new
30 @request = ActionController::TestRequest.new
30 @request = ActionController::TestRequest.new
31 @response = ActionController::TestResponse.new
31 @response = ActionController::TestResponse.new
32 Attachment.storage_path = "#{RAILS_ROOT}/test/fixtures/files"
32 Attachment.storage_path = "#{RAILS_ROOT}/test/fixtures/files"
33 User.current = nil
33 User.current = nil
34 end
34 end
35
35
36 def test_routing
36 def test_routing
37 assert_routing('/attachments/1', :controller => 'attachments', :action => 'show', :id => '1')
37 assert_routing('/attachments/1', :controller => 'attachments', :action => 'show', :id => '1')
38 assert_routing('/attachments/1/filename.ext', :controller => 'attachments', :action => 'show', :id => '1', :filename => 'filename.ext')
38 assert_routing('/attachments/1/filename.ext', :controller => 'attachments', :action => 'show', :id => '1', :filename => 'filename.ext')
39 assert_routing('/attachments/download/1', :controller => 'attachments', :action => 'download', :id => '1')
39 assert_routing('/attachments/download/1', :controller => 'attachments', :action => 'download', :id => '1')
40 assert_routing('/attachments/download/1/filename.ext', :controller => 'attachments', :action => 'download', :id => '1', :filename => 'filename.ext')
40 assert_routing('/attachments/download/1/filename.ext', :controller => 'attachments', :action => 'download', :id => '1', :filename => 'filename.ext')
41 end
41 end
42
42
43 def test_recognizes
43 def test_recognizes
44 assert_recognizes({:controller => 'attachments', :action => 'show', :id => '1'}, '/attachments/1')
44 assert_recognizes({:controller => 'attachments', :action => 'show', :id => '1'}, '/attachments/1')
45 assert_recognizes({:controller => 'attachments', :action => 'show', :id => '1'}, '/attachments/show/1')
45 assert_recognizes({:controller => 'attachments', :action => 'show', :id => '1'}, '/attachments/show/1')
46 assert_recognizes({:controller => 'attachments', :action => 'show', :id => '1', :filename => 'filename.ext'}, '/attachments/1/filename.ext')
46 assert_recognizes({:controller => 'attachments', :action => 'show', :id => '1', :filename => 'filename.ext'}, '/attachments/1/filename.ext')
47 assert_recognizes({:controller => 'attachments', :action => 'download', :id => '1'}, '/attachments/download/1')
47 assert_recognizes({:controller => 'attachments', :action => 'download', :id => '1'}, '/attachments/download/1')
48 assert_recognizes({:controller => 'attachments', :action => 'download', :id => '1', :filename => 'filename.ext'},'/attachments/download/1/filename.ext')
48 assert_recognizes({:controller => 'attachments', :action => 'download', :id => '1', :filename => 'filename.ext'},'/attachments/download/1/filename.ext')
49 end
49 end
50
50
51 def test_show_diff
51 def test_show_diff
52 get :show, :id => 5
52 get :show, :id => 5
53 assert_response :success
53 assert_response :success
54 assert_template 'diff'
54 assert_template 'diff'
55 end
55 end
56
56
57 def test_show_text_file
57 def test_show_text_file
58 get :show, :id => 4
58 get :show, :id => 4
59 assert_response :success
59 assert_response :success
60 assert_template 'file'
60 assert_template 'file'
61 end
61 end
62
62
63 def test_show_other
63 def test_show_other
64 get :show, :id => 6
64 get :show, :id => 6
65 assert_response :success
65 assert_response :success
66 assert_equal 'application/octet-stream', @response.content_type
66 assert_equal 'application/octet-stream', @response.content_type
67 end
67 end
68
68
69 def test_download_text_file
69 def test_download_text_file
70 get :download, :id => 4
70 get :download, :id => 4
71 assert_response :success
71 assert_response :success
72 assert_equal 'application/x-ruby', @response.content_type
72 assert_equal 'application/x-ruby', @response.content_type
73 end
73 end
74
74
75 def test_anonymous_on_private_private
75 def test_anonymous_on_private_private
76 get :download, :id => 7
76 get :download, :id => 7
77 assert_redirected_to 'account/login'
77 assert_redirected_to 'account/login'
78 end
78 end
79
79
80 def test_destroy_issue_attachment
80 def test_destroy_issue_attachment
81 issue = Issue.find(3)
81 issue = Issue.find(3)
82 @request.session[:user_id] = 2
82 @request.session[:user_id] = 2
83
83
84 assert_difference 'issue.attachments.count', -1 do
84 assert_difference 'issue.attachments.count', -1 do
85 post :destroy, :id => 1
85 post :destroy, :id => 1
86 end
86 end
87 # no referrer
87 # no referrer
88 assert_redirected_to 'projects/show/ecookbook'
88 assert_redirected_to 'projects/show/ecookbook'
89 assert_nil Attachment.find_by_id(1)
89 assert_nil Attachment.find_by_id(1)
90 j = issue.journals.find(:first, :order => 'created_on DESC')
90 j = issue.journals.find(:first, :order => 'created_on DESC')
91 assert_equal 'attachment', j.details.first.property
91 assert_equal 'attachment', j.details.first.property
92 assert_equal '1', j.details.first.prop_key
92 assert_equal '1', j.details.first.prop_key
93 assert_equal 'error281.txt', j.details.first.old_value
93 assert_equal 'error281.txt', j.details.first.old_value
94 end
94 end
95
95
96 def test_destroy_wiki_page_attachment
96 def test_destroy_wiki_page_attachment
97 @request.session[:user_id] = 2
97 @request.session[:user_id] = 2
98 assert_difference 'Attachment.count', -1 do
98 assert_difference 'Attachment.count', -1 do
99 post :destroy, :id => 3
99 post :destroy, :id => 3
100 assert_response 302
101 end
102 end
103
104 def test_destroy_project_attachment
105 @request.session[:user_id] = 2
106 assert_difference 'Attachment.count', -1 do
107 post :destroy, :id => 8
108 assert_response 302
109 end
110 end
111
112 def test_destroy_version_attachment
113 @request.session[:user_id] = 2
114 assert_difference 'Attachment.count', -1 do
115 post :destroy, :id => 9
116 assert_response 302
100 end
117 end
101 end
118 end
102
119
103 def test_destroy_without_permission
120 def test_destroy_without_permission
104 post :destroy, :id => 3
121 post :destroy, :id => 3
105 assert_redirected_to '/login'
122 assert_redirected_to '/login'
106 assert Attachment.find_by_id(3)
123 assert Attachment.find_by_id(3)
107 end
124 end
108 end
125 end
@@ -1,295 +1,340
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'projects_controller'
19 require 'projects_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class ProjectsController; def rescue_action(e) raise e end; end
22 class ProjectsController; def rescue_action(e) raise e end; end
23
23
24 class ProjectsControllerTest < Test::Unit::TestCase
24 class ProjectsControllerTest < Test::Unit::TestCase
25 fixtures :projects, :versions, :users, :roles, :members, :issues, :journals, :journal_details,
25 fixtures :projects, :versions, :users, :roles, :members, :issues, :journals, :journal_details,
26 :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages
26 :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
27 :attachments
27
28
28 def setup
29 def setup
29 @controller = ProjectsController.new
30 @controller = ProjectsController.new
30 @request = ActionController::TestRequest.new
31 @request = ActionController::TestRequest.new
31 @response = ActionController::TestResponse.new
32 @response = ActionController::TestResponse.new
32 @request.session[:user_id] = nil
33 @request.session[:user_id] = nil
33 Setting.default_language = 'en'
34 Setting.default_language = 'en'
34 end
35 end
35
36
36 def test_index
37 def test_index
37 get :index
38 get :index
38 assert_response :success
39 assert_response :success
39 assert_template 'index'
40 assert_template 'index'
40 assert_not_nil assigns(:project_tree)
41 assert_not_nil assigns(:project_tree)
41 # Root project as hash key
42 # Root project as hash key
42 assert assigns(:project_tree).keys.include?(Project.find(1))
43 assert assigns(:project_tree).keys.include?(Project.find(1))
43 # Subproject in corresponding value
44 # Subproject in corresponding value
44 assert assigns(:project_tree)[Project.find(1)].include?(Project.find(3))
45 assert assigns(:project_tree)[Project.find(1)].include?(Project.find(3))
45 end
46 end
46
47
47 def test_index_atom
48 def test_index_atom
48 get :index, :format => 'atom'
49 get :index, :format => 'atom'
49 assert_response :success
50 assert_response :success
50 assert_template 'common/feed.atom.rxml'
51 assert_template 'common/feed.atom.rxml'
51 assert_select 'feed>title', :text => 'Redmine: Latest projects'
52 assert_select 'feed>title', :text => 'Redmine: Latest projects'
52 assert_select 'feed>entry', :count => Project.count(:conditions => Project.visible_by(User.current))
53 assert_select 'feed>entry', :count => Project.count(:conditions => Project.visible_by(User.current))
53 end
54 end
54
55
55 def test_show_by_id
56 def test_show_by_id
56 get :show, :id => 1
57 get :show, :id => 1
57 assert_response :success
58 assert_response :success
58 assert_template 'show'
59 assert_template 'show'
59 assert_not_nil assigns(:project)
60 assert_not_nil assigns(:project)
60 end
61 end
61
62
62 def test_show_by_identifier
63 def test_show_by_identifier
63 get :show, :id => 'ecookbook'
64 get :show, :id => 'ecookbook'
64 assert_response :success
65 assert_response :success
65 assert_template 'show'
66 assert_template 'show'
66 assert_not_nil assigns(:project)
67 assert_not_nil assigns(:project)
67 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
68 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
68 end
69 end
69
70
70 def test_private_subprojects_hidden
71 def test_private_subprojects_hidden
71 get :show, :id => 'ecookbook'
72 get :show, :id => 'ecookbook'
72 assert_response :success
73 assert_response :success
73 assert_template 'show'
74 assert_template 'show'
74 assert_no_tag :tag => 'a', :content => /Private child/
75 assert_no_tag :tag => 'a', :content => /Private child/
75 end
76 end
76
77
77 def test_private_subprojects_visible
78 def test_private_subprojects_visible
78 @request.session[:user_id] = 2 # manager who is a member of the private subproject
79 @request.session[:user_id] = 2 # manager who is a member of the private subproject
79 get :show, :id => 'ecookbook'
80 get :show, :id => 'ecookbook'
80 assert_response :success
81 assert_response :success
81 assert_template 'show'
82 assert_template 'show'
82 assert_tag :tag => 'a', :content => /Private child/
83 assert_tag :tag => 'a', :content => /Private child/
83 end
84 end
84
85
85 def test_settings
86 def test_settings
86 @request.session[:user_id] = 2 # manager
87 @request.session[:user_id] = 2 # manager
87 get :settings, :id => 1
88 get :settings, :id => 1
88 assert_response :success
89 assert_response :success
89 assert_template 'settings'
90 assert_template 'settings'
90 end
91 end
91
92
92 def test_edit
93 def test_edit
93 @request.session[:user_id] = 2 # manager
94 @request.session[:user_id] = 2 # manager
94 post :edit, :id => 1, :project => {:name => 'Test changed name',
95 post :edit, :id => 1, :project => {:name => 'Test changed name',
95 :issue_custom_field_ids => ['']}
96 :issue_custom_field_ids => ['']}
96 assert_redirected_to 'projects/settings/ecookbook'
97 assert_redirected_to 'projects/settings/ecookbook'
97 project = Project.find(1)
98 project = Project.find(1)
98 assert_equal 'Test changed name', project.name
99 assert_equal 'Test changed name', project.name
99 end
100 end
100
101
101 def test_get_destroy
102 def test_get_destroy
102 @request.session[:user_id] = 1 # admin
103 @request.session[:user_id] = 1 # admin
103 get :destroy, :id => 1
104 get :destroy, :id => 1
104 assert_response :success
105 assert_response :success
105 assert_template 'destroy'
106 assert_template 'destroy'
106 assert_not_nil Project.find_by_id(1)
107 assert_not_nil Project.find_by_id(1)
107 end
108 end
108
109
109 def test_post_destroy
110 def test_post_destroy
110 @request.session[:user_id] = 1 # admin
111 @request.session[:user_id] = 1 # admin
111 post :destroy, :id => 1, :confirm => 1
112 post :destroy, :id => 1, :confirm => 1
112 assert_redirected_to 'admin/projects'
113 assert_redirected_to 'admin/projects'
113 assert_nil Project.find_by_id(1)
114 assert_nil Project.find_by_id(1)
114 end
115 end
115
116
117 def test_add_file
118 set_tmp_attachments_directory
119 @request.session[:user_id] = 2
120 Setting.notified_events << 'file_added'
121 ActionMailer::Base.deliveries.clear
122
123 assert_difference 'Attachment.count' do
124 post :add_file, :id => 1, :version_id => '',
125 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
126 end
127 assert_redirected_to 'projects/list_files/ecookbook'
128 a = Attachment.find(:first, :order => 'created_on DESC')
129 assert_equal 'testfile.txt', a.filename
130 assert_equal Project.find(1), a.container
131
132 mail = ActionMailer::Base.deliveries.last
133 assert_kind_of TMail::Mail, mail
134 assert_equal "[eCookbook] New file", mail.subject
135 assert mail.body.include?('testfile.txt')
136 end
137
138 def test_add_version_file
139 set_tmp_attachments_directory
140 @request.session[:user_id] = 2
141 Setting.notified_events << 'file_added'
142
143 assert_difference 'Attachment.count' do
144 post :add_file, :id => 1, :version_id => '2',
145 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
146 end
147 assert_redirected_to 'projects/list_files/ecookbook'
148 a = Attachment.find(:first, :order => 'created_on DESC')
149 assert_equal 'testfile.txt', a.filename
150 assert_equal Version.find(2), a.container
151 end
152
116 def test_list_files
153 def test_list_files
117 get :list_files, :id => 1
154 get :list_files, :id => 1
118 assert_response :success
155 assert_response :success
119 assert_template 'list_files'
156 assert_template 'list_files'
120 assert_not_nil assigns(:versions)
157 assert_not_nil assigns(:containers)
158
159 # file attached to the project
160 assert_tag :a, :content => 'project_file.zip',
161 :attributes => { :href => '/attachments/download/8/project_file.zip' }
162
163 # file attached to a project's version
164 assert_tag :a, :content => 'version_file.zip',
165 :attributes => { :href => '/attachments/download/9/version_file.zip' }
121 end
166 end
122
167
123 def test_changelog
168 def test_changelog
124 get :changelog, :id => 1
169 get :changelog, :id => 1
125 assert_response :success
170 assert_response :success
126 assert_template 'changelog'
171 assert_template 'changelog'
127 assert_not_nil assigns(:versions)
172 assert_not_nil assigns(:versions)
128 end
173 end
129
174
130 def test_roadmap
175 def test_roadmap
131 get :roadmap, :id => 1
176 get :roadmap, :id => 1
132 assert_response :success
177 assert_response :success
133 assert_template 'roadmap'
178 assert_template 'roadmap'
134 assert_not_nil assigns(:versions)
179 assert_not_nil assigns(:versions)
135 # Version with no date set appears
180 # Version with no date set appears
136 assert assigns(:versions).include?(Version.find(3))
181 assert assigns(:versions).include?(Version.find(3))
137 # Completed version doesn't appear
182 # Completed version doesn't appear
138 assert !assigns(:versions).include?(Version.find(1))
183 assert !assigns(:versions).include?(Version.find(1))
139 end
184 end
140
185
141 def test_roadmap_with_completed_versions
186 def test_roadmap_with_completed_versions
142 get :roadmap, :id => 1, :completed => 1
187 get :roadmap, :id => 1, :completed => 1
143 assert_response :success
188 assert_response :success
144 assert_template 'roadmap'
189 assert_template 'roadmap'
145 assert_not_nil assigns(:versions)
190 assert_not_nil assigns(:versions)
146 # Version with no date set appears
191 # Version with no date set appears
147 assert assigns(:versions).include?(Version.find(3))
192 assert assigns(:versions).include?(Version.find(3))
148 # Completed version appears
193 # Completed version appears
149 assert assigns(:versions).include?(Version.find(1))
194 assert assigns(:versions).include?(Version.find(1))
150 end
195 end
151
196
152 def test_project_activity
197 def test_project_activity
153 get :activity, :id => 1, :with_subprojects => 0
198 get :activity, :id => 1, :with_subprojects => 0
154 assert_response :success
199 assert_response :success
155 assert_template 'activity'
200 assert_template 'activity'
156 assert_not_nil assigns(:events_by_day)
201 assert_not_nil assigns(:events_by_day)
157
202
158 assert_tag :tag => "h3",
203 assert_tag :tag => "h3",
159 :content => /#{2.days.ago.to_date.day}/,
204 :content => /#{2.days.ago.to_date.day}/,
160 :sibling => { :tag => "dl",
205 :sibling => { :tag => "dl",
161 :child => { :tag => "dt",
206 :child => { :tag => "dt",
162 :attributes => { :class => /issue-edit/ },
207 :attributes => { :class => /issue-edit/ },
163 :child => { :tag => "a",
208 :child => { :tag => "a",
164 :content => /(#{IssueStatus.find(2).name})/,
209 :content => /(#{IssueStatus.find(2).name})/,
165 }
210 }
166 }
211 }
167 }
212 }
168 end
213 end
169
214
170 def test_previous_project_activity
215 def test_previous_project_activity
171 get :activity, :id => 1, :from => 3.days.ago.to_date
216 get :activity, :id => 1, :from => 3.days.ago.to_date
172 assert_response :success
217 assert_response :success
173 assert_template 'activity'
218 assert_template 'activity'
174 assert_not_nil assigns(:events_by_day)
219 assert_not_nil assigns(:events_by_day)
175
220
176 assert_tag :tag => "h3",
221 assert_tag :tag => "h3",
177 :content => /#{3.day.ago.to_date.day}/,
222 :content => /#{3.day.ago.to_date.day}/,
178 :sibling => { :tag => "dl",
223 :sibling => { :tag => "dl",
179 :child => { :tag => "dt",
224 :child => { :tag => "dt",
180 :attributes => { :class => /issue/ },
225 :attributes => { :class => /issue/ },
181 :child => { :tag => "a",
226 :child => { :tag => "a",
182 :content => /#{Issue.find(1).subject}/,
227 :content => /#{Issue.find(1).subject}/,
183 }
228 }
184 }
229 }
185 }
230 }
186 end
231 end
187
232
188 def test_global_activity
233 def test_global_activity
189 get :activity
234 get :activity
190 assert_response :success
235 assert_response :success
191 assert_template 'activity'
236 assert_template 'activity'
192 assert_not_nil assigns(:events_by_day)
237 assert_not_nil assigns(:events_by_day)
193
238
194 assert_tag :tag => "h3",
239 assert_tag :tag => "h3",
195 :content => /#{5.day.ago.to_date.day}/,
240 :content => /#{5.day.ago.to_date.day}/,
196 :sibling => { :tag => "dl",
241 :sibling => { :tag => "dl",
197 :child => { :tag => "dt",
242 :child => { :tag => "dt",
198 :attributes => { :class => /issue/ },
243 :attributes => { :class => /issue/ },
199 :child => { :tag => "a",
244 :child => { :tag => "a",
200 :content => /#{Issue.find(5).subject}/,
245 :content => /#{Issue.find(5).subject}/,
201 }
246 }
202 }
247 }
203 }
248 }
204 end
249 end
205
250
206 def test_user_activity
251 def test_user_activity
207 get :activity, :user_id => 2
252 get :activity, :user_id => 2
208 assert_response :success
253 assert_response :success
209 assert_template 'activity'
254 assert_template 'activity'
210 assert_not_nil assigns(:events_by_day)
255 assert_not_nil assigns(:events_by_day)
211
256
212 assert_tag :tag => "h3",
257 assert_tag :tag => "h3",
213 :content => /#{3.day.ago.to_date.day}/,
258 :content => /#{3.day.ago.to_date.day}/,
214 :sibling => { :tag => "dl",
259 :sibling => { :tag => "dl",
215 :child => { :tag => "dt",
260 :child => { :tag => "dt",
216 :attributes => { :class => /issue/ },
261 :attributes => { :class => /issue/ },
217 :child => { :tag => "a",
262 :child => { :tag => "a",
218 :content => /#{Issue.find(1).subject}/,
263 :content => /#{Issue.find(1).subject}/,
219 }
264 }
220 }
265 }
221 }
266 }
222 end
267 end
223
268
224 def test_activity_atom_feed
269 def test_activity_atom_feed
225 get :activity, :format => 'atom'
270 get :activity, :format => 'atom'
226 assert_response :success
271 assert_response :success
227 assert_template 'common/feed.atom.rxml'
272 assert_template 'common/feed.atom.rxml'
228 end
273 end
229
274
230 def test_archive
275 def test_archive
231 @request.session[:user_id] = 1 # admin
276 @request.session[:user_id] = 1 # admin
232 post :archive, :id => 1
277 post :archive, :id => 1
233 assert_redirected_to 'admin/projects'
278 assert_redirected_to 'admin/projects'
234 assert !Project.find(1).active?
279 assert !Project.find(1).active?
235 end
280 end
236
281
237 def test_unarchive
282 def test_unarchive
238 @request.session[:user_id] = 1 # admin
283 @request.session[:user_id] = 1 # admin
239 Project.find(1).archive
284 Project.find(1).archive
240 post :unarchive, :id => 1
285 post :unarchive, :id => 1
241 assert_redirected_to 'admin/projects'
286 assert_redirected_to 'admin/projects'
242 assert Project.find(1).active?
287 assert Project.find(1).active?
243 end
288 end
244
289
245 def test_project_menu
290 def test_project_menu
246 assert_no_difference 'Redmine::MenuManager.items(:project_menu).size' do
291 assert_no_difference 'Redmine::MenuManager.items(:project_menu).size' do
247 Redmine::MenuManager.map :project_menu do |menu|
292 Redmine::MenuManager.map :project_menu do |menu|
248 menu.push :foo, { :controller => 'projects', :action => 'show' }, :cation => 'Foo'
293 menu.push :foo, { :controller => 'projects', :action => 'show' }, :cation => 'Foo'
249 menu.push :bar, { :controller => 'projects', :action => 'show' }, :before => :activity
294 menu.push :bar, { :controller => 'projects', :action => 'show' }, :before => :activity
250 menu.push :hello, { :controller => 'projects', :action => 'show' }, :caption => Proc.new {|p| p.name.upcase }, :after => :bar
295 menu.push :hello, { :controller => 'projects', :action => 'show' }, :caption => Proc.new {|p| p.name.upcase }, :after => :bar
251 end
296 end
252
297
253 get :show, :id => 1
298 get :show, :id => 1
254 assert_tag :div, :attributes => { :id => 'main-menu' },
299 assert_tag :div, :attributes => { :id => 'main-menu' },
255 :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'Foo',
300 :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'Foo',
256 :attributes => { :class => 'foo' } } }
301 :attributes => { :class => 'foo' } } }
257
302
258 assert_tag :div, :attributes => { :id => 'main-menu' },
303 assert_tag :div, :attributes => { :id => 'main-menu' },
259 :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'Bar',
304 :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'Bar',
260 :attributes => { :class => 'bar' } },
305 :attributes => { :class => 'bar' } },
261 :before => { :tag => 'li', :child => { :tag => 'a', :content => 'ECOOKBOOK' } } }
306 :before => { :tag => 'li', :child => { :tag => 'a', :content => 'ECOOKBOOK' } } }
262
307
263 assert_tag :div, :attributes => { :id => 'main-menu' },
308 assert_tag :div, :attributes => { :id => 'main-menu' },
264 :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'ECOOKBOOK',
309 :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'ECOOKBOOK',
265 :attributes => { :class => 'hello' } },
310 :attributes => { :class => 'hello' } },
266 :before => { :tag => 'li', :child => { :tag => 'a', :content => 'Activity' } } }
311 :before => { :tag => 'li', :child => { :tag => 'a', :content => 'Activity' } } }
267
312
268 # Remove the menu items
313 # Remove the menu items
269 Redmine::MenuManager.map :project_menu do |menu|
314 Redmine::MenuManager.map :project_menu do |menu|
270 menu.delete :foo
315 menu.delete :foo
271 menu.delete :bar
316 menu.delete :bar
272 menu.delete :hello
317 menu.delete :hello
273 end
318 end
274 end
319 end
275 end
320 end
276
321
277 # A hook that is manually registered later
322 # A hook that is manually registered later
278 class ProjectBasedTemplate < Redmine::Hook::ViewListener
323 class ProjectBasedTemplate < Redmine::Hook::ViewListener
279 def view_layouts_base_html_head(context)
324 def view_layouts_base_html_head(context)
280 # Adds a project stylesheet
325 # Adds a project stylesheet
281 stylesheet_link_tag(context[:project].identifier) if context[:project]
326 stylesheet_link_tag(context[:project].identifier) if context[:project]
282 end
327 end
283 end
328 end
284 # Don't use this hook now
329 # Don't use this hook now
285 Redmine::Hook.clear_listeners
330 Redmine::Hook.clear_listeners
286
331
287 def test_hook_response
332 def test_hook_response
288 Redmine::Hook.add_listener(ProjectBasedTemplate)
333 Redmine::Hook.add_listener(ProjectBasedTemplate)
289 get :show, :id => 1
334 get :show, :id => 1
290 assert_tag :tag => 'link', :attributes => {:href => '/stylesheets/ecookbook.css'},
335 assert_tag :tag => 'link', :attributes => {:href => '/stylesheets/ecookbook.css'},
291 :parent => {:tag => 'head'}
336 :parent => {:tag => 'head'}
292
337
293 Redmine::Hook.clear_listeners
338 Redmine::Hook.clear_listeners
294 end
339 end
295 end
340 end
General Comments 0
You need to be logged in to leave comments. Login now