##// END OF EJS Templates
Adds a link to automatically close completed versions in project settings (#1245)....
Jean-Philippe Lang -
r2909:8f40750ad7f7
parent child
Show More
@@ -1,54 +1,69
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class VersionsController < ApplicationController
18 class VersionsController < ApplicationController
19 menu_item :roadmap
19 menu_item :roadmap
20 before_filter :find_project, :authorize
20 before_filter :find_version, :except => :close_completed
21 before_filter :find_project, :only => :close_completed
22 before_filter :authorize
21
23
22 def show
24 def show
23 end
25 end
24
26
25 def edit
27 def edit
26 if request.post? and @version.update_attributes(params[:version])
28 if request.post? and @version.update_attributes(params[:version])
27 flash[:notice] = l(:notice_successful_update)
29 flash[:notice] = l(:notice_successful_update)
28 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
30 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
29 end
31 end
30 end
32 end
33
34 def close_completed
35 if request.post?
36 @project.close_completed_versions
37 end
38 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
39 end
31
40
32 def destroy
41 def destroy
33 @version.destroy
42 @version.destroy
34 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
43 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
35 rescue
44 rescue
36 flash[:error] = l(:notice_unable_delete_version)
45 flash[:error] = l(:notice_unable_delete_version)
37 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
46 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
38 end
47 end
39
48
40 def status_by
49 def status_by
41 respond_to do |format|
50 respond_to do |format|
42 format.html { render :action => 'show' }
51 format.html { render :action => 'show' }
43 format.js { render(:update) {|page| page.replace_html 'status_by', render_issue_status_by(@version, params[:status_by])} }
52 format.js { render(:update) {|page| page.replace_html 'status_by', render_issue_status_by(@version, params[:status_by])} }
44 end
53 end
45 end
54 end
46
55
47 private
56 private
48 def find_project
57 def find_version
49 @version = Version.find(params[:id])
58 @version = Version.find(params[:id])
50 @project = @version.project
59 @project = @version.project
51 rescue ActiveRecord::RecordNotFound
60 rescue ActiveRecord::RecordNotFound
52 render_404
61 render_404
53 end
62 end
63
64 def find_project
65 @project = Project.find(params[:project_id])
66 rescue ActiveRecord::RecordNotFound
67 render_404
68 end
54 end
69 end
@@ -1,583 +1,594
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 # Specific overidden Activities
23 # Specific overidden Activities
24 has_many :time_entry_activities do
24 has_many :time_entry_activities do
25 def active
25 def active
26 find(:all, :conditions => {:active => true})
26 find(:all, :conditions => {:active => true})
27 end
27 end
28 end
28 end
29 has_many :members, :include => :user, :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}"
29 has_many :members, :include => :user, :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}"
30 has_many :member_principals, :class_name => 'Member',
30 has_many :member_principals, :class_name => 'Member',
31 :include => :principal,
31 :include => :principal,
32 :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{User::STATUS_ACTIVE})"
32 :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{User::STATUS_ACTIVE})"
33 has_many :users, :through => :members
33 has_many :users, :through => :members
34 has_many :principals, :through => :member_principals, :source => :principal
34 has_many :principals, :through => :member_principals, :source => :principal
35
35
36 has_many :enabled_modules, :dependent => :delete_all
36 has_many :enabled_modules, :dependent => :delete_all
37 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
37 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
38 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
38 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
39 has_many :issue_changes, :through => :issues, :source => :journals
39 has_many :issue_changes, :through => :issues, :source => :journals
40 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
40 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
41 has_many :time_entries, :dependent => :delete_all
41 has_many :time_entries, :dependent => :delete_all
42 has_many :queries, :dependent => :delete_all
42 has_many :queries, :dependent => :delete_all
43 has_many :documents, :dependent => :destroy
43 has_many :documents, :dependent => :destroy
44 has_many :news, :dependent => :delete_all, :include => :author
44 has_many :news, :dependent => :delete_all, :include => :author
45 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
45 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
46 has_many :boards, :dependent => :destroy, :order => "position ASC"
46 has_many :boards, :dependent => :destroy, :order => "position ASC"
47 has_one :repository, :dependent => :destroy
47 has_one :repository, :dependent => :destroy
48 has_many :changesets, :through => :repository
48 has_many :changesets, :through => :repository
49 has_one :wiki, :dependent => :destroy
49 has_one :wiki, :dependent => :destroy
50 # Custom field for the project issues
50 # Custom field for the project issues
51 has_and_belongs_to_many :issue_custom_fields,
51 has_and_belongs_to_many :issue_custom_fields,
52 :class_name => 'IssueCustomField',
52 :class_name => 'IssueCustomField',
53 :order => "#{CustomField.table_name}.position",
53 :order => "#{CustomField.table_name}.position",
54 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
54 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
55 :association_foreign_key => 'custom_field_id'
55 :association_foreign_key => 'custom_field_id'
56
56
57 acts_as_nested_set :order => 'name', :dependent => :destroy
57 acts_as_nested_set :order => 'name', :dependent => :destroy
58 acts_as_attachable :view_permission => :view_files,
58 acts_as_attachable :view_permission => :view_files,
59 :delete_permission => :manage_files
59 :delete_permission => :manage_files
60
60
61 acts_as_customizable
61 acts_as_customizable
62 acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil
62 acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil
63 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
63 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
64 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}},
64 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}},
65 :author => nil
65 :author => nil
66
66
67 attr_protected :status, :enabled_module_names
67 attr_protected :status, :enabled_module_names
68
68
69 validates_presence_of :name, :identifier
69 validates_presence_of :name, :identifier
70 validates_uniqueness_of :name, :identifier
70 validates_uniqueness_of :name, :identifier
71 validates_associated :repository, :wiki
71 validates_associated :repository, :wiki
72 validates_length_of :name, :maximum => 30
72 validates_length_of :name, :maximum => 30
73 validates_length_of :homepage, :maximum => 255
73 validates_length_of :homepage, :maximum => 255
74 validates_length_of :identifier, :in => 1..20
74 validates_length_of :identifier, :in => 1..20
75 # donwcase letters, digits, dashes but not digits only
75 # donwcase letters, digits, dashes but not digits only
76 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :if => Proc.new { |p| p.identifier_changed? }
76 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :if => Proc.new { |p| p.identifier_changed? }
77 # reserved words
77 # reserved words
78 validates_exclusion_of :identifier, :in => %w( new )
78 validates_exclusion_of :identifier, :in => %w( new )
79
79
80 before_destroy :delete_all_members
80 before_destroy :delete_all_members
81
81
82 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] } }
82 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] } }
83 named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
83 named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
84 named_scope :all_public, { :conditions => { :is_public => true } }
84 named_scope :all_public, { :conditions => { :is_public => true } }
85 named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
85 named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
86
86
87 def identifier=(identifier)
87 def identifier=(identifier)
88 super unless identifier_frozen?
88 super unless identifier_frozen?
89 end
89 end
90
90
91 def identifier_frozen?
91 def identifier_frozen?
92 errors[:identifier].nil? && !(new_record? || identifier.blank?)
92 errors[:identifier].nil? && !(new_record? || identifier.blank?)
93 end
93 end
94
94
95 def issues_with_subprojects(include_subprojects=false)
95 def issues_with_subprojects(include_subprojects=false)
96 conditions = nil
96 conditions = nil
97 if include_subprojects
97 if include_subprojects
98 ids = [id] + descendants.collect(&:id)
98 ids = [id] + descendants.collect(&:id)
99 conditions = ["#{Project.table_name}.id IN (#{ids.join(',')}) AND #{Project.visible_by}"]
99 conditions = ["#{Project.table_name}.id IN (#{ids.join(',')}) AND #{Project.visible_by}"]
100 end
100 end
101 conditions ||= ["#{Project.table_name}.id = ?", id]
101 conditions ||= ["#{Project.table_name}.id = ?", id]
102 # Quick and dirty fix for Rails 2 compatibility
102 # Quick and dirty fix for Rails 2 compatibility
103 Issue.send(:with_scope, :find => { :conditions => conditions }) do
103 Issue.send(:with_scope, :find => { :conditions => conditions }) do
104 Version.send(:with_scope, :find => { :conditions => conditions }) do
104 Version.send(:with_scope, :find => { :conditions => conditions }) do
105 yield
105 yield
106 end
106 end
107 end
107 end
108 end
108 end
109
109
110 # returns latest created projects
110 # returns latest created projects
111 # non public projects will be returned only if user is a member of those
111 # non public projects will be returned only if user is a member of those
112 def self.latest(user=nil, count=5)
112 def self.latest(user=nil, count=5)
113 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
113 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
114 end
114 end
115
115
116 # Returns a SQL :conditions string used to find all active projects for the specified user.
116 # Returns a SQL :conditions string used to find all active projects for the specified user.
117 #
117 #
118 # Examples:
118 # Examples:
119 # Projects.visible_by(admin) => "projects.status = 1"
119 # Projects.visible_by(admin) => "projects.status = 1"
120 # Projects.visible_by(normal_user) => "projects.status = 1 AND projects.is_public = 1"
120 # Projects.visible_by(normal_user) => "projects.status = 1 AND projects.is_public = 1"
121 def self.visible_by(user=nil)
121 def self.visible_by(user=nil)
122 user ||= User.current
122 user ||= User.current
123 if user && user.admin?
123 if user && user.admin?
124 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
124 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
125 elsif user && user.memberships.any?
125 elsif user && user.memberships.any?
126 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(',')}))"
126 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(',')}))"
127 else
127 else
128 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
128 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
129 end
129 end
130 end
130 end
131
131
132 def self.allowed_to_condition(user, permission, options={})
132 def self.allowed_to_condition(user, permission, options={})
133 statements = []
133 statements = []
134 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
134 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
135 if perm = Redmine::AccessControl.permission(permission)
135 if perm = Redmine::AccessControl.permission(permission)
136 unless perm.project_module.nil?
136 unless perm.project_module.nil?
137 # If the permission belongs to a project module, make sure the module is enabled
137 # If the permission belongs to a project module, make sure the module is enabled
138 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
138 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
139 end
139 end
140 end
140 end
141 if options[:project]
141 if options[:project]
142 project_statement = "#{Project.table_name}.id = #{options[:project].id}"
142 project_statement = "#{Project.table_name}.id = #{options[:project].id}"
143 project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects]
143 project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects]
144 base_statement = "(#{project_statement}) AND (#{base_statement})"
144 base_statement = "(#{project_statement}) AND (#{base_statement})"
145 end
145 end
146 if user.admin?
146 if user.admin?
147 # no restriction
147 # no restriction
148 else
148 else
149 statements << "1=0"
149 statements << "1=0"
150 if user.logged?
150 if user.logged?
151 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" if Role.non_member.allowed_to?(permission)
151 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" if Role.non_member.allowed_to?(permission)
152 allowed_project_ids = user.memberships.select {|m| m.roles.detect {|role| role.allowed_to?(permission)}}.collect {|m| m.project_id}
152 allowed_project_ids = user.memberships.select {|m| m.roles.detect {|role| role.allowed_to?(permission)}}.collect {|m| m.project_id}
153 statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
153 statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
154 elsif Role.anonymous.allowed_to?(permission)
154 elsif Role.anonymous.allowed_to?(permission)
155 # anonymous user allowed on public project
155 # anonymous user allowed on public project
156 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
156 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
157 else
157 else
158 # anonymous user is not authorized
158 # anonymous user is not authorized
159 end
159 end
160 end
160 end
161 statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
161 statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
162 end
162 end
163
163
164 # Returns the Systemwide and project specific activities
164 # Returns the Systemwide and project specific activities
165 def activities(include_inactive=false)
165 def activities(include_inactive=false)
166 if include_inactive
166 if include_inactive
167 return all_activities
167 return all_activities
168 else
168 else
169 return active_activities
169 return active_activities
170 end
170 end
171 end
171 end
172
172
173 # Will create a new Project specific Activity or update an existing one
173 # Will create a new Project specific Activity or update an existing one
174 #
174 #
175 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
175 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
176 # does not successfully save.
176 # does not successfully save.
177 def update_or_create_time_entry_activity(id, activity_hash)
177 def update_or_create_time_entry_activity(id, activity_hash)
178 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
178 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
179 self.create_time_entry_activity_if_needed(activity_hash)
179 self.create_time_entry_activity_if_needed(activity_hash)
180 else
180 else
181 activity = project.time_entry_activities.find_by_id(id.to_i)
181 activity = project.time_entry_activities.find_by_id(id.to_i)
182 activity.update_attributes(activity_hash) if activity
182 activity.update_attributes(activity_hash) if activity
183 end
183 end
184 end
184 end
185
185
186 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
186 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
187 #
187 #
188 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
188 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
189 # does not successfully save.
189 # does not successfully save.
190 def create_time_entry_activity_if_needed(activity)
190 def create_time_entry_activity_if_needed(activity)
191 if activity['parent_id']
191 if activity['parent_id']
192
192
193 parent_activity = TimeEntryActivity.find(activity['parent_id'])
193 parent_activity = TimeEntryActivity.find(activity['parent_id'])
194 activity['name'] = parent_activity.name
194 activity['name'] = parent_activity.name
195 activity['position'] = parent_activity.position
195 activity['position'] = parent_activity.position
196
196
197 if Enumeration.overridding_change?(activity, parent_activity)
197 if Enumeration.overridding_change?(activity, parent_activity)
198 project_activity = self.time_entry_activities.create(activity)
198 project_activity = self.time_entry_activities.create(activity)
199
199
200 if project_activity.new_record?
200 if project_activity.new_record?
201 raise ActiveRecord::Rollback, "Overridding TimeEntryActivity was not successfully saved"
201 raise ActiveRecord::Rollback, "Overridding TimeEntryActivity was not successfully saved"
202 else
202 else
203 self.time_entries.update_all("activity_id = #{project_activity.id}", ["activity_id = ?", parent_activity.id])
203 self.time_entries.update_all("activity_id = #{project_activity.id}", ["activity_id = ?", parent_activity.id])
204 end
204 end
205 end
205 end
206 end
206 end
207 end
207 end
208
208
209 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
209 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
210 #
210 #
211 # Examples:
211 # Examples:
212 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
212 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
213 # project.project_condition(false) => "projects.id = 1"
213 # project.project_condition(false) => "projects.id = 1"
214 def project_condition(with_subprojects)
214 def project_condition(with_subprojects)
215 cond = "#{Project.table_name}.id = #{id}"
215 cond = "#{Project.table_name}.id = #{id}"
216 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
216 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
217 cond
217 cond
218 end
218 end
219
219
220 def self.find(*args)
220 def self.find(*args)
221 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
221 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
222 project = find_by_identifier(*args)
222 project = find_by_identifier(*args)
223 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
223 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
224 project
224 project
225 else
225 else
226 super
226 super
227 end
227 end
228 end
228 end
229
229
230 def to_param
230 def to_param
231 # id is used for projects with a numeric identifier (compatibility)
231 # id is used for projects with a numeric identifier (compatibility)
232 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
232 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
233 end
233 end
234
234
235 def active?
235 def active?
236 self.status == STATUS_ACTIVE
236 self.status == STATUS_ACTIVE
237 end
237 end
238
238
239 # Archives the project and its descendants recursively
239 # Archives the project and its descendants recursively
240 def archive
240 def archive
241 # Archive subprojects if any
241 # Archive subprojects if any
242 children.each do |subproject|
242 children.each do |subproject|
243 subproject.archive
243 subproject.archive
244 end
244 end
245 update_attribute :status, STATUS_ARCHIVED
245 update_attribute :status, STATUS_ARCHIVED
246 end
246 end
247
247
248 # Unarchives the project
248 # Unarchives the project
249 # All its ancestors must be active
249 # All its ancestors must be active
250 def unarchive
250 def unarchive
251 return false if ancestors.detect {|a| !a.active?}
251 return false if ancestors.detect {|a| !a.active?}
252 update_attribute :status, STATUS_ACTIVE
252 update_attribute :status, STATUS_ACTIVE
253 end
253 end
254
254
255 # Returns an array of projects the project can be moved to
255 # Returns an array of projects the project can be moved to
256 def possible_parents
256 def possible_parents
257 @possible_parents ||= (Project.active.find(:all) - self_and_descendants)
257 @possible_parents ||= (Project.active.find(:all) - self_and_descendants)
258 end
258 end
259
259
260 # Sets the parent of the project
260 # Sets the parent of the project
261 # Argument can be either a Project, a String, a Fixnum or nil
261 # Argument can be either a Project, a String, a Fixnum or nil
262 def set_parent!(p)
262 def set_parent!(p)
263 unless p.nil? || p.is_a?(Project)
263 unless p.nil? || p.is_a?(Project)
264 if p.to_s.blank?
264 if p.to_s.blank?
265 p = nil
265 p = nil
266 else
266 else
267 p = Project.find_by_id(p)
267 p = Project.find_by_id(p)
268 return false unless p
268 return false unless p
269 end
269 end
270 end
270 end
271 if p == parent && !p.nil?
271 if p == parent && !p.nil?
272 # Nothing to do
272 # Nothing to do
273 true
273 true
274 elsif p.nil? || (p.active? && move_possible?(p))
274 elsif p.nil? || (p.active? && move_possible?(p))
275 # Insert the project so that target's children or root projects stay alphabetically sorted
275 # Insert the project so that target's children or root projects stay alphabetically sorted
276 sibs = (p.nil? ? self.class.roots : p.children)
276 sibs = (p.nil? ? self.class.roots : p.children)
277 to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
277 to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
278 if to_be_inserted_before
278 if to_be_inserted_before
279 move_to_left_of(to_be_inserted_before)
279 move_to_left_of(to_be_inserted_before)
280 elsif p.nil?
280 elsif p.nil?
281 if sibs.empty?
281 if sibs.empty?
282 # move_to_root adds the project in first (ie. left) position
282 # move_to_root adds the project in first (ie. left) position
283 move_to_root
283 move_to_root
284 else
284 else
285 move_to_right_of(sibs.last) unless self == sibs.last
285 move_to_right_of(sibs.last) unless self == sibs.last
286 end
286 end
287 else
287 else
288 # move_to_child_of adds the project in last (ie.right) position
288 # move_to_child_of adds the project in last (ie.right) position
289 move_to_child_of(p)
289 move_to_child_of(p)
290 end
290 end
291 true
291 true
292 else
292 else
293 # Can not move to the given target
293 # Can not move to the given target
294 false
294 false
295 end
295 end
296 end
296 end
297
297
298 # Returns an array of the trackers used by the project and its active sub projects
298 # Returns an array of the trackers used by the project and its active sub projects
299 def rolled_up_trackers
299 def rolled_up_trackers
300 @rolled_up_trackers ||=
300 @rolled_up_trackers ||=
301 Tracker.find(:all, :include => :projects,
301 Tracker.find(:all, :include => :projects,
302 :select => "DISTINCT #{Tracker.table_name}.*",
302 :select => "DISTINCT #{Tracker.table_name}.*",
303 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
303 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
304 :order => "#{Tracker.table_name}.position")
304 :order => "#{Tracker.table_name}.position")
305 end
305 end
306
306
307 # Closes open and locked project versions that are completed
308 def close_completed_versions
309 Version.transaction do
310 versions.find(:all, :conditions => {:status => %w(open locked)}).each do |version|
311 if version.completed?
312 version.update_attribute(:status, 'closed')
313 end
314 end
315 end
316 end
317
307 # Returns a hash of project users grouped by role
318 # Returns a hash of project users grouped by role
308 def users_by_role
319 def users_by_role
309 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m|
320 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m|
310 m.roles.each do |r|
321 m.roles.each do |r|
311 h[r] ||= []
322 h[r] ||= []
312 h[r] << m.user
323 h[r] << m.user
313 end
324 end
314 h
325 h
315 end
326 end
316 end
327 end
317
328
318 # Deletes all project's members
329 # Deletes all project's members
319 def delete_all_members
330 def delete_all_members
320 me, mr = Member.table_name, MemberRole.table_name
331 me, mr = Member.table_name, MemberRole.table_name
321 connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
332 connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
322 Member.delete_all(['project_id = ?', id])
333 Member.delete_all(['project_id = ?', id])
323 end
334 end
324
335
325 # Users issues can be assigned to
336 # Users issues can be assigned to
326 def assignable_users
337 def assignable_users
327 members.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.user}.sort
338 members.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.user}.sort
328 end
339 end
329
340
330 # Returns the mail adresses of users that should be always notified on project events
341 # Returns the mail adresses of users that should be always notified on project events
331 def recipients
342 def recipients
332 members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user.mail}
343 members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user.mail}
333 end
344 end
334
345
335 # Returns an array of all custom fields enabled for project issues
346 # Returns an array of all custom fields enabled for project issues
336 # (explictly associated custom fields and custom fields enabled for all projects)
347 # (explictly associated custom fields and custom fields enabled for all projects)
337 def all_issue_custom_fields
348 def all_issue_custom_fields
338 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
349 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
339 end
350 end
340
351
341 def project
352 def project
342 self
353 self
343 end
354 end
344
355
345 def <=>(project)
356 def <=>(project)
346 name.downcase <=> project.name.downcase
357 name.downcase <=> project.name.downcase
347 end
358 end
348
359
349 def to_s
360 def to_s
350 name
361 name
351 end
362 end
352
363
353 # Returns a short description of the projects (first lines)
364 # Returns a short description of the projects (first lines)
354 def short_description(length = 255)
365 def short_description(length = 255)
355 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
366 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
356 end
367 end
357
368
358 # Return true if this project is allowed to do the specified action.
369 # Return true if this project is allowed to do the specified action.
359 # action can be:
370 # action can be:
360 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
371 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
361 # * a permission Symbol (eg. :edit_project)
372 # * a permission Symbol (eg. :edit_project)
362 def allows_to?(action)
373 def allows_to?(action)
363 if action.is_a? Hash
374 if action.is_a? Hash
364 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
375 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
365 else
376 else
366 allowed_permissions.include? action
377 allowed_permissions.include? action
367 end
378 end
368 end
379 end
369
380
370 def module_enabled?(module_name)
381 def module_enabled?(module_name)
371 module_name = module_name.to_s
382 module_name = module_name.to_s
372 enabled_modules.detect {|m| m.name == module_name}
383 enabled_modules.detect {|m| m.name == module_name}
373 end
384 end
374
385
375 def enabled_module_names=(module_names)
386 def enabled_module_names=(module_names)
376 if module_names && module_names.is_a?(Array)
387 if module_names && module_names.is_a?(Array)
377 module_names = module_names.collect(&:to_s)
388 module_names = module_names.collect(&:to_s)
378 # remove disabled modules
389 # remove disabled modules
379 enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
390 enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
380 # add new modules
391 # add new modules
381 module_names.each {|name| enabled_modules << EnabledModule.new(:name => name)}
392 module_names.each {|name| enabled_modules << EnabledModule.new(:name => name)}
382 else
393 else
383 enabled_modules.clear
394 enabled_modules.clear
384 end
395 end
385 end
396 end
386
397
387 # Returns an auto-generated project identifier based on the last identifier used
398 # Returns an auto-generated project identifier based on the last identifier used
388 def self.next_identifier
399 def self.next_identifier
389 p = Project.find(:first, :order => 'created_on DESC')
400 p = Project.find(:first, :order => 'created_on DESC')
390 p.nil? ? nil : p.identifier.to_s.succ
401 p.nil? ? nil : p.identifier.to_s.succ
391 end
402 end
392
403
393 # Copies and saves the Project instance based on the +project+.
404 # Copies and saves the Project instance based on the +project+.
394 # Duplicates the source project's:
405 # Duplicates the source project's:
395 # * Wiki
406 # * Wiki
396 # * Versions
407 # * Versions
397 # * Categories
408 # * Categories
398 # * Issues
409 # * Issues
399 # * Members
410 # * Members
400 # * Queries
411 # * Queries
401 #
412 #
402 # Accepts an +options+ argument to specify what to copy
413 # Accepts an +options+ argument to specify what to copy
403 #
414 #
404 # Examples:
415 # Examples:
405 # project.copy(1) # => copies everything
416 # project.copy(1) # => copies everything
406 # project.copy(1, :only => 'members') # => copies members only
417 # project.copy(1, :only => 'members') # => copies members only
407 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
418 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
408 def copy(project, options={})
419 def copy(project, options={})
409 project = project.is_a?(Project) ? project : Project.find(project)
420 project = project.is_a?(Project) ? project : Project.find(project)
410
421
411 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
422 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
412 to_be_copied = to_be_copied & options[:only].to_a unless options[:only].nil?
423 to_be_copied = to_be_copied & options[:only].to_a unless options[:only].nil?
413
424
414 Project.transaction do
425 Project.transaction do
415 if save
426 if save
416 reload
427 reload
417 to_be_copied.each do |name|
428 to_be_copied.each do |name|
418 send "copy_#{name}", project
429 send "copy_#{name}", project
419 end
430 end
420 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
431 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
421 save
432 save
422 end
433 end
423 end
434 end
424 end
435 end
425
436
426
437
427 # Copies +project+ and returns the new instance. This will not save
438 # Copies +project+ and returns the new instance. This will not save
428 # the copy
439 # the copy
429 def self.copy_from(project)
440 def self.copy_from(project)
430 begin
441 begin
431 project = project.is_a?(Project) ? project : Project.find(project)
442 project = project.is_a?(Project) ? project : Project.find(project)
432 if project
443 if project
433 # clear unique attributes
444 # clear unique attributes
434 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
445 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
435 copy = Project.new(attributes)
446 copy = Project.new(attributes)
436 copy.enabled_modules = project.enabled_modules
447 copy.enabled_modules = project.enabled_modules
437 copy.trackers = project.trackers
448 copy.trackers = project.trackers
438 copy.custom_values = project.custom_values.collect {|v| v.clone}
449 copy.custom_values = project.custom_values.collect {|v| v.clone}
439 copy.issue_custom_fields = project.issue_custom_fields
450 copy.issue_custom_fields = project.issue_custom_fields
440 return copy
451 return copy
441 else
452 else
442 return nil
453 return nil
443 end
454 end
444 rescue ActiveRecord::RecordNotFound
455 rescue ActiveRecord::RecordNotFound
445 return nil
456 return nil
446 end
457 end
447 end
458 end
448
459
449 private
460 private
450
461
451 # Copies wiki from +project+
462 # Copies wiki from +project+
452 def copy_wiki(project)
463 def copy_wiki(project)
453 # Check that the source project has a wiki first
464 # Check that the source project has a wiki first
454 unless project.wiki.nil?
465 unless project.wiki.nil?
455 self.wiki ||= Wiki.new
466 self.wiki ||= Wiki.new
456 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
467 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
457 project.wiki.pages.each do |page|
468 project.wiki.pages.each do |page|
458 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
469 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
459 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
470 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
460 new_wiki_page.content = new_wiki_content
471 new_wiki_page.content = new_wiki_content
461 wiki.pages << new_wiki_page
472 wiki.pages << new_wiki_page
462 end
473 end
463 end
474 end
464 end
475 end
465
476
466 # Copies versions from +project+
477 # Copies versions from +project+
467 def copy_versions(project)
478 def copy_versions(project)
468 project.versions.each do |version|
479 project.versions.each do |version|
469 new_version = Version.new
480 new_version = Version.new
470 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
481 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
471 self.versions << new_version
482 self.versions << new_version
472 end
483 end
473 end
484 end
474
485
475 # Copies issue categories from +project+
486 # Copies issue categories from +project+
476 def copy_issue_categories(project)
487 def copy_issue_categories(project)
477 project.issue_categories.each do |issue_category|
488 project.issue_categories.each do |issue_category|
478 new_issue_category = IssueCategory.new
489 new_issue_category = IssueCategory.new
479 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
490 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
480 self.issue_categories << new_issue_category
491 self.issue_categories << new_issue_category
481 end
492 end
482 end
493 end
483
494
484 # Copies issues from +project+
495 # Copies issues from +project+
485 def copy_issues(project)
496 def copy_issues(project)
486 project.issues.each do |issue|
497 project.issues.each do |issue|
487 new_issue = Issue.new
498 new_issue = Issue.new
488 new_issue.copy_from(issue)
499 new_issue.copy_from(issue)
489 # Reassign fixed_versions by name, since names are unique per
500 # Reassign fixed_versions by name, since names are unique per
490 # project and the versions for self are not yet saved
501 # project and the versions for self are not yet saved
491 if issue.fixed_version
502 if issue.fixed_version
492 new_issue.fixed_version = self.versions.select {|v| v.name == issue.fixed_version.name}.first
503 new_issue.fixed_version = self.versions.select {|v| v.name == issue.fixed_version.name}.first
493 end
504 end
494 # Reassign the category by name, since names are unique per
505 # Reassign the category by name, since names are unique per
495 # project and the categories for self are not yet saved
506 # project and the categories for self are not yet saved
496 if issue.category
507 if issue.category
497 new_issue.category = self.issue_categories.select {|c| c.name == issue.category.name}.first
508 new_issue.category = self.issue_categories.select {|c| c.name == issue.category.name}.first
498 end
509 end
499 self.issues << new_issue
510 self.issues << new_issue
500 end
511 end
501 end
512 end
502
513
503 # Copies members from +project+
514 # Copies members from +project+
504 def copy_members(project)
515 def copy_members(project)
505 project.members.each do |member|
516 project.members.each do |member|
506 new_member = Member.new
517 new_member = Member.new
507 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
518 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
508 new_member.role_ids = member.role_ids.dup
519 new_member.role_ids = member.role_ids.dup
509 new_member.project = self
520 new_member.project = self
510 self.members << new_member
521 self.members << new_member
511 end
522 end
512 end
523 end
513
524
514 # Copies queries from +project+
525 # Copies queries from +project+
515 def copy_queries(project)
526 def copy_queries(project)
516 project.queries.each do |query|
527 project.queries.each do |query|
517 new_query = Query.new
528 new_query = Query.new
518 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria")
529 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria")
519 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
530 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
520 new_query.project = self
531 new_query.project = self
521 self.queries << new_query
532 self.queries << new_query
522 end
533 end
523 end
534 end
524
535
525 # Copies boards from +project+
536 # Copies boards from +project+
526 def copy_boards(project)
537 def copy_boards(project)
527 project.boards.each do |board|
538 project.boards.each do |board|
528 new_board = Board.new
539 new_board = Board.new
529 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
540 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
530 new_board.project = self
541 new_board.project = self
531 self.boards << new_board
542 self.boards << new_board
532 end
543 end
533 end
544 end
534
545
535 def allowed_permissions
546 def allowed_permissions
536 @allowed_permissions ||= begin
547 @allowed_permissions ||= begin
537 module_names = enabled_modules.collect {|m| m.name}
548 module_names = enabled_modules.collect {|m| m.name}
538 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
549 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
539 end
550 end
540 end
551 end
541
552
542 def allowed_actions
553 def allowed_actions
543 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
554 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
544 end
555 end
545
556
546 # Returns all the active Systemwide and project specific activities
557 # Returns all the active Systemwide and project specific activities
547 def active_activities
558 def active_activities
548 overridden_activity_ids = self.time_entry_activities.active.collect(&:parent_id)
559 overridden_activity_ids = self.time_entry_activities.active.collect(&:parent_id)
549
560
550 if overridden_activity_ids.empty?
561 if overridden_activity_ids.empty?
551 return TimeEntryActivity.active
562 return TimeEntryActivity.active
552 else
563 else
553 return system_activities_and_project_overrides
564 return system_activities_and_project_overrides
554 end
565 end
555 end
566 end
556
567
557 # Returns all the Systemwide and project specific activities
568 # Returns all the Systemwide and project specific activities
558 # (inactive and active)
569 # (inactive and active)
559 def all_activities
570 def all_activities
560 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
571 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
561
572
562 if overridden_activity_ids.empty?
573 if overridden_activity_ids.empty?
563 return TimeEntryActivity.all
574 return TimeEntryActivity.all
564 else
575 else
565 return system_activities_and_project_overrides(true)
576 return system_activities_and_project_overrides(true)
566 end
577 end
567 end
578 end
568
579
569 # Returns the systemwide active activities merged with the project specific overrides
580 # Returns the systemwide active activities merged with the project specific overrides
570 def system_activities_and_project_overrides(include_inactive=false)
581 def system_activities_and_project_overrides(include_inactive=false)
571 if include_inactive
582 if include_inactive
572 return TimeEntryActivity.all.
583 return TimeEntryActivity.all.
573 find(:all,
584 find(:all,
574 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
585 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
575 self.time_entry_activities
586 self.time_entry_activities
576 else
587 else
577 return TimeEntryActivity.active.
588 return TimeEntryActivity.active.
578 find(:all,
589 find(:all,
579 :conditions => ["id NOT IN (?)", self.time_entry_activities.active.collect(&:parent_id)]) +
590 :conditions => ["id NOT IN (?)", self.time_entry_activities.active.collect(&:parent_id)]) +
580 self.time_entry_activities.active
591 self.time_entry_activities.active
581 end
592 end
582 end
593 end
583 end
594 end
@@ -1,31 +1,37
1 <% if @project.versions.any? %>
1 <% if @project.versions.any? %>
2 <table class="list versions">
2 <table class="list versions">
3 <thead>
3 <thead>
4 <th><%= l(:label_version) %></th>
4 <th><%= l(:label_version) %></th>
5 <th><%= l(:field_effective_date) %></th>
5 <th><%= l(:field_effective_date) %></th>
6 <th><%= l(:field_description) %></th>
6 <th><%= l(:field_description) %></th>
7 <th><%= l(:field_status) %></th>
7 <th><%= l(:field_status) %></th>
8 <th><%= l(:label_wiki_page) unless @project.wiki.nil? %></th>
8 <th><%= l(:label_wiki_page) unless @project.wiki.nil? %></th>
9 <th style="width:15%"></th>
9 <th style="width:15%"></th>
10 </thead>
10 </thead>
11 <tbody>
11 <tbody>
12 <% for version in @project.versions.sort %>
12 <% for version in @project.versions.sort %>
13 <tr class="version <%= cycle 'odd', 'even' %> <%=h version.status %>">
13 <tr class="version <%= cycle 'odd', 'even' %> <%=h version.status %>">
14 <td><%= link_to h(version.name), :controller => 'versions', :action => 'show', :id => version %></td>
14 <td><%= link_to h(version.name), :controller => 'versions', :action => 'show', :id => version %></td>
15 <td align="center"><%= format_date(version.effective_date) %></td>
15 <td align="center"><%= format_date(version.effective_date) %></td>
16 <td><%=h version.description %></td>
16 <td><%=h version.description %></td>
17 <td><%= l("version_status_#{version.status}") %></td>
17 <td><%= l("version_status_#{version.status}") %></td>
18 <td><%= link_to(h(version.wiki_page_title), :controller => 'wiki', :page => Wiki.titleize(version.wiki_page_title)) unless version.wiki_page_title.blank? || @project.wiki.nil? %></td>
18 <td><%= link_to(h(version.wiki_page_title), :controller => 'wiki', :page => Wiki.titleize(version.wiki_page_title)) unless version.wiki_page_title.blank? || @project.wiki.nil? %></td>
19 <td class="buttons">
19 <td class="buttons">
20 <%= link_to_if_authorized l(:button_edit), {:controller => 'versions', :action => 'edit', :id => version }, :class => 'icon icon-edit' %>
20 <%= link_to_if_authorized l(:button_edit), {:controller => 'versions', :action => 'edit', :id => version }, :class => 'icon icon-edit' %>
21 <%= link_to_if_authorized l(:button_delete), {:controller => 'versions', :action => 'destroy', :id => version}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
21 <%= link_to_if_authorized l(:button_delete), {:controller => 'versions', :action => 'destroy', :id => version}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
22 </td>
22 </td>
23 </tr>
23 </tr>
24 <% end; reset_cycle %>
24 <% end; reset_cycle %>
25 </tbody>
25 </tbody>
26 </table>
26 </table>
27 <% else %>
27 <% else %>
28 <p class="nodata"><%= l(:label_no_data) %></p>
28 <p class="nodata"><%= l(:label_no_data) %></p>
29 <% end %>
29 <% end %>
30
30
31 <div class="contextual">
32 <% if @project.versions.any? %>
33 <%= link_to 'Close completed versions', {:controller => 'versions', :action => 'close_completed', :project_id => @project}, :method => :post %>
34 <% end %>
35 </div>
36
31 <p><%= link_to_if_authorized l(:label_version_new), :controller => 'projects', :action => 'add_version', :id => @project %></p>
37 <p><%= link_to_if_authorized l(:label_version_new), :controller => 'projects', :action => 'add_version', :id => @project %></p>
@@ -1,262 +1,268
1 ActionController::Routing::Routes.draw do |map|
1 ActionController::Routing::Routes.draw do |map|
2 # Add your own custom routes here.
2 # Add your own custom routes here.
3 # The priority is based upon order of creation: first created -> highest priority.
3 # The priority is based upon order of creation: first created -> highest priority.
4
4
5 # Here's a sample route:
5 # Here's a sample route:
6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
7 # Keep in mind you can assign values other than :controller and :action
7 # Keep in mind you can assign values other than :controller and :action
8
8
9 map.home '', :controller => 'welcome'
9 map.home '', :controller => 'welcome'
10
10
11 map.signin 'login', :controller => 'account', :action => 'login'
11 map.signin 'login', :controller => 'account', :action => 'login'
12 map.signout 'logout', :controller => 'account', :action => 'logout'
12 map.signout 'logout', :controller => 'account', :action => 'logout'
13
13
14 map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
14 map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
15 map.connect 'help/:ctrl/:page', :controller => 'help'
15 map.connect 'help/:ctrl/:page', :controller => 'help'
16
16
17 map.connect 'time_entries/:id/edit', :action => 'edit', :controller => 'timelog'
17 map.connect 'time_entries/:id/edit', :action => 'edit', :controller => 'timelog'
18 map.connect 'projects/:project_id/time_entries/new', :action => 'edit', :controller => 'timelog'
18 map.connect 'projects/:project_id/time_entries/new', :action => 'edit', :controller => 'timelog'
19 map.connect 'projects/:project_id/issues/:issue_id/time_entries/new', :action => 'edit', :controller => 'timelog'
19 map.connect 'projects/:project_id/issues/:issue_id/time_entries/new', :action => 'edit', :controller => 'timelog'
20
20
21 map.with_options :controller => 'timelog' do |timelog|
21 map.with_options :controller => 'timelog' do |timelog|
22 timelog.connect 'projects/:project_id/time_entries', :action => 'details'
22 timelog.connect 'projects/:project_id/time_entries', :action => 'details'
23
23
24 timelog.with_options :action => 'details', :conditions => {:method => :get} do |time_details|
24 timelog.with_options :action => 'details', :conditions => {:method => :get} do |time_details|
25 time_details.connect 'time_entries'
25 time_details.connect 'time_entries'
26 time_details.connect 'time_entries.:format'
26 time_details.connect 'time_entries.:format'
27 time_details.connect 'issues/:issue_id/time_entries'
27 time_details.connect 'issues/:issue_id/time_entries'
28 time_details.connect 'issues/:issue_id/time_entries.:format'
28 time_details.connect 'issues/:issue_id/time_entries.:format'
29 time_details.connect 'projects/:project_id/time_entries.:format'
29 time_details.connect 'projects/:project_id/time_entries.:format'
30 time_details.connect 'projects/:project_id/issues/:issue_id/time_entries'
30 time_details.connect 'projects/:project_id/issues/:issue_id/time_entries'
31 time_details.connect 'projects/:project_id/issues/:issue_id/time_entries.:format'
31 time_details.connect 'projects/:project_id/issues/:issue_id/time_entries.:format'
32 end
32 end
33 timelog.connect 'projects/:project_id/time_entries/report', :action => 'report'
33 timelog.connect 'projects/:project_id/time_entries/report', :action => 'report'
34 timelog.with_options :action => 'report',:conditions => {:method => :get} do |time_report|
34 timelog.with_options :action => 'report',:conditions => {:method => :get} do |time_report|
35 time_report.connect 'time_entries/report'
35 time_report.connect 'time_entries/report'
36 time_report.connect 'time_entries/report.:format'
36 time_report.connect 'time_entries/report.:format'
37 time_report.connect 'projects/:project_id/time_entries/report.:format'
37 time_report.connect 'projects/:project_id/time_entries/report.:format'
38 end
38 end
39
39
40 timelog.with_options :action => 'edit', :conditions => {:method => :get} do |time_edit|
40 timelog.with_options :action => 'edit', :conditions => {:method => :get} do |time_edit|
41 time_edit.connect 'issues/:issue_id/time_entries/new'
41 time_edit.connect 'issues/:issue_id/time_entries/new'
42 end
42 end
43
43
44 timelog.connect 'time_entries/:id/destroy', :action => 'destroy', :conditions => {:method => :post}
44 timelog.connect 'time_entries/:id/destroy', :action => 'destroy', :conditions => {:method => :post}
45 end
45 end
46
46
47 map.connect 'projects/:id/wiki', :controller => 'wikis', :action => 'edit', :conditions => {:method => :post}
47 map.connect 'projects/:id/wiki', :controller => 'wikis', :action => 'edit', :conditions => {:method => :post}
48 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :get}
48 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :get}
49 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :post}
49 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :post}
50 map.with_options :controller => 'wiki' do |wiki_routes|
50 map.with_options :controller => 'wiki' do |wiki_routes|
51 wiki_routes.with_options :conditions => {:method => :get} do |wiki_views|
51 wiki_routes.with_options :conditions => {:method => :get} do |wiki_views|
52 wiki_views.connect 'projects/:id/wiki/:page', :action => 'special', :page => /page_index|date_index|export/i
52 wiki_views.connect 'projects/:id/wiki/:page', :action => 'special', :page => /page_index|date_index|export/i
53 wiki_views.connect 'projects/:id/wiki/:page', :action => 'index', :page => nil
53 wiki_views.connect 'projects/:id/wiki/:page', :action => 'index', :page => nil
54 wiki_views.connect 'projects/:id/wiki/:page/edit', :action => 'edit'
54 wiki_views.connect 'projects/:id/wiki/:page/edit', :action => 'edit'
55 wiki_views.connect 'projects/:id/wiki/:page/rename', :action => 'rename'
55 wiki_views.connect 'projects/:id/wiki/:page/rename', :action => 'rename'
56 wiki_views.connect 'projects/:id/wiki/:page/history', :action => 'history'
56 wiki_views.connect 'projects/:id/wiki/:page/history', :action => 'history'
57 wiki_views.connect 'projects/:id/wiki/:page/diff/:version/vs/:version_from', :action => 'diff'
57 wiki_views.connect 'projects/:id/wiki/:page/diff/:version/vs/:version_from', :action => 'diff'
58 wiki_views.connect 'projects/:id/wiki/:page/annotate/:version', :action => 'annotate'
58 wiki_views.connect 'projects/:id/wiki/:page/annotate/:version', :action => 'annotate'
59 end
59 end
60
60
61 wiki_routes.connect 'projects/:id/wiki/:page/:action',
61 wiki_routes.connect 'projects/:id/wiki/:page/:action',
62 :action => /edit|rename|destroy|preview|protect/,
62 :action => /edit|rename|destroy|preview|protect/,
63 :conditions => {:method => :post}
63 :conditions => {:method => :post}
64 end
64 end
65
65
66 map.with_options :controller => 'messages' do |messages_routes|
66 map.with_options :controller => 'messages' do |messages_routes|
67 messages_routes.with_options :conditions => {:method => :get} do |messages_views|
67 messages_routes.with_options :conditions => {:method => :get} do |messages_views|
68 messages_views.connect 'boards/:board_id/topics/new', :action => 'new'
68 messages_views.connect 'boards/:board_id/topics/new', :action => 'new'
69 messages_views.connect 'boards/:board_id/topics/:id', :action => 'show'
69 messages_views.connect 'boards/:board_id/topics/:id', :action => 'show'
70 messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
70 messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
71 end
71 end
72 messages_routes.with_options :conditions => {:method => :post} do |messages_actions|
72 messages_routes.with_options :conditions => {:method => :post} do |messages_actions|
73 messages_actions.connect 'boards/:board_id/topics/new', :action => 'new'
73 messages_actions.connect 'boards/:board_id/topics/new', :action => 'new'
74 messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply'
74 messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply'
75 messages_actions.connect 'boards/:board_id/topics/:id/:action', :action => /edit|destroy/
75 messages_actions.connect 'boards/:board_id/topics/:id/:action', :action => /edit|destroy/
76 end
76 end
77 end
77 end
78
78
79 map.with_options :controller => 'boards' do |board_routes|
79 map.with_options :controller => 'boards' do |board_routes|
80 board_routes.with_options :conditions => {:method => :get} do |board_views|
80 board_routes.with_options :conditions => {:method => :get} do |board_views|
81 board_views.connect 'projects/:project_id/boards', :action => 'index'
81 board_views.connect 'projects/:project_id/boards', :action => 'index'
82 board_views.connect 'projects/:project_id/boards/new', :action => 'new'
82 board_views.connect 'projects/:project_id/boards/new', :action => 'new'
83 board_views.connect 'projects/:project_id/boards/:id', :action => 'show'
83 board_views.connect 'projects/:project_id/boards/:id', :action => 'show'
84 board_views.connect 'projects/:project_id/boards/:id.:format', :action => 'show'
84 board_views.connect 'projects/:project_id/boards/:id.:format', :action => 'show'
85 board_views.connect 'projects/:project_id/boards/:id/edit', :action => 'edit'
85 board_views.connect 'projects/:project_id/boards/:id/edit', :action => 'edit'
86 end
86 end
87 board_routes.with_options :conditions => {:method => :post} do |board_actions|
87 board_routes.with_options :conditions => {:method => :post} do |board_actions|
88 board_actions.connect 'projects/:project_id/boards', :action => 'new'
88 board_actions.connect 'projects/:project_id/boards', :action => 'new'
89 board_actions.connect 'projects/:project_id/boards/:id/:action', :action => /edit|destroy/
89 board_actions.connect 'projects/:project_id/boards/:id/:action', :action => /edit|destroy/
90 end
90 end
91 end
91 end
92
92
93 map.with_options :controller => 'documents' do |document_routes|
93 map.with_options :controller => 'documents' do |document_routes|
94 document_routes.with_options :conditions => {:method => :get} do |document_views|
94 document_routes.with_options :conditions => {:method => :get} do |document_views|
95 document_views.connect 'projects/:project_id/documents', :action => 'index'
95 document_views.connect 'projects/:project_id/documents', :action => 'index'
96 document_views.connect 'projects/:project_id/documents/new', :action => 'new'
96 document_views.connect 'projects/:project_id/documents/new', :action => 'new'
97 document_views.connect 'documents/:id', :action => 'show'
97 document_views.connect 'documents/:id', :action => 'show'
98 document_views.connect 'documents/:id/edit', :action => 'edit'
98 document_views.connect 'documents/:id/edit', :action => 'edit'
99 end
99 end
100 document_routes.with_options :conditions => {:method => :post} do |document_actions|
100 document_routes.with_options :conditions => {:method => :post} do |document_actions|
101 document_actions.connect 'projects/:project_id/documents', :action => 'new'
101 document_actions.connect 'projects/:project_id/documents', :action => 'new'
102 document_actions.connect 'documents/:id/:action', :action => /destroy|edit/
102 document_actions.connect 'documents/:id/:action', :action => /destroy|edit/
103 end
103 end
104 end
104 end
105
105
106 map.with_options :controller => 'issues' do |issues_routes|
106 map.with_options :controller => 'issues' do |issues_routes|
107 issues_routes.with_options :conditions => {:method => :get} do |issues_views|
107 issues_routes.with_options :conditions => {:method => :get} do |issues_views|
108 issues_views.connect 'issues', :action => 'index'
108 issues_views.connect 'issues', :action => 'index'
109 issues_views.connect 'issues.:format', :action => 'index'
109 issues_views.connect 'issues.:format', :action => 'index'
110 issues_views.connect 'projects/:project_id/issues', :action => 'index'
110 issues_views.connect 'projects/:project_id/issues', :action => 'index'
111 issues_views.connect 'projects/:project_id/issues.:format', :action => 'index'
111 issues_views.connect 'projects/:project_id/issues.:format', :action => 'index'
112 issues_views.connect 'projects/:project_id/issues/new', :action => 'new'
112 issues_views.connect 'projects/:project_id/issues/new', :action => 'new'
113 issues_views.connect 'projects/:project_id/issues/gantt', :action => 'gantt'
113 issues_views.connect 'projects/:project_id/issues/gantt', :action => 'gantt'
114 issues_views.connect 'projects/:project_id/issues/calendar', :action => 'calendar'
114 issues_views.connect 'projects/:project_id/issues/calendar', :action => 'calendar'
115 issues_views.connect 'projects/:project_id/issues/:copy_from/copy', :action => 'new'
115 issues_views.connect 'projects/:project_id/issues/:copy_from/copy', :action => 'new'
116 issues_views.connect 'issues/:id', :action => 'show', :id => /\d+/
116 issues_views.connect 'issues/:id', :action => 'show', :id => /\d+/
117 issues_views.connect 'issues/:id.:format', :action => 'show', :id => /\d+/
117 issues_views.connect 'issues/:id.:format', :action => 'show', :id => /\d+/
118 issues_views.connect 'issues/:id/edit', :action => 'edit', :id => /\d+/
118 issues_views.connect 'issues/:id/edit', :action => 'edit', :id => /\d+/
119 issues_views.connect 'issues/:id/move', :action => 'move', :id => /\d+/
119 issues_views.connect 'issues/:id/move', :action => 'move', :id => /\d+/
120 end
120 end
121 issues_routes.with_options :conditions => {:method => :post} do |issues_actions|
121 issues_routes.with_options :conditions => {:method => :post} do |issues_actions|
122 issues_actions.connect 'projects/:project_id/issues', :action => 'new'
122 issues_actions.connect 'projects/:project_id/issues', :action => 'new'
123 issues_actions.connect 'issues/:id/quoted', :action => 'reply', :id => /\d+/
123 issues_actions.connect 'issues/:id/quoted', :action => 'reply', :id => /\d+/
124 issues_actions.connect 'issues/:id/:action', :action => /edit|move|destroy/, :id => /\d+/
124 issues_actions.connect 'issues/:id/:action', :action => /edit|move|destroy/, :id => /\d+/
125 end
125 end
126 issues_routes.connect 'issues/:action'
126 issues_routes.connect 'issues/:action'
127 end
127 end
128
128
129 map.with_options :controller => 'issue_relations', :conditions => {:method => :post} do |relations|
129 map.with_options :controller => 'issue_relations', :conditions => {:method => :post} do |relations|
130 relations.connect 'issues/:issue_id/relations/:id', :action => 'new'
130 relations.connect 'issues/:issue_id/relations/:id', :action => 'new'
131 relations.connect 'issues/:issue_id/relations/:id/destroy', :action => 'destroy'
131 relations.connect 'issues/:issue_id/relations/:id/destroy', :action => 'destroy'
132 end
132 end
133
133
134 map.with_options :controller => 'reports', :action => 'issue_report', :conditions => {:method => :get} do |reports|
134 map.with_options :controller => 'reports', :action => 'issue_report', :conditions => {:method => :get} do |reports|
135 reports.connect 'projects/:id/issues/report'
135 reports.connect 'projects/:id/issues/report'
136 reports.connect 'projects/:id/issues/report/:detail'
136 reports.connect 'projects/:id/issues/report/:detail'
137 end
137 end
138
138
139 map.with_options :controller => 'news' do |news_routes|
139 map.with_options :controller => 'news' do |news_routes|
140 news_routes.with_options :conditions => {:method => :get} do |news_views|
140 news_routes.with_options :conditions => {:method => :get} do |news_views|
141 news_views.connect 'news', :action => 'index'
141 news_views.connect 'news', :action => 'index'
142 news_views.connect 'projects/:project_id/news', :action => 'index'
142 news_views.connect 'projects/:project_id/news', :action => 'index'
143 news_views.connect 'projects/:project_id/news.:format', :action => 'index'
143 news_views.connect 'projects/:project_id/news.:format', :action => 'index'
144 news_views.connect 'news.:format', :action => 'index'
144 news_views.connect 'news.:format', :action => 'index'
145 news_views.connect 'projects/:project_id/news/new', :action => 'new'
145 news_views.connect 'projects/:project_id/news/new', :action => 'new'
146 news_views.connect 'news/:id', :action => 'show'
146 news_views.connect 'news/:id', :action => 'show'
147 news_views.connect 'news/:id/edit', :action => 'edit'
147 news_views.connect 'news/:id/edit', :action => 'edit'
148 end
148 end
149 news_routes.with_options do |news_actions|
149 news_routes.with_options do |news_actions|
150 news_actions.connect 'projects/:project_id/news', :action => 'new'
150 news_actions.connect 'projects/:project_id/news', :action => 'new'
151 news_actions.connect 'news/:id/edit', :action => 'edit'
151 news_actions.connect 'news/:id/edit', :action => 'edit'
152 news_actions.connect 'news/:id/destroy', :action => 'destroy'
152 news_actions.connect 'news/:id/destroy', :action => 'destroy'
153 end
153 end
154 end
154 end
155
155
156 map.connect 'projects/:id/members/new', :controller => 'members', :action => 'new'
156 map.connect 'projects/:id/members/new', :controller => 'members', :action => 'new'
157
157
158 map.with_options :controller => 'users' do |users|
158 map.with_options :controller => 'users' do |users|
159 users.with_options :conditions => {:method => :get} do |user_views|
159 users.with_options :conditions => {:method => :get} do |user_views|
160 user_views.connect 'users', :action => 'index'
160 user_views.connect 'users', :action => 'index'
161 user_views.connect 'users/:id', :action => 'show', :id => /\d+/
161 user_views.connect 'users/:id', :action => 'show', :id => /\d+/
162 user_views.connect 'users/new', :action => 'add'
162 user_views.connect 'users/new', :action => 'add'
163 user_views.connect 'users/:id/edit/:tab', :action => 'edit', :tab => nil
163 user_views.connect 'users/:id/edit/:tab', :action => 'edit', :tab => nil
164 end
164 end
165 users.with_options :conditions => {:method => :post} do |user_actions|
165 users.with_options :conditions => {:method => :post} do |user_actions|
166 user_actions.connect 'users', :action => 'add'
166 user_actions.connect 'users', :action => 'add'
167 user_actions.connect 'users/new', :action => 'add'
167 user_actions.connect 'users/new', :action => 'add'
168 user_actions.connect 'users/:id/edit', :action => 'edit'
168 user_actions.connect 'users/:id/edit', :action => 'edit'
169 user_actions.connect 'users/:id/memberships', :action => 'edit_membership'
169 user_actions.connect 'users/:id/memberships', :action => 'edit_membership'
170 user_actions.connect 'users/:id/memberships/:membership_id', :action => 'edit_membership'
170 user_actions.connect 'users/:id/memberships/:membership_id', :action => 'edit_membership'
171 user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership'
171 user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership'
172 end
172 end
173 end
173 end
174
174
175 map.with_options :controller => 'projects' do |projects|
175 map.with_options :controller => 'projects' do |projects|
176 projects.with_options :conditions => {:method => :get} do |project_views|
176 projects.with_options :conditions => {:method => :get} do |project_views|
177 project_views.connect 'projects', :action => 'index'
177 project_views.connect 'projects', :action => 'index'
178 project_views.connect 'projects.:format', :action => 'index'
178 project_views.connect 'projects.:format', :action => 'index'
179 project_views.connect 'projects/new', :action => 'add'
179 project_views.connect 'projects/new', :action => 'add'
180 project_views.connect 'projects/:id', :action => 'show'
180 project_views.connect 'projects/:id', :action => 'show'
181 project_views.connect 'projects/:id/:action', :action => /roadmap|changelog|destroy|settings/
181 project_views.connect 'projects/:id/:action', :action => /roadmap|changelog|destroy|settings/
182 project_views.connect 'projects/:id/files', :action => 'list_files'
182 project_views.connect 'projects/:id/files', :action => 'list_files'
183 project_views.connect 'projects/:id/files/new', :action => 'add_file'
183 project_views.connect 'projects/:id/files/new', :action => 'add_file'
184 project_views.connect 'projects/:id/versions/new', :action => 'add_version'
184 project_views.connect 'projects/:id/versions/new', :action => 'add_version'
185 project_views.connect 'projects/:id/categories/new', :action => 'add_issue_category'
185 project_views.connect 'projects/:id/categories/new', :action => 'add_issue_category'
186 project_views.connect 'projects/:id/settings/:tab', :action => 'settings'
186 project_views.connect 'projects/:id/settings/:tab', :action => 'settings'
187 end
187 end
188
188
189 projects.with_options :action => 'activity', :conditions => {:method => :get} do |activity|
189 projects.with_options :action => 'activity', :conditions => {:method => :get} do |activity|
190 activity.connect 'projects/:id/activity'
190 activity.connect 'projects/:id/activity'
191 activity.connect 'projects/:id/activity.:format'
191 activity.connect 'projects/:id/activity.:format'
192 activity.connect 'activity', :id => nil
192 activity.connect 'activity', :id => nil
193 activity.connect 'activity.:format', :id => nil
193 activity.connect 'activity.:format', :id => nil
194 end
194 end
195
195
196 projects.with_options :conditions => {:method => :post} do |project_actions|
196 projects.with_options :conditions => {:method => :post} do |project_actions|
197 project_actions.connect 'projects/new', :action => 'add'
197 project_actions.connect 'projects/new', :action => 'add'
198 project_actions.connect 'projects', :action => 'add'
198 project_actions.connect 'projects', :action => 'add'
199 project_actions.connect 'projects/:id/:action', :action => /destroy|archive|unarchive/
199 project_actions.connect 'projects/:id/:action', :action => /destroy|archive|unarchive/
200 project_actions.connect 'projects/:id/files/new', :action => 'add_file'
200 project_actions.connect 'projects/:id/files/new', :action => 'add_file'
201 project_actions.connect 'projects/:id/versions/new', :action => 'add_version'
201 project_actions.connect 'projects/:id/versions/new', :action => 'add_version'
202 project_actions.connect 'projects/:id/categories/new', :action => 'add_issue_category'
202 project_actions.connect 'projects/:id/categories/new', :action => 'add_issue_category'
203 project_actions.connect 'projects/:id/activities/save', :action => 'save_activities'
203 project_actions.connect 'projects/:id/activities/save', :action => 'save_activities'
204 end
204 end
205
205
206 projects.with_options :conditions => {:method => :delete} do |project_actions|
206 projects.with_options :conditions => {:method => :delete} do |project_actions|
207 project_actions.conditions 'projects/:id/reset_activities', :action => 'reset_activities'
207 project_actions.conditions 'projects/:id/reset_activities', :action => 'reset_activities'
208 end
208 end
209 end
209 end
210
210
211 map.with_options :controller => 'versions' do |versions|
212 versions.with_options :conditions => {:method => :post} do |version_actions|
213 version_actions.connect 'projects/:project_id/versions/close_completed', :action => 'close_completed'
214 end
215 end
216
211 map.with_options :controller => 'repositories' do |repositories|
217 map.with_options :controller => 'repositories' do |repositories|
212 repositories.with_options :conditions => {:method => :get} do |repository_views|
218 repositories.with_options :conditions => {:method => :get} do |repository_views|
213 repository_views.connect 'projects/:id/repository', :action => 'show'
219 repository_views.connect 'projects/:id/repository', :action => 'show'
214 repository_views.connect 'projects/:id/repository/edit', :action => 'edit'
220 repository_views.connect 'projects/:id/repository/edit', :action => 'edit'
215 repository_views.connect 'projects/:id/repository/statistics', :action => 'stats'
221 repository_views.connect 'projects/:id/repository/statistics', :action => 'stats'
216 repository_views.connect 'projects/:id/repository/revisions', :action => 'revisions'
222 repository_views.connect 'projects/:id/repository/revisions', :action => 'revisions'
217 repository_views.connect 'projects/:id/repository/revisions.:format', :action => 'revisions'
223 repository_views.connect 'projects/:id/repository/revisions.:format', :action => 'revisions'
218 repository_views.connect 'projects/:id/repository/revisions/:rev', :action => 'revision'
224 repository_views.connect 'projects/:id/repository/revisions/:rev', :action => 'revision'
219 repository_views.connect 'projects/:id/repository/revisions/:rev/diff', :action => 'diff'
225 repository_views.connect 'projects/:id/repository/revisions/:rev/diff', :action => 'diff'
220 repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format', :action => 'diff'
226 repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format', :action => 'diff'
221 repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
227 repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
222 repository_views.connect 'projects/:id/repository/:action/*path'
228 repository_views.connect 'projects/:id/repository/:action/*path'
223 end
229 end
224
230
225 repositories.connect 'projects/:id/repository/:action', :conditions => {:method => :post}
231 repositories.connect 'projects/:id/repository/:action', :conditions => {:method => :post}
226 end
232 end
227
233
228 map.connect 'attachments/:id', :controller => 'attachments', :action => 'show', :id => /\d+/
234 map.connect 'attachments/:id', :controller => 'attachments', :action => 'show', :id => /\d+/
229 map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/
235 map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/
230 map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/
236 map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/
231
237
232 map.resources :groups
238 map.resources :groups
233
239
234 #left old routes at the bottom for backwards compat
240 #left old routes at the bottom for backwards compat
235 map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
241 map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
236 map.connect 'projects/:project_id/documents/:action', :controller => 'documents'
242 map.connect 'projects/:project_id/documents/:action', :controller => 'documents'
237 map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
243 map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
238 map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
244 map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
239 map.connect 'wiki/:id/:page/:action', :page => nil, :controller => 'wiki'
245 map.connect 'wiki/:id/:page/:action', :page => nil, :controller => 'wiki'
240 map.connect 'issues/:issue_id/relations/:action/:id', :controller => 'issue_relations'
246 map.connect 'issues/:issue_id/relations/:action/:id', :controller => 'issue_relations'
241 map.connect 'projects/:project_id/news/:action', :controller => 'news'
247 map.connect 'projects/:project_id/news/:action', :controller => 'news'
242 map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/
248 map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/
243 map.with_options :controller => 'repositories' do |omap|
249 map.with_options :controller => 'repositories' do |omap|
244 omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
250 omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
245 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
251 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
246 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
252 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
247 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
253 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
248 omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
254 omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
249 omap.connect 'repositories/revision/:id/:rev', :action => 'revision'
255 omap.connect 'repositories/revision/:id/:rev', :action => 'revision'
250 end
256 end
251
257
252 map.with_options :controller => 'sys' do |sys|
258 map.with_options :controller => 'sys' do |sys|
253 sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
259 sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
254 sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
260 sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
255 end
261 end
256
262
257 # Install the default route as the lowest priority.
263 # Install the default route as the lowest priority.
258 map.connect ':controller/:action/:id'
264 map.connect ':controller/:action/:id'
259 map.connect 'robots.txt', :controller => 'welcome', :action => 'robots'
265 map.connect 'robots.txt', :controller => 'welcome', :action => 'robots'
260 # Used for OpenID
266 # Used for OpenID
261 map.root :controller => 'account', :action => 'login'
267 map.root :controller => 'account', :action => 'login'
262 end
268 end
@@ -1,174 +1,174
1 require 'redmine/access_control'
1 require 'redmine/access_control'
2 require 'redmine/menu_manager'
2 require 'redmine/menu_manager'
3 require 'redmine/activity'
3 require 'redmine/activity'
4 require 'redmine/mime_type'
4 require 'redmine/mime_type'
5 require 'redmine/core_ext'
5 require 'redmine/core_ext'
6 require 'redmine/themes'
6 require 'redmine/themes'
7 require 'redmine/hook'
7 require 'redmine/hook'
8 require 'redmine/plugin'
8 require 'redmine/plugin'
9 require 'redmine/wiki_formatting'
9 require 'redmine/wiki_formatting'
10
10
11 begin
11 begin
12 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
12 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
13 rescue LoadError
13 rescue LoadError
14 # RMagick is not available
14 # RMagick is not available
15 end
15 end
16
16
17 if RUBY_VERSION < '1.9'
17 if RUBY_VERSION < '1.9'
18 require 'faster_csv'
18 require 'faster_csv'
19 else
19 else
20 require 'csv'
20 require 'csv'
21 FCSV = CSV
21 FCSV = CSV
22 end
22 end
23
23
24 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar Git Filesystem )
24 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar Git Filesystem )
25
25
26 # Permissions
26 # Permissions
27 Redmine::AccessControl.map do |map|
27 Redmine::AccessControl.map do |map|
28 map.permission :view_project, {:projects => [:show, :activity]}, :public => true
28 map.permission :view_project, {:projects => [:show, :activity]}, :public => true
29 map.permission :search_project, {:search => :index}, :public => true
29 map.permission :search_project, {:search => :index}, :public => true
30 map.permission :add_project, {:projects => :add}, :require => :loggedin
30 map.permission :add_project, {:projects => :add}, :require => :loggedin
31 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
31 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
32 map.permission :select_project_modules, {:projects => :modules}, :require => :member
32 map.permission :select_project_modules, {:projects => :modules}, :require => :member
33 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member
33 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member
34 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :destroy]}, :require => :member
34 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :close_completed, :destroy]}, :require => :member
35
35
36 map.project_module :issue_tracking do |map|
36 map.project_module :issue_tracking do |map|
37 # Issue categories
37 # Issue categories
38 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
38 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
39 # Issues
39 # Issues
40 map.permission :view_issues, {:projects => [:changelog, :roadmap],
40 map.permission :view_issues, {:projects => [:changelog, :roadmap],
41 :issues => [:index, :changes, :show, :context_menu],
41 :issues => [:index, :changes, :show, :context_menu],
42 :versions => [:show, :status_by],
42 :versions => [:show, :status_by],
43 :queries => :index,
43 :queries => :index,
44 :reports => :issue_report}, :public => true
44 :reports => :issue_report}, :public => true
45 map.permission :add_issues, {:issues => :new}
45 map.permission :add_issues, {:issues => :new}
46 map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit]}
46 map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit]}
47 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
47 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
48 map.permission :add_issue_notes, {:issues => [:edit, :reply]}
48 map.permission :add_issue_notes, {:issues => [:edit, :reply]}
49 map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
49 map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
50 map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
50 map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
51 map.permission :move_issues, {:issues => :move}, :require => :loggedin
51 map.permission :move_issues, {:issues => :move}, :require => :loggedin
52 map.permission :delete_issues, {:issues => :destroy}, :require => :member
52 map.permission :delete_issues, {:issues => :destroy}, :require => :member
53 # Queries
53 # Queries
54 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
54 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
55 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
55 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
56 # Gantt & calendar
56 # Gantt & calendar
57 map.permission :view_gantt, :issues => :gantt
57 map.permission :view_gantt, :issues => :gantt
58 map.permission :view_calendar, :issues => :calendar
58 map.permission :view_calendar, :issues => :calendar
59 # Watchers
59 # Watchers
60 map.permission :view_issue_watchers, {}
60 map.permission :view_issue_watchers, {}
61 map.permission :add_issue_watchers, {:watchers => :new}
61 map.permission :add_issue_watchers, {:watchers => :new}
62 map.permission :delete_issue_watchers, {:watchers => :destroy}
62 map.permission :delete_issue_watchers, {:watchers => :destroy}
63 end
63 end
64
64
65 map.project_module :time_tracking do |map|
65 map.project_module :time_tracking do |map|
66 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
66 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
67 map.permission :view_time_entries, :timelog => [:details, :report]
67 map.permission :view_time_entries, :timelog => [:details, :report]
68 map.permission :edit_time_entries, {:timelog => [:edit, :destroy]}, :require => :member
68 map.permission :edit_time_entries, {:timelog => [:edit, :destroy]}, :require => :member
69 map.permission :edit_own_time_entries, {:timelog => [:edit, :destroy]}, :require => :loggedin
69 map.permission :edit_own_time_entries, {:timelog => [:edit, :destroy]}, :require => :loggedin
70 map.permission :manage_project_activities, {:projects => [:save_activities, :reset_activities]}, :require => :member
70 map.permission :manage_project_activities, {:projects => [:save_activities, :reset_activities]}, :require => :member
71 end
71 end
72
72
73 map.project_module :news do |map|
73 map.project_module :news do |map|
74 map.permission :manage_news, {:news => [:new, :edit, :destroy, :destroy_comment]}, :require => :member
74 map.permission :manage_news, {:news => [:new, :edit, :destroy, :destroy_comment]}, :require => :member
75 map.permission :view_news, {:news => [:index, :show]}, :public => true
75 map.permission :view_news, {:news => [:index, :show]}, :public => true
76 map.permission :comment_news, {:news => :add_comment}
76 map.permission :comment_news, {:news => :add_comment}
77 end
77 end
78
78
79 map.project_module :documents do |map|
79 map.project_module :documents do |map|
80 map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment]}, :require => :loggedin
80 map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment]}, :require => :loggedin
81 map.permission :view_documents, :documents => [:index, :show, :download]
81 map.permission :view_documents, :documents => [:index, :show, :download]
82 end
82 end
83
83
84 map.project_module :files do |map|
84 map.project_module :files do |map|
85 map.permission :manage_files, {:projects => :add_file}, :require => :loggedin
85 map.permission :manage_files, {:projects => :add_file}, :require => :loggedin
86 map.permission :view_files, :projects => :list_files, :versions => :download
86 map.permission :view_files, :projects => :list_files, :versions => :download
87 end
87 end
88
88
89 map.project_module :wiki do |map|
89 map.project_module :wiki do |map|
90 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
90 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
91 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
91 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
92 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
92 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
93 map.permission :view_wiki_pages, :wiki => [:index, :special]
93 map.permission :view_wiki_pages, :wiki => [:index, :special]
94 map.permission :view_wiki_edits, :wiki => [:history, :diff, :annotate]
94 map.permission :view_wiki_edits, :wiki => [:history, :diff, :annotate]
95 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment]
95 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment]
96 map.permission :delete_wiki_pages_attachments, {}
96 map.permission :delete_wiki_pages_attachments, {}
97 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
97 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
98 end
98 end
99
99
100 map.project_module :repository do |map|
100 map.project_module :repository do |map|
101 map.permission :manage_repository, {:repositories => [:edit, :committers, :destroy]}, :require => :member
101 map.permission :manage_repository, {:repositories => [:edit, :committers, :destroy]}, :require => :member
102 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
102 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
103 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
103 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
104 map.permission :commit_access, {}
104 map.permission :commit_access, {}
105 end
105 end
106
106
107 map.project_module :boards do |map|
107 map.project_module :boards do |map|
108 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
108 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
109 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
109 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
110 map.permission :add_messages, {:messages => [:new, :reply, :quote]}
110 map.permission :add_messages, {:messages => [:new, :reply, :quote]}
111 map.permission :edit_messages, {:messages => :edit}, :require => :member
111 map.permission :edit_messages, {:messages => :edit}, :require => :member
112 map.permission :edit_own_messages, {:messages => :edit}, :require => :loggedin
112 map.permission :edit_own_messages, {:messages => :edit}, :require => :loggedin
113 map.permission :delete_messages, {:messages => :destroy}, :require => :member
113 map.permission :delete_messages, {:messages => :destroy}, :require => :member
114 map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin
114 map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin
115 end
115 end
116 end
116 end
117
117
118 Redmine::MenuManager.map :top_menu do |menu|
118 Redmine::MenuManager.map :top_menu do |menu|
119 menu.push :home, :home_path
119 menu.push :home, :home_path
120 menu.push :my_page, { :controller => 'my', :action => 'page' }, :if => Proc.new { User.current.logged? }
120 menu.push :my_page, { :controller => 'my', :action => 'page' }, :if => Proc.new { User.current.logged? }
121 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural
121 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural
122 menu.push :administration, { :controller => 'admin', :action => 'index' }, :if => Proc.new { User.current.admin? }, :last => true
122 menu.push :administration, { :controller => 'admin', :action => 'index' }, :if => Proc.new { User.current.admin? }, :last => true
123 menu.push :help, Redmine::Info.help_url, :last => true
123 menu.push :help, Redmine::Info.help_url, :last => true
124 end
124 end
125
125
126 Redmine::MenuManager.map :account_menu do |menu|
126 Redmine::MenuManager.map :account_menu do |menu|
127 menu.push :login, :signin_path, :if => Proc.new { !User.current.logged? }
127 menu.push :login, :signin_path, :if => Proc.new { !User.current.logged? }
128 menu.push :register, { :controller => 'account', :action => 'register' }, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
128 menu.push :register, { :controller => 'account', :action => 'register' }, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
129 menu.push :my_account, { :controller => 'my', :action => 'account' }, :if => Proc.new { User.current.logged? }
129 menu.push :my_account, { :controller => 'my', :action => 'account' }, :if => Proc.new { User.current.logged? }
130 menu.push :logout, :signout_path, :if => Proc.new { User.current.logged? }
130 menu.push :logout, :signout_path, :if => Proc.new { User.current.logged? }
131 end
131 end
132
132
133 Redmine::MenuManager.map :application_menu do |menu|
133 Redmine::MenuManager.map :application_menu do |menu|
134 # Empty
134 # Empty
135 end
135 end
136
136
137 Redmine::MenuManager.map :admin_menu do |menu|
137 Redmine::MenuManager.map :admin_menu do |menu|
138 # Empty
138 # Empty
139 end
139 end
140
140
141 Redmine::MenuManager.map :project_menu do |menu|
141 Redmine::MenuManager.map :project_menu do |menu|
142 menu.push :overview, { :controller => 'projects', :action => 'show' }
142 menu.push :overview, { :controller => 'projects', :action => 'show' }
143 menu.push :activity, { :controller => 'projects', :action => 'activity' }
143 menu.push :activity, { :controller => 'projects', :action => 'activity' }
144 menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' },
144 menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' },
145 :if => Proc.new { |p| p.versions.any? }
145 :if => Proc.new { |p| p.versions.any? }
146 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
146 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
147 menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new,
147 menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new,
148 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
148 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
149 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
149 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
150 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
150 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
151 menu.push :wiki, { :controller => 'wiki', :action => 'index', :page => nil },
151 menu.push :wiki, { :controller => 'wiki', :action => 'index', :page => nil },
152 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
152 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
153 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
153 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
154 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
154 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
155 menu.push :files, { :controller => 'projects', :action => 'list_files' }, :caption => :label_attachment_plural
155 menu.push :files, { :controller => 'projects', :action => 'list_files' }, :caption => :label_attachment_plural
156 menu.push :repository, { :controller => 'repositories', :action => 'show' },
156 menu.push :repository, { :controller => 'repositories', :action => 'show' },
157 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
157 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
158 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
158 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
159 end
159 end
160
160
161 Redmine::Activity.map do |activity|
161 Redmine::Activity.map do |activity|
162 activity.register :issues, :class_name => %w(Issue Journal)
162 activity.register :issues, :class_name => %w(Issue Journal)
163 activity.register :changesets
163 activity.register :changesets
164 activity.register :news
164 activity.register :news
165 activity.register :documents, :class_name => %w(Document Attachment)
165 activity.register :documents, :class_name => %w(Document Attachment)
166 activity.register :files, :class_name => 'Attachment'
166 activity.register :files, :class_name => 'Attachment'
167 activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false
167 activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false
168 activity.register :messages, :default => false
168 activity.register :messages, :default => false
169 activity.register :time_entries, :default => false
169 activity.register :time_entries, :default => false
170 end
170 end
171
171
172 Redmine::WikiFormatting.map do |format|
172 Redmine::WikiFormatting.map do |format|
173 format.register :textile, Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting::Textile::Helper
173 format.register :textile, Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting::Textile::Helper
174 end
174 end
@@ -1,73 +1,81
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'versions_controller'
19 require 'versions_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class VersionsController; def rescue_action(e) raise e end; end
22 class VersionsController; def rescue_action(e) raise e end; end
23
23
24 class VersionsControllerTest < ActionController::TestCase
24 class VersionsControllerTest < ActionController::TestCase
25 fixtures :projects, :versions, :issues, :users, :roles, :members, :member_roles, :enabled_modules
25 fixtures :projects, :versions, :issues, :users, :roles, :members, :member_roles, :enabled_modules
26
26
27 def setup
27 def setup
28 @controller = VersionsController.new
28 @controller = VersionsController.new
29 @request = ActionController::TestRequest.new
29 @request = ActionController::TestRequest.new
30 @response = ActionController::TestResponse.new
30 @response = ActionController::TestResponse.new
31 User.current = nil
31 User.current = nil
32 end
32 end
33
33
34 def test_show
34 def test_show
35 get :show, :id => 2
35 get :show, :id => 2
36 assert_response :success
36 assert_response :success
37 assert_template 'show'
37 assert_template 'show'
38 assert_not_nil assigns(:version)
38 assert_not_nil assigns(:version)
39
39
40 assert_tag :tag => 'h2', :content => /1.0/
40 assert_tag :tag => 'h2', :content => /1.0/
41 end
41 end
42
42
43 def test_get_edit
43 def test_get_edit
44 @request.session[:user_id] = 2
44 @request.session[:user_id] = 2
45 get :edit, :id => 2
45 get :edit, :id => 2
46 assert_response :success
46 assert_response :success
47 assert_template 'edit'
47 assert_template 'edit'
48 end
48 end
49
49
50 def test_close_completed
51 Version.update_all("status = 'open'")
52 @request.session[:user_id] = 2
53 post :close_completed, :project_id => 'ecookbook'
54 assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
55 assert_not_nil Version.find_by_status('closed')
56 end
57
50 def test_post_edit
58 def test_post_edit
51 @request.session[:user_id] = 2
59 @request.session[:user_id] = 2
52 post :edit, :id => 2,
60 post :edit, :id => 2,
53 :version => { :name => 'New version name',
61 :version => { :name => 'New version name',
54 :effective_date => Date.today.strftime("%Y-%m-%d")}
62 :effective_date => Date.today.strftime("%Y-%m-%d")}
55 assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
63 assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
56 version = Version.find(2)
64 version = Version.find(2)
57 assert_equal 'New version name', version.name
65 assert_equal 'New version name', version.name
58 assert_equal Date.today, version.effective_date
66 assert_equal Date.today, version.effective_date
59 end
67 end
60
68
61 def test_destroy
69 def test_destroy
62 @request.session[:user_id] = 2
70 @request.session[:user_id] = 2
63 post :destroy, :id => 3
71 post :destroy, :id => 3
64 assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
72 assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
65 assert_nil Version.find_by_id(3)
73 assert_nil Version.find_by_id(3)
66 end
74 end
67
75
68 def test_issue_status_by
76 def test_issue_status_by
69 xhr :get, :status_by, :id => 2
77 xhr :get, :status_by, :id => 2
70 assert_response :success
78 assert_response :success
71 assert_template '_issue_counts'
79 assert_template '_issue_counts'
72 end
80 end
73 end
81 end
@@ -1,530 +1,541
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class ProjectTest < ActiveSupport::TestCase
20 class ProjectTest < ActiveSupport::TestCase
21 fixtures :projects, :enabled_modules,
21 fixtures :projects, :enabled_modules,
22 :issues, :issue_statuses, :journals, :journal_details,
22 :issues, :issue_statuses, :journals, :journal_details,
23 :users, :members, :member_roles, :roles, :projects_trackers, :trackers, :boards,
23 :users, :members, :member_roles, :roles, :projects_trackers, :trackers, :boards,
24 :queries
24 :queries
25
25
26 def setup
26 def setup
27 @ecookbook = Project.find(1)
27 @ecookbook = Project.find(1)
28 @ecookbook_sub1 = Project.find(3)
28 @ecookbook_sub1 = Project.find(3)
29 end
29 end
30
30
31 should_validate_presence_of :name
31 should_validate_presence_of :name
32 should_validate_presence_of :identifier
32 should_validate_presence_of :identifier
33
33
34 should_validate_uniqueness_of :name
34 should_validate_uniqueness_of :name
35 should_validate_uniqueness_of :identifier
35 should_validate_uniqueness_of :identifier
36
36
37 context "associations" do
37 context "associations" do
38 should_have_many :members
38 should_have_many :members
39 should_have_many :users, :through => :members
39 should_have_many :users, :through => :members
40 should_have_many :member_principals
40 should_have_many :member_principals
41 should_have_many :principals, :through => :member_principals
41 should_have_many :principals, :through => :member_principals
42 should_have_many :enabled_modules
42 should_have_many :enabled_modules
43 should_have_many :issues
43 should_have_many :issues
44 should_have_many :issue_changes, :through => :issues
44 should_have_many :issue_changes, :through => :issues
45 should_have_many :versions
45 should_have_many :versions
46 should_have_many :time_entries
46 should_have_many :time_entries
47 should_have_many :queries
47 should_have_many :queries
48 should_have_many :documents
48 should_have_many :documents
49 should_have_many :news
49 should_have_many :news
50 should_have_many :issue_categories
50 should_have_many :issue_categories
51 should_have_many :boards
51 should_have_many :boards
52 should_have_many :changesets, :through => :repository
52 should_have_many :changesets, :through => :repository
53
53
54 should_have_one :repository
54 should_have_one :repository
55 should_have_one :wiki
55 should_have_one :wiki
56
56
57 should_have_and_belong_to_many :trackers
57 should_have_and_belong_to_many :trackers
58 should_have_and_belong_to_many :issue_custom_fields
58 should_have_and_belong_to_many :issue_custom_fields
59 end
59 end
60
60
61 def test_truth
61 def test_truth
62 assert_kind_of Project, @ecookbook
62 assert_kind_of Project, @ecookbook
63 assert_equal "eCookbook", @ecookbook.name
63 assert_equal "eCookbook", @ecookbook.name
64 end
64 end
65
65
66 def test_update
66 def test_update
67 assert_equal "eCookbook", @ecookbook.name
67 assert_equal "eCookbook", @ecookbook.name
68 @ecookbook.name = "eCook"
68 @ecookbook.name = "eCook"
69 assert @ecookbook.save, @ecookbook.errors.full_messages.join("; ")
69 assert @ecookbook.save, @ecookbook.errors.full_messages.join("; ")
70 @ecookbook.reload
70 @ecookbook.reload
71 assert_equal "eCook", @ecookbook.name
71 assert_equal "eCook", @ecookbook.name
72 end
72 end
73
73
74 def test_validate_identifier
74 def test_validate_identifier
75 to_test = {"abc" => true,
75 to_test = {"abc" => true,
76 "ab12" => true,
76 "ab12" => true,
77 "ab-12" => true,
77 "ab-12" => true,
78 "12" => false,
78 "12" => false,
79 "new" => false}
79 "new" => false}
80
80
81 to_test.each do |identifier, valid|
81 to_test.each do |identifier, valid|
82 p = Project.new
82 p = Project.new
83 p.identifier = identifier
83 p.identifier = identifier
84 p.valid?
84 p.valid?
85 assert_equal valid, p.errors.on('identifier').nil?
85 assert_equal valid, p.errors.on('identifier').nil?
86 end
86 end
87 end
87 end
88
88
89 def test_members_should_be_active_users
89 def test_members_should_be_active_users
90 Project.all.each do |project|
90 Project.all.each do |project|
91 assert_nil project.members.detect {|m| !(m.user.is_a?(User) && m.user.active?) }
91 assert_nil project.members.detect {|m| !(m.user.is_a?(User) && m.user.active?) }
92 end
92 end
93 end
93 end
94
94
95 def test_users_should_be_active_users
95 def test_users_should_be_active_users
96 Project.all.each do |project|
96 Project.all.each do |project|
97 assert_nil project.users.detect {|u| !(u.is_a?(User) && u.active?) }
97 assert_nil project.users.detect {|u| !(u.is_a?(User) && u.active?) }
98 end
98 end
99 end
99 end
100
100
101 def test_archive
101 def test_archive
102 user = @ecookbook.members.first.user
102 user = @ecookbook.members.first.user
103 @ecookbook.archive
103 @ecookbook.archive
104 @ecookbook.reload
104 @ecookbook.reload
105
105
106 assert !@ecookbook.active?
106 assert !@ecookbook.active?
107 assert !user.projects.include?(@ecookbook)
107 assert !user.projects.include?(@ecookbook)
108 # Subproject are also archived
108 # Subproject are also archived
109 assert !@ecookbook.children.empty?
109 assert !@ecookbook.children.empty?
110 assert @ecookbook.descendants.active.empty?
110 assert @ecookbook.descendants.active.empty?
111 end
111 end
112
112
113 def test_unarchive
113 def test_unarchive
114 user = @ecookbook.members.first.user
114 user = @ecookbook.members.first.user
115 @ecookbook.archive
115 @ecookbook.archive
116 # A subproject of an archived project can not be unarchived
116 # A subproject of an archived project can not be unarchived
117 assert !@ecookbook_sub1.unarchive
117 assert !@ecookbook_sub1.unarchive
118
118
119 # Unarchive project
119 # Unarchive project
120 assert @ecookbook.unarchive
120 assert @ecookbook.unarchive
121 @ecookbook.reload
121 @ecookbook.reload
122 assert @ecookbook.active?
122 assert @ecookbook.active?
123 assert user.projects.include?(@ecookbook)
123 assert user.projects.include?(@ecookbook)
124 # Subproject can now be unarchived
124 # Subproject can now be unarchived
125 @ecookbook_sub1.reload
125 @ecookbook_sub1.reload
126 assert @ecookbook_sub1.unarchive
126 assert @ecookbook_sub1.unarchive
127 end
127 end
128
128
129 def test_destroy
129 def test_destroy
130 # 2 active members
130 # 2 active members
131 assert_equal 2, @ecookbook.members.size
131 assert_equal 2, @ecookbook.members.size
132 # and 1 is locked
132 # and 1 is locked
133 assert_equal 3, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
133 assert_equal 3, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
134 # some boards
134 # some boards
135 assert @ecookbook.boards.any?
135 assert @ecookbook.boards.any?
136
136
137 @ecookbook.destroy
137 @ecookbook.destroy
138 # make sure that the project non longer exists
138 # make sure that the project non longer exists
139 assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) }
139 assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) }
140 # make sure related data was removed
140 # make sure related data was removed
141 assert Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
141 assert Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
142 assert Board.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
142 assert Board.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
143 end
143 end
144
144
145 def test_move_an_orphan_project_to_a_root_project
145 def test_move_an_orphan_project_to_a_root_project
146 sub = Project.find(2)
146 sub = Project.find(2)
147 sub.set_parent! @ecookbook
147 sub.set_parent! @ecookbook
148 assert_equal @ecookbook.id, sub.parent.id
148 assert_equal @ecookbook.id, sub.parent.id
149 @ecookbook.reload
149 @ecookbook.reload
150 assert_equal 4, @ecookbook.children.size
150 assert_equal 4, @ecookbook.children.size
151 end
151 end
152
152
153 def test_move_an_orphan_project_to_a_subproject
153 def test_move_an_orphan_project_to_a_subproject
154 sub = Project.find(2)
154 sub = Project.find(2)
155 assert sub.set_parent!(@ecookbook_sub1)
155 assert sub.set_parent!(@ecookbook_sub1)
156 end
156 end
157
157
158 def test_move_a_root_project_to_a_project
158 def test_move_a_root_project_to_a_project
159 sub = @ecookbook
159 sub = @ecookbook
160 assert sub.set_parent!(Project.find(2))
160 assert sub.set_parent!(Project.find(2))
161 end
161 end
162
162
163 def test_should_not_move_a_project_to_its_children
163 def test_should_not_move_a_project_to_its_children
164 sub = @ecookbook
164 sub = @ecookbook
165 assert !(sub.set_parent!(Project.find(3)))
165 assert !(sub.set_parent!(Project.find(3)))
166 end
166 end
167
167
168 def test_set_parent_should_add_roots_in_alphabetical_order
168 def test_set_parent_should_add_roots_in_alphabetical_order
169 ProjectCustomField.delete_all
169 ProjectCustomField.delete_all
170 Project.delete_all
170 Project.delete_all
171 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(nil)
171 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(nil)
172 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(nil)
172 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(nil)
173 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(nil)
173 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(nil)
174 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(nil)
174 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(nil)
175
175
176 assert_equal 4, Project.count
176 assert_equal 4, Project.count
177 assert_equal Project.all.sort_by(&:name), Project.all.sort_by(&:lft)
177 assert_equal Project.all.sort_by(&:name), Project.all.sort_by(&:lft)
178 end
178 end
179
179
180 def test_set_parent_should_add_children_in_alphabetical_order
180 def test_set_parent_should_add_children_in_alphabetical_order
181 ProjectCustomField.delete_all
181 ProjectCustomField.delete_all
182 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
182 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
183 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(parent)
183 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(parent)
184 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(parent)
184 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(parent)
185 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(parent)
185 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(parent)
186 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(parent)
186 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(parent)
187
187
188 parent.reload
188 parent.reload
189 assert_equal 4, parent.children.size
189 assert_equal 4, parent.children.size
190 assert_equal parent.children.sort_by(&:name), parent.children
190 assert_equal parent.children.sort_by(&:name), parent.children
191 end
191 end
192
192
193 def test_rebuild_should_sort_children_alphabetically
193 def test_rebuild_should_sort_children_alphabetically
194 ProjectCustomField.delete_all
194 ProjectCustomField.delete_all
195 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
195 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
196 Project.create!(:name => 'Project C', :identifier => 'project-c').move_to_child_of(parent)
196 Project.create!(:name => 'Project C', :identifier => 'project-c').move_to_child_of(parent)
197 Project.create!(:name => 'Project B', :identifier => 'project-b').move_to_child_of(parent)
197 Project.create!(:name => 'Project B', :identifier => 'project-b').move_to_child_of(parent)
198 Project.create!(:name => 'Project D', :identifier => 'project-d').move_to_child_of(parent)
198 Project.create!(:name => 'Project D', :identifier => 'project-d').move_to_child_of(parent)
199 Project.create!(:name => 'Project A', :identifier => 'project-a').move_to_child_of(parent)
199 Project.create!(:name => 'Project A', :identifier => 'project-a').move_to_child_of(parent)
200
200
201 Project.update_all("lft = NULL, rgt = NULL")
201 Project.update_all("lft = NULL, rgt = NULL")
202 Project.rebuild!
202 Project.rebuild!
203
203
204 parent.reload
204 parent.reload
205 assert_equal 4, parent.children.size
205 assert_equal 4, parent.children.size
206 assert_equal parent.children.sort_by(&:name), parent.children
206 assert_equal parent.children.sort_by(&:name), parent.children
207 end
207 end
208
208
209 def test_parent
209 def test_parent
210 p = Project.find(6).parent
210 p = Project.find(6).parent
211 assert p.is_a?(Project)
211 assert p.is_a?(Project)
212 assert_equal 5, p.id
212 assert_equal 5, p.id
213 end
213 end
214
214
215 def test_ancestors
215 def test_ancestors
216 a = Project.find(6).ancestors
216 a = Project.find(6).ancestors
217 assert a.first.is_a?(Project)
217 assert a.first.is_a?(Project)
218 assert_equal [1, 5], a.collect(&:id)
218 assert_equal [1, 5], a.collect(&:id)
219 end
219 end
220
220
221 def test_root
221 def test_root
222 r = Project.find(6).root
222 r = Project.find(6).root
223 assert r.is_a?(Project)
223 assert r.is_a?(Project)
224 assert_equal 1, r.id
224 assert_equal 1, r.id
225 end
225 end
226
226
227 def test_children
227 def test_children
228 c = Project.find(1).children
228 c = Project.find(1).children
229 assert c.first.is_a?(Project)
229 assert c.first.is_a?(Project)
230 assert_equal [5, 3, 4], c.collect(&:id)
230 assert_equal [5, 3, 4], c.collect(&:id)
231 end
231 end
232
232
233 def test_descendants
233 def test_descendants
234 d = Project.find(1).descendants
234 d = Project.find(1).descendants
235 assert d.first.is_a?(Project)
235 assert d.first.is_a?(Project)
236 assert_equal [5, 6, 3, 4], d.collect(&:id)
236 assert_equal [5, 6, 3, 4], d.collect(&:id)
237 end
237 end
238
238
239 def test_users_by_role
239 def test_users_by_role
240 users_by_role = Project.find(1).users_by_role
240 users_by_role = Project.find(1).users_by_role
241 assert_kind_of Hash, users_by_role
241 assert_kind_of Hash, users_by_role
242 role = Role.find(1)
242 role = Role.find(1)
243 assert_kind_of Array, users_by_role[role]
243 assert_kind_of Array, users_by_role[role]
244 assert users_by_role[role].include?(User.find(2))
244 assert users_by_role[role].include?(User.find(2))
245 end
245 end
246
246
247 def test_rolled_up_trackers
247 def test_rolled_up_trackers
248 parent = Project.find(1)
248 parent = Project.find(1)
249 parent.trackers = Tracker.find([1,2])
249 parent.trackers = Tracker.find([1,2])
250 child = parent.children.find(3)
250 child = parent.children.find(3)
251
251
252 assert_equal [1, 2], parent.tracker_ids
252 assert_equal [1, 2], parent.tracker_ids
253 assert_equal [2, 3], child.trackers.collect(&:id)
253 assert_equal [2, 3], child.trackers.collect(&:id)
254
254
255 assert_kind_of Tracker, parent.rolled_up_trackers.first
255 assert_kind_of Tracker, parent.rolled_up_trackers.first
256 assert_equal Tracker.find(1), parent.rolled_up_trackers.first
256 assert_equal Tracker.find(1), parent.rolled_up_trackers.first
257
257
258 assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
258 assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
259 assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
259 assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
260 end
260 end
261
261
262 def test_rolled_up_trackers_should_ignore_archived_subprojects
262 def test_rolled_up_trackers_should_ignore_archived_subprojects
263 parent = Project.find(1)
263 parent = Project.find(1)
264 parent.trackers = Tracker.find([1,2])
264 parent.trackers = Tracker.find([1,2])
265 child = parent.children.find(3)
265 child = parent.children.find(3)
266 child.trackers = Tracker.find([1,3])
266 child.trackers = Tracker.find([1,3])
267 parent.children.each(&:archive)
267 parent.children.each(&:archive)
268
268
269 assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
269 assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
270 end
270 end
271
271
272 def test_next_identifier
272 def test_next_identifier
273 ProjectCustomField.delete_all
273 ProjectCustomField.delete_all
274 Project.create!(:name => 'last', :identifier => 'p2008040')
274 Project.create!(:name => 'last', :identifier => 'p2008040')
275 assert_equal 'p2008041', Project.next_identifier
275 assert_equal 'p2008041', Project.next_identifier
276 end
276 end
277
277
278 def test_next_identifier_first_project
278 def test_next_identifier_first_project
279 Project.delete_all
279 Project.delete_all
280 assert_nil Project.next_identifier
280 assert_nil Project.next_identifier
281 end
281 end
282
282
283
283
284 def test_enabled_module_names_should_not_recreate_enabled_modules
284 def test_enabled_module_names_should_not_recreate_enabled_modules
285 project = Project.find(1)
285 project = Project.find(1)
286 # Remove one module
286 # Remove one module
287 modules = project.enabled_modules.slice(0..-2)
287 modules = project.enabled_modules.slice(0..-2)
288 assert modules.any?
288 assert modules.any?
289 assert_difference 'EnabledModule.count', -1 do
289 assert_difference 'EnabledModule.count', -1 do
290 project.enabled_module_names = modules.collect(&:name)
290 project.enabled_module_names = modules.collect(&:name)
291 end
291 end
292 project.reload
292 project.reload
293 # Ids should be preserved
293 # Ids should be preserved
294 assert_equal project.enabled_module_ids.sort, modules.collect(&:id).sort
294 assert_equal project.enabled_module_ids.sort, modules.collect(&:id).sort
295 end
295 end
296
296
297 def test_copy_from_existing_project
297 def test_copy_from_existing_project
298 source_project = Project.find(1)
298 source_project = Project.find(1)
299 copied_project = Project.copy_from(1)
299 copied_project = Project.copy_from(1)
300
300
301 assert copied_project
301 assert copied_project
302 # Cleared attributes
302 # Cleared attributes
303 assert copied_project.id.blank?
303 assert copied_project.id.blank?
304 assert copied_project.name.blank?
304 assert copied_project.name.blank?
305 assert copied_project.identifier.blank?
305 assert copied_project.identifier.blank?
306
306
307 # Duplicated attributes
307 # Duplicated attributes
308 assert_equal source_project.description, copied_project.description
308 assert_equal source_project.description, copied_project.description
309 assert_equal source_project.enabled_modules, copied_project.enabled_modules
309 assert_equal source_project.enabled_modules, copied_project.enabled_modules
310 assert_equal source_project.trackers, copied_project.trackers
310 assert_equal source_project.trackers, copied_project.trackers
311
311
312 # Default attributes
312 # Default attributes
313 assert_equal 1, copied_project.status
313 assert_equal 1, copied_project.status
314 end
314 end
315
315
316 def test_activities_should_use_the_system_activities
316 def test_activities_should_use_the_system_activities
317 project = Project.find(1)
317 project = Project.find(1)
318 assert_equal project.activities, TimeEntryActivity.find(:all, :conditions => {:active => true} )
318 assert_equal project.activities, TimeEntryActivity.find(:all, :conditions => {:active => true} )
319 end
319 end
320
320
321
321
322 def test_activities_should_use_the_project_specific_activities
322 def test_activities_should_use_the_project_specific_activities
323 project = Project.find(1)
323 project = Project.find(1)
324 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project})
324 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project})
325 assert overridden_activity.save!
325 assert overridden_activity.save!
326
326
327 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
327 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
328 end
328 end
329
329
330 def test_activities_should_not_include_the_inactive_project_specific_activities
330 def test_activities_should_not_include_the_inactive_project_specific_activities
331 project = Project.find(1)
331 project = Project.find(1)
332 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
332 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
333 assert overridden_activity.save!
333 assert overridden_activity.save!
334
334
335 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity found"
335 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity found"
336 end
336 end
337
337
338 def test_activities_should_not_include_project_specific_activities_from_other_projects
338 def test_activities_should_not_include_project_specific_activities_from_other_projects
339 project = Project.find(1)
339 project = Project.find(1)
340 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(2)})
340 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(2)})
341 assert overridden_activity.save!
341 assert overridden_activity.save!
342
342
343 assert !project.activities.include?(overridden_activity), "Project specific Activity found on a different project"
343 assert !project.activities.include?(overridden_activity), "Project specific Activity found on a different project"
344 end
344 end
345
345
346 def test_activities_should_handle_nils
346 def test_activities_should_handle_nils
347 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.find(:first)})
347 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.find(:first)})
348 TimeEntryActivity.delete_all
348 TimeEntryActivity.delete_all
349
349
350 # No activities
350 # No activities
351 project = Project.find(1)
351 project = Project.find(1)
352 assert project.activities.empty?
352 assert project.activities.empty?
353
353
354 # No system, one overridden
354 # No system, one overridden
355 assert overridden_activity.save!
355 assert overridden_activity.save!
356 project.reload
356 project.reload
357 assert_equal [overridden_activity], project.activities
357 assert_equal [overridden_activity], project.activities
358 end
358 end
359
359
360 def test_activities_should_override_system_activities_with_project_activities
360 def test_activities_should_override_system_activities_with_project_activities
361 project = Project.find(1)
361 project = Project.find(1)
362 parent_activity = TimeEntryActivity.find(:first)
362 parent_activity = TimeEntryActivity.find(:first)
363 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => parent_activity})
363 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => parent_activity})
364 assert overridden_activity.save!
364 assert overridden_activity.save!
365
365
366 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
366 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
367 assert !project.activities.include?(parent_activity), "System Activity found when it should have been overridden"
367 assert !project.activities.include?(parent_activity), "System Activity found when it should have been overridden"
368 end
368 end
369
369
370 def test_activities_should_include_inactive_activities_if_specified
370 def test_activities_should_include_inactive_activities_if_specified
371 project = Project.find(1)
371 project = Project.find(1)
372 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
372 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
373 assert overridden_activity.save!
373 assert overridden_activity.save!
374
374
375 assert project.activities(true).include?(overridden_activity), "Inactive Project specific Activity not found"
375 assert project.activities(true).include?(overridden_activity), "Inactive Project specific Activity not found"
376 end
376 end
377
378 def test_close_completed_versions
379 Version.update_all("status = 'open'")
380 project = Project.find(1)
381 assert_not_nil project.versions.detect {|v| v.completed? && v.status == 'open'}
382 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
383 project.close_completed_versions
384 project.reload
385 assert_nil project.versions.detect {|v| v.completed? && v.status != 'closed'}
386 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
387 end
377
388
378 context "Project#copy" do
389 context "Project#copy" do
379 setup do
390 setup do
380 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
391 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
381 Project.destroy_all :identifier => "copy-test"
392 Project.destroy_all :identifier => "copy-test"
382 @source_project = Project.find(2)
393 @source_project = Project.find(2)
383 @project = Project.new(:name => 'Copy Test', :identifier => 'copy-test')
394 @project = Project.new(:name => 'Copy Test', :identifier => 'copy-test')
384 @project.trackers = @source_project.trackers
395 @project.trackers = @source_project.trackers
385 @project.enabled_module_names = @source_project.enabled_modules.collect(&:name)
396 @project.enabled_module_names = @source_project.enabled_modules.collect(&:name)
386 end
397 end
387
398
388 should "copy issues" do
399 should "copy issues" do
389 assert @project.valid?
400 assert @project.valid?
390 assert @project.issues.empty?
401 assert @project.issues.empty?
391 assert @project.copy(@source_project)
402 assert @project.copy(@source_project)
392
403
393 assert_equal @source_project.issues.size, @project.issues.size
404 assert_equal @source_project.issues.size, @project.issues.size
394 @project.issues.each do |issue|
405 @project.issues.each do |issue|
395 assert issue.valid?
406 assert issue.valid?
396 assert ! issue.assigned_to.blank?
407 assert ! issue.assigned_to.blank?
397 assert_equal @project, issue.project
408 assert_equal @project, issue.project
398 end
409 end
399 end
410 end
400
411
401 should "change the new issues to use the copied version" do
412 should "change the new issues to use the copied version" do
402 assigned_version = Version.generate!(:name => "Assigned Issues")
413 assigned_version = Version.generate!(:name => "Assigned Issues")
403 @source_project.versions << assigned_version
414 @source_project.versions << assigned_version
404 assert_equal 1, @source_project.versions.size
415 assert_equal 1, @source_project.versions.size
405 @source_project.issues << Issue.generate!(:fixed_version_id => assigned_version.id,
416 @source_project.issues << Issue.generate!(:fixed_version_id => assigned_version.id,
406 :subject => "change the new issues to use the copied version",
417 :subject => "change the new issues to use the copied version",
407 :tracker_id => 1,
418 :tracker_id => 1,
408 :project_id => @source_project.id)
419 :project_id => @source_project.id)
409
420
410 assert @project.copy(@source_project)
421 assert @project.copy(@source_project)
411 @project.reload
422 @project.reload
412 copied_issue = @project.issues.first(:conditions => {:subject => "change the new issues to use the copied version"})
423 copied_issue = @project.issues.first(:conditions => {:subject => "change the new issues to use the copied version"})
413
424
414 assert copied_issue
425 assert copied_issue
415 assert copied_issue.fixed_version
426 assert copied_issue.fixed_version
416 assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
427 assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
417 assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record
428 assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record
418 end
429 end
419
430
420 should "copy members" do
431 should "copy members" do
421 assert @project.valid?
432 assert @project.valid?
422 assert @project.members.empty?
433 assert @project.members.empty?
423 assert @project.copy(@source_project)
434 assert @project.copy(@source_project)
424
435
425 assert_equal @source_project.members.size, @project.members.size
436 assert_equal @source_project.members.size, @project.members.size
426 @project.members.each do |member|
437 @project.members.each do |member|
427 assert member
438 assert member
428 assert_equal @project, member.project
439 assert_equal @project, member.project
429 end
440 end
430 end
441 end
431
442
432 should "copy project specific queries" do
443 should "copy project specific queries" do
433 assert @project.valid?
444 assert @project.valid?
434 assert @project.queries.empty?
445 assert @project.queries.empty?
435 assert @project.copy(@source_project)
446 assert @project.copy(@source_project)
436
447
437 assert_equal @source_project.queries.size, @project.queries.size
448 assert_equal @source_project.queries.size, @project.queries.size
438 @project.queries.each do |query|
449 @project.queries.each do |query|
439 assert query
450 assert query
440 assert_equal @project, query.project
451 assert_equal @project, query.project
441 end
452 end
442 end
453 end
443
454
444 should "copy versions" do
455 should "copy versions" do
445 @source_project.versions << Version.generate!
456 @source_project.versions << Version.generate!
446 @source_project.versions << Version.generate!
457 @source_project.versions << Version.generate!
447
458
448 assert @project.versions.empty?
459 assert @project.versions.empty?
449 assert @project.copy(@source_project)
460 assert @project.copy(@source_project)
450
461
451 assert_equal @source_project.versions.size, @project.versions.size
462 assert_equal @source_project.versions.size, @project.versions.size
452 @project.versions.each do |version|
463 @project.versions.each do |version|
453 assert version
464 assert version
454 assert_equal @project, version.project
465 assert_equal @project, version.project
455 end
466 end
456 end
467 end
457
468
458 should "copy wiki" do
469 should "copy wiki" do
459 assert_difference 'Wiki.count' do
470 assert_difference 'Wiki.count' do
460 assert @project.copy(@source_project)
471 assert @project.copy(@source_project)
461 end
472 end
462
473
463 assert @project.wiki
474 assert @project.wiki
464 assert_not_equal @source_project.wiki, @project.wiki
475 assert_not_equal @source_project.wiki, @project.wiki
465 assert_equal "Start page", @project.wiki.start_page
476 assert_equal "Start page", @project.wiki.start_page
466 end
477 end
467
478
468 should "copy wiki pages and content" do
479 should "copy wiki pages and content" do
469 assert @project.copy(@source_project)
480 assert @project.copy(@source_project)
470
481
471 assert @project.wiki
482 assert @project.wiki
472 assert_equal 1, @project.wiki.pages.length
483 assert_equal 1, @project.wiki.pages.length
473
484
474 @project.wiki.pages.each do |wiki_page|
485 @project.wiki.pages.each do |wiki_page|
475 assert wiki_page.content
486 assert wiki_page.content
476 assert !@source_project.wiki.pages.include?(wiki_page)
487 assert !@source_project.wiki.pages.include?(wiki_page)
477 end
488 end
478 end
489 end
479
490
480 should "copy custom fields"
491 should "copy custom fields"
481
492
482 should "copy issue categories" do
493 should "copy issue categories" do
483 assert @project.copy(@source_project)
494 assert @project.copy(@source_project)
484
495
485 assert_equal 2, @project.issue_categories.size
496 assert_equal 2, @project.issue_categories.size
486 @project.issue_categories.each do |issue_category|
497 @project.issue_categories.each do |issue_category|
487 assert !@source_project.issue_categories.include?(issue_category)
498 assert !@source_project.issue_categories.include?(issue_category)
488 end
499 end
489 end
500 end
490
501
491 should "copy boards" do
502 should "copy boards" do
492 assert @project.copy(@source_project)
503 assert @project.copy(@source_project)
493
504
494 assert_equal 1, @project.boards.size
505 assert_equal 1, @project.boards.size
495 @project.boards.each do |board|
506 @project.boards.each do |board|
496 assert !@source_project.boards.include?(board)
507 assert !@source_project.boards.include?(board)
497 end
508 end
498 end
509 end
499
510
500 should "change the new issues to use the copied issue categories" do
511 should "change the new issues to use the copied issue categories" do
501 issue = Issue.find(4)
512 issue = Issue.find(4)
502 issue.update_attribute(:category_id, 3)
513 issue.update_attribute(:category_id, 3)
503
514
504 assert @project.copy(@source_project)
515 assert @project.copy(@source_project)
505
516
506 @project.issues.each do |issue|
517 @project.issues.each do |issue|
507 assert issue.category
518 assert issue.category
508 assert_equal "Stock management", issue.category.name # Same name
519 assert_equal "Stock management", issue.category.name # Same name
509 assert_not_equal IssueCategory.find(3), issue.category # Different record
520 assert_not_equal IssueCategory.find(3), issue.category # Different record
510 end
521 end
511 end
522 end
512
523
513 should "limit copy with :only option" do
524 should "limit copy with :only option" do
514 assert @project.members.empty?
525 assert @project.members.empty?
515 assert @project.issue_categories.empty?
526 assert @project.issue_categories.empty?
516 assert @source_project.issues.any?
527 assert @source_project.issues.any?
517
528
518 assert @project.copy(@source_project, :only => ['members', 'issue_categories'])
529 assert @project.copy(@source_project, :only => ['members', 'issue_categories'])
519
530
520 assert @project.members.any?
531 assert @project.members.any?
521 assert @project.issue_categories.any?
532 assert @project.issue_categories.any?
522 assert @project.issues.empty?
533 assert @project.issues.empty?
523 end
534 end
524
535
525 should "copy issue relations"
536 should "copy issue relations"
526 should "link issue relations if cross project issue relations are valid"
537 should "link issue relations if cross project issue relations are valid"
527
538
528 end
539 end
529
540
530 end
541 end
General Comments 0
You need to be logged in to leave comments. Login now