annotate app/controllers/projects_controller.rb @ 8:0c83d98252d9 yuya

* Add custom repo prefix and proper auth realm, remove auth cache (seems like an unwise feature), pass DB handle around, various other bits of tidying
author Chris Cannam
date Thu, 12 Aug 2010 15:31:37 +0100
parents 513646585e45
children 40f7cfd4df19
rev   line source
Chris@0 1 # Redmine - project management software
Chris@0 2 # Copyright (C) 2006-2009 Jean-Philippe Lang
Chris@0 3 #
Chris@0 4 # This program is free software; you can redistribute it and/or
Chris@0 5 # modify it under the terms of the GNU General Public License
Chris@0 6 # as published by the Free Software Foundation; either version 2
Chris@0 7 # of the License, or (at your option) any later version.
Chris@0 8 #
Chris@0 9 # This program is distributed in the hope that it will be useful,
Chris@0 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
Chris@0 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Chris@0 12 # GNU General Public License for more details.
Chris@0 13 #
Chris@0 14 # You should have received a copy of the GNU General Public License
Chris@0 15 # along with this program; if not, write to the Free Software
Chris@0 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Chris@0 17
Chris@0 18 class ProjectsController < ApplicationController
Chris@0 19 menu_item :overview
Chris@0 20 menu_item :activity, :only => :activity
Chris@0 21 menu_item :roadmap, :only => :roadmap
Chris@0 22 menu_item :files, :only => [:list_files, :add_file]
Chris@0 23 menu_item :settings, :only => :settings
Chris@0 24
Chris@0 25 before_filter :find_project, :except => [ :index, :list, :add, :copy, :activity ]
Chris@0 26 before_filter :find_optional_project, :only => :activity
Chris@0 27 before_filter :authorize, :except => [ :index, :list, :add, :copy, :archive, :unarchive, :destroy, :activity ]
Chris@0 28 before_filter :authorize_global, :only => :add
Chris@0 29 before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
Chris@0 30 accept_key_auth :activity, :index
Chris@0 31
Chris@0 32 after_filter :only => [:add, :edit, :archive, :unarchive, :destroy] do |controller|
Chris@0 33 if controller.request.post?
Chris@0 34 controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt'
Chris@0 35 end
Chris@0 36 end
Chris@0 37
Chris@0 38 helper :sort
Chris@0 39 include SortHelper
Chris@0 40 helper :custom_fields
Chris@0 41 include CustomFieldsHelper
Chris@0 42 helper :issues
Chris@0 43 helper :queries
Chris@0 44 include QueriesHelper
Chris@0 45 helper :repositories
Chris@0 46 include RepositoriesHelper
Chris@0 47 include ProjectsHelper
Chris@0 48
Chris@0 49 # Lists visible projects
Chris@0 50 def index
Chris@0 51 respond_to do |format|
Chris@0 52 format.html {
Chris@0 53 @projects = Project.visible.find(:all, :order => 'lft')
Chris@0 54 }
Chris@0 55 format.xml {
Chris@0 56 @projects = Project.visible.find(:all, :order => 'lft')
Chris@0 57 }
Chris@0 58 format.atom {
Chris@0 59 projects = Project.visible.find(:all, :order => 'created_on DESC',
Chris@0 60 :limit => Setting.feeds_limit.to_i)
Chris@0 61 render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
Chris@0 62 }
Chris@0 63 end
Chris@0 64 end
Chris@0 65
Chris@0 66 # Add a new project
Chris@0 67 def add
Chris@0 68 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
Chris@0 69 @trackers = Tracker.all
Chris@0 70 @project = Project.new(params[:project])
Chris@0 71 if request.get?
Chris@0 72 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
Chris@0 73 @project.trackers = Tracker.all
Chris@0 74 @project.is_public = Setting.default_projects_public?
Chris@0 75 @project.enabled_module_names = Setting.default_projects_modules
Chris@0 76 else
Chris@0 77 @project.enabled_module_names = params[:enabled_modules]
Chris@0 78 if validate_parent_id && @project.save
Chris@0 79 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
Chris@0 80 # Add current user as a project member if he is not admin
Chris@0 81 unless User.current.admin?
Chris@0 82 r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
Chris@0 83 m = Member.new(:user => User.current, :roles => [r])
Chris@0 84 @project.members << m
Chris@0 85 end
Chris@0 86 respond_to do |format|
Chris@0 87 format.html {
Chris@0 88 flash[:notice] = l(:notice_successful_create)
Chris@0 89 redirect_to :controller => 'projects', :action => 'settings', :id => @project
Chris@0 90 }
Chris@0 91 format.xml { head :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
Chris@0 92 end
Chris@0 93 else
Chris@0 94 respond_to do |format|
Chris@0 95 format.html
Chris@0 96 format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
Chris@0 97 end
Chris@0 98 end
Chris@0 99 end
Chris@0 100 end
Chris@0 101
Chris@0 102 def copy
Chris@0 103 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
Chris@0 104 @trackers = Tracker.all
Chris@0 105 @root_projects = Project.find(:all,
Chris@0 106 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
Chris@0 107 :order => 'name')
Chris@0 108 @source_project = Project.find(params[:id])
Chris@0 109 if request.get?
Chris@0 110 @project = Project.copy_from(@source_project)
Chris@0 111 if @project
Chris@0 112 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
Chris@0 113 else
Chris@0 114 redirect_to :controller => 'admin', :action => 'projects'
Chris@0 115 end
Chris@0 116 else
Chris@0 117 Mailer.with_deliveries(params[:notifications] == '1') do
Chris@0 118 @project = Project.new(params[:project])
Chris@0 119 @project.enabled_module_names = params[:enabled_modules]
Chris@0 120 if validate_parent_id && @project.copy(@source_project, :only => params[:only])
Chris@0 121 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
Chris@0 122 flash[:notice] = l(:notice_successful_create)
Chris@0 123 redirect_to :controller => 'admin', :action => 'projects'
Chris@0 124 elsif !@project.new_record?
Chris@0 125 # Project was created
Chris@0 126 # But some objects were not copied due to validation failures
Chris@0 127 # (eg. issues from disabled trackers)
Chris@0 128 # TODO: inform about that
Chris@0 129 redirect_to :controller => 'admin', :action => 'projects'
Chris@0 130 end
Chris@0 131 end
Chris@0 132 end
Chris@0 133 rescue ActiveRecord::RecordNotFound
Chris@0 134 redirect_to :controller => 'admin', :action => 'projects'
Chris@0 135 end
Chris@0 136
Chris@0 137 # Show @project
Chris@0 138 def show
Chris@0 139 if params[:jump]
Chris@0 140 # try to redirect to the requested menu item
Chris@0 141 redirect_to_project_menu_item(@project, params[:jump]) && return
Chris@0 142 end
Chris@0 143
Chris@0 144 @users_by_role = @project.users_by_role
Chris@0 145 @subprojects = @project.children.visible
Chris@0 146 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
Chris@0 147 @trackers = @project.rolled_up_trackers
Chris@0 148
Chris@0 149 cond = @project.project_condition(Setting.display_subprojects_issues?)
Chris@0 150
Chris@0 151 @open_issues_by_tracker = Issue.visible.count(:group => :tracker,
Chris@0 152 :include => [:project, :status, :tracker],
Chris@0 153 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
Chris@0 154 @total_issues_by_tracker = Issue.visible.count(:group => :tracker,
Chris@0 155 :include => [:project, :status, :tracker],
Chris@0 156 :conditions => cond)
Chris@0 157
Chris@0 158 TimeEntry.visible_by(User.current) do
Chris@0 159 @total_hours = TimeEntry.sum(:hours,
Chris@0 160 :include => :project,
Chris@0 161 :conditions => cond).to_f
Chris@0 162 end
Chris@0 163 @key = User.current.rss_key
Chris@0 164
Chris@0 165 respond_to do |format|
Chris@0 166 format.html
Chris@0 167 format.xml
Chris@0 168 end
Chris@0 169 end
Chris@0 170
Chris@0 171 def settings
Chris@0 172 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
Chris@0 173 @issue_category ||= IssueCategory.new
Chris@0 174 @member ||= @project.members.new
Chris@0 175 @trackers = Tracker.all
Chris@0 176 @repository ||= @project.repository
Chris@0 177 @wiki ||= @project.wiki
Chris@0 178 end
Chris@0 179
Chris@0 180 # Edit @project
Chris@0 181 def edit
Chris@0 182 if request.get?
Chris@0 183 else
Chris@0 184 @project.attributes = params[:project]
Chris@0 185 if validate_parent_id && @project.save
Chris@0 186 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
Chris@0 187 respond_to do |format|
Chris@0 188 format.html {
Chris@0 189 flash[:notice] = l(:notice_successful_update)
Chris@0 190 redirect_to :action => 'settings', :id => @project
Chris@0 191 }
Chris@0 192 format.xml { head :ok }
Chris@0 193 end
Chris@0 194 else
Chris@0 195 respond_to do |format|
Chris@0 196 format.html {
Chris@0 197 settings
Chris@0 198 render :action => 'settings'
Chris@0 199 }
Chris@0 200 format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
Chris@0 201 end
Chris@0 202 end
Chris@0 203 end
Chris@0 204 end
Chris@0 205
Chris@0 206 def modules
Chris@0 207 @project.enabled_module_names = params[:enabled_modules]
Chris@0 208 flash[:notice] = l(:notice_successful_update)
Chris@0 209 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
Chris@0 210 end
Chris@0 211
Chris@0 212 def archive
Chris@0 213 if request.post?
Chris@0 214 unless @project.archive
Chris@0 215 flash[:error] = l(:error_can_not_archive_project)
Chris@0 216 end
Chris@0 217 end
Chris@0 218 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
Chris@0 219 end
Chris@0 220
Chris@0 221 def unarchive
Chris@0 222 @project.unarchive if request.post? && !@project.active?
Chris@0 223 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
Chris@0 224 end
Chris@0 225
Chris@0 226 # Delete @project
Chris@0 227 def destroy
Chris@0 228 @project_to_destroy = @project
Chris@0 229 if request.get?
Chris@0 230 # display confirmation view
Chris@0 231 else
Chris@0 232 if params[:format] == 'xml' || params[:confirm]
Chris@0 233 @project_to_destroy.destroy
Chris@0 234 respond_to do |format|
Chris@0 235 format.html { redirect_to :controller => 'admin', :action => 'projects' }
Chris@0 236 format.xml { head :ok }
Chris@0 237 end
Chris@0 238 end
Chris@0 239 end
Chris@0 240 # hide project in layout
Chris@0 241 @project = nil
Chris@0 242 end
Chris@0 243
Chris@0 244 def add_file
Chris@0 245 if request.post?
Chris@0 246 container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
Chris@0 247 attachments = Attachment.attach_files(container, params[:attachments])
Chris@0 248 render_attachment_warning_if_needed(container)
Chris@0 249
Chris@0 250 if !attachments.empty? && Setting.notified_events.include?('file_added')
Chris@0 251 Mailer.deliver_attachments_added(attachments[:files])
Chris@0 252 end
Chris@0 253 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
Chris@0 254 return
Chris@0 255 end
Chris@0 256 @versions = @project.versions.sort
Chris@0 257 end
Chris@0 258
Chris@0 259 def save_activities
Chris@0 260 if request.post? && params[:enumerations]
Chris@0 261 Project.transaction do
Chris@0 262 params[:enumerations].each do |id, activity|
Chris@0 263 @project.update_or_create_time_entry_activity(id, activity)
Chris@0 264 end
Chris@0 265 end
Chris@0 266 flash[:notice] = l(:notice_successful_update)
Chris@0 267 end
Chris@0 268
Chris@0 269 redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
Chris@0 270 end
Chris@0 271
Chris@0 272 def reset_activities
Chris@0 273 @project.time_entry_activities.each do |time_entry_activity|
Chris@0 274 time_entry_activity.destroy(time_entry_activity.parent)
Chris@0 275 end
Chris@0 276 flash[:notice] = l(:notice_successful_update)
Chris@0 277 redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
Chris@0 278 end
Chris@0 279
Chris@0 280 def list_files
Chris@0 281 sort_init 'filename', 'asc'
Chris@0 282 sort_update 'filename' => "#{Attachment.table_name}.filename",
Chris@0 283 'created_on' => "#{Attachment.table_name}.created_on",
Chris@0 284 'size' => "#{Attachment.table_name}.filesize",
Chris@0 285 'downloads' => "#{Attachment.table_name}.downloads"
Chris@0 286
Chris@0 287 @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
Chris@0 288 @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
Chris@0 289 render :layout => !request.xhr?
Chris@0 290 end
Chris@0 291
Chris@0 292 def roadmap
Chris@0 293 @trackers = @project.trackers.find(:all, :order => 'position')
Chris@0 294 retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?})
Chris@0 295 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
Chris@0 296 project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
Chris@0 297
Chris@0 298 @versions = @project.shared_versions || []
Chris@0 299 @versions += @project.rolled_up_versions.visible if @with_subprojects
Chris@0 300 @versions = @versions.uniq.sort
Chris@0 301 @versions.reject! {|version| version.closed? || version.completed? } unless params[:completed]
Chris@0 302
Chris@0 303 @issues_by_version = {}
Chris@0 304 unless @selected_tracker_ids.empty?
Chris@0 305 @versions.each do |version|
Chris@0 306 issues = version.fixed_issues.visible.find(:all,
Chris@0 307 :include => [:project, :status, :tracker, :priority],
Chris@0 308 :conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids},
Chris@0 309 :order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
Chris@0 310 @issues_by_version[version] = issues
Chris@0 311 end
Chris@0 312 end
Chris@0 313 @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?}
Chris@0 314 end
Chris@0 315
Chris@0 316 def activity
Chris@0 317 @days = Setting.activity_days_default.to_i
Chris@0 318
Chris@0 319 if params[:from]
Chris@0 320 begin; @date_to = params[:from].to_date + 1; rescue; end
Chris@0 321 end
Chris@0 322
Chris@0 323 @date_to ||= Date.today + 1
Chris@0 324 @date_from = @date_to - @days
Chris@0 325 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
Chris@0 326 @author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id]))
Chris@0 327
Chris@0 328 @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project,
Chris@0 329 :with_subprojects => @with_subprojects,
Chris@0 330 :author => @author)
Chris@0 331 @activity.scope_select {|t| !params["show_#{t}"].nil?}
Chris@0 332 @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
Chris@0 333
Chris@0 334 events = @activity.events(@date_from, @date_to)
Chris@0 335
Chris@0 336 if events.empty? || stale?(:etag => [events.first, User.current])
Chris@0 337 respond_to do |format|
Chris@0 338 format.html {
Chris@0 339 @events_by_day = events.group_by(&:event_date)
Chris@0 340 render :layout => false if request.xhr?
Chris@0 341 }
Chris@0 342 format.atom {
Chris@0 343 title = l(:label_activity)
Chris@0 344 if @author
Chris@0 345 title = @author.name
Chris@0 346 elsif @activity.scope.size == 1
Chris@0 347 title = l("label_#{@activity.scope.first.singularize}_plural")
Chris@0 348 end
Chris@0 349 render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
Chris@0 350 }
Chris@0 351 end
Chris@0 352 end
Chris@0 353
Chris@0 354 rescue ActiveRecord::RecordNotFound
Chris@0 355 render_404
Chris@0 356 end
Chris@0 357
Chris@0 358 private
Chris@0 359 def find_optional_project
Chris@0 360 return true unless params[:id]
Chris@0 361 @project = Project.find(params[:id])
Chris@0 362 authorize
Chris@0 363 rescue ActiveRecord::RecordNotFound
Chris@0 364 render_404
Chris@0 365 end
Chris@0 366
Chris@0 367 def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil)
Chris@0 368 if ids = params[:tracker_ids]
Chris@0 369 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
Chris@0 370 else
Chris@0 371 @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
Chris@0 372 end
Chris@0 373 end
Chris@0 374
Chris@0 375 # Validates parent_id param according to user's permissions
Chris@0 376 # TODO: move it to Project model in a validation that depends on User.current
Chris@0 377 def validate_parent_id
Chris@0 378 return true if User.current.admin?
Chris@0 379 parent_id = params[:project] && params[:project][:parent_id]
Chris@0 380 if parent_id || @project.new_record?
Chris@0 381 parent = parent_id.blank? ? nil : Project.find_by_id(parent_id.to_i)
Chris@0 382 unless @project.allowed_parents.include?(parent)
Chris@0 383 @project.errors.add :parent_id, :invalid
Chris@0 384 return false
Chris@0 385 end
Chris@0 386 end
Chris@0 387 true
Chris@0 388 end
Chris@0 389 end