##// END OF EJS Templates
add bulk edit and bulk update actions for time entries (#7996)....
Toshi MARUYAMA -
r5193:8a3151728875
parent child
Show More
@@ -1,268 +1,317
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2010 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class TimelogController < ApplicationController
19 19 menu_item :issues
20 20 before_filter :find_project, :only => [:new, :create]
21 21 before_filter :find_time_entry, :only => [:show, :edit, :update, :destroy]
22 before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update]
22 23 before_filter :authorize, :except => [:index]
23 24 before_filter :find_optional_project, :only => [:index]
24 25 accept_key_auth :index, :show, :create, :update, :destroy
25 26
26 27 helper :sort
27 28 include SortHelper
28 29 helper :issues
29 30 include TimelogHelper
30 31 helper :custom_fields
31 32 include CustomFieldsHelper
32 33
33 34 def index
34 35 sort_init 'spent_on', 'desc'
35 36 sort_update 'spent_on' => 'spent_on',
36 37 'user' => 'user_id',
37 38 'activity' => 'activity_id',
38 39 'project' => "#{Project.table_name}.name",
39 40 'issue' => 'issue_id',
40 41 'hours' => 'hours'
41 42
42 43 cond = ARCondition.new
43 44 if @issue
44 45 cond << "#{Issue.table_name}.root_id = #{@issue.root_id} AND #{Issue.table_name}.lft >= #{@issue.lft} AND #{Issue.table_name}.rgt <= #{@issue.rgt}"
45 46 elsif @project
46 47 cond << @project.project_condition(Setting.display_subprojects_issues?)
47 48 end
48 49
49 50 retrieve_date_range
50 51 cond << ['spent_on BETWEEN ? AND ?', @from, @to]
51 52
52 53 respond_to do |format|
53 54 format.html {
54 55 # Paginate results
55 56 @entry_count = TimeEntry.visible.count(:include => [:project, :issue], :conditions => cond.conditions)
56 57 @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
57 58 @entries = TimeEntry.visible.find(:all,
58 59 :include => [:project, :activity, :user, {:issue => :tracker}],
59 60 :conditions => cond.conditions,
60 61 :order => sort_clause,
61 62 :limit => @entry_pages.items_per_page,
62 63 :offset => @entry_pages.current.offset)
63 64 @total_hours = TimeEntry.visible.sum(:hours, :include => [:project, :issue], :conditions => cond.conditions).to_f
64 65
65 66 render :layout => !request.xhr?
66 67 }
67 68 format.api {
68 69 @entry_count = TimeEntry.visible.count(:include => [:project, :issue], :conditions => cond.conditions)
69 70 @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
70 71 @entries = TimeEntry.visible.find(:all,
71 72 :include => [:project, :activity, :user, {:issue => :tracker}],
72 73 :conditions => cond.conditions,
73 74 :order => sort_clause,
74 75 :limit => @entry_pages.items_per_page,
75 76 :offset => @entry_pages.current.offset)
76 77 }
77 78 format.atom {
78 79 entries = TimeEntry.visible.find(:all,
79 80 :include => [:project, :activity, :user, {:issue => :tracker}],
80 81 :conditions => cond.conditions,
81 82 :order => "#{TimeEntry.table_name}.created_on DESC",
82 83 :limit => Setting.feeds_limit.to_i)
83 84 render_feed(entries, :title => l(:label_spent_time))
84 85 }
85 86 format.csv {
86 87 # Export all entries
87 88 @entries = TimeEntry.visible.find(:all,
88 89 :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
89 90 :conditions => cond.conditions,
90 91 :order => sort_clause)
91 92 send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv')
92 93 }
93 94 end
94 95 end
95 96
96 97 def show
97 98 respond_to do |format|
98 99 # TODO: Implement html response
99 100 format.html { render :nothing => true, :status => 406 }
100 101 format.api
101 102 end
102 103 end
103 104
104 105 def new
105 106 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
106 107 @time_entry.attributes = params[:time_entry]
107 108
108 109 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
109 110 render :action => 'edit'
110 111 end
111 112
112 113 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
113 114 def create
114 115 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
115 116 @time_entry.attributes = params[:time_entry]
116 117
117 118 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
118 119
119 120 if @time_entry.save
120 121 respond_to do |format|
121 122 format.html {
122 123 flash[:notice] = l(:notice_successful_update)
123 124 redirect_back_or_default :action => 'index', :project_id => @time_entry.project
124 125 }
125 126 format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
126 127 end
127 128 else
128 129 respond_to do |format|
129 130 format.html { render :action => 'edit' }
130 131 format.api { render_validation_errors(@time_entry) }
131 132 end
132 133 end
133 134 end
134 135
135 136 def edit
136 137 @time_entry.attributes = params[:time_entry]
137 138
138 139 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
139 140 end
140 141
141 142 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
142 143 def update
143 144 @time_entry.attributes = params[:time_entry]
144 145
145 146 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
146 147
147 148 if @time_entry.save
148 149 respond_to do |format|
149 150 format.html {
150 151 flash[:notice] = l(:notice_successful_update)
151 152 redirect_back_or_default :action => 'index', :project_id => @time_entry.project
152 153 }
153 154 format.api { head :ok }
154 155 end
155 156 else
156 157 respond_to do |format|
157 158 format.html { render :action => 'edit' }
158 159 format.api { render_validation_errors(@time_entry) }
159 160 end
160 161 end
161 162 end
162 163
164 def bulk_edit
165 @available_activities = TimeEntryActivity.shared.active
166 @custom_fields = TimeEntry.first.available_custom_fields
167 end
168
169 def bulk_update
170 attributes = parse_params_for_bulk_time_entry_attributes(params)
171
172 unsaved_time_entry_ids = []
173 @time_entries.each do |time_entry|
174 time_entry.reload
175 time_entry.attributes = attributes
176 call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
177 unless time_entry.save
178 # Keep unsaved time_entry ids to display them in flash error
179 unsaved_time_entry_ids << time_entry.id
180 end
181 end
182 set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
183 redirect_back_or_default({:controller => 'timelog', :action => 'index', :project_id => @project})
184 end
185
163 186 verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
164 187 def destroy
165 188 if @time_entry.destroy && @time_entry.destroyed?
166 189 respond_to do |format|
167 190 format.html {
168 191 flash[:notice] = l(:notice_successful_delete)
169 192 redirect_to :back
170 193 }
171 194 format.api { head :ok }
172 195 end
173 196 else
174 197 respond_to do |format|
175 198 format.html {
176 199 flash[:error] = l(:notice_unable_delete_time_entry)
177 200 redirect_to :back
178 201 }
179 202 format.api { render_validation_errors(@time_entry) }
180 203 end
181 204 end
182 205 rescue ::ActionController::RedirectBackError
183 206 redirect_to :action => 'index', :project_id => @time_entry.project
184 207 end
185 208
186 209 private
187 210 def find_time_entry
188 211 @time_entry = TimeEntry.find(params[:id])
189 212 unless @time_entry.editable_by?(User.current)
190 213 render_403
191 214 return false
192 215 end
193 216 @project = @time_entry.project
194 217 rescue ActiveRecord::RecordNotFound
195 218 render_404
196 219 end
197 220
221 def find_time_entries
222 @time_entries = TimeEntry.find_all_by_id(params[:id] || params[:ids])
223 raise ActiveRecord::RecordNotFound if @time_entries.empty?
224 @projects = @time_entries.collect(&:project).compact.uniq
225 @project = @projects.first if @projects.size == 1
226 rescue ActiveRecord::RecordNotFound
227 render_404
228 end
229
230 def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids)
231 if unsaved_time_entry_ids.empty?
232 flash[:notice] = l(:notice_successful_update) unless time_entries.empty?
233 else
234 flash[:error] = l(:notice_failed_to_save_time_entries,
235 :count => unsaved_time_entry_ids.size,
236 :total => time_entries.size,
237 :ids => '#' + unsaved_time_entry_ids.join(', #'))
238 end
239 end
240
198 241 def find_project
199 242 if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present?
200 243 @issue = Issue.find(issue_id)
201 244 @project = @issue.project
202 245 elsif (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present?
203 246 @project = Project.find(project_id)
204 247 else
205 248 render_404
206 249 return false
207 250 end
208 251 rescue ActiveRecord::RecordNotFound
209 252 render_404
210 253 end
211 254
212 255 def find_optional_project
213 256 if !params[:issue_id].blank?
214 257 @issue = Issue.find(params[:issue_id])
215 258 @project = @issue.project
216 259 elsif !params[:project_id].blank?
217 260 @project = Project.find(params[:project_id])
218 261 end
219 262 deny_access unless User.current.allowed_to?(:view_time_entries, @project, :global => true)
220 263 end
221 264
222 265 # Retrieves the date range based on predefined ranges or specific from/to param dates
223 266 def retrieve_date_range
224 267 @free_period = false
225 268 @from, @to = nil, nil
226 269
227 270 if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
228 271 case params[:period].to_s
229 272 when 'today'
230 273 @from = @to = Date.today
231 274 when 'yesterday'
232 275 @from = @to = Date.today - 1
233 276 when 'current_week'
234 277 @from = Date.today - (Date.today.cwday - 1)%7
235 278 @to = @from + 6
236 279 when 'last_week'
237 280 @from = Date.today - 7 - (Date.today.cwday - 1)%7
238 281 @to = @from + 6
239 282 when '7_days'
240 283 @from = Date.today - 7
241 284 @to = Date.today
242 285 when 'current_month'
243 286 @from = Date.civil(Date.today.year, Date.today.month, 1)
244 287 @to = (@from >> 1) - 1
245 288 when 'last_month'
246 289 @from = Date.civil(Date.today.year, Date.today.month, 1) << 1
247 290 @to = (@from >> 1) - 1
248 291 when '30_days'
249 292 @from = Date.today - 30
250 293 @to = Date.today
251 294 when 'current_year'
252 295 @from = Date.civil(Date.today.year, 1, 1)
253 296 @to = Date.civil(Date.today.year, 12, 31)
254 297 end
255 298 elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
256 299 begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
257 300 begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
258 301 @free_period = true
259 302 else
260 303 # default
261 304 end
262 305
263 306 @from, @to = @to, @from if @from && @to && @from > @to
264 307 @from ||= (TimeEntry.earilest_date_for_project(@project) || Date.today)
265 308 @to ||= (TimeEntry.latest_date_for_project(@project) || Date.today)
266 309 end
267 310
311 def parse_params_for_bulk_time_entry_attributes(params)
312 attributes = (params[:time_entry] || {}).reject {|k,v| v.blank?}
313 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
314 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
315 attributes
316 end
268 317 end
@@ -1,839 +1,845
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Project < ActiveRecord::Base
19 19 include Redmine::SafeAttributes
20 20
21 21 # Project statuses
22 22 STATUS_ACTIVE = 1
23 23 STATUS_ARCHIVED = 9
24 24
25 25 # Maximum length for project identifiers
26 26 IDENTIFIER_MAX_LENGTH = 100
27 27
28 28 # Specific overidden Activities
29 29 has_many :time_entry_activities
30 30 has_many :members, :include => [:user, :roles], :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}"
31 31 has_many :memberships, :class_name => 'Member'
32 32 has_many :member_principals, :class_name => 'Member',
33 33 :include => :principal,
34 34 :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{User::STATUS_ACTIVE})"
35 35 has_many :users, :through => :members
36 36 has_many :principals, :through => :member_principals, :source => :principal
37 37
38 38 has_many :enabled_modules, :dependent => :delete_all
39 39 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
40 40 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
41 41 has_many :issue_changes, :through => :issues, :source => :journals
42 42 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
43 43 has_many :time_entries, :dependent => :delete_all
44 44 has_many :queries, :dependent => :delete_all
45 45 has_many :documents, :dependent => :destroy
46 46 has_many :news, :dependent => :destroy, :include => :author
47 47 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
48 48 has_many :boards, :dependent => :destroy, :order => "position ASC"
49 49 has_one :repository, :dependent => :destroy
50 50 has_many :changesets, :through => :repository
51 51 has_one :wiki, :dependent => :destroy
52 52 # Custom field for the project issues
53 53 has_and_belongs_to_many :issue_custom_fields,
54 54 :class_name => 'IssueCustomField',
55 55 :order => "#{CustomField.table_name}.position",
56 56 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
57 57 :association_foreign_key => 'custom_field_id'
58 58
59 59 acts_as_nested_set :order => 'name', :dependent => :destroy
60 60 acts_as_attachable :view_permission => :view_files,
61 61 :delete_permission => :manage_files
62 62
63 63 acts_as_customizable
64 64 acts_as_searchable :columns => ['name', 'identifier', 'description'], :project_key => 'id', :permission => nil
65 65 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
66 66 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o}},
67 67 :author => nil
68 68
69 69 attr_protected :status
70 70
71 71 validates_presence_of :name, :identifier
72 72 validates_uniqueness_of :identifier
73 73 validates_associated :repository, :wiki
74 74 validates_length_of :name, :maximum => 255
75 75 validates_length_of :homepage, :maximum => 255
76 76 validates_length_of :identifier, :in => 1..IDENTIFIER_MAX_LENGTH
77 77 # donwcase letters, digits, dashes but not digits only
78 78 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :if => Proc.new { |p| p.identifier_changed? }
79 79 # reserved words
80 80 validates_exclusion_of :identifier, :in => %w( new )
81 81
82 82 before_destroy :delete_all_members
83 83
84 84 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] } }
85 85 named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
86 86 named_scope :all_public, { :conditions => { :is_public => true } }
87 87 named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
88 88
89 89 def initialize(attributes = nil)
90 90 super
91 91
92 92 initialized = (attributes || {}).stringify_keys
93 93 if !initialized.key?('identifier') && Setting.sequential_project_identifiers?
94 94 self.identifier = Project.next_identifier
95 95 end
96 96 if !initialized.key?('is_public')
97 97 self.is_public = Setting.default_projects_public?
98 98 end
99 99 if !initialized.key?('enabled_module_names')
100 100 self.enabled_module_names = Setting.default_projects_modules
101 101 end
102 102 if !initialized.key?('trackers') && !initialized.key?('tracker_ids')
103 103 self.trackers = Tracker.all
104 104 end
105 105 end
106 106
107 107 def identifier=(identifier)
108 108 super unless identifier_frozen?
109 109 end
110 110
111 111 def identifier_frozen?
112 112 errors[:identifier].nil? && !(new_record? || identifier.blank?)
113 113 end
114 114
115 115 # returns latest created projects
116 116 # non public projects will be returned only if user is a member of those
117 117 def self.latest(user=nil, count=5)
118 118 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
119 119 end
120 120
121 121 # Returns a SQL :conditions string used to find all active projects for the specified user.
122 122 #
123 123 # Examples:
124 124 # Projects.visible_by(admin) => "projects.status = 1"
125 125 # Projects.visible_by(normal_user) => "projects.status = 1 AND projects.is_public = 1"
126 126 def self.visible_by(user=nil)
127 127 user ||= User.current
128 128 if user && user.admin?
129 129 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
130 130 elsif user && user.memberships.any?
131 131 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(',')}))"
132 132 else
133 133 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
134 134 end
135 135 end
136 136
137 137 def self.allowed_to_condition(user, permission, options={})
138 138 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
139 139 if perm = Redmine::AccessControl.permission(permission)
140 140 unless perm.project_module.nil?
141 141 # If the permission belongs to a project module, make sure the module is enabled
142 142 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
143 143 end
144 144 end
145 145 if options[:project]
146 146 project_statement = "#{Project.table_name}.id = #{options[:project].id}"
147 147 project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects]
148 148 base_statement = "(#{project_statement}) AND (#{base_statement})"
149 149 end
150 150
151 151 if user.admin?
152 152 base_statement
153 153 else
154 154 statement_by_role = {}
155 155 if user.logged?
156 156 if Role.non_member.allowed_to?(permission) && !options[:member]
157 157 statement_by_role[Role.non_member] = "#{Project.table_name}.is_public = #{connection.quoted_true}"
158 158 end
159 159 user.projects_by_role.each do |role, projects|
160 160 if role.allowed_to?(permission)
161 161 statement_by_role[role] = "#{Project.table_name}.id IN (#{projects.collect(&:id).join(',')})"
162 162 end
163 163 end
164 164 else
165 165 if Role.anonymous.allowed_to?(permission) && !options[:member]
166 166 statement_by_role[Role.anonymous] = "#{Project.table_name}.is_public = #{connection.quoted_true}"
167 167 end
168 168 end
169 169 if statement_by_role.empty?
170 170 "1=0"
171 171 else
172 172 "((#{base_statement}) AND (#{statement_by_role.values.join(' OR ')}))"
173 173 end
174 174 end
175 175 end
176 176
177 177 # Returns the Systemwide and project specific activities
178 178 def activities(include_inactive=false)
179 179 if include_inactive
180 180 return all_activities
181 181 else
182 182 return active_activities
183 183 end
184 184 end
185 185
186 186 # Will create a new Project specific Activity or update an existing one
187 187 #
188 188 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
189 189 # does not successfully save.
190 190 def update_or_create_time_entry_activity(id, activity_hash)
191 191 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
192 192 self.create_time_entry_activity_if_needed(activity_hash)
193 193 else
194 194 activity = project.time_entry_activities.find_by_id(id.to_i)
195 195 activity.update_attributes(activity_hash) if activity
196 196 end
197 197 end
198 198
199 199 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
200 200 #
201 201 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
202 202 # does not successfully save.
203 203 def create_time_entry_activity_if_needed(activity)
204 204 if activity['parent_id']
205 205
206 206 parent_activity = TimeEntryActivity.find(activity['parent_id'])
207 207 activity['name'] = parent_activity.name
208 208 activity['position'] = parent_activity.position
209 209
210 210 if Enumeration.overridding_change?(activity, parent_activity)
211 211 project_activity = self.time_entry_activities.create(activity)
212 212
213 213 if project_activity.new_record?
214 214 raise ActiveRecord::Rollback, "Overridding TimeEntryActivity was not successfully saved"
215 215 else
216 216 self.time_entries.update_all("activity_id = #{project_activity.id}", ["activity_id = ?", parent_activity.id])
217 217 end
218 218 end
219 219 end
220 220 end
221 221
222 222 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
223 223 #
224 224 # Examples:
225 225 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
226 226 # project.project_condition(false) => "projects.id = 1"
227 227 def project_condition(with_subprojects)
228 228 cond = "#{Project.table_name}.id = #{id}"
229 229 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
230 230 cond
231 231 end
232 232
233 233 def self.find(*args)
234 234 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
235 235 project = find_by_identifier(*args)
236 236 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
237 237 project
238 238 else
239 239 super
240 240 end
241 241 end
242 242
243 243 def to_param
244 244 # id is used for projects with a numeric identifier (compatibility)
245 245 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
246 246 end
247 247
248 248 def active?
249 249 self.status == STATUS_ACTIVE
250 250 end
251 251
252 252 def archived?
253 253 self.status == STATUS_ARCHIVED
254 254 end
255 255
256 256 # Archives the project and its descendants
257 257 def archive
258 258 # Check that there is no issue of a non descendant project that is assigned
259 259 # to one of the project or descendant versions
260 260 v_ids = self_and_descendants.collect {|p| p.version_ids}.flatten
261 261 if v_ids.any? && Issue.find(:first, :include => :project,
262 262 :conditions => ["(#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?)" +
263 263 " AND #{Issue.table_name}.fixed_version_id IN (?)", lft, rgt, v_ids])
264 264 return false
265 265 end
266 266 Project.transaction do
267 267 archive!
268 268 end
269 269 true
270 270 end
271 271
272 272 # Unarchives the project
273 273 # All its ancestors must be active
274 274 def unarchive
275 275 return false if ancestors.detect {|a| !a.active?}
276 276 update_attribute :status, STATUS_ACTIVE
277 277 end
278 278
279 279 # Returns an array of projects the project can be moved to
280 280 # by the current user
281 281 def allowed_parents
282 282 return @allowed_parents if @allowed_parents
283 283 @allowed_parents = Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_subprojects))
284 284 @allowed_parents = @allowed_parents - self_and_descendants
285 285 if User.current.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?)
286 286 @allowed_parents << nil
287 287 end
288 288 unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
289 289 @allowed_parents << parent
290 290 end
291 291 @allowed_parents
292 292 end
293 293
294 294 # Sets the parent of the project with authorization check
295 295 def set_allowed_parent!(p)
296 296 unless p.nil? || p.is_a?(Project)
297 297 if p.to_s.blank?
298 298 p = nil
299 299 else
300 300 p = Project.find_by_id(p)
301 301 return false unless p
302 302 end
303 303 end
304 304 if p.nil?
305 305 if !new_record? && allowed_parents.empty?
306 306 return false
307 307 end
308 308 elsif !allowed_parents.include?(p)
309 309 return false
310 310 end
311 311 set_parent!(p)
312 312 end
313 313
314 314 # Sets the parent of the project
315 315 # Argument can be either a Project, a String, a Fixnum or nil
316 316 def set_parent!(p)
317 317 unless p.nil? || p.is_a?(Project)
318 318 if p.to_s.blank?
319 319 p = nil
320 320 else
321 321 p = Project.find_by_id(p)
322 322 return false unless p
323 323 end
324 324 end
325 325 if p == parent && !p.nil?
326 326 # Nothing to do
327 327 true
328 328 elsif p.nil? || (p.active? && move_possible?(p))
329 329 # Insert the project so that target's children or root projects stay alphabetically sorted
330 330 sibs = (p.nil? ? self.class.roots : p.children)
331 331 to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
332 332 if to_be_inserted_before
333 333 move_to_left_of(to_be_inserted_before)
334 334 elsif p.nil?
335 335 if sibs.empty?
336 336 # move_to_root adds the project in first (ie. left) position
337 337 move_to_root
338 338 else
339 339 move_to_right_of(sibs.last) unless self == sibs.last
340 340 end
341 341 else
342 342 # move_to_child_of adds the project in last (ie.right) position
343 343 move_to_child_of(p)
344 344 end
345 345 Issue.update_versions_from_hierarchy_change(self)
346 346 true
347 347 else
348 348 # Can not move to the given target
349 349 false
350 350 end
351 351 end
352 352
353 353 # Returns an array of the trackers used by the project and its active sub projects
354 354 def rolled_up_trackers
355 355 @rolled_up_trackers ||=
356 356 Tracker.find(:all, :joins => :projects,
357 357 :select => "DISTINCT #{Tracker.table_name}.*",
358 358 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
359 359 :order => "#{Tracker.table_name}.position")
360 360 end
361 361
362 362 # Closes open and locked project versions that are completed
363 363 def close_completed_versions
364 364 Version.transaction do
365 365 versions.find(:all, :conditions => {:status => %w(open locked)}).each do |version|
366 366 if version.completed?
367 367 version.update_attribute(:status, 'closed')
368 368 end
369 369 end
370 370 end
371 371 end
372 372
373 373 # Returns a scope of the Versions on subprojects
374 374 def rolled_up_versions
375 375 @rolled_up_versions ||=
376 376 Version.scoped(:include => :project,
377 377 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt])
378 378 end
379 379
380 380 # Returns a scope of the Versions used by the project
381 381 def shared_versions
382 382 @shared_versions ||= begin
383 383 r = root? ? self : root
384 384 Version.scoped(:include => :project,
385 385 :conditions => "#{Project.table_name}.id = #{id}" +
386 386 " OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" +
387 387 " #{Version.table_name}.sharing = 'system'" +
388 388 " OR (#{Project.table_name}.lft >= #{r.lft} AND #{Project.table_name}.rgt <= #{r.rgt} AND #{Version.table_name}.sharing = 'tree')" +
389 389 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
390 390 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
391 391 "))")
392 392 end
393 393 end
394 394
395 395 # Returns a hash of project users grouped by role
396 396 def users_by_role
397 397 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m|
398 398 m.roles.each do |r|
399 399 h[r] ||= []
400 400 h[r] << m.user
401 401 end
402 402 h
403 403 end
404 404 end
405 405
406 406 # Deletes all project's members
407 407 def delete_all_members
408 408 me, mr = Member.table_name, MemberRole.table_name
409 409 connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
410 410 Member.delete_all(['project_id = ?', id])
411 411 end
412 412
413 413 # Users issues can be assigned to
414 414 def assignable_users
415 415 members.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.user}.sort
416 416 end
417 417
418 418 # Returns the mail adresses of users that should be always notified on project events
419 419 def recipients
420 420 notified_users.collect {|user| user.mail}
421 421 end
422 422
423 423 # Returns the users that should be notified on project events
424 424 def notified_users
425 425 # TODO: User part should be extracted to User#notify_about?
426 426 members.select {|m| m.mail_notification? || m.user.mail_notification == 'all'}.collect {|m| m.user}
427 427 end
428 428
429 429 # Returns an array of all custom fields enabled for project issues
430 430 # (explictly associated custom fields and custom fields enabled for all projects)
431 431 def all_issue_custom_fields
432 432 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
433 433 end
434 434
435 # Returns an array of all custom fields enabled for project time entries
436 # (explictly associated custom fields and custom fields enabled for all projects)
437 def all_time_entry_custom_fields
438 @all_time_entry_custom_fields ||= (TimeEntryCustomField.for_all + time_entry_custom_fields).uniq.sort
439 end
440
435 441 def project
436 442 self
437 443 end
438 444
439 445 def <=>(project)
440 446 name.downcase <=> project.name.downcase
441 447 end
442 448
443 449 def to_s
444 450 name
445 451 end
446 452
447 453 # Returns a short description of the projects (first lines)
448 454 def short_description(length = 255)
449 455 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
450 456 end
451 457
452 458 def css_classes
453 459 s = 'project'
454 460 s << ' root' if root?
455 461 s << ' child' if child?
456 462 s << (leaf? ? ' leaf' : ' parent')
457 463 s
458 464 end
459 465
460 466 # The earliest start date of a project, based on it's issues and versions
461 467 def start_date
462 468 [
463 469 issues.minimum('start_date'),
464 470 shared_versions.collect(&:effective_date),
465 471 shared_versions.collect(&:start_date)
466 472 ].flatten.compact.min
467 473 end
468 474
469 475 # The latest due date of an issue or version
470 476 def due_date
471 477 [
472 478 issues.maximum('due_date'),
473 479 shared_versions.collect(&:effective_date),
474 480 shared_versions.collect {|v| v.fixed_issues.maximum('due_date')}
475 481 ].flatten.compact.max
476 482 end
477 483
478 484 def overdue?
479 485 active? && !due_date.nil? && (due_date < Date.today)
480 486 end
481 487
482 488 # Returns the percent completed for this project, based on the
483 489 # progress on it's versions.
484 490 def completed_percent(options={:include_subprojects => false})
485 491 if options.delete(:include_subprojects)
486 492 total = self_and_descendants.collect(&:completed_percent).sum
487 493
488 494 total / self_and_descendants.count
489 495 else
490 496 if versions.count > 0
491 497 total = versions.collect(&:completed_pourcent).sum
492 498
493 499 total / versions.count
494 500 else
495 501 100
496 502 end
497 503 end
498 504 end
499 505
500 506 # Return true if this project is allowed to do the specified action.
501 507 # action can be:
502 508 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
503 509 # * a permission Symbol (eg. :edit_project)
504 510 def allows_to?(action)
505 511 if action.is_a? Hash
506 512 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
507 513 else
508 514 allowed_permissions.include? action
509 515 end
510 516 end
511 517
512 518 def module_enabled?(module_name)
513 519 module_name = module_name.to_s
514 520 enabled_modules.detect {|m| m.name == module_name}
515 521 end
516 522
517 523 def enabled_module_names=(module_names)
518 524 if module_names && module_names.is_a?(Array)
519 525 module_names = module_names.collect(&:to_s).reject(&:blank?)
520 526 self.enabled_modules = module_names.collect {|name| enabled_modules.detect {|mod| mod.name == name} || EnabledModule.new(:name => name)}
521 527 else
522 528 enabled_modules.clear
523 529 end
524 530 end
525 531
526 532 # Returns an array of the enabled modules names
527 533 def enabled_module_names
528 534 enabled_modules.collect(&:name)
529 535 end
530 536
531 537 safe_attributes 'name',
532 538 'description',
533 539 'homepage',
534 540 'is_public',
535 541 'identifier',
536 542 'custom_field_values',
537 543 'custom_fields',
538 544 'tracker_ids',
539 545 'issue_custom_field_ids'
540 546
541 547 safe_attributes 'enabled_module_names',
542 548 :if => lambda {|project, user| project.new_record? || user.allowed_to?(:select_project_modules, project) }
543 549
544 550 # Returns an array of projects that are in this project's hierarchy
545 551 #
546 552 # Example: parents, children, siblings
547 553 def hierarchy
548 554 parents = project.self_and_ancestors || []
549 555 descendants = project.descendants || []
550 556 project_hierarchy = parents | descendants # Set union
551 557 end
552 558
553 559 # Returns an auto-generated project identifier based on the last identifier used
554 560 def self.next_identifier
555 561 p = Project.find(:first, :order => 'created_on DESC')
556 562 p.nil? ? nil : p.identifier.to_s.succ
557 563 end
558 564
559 565 # Copies and saves the Project instance based on the +project+.
560 566 # Duplicates the source project's:
561 567 # * Wiki
562 568 # * Versions
563 569 # * Categories
564 570 # * Issues
565 571 # * Members
566 572 # * Queries
567 573 #
568 574 # Accepts an +options+ argument to specify what to copy
569 575 #
570 576 # Examples:
571 577 # project.copy(1) # => copies everything
572 578 # project.copy(1, :only => 'members') # => copies members only
573 579 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
574 580 def copy(project, options={})
575 581 project = project.is_a?(Project) ? project : Project.find(project)
576 582
577 583 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
578 584 to_be_copied = to_be_copied & options[:only].to_a unless options[:only].nil?
579 585
580 586 Project.transaction do
581 587 if save
582 588 reload
583 589 to_be_copied.each do |name|
584 590 send "copy_#{name}", project
585 591 end
586 592 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
587 593 save
588 594 end
589 595 end
590 596 end
591 597
592 598
593 599 # Copies +project+ and returns the new instance. This will not save
594 600 # the copy
595 601 def self.copy_from(project)
596 602 begin
597 603 project = project.is_a?(Project) ? project : Project.find(project)
598 604 if project
599 605 # clear unique attributes
600 606 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
601 607 copy = Project.new(attributes)
602 608 copy.enabled_modules = project.enabled_modules
603 609 copy.trackers = project.trackers
604 610 copy.custom_values = project.custom_values.collect {|v| v.clone}
605 611 copy.issue_custom_fields = project.issue_custom_fields
606 612 return copy
607 613 else
608 614 return nil
609 615 end
610 616 rescue ActiveRecord::RecordNotFound
611 617 return nil
612 618 end
613 619 end
614 620
615 621 # Yields the given block for each project with its level in the tree
616 622 def self.project_tree(projects, &block)
617 623 ancestors = []
618 624 projects.sort_by(&:lft).each do |project|
619 625 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
620 626 ancestors.pop
621 627 end
622 628 yield project, ancestors.size
623 629 ancestors << project
624 630 end
625 631 end
626 632
627 633 private
628 634
629 635 # Copies wiki from +project+
630 636 def copy_wiki(project)
631 637 # Check that the source project has a wiki first
632 638 unless project.wiki.nil?
633 639 self.wiki ||= Wiki.new
634 640 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
635 641 wiki_pages_map = {}
636 642 project.wiki.pages.each do |page|
637 643 # Skip pages without content
638 644 next if page.content.nil?
639 645 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
640 646 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
641 647 new_wiki_page.content = new_wiki_content
642 648 wiki.pages << new_wiki_page
643 649 wiki_pages_map[page.id] = new_wiki_page
644 650 end
645 651 wiki.save
646 652 # Reproduce page hierarchy
647 653 project.wiki.pages.each do |page|
648 654 if page.parent_id && wiki_pages_map[page.id]
649 655 wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id]
650 656 wiki_pages_map[page.id].save
651 657 end
652 658 end
653 659 end
654 660 end
655 661
656 662 # Copies versions from +project+
657 663 def copy_versions(project)
658 664 project.versions.each do |version|
659 665 new_version = Version.new
660 666 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
661 667 self.versions << new_version
662 668 end
663 669 end
664 670
665 671 # Copies issue categories from +project+
666 672 def copy_issue_categories(project)
667 673 project.issue_categories.each do |issue_category|
668 674 new_issue_category = IssueCategory.new
669 675 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
670 676 self.issue_categories << new_issue_category
671 677 end
672 678 end
673 679
674 680 # Copies issues from +project+
675 681 def copy_issues(project)
676 682 # Stores the source issue id as a key and the copied issues as the
677 683 # value. Used to map the two togeather for issue relations.
678 684 issues_map = {}
679 685
680 686 # Get issues sorted by root_id, lft so that parent issues
681 687 # get copied before their children
682 688 project.issues.find(:all, :order => 'root_id, lft').each do |issue|
683 689 new_issue = Issue.new
684 690 new_issue.copy_from(issue)
685 691 new_issue.project = self
686 692 # Reassign fixed_versions by name, since names are unique per
687 693 # project and the versions for self are not yet saved
688 694 if issue.fixed_version
689 695 new_issue.fixed_version = self.versions.select {|v| v.name == issue.fixed_version.name}.first
690 696 end
691 697 # Reassign the category by name, since names are unique per
692 698 # project and the categories for self are not yet saved
693 699 if issue.category
694 700 new_issue.category = self.issue_categories.select {|c| c.name == issue.category.name}.first
695 701 end
696 702 # Parent issue
697 703 if issue.parent_id
698 704 if copied_parent = issues_map[issue.parent_id]
699 705 new_issue.parent_issue_id = copied_parent.id
700 706 end
701 707 end
702 708
703 709 self.issues << new_issue
704 710 if new_issue.new_record?
705 711 logger.info "Project#copy_issues: issue ##{issue.id} could not be copied: #{new_issue.errors.full_messages}" if logger && logger.info
706 712 else
707 713 issues_map[issue.id] = new_issue unless new_issue.new_record?
708 714 end
709 715 end
710 716
711 717 # Relations after in case issues related each other
712 718 project.issues.each do |issue|
713 719 new_issue = issues_map[issue.id]
714 720 unless new_issue
715 721 # Issue was not copied
716 722 next
717 723 end
718 724
719 725 # Relations
720 726 issue.relations_from.each do |source_relation|
721 727 new_issue_relation = IssueRelation.new
722 728 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
723 729 new_issue_relation.issue_to = issues_map[source_relation.issue_to_id]
724 730 if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
725 731 new_issue_relation.issue_to = source_relation.issue_to
726 732 end
727 733 new_issue.relations_from << new_issue_relation
728 734 end
729 735
730 736 issue.relations_to.each do |source_relation|
731 737 new_issue_relation = IssueRelation.new
732 738 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
733 739 new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
734 740 if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
735 741 new_issue_relation.issue_from = source_relation.issue_from
736 742 end
737 743 new_issue.relations_to << new_issue_relation
738 744 end
739 745 end
740 746 end
741 747
742 748 # Copies members from +project+
743 749 def copy_members(project)
744 750 # Copy users first, then groups to handle members with inherited and given roles
745 751 members_to_copy = []
746 752 members_to_copy += project.memberships.select {|m| m.principal.is_a?(User)}
747 753 members_to_copy += project.memberships.select {|m| !m.principal.is_a?(User)}
748 754
749 755 members_to_copy.each do |member|
750 756 new_member = Member.new
751 757 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
752 758 # only copy non inherited roles
753 759 # inherited roles will be added when copying the group membership
754 760 role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)
755 761 next if role_ids.empty?
756 762 new_member.role_ids = role_ids
757 763 new_member.project = self
758 764 self.members << new_member
759 765 end
760 766 end
761 767
762 768 # Copies queries from +project+
763 769 def copy_queries(project)
764 770 project.queries.each do |query|
765 771 new_query = Query.new
766 772 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria")
767 773 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
768 774 new_query.project = self
769 775 self.queries << new_query
770 776 end
771 777 end
772 778
773 779 # Copies boards from +project+
774 780 def copy_boards(project)
775 781 project.boards.each do |board|
776 782 new_board = Board.new
777 783 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
778 784 new_board.project = self
779 785 self.boards << new_board
780 786 end
781 787 end
782 788
783 789 def allowed_permissions
784 790 @allowed_permissions ||= begin
785 791 module_names = enabled_modules.all(:select => :name).collect {|m| m.name}
786 792 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
787 793 end
788 794 end
789 795
790 796 def allowed_actions
791 797 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
792 798 end
793 799
794 800 # Returns all the active Systemwide and project specific activities
795 801 def active_activities
796 802 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
797 803
798 804 if overridden_activity_ids.empty?
799 805 return TimeEntryActivity.shared.active
800 806 else
801 807 return system_activities_and_project_overrides
802 808 end
803 809 end
804 810
805 811 # Returns all the Systemwide and project specific activities
806 812 # (inactive and active)
807 813 def all_activities
808 814 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
809 815
810 816 if overridden_activity_ids.empty?
811 817 return TimeEntryActivity.shared
812 818 else
813 819 return system_activities_and_project_overrides(true)
814 820 end
815 821 end
816 822
817 823 # Returns the systemwide active activities merged with the project specific overrides
818 824 def system_activities_and_project_overrides(include_inactive=false)
819 825 if include_inactive
820 826 return TimeEntryActivity.shared.
821 827 find(:all,
822 828 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
823 829 self.time_entry_activities
824 830 else
825 831 return TimeEntryActivity.shared.active.
826 832 find(:all,
827 833 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
828 834 self.time_entry_activities.active
829 835 end
830 836 end
831 837
832 838 # Archives subprojects recursively
833 839 def archive!
834 840 children.each do |subproject|
835 841 subproject.send :archive!
836 842 end
837 843 update_attribute :status, STATUS_ARCHIVED
838 844 end
839 845 end
@@ -1,253 +1,260
1 1 ActionController::Routing::Routes.draw do |map|
2 2 # Add your own custom routes here.
3 3 # The priority is based upon order of creation: first created -> highest priority.
4 4
5 5 # Here's a sample route:
6 6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
7 7 # Keep in mind you can assign values other than :controller and :action
8 8
9 9 map.home '', :controller => 'welcome'
10 10
11 11 map.signin 'login', :controller => 'account', :action => 'login'
12 12 map.signout 'logout', :controller => 'account', :action => 'logout'
13 13
14 14 map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
15 15 map.connect 'help/:ctrl/:page', :controller => 'help'
16 16
17 17 map.with_options :controller => 'time_entry_reports', :action => 'report',:conditions => {:method => :get} do |time_report|
18 18 time_report.connect 'projects/:project_id/issues/:issue_id/time_entries/report'
19 19 time_report.connect 'projects/:project_id/issues/:issue_id/time_entries/report.:format'
20 20 time_report.connect 'projects/:project_id/time_entries/report'
21 21 time_report.connect 'projects/:project_id/time_entries/report.:format'
22 22 time_report.connect 'time_entries/report'
23 23 time_report.connect 'time_entries/report.:format'
24 24 end
25 25
26 map.bulk_edit_time_entry 'time_entries/bulk_edit',
27 :controller => 'timelog', :action => 'bulk_edit', :conditions => { :method => :get }
28 map.bulk_update_time_entry 'time_entries/bulk_edit',
29 :controller => 'timelog', :action => 'bulk_update', :conditions => { :method => :post }
30 map.time_entries_context_menu '/time_entries/context_menu',
31 :controller => 'context_menus', :action => 'time_entries'
32 # TODO: wasteful since this is also nested under issues, projects, and projects/issues
26 33 map.resources :time_entries, :controller => 'timelog'
27 34
28 35 map.connect 'projects/:id/wiki', :controller => 'wikis', :action => 'edit', :conditions => {:method => :post}
29 36 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :get}
30 37 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :post}
31 38
32 39 map.with_options :controller => 'messages' do |messages_routes|
33 40 messages_routes.with_options :conditions => {:method => :get} do |messages_views|
34 41 messages_views.connect 'boards/:board_id/topics/new', :action => 'new'
35 42 messages_views.connect 'boards/:board_id/topics/:id', :action => 'show'
36 43 messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
37 44 end
38 45 messages_routes.with_options :conditions => {:method => :post} do |messages_actions|
39 46 messages_actions.connect 'boards/:board_id/topics/new', :action => 'new'
40 47 messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply'
41 48 messages_actions.connect 'boards/:board_id/topics/:id/:action', :action => /edit|destroy/
42 49 end
43 50 end
44 51
45 52 map.with_options :controller => 'boards' do |board_routes|
46 53 board_routes.with_options :conditions => {:method => :get} do |board_views|
47 54 board_views.connect 'projects/:project_id/boards', :action => 'index'
48 55 board_views.connect 'projects/:project_id/boards/new', :action => 'new'
49 56 board_views.connect 'projects/:project_id/boards/:id', :action => 'show'
50 57 board_views.connect 'projects/:project_id/boards/:id.:format', :action => 'show'
51 58 board_views.connect 'projects/:project_id/boards/:id/edit', :action => 'edit'
52 59 end
53 60 board_routes.with_options :conditions => {:method => :post} do |board_actions|
54 61 board_actions.connect 'projects/:project_id/boards', :action => 'new'
55 62 board_actions.connect 'projects/:project_id/boards/:id/:action', :action => /edit|destroy/
56 63 end
57 64 end
58 65
59 66 map.with_options :controller => 'documents' do |document_routes|
60 67 document_routes.with_options :conditions => {:method => :get} do |document_views|
61 68 document_views.connect 'projects/:project_id/documents', :action => 'index'
62 69 document_views.connect 'projects/:project_id/documents/new', :action => 'new'
63 70 document_views.connect 'documents/:id', :action => 'show'
64 71 document_views.connect 'documents/:id/edit', :action => 'edit'
65 72 end
66 73 document_routes.with_options :conditions => {:method => :post} do |document_actions|
67 74 document_actions.connect 'projects/:project_id/documents', :action => 'new'
68 75 document_actions.connect 'documents/:id/:action', :action => /destroy|edit/
69 76 end
70 77 end
71 78
72 79 map.resources :issue_moves, :only => [:new, :create], :path_prefix => '/issues', :as => 'move'
73 80
74 81 # Misc issue routes. TODO: move into resources
75 82 map.auto_complete_issues '/issues/auto_complete', :controller => 'auto_completes', :action => 'issues'
76 83 map.preview_issue '/issues/preview/:id', :controller => 'previews', :action => 'issue' # TODO: would look nicer as /issues/:id/preview
77 84 map.issues_context_menu '/issues/context_menu', :controller => 'context_menus', :action => 'issues'
78 85 map.issue_changes '/issues/changes', :controller => 'journals', :action => 'index'
79 86 map.bulk_edit_issue 'issues/bulk_edit', :controller => 'issues', :action => 'bulk_edit', :conditions => { :method => :get }
80 87 map.bulk_update_issue 'issues/bulk_edit', :controller => 'issues', :action => 'bulk_update', :conditions => { :method => :post }
81 88 map.quoted_issue '/issues/:id/quoted', :controller => 'journals', :action => 'new', :id => /\d+/, :conditions => { :method => :post }
82 89 map.connect '/issues/:id/destroy', :controller => 'issues', :action => 'destroy', :conditions => { :method => :post } # legacy
83 90
84 91 map.with_options :controller => 'gantts', :action => 'show' do |gantts_routes|
85 92 gantts_routes.connect '/projects/:project_id/issues/gantt'
86 93 gantts_routes.connect '/projects/:project_id/issues/gantt.:format'
87 94 gantts_routes.connect '/issues/gantt.:format'
88 95 end
89 96
90 97 map.with_options :controller => 'calendars', :action => 'show' do |calendars_routes|
91 98 calendars_routes.connect '/projects/:project_id/issues/calendar'
92 99 calendars_routes.connect '/issues/calendar'
93 100 end
94 101
95 102 map.with_options :controller => 'reports', :conditions => {:method => :get} do |reports|
96 103 reports.connect 'projects/:id/issues/report', :action => 'issue_report'
97 104 reports.connect 'projects/:id/issues/report/:detail', :action => 'issue_report_details'
98 105 end
99 106
100 107 # Following two routes conflict with the resources because #index allows POST
101 108 map.connect '/issues', :controller => 'issues', :action => 'index', :conditions => { :method => :post }
102 109 map.connect '/issues/create', :controller => 'issues', :action => 'index', :conditions => { :method => :post }
103 110
104 111 map.resources :issues, :member => { :edit => :post }, :collection => {} do |issues|
105 112 issues.resources :time_entries, :controller => 'timelog'
106 113 end
107 114
108 115 map.resources :issues, :path_prefix => '/projects/:project_id', :collection => { :create => :post } do |issues|
109 116 issues.resources :time_entries, :controller => 'timelog'
110 117 end
111 118
112 119 map.with_options :controller => 'issue_relations', :conditions => {:method => :post} do |relations|
113 120 relations.connect 'issues/:issue_id/relations/:id', :action => 'new'
114 121 relations.connect 'issues/:issue_id/relations/:id/destroy', :action => 'destroy'
115 122 end
116 123
117 124 map.connect 'projects/:id/members/new', :controller => 'members', :action => 'new'
118 125
119 126 map.with_options :controller => 'users' do |users|
120 127 users.connect 'users/:id/edit/:tab', :action => 'edit', :tab => nil, :conditions => {:method => :get}
121 128
122 129 users.with_options :conditions => {:method => :post} do |user_actions|
123 130 user_actions.connect 'users/:id/memberships', :action => 'edit_membership'
124 131 user_actions.connect 'users/:id/memberships/:membership_id', :action => 'edit_membership'
125 132 user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership'
126 133 end
127 134 end
128 135
129 136 map.resources :users, :member => {
130 137 :edit_membership => :post,
131 138 :destroy_membership => :post
132 139 }
133 140
134 141 # For nice "roadmap" in the url for the index action
135 142 map.connect 'projects/:project_id/roadmap', :controller => 'versions', :action => 'index'
136 143
137 144 map.all_news 'news', :controller => 'news', :action => 'index'
138 145 map.formatted_all_news 'news.:format', :controller => 'news', :action => 'index'
139 146 map.preview_news '/news/preview', :controller => 'previews', :action => 'news'
140 147 map.connect 'news/:id/comments', :controller => 'comments', :action => 'create', :conditions => {:method => :post}
141 148 map.connect 'news/:id/comments/:comment_id', :controller => 'comments', :action => 'destroy', :conditions => {:method => :delete}
142 149
143 150 map.resources :projects, :member => {
144 151 :copy => [:get, :post],
145 152 :settings => :get,
146 153 :modules => :post,
147 154 :archive => :post,
148 155 :unarchive => :post
149 156 } do |project|
150 157 project.resource :project_enumerations, :as => 'enumerations', :only => [:update, :destroy]
151 158 project.resources :files, :only => [:index, :new, :create]
152 159 project.resources :versions, :collection => {:close_completed => :put}, :member => {:status_by => :post}
153 160 project.resources :news, :shallow => true
154 161 project.resources :time_entries, :controller => 'timelog', :path_prefix => 'projects/:project_id'
155 162
156 163 project.wiki_start_page 'wiki', :controller => 'wiki', :action => 'show', :conditions => {:method => :get}
157 164 project.wiki_index 'wiki/index', :controller => 'wiki', :action => 'index', :conditions => {:method => :get}
158 165 project.wiki_diff 'wiki/:id/diff/:version', :controller => 'wiki', :action => 'diff', :version => nil
159 166 project.wiki_diff 'wiki/:id/diff/:version/vs/:version_from', :controller => 'wiki', :action => 'diff'
160 167 project.wiki_annotate 'wiki/:id/annotate/:version', :controller => 'wiki', :action => 'annotate'
161 168 project.resources :wiki, :except => [:new, :create], :member => {
162 169 :rename => [:get, :post],
163 170 :history => :get,
164 171 :preview => :any,
165 172 :protect => :post,
166 173 :add_attachment => :post
167 174 }, :collection => {
168 175 :export => :get,
169 176 :date_index => :get
170 177 }
171 178
172 179 end
173 180
174 181 # Destroy uses a get request to prompt the user before the actual DELETE request
175 182 map.project_destroy_confirm 'projects/:id/destroy', :controller => 'projects', :action => 'destroy', :conditions => {:method => :get}
176 183
177 184 # TODO: port to be part of the resources route(s)
178 185 map.with_options :controller => 'projects' do |project_mapper|
179 186 project_mapper.with_options :conditions => {:method => :get} do |project_views|
180 187 project_views.connect 'projects/:id/settings/:tab', :controller => 'projects', :action => 'settings'
181 188 project_views.connect 'projects/:project_id/issues/:copy_from/copy', :controller => 'issues', :action => 'new'
182 189 end
183 190 end
184 191
185 192 map.with_options :controller => 'activities', :action => 'index', :conditions => {:method => :get} do |activity|
186 193 activity.connect 'projects/:id/activity'
187 194 activity.connect 'projects/:id/activity.:format'
188 195 activity.connect 'activity', :id => nil
189 196 activity.connect 'activity.:format', :id => nil
190 197 end
191 198
192 199
193 200 map.with_options :controller => 'issue_categories' do |categories|
194 201 categories.connect 'projects/:project_id/issue_categories/new', :action => 'new'
195 202 end
196 203
197 204 map.with_options :controller => 'repositories' do |repositories|
198 205 repositories.with_options :conditions => {:method => :get} do |repository_views|
199 206 repository_views.connect 'projects/:id/repository', :action => 'show'
200 207 repository_views.connect 'projects/:id/repository/edit', :action => 'edit'
201 208 repository_views.connect 'projects/:id/repository/statistics', :action => 'stats'
202 209 repository_views.connect 'projects/:id/repository/revisions', :action => 'revisions'
203 210 repository_views.connect 'projects/:id/repository/revisions.:format', :action => 'revisions'
204 211 repository_views.connect 'projects/:id/repository/revisions/:rev', :action => 'revision'
205 212 repository_views.connect 'projects/:id/repository/revisions/:rev/diff', :action => 'diff'
206 213 repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format', :action => 'diff'
207 214 repository_views.connect 'projects/:id/repository/revisions/:rev/raw/*path', :action => 'entry', :format => 'raw', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
208 215 repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
209 216 repository_views.connect 'projects/:id/repository/raw/*path', :action => 'entry', :format => 'raw'
210 217 # TODO: why the following route is required?
211 218 repository_views.connect 'projects/:id/repository/entry/*path', :action => 'entry'
212 219 repository_views.connect 'projects/:id/repository/:action/*path'
213 220 end
214 221
215 222 repositories.connect 'projects/:id/repository/:action', :conditions => {:method => :post}
216 223 end
217 224
218 225 map.connect 'attachments/:id', :controller => 'attachments', :action => 'show', :id => /\d+/
219 226 map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/
220 227 map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/
221 228
222 229 map.resources :groups
223 230
224 231 #left old routes at the bottom for backwards compat
225 232 map.connect 'projects/:project_id/queries/:action', :controller => 'queries'
226 233 map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
227 234 map.connect 'projects/:project_id/documents/:action', :controller => 'documents'
228 235 map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
229 236 map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
230 237 map.connect 'wiki/:id/:page/:action', :page => nil, :controller => 'wiki'
231 238 map.connect 'issues/:issue_id/relations/:action/:id', :controller => 'issue_relations'
232 239 map.connect 'projects/:project_id/news/:action', :controller => 'news'
233 240 map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/
234 241 map.with_options :controller => 'repositories' do |omap|
235 242 omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
236 243 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
237 244 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
238 245 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
239 246 omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
240 247 omap.connect 'repositories/revision/:id/:rev', :action => 'revision'
241 248 end
242 249
243 250 map.with_options :controller => 'sys' do |sys|
244 251 sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
245 252 sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
246 253 end
247 254
248 255 # Install the default route as the lowest priority.
249 256 map.connect ':controller/:action/:id'
250 257 map.connect 'robots.txt', :controller => 'welcome', :action => 'robots'
251 258 # Used for OpenID
252 259 map.root :controller => 'account', :action => 'login'
253 260 end
@@ -1,235 +1,235
1 1 require 'redmine/access_control'
2 2 require 'redmine/menu_manager'
3 3 require 'redmine/activity'
4 4 require 'redmine/search'
5 5 require 'redmine/custom_field_format'
6 6 require 'redmine/mime_type'
7 7 require 'redmine/core_ext'
8 8 require 'redmine/themes'
9 9 require 'redmine/hook'
10 10 require 'redmine/plugin'
11 11 require 'redmine/notifiable'
12 12 require 'redmine/wiki_formatting'
13 13 require 'redmine/scm/base'
14 14
15 15 begin
16 16 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
17 17 rescue LoadError
18 18 # RMagick is not available
19 19 end
20 20
21 21 if RUBY_VERSION < '1.9'
22 22 require 'faster_csv'
23 23 else
24 24 require 'csv'
25 25 FCSV = CSV
26 26 end
27 27
28 28 Redmine::Scm::Base.add "Subversion"
29 29 Redmine::Scm::Base.add "Darcs"
30 30 Redmine::Scm::Base.add "Mercurial"
31 31 Redmine::Scm::Base.add "Cvs"
32 32 Redmine::Scm::Base.add "Bazaar"
33 33 Redmine::Scm::Base.add "Git"
34 34 Redmine::Scm::Base.add "Filesystem"
35 35
36 36 Redmine::CustomFieldFormat.map do |fields|
37 37 fields.register Redmine::CustomFieldFormat.new('string', :label => :label_string, :order => 1)
38 38 fields.register Redmine::CustomFieldFormat.new('text', :label => :label_text, :order => 2)
39 39 fields.register Redmine::CustomFieldFormat.new('int', :label => :label_integer, :order => 3)
40 40 fields.register Redmine::CustomFieldFormat.new('float', :label => :label_float, :order => 4)
41 41 fields.register Redmine::CustomFieldFormat.new('list', :label => :label_list, :order => 5)
42 42 fields.register Redmine::CustomFieldFormat.new('date', :label => :label_date, :order => 6)
43 43 fields.register Redmine::CustomFieldFormat.new('bool', :label => :label_boolean, :order => 7)
44 44 fields.register Redmine::CustomFieldFormat.new('user', :label => :label_user, :only => %w(Issue TimeEntry Version Project), :edit_as => 'list', :order => 8)
45 45 fields.register Redmine::CustomFieldFormat.new('version', :label => :label_version, :only => %w(Issue TimeEntry Version Project), :edit_as => 'list', :order => 9)
46 46 end
47 47
48 48 # Permissions
49 49 Redmine::AccessControl.map do |map|
50 50 map.permission :view_project, {:projects => [:show], :activities => [:index]}, :public => true
51 51 map.permission :search_project, {:search => :index}, :public => true
52 52 map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin
53 53 map.permission :edit_project, {:projects => [:settings, :edit, :update]}, :require => :member
54 54 map.permission :select_project_modules, {:projects => :modules}, :require => :member
55 55 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member
56 56 map.permission :manage_versions, {:projects => :settings, :versions => [:new, :create, :edit, :update, :close_completed, :destroy]}, :require => :member
57 57 map.permission :add_subprojects, {:projects => [:new, :create]}, :require => :member
58 58
59 59 map.project_module :issue_tracking do |map|
60 60 # Issue categories
61 61 map.permission :manage_categories, {:projects => :settings, :issue_categories => [:new, :edit, :destroy]}, :require => :member
62 62 # Issues
63 63 map.permission :view_issues, {:issues => [:index, :show],
64 64 :auto_complete => [:issues],
65 65 :context_menus => [:issues],
66 66 :versions => [:index, :show, :status_by],
67 67 :journals => [:index, :diff],
68 68 :queries => :index,
69 69 :reports => [:issue_report, :issue_report_details]}
70 70 map.permission :add_issues, {:issues => [:new, :create, :update_form]}
71 71 map.permission :edit_issues, {:issues => [:edit, :update, :bulk_edit, :bulk_update, :update_form], :journals => [:new]}
72 72 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
73 73 map.permission :manage_subtasks, {}
74 74 map.permission :add_issue_notes, {:issues => [:edit, :update], :journals => [:new]}
75 75 map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
76 76 map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
77 77 map.permission :move_issues, {:issue_moves => [:new, :create]}, :require => :loggedin
78 78 map.permission :delete_issues, {:issues => :destroy}, :require => :member
79 79 # Queries
80 80 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
81 81 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
82 82 # Watchers
83 83 map.permission :view_issue_watchers, {}
84 84 map.permission :add_issue_watchers, {:watchers => :new}
85 85 map.permission :delete_issue_watchers, {:watchers => :destroy}
86 86 end
87 87
88 88 map.project_module :time_tracking do |map|
89 map.permission :log_time, {:timelog => [:new, :create, :edit, :update]}, :require => :loggedin
89 map.permission :log_time, {:timelog => [:new, :create, :edit, :update, :bulk_edit, :bulk_update]}, :require => :loggedin
90 90 map.permission :view_time_entries, :timelog => [:index, :show], :time_entry_reports => [:report]
91 map.permission :edit_time_entries, {:timelog => [:new, :create, :edit, :update, :destroy]}, :require => :member
92 map.permission :edit_own_time_entries, {:timelog => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin
91 map.permission :edit_time_entries, {:timelog => [:new, :create, :edit, :update, :destroy, :bulk_edit, :bulk_update]}, :require => :member
92 map.permission :edit_own_time_entries, {:timelog => [:new, :create, :edit, :update, :destroy,:bulk_edit, :bulk_update]}, :require => :loggedin
93 93 map.permission :manage_project_activities, {:project_enumerations => [:update, :destroy]}, :require => :member
94 94 end
95 95
96 96 map.project_module :news do |map|
97 97 map.permission :manage_news, {:news => [:new, :create, :edit, :update, :destroy], :comments => [:destroy]}, :require => :member
98 98 map.permission :view_news, {:news => [:index, :show]}, :public => true
99 99 map.permission :comment_news, {:comments => :create}
100 100 end
101 101
102 102 map.project_module :documents do |map|
103 103 map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment]}, :require => :loggedin
104 104 map.permission :view_documents, :documents => [:index, :show, :download]
105 105 end
106 106
107 107 map.project_module :files do |map|
108 108 map.permission :manage_files, {:files => [:new, :create]}, :require => :loggedin
109 109 map.permission :view_files, :files => :index, :versions => :download
110 110 end
111 111
112 112 map.project_module :wiki do |map|
113 113 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
114 114 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
115 115 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
116 116 map.permission :view_wiki_pages, :wiki => [:index, :show, :special, :date_index]
117 117 map.permission :export_wiki_pages, :wiki => [:export]
118 118 map.permission :view_wiki_edits, :wiki => [:history, :diff, :annotate]
119 119 map.permission :edit_wiki_pages, :wiki => [:edit, :update, :preview, :add_attachment]
120 120 map.permission :delete_wiki_pages_attachments, {}
121 121 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
122 122 end
123 123
124 124 map.project_module :repository do |map|
125 125 map.permission :manage_repository, {:repositories => [:edit, :committers, :destroy]}, :require => :member
126 126 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
127 127 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
128 128 map.permission :commit_access, {}
129 129 end
130 130
131 131 map.project_module :boards do |map|
132 132 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
133 133 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
134 134 map.permission :add_messages, {:messages => [:new, :reply, :quote]}
135 135 map.permission :edit_messages, {:messages => :edit}, :require => :member
136 136 map.permission :edit_own_messages, {:messages => :edit}, :require => :loggedin
137 137 map.permission :delete_messages, {:messages => :destroy}, :require => :member
138 138 map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin
139 139 end
140 140
141 141 map.project_module :calendar do |map|
142 142 map.permission :view_calendar, :calendars => [:show, :update]
143 143 end
144 144
145 145 map.project_module :gantt do |map|
146 146 map.permission :view_gantt, :gantts => [:show, :update]
147 147 end
148 148 end
149 149
150 150 Redmine::MenuManager.map :top_menu do |menu|
151 151 menu.push :home, :home_path
152 152 menu.push :my_page, { :controller => 'my', :action => 'page' }, :if => Proc.new { User.current.logged? }
153 153 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural
154 154 menu.push :administration, { :controller => 'admin', :action => 'index' }, :if => Proc.new { User.current.admin? }, :last => true
155 155 menu.push :help, Redmine::Info.help_url, :last => true
156 156 end
157 157
158 158 Redmine::MenuManager.map :account_menu do |menu|
159 159 menu.push :login, :signin_path, :if => Proc.new { !User.current.logged? }
160 160 menu.push :register, { :controller => 'account', :action => 'register' }, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
161 161 menu.push :my_account, { :controller => 'my', :action => 'account' }, :if => Proc.new { User.current.logged? }
162 162 menu.push :logout, :signout_path, :if => Proc.new { User.current.logged? }
163 163 end
164 164
165 165 Redmine::MenuManager.map :application_menu do |menu|
166 166 # Empty
167 167 end
168 168
169 169 Redmine::MenuManager.map :admin_menu do |menu|
170 170 menu.push :projects, {:controller => 'admin', :action => 'projects'}, :caption => :label_project_plural
171 171 menu.push :users, {:controller => 'users'}, :caption => :label_user_plural
172 172 menu.push :groups, {:controller => 'groups'}, :caption => :label_group_plural
173 173 menu.push :roles, {:controller => 'roles'}, :caption => :label_role_and_permissions
174 174 menu.push :trackers, {:controller => 'trackers'}, :caption => :label_tracker_plural
175 175 menu.push :issue_statuses, {:controller => 'issue_statuses'}, :caption => :label_issue_status_plural,
176 176 :html => {:class => 'issue_statuses'}
177 177 menu.push :workflows, {:controller => 'workflows', :action => 'edit'}, :caption => :label_workflow
178 178 menu.push :custom_fields, {:controller => 'custom_fields'}, :caption => :label_custom_field_plural,
179 179 :html => {:class => 'custom_fields'}
180 180 menu.push :enumerations, {:controller => 'enumerations'}
181 181 menu.push :settings, {:controller => 'settings'}
182 182 menu.push :ldap_authentication, {:controller => 'ldap_auth_sources', :action => 'index'},
183 183 :html => {:class => 'server_authentication'}
184 184 menu.push :plugins, {:controller => 'admin', :action => 'plugins'}, :last => true
185 185 menu.push :info, {:controller => 'admin', :action => 'info'}, :caption => :label_information_plural, :last => true
186 186 end
187 187
188 188 Redmine::MenuManager.map :project_menu do |menu|
189 189 menu.push :overview, { :controller => 'projects', :action => 'show' }
190 190 menu.push :activity, { :controller => 'activities', :action => 'index' }
191 191 menu.push :roadmap, { :controller => 'versions', :action => 'index' }, :param => :project_id,
192 192 :if => Proc.new { |p| p.shared_versions.any? }
193 193 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
194 194 menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new,
195 195 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
196 196 menu.push :gantt, { :controller => 'gantts', :action => 'show' }, :param => :project_id, :caption => :label_gantt
197 197 menu.push :calendar, { :controller => 'calendars', :action => 'show' }, :param => :project_id, :caption => :label_calendar
198 198 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
199 199 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
200 200 menu.push :wiki, { :controller => 'wiki', :action => 'show', :id => nil }, :param => :project_id,
201 201 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
202 202 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
203 203 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
204 204 menu.push :files, { :controller => 'files', :action => 'index' }, :caption => :label_file_plural, :param => :project_id
205 205 menu.push :repository, { :controller => 'repositories', :action => 'show' },
206 206 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
207 207 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
208 208 end
209 209
210 210 Redmine::Activity.map do |activity|
211 211 activity.register :issues, :class_name => %w(Issue Journal)
212 212 activity.register :changesets
213 213 activity.register :news
214 214 activity.register :documents, :class_name => %w(Document Attachment)
215 215 activity.register :files, :class_name => 'Attachment'
216 216 activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false
217 217 activity.register :messages, :default => false
218 218 activity.register :time_entries, :default => false
219 219 end
220 220
221 221 Redmine::Search.map do |search|
222 222 search.register :issues
223 223 search.register :news
224 224 search.register :documents
225 225 search.register :changesets
226 226 search.register :wiki_pages
227 227 search.register :messages
228 228 search.register :projects
229 229 end
230 230
231 231 Redmine::WikiFormatting.map do |format|
232 232 format.register :textile, Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting::Textile::Helper
233 233 end
234 234
235 235 ActionView::Template.register_template_handler :rsb, Redmine::Views::ApiTemplateHandler
General Comments 0
You need to be logged in to leave comments. Login now