2019-03-17 13:36:03 +00:00
# frozen_string_literal: true
2019-03-15 01:32:57 +00:00
2011-03-15 15:39:59 +00:00
# Redmine - project management software
2019-05-25 07:36:06 +00:00
# Copyright (C) 2006-2019 Jean-Philippe Lang
2007-03-12 17:59:02 +00:00
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
2011-08-21 08:46:33 +00:00
#
2007-03-12 17:59:02 +00:00
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
2011-08-21 08:46:33 +00:00
#
2007-03-12 17:59:02 +00:00
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Project < ActiveRecord :: Base
2010-12-12 13:19:07 +00:00
include Redmine :: SafeAttributes
2015-01-07 20:19:49 +00:00
include Redmine :: NestedSet :: ProjectNestedSet
2011-08-21 08:46:33 +00:00
2007-05-27 17:42:04 +00:00
# Project statuses
STATUS_ACTIVE = 1
2012-06-25 17:49:35 +00:00
STATUS_CLOSED = 5
2007-05-27 17:42:04 +00:00
STATUS_ARCHIVED = 9
2011-08-21 08:46:33 +00:00
2010-11-14 12:33:14 +00:00
# Maximum length for project identifiers
IDENTIFIER_MAX_LENGTH = 100
2011-08-21 08:46:33 +00:00
2014-04-07 08:07:43 +00:00
# Specific overridden Activities
2009-11-21 12:00:49 +00:00
has_many :time_entry_activities
2015-06-18 19:12:17 +00:00
has_many :memberships , :class_name = > 'Member' , :inverse_of = > :project
# Memberships of active users only
2014-10-22 17:37:16 +00:00
has_many :members ,
2015-06-18 19:12:17 +00:00
lambda { joins ( :principal ) . where ( :users = > { :type = > 'User' , :status = > Principal :: STATUS_ACTIVE } ) }
2007-09-14 11:34:08 +00:00
has_many :enabled_modules , :dependent = > :delete_all
2014-10-29 22:30:25 +00:00
has_and_belongs_to_many :trackers , lambda { order ( :position ) }
2014-10-22 17:37:16 +00:00
has_many :issues , :dependent = > :destroy
2007-08-16 10:38:23 +00:00
has_many :issue_changes , :through = > :issues , :source = > :journals
2017-01-31 17:54:46 +00:00
has_many :versions , :dependent = > :destroy
2015-11-01 08:11:36 +00:00
belongs_to :default_version , :class_name = > 'Version'
2017-04-08 08:02:06 +00:00
belongs_to :default_assigned_to , :class_name = > 'Principal'
2013-12-18 18:39:09 +00:00
has_many :time_entries , :dependent = > :destroy
2017-01-31 18:15:32 +00:00
has_many :queries , :dependent = > :delete_all
2007-03-12 17:59:02 +00:00
has_many :documents , :dependent = > :destroy
2014-10-22 17:37:16 +00:00
has_many :news , lambda { includes ( :author ) } , :dependent = > :destroy
2017-01-31 18:31:25 +00:00
has_many :issue_categories , lambda { order ( :name ) } , :dependent = > :delete_all
2017-06-10 23:47:15 +00:00
has_many :boards , lambda { order ( :position ) } , :inverse_of = > :project , :dependent = > :destroy
2017-01-31 18:31:25 +00:00
has_one :repository , lambda { where ( :is_default = > true ) }
2012-01-15 18:19:19 +00:00
has_many :repositories , :dependent = > :destroy
2007-09-07 20:07:54 +00:00
has_many :changesets , :through = > :repository
2007-03-12 17:59:02 +00:00
has_one :wiki , :dependent = > :destroy
2007-11-12 17:23:14 +00:00
# Custom field for the project issues
2011-08-21 08:46:33 +00:00
has_and_belongs_to_many :issue_custom_fields ,
2017-01-31 18:31:25 +00:00
lambda { order ( :position ) } ,
2007-11-12 17:23:14 +00:00
:class_name = > 'IssueCustomField' ,
:join_table = > " #{ table_name_prefix } custom_fields_projects #{ table_name_suffix } " ,
:association_foreign_key = > 'custom_field_id'
2011-08-21 08:46:33 +00:00
2008-12-09 18:00:27 +00:00
acts_as_attachable :view_permission = > :view_files ,
2014-11-29 13:41:53 +00:00
:edit_permission = > :manage_files ,
2008-12-09 18:00:27 +00:00
:delete_permission = > :manage_files
2007-09-27 17:28:22 +00:00
2008-06-27 20:13:56 +00:00
acts_as_customizable
2015-01-09 21:06:09 +00:00
acts_as_searchable :columns = > [ 'name' , 'identifier' , 'description' ] , :project_key = > " #{ Project . table_name } .id " , :permission = > nil
2007-09-27 17:28:22 +00:00
acts_as_event :title = > Proc . new { | o | " #{ l ( :label_project ) } : #{ o . name } " } ,
2010-02-24 21:08:08 +00:00
:url = > Proc . new { | o | { :controller = > 'projects' , :action = > 'show' , :id = > o } } ,
2008-07-12 09:42:18 +00:00
:author = > nil
2007-09-27 17:28:22 +00:00
2008-01-20 18:37:51 +00:00
validates_presence_of :name , :identifier
2014-12-06 11:23:05 +00:00
validates_uniqueness_of :identifier , :if = > Proc . new { | p | p . identifier_changed? }
2010-11-14 12:33:14 +00:00
validates_length_of :name , :maximum = > 255
2008-05-25 13:37:29 +00:00
validates_length_of :homepage , :maximum = > 255
2016-11-19 10:50:03 +00:00
validates_length_of :identifier , :maximum = > IDENTIFIER_MAX_LENGTH
2014-04-07 08:07:43 +00:00
# downcase letters, digits, dashes but not digits only
2012-12-08 10:59:31 +00:00
validates_format_of :identifier , :with = > / \ A(?! \ d+$)[a-z0-9 \ -_]* \ z / , :if = > Proc . new { | p | p . identifier_changed? }
2009-07-18 08:06:51 +00:00
# reserved words
validates_exclusion_of :identifier , :in = > %w( new )
2015-01-07 22:19:57 +00:00
validate :validate_parent
2009-07-18 08:06:51 +00:00
2017-07-23 11:26:04 +00:00
after_save :update_inherited_members , :if = > Proc . new { | project | project . saved_change_to_inherit_members? }
after_save :remove_inherited_member_roles , :add_inherited_member_roles , :if = > Proc . new { | project | project . saved_change_to_parent_id? }
after_update :update_versions_from_hierarchy_change , :if = > Proc . new { | project | project . saved_change_to_parent_id? }
2011-03-20 11:46:01 +00:00
before_destroy :delete_all_members
2008-09-13 16:31:11 +00:00
2012-12-07 17:59:20 +00:00
scope :has_module , lambda { | mod |
where ( " #{ Project . table_name } .id IN (SELECT em.project_id FROM #{ EnabledModule . table_name } em WHERE em.name=?) " , mod . to_s )
}
scope :active , lambda { where ( :status = > STATUS_ACTIVE ) }
scope :status , lambda { | arg | where ( arg . blank? ? nil : { :status = > arg . to_i } ) }
scope :all_public , lambda { where ( :is_public = > true ) }
scope :visible , lambda { | * args | where ( Project . visible_condition ( args . shift || User . current , * args ) ) }
2013-04-21 04:56:26 +00:00
scope :allowed_to , lambda { | * args |
2019-08-20 03:27:15 +00:00
user = args . first . is_a? ( Symbol ) ? User . current : args . shift
permission = args . shift
2012-12-07 17:59:20 +00:00
where ( Project . allowed_to_condition ( user , permission , * args ) )
2012-01-21 14:26:51 +00:00
}
2012-04-26 23:51:10 +00:00
scope :like , lambda { | arg |
2017-04-04 17:53:48 +00:00
if arg . present?
pattern = " % #{ arg . to_s . strip } % "
where ( " LOWER(identifier) LIKE LOWER(:p) OR LOWER(name) LIKE LOWER(:p) " , :p = > pattern )
2011-12-04 22:31:02 +00:00
end
}
2014-11-02 20:44:11 +00:00
scope :sorted , lambda { order ( :lft ) }
2015-09-20 11:38:01 +00:00
scope :having_trackers , lambda {
where ( " #{ Project . table_name } .id IN (SELECT DISTINCT project_id FROM #{ table_name_prefix } projects_trackers #{ table_name_suffix } ) " )
}
2011-08-21 08:46:33 +00:00
2011-12-18 13:26:20 +00:00
def initialize ( attributes = nil , * args )
2010-12-03 16:15:16 +00:00
super
2011-08-21 08:46:33 +00:00
2010-12-03 16:15:16 +00:00
initialized = ( attributes || { } ) . stringify_keys
2011-08-21 08:46:33 +00:00
if ! initialized . key? ( 'identifier' ) && Setting . sequential_project_identifiers?
2010-12-03 16:15:16 +00:00
self . identifier = Project . next_identifier
end
if ! initialized . key? ( 'is_public' )
self . is_public = Setting . default_projects_public?
end
if ! initialized . key? ( 'enabled_module_names' )
self . enabled_module_names = Setting . default_projects_modules
end
if ! initialized . key? ( 'trackers' ) && ! initialized . key? ( 'tracker_ids' )
2013-02-15 08:28:34 +00:00
default = Setting . default_projects_tracker_ids
if default . is_a? ( Array )
2014-10-22 17:37:16 +00:00
self . trackers = Tracker . where ( :id = > default . map ( & :to_i ) ) . sorted . to_a
2013-02-15 08:28:34 +00:00
else
2014-10-22 17:37:16 +00:00
self . trackers = Tracker . sorted . to_a
2013-02-15 08:28:34 +00:00
end
2010-12-03 16:15:16 +00:00
end
end
2011-08-21 08:46:33 +00:00
2007-04-01 19:43:59 +00:00
def identifier = ( identifier )
super unless identifier_frozen?
end
2011-08-21 08:46:33 +00:00
2007-04-01 19:43:59 +00:00
def identifier_frozen?
2012-05-25 16:43:18 +00:00
errors [ :identifier ] . blank? && ! ( new_record? || identifier . blank? )
2007-04-01 19:43:59 +00:00
end
2007-11-08 19:00:37 +00:00
2007-03-12 17:59:02 +00:00
# returns latest created projects
# non public projects will be returned only if user is a member of those
def self . latest ( user = nil , count = 5 )
2015-11-08 21:31:18 +00:00
visible ( user ) . limit ( count ) .
order ( :created_on = > :desc ) .
where ( " #{ table_name } .created_on >= ? " , 30 . days . ago ) .
to_a
2013-02-17 10:14:08 +00:00
end
2007-03-12 17:59:02 +00:00
2011-07-09 11:41:04 +00:00
# Returns true if the project is visible to +user+ or to the current user.
def visible? ( user = User . current )
user . allowed_to? ( :view_project , self )
end
2011-08-21 08:46:33 +00:00
2011-04-05 12:50:19 +00:00
# Returns a SQL conditions string used to find all projects visible by the specified user.
2009-03-21 00:39:53 +00:00
#
# Examples:
2011-04-05 12:50:19 +00:00
# Project.visible_condition(admin) => "projects.status = 1"
# Project.visible_condition(normal_user) => "((projects.status = 1) AND (projects.is_public = 1 OR projects.id IN (1,3,4)))"
# Project.visible_condition(anonymous) => "((projects.status = 1) AND (projects.is_public = 1))"
def self . visible_condition ( user , options = { } )
allowed_to_condition ( user , :view_project , options )
2007-05-27 17:42:04 +00:00
end
2011-08-21 08:46:33 +00:00
2011-04-05 12:50:19 +00:00
# Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+
#
# Valid options:
2017-01-19 20:16:49 +00:00
# * :skip_pre_condition => true don't check that the module is enabled (eg. when the condition is already set elsewhere in the query)
# * :project => project limit the condition to project
# * :with_subprojects => true limit the condition to project and its subprojects
# * :member => true limit the condition to the user projects
2008-03-11 19:33:38 +00:00
def self . allowed_to_condition ( user , permission , options = { } )
2012-06-25 17:49:35 +00:00
perm = Redmine :: AccessControl . permission ( permission )
base_statement = ( perm && perm . read? ? " #{ Project . table_name } .status <> #{ Project :: STATUS_ARCHIVED } " : " #{ Project . table_name } .status = #{ Project :: STATUS_ACTIVE } " )
2017-01-19 20:16:49 +00:00
if ! options [ :skip_pre_condition ] && perm && perm . project_module
2012-06-25 17:49:35 +00:00
# If the permission belongs to a project module, make sure the module is enabled
2019-03-17 13:36:03 +00:00
base_statement += " AND EXISTS (SELECT 1 AS one FROM #{ EnabledModule . table_name } em WHERE em.project_id = #{ Project . table_name } .id AND em.name=' #{ perm . project_module } ') "
2008-09-25 18:51:03 +00:00
end
2014-10-23 17:23:02 +00:00
if project = options [ :project ]
project_statement = project . project_condition ( options [ :with_subprojects ] )
2008-03-11 19:33:38 +00:00
base_statement = " ( #{ project_statement } ) AND ( #{ base_statement } ) "
end
2011-08-21 08:46:33 +00:00
2008-02-27 20:50:19 +00:00
if user . admin?
2011-03-15 15:39:59 +00:00
base_statement
2008-02-27 20:50:19 +00:00
else
2011-03-15 15:39:59 +00:00
statement_by_role = { }
2011-04-14 17:49:20 +00:00
unless options [ :member ]
2013-06-12 19:38:43 +00:00
role = user . builtin_role
2011-04-14 17:49:20 +00:00
if role . allowed_to? ( permission )
2015-07-25 06:44:01 +00:00
s = " #{ Project . table_name } .is_public = #{ connection . quoted_true } "
if user . id
2016-08-30 19:24:03 +00:00
group = role . anonymous? ? Group . anonymous : Group . non_member
principal_ids = [ user . id , group . id ] . compact
s = " ( #{ s } AND #{ Project . table_name } .id NOT IN (SELECT project_id FROM #{ Member . table_name } WHERE user_id IN ( #{ principal_ids . join ( ',' ) } ))) "
2015-07-25 06:44:01 +00:00
end
statement_by_role [ role ] = s
2011-03-15 15:39:59 +00:00
end
2011-04-14 17:49:20 +00:00
end
2017-01-03 20:53:57 +00:00
user . project_ids_by_role . each do | role , project_ids |
if role . allowed_to? ( permission ) && project_ids . any?
statement_by_role [ role ] = " #{ Project . table_name } .id IN ( #{ project_ids . join ( ',' ) } ) "
2009-11-15 13:22:08 +00:00
end
2008-07-10 12:31:49 +00:00
end
2011-03-15 15:39:59 +00:00
if statement_by_role . empty?
" 1=0 "
else
2011-04-11 17:53:15 +00:00
if block_given?
statement_by_role . each do | role , statement |
if s = yield ( role , user )
statement_by_role [ role ] = " ( #{ statement } AND ( #{ s } )) "
end
end
end
2011-03-15 15:39:59 +00:00
" (( #{ base_statement } ) AND ( #{ statement_by_role . values . join ( ' OR ' ) } )) "
end
2008-02-27 20:50:19 +00:00
end
end
2009-03-21 00:39:53 +00:00
2014-09-28 14:51:08 +00:00
def override_roles ( role )
2015-06-18 19:12:17 +00:00
@override_members || = memberships .
joins ( :principal ) .
where ( :users = > { :type = > [ 'GroupAnonymous' , 'GroupNonMember' ] } ) . to_a
2015-01-24 08:54:04 +00:00
2014-10-22 17:37:16 +00:00
group_class = role . anonymous? ? GroupAnonymous : GroupNonMember
2015-01-24 08:54:04 +00:00
member = @override_members . detect { | m | m . principal . is_a? group_class }
2014-10-22 17:37:16 +00:00
member ? member . roles . to_a : [ role ]
2014-09-28 14:51:08 +00:00
end
2013-03-12 18:07:37 +00:00
def principals
2016-07-14 07:15:13 +00:00
@principals || = Principal . active . joins ( :members ) . where ( " #{ Member . table_name } .project_id = ? " , id ) . distinct
2013-03-12 18:07:37 +00:00
end
2013-03-12 18:32:36 +00:00
def users
2016-07-14 07:15:13 +00:00
@users || = User . active . joins ( :members ) . where ( " #{ Member . table_name } .project_id = ? " , id ) . distinct
2013-03-12 18:32:36 +00:00
end
2009-10-21 22:34:45 +00:00
# Returns the Systemwide and project specific activities
def activities ( include_inactive = false )
2015-05-30 10:05:43 +00:00
t = TimeEntryActivity . table_name
scope = TimeEntryActivity . where ( " #{ t } .project_id IS NULL OR #{ t } .project_id = ? " , id )
overridden_activity_ids = self . time_entry_activities . pluck ( :parent_id ) . compact
if overridden_activity_ids . any?
scope = scope . where ( " #{ t } .id NOT IN (?) " , overridden_activity_ids )
end
unless include_inactive
scope = scope . active
2009-10-21 22:34:45 +00:00
end
2015-05-30 10:05:43 +00:00
scope
2009-10-21 22:34:45 +00:00
end
2009-10-21 22:34:39 +00:00
2017-07-23 11:26:04 +00:00
# Creates or updates project time entry activities
def update_or_create_time_entry_activities ( activities )
transaction do
activities . each do | id , activity |
update_or_create_time_entry_activity ( id , activity )
end
end
end
2009-10-21 22:34:52 +00:00
# Will create a new Project specific Activity or update an existing one
#
# This will raise a ActiveRecord::Rollback if the TimeEntryActivity
# does not successfully save.
def update_or_create_time_entry_activity ( id , activity_hash )
2009-10-21 22:34:45 +00:00
if activity_hash . respond_to? ( :has_key? ) && activity_hash . has_key? ( 'parent_id' )
2009-10-21 22:34:52 +00:00
self . create_time_entry_activity_if_needed ( activity_hash )
2009-10-21 22:34:39 +00:00
else
2009-10-21 22:34:45 +00:00
activity = project . time_entry_activities . find_by_id ( id . to_i )
2019-08-17 08:14:36 +00:00
activity . update ( activity_hash ) if activity
2009-10-21 22:34:45 +00:00
end
end
2011-08-21 08:46:33 +00:00
2009-10-21 22:34:52 +00:00
# Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
#
# This will raise a ActiveRecord::Rollback if the TimeEntryActivity
# does not successfully save.
def create_time_entry_activity_if_needed ( activity )
2009-10-21 22:34:45 +00:00
if activity [ 'parent_id' ]
parent_activity = TimeEntryActivity . find ( activity [ 'parent_id' ] )
activity [ 'name' ] = parent_activity . name
activity [ 'position' ] = parent_activity . position
2014-04-06 13:32:35 +00:00
if Enumeration . overriding_change? ( activity , parent_activity )
2009-10-21 22:34:52 +00:00
project_activity = self . time_entry_activities . create ( activity )
if project_activity . new_record?
2014-04-06 13:32:35 +00:00
raise ActiveRecord :: Rollback , " Overriding TimeEntryActivity was not successfully saved "
2009-10-21 22:34:52 +00:00
else
2014-01-10 12:49:20 +00:00
self . time_entries .
2015-10-19 17:18:18 +00:00
where ( :activity_id = > parent_activity . id ) .
update_all ( :activity_id = > project_activity . id )
2009-10-21 22:34:52 +00:00
end
2009-10-21 22:34:45 +00:00
end
2009-10-21 22:34:39 +00:00
end
end
2009-03-21 00:39:53 +00:00
# Returns a :conditions SQL string that can be used to find the issues associated with this project.
#
# Examples:
# project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
# project.project_condition(false) => "projects.id = 1"
2008-03-27 18:22:12 +00:00
def project_condition ( with_subprojects )
cond = " #{ Project . table_name } .id = #{ id } "
2009-01-24 11:31:15 +00:00
cond = " ( #{ cond } OR ( #{ Project . table_name } .lft > #{ lft } AND #{ Project . table_name } .rgt < #{ rgt } )) " if with_subprojects
2008-03-27 18:22:12 +00:00
cond
end
2011-08-21 08:46:33 +00:00
2007-12-17 21:00:56 +00:00
def self . find ( * args )
2019-03-27 02:15:24 +00:00
if args . first && args . first . is_a? ( String ) && ! / ^ \ d*$ / . match? ( args . first )
2007-12-17 21:00:56 +00:00
project = find_by_identifier ( * args )
raise ActiveRecord :: RecordNotFound , " Couldn't find Project with identifier= #{ args . first } " if project . nil?
project
else
super
end
end
2011-08-21 08:46:33 +00:00
2012-04-25 17:17:49 +00:00
def self . find_by_param ( * args )
self . find ( * args )
end
2013-02-28 18:47:25 +00:00
alias :base_reload :reload
2012-02-24 18:38:05 +00:00
def reload ( * args )
2013-03-12 18:07:37 +00:00
@principals = nil
2013-03-12 18:32:36 +00:00
@users = nil
2012-02-24 18:38:05 +00:00
@shared_versions = nil
@rolled_up_versions = nil
@rolled_up_trackers = nil
2017-05-27 09:12:42 +00:00
@rolled_up_statuses = nil
2017-04-03 14:04:04 +00:00
@rolled_up_custom_fields = nil
2012-02-24 18:38:05 +00:00
@all_issue_custom_fields = nil
@all_time_entry_custom_fields = nil
@to_param = nil
@allowed_parents = nil
@allowed_permissions = nil
@actions_allowed = nil
2013-01-06 14:03:49 +00:00
@start_date = nil
@due_date = nil
2014-09-28 14:51:08 +00:00
@override_members = nil
2014-10-25 11:14:57 +00:00
@assignable_users = nil
2013-02-28 18:47:25 +00:00
base_reload ( * args )
2012-02-24 18:38:05 +00:00
end
2007-12-17 21:00:56 +00:00
def to_param
2015-09-20 11:50:21 +00:00
if new_record?
nil
else
# id is used for projects with a numeric identifier (compatibility)
2019-03-27 02:15:24 +00:00
@to_param || = ( %r{ ^ \ d*$ } . match? ( identifier . to_s ) ? id . to_s : identifier )
2015-09-20 11:50:21 +00:00
end
2007-12-17 21:00:56 +00:00
end
2011-08-21 08:46:33 +00:00
2007-05-27 17:42:04 +00:00
def active?
self . status == STATUS_ACTIVE
end
2011-08-21 08:46:33 +00:00
2016-12-13 20:01:57 +00:00
def closed?
self . status == STATUS_CLOSED
end
2010-10-23 09:48:01 +00:00
def archived?
self . status == STATUS_ARCHIVED
end
2011-08-21 08:46:33 +00:00
2009-12-06 10:28:20 +00:00
# Archives the project and its descendants
2007-05-27 17:42:04 +00:00
def archive
2009-12-06 10:28:20 +00:00
# Check that there is no issue of a non descendant project that is assigned
# to one of the project or descendant versions
2014-12-02 20:32:09 +00:00
version_ids = self_and_descendants . joins ( :versions ) . pluck ( " #{ Version . table_name } .id " )
if version_ids . any? &&
2012-12-03 18:21:32 +00:00
Issue .
2017-06-03 09:22:41 +00:00
joins ( :project ) .
2012-12-03 18:21:32 +00:00
where ( " #{ Project . table_name } .lft < ? OR #{ Project . table_name } .rgt > ? " , lft , rgt ) .
2014-12-02 20:32:09 +00:00
where ( :fixed_version_id = > version_ids ) .
2012-12-03 18:21:32 +00:00
exists?
2009-12-06 10:28:20 +00:00
return false
2007-03-12 17:59:02 +00:00
end
2009-12-06 10:28:20 +00:00
Project . transaction do
archive!
end
true
2007-05-27 17:42:04 +00:00
end
2011-08-21 08:46:33 +00:00
2018-12-02 11:13:28 +00:00
# Unarchives the project and its archived ancestors
2007-05-27 17:42:04 +00:00
def unarchive
2018-12-02 11:13:28 +00:00
new_status = ancestors . any? ( & :closed? ) ? STATUS_CLOSED : STATUS_ACTIVE
self_and_ancestors . status ( STATUS_ARCHIVED ) . update_all :status = > new_status
reload
2007-05-27 17:42:04 +00:00
end
2011-08-21 08:46:33 +00:00
2012-06-25 17:49:35 +00:00
def close
self_and_descendants . status ( STATUS_ACTIVE ) . update_all :status = > STATUS_CLOSED
end
def reopen
self_and_descendants . status ( STATUS_CLOSED ) . update_all :status = > STATUS_ACTIVE
end
2009-01-24 11:31:15 +00:00
# Returns an array of projects the project can be moved to
2009-11-15 13:22:08 +00:00
# by the current user
2015-01-07 22:19:57 +00:00
def allowed_parents ( user = User . current )
2009-11-15 13:22:08 +00:00
return @allowed_parents if @allowed_parents
2015-01-07 22:19:57 +00:00
@allowed_parents = Project . allowed_to ( user , :add_subprojects ) . to_a
2009-12-24 16:18:15 +00:00
@allowed_parents = @allowed_parents - self_and_descendants
2015-01-07 22:19:57 +00:00
if user . allowed_to? ( :add_project , nil , :global = > true ) || ( ! new_record? && parent . nil? )
2009-12-24 16:18:15 +00:00
@allowed_parents << nil
end
2009-11-15 13:22:08 +00:00
unless parent . nil? || @allowed_parents . empty? || @allowed_parents . include? ( parent )
@allowed_parents << parent
end
@allowed_parents
end
2011-08-21 08:46:33 +00:00
2015-01-07 22:33:25 +00:00
# Sets the parent of the project and saves the project
2009-01-24 11:31:15 +00:00
# Argument can be either a Project, a String, a Fixnum or nil
def set_parent! ( p )
2015-01-07 22:19:57 +00:00
if p . is_a? ( Project )
2015-01-07 20:19:49 +00:00
self . parent = p
2009-01-24 11:31:15 +00:00
else
2015-01-07 22:19:57 +00:00
self . parent_id = p
2009-01-24 11:31:15 +00:00
end
2015-01-07 22:19:57 +00:00
save
2007-03-12 17:59:02 +00:00
end
2011-08-21 08:46:33 +00:00
2016-06-18 06:42:25 +00:00
# Returns a scope of the trackers used by the project and its active sub projects
def rolled_up_trackers ( include_subprojects = true )
if include_subprojects
@rolled_up_trackers || = rolled_up_trackers_base_scope .
where ( " #{ Project . table_name } .lft >= ? AND #{ Project . table_name } .rgt <= ? " , lft , rgt )
else
rolled_up_trackers_base_scope .
where ( :projects = > { :id = > id } )
end
end
def rolled_up_trackers_base_scope
Tracker .
joins ( projects : :enabled_modules ) .
where ( " #{ Project . table_name } .status <> ? " , STATUS_ARCHIVED ) .
where ( :enabled_modules = > { :name = > 'issue_tracking' } ) .
2016-07-14 07:15:13 +00:00
distinct .
2016-06-18 06:42:25 +00:00
sorted
2008-01-16 21:27:48 +00:00
end
2011-08-21 08:46:33 +00:00
2017-05-27 09:12:42 +00:00
def rolled_up_statuses
issue_status_ids = WorkflowTransition .
2017-05-27 09:13:35 +00:00
where ( :tracker_id = > rolled_up_trackers . map ( & :id ) ) .
2017-05-27 09:12:42 +00:00
distinct .
pluck ( :old_status_id , :new_status_id ) .
flatten .
uniq
IssueStatus . where ( :id = > issue_status_ids ) . sorted
end
2009-11-09 18:53:12 +00:00
# Closes open and locked project versions that are completed
def close_completed_versions
Version . transaction do
2014-01-27 02:18:30 +00:00
versions . where ( :status = > %w( open locked ) ) . each do | version |
2009-11-09 18:53:12 +00:00
if version . completed?
version . update_attribute ( :status , 'closed' )
end
end
end
end
2010-05-27 17:16:10 +00:00
# Returns a scope of the Versions on subprojects
def rolled_up_versions
@rolled_up_versions || =
2013-05-18 19:58:43 +00:00
Version .
2014-10-22 17:37:16 +00:00
joins ( :project ) .
2013-05-18 19:58:43 +00:00
where ( " #{ Project . table_name } .lft >= ? AND #{ Project . table_name } .rgt <= ? AND #{ Project . table_name } .status <> ? " , lft , rgt , STATUS_ARCHIVED )
2010-05-27 17:16:10 +00:00
end
2011-08-21 08:46:33 +00:00
2009-12-06 10:28:20 +00:00
# Returns a scope of the Versions used by the project
def shared_versions
2012-02-13 18:57:12 +00:00
if new_record?
2013-05-18 19:58:43 +00:00
Version .
2014-10-22 17:37:16 +00:00
joins ( :project ) .
preload ( :project ) .
2013-05-18 19:58:43 +00:00
where ( " #{ Project . table_name } .status <> ? AND #{ Version . table_name } .sharing = 'system' " , STATUS_ARCHIVED )
2012-02-13 18:57:12 +00:00
else
@shared_versions || = begin
r = root? ? self : root
2013-05-18 19:58:43 +00:00
Version .
2014-10-22 17:37:16 +00:00
joins ( :project ) .
preload ( :project ) .
2013-05-18 19:58:43 +00:00
where ( " #{ Project . table_name } .id = #{ id } " +
" OR ( #{ Project . table_name } .status <> #{ Project :: STATUS_ARCHIVED } AND ( " +
" #{ Version . table_name } .sharing = 'system' " +
" OR ( #{ Project . table_name } .lft >= #{ r . lft } AND #{ Project . table_name } .rgt <= #{ r . rgt } AND #{ Version . table_name } .sharing = 'tree') " +
" OR ( #{ Project . table_name } .lft < #{ lft } AND #{ Project . table_name } .rgt > #{ rgt } AND #{ Version . table_name } .sharing IN ('hierarchy', 'descendants')) " +
" OR ( #{ Project . table_name } .lft > #{ lft } AND #{ Project . table_name } .rgt < #{ rgt } AND #{ Version . table_name } .sharing = 'hierarchy') " +
" )) " )
2012-02-13 18:57:12 +00:00
end
2011-03-28 20:29:43 +00:00
end
2009-12-06 10:28:20 +00:00
end
2009-05-12 19:04:52 +00:00
# Returns a hash of project users grouped by role
def users_by_role
2014-01-27 02:18:30 +00:00
members . includes ( :user , :roles ) . inject ( { } ) do | h , m |
2009-05-12 19:04:52 +00:00
m . roles . each do | r |
h [ r ] || = [ ]
h [ r ] << m . user
end
h
end
end
2011-08-21 08:46:33 +00:00
2014-11-02 20:55:02 +00:00
# Adds user as a project member with the default role
# Used for when a non-admin user creates a project
def add_default_member ( user )
2016-08-30 19:32:52 +00:00
role = self . class . default_member_role
2014-11-02 20:55:02 +00:00
member = Member . new ( :project = > self , :principal = > user , :roles = > [ role ] )
self . members << member
member
end
2017-07-04 04:00:23 +00:00
# Default role that is given to non-admin users that
# create a project
2016-08-30 19:32:52 +00:00
def self . default_member_role
Role . givable . find_by_id ( Setting . new_project_user_role_id . to_i ) || Role . givable . first
end
2007-12-10 17:58:07 +00:00
# Deletes all project's members
def delete_all_members
2009-05-10 10:54:31 +00:00
me , mr = Member . table_name , MemberRole . table_name
2014-10-22 17:37:16 +00:00
self . class . connection . delete ( " DELETE FROM #{ mr } WHERE #{ mr } .member_id IN (SELECT #{ me } .id FROM #{ me } WHERE #{ me } .project_id = #{ id } ) " )
2016-07-16 10:30:45 +00:00
Member . where ( :project_id = > id ) . delete_all
2007-12-10 17:58:07 +00:00
end
2011-08-21 08:46:33 +00:00
2014-10-25 11:14:16 +00:00
# Return a Principal scope of users/groups issues can be assigned to
2016-06-28 20:31:08 +00:00
def assignable_users ( tracker = nil )
return @assignable_users [ tracker ] if @assignable_users && @assignable_users [ tracker ]
2014-09-28 14:51:08 +00:00
types = [ 'User' ]
types << 'Group' if Setting . issue_group_assignment?
2016-06-28 20:31:08 +00:00
scope = Principal .
2014-10-25 11:14:16 +00:00
active .
joins ( :members = > :roles ) .
where ( :type = > types , :members = > { :project_id = > id } , :roles = > { :assignable = > true } ) .
2016-07-14 07:15:13 +00:00
distinct .
2014-10-25 11:14:16 +00:00
sorted
2016-06-28 20:31:08 +00:00
if tracker
# Rejects users that cannot the view the tracker
roles = Role . where ( :assignable = > true ) . select { | role | role . permissions_tracker? ( :view_issues , tracker ) }
scope = scope . where ( :roles = > { :id = > roles . map ( & :id ) } )
end
@assignable_users || = { }
@assignable_users [ tracker ] = scope
2007-10-09 19:07:19 +00:00
end
2011-08-21 08:46:33 +00:00
2014-04-06 14:14:23 +00:00
# Returns the mail addresses of users that should be always notified on project events
2007-10-20 12:47:05 +00:00
def recipients
2010-10-10 21:42:24 +00:00
notified_users . collect { | user | user . mail }
2007-10-20 12:47:05 +00:00
end
2011-08-21 08:46:33 +00:00
2009-12-03 21:28:14 +00:00
# Returns the users that should be notified on project events
def notified_users
2010-10-10 21:42:24 +00:00
# TODO: User part should be extracted to User#notify_about?
2016-06-12 12:21:57 +00:00
members . preload ( :principal ) . select { | m | m . principal . present? && ( m . mail_notification? || m . principal . mail_notification == 'all' ) } . collect { | m | m . principal }
2009-12-03 21:28:14 +00:00
end
2011-08-21 08:46:33 +00:00
2013-06-01 10:26:17 +00:00
# Returns a scope of all custom fields enabled for project issues
2014-04-06 14:14:23 +00:00
# (explicitly associated custom fields and custom fields enabled for all projects)
2008-06-27 20:13:56 +00:00
def all_issue_custom_fields
2015-09-20 20:25:16 +00:00
if new_record?
@all_issue_custom_fields || = IssueCustomField .
sorted .
where ( " is_for_all = ? OR id IN (?) " , true , issue_custom_field_ids )
else
@all_issue_custom_fields || = IssueCustomField .
sorted .
where ( " is_for_all = ? OR id IN (SELECT DISTINCT cfp.custom_field_id " +
" FROM #{ table_name_prefix } custom_fields_projects #{ table_name_suffix } cfp " +
" WHERE cfp.project_id = ?) " , true , id )
end
2007-03-12 17:59:02 +00:00
end
2011-04-04 11:53:29 +00:00
2017-04-03 14:04:04 +00:00
# Returns a scope of all custom fields enabled for issues of the project
# and its subprojects
def rolled_up_custom_fields
if leaf?
all_issue_custom_fields
else
@rolled_up_custom_fields || = IssueCustomField .
sorted .
where ( " is_for_all = ? OR EXISTS (SELECT 1 " +
" FROM #{ table_name_prefix } custom_fields_projects #{ table_name_suffix } cfp " +
" JOIN #{ Project . table_name } p ON p.id = cfp.project_id " +
" WHERE cfp.custom_field_id = #{ CustomField . table_name } .id " +
" AND p.lft >= ? AND p.rgt <= ?) " , true , lft , rgt )
end
end
2008-05-18 16:15:22 +00:00
def project
self
end
2011-08-21 08:46:33 +00:00
2007-09-05 17:24:22 +00:00
def <=> ( project )
2015-08-12 12:54:06 +00:00
name . casecmp ( project . name )
2007-09-05 17:24:22 +00:00
end
2011-08-21 08:46:33 +00:00
2008-01-20 18:37:51 +00:00
def to_s
name
end
2011-08-21 08:46:33 +00:00
2008-01-20 18:37:51 +00:00
# Returns a short description of the projects (first lines)
def short_description ( length = 255 )
2009-01-24 11:31:15 +00:00
description . gsub ( / ^(.{ #{ length } }[^ \ n \ r]*).*$ /m , '\1...' ) . strip if description
2008-01-20 18:37:51 +00:00
end
2010-09-10 03:09:02 +00:00
2010-09-10 23:07:10 +00:00
def css_classes
2019-03-17 13:36:03 +00:00
s = + 'project'
2010-09-10 23:07:10 +00:00
s << ' root' if root?
s << ' child' if child?
s << ( leaf? ? ' leaf' : ' parent' )
2018-07-22 05:02:12 +00:00
s << ' public' if is_public?
2012-06-25 17:49:35 +00:00
unless active?
if archived?
s << ' archived'
else
s << ' closed'
end
end
2010-09-10 23:07:10 +00:00
s
end
2010-09-10 03:09:02 +00:00
# The earliest start date of a project, based on it's issues and versions
def start_date
2013-01-06 14:03:49 +00:00
@start_date || = [
2010-12-07 19:42:36 +00:00
issues . minimum ( 'start_date' ) ,
2013-01-06 14:03:49 +00:00
shared_versions . minimum ( 'effective_date' ) ,
Issue . fixed_version ( shared_versions ) . minimum ( 'start_date' )
] . compact . min
2010-09-10 03:09:02 +00:00
end
2010-09-26 17:35:18 +00:00
# The latest due date of an issue or version
2010-09-10 03:09:02 +00:00
def due_date
2013-01-06 14:03:49 +00:00
@due_date || = [
2010-12-07 19:42:36 +00:00
issues . maximum ( 'due_date' ) ,
2013-01-06 14:03:49 +00:00
shared_versions . maximum ( 'effective_date' ) ,
Issue . fixed_version ( shared_versions ) . maximum ( 'due_date' )
] . compact . max
2010-09-10 03:09:02 +00:00
end
def overdue?
2016-05-07 10:42:22 +00:00
active? && ! due_date . nil? && ( due_date < User . current . today )
2010-09-10 03:09:02 +00:00
end
# Returns the percent completed for this project, based on the
# progress on it's versions.
def completed_percent ( options = { :include_subprojects = > false } )
if options . delete ( :include_subprojects )
total = self_and_descendants . collect ( & :completed_percent ) . sum
total / self_and_descendants . count
else
if versions . count > 0
2013-01-04 08:30:25 +00:00
total = versions . collect ( & :completed_percent ) . sum
2010-09-10 03:09:02 +00:00
total / versions . count
else
100
end
end
end
2011-08-21 08:46:33 +00:00
2012-06-25 18:44:25 +00:00
# Return true if this project allows to do the specified action.
2009-03-21 00:39:53 +00:00
# action can be:
# * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
# * a permission Symbol (eg. :edit_project)
2007-09-14 11:34:08 +00:00
def allows_to? ( action )
2012-06-25 18:44:25 +00:00
if archived?
# No action allowed on archived projects
return false
end
unless active? || Redmine :: AccessControl . read_action? ( action )
# No write action allowed on closed projects
return false
end
# No action allowed on disabled modules
2007-09-14 11:34:08 +00:00
if action . is_a? Hash
allowed_actions . include? " #{ action [ :controller ] } / #{ action [ :action ] } "
else
allowed_permissions . include? action
end
end
2011-08-21 08:46:33 +00:00
2014-02-09 11:58:19 +00:00
# Return the enabled module with the given name
# or nil if the module is not enabled for the project
2014-02-09 11:54:21 +00:00
def enabled_module ( name )
name = name . to_s
enabled_modules . detect { | m | m . name == name }
end
2014-02-09 11:58:19 +00:00
# Return true if the module with the given name is enabled
2014-02-09 11:54:21 +00:00
def module_enabled? ( name )
enabled_module ( name ) . present?
2007-09-14 11:34:08 +00:00
end
2011-08-21 08:46:33 +00:00
2007-09-14 11:34:08 +00:00
def enabled_module_names = ( module_names )
2009-02-15 16:26:48 +00:00
if module_names && module_names . is_a? ( Array )
2010-12-03 16:15:16 +00:00
module_names = module_names . collect ( & :to_s ) . reject ( & :blank? )
2011-03-30 17:16:25 +00:00
self . enabled_modules = module_names . collect { | name | enabled_modules . detect { | mod | mod . name == name } || EnabledModule . new ( :name = > name ) }
2009-02-15 16:26:48 +00:00
else
enabled_modules . clear
2007-09-14 11:34:08 +00:00
end
end
2011-08-21 08:46:33 +00:00
2010-12-03 16:15:16 +00:00
# Returns an array of the enabled modules names
def enabled_module_names
enabled_modules . collect ( & :name )
end
2011-06-19 23:45:22 +00:00
# Enable a specific module
#
# Examples:
# project.enable_module!(:issue_tracking)
# project.enable_module!("issue_tracking")
def enable_module! ( name )
enabled_modules << EnabledModule . new ( :name = > name . to_s ) unless module_enabled? ( name )
end
# Disable a module if it exists
#
# Examples:
# project.disable_module!(:issue_tracking)
# project.disable_module!("issue_tracking")
# project.disable_module!(project.enabled_modules.first)
def disable_module! ( target )
target = enabled_modules . detect { | mod | target . to_s == mod . name } unless enabled_modules . include? ( target )
target . destroy unless target . blank?
end
2010-12-12 13:19:07 +00:00
safe_attributes 'name' ,
'description' ,
'homepage' ,
'is_public' ,
'identifier' ,
'custom_field_values' ,
'custom_fields' ,
2010-12-17 16:10:46 +00:00
'tracker_ids' ,
2015-01-07 22:19:57 +00:00
'issue_custom_field_ids' ,
2015-11-01 08:11:36 +00:00
'parent_id' ,
2017-04-08 08:02:06 +00:00
'default_version_id' ,
'default_assigned_to_id'
2010-09-14 19:02:25 +00:00
2011-01-06 20:36:31 +00:00
safe_attributes 'enabled_module_names' ,
2016-08-30 19:32:52 +00:00
:if = > lambda { | project , user |
if project . new_record?
if user . admin?
true
else
default_member_role . has_permission? ( :select_project_modules )
end
else
user . allowed_to? ( :select_project_modules , project )
end
}
2011-08-21 08:46:33 +00:00
2013-02-02 12:50:45 +00:00
safe_attributes 'inherit_members' ,
2013-02-02 13:18:04 +00:00
:if = > lambda { | project , user | project . parent . nil? || project . parent . visible? ( user ) }
2013-02-02 12:50:45 +00:00
2015-01-07 22:19:57 +00:00
def safe_attributes = ( attrs , user = User . current )
2017-07-23 11:26:04 +00:00
if attrs . respond_to? ( :to_unsafe_hash )
attrs = attrs . to_unsafe_hash
end
2015-01-07 22:19:57 +00:00
return unless attrs . is_a? ( Hash )
attrs = attrs . deep_dup
@unallowed_parent_id = nil
2015-09-21 20:32:11 +00:00
if new_record? || attrs . key? ( 'parent_id' )
parent_id_param = attrs [ 'parent_id' ] . to_s
if new_record? || parent_id_param != parent_id . to_s
p = parent_id_param . present? ? Project . find_by_id ( parent_id_param ) : nil
unless allowed_parents ( user ) . include? ( p )
attrs . delete ( 'parent_id' )
@unallowed_parent_id = true
end
2015-01-07 22:19:57 +00:00
end
end
super ( attrs , user )
end
2008-08-31 12:59:57 +00:00
# Returns an auto-generated project identifier based on the last identifier used
def self . next_identifier
2013-06-08 09:50:34 +00:00
p = Project . order ( 'id DESC' ) . first
2008-08-31 12:59:57 +00:00
p . nil? ? nil : p . identifier . to_s . succ
end
2007-03-12 17:59:02 +00:00
2009-05-03 21:25:37 +00:00
# Copies and saves the Project instance based on the +project+.
2009-10-24 13:30:23 +00:00
# Duplicates the source project's:
# * Wiki
# * Versions
# * Categories
2009-05-03 21:25:37 +00:00
# * Issues
# * Members
# * Queries
2009-10-24 13:30:23 +00:00
#
# Accepts an +options+ argument to specify what to copy
#
# Examples:
# project.copy(1) # => copies everything
# project.copy(1, :only => 'members') # => copies members only
# project.copy(1, :only => ['members', 'versions']) # => copies members and versions
def copy ( project , options = { } )
2009-05-03 21:25:37 +00:00
project = project . is_a? ( Project ) ? project : Project . find ( project )
2011-08-21 08:46:33 +00:00
2018-04-28 03:45:23 +00:00
to_be_copied = %w( members wiki versions issue_categories issues queries boards documents )
2014-10-22 17:37:16 +00:00
to_be_copied = to_be_copied & Array . wrap ( options [ :only ] ) unless options [ :only ] . nil?
2011-08-21 08:46:33 +00:00
2009-10-24 13:30:23 +00:00
Project . transaction do
2009-10-25 10:30:39 +00:00
if save
reload
2018-05-13 08:49:18 +00:00
self . attachments = project . attachments . map do | attachment |
attachment . copy ( :container = > self )
end
2009-10-25 10:30:39 +00:00
to_be_copied . each do | name |
send " copy_ #{ name } " , project
end
Redmine :: Hook . call_hook ( :model_project_copy_before_save , :source_project = > project , :destination_project = > self )
save
2015-01-07 22:29:50 +00:00
else
false
2009-05-03 21:25:37 +00:00
end
end
end
2012-12-03 23:03:33 +00:00
# Returns a new unsaved Project instance with attributes copied from +project+
2009-05-03 21:25:37 +00:00
def self . copy_from ( project )
2012-12-03 23:03:33 +00:00
project = project . is_a? ( Project ) ? project : Project . find ( project )
# clear unique attributes
attributes = project . attributes . dup . except ( 'id' , 'name' , 'identifier' , 'status' , 'parent_id' , 'lft' , 'rgt' )
copy = Project . new ( attributes )
2015-08-18 06:46:49 +00:00
copy . enabled_module_names = project . enabled_module_names
2012-12-03 23:03:33 +00:00
copy . trackers = project . trackers
copy . custom_values = project . custom_values . collect { | v | v . clone }
copy . issue_custom_fields = project . issue_custom_fields
copy
2009-05-03 21:25:37 +00:00
end
2010-10-22 22:38:45 +00:00
# Yields the given block for each project with its level in the tree
2016-08-30 20:10:56 +00:00
def self . project_tree ( projects , options = { } , & block )
2010-10-22 22:38:45 +00:00
ancestors = [ ]
2016-08-30 20:10:56 +00:00
if options [ :init_level ] && projects . first
ancestors = projects . first . ancestors . to_a
end
2010-10-22 22:38:45 +00:00
projects . sort_by ( & :lft ) . each do | project |
2011-08-21 08:46:33 +00:00
while ( ancestors . any? && ! project . is_descendant_of? ( ancestors . last ) )
2010-10-22 22:38:45 +00:00
ancestors . pop
end
yield project , ancestors . size
ancestors << project
end
2019-08-20 01:44:44 +00:00
end
def visible_custom_field_values ( user = nil )
user || = User . current
custom_field_values . select do | value |
value . custom_field . visible_by? ( project , user )
end
2010-10-22 22:38:45 +00:00
end
2011-08-21 08:46:33 +00:00
2009-10-24 13:30:23 +00:00
private
2011-08-21 08:46:33 +00:00
2013-02-02 12:50:45 +00:00
def update_inherited_members
if parent
2017-07-23 11:26:04 +00:00
if inherit_members? && ! inherit_members_before_last_save
2013-02-02 12:50:45 +00:00
remove_inherited_member_roles
add_inherited_member_roles
2017-07-23 11:26:04 +00:00
elsif ! inherit_members? && inherit_members_before_last_save
2013-02-02 12:50:45 +00:00
remove_inherited_member_roles
end
end
end
def remove_inherited_member_roles
2016-10-02 11:45:39 +00:00
member_roles = MemberRole . where ( :member_id = > membership_ids ) . to_a
2013-02-02 12:50:45 +00:00
member_role_ids = member_roles . map ( & :id )
member_roles . each do | member_role |
if member_role . inherited_from && ! member_role_ids . include? ( member_role . inherited_from )
member_role . destroy
end
end
end
def add_inherited_member_roles
if inherit_members? && parent
parent . memberships . each do | parent_member |
member = Member . find_or_new ( self . id , parent_member . user_id )
parent_member . member_roles . each do | parent_member_role |
member . member_roles << MemberRole . new ( :role = > parent_member_role . role , :inherited_from = > parent_member_role . id )
end
member . save!
end
2015-01-07 20:19:49 +00:00
memberships . reset
2013-02-02 12:50:45 +00:00
end
end
2015-01-07 22:19:57 +00:00
def update_versions_from_hierarchy_change
Issue . update_versions_from_hierarchy_change ( self )
end
def validate_parent
if @unallowed_parent_id
errors . add ( :parent_id , :invalid )
elsif parent_id_changed?
unless parent . nil? || ( parent . active? && move_possible? ( parent ) )
errors . add ( :parent_id , :invalid )
end
end
end
2009-10-24 13:30:23 +00:00
# Copies wiki from +project+
def copy_wiki ( project )
2009-10-25 10:30:39 +00:00
# Check that the source project has a wiki first
unless project . wiki . nil?
2013-02-07 19:24:57 +00:00
wiki = self . wiki || Wiki . new
2009-10-25 10:35:59 +00:00
wiki . attributes = project . wiki . attributes . dup . except ( " id " , " project_id " )
2010-02-11 19:30:53 +00:00
wiki_pages_map = { }
2009-10-25 10:30:39 +00:00
project . wiki . pages . each do | page |
2010-02-11 19:30:53 +00:00
# Skip pages without content
next if page . content . nil?
2009-10-25 10:44:03 +00:00
new_wiki_content = WikiContent . new ( page . content . attributes . dup . except ( " id " , " page_id " , " updated_on " ) )
new_wiki_page = WikiPage . new ( page . attributes . dup . except ( " id " , " wiki_id " , " created_on " , " parent_id " ) )
2009-10-25 10:30:39 +00:00
new_wiki_page . content = new_wiki_content
wiki . pages << new_wiki_page
2018-04-28 04:44:32 +00:00
new_wiki_page . attachments = page . attachments . map { | attachement | attachement . copy ( :container = > new_wiki_page ) }
2010-02-11 19:30:53 +00:00
wiki_pages_map [ page . id ] = new_wiki_page
end
2013-02-07 19:24:57 +00:00
self . wiki = wiki
2010-02-11 19:30:53 +00:00
wiki . save
# Reproduce page hierarchy
project . wiki . pages . each do | page |
if page . parent_id && wiki_pages_map [ page . id ]
wiki_pages_map [ page . id ] . parent = wiki_pages_map [ page . parent_id ]
wiki_pages_map [ page . id ] . save
end
2009-10-25 10:30:39 +00:00
end
2009-10-24 13:30:23 +00:00
end
end
# Copies versions from +project+
def copy_versions ( project )
project . versions . each do | version |
new_version = Version . new
2009-10-25 10:44:03 +00:00
new_version . attributes = version . attributes . dup . except ( " id " , " project_id " , " created_on " , " updated_on " )
2018-05-13 08:48:10 +00:00
new_version . attachments = version . attachments . map do | attachment |
attachment . copy ( :container = > new_version )
end
2009-10-24 13:30:23 +00:00
self . versions << new_version
end
end
# Copies issue categories from +project+
def copy_issue_categories ( project )
project . issue_categories . each do | issue_category |
new_issue_category = IssueCategory . new
2009-10-25 10:35:59 +00:00
new_issue_category . attributes = issue_category . attributes . dup . except ( " id " , " project_id " )
2009-10-24 13:30:23 +00:00
self . issue_categories << new_issue_category
end
end
2011-08-21 08:46:33 +00:00
2009-10-24 13:30:23 +00:00
# Copies issues from +project+
def copy_issues ( project )
2009-12-13 03:37:21 +00:00
# Stores the source issue id as a key and the copied issues as the
2014-04-07 08:07:43 +00:00
# value. Used to map the two together for issue relations.
2009-12-13 03:37:21 +00:00
issues_map = { }
2011-08-21 08:46:33 +00:00
2012-09-09 10:11:49 +00:00
# Store status and reopen locked/closed versions
version_statuses = versions . reject ( & :open? ) . map { | version | [ version , version . status ] }
version_statuses . each do | version , status |
version . update_attribute :status , 'open'
end
2010-03-13 14:56:49 +00:00
# Get issues sorted by root_id, lft so that parent issues
# get copied before their children
2014-01-09 00:05:33 +00:00
project . issues . reorder ( 'root_id, lft' ) . each do | issue |
2009-10-24 13:30:23 +00:00
new_issue = Issue . new
2017-04-03 10:30:29 +00:00
new_issue . copy_from ( issue , :subtasks = > false , :link = > false , :keep_status = > true )
2010-03-13 14:56:49 +00:00
new_issue . project = self
2013-05-13 19:52:37 +00:00
# Changing project resets the custom field values
# TODO: handle this in Issue#project=
new_issue . custom_field_values = issue . custom_field_values . inject ( { } ) { | h , v | h [ v . custom_field_id ] = v . value ; h }
2012-09-09 09:51:44 +00:00
# Reassign fixed_versions by name, since names are unique per project
2012-09-09 10:01:03 +00:00
if issue . fixed_version && issue . fixed_version . project == project
2012-09-09 09:51:44 +00:00
new_issue . fixed_version = self . versions . detect { | v | v . name == issue . fixed_version . name }
2009-10-24 13:30:23 +00:00
end
2015-09-20 12:50:06 +00:00
# Reassign version custom field values
new_issue . custom_field_values . each do | custom_value |
if custom_value . custom_field . field_format == 'version' && custom_value . value . present?
versions = Version . where ( :id = > custom_value . value ) . to_a
new_value = versions . map do | version |
if version . project == project
self . versions . detect { | v | v . name == version . name } . try ( :id )
else
version . id
end
end
new_value . compact!
new_value = new_value . first unless custom_value . custom_field . multiple?
custom_value . value = new_value
end
end
2012-09-09 09:51:44 +00:00
# Reassign the category by name, since names are unique per project
2009-10-24 13:30:23 +00:00
if issue . category
2012-09-09 09:51:44 +00:00
new_issue . category = self . issue_categories . detect { | c | c . name == issue . category . name }
2009-10-24 13:30:23 +00:00
end
2010-03-13 14:56:49 +00:00
# Parent issue
if issue . parent_id
if copied_parent = issues_map [ issue . parent_id ]
new_issue . parent_issue_id = copied_parent . id
end
end
2011-08-21 08:46:33 +00:00
2009-10-24 13:30:23 +00:00
self . issues << new_issue
2010-12-10 17:37:24 +00:00
if new_issue . new_record?
2014-12-14 21:53:38 +00:00
logger . info " Project # copy_issues: issue # #{ issue . id } could not be copied: #{ new_issue . errors . full_messages } " if logger && logger . info?
2010-12-10 17:37:24 +00:00
else
issues_map [ issue . id ] = new_issue unless new_issue . new_record?
end
2009-12-13 03:37:21 +00:00
end
2012-09-09 10:11:49 +00:00
# Restore locked/closed version statuses
version_statuses . each do | version , status |
version . update_attribute :status , status
end
2009-12-13 03:37:21 +00:00
# Relations after in case issues related each other
project . issues . each do | issue |
new_issue = issues_map [ issue . id ]
2010-12-10 17:37:24 +00:00
unless new_issue
# Issue was not copied
next
end
2011-08-21 08:46:33 +00:00
2009-12-13 03:37:21 +00:00
# Relations
issue . relations_from . each do | source_relation |
new_issue_relation = IssueRelation . new
new_issue_relation . attributes = source_relation . attributes . dup . except ( " id " , " issue_from_id " , " issue_to_id " )
new_issue_relation . issue_to = issues_map [ source_relation . issue_to_id ]
if new_issue_relation . issue_to . nil? && Setting . cross_project_issue_relations?
new_issue_relation . issue_to = source_relation . issue_to
end
new_issue . relations_from << new_issue_relation
end
2011-08-21 08:46:33 +00:00
2009-12-13 03:37:21 +00:00
issue . relations_to . each do | source_relation |
new_issue_relation = IssueRelation . new
new_issue_relation . attributes = source_relation . attributes . dup . except ( " id " , " issue_from_id " , " issue_to_id " )
new_issue_relation . issue_from = issues_map [ source_relation . issue_from_id ]
if new_issue_relation . issue_from . nil? && Setting . cross_project_issue_relations?
new_issue_relation . issue_from = source_relation . issue_from
end
new_issue . relations_to << new_issue_relation
end
2009-10-24 13:30:23 +00:00
end
end
# Copies members from +project+
def copy_members ( project )
2011-01-02 11:38:35 +00:00
# Copy users first, then groups to handle members with inherited and given roles
members_to_copy = [ ]
members_to_copy += project . memberships . select { | m | m . principal . is_a? ( User ) }
members_to_copy += project . memberships . select { | m | ! m . principal . is_a? ( User ) }
2011-08-21 08:46:33 +00:00
2011-01-02 11:38:35 +00:00
members_to_copy . each do | member |
2009-10-24 13:30:23 +00:00
new_member = Member . new
2009-10-25 10:44:03 +00:00
new_member . attributes = member . attributes . dup . except ( " id " , " project_id " , " created_on " )
2009-12-26 16:14:55 +00:00
# only copy non inherited roles
# inherited roles will be added when copying the group membership
role_ids = member . member_roles . reject ( & :inherited? ) . collect ( & :role_id )
next if role_ids . empty?
new_member . role_ids = role_ids
2009-10-24 13:30:23 +00:00
new_member . project = self
self . members << new_member
end
end
# Copies queries from +project+
def copy_queries ( project )
project . queries . each do | query |
2017-01-31 18:15:32 +00:00
new_query = query . class . new
2014-10-24 18:41:35 +00:00
new_query . attributes = query . attributes . dup . except ( " id " , " project_id " , " sort_criteria " , " user_id " , " type " )
2009-10-24 13:30:23 +00:00
new_query . sort_criteria = query . sort_criteria if query . sort_criteria
new_query . project = self
2011-11-11 17:33:02 +00:00
new_query . user_id = query . user_id
2017-01-31 18:15:32 +00:00
new_query . role_ids = query . role_ids if query . visibility == :: Query :: VISIBILITY_ROLES
2009-10-24 13:30:23 +00:00
self . queries << new_query
end
end
2009-10-25 11:23:46 +00:00
# Copies boards from +project+
def copy_boards ( project )
project . boards . each do | board |
new_board = Board . new
new_board . attributes = board . attributes . dup . except ( " id " , " project_id " , " topics_count " , " messages_count " , " last_message_id " )
new_board . project = self
self . boards << new_board
end
end
2011-08-21 08:46:33 +00:00
2018-04-28 03:45:23 +00:00
# Copies documents from +project+
def copy_documents ( project )
project . documents . each do | document |
new_document = Document . new
new_document . attributes = document . attributes . dup . except ( " id " , " project_id " )
new_document . project = self
new_document . attachments = document . attachments . map do | attachement |
attachement . copy ( :container = > new_document )
end
self . documents << new_document
end
end
2007-09-14 11:34:08 +00:00
def allowed_permissions
@allowed_permissions || = begin
2013-10-20 16:04:04 +00:00
module_names = enabled_modules . loaded? ? enabled_modules . map ( & :name ) : enabled_modules . pluck ( :name )
2007-09-14 11:34:08 +00:00
Redmine :: AccessControl . modules_permissions ( module_names ) . collect { | p | p . name }
end
end
def allowed_actions
@actions_allowed || = allowed_permissions . inject ( [ ] ) { | actions , permission | actions += Redmine :: AccessControl . allowed_actions ( permission ) } . flatten
end
2009-10-21 22:34:39 +00:00
2009-12-06 10:28:20 +00:00
# Archives subprojects recursively
def archive!
children . each do | subproject |
subproject . send :archive!
end
update_attribute :status , STATUS_ARCHIVED
end
2006-06-28 18:11:03 +00:00
end