comparison app/models/.svn/text-base/project.rb.svn-base @ 441:cbce1fd3b1b7 redmine-1.2

Update to Redmine 1.2-stable branch (Redmine SVN rev 6000)
author Chris Cannam
date Mon, 06 Jun 2011 14:24:13 +0100
parents 8661b858af72
children 0c939c159af4
comparison
equal deleted inserted replaced
245:051f544170fe 441:cbce1fd3b1b7
1 # redMine - project management software 1 # Redmine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 # 3 #
4 # This program is free software; you can redistribute it and/or 4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License 5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2 6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version. 7 # of the License, or (at your option) any later version.
41 has_many :issue_changes, :through => :issues, :source => :journals 41 has_many :issue_changes, :through => :issues, :source => :journals
42 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC" 42 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
43 has_many :time_entries, :dependent => :delete_all 43 has_many :time_entries, :dependent => :delete_all
44 has_many :queries, :dependent => :delete_all 44 has_many :queries, :dependent => :delete_all
45 has_many :documents, :dependent => :destroy 45 has_many :documents, :dependent => :destroy
46 has_many :news, :dependent => :delete_all, :include => :author 46 has_many :news, :dependent => :destroy, :include => :author
47 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name" 47 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
48 has_many :boards, :dependent => :destroy, :order => "position ASC" 48 has_many :boards, :dependent => :destroy, :order => "position ASC"
49 has_one :repository, :dependent => :destroy 49 has_one :repository, :dependent => :destroy
50 has_many :changesets, :through => :repository 50 has_many :changesets, :through => :repository
51 has_one :wiki, :dependent => :destroy 51 has_one :wiki, :dependent => :destroy
54 :class_name => 'IssueCustomField', 54 :class_name => 'IssueCustomField',
55 :order => "#{CustomField.table_name}.position", 55 :order => "#{CustomField.table_name}.position",
56 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", 56 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
57 :association_foreign_key => 'custom_field_id' 57 :association_foreign_key => 'custom_field_id'
58 58
59 acts_as_nested_set :order => 'name' 59 acts_as_nested_set :order => 'name', :dependent => :destroy
60 acts_as_attachable :view_permission => :view_files, 60 acts_as_attachable :view_permission => :view_files,
61 :delete_permission => :manage_files 61 :delete_permission => :manage_files
62 62
63 acts_as_customizable 63 acts_as_customizable
64 acts_as_searchable :columns => ['name', 'identifier', 'description'], :project_key => 'id', :permission => nil 64 acts_as_searchable :columns => ['name', 'identifier', 'description'], :project_key => 'id', :permission => nil
77 # donwcase letters, digits, dashes but not digits only 77 # donwcase letters, digits, dashes but not digits only
78 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :if => Proc.new { |p| p.identifier_changed? } 78 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :if => Proc.new { |p| p.identifier_changed? }
79 # reserved words 79 # reserved words
80 validates_exclusion_of :identifier, :in => %w( new ) 80 validates_exclusion_of :identifier, :in => %w( new )
81 81
82 before_destroy :delete_all_members, :destroy_children 82 before_destroy :delete_all_members
83 83
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] } } 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 named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"} 85 named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
86 named_scope :all_public, { :conditions => { :is_public => true } } 86 named_scope :all_public, { :conditions => { :is_public => true } }
87 named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } } 87 named_scope :visible, lambda {|*args| {:conditions => Project.visible_condition(args.shift || User.current, *args) }}
88 88
89 def initialize(attributes = nil) 89 def initialize(attributes = nil)
90 super 90 super
91 91
92 initialized = (attributes || {}).stringify_keys 92 initialized = (attributes || {}).stringify_keys
113 end 113 end
114 114
115 # returns latest created projects 115 # returns latest created projects
116 # non public projects will be returned only if user is a member of those 116 # non public projects will be returned only if user is a member of those
117 def self.latest(user=nil, count=5) 117 def self.latest(user=nil, count=5)
118 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC") 118 visible(user).find(:all, :limit => count, :order => "created_on DESC")
119 end 119 end
120 120
121 # Returns a SQL :conditions string used to find all active projects for the specified user. 121 def self.visible_by(user=nil)
122 ActiveSupport::Deprecation.warn "Project.visible_by is deprecated and will be removed in Redmine 1.3.0. Use Project.visible_condition instead."
123 visible_condition(user || User.current)
124 end
125
126 # Returns a SQL conditions string used to find all projects visible by the specified user.
122 # 127 #
123 # Examples: 128 # Examples:
124 # Projects.visible_by(admin) => "projects.status = 1" 129 # Project.visible_condition(admin) => "projects.status = 1"
125 # Projects.visible_by(normal_user) => "projects.status = 1 AND projects.is_public = 1" 130 # Project.visible_condition(normal_user) => "((projects.status = 1) AND (projects.is_public = 1 OR projects.id IN (1,3,4)))"
126 def self.visible_by(user=nil) 131 # Project.visible_condition(anonymous) => "((projects.status = 1) AND (projects.is_public = 1))"
127 user ||= User.current 132 def self.visible_condition(user, options={})
128 if user && user.admin? 133 allowed_to_condition(user, :view_project, options)
129 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}" 134 end
130 elsif user && user.memberships.any? 135
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(',')}))" 136 # Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+
132 else 137 #
133 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}" 138 # Valid options:
134 end 139 # * :project => limit the condition to project
135 end 140 # * :with_subprojects => limit the condition to project and its subprojects
136 141 # * :member => limit the condition to the user projects
137 def self.allowed_to_condition(user, permission, options={}) 142 def self.allowed_to_condition(user, permission, options={})
138 statements = []
139 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}" 143 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
140 if perm = Redmine::AccessControl.permission(permission) 144 if perm = Redmine::AccessControl.permission(permission)
141 unless perm.project_module.nil? 145 unless perm.project_module.nil?
142 # If the permission belongs to a project module, make sure the module is enabled 146 # If the permission belongs to a project module, make sure the module is enabled
143 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')" 147 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
146 if options[:project] 150 if options[:project]
147 project_statement = "#{Project.table_name}.id = #{options[:project].id}" 151 project_statement = "#{Project.table_name}.id = #{options[:project].id}"
148 project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects] 152 project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects]
149 base_statement = "(#{project_statement}) AND (#{base_statement})" 153 base_statement = "(#{project_statement}) AND (#{base_statement})"
150 end 154 end
155
151 if user.admin? 156 if user.admin?
152 # no restriction 157 base_statement
153 else 158 else
154 statements << "1=0" 159 statement_by_role = {}
160 unless options[:member]
161 role = user.logged? ? Role.non_member : Role.anonymous
162 if role.allowed_to?(permission)
163 statement_by_role[role] = "#{Project.table_name}.is_public = #{connection.quoted_true}"
164 end
165 end
155 if user.logged? 166 if user.logged?
156 if Role.non_member.allowed_to?(permission) && !options[:member] 167 user.projects_by_role.each do |role, projects|
157 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" 168 if role.allowed_to?(permission)
158 end 169 statement_by_role[role] = "#{Project.table_name}.id IN (#{projects.collect(&:id).join(',')})"
159 allowed_project_ids = user.memberships.select {|m| m.roles.detect {|role| role.allowed_to?(permission)}}.collect {|m| m.project_id} 170 end
160 statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any? 171 end
172 end
173 if statement_by_role.empty?
174 "1=0"
161 else 175 else
162 if Role.anonymous.allowed_to?(permission) && !options[:member] 176 if block_given?
163 # anonymous user allowed on public project 177 statement_by_role.each do |role, statement|
164 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" 178 if s = yield(role, user)
165 end 179 statement_by_role[role] = "(#{statement} AND (#{s}))"
166 end 180 end
167 end 181 end
168 statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))" 182 end
183 "((#{base_statement}) AND (#{statement_by_role.values.join(' OR ')}))"
184 end
185 end
169 end 186 end
170 187
171 # Returns the Systemwide and project specific activities 188 # Returns the Systemwide and project specific activities
172 def activities(include_inactive=false) 189 def activities(include_inactive=false)
173 if include_inactive 190 if include_inactive
345 end 362 end
346 363
347 # Returns an array of the trackers used by the project and its active sub projects 364 # Returns an array of the trackers used by the project and its active sub projects
348 def rolled_up_trackers 365 def rolled_up_trackers
349 @rolled_up_trackers ||= 366 @rolled_up_trackers ||=
350 Tracker.find(:all, :include => :projects, 367 Tracker.find(:all, :joins => :projects,
351 :select => "DISTINCT #{Tracker.table_name}.*", 368 :select => "DISTINCT #{Tracker.table_name}.*",
352 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt], 369 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
353 :order => "#{Tracker.table_name}.position") 370 :order => "#{Tracker.table_name}.position")
354 end 371 end
355 372
371 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt]) 388 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt])
372 end 389 end
373 390
374 # Returns a scope of the Versions used by the project 391 # Returns a scope of the Versions used by the project
375 def shared_versions 392 def shared_versions
376 @shared_versions ||= 393 @shared_versions ||= begin
394 r = root? ? self : root
377 Version.scoped(:include => :project, 395 Version.scoped(:include => :project,
378 :conditions => "#{Project.table_name}.id = #{id}" + 396 :conditions => "#{Project.table_name}.id = #{id}" +
379 " OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" + 397 " OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" +
380 " #{Version.table_name}.sharing = 'system'" + 398 " #{Version.table_name}.sharing = 'system'" +
381 " OR (#{Project.table_name}.lft >= #{root.lft} AND #{Project.table_name}.rgt <= #{root.rgt} AND #{Version.table_name}.sharing = 'tree')" + 399 " OR (#{Project.table_name}.lft >= #{r.lft} AND #{Project.table_name}.rgt <= #{r.rgt} AND #{Version.table_name}.sharing = 'tree')" +
382 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" + 400 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
383 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" + 401 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
384 "))") 402 "))")
403 end
385 end 404 end
386 405
387 # Returns a hash of project users grouped by role 406 # Returns a hash of project users grouped by role
388 def users_by_role 407 def users_by_role
389 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m| 408 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m|
420 439
421 # Returns an array of all custom fields enabled for project issues 440 # Returns an array of all custom fields enabled for project issues
422 # (explictly associated custom fields and custom fields enabled for all projects) 441 # (explictly associated custom fields and custom fields enabled for all projects)
423 def all_issue_custom_fields 442 def all_issue_custom_fields
424 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort 443 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
444 end
445
446 # Returns an array of all custom fields enabled for project time entries
447 # (explictly associated custom fields and custom fields enabled for all projects)
448 def all_time_entry_custom_fields
449 @all_time_entry_custom_fields ||= (TimeEntryCustomField.for_all + time_entry_custom_fields).uniq.sort
425 end 450 end
426 451
427 def project 452 def project
428 self 453 self
429 end 454 end
507 end 532 end
508 533
509 def enabled_module_names=(module_names) 534 def enabled_module_names=(module_names)
510 if module_names && module_names.is_a?(Array) 535 if module_names && module_names.is_a?(Array)
511 module_names = module_names.collect(&:to_s).reject(&:blank?) 536 module_names = module_names.collect(&:to_s).reject(&:blank?)
512 # remove disabled modules 537 self.enabled_modules = module_names.collect {|name| enabled_modules.detect {|mod| mod.name == name} || EnabledModule.new(:name => name)}
513 enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
514 # add new modules
515 module_names.reject {|name| module_enabled?(name)}.each {|name| enabled_modules << EnabledModule.new(:name => name)}
516 else 538 else
517 enabled_modules.clear 539 enabled_modules.clear
518 end 540 end
519 end 541 end
520 542
619 end 641 end
620 end 642 end
621 643
622 private 644 private
623 645
624 # Destroys children before destroying self
625 def destroy_children
626 children.each do |child|
627 child.destroy
628 end
629 end
630
631 # Copies wiki from +project+ 646 # Copies wiki from +project+
632 def copy_wiki(project) 647 def copy_wiki(project)
633 # Check that the source project has a wiki first 648 # Check that the source project has a wiki first
634 unless project.wiki.nil? 649 unless project.wiki.nil?
635 self.wiki ||= Wiki.new 650 self.wiki ||= Wiki.new
672 self.issue_categories << new_issue_category 687 self.issue_categories << new_issue_category
673 end 688 end
674 end 689 end
675 690
676 # Copies issues from +project+ 691 # Copies issues from +project+
692 # Note: issues assigned to a closed version won't be copied due to validation rules
677 def copy_issues(project) 693 def copy_issues(project)
678 # Stores the source issue id as a key and the copied issues as the 694 # Stores the source issue id as a key and the copied issues as the
679 # value. Used to map the two togeather for issue relations. 695 # value. Used to map the two togeather for issue relations.
680 issues_map = {} 696 issues_map = {}
681 697