You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

project.rb 43KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315
  1. # frozen_string_literal: true
  2. # Redmine - project management software
  3. # Copyright (C) 2006-2023 Jean-Philippe Lang
  4. #
  5. # This program is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU General Public License
  7. # as published by the Free Software Foundation; either version 2
  8. # of the License, or (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. class Project < ApplicationRecord
  19. include Redmine::SafeAttributes
  20. include Redmine::NestedSet::ProjectNestedSet
  21. # Project statuses
  22. STATUS_ACTIVE = 1
  23. STATUS_CLOSED = 5
  24. STATUS_ARCHIVED = 9
  25. STATUS_SCHEDULED_FOR_DELETION = 10
  26. # Maximum length for project identifiers
  27. IDENTIFIER_MAX_LENGTH = 100
  28. has_many :memberships, :class_name => 'Member', :inverse_of => :project
  29. # Memberships of active users only
  30. has_many :members,
  31. lambda {joins(:principal).where(:users => {:type => 'User', :status => Principal::STATUS_ACTIVE})}
  32. has_many :users, through: :members
  33. has_many :enabled_modules, :dependent => :delete_all
  34. has_and_belongs_to_many :trackers, lambda {order(:position)}
  35. has_many :issues, :dependent => :destroy
  36. has_many :issue_changes, :through => :issues, :source => :journals
  37. has_many :versions, :dependent => :destroy
  38. belongs_to :default_version, :class_name => 'Version'
  39. belongs_to :default_assigned_to, :class_name => 'Principal'
  40. has_many :time_entries, :dependent => :destroy
  41. # Specific overridden Activities
  42. has_many :time_entry_activities, :dependent => :destroy
  43. has_many :queries, :dependent => :destroy
  44. has_many :documents, :dependent => :destroy
  45. has_many :news, lambda {includes(:author)}, :dependent => :destroy
  46. has_many :issue_categories, lambda {order(:name)}, :dependent => :delete_all
  47. has_many :boards, lambda {order(:position)}, :inverse_of => :project, :dependent => :destroy
  48. has_one :repository, lambda {where(:is_default => true)}
  49. has_many :repositories, :dependent => :destroy
  50. has_many :changesets, :through => :repository
  51. has_one :wiki, :dependent => :destroy
  52. # Custom field for the project issues
  53. has_and_belongs_to_many :issue_custom_fields,
  54. lambda {order(:position)},
  55. :class_name => 'IssueCustomField',
  56. :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
  57. :association_foreign_key => 'custom_field_id'
  58. # Default Custom Query
  59. belongs_to :default_issue_query, :class_name => 'IssueQuery'
  60. acts_as_attachable :view_permission => :view_files,
  61. :edit_permission => :manage_files,
  62. :delete_permission => :manage_files
  63. acts_as_customizable
  64. acts_as_searchable :columns => ['name', 'identifier', 'description'],
  65. :project_key => "#{Project.table_name}.id",
  66. :permission => nil
  67. acts_as_event :title => proc {|o| "#{l(:label_project)}: #{o.name}"},
  68. :url => proc {|o| {:controller => 'projects', :action => 'show', :id => o}},
  69. :author => nil
  70. validates_presence_of :name, :identifier
  71. validates_uniqueness_of :identifier, :if => proc {|p| p.identifier_changed?}, :case_sensitive => true
  72. validates_length_of :name, :maximum => 255
  73. validates_length_of :homepage, :maximum => 255
  74. validates_length_of :identifier, :maximum => IDENTIFIER_MAX_LENGTH
  75. # downcase letters, digits, dashes but not digits only
  76. validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/,
  77. :if => proc {|p| p.identifier_changed?}
  78. # reserved words
  79. validates_exclusion_of :identifier, :in => %w(new)
  80. validate :validate_parent
  81. after_save :update_inherited_members,
  82. :if => proc {|project| project.saved_change_to_inherit_members?}
  83. after_save :remove_inherited_member_roles, :add_inherited_member_roles,
  84. :if => proc {|project| project.saved_change_to_parent_id?}
  85. after_update :update_versions_from_hierarchy_change,
  86. :if => proc {|project| project.saved_change_to_parent_id?}
  87. before_destroy :delete_all_members
  88. scope :has_module, (lambda do |mod|
  89. where("#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s)
  90. end)
  91. scope :active, lambda {where(:status => STATUS_ACTIVE)}
  92. scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i})}
  93. scope :all_public, lambda {where(:is_public => true)}
  94. scope :visible, lambda {|*args| where(Project.visible_condition(args.shift || User.current, *args))}
  95. scope :allowed_to, (lambda do |*args|
  96. user = args.first.is_a?(Symbol) ? User.current : args.shift
  97. permission = args.shift
  98. where(Project.allowed_to_condition(user, permission, *args))
  99. end)
  100. scope :like, (lambda do |arg|
  101. if arg.present?
  102. pattern = "%#{sanitize_sql_like arg.to_s.strip}%"
  103. where("LOWER(identifier) LIKE LOWER(:p) ESCAPE :s OR LOWER(name) LIKE LOWER(:p) ESCAPE :s", :p => pattern, :s => '\\')
  104. end
  105. end)
  106. scope :sorted, lambda {order(:lft)}
  107. scope :having_trackers, (lambda do
  108. where("#{Project.table_name}.id IN (SELECT DISTINCT project_id FROM #{table_name_prefix}projects_trackers#{table_name_suffix})")
  109. end)
  110. def initialize(attributes=nil, *args)
  111. super
  112. initialized = (attributes || {}).stringify_keys
  113. if !initialized.key?('identifier') && Setting.sequential_project_identifiers?
  114. self.identifier = Project.next_identifier
  115. end
  116. if !initialized.key?('is_public')
  117. self.is_public = Setting.default_projects_public?
  118. end
  119. if !initialized.key?('enabled_module_names')
  120. self.enabled_module_names = Setting.default_projects_modules
  121. end
  122. if !initialized.key?('trackers') && !initialized.key?('tracker_ids')
  123. default = Setting.default_projects_tracker_ids
  124. if default.is_a?(Array)
  125. self.trackers = Tracker.where(:id => default.map(&:to_i)).sorted.to_a
  126. else
  127. self.trackers = Tracker.sorted.to_a
  128. end
  129. end
  130. end
  131. def identifier=(identifier)
  132. super unless identifier_frozen?
  133. end
  134. def identifier_frozen?
  135. errors[:identifier].blank? && !(new_record? || identifier.blank?)
  136. end
  137. # returns latest created projects
  138. # non public projects will be returned only if user is a member of those
  139. def self.latest(user=nil, count=5)
  140. visible(user).limit(count).
  141. order(:created_on => :desc).
  142. where("#{table_name}.created_on >= ?", 30.days.ago).
  143. to_a
  144. end
  145. # Returns true if the project is visible to +user+ or to the current user.
  146. def visible?(user=User.current)
  147. user.allowed_to?(:view_project, self)
  148. end
  149. # Returns a SQL conditions string used to find all projects visible by the specified user.
  150. #
  151. # Examples:
  152. # Project.visible_condition(admin) => "projects.status = 1"
  153. # Project.visible_condition(normal_user) => "((projects.status = 1) AND (projects.is_public = 1 OR projects.id IN (1,3,4)))"
  154. # Project.visible_condition(anonymous) => "((projects.status = 1) AND (projects.is_public = 1))"
  155. def self.visible_condition(user, options={})
  156. allowed_to_condition(user, :view_project, options)
  157. end
  158. # Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+
  159. #
  160. # Valid options:
  161. # * :skip_pre_condition => true don't check that the module is enabled (eg. when the condition is already set elsewhere in the query)
  162. # * :project => project limit the condition to project
  163. # * :with_subprojects => true limit the condition to project and its subprojects
  164. # * :member => true limit the condition to the user projects
  165. def self.allowed_to_condition(user, permission, options={})
  166. perm = Redmine::AccessControl.permission(permission)
  167. base_statement =
  168. if perm && perm.read?
  169. "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND #{Project.table_name}.status <> #{Project::STATUS_SCHEDULED_FOR_DELETION}"
  170. else
  171. "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}"
  172. end
  173. if !options[:skip_pre_condition] && perm && perm.project_module
  174. # If the permission belongs to a project module, make sure the module is enabled
  175. base_statement +=
  176. " AND EXISTS (SELECT 1 AS one FROM #{EnabledModule.table_name} em" \
  177. " WHERE em.project_id = #{Project.table_name}.id" \
  178. " AND em.name='#{perm.project_module}')"
  179. end
  180. if project = options[:project]
  181. project_statement = project.project_condition(options[:with_subprojects])
  182. base_statement = "(#{project_statement}) AND (#{base_statement})"
  183. end
  184. if user.admin?
  185. base_statement
  186. else
  187. statement_by_role = {}
  188. unless options[:member]
  189. role = user.builtin_role
  190. if role.allowed_to?(permission)
  191. s = "#{Project.table_name}.is_public = #{connection.quoted_true}"
  192. if user.id
  193. group = role.anonymous? ? Group.anonymous : Group.non_member
  194. principal_ids = [user.id, group.id].compact
  195. s =
  196. "(#{s} AND #{Project.table_name}.id NOT IN " \
  197. "(SELECT project_id FROM #{Member.table_name} " \
  198. "WHERE user_id IN (#{principal_ids.join(',')})))"
  199. end
  200. statement_by_role[role] = s
  201. end
  202. end
  203. user.project_ids_by_role.each do |role, project_ids|
  204. if role.allowed_to?(permission) && project_ids.any?
  205. statement_by_role[role] = "#{Project.table_name}.id IN (#{project_ids.join(',')})"
  206. end
  207. end
  208. if statement_by_role.empty?
  209. "1=0"
  210. else
  211. if block_given?
  212. statement_by_role.each do |role, statement|
  213. if s = yield(role, user)
  214. statement_by_role[role] = "(#{statement} AND (#{s}))"
  215. end
  216. end
  217. end
  218. "((#{base_statement}) AND (#{statement_by_role.values.join(' OR ')}))"
  219. end
  220. end
  221. end
  222. def override_roles(role)
  223. @override_members ||= memberships.
  224. joins(:principal).
  225. where(:users => {:type => ['GroupAnonymous', 'GroupNonMember']}).to_a
  226. group_class = role.anonymous? ? GroupAnonymous : GroupNonMember
  227. member = @override_members.detect {|m| m.principal.is_a? group_class}
  228. member ? member.roles.to_a : [role]
  229. end
  230. def principals
  231. @principals ||=
  232. Principal.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).distinct
  233. end
  234. def users
  235. @users ||=
  236. User.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).distinct
  237. end
  238. # Returns the Systemwide and project specific activities
  239. def activities(include_inactive=false)
  240. t = TimeEntryActivity.table_name
  241. scope = TimeEntryActivity.where("#{t}.project_id IS NULL OR #{t}.project_id = ?", id)
  242. overridden_activity_ids = self.time_entry_activities.pluck(:parent_id).compact
  243. if overridden_activity_ids.any?
  244. scope = scope.where("#{t}.id NOT IN (?)", overridden_activity_ids)
  245. end
  246. unless include_inactive
  247. scope = scope.active
  248. end
  249. scope
  250. end
  251. # Creates or updates project time entry activities
  252. def update_or_create_time_entry_activities(activities)
  253. transaction do
  254. activities.each do |id, activity|
  255. update_or_create_time_entry_activity(id, activity)
  256. end
  257. end
  258. end
  259. # Will create a new Project specific Activity or update an existing one
  260. #
  261. # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
  262. # does not successfully save.
  263. def update_or_create_time_entry_activity(id, activity_hash)
  264. if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
  265. self.create_time_entry_activity_if_needed(activity_hash)
  266. else
  267. activity = project.time_entry_activities.find_by_id(id.to_i)
  268. activity.update(activity_hash) if activity
  269. end
  270. end
  271. # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
  272. #
  273. # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
  274. # does not successfully save.
  275. def create_time_entry_activity_if_needed(activity)
  276. if activity['parent_id']
  277. parent_activity = TimeEntryActivity.find(activity['parent_id'])
  278. activity['name'] = parent_activity.name
  279. activity['position'] = parent_activity.position
  280. if Enumeration.overriding_change?(activity, parent_activity)
  281. project_activity = self.time_entry_activities.create(activity)
  282. if project_activity.new_record?
  283. raise ActiveRecord::Rollback, "Overriding TimeEntryActivity was not successfully saved"
  284. else
  285. self.time_entries.
  286. where(:activity_id => parent_activity.id).
  287. update_all(:activity_id => project_activity.id)
  288. end
  289. end
  290. end
  291. end
  292. # returns the time log activity to be used when logging time via a changeset
  293. def commit_logtime_activity
  294. activity_id = Setting.commit_logtime_activity_id.to_i
  295. if activity_id > 0
  296. activities
  297. .find_by('id = ? OR parent_id = ?', activity_id, activity_id)
  298. end
  299. end
  300. # Returns a :conditions SQL string that can be used to find the issues associated with this project.
  301. #
  302. # Examples:
  303. # project.project_condition(true) => "(projects.lft >= 1 AND projects.rgt <= 10)"
  304. # project.project_condition(false) => "projects.id = 1"
  305. def project_condition(with_subprojects)
  306. if with_subprojects
  307. "(" \
  308. "#{Project.table_name}.lft >= #{lft} AND " \
  309. "#{Project.table_name}.rgt <= #{rgt}" \
  310. ")"
  311. else
  312. "#{Project.table_name}.id = #{id}"
  313. end
  314. end
  315. def self.find(*args)
  316. if args.first && args.first.is_a?(String) && !/^\d*$/.match?(args.first)
  317. project = find_by_identifier(*args)
  318. if project.nil?
  319. raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}"
  320. end
  321. project
  322. else
  323. super
  324. end
  325. end
  326. def self.find_by_param(*args)
  327. self.find(*args)
  328. end
  329. alias :base_reload :reload
  330. def reload(*args)
  331. @principals = nil
  332. @users = nil
  333. @shared_versions = nil
  334. @rolled_up_versions = nil
  335. @rolled_up_trackers = nil
  336. @rolled_up_statuses = nil
  337. @rolled_up_custom_fields = nil
  338. @all_issue_custom_fields = nil
  339. @all_time_entry_custom_fields = nil
  340. @to_param = nil
  341. @allowed_parents = nil
  342. @allowed_permissions = nil
  343. @actions_allowed = nil
  344. @start_date = nil
  345. @due_date = nil
  346. @override_members = nil
  347. @assignable_users = nil
  348. base_reload(*args)
  349. end
  350. def to_param
  351. if new_record?
  352. nil
  353. else
  354. # id is used for projects with a numeric identifier (compatibility)
  355. @to_param ||= (%r{^\d*$}.match?(identifier.to_s) ? id.to_s : identifier)
  356. end
  357. end
  358. def active?
  359. self.status == STATUS_ACTIVE
  360. end
  361. def closed?
  362. self.status == STATUS_CLOSED
  363. end
  364. def archived?
  365. self.status == STATUS_ARCHIVED
  366. end
  367. def scheduled_for_deletion?
  368. self.status == STATUS_SCHEDULED_FOR_DELETION
  369. end
  370. # Archives the project and its descendants
  371. def archive
  372. # Check that there is no issue of a non descendant project that is assigned
  373. # to one of the project or descendant versions
  374. version_ids = self_and_descendants.joins(:versions).pluck("#{Version.table_name}.id")
  375. if version_ids.any? &&
  376. Issue.
  377. joins(:project).
  378. where("#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?", lft, rgt).
  379. where(:fixed_version_id => version_ids).
  380. exists?
  381. return false
  382. end
  383. Project.transaction do
  384. archive!
  385. end
  386. true
  387. end
  388. # Unarchives the project and its archived ancestors
  389. def unarchive
  390. new_status = ancestors.any?(&:closed?) ? STATUS_CLOSED : STATUS_ACTIVE
  391. self_and_ancestors.status(STATUS_ARCHIVED).update_all :status => new_status
  392. reload
  393. end
  394. def close
  395. self_and_descendants.status(STATUS_ACTIVE).update_all :status => STATUS_CLOSED
  396. end
  397. def reopen
  398. self_and_descendants.status(STATUS_CLOSED).update_all :status => STATUS_ACTIVE
  399. end
  400. # Returns an array of projects the project can be moved to
  401. # by the current user
  402. def allowed_parents(user=User.current)
  403. return @allowed_parents if @allowed_parents
  404. @allowed_parents = Project.allowed_to(user, :add_subprojects).to_a
  405. @allowed_parents = @allowed_parents - self_and_descendants
  406. if user.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?)
  407. @allowed_parents << nil
  408. end
  409. unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
  410. @allowed_parents << parent
  411. end
  412. @allowed_parents
  413. end
  414. # Sets the parent of the project and saves the project
  415. # Argument can be either a Project, a String, a Fixnum or nil
  416. def set_parent!(p)
  417. if p.is_a?(Project)
  418. self.parent = p
  419. else
  420. self.parent_id = p
  421. end
  422. save
  423. end
  424. # Returns a scope of the trackers used by the project and its active sub projects
  425. def rolled_up_trackers(include_subprojects=true)
  426. if include_subprojects
  427. @rolled_up_trackers ||= rolled_up_trackers_base_scope.
  428. where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ?", lft, rgt)
  429. else
  430. rolled_up_trackers_base_scope.
  431. where(:projects => {:id => id})
  432. end
  433. end
  434. def rolled_up_trackers_base_scope
  435. Tracker.
  436. joins(projects: :enabled_modules).
  437. where("#{Project.table_name}.status <> ?", STATUS_ARCHIVED).
  438. where(:enabled_modules => {:name => 'issue_tracking'}).
  439. distinct.
  440. sorted
  441. end
  442. def rolled_up_statuses
  443. issue_status_ids = WorkflowTransition.
  444. where(:tracker_id => rolled_up_trackers.map(&:id)).
  445. distinct.
  446. pluck(:old_status_id, :new_status_id).
  447. flatten.
  448. uniq
  449. IssueStatus.where(:id => issue_status_ids).sorted
  450. end
  451. # Closes open and locked project versions that are completed
  452. def close_completed_versions
  453. Version.transaction do
  454. versions.where(:status => %w(open locked)).each do |version|
  455. if version.completed?
  456. version.update_attribute(:status, 'closed')
  457. end
  458. end
  459. end
  460. end
  461. # Returns a scope of the Versions on subprojects
  462. def rolled_up_versions
  463. @rolled_up_versions ||=
  464. Version.
  465. joins(:project).
  466. where(
  467. "#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ?" \
  468. " AND #{Project.table_name}.status <> ?", lft, rgt, STATUS_ARCHIVED
  469. )
  470. end
  471. # Returns a scope of the Versions used by the project
  472. def shared_versions
  473. if new_record?
  474. Version.
  475. joins(:project).
  476. preload(:project).
  477. where("#{Project.table_name}.status <> ? AND #{Version.table_name}.sharing = 'system'",
  478. STATUS_ARCHIVED)
  479. else
  480. @shared_versions ||= begin
  481. r = root? ? self : root
  482. Version.
  483. joins(:project).
  484. preload(:project).
  485. where(
  486. "#{Project.table_name}.id = #{id}" \
  487. " OR (#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND (" \
  488. " #{Version.table_name}.sharing = 'system'" \
  489. " OR (#{Project.table_name}.lft >= #{r.lft}" \
  490. " AND #{Project.table_name}.rgt <= #{r.rgt}" \
  491. " AND #{Version.table_name}.sharing = 'tree')" \
  492. " OR (#{Project.table_name}.lft < #{lft}" \
  493. " AND #{Project.table_name}.rgt > #{rgt}" \
  494. " AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" \
  495. " OR (#{Project.table_name}.lft > #{lft}" \
  496. " AND #{Project.table_name}.rgt < #{rgt}" \
  497. " AND #{Version.table_name}.sharing = 'hierarchy')" \
  498. "))"
  499. )
  500. end
  501. end
  502. end
  503. # Returns a hash of project users/groups grouped by role
  504. def principals_by_role
  505. memberships.active.includes(:principal, :roles).inject({}) do |h, m|
  506. m.roles.each do |r|
  507. h[r] ||= []
  508. h[r] << m.principal
  509. end
  510. h
  511. end
  512. end
  513. # Adds user as a project member with the default role
  514. # Used for when a non-admin user creates a project
  515. def add_default_member(user)
  516. role = self.class.default_member_role
  517. member = Member.new(:project => self, :principal => user, :roles => [role])
  518. self.members << member
  519. member
  520. end
  521. # Default role that is given to non-admin users that
  522. # create a project
  523. def self.default_member_role
  524. Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
  525. end
  526. # Deletes all project's members
  527. def delete_all_members
  528. me, mr = Member.table_name, MemberRole.table_name
  529. self.class.connection.delete(
  530. "DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} " \
  531. "WHERE #{me}.project_id = #{id})"
  532. )
  533. Member.where(:project_id => id).delete_all
  534. end
  535. # Return a Principal scope of users/groups issues can be assigned to
  536. def assignable_users(tracker=nil)
  537. return @assignable_users[tracker] if @assignable_users && @assignable_users[tracker]
  538. types = ['User']
  539. types << 'Group' if Setting.issue_group_assignment?
  540. scope = Principal.
  541. active.
  542. joins(:members => :roles).
  543. where(:type => types, :members => {:project_id => id}, :roles => {:assignable => true}).
  544. distinct.
  545. sorted
  546. if tracker
  547. # Rejects users that cannot the view the tracker
  548. roles =
  549. Role.where(:assignable => true).select do |role|
  550. role.permissions_tracker?(:view_issues, tracker)
  551. end
  552. scope = scope.where(:roles => {:id => roles.map(&:id)})
  553. end
  554. @assignable_users ||= {}
  555. @assignable_users[tracker] = scope
  556. end
  557. # Returns the mail addresses of users that should be always notified on project events
  558. def recipients
  559. notified_users.collect {|user| user.mail}
  560. end
  561. # Returns the users that should be notified on project events
  562. def notified_users
  563. users.where('members.mail_notification = ? OR users.mail_notification = ?', true, 'all')
  564. end
  565. # Returns a scope of all custom fields enabled for project issues
  566. # (explicitly associated custom fields and custom fields enabled for all projects)
  567. def all_issue_custom_fields
  568. if new_record?
  569. @all_issue_custom_fields ||= IssueCustomField.
  570. sorted.
  571. where("is_for_all = ? OR id IN (?)", true, issue_custom_field_ids)
  572. else
  573. @all_issue_custom_fields ||= IssueCustomField.
  574. sorted.
  575. where("is_for_all = ? OR id IN (SELECT DISTINCT cfp.custom_field_id" +
  576. " FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} cfp" +
  577. " WHERE cfp.project_id = ?)", true, id)
  578. end
  579. end
  580. # Returns a scope of all custom fields enabled for issues of the project
  581. # and its subprojects
  582. def rolled_up_custom_fields
  583. if leaf?
  584. all_issue_custom_fields
  585. else
  586. @rolled_up_custom_fields ||= IssueCustomField.
  587. sorted.
  588. where("is_for_all = ? OR EXISTS (SELECT 1" +
  589. " FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} cfp" +
  590. " JOIN #{Project.table_name} p ON p.id = cfp.project_id" +
  591. " WHERE cfp.custom_field_id = #{CustomField.table_name}.id" +
  592. " AND p.lft >= ? AND p.rgt <= ?)", true, lft, rgt)
  593. end
  594. end
  595. def project
  596. self
  597. end
  598. def <=>(project)
  599. return nil unless project.is_a?(Project)
  600. name.casecmp(project.name)
  601. end
  602. def to_s
  603. name
  604. end
  605. # Returns a short description of the projects (first lines)
  606. def short_description(length = 255)
  607. description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
  608. end
  609. def css_classes
  610. s = +'project'
  611. s << ' root' if root?
  612. s << ' child' if child?
  613. s << (leaf? ? ' leaf' : ' parent')
  614. s << ' public' if is_public?
  615. unless active?
  616. if archived?
  617. s << ' archived'
  618. else
  619. s << ' closed'
  620. end
  621. end
  622. s
  623. end
  624. # The earliest start date of a project, based on it's issues and versions
  625. def start_date
  626. @start_date ||=
  627. [
  628. issues.minimum('start_date'),
  629. shared_versions.minimum('effective_date'),
  630. Issue.fixed_version(shared_versions).minimum('start_date')
  631. ].compact.min
  632. end
  633. # The latest due date of an issue or version
  634. def due_date
  635. @due_date ||=
  636. [
  637. issues.maximum('due_date'),
  638. shared_versions.maximum('effective_date'),
  639. Issue.fixed_version(shared_versions).maximum('due_date')
  640. ].compact.max
  641. end
  642. def overdue?
  643. active? && !due_date.nil? && (due_date < User.current.today)
  644. end
  645. # Returns the percent completed for this project, based on the
  646. # progress on it's versions.
  647. def completed_percent(options={:include_subprojects => false})
  648. if options.delete(:include_subprojects)
  649. total = self_and_descendants.sum(&:completed_percent)
  650. total / self_and_descendants.count
  651. else
  652. if versions.count > 0
  653. total = versions.sum(&:completed_percent)
  654. total / versions.count
  655. else
  656. 100
  657. end
  658. end
  659. end
  660. # Return true if this project allows to do the specified action.
  661. # action can be:
  662. # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
  663. # * a permission Symbol (eg. :edit_project)
  664. def allows_to?(action)
  665. if archived?
  666. # No action allowed on archived projects
  667. return false
  668. end
  669. unless active? || Redmine::AccessControl.read_action?(action)
  670. # No write action allowed on closed projects
  671. return false
  672. end
  673. # No action allowed on disabled modules
  674. if action.is_a? Hash
  675. allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
  676. else
  677. allowed_permissions.include? action
  678. end
  679. end
  680. def deletable?(user = User.current)
  681. if user.admin?
  682. return true
  683. else
  684. user.allowed_to?(:delete_project, self) && leaf?
  685. end
  686. end
  687. # Return the enabled module with the given name
  688. # or nil if the module is not enabled for the project
  689. def enabled_module(name)
  690. name = name.to_s
  691. enabled_modules.detect {|m| m.name == name}
  692. end
  693. # Return true if the module with the given name is enabled
  694. def module_enabled?(name)
  695. enabled_module(name).present?
  696. end
  697. def enabled_module_names=(module_names)
  698. if module_names && module_names.is_a?(Array)
  699. module_names = module_names.collect(&:to_s).reject(&:blank?)
  700. self.enabled_modules =
  701. module_names.collect do |name|
  702. enabled_modules.detect {|mod| mod.name == name} ||
  703. EnabledModule.new(:name => name)
  704. end
  705. else
  706. enabled_modules.clear
  707. end
  708. end
  709. # Returns an array of the enabled modules names
  710. def enabled_module_names
  711. enabled_modules.collect(&:name)
  712. end
  713. # Enable a specific module
  714. #
  715. # Examples:
  716. # project.enable_module!(:issue_tracking)
  717. # project.enable_module!("issue_tracking")
  718. def enable_module!(name)
  719. enabled_modules << EnabledModule.new(:name => name.to_s) unless module_enabled?(name)
  720. end
  721. # Disable a module if it exists
  722. #
  723. # Examples:
  724. # project.disable_module!(:issue_tracking)
  725. # project.disable_module!("issue_tracking")
  726. # project.disable_module!(project.enabled_modules.first)
  727. def disable_module!(target)
  728. target = enabled_modules.detect{|mod| target.to_s == mod.name} unless enabled_modules.include?(target)
  729. target.destroy unless target.blank?
  730. end
  731. safe_attributes(
  732. 'name',
  733. 'description',
  734. 'homepage',
  735. 'identifier',
  736. 'custom_field_values',
  737. 'custom_fields',
  738. 'tracker_ids',
  739. 'issue_custom_field_ids',
  740. 'parent_id',
  741. 'default_version_id',
  742. 'default_issue_query_id',
  743. 'default_assigned_to_id')
  744. safe_attributes(
  745. 'is_public',
  746. :if =>
  747. lambda do |project, user|
  748. if project.new_record?
  749. if user.admin?
  750. true
  751. else
  752. default_member_role&.has_permission?(:select_project_publicity)
  753. end
  754. else
  755. user.allowed_to?(:select_project_publicity, project)
  756. end
  757. end
  758. )
  759. safe_attributes(
  760. 'enabled_module_names',
  761. :if =>
  762. lambda do |project, user|
  763. if project.new_record?
  764. if user.admin?
  765. true
  766. else
  767. default_member_role&.has_permission?(:select_project_modules)
  768. end
  769. else
  770. user.allowed_to?(:select_project_modules, project)
  771. end
  772. end
  773. )
  774. safe_attributes(
  775. 'inherit_members',
  776. :if => lambda {|project, user| project.parent.nil? || project.parent.visible?(user)})
  777. def safe_attributes=(attrs, user=User.current)
  778. if attrs.respond_to?(:to_unsafe_hash)
  779. attrs = attrs.to_unsafe_hash
  780. end
  781. return unless attrs.is_a?(Hash)
  782. attrs = attrs.deep_dup
  783. @unallowed_parent_id = nil
  784. if new_record? || attrs.key?('parent_id')
  785. parent_id_param = attrs['parent_id'].to_s
  786. if new_record? || parent_id_param != parent_id.to_s
  787. p = parent_id_param.present? ? Project.find_by_id(parent_id_param) : nil
  788. unless allowed_parents(user).include?(p)
  789. attrs.delete('parent_id')
  790. @unallowed_parent_id = true
  791. end
  792. end
  793. end
  794. # Reject custom fields values not visible by the user
  795. if attrs['custom_field_values'].present?
  796. editable_custom_field_ids = editable_custom_field_values(user).map {|v| v.custom_field_id.to_s}
  797. attrs['custom_field_values'].reject! {|k, v| !editable_custom_field_ids.include?(k.to_s)}
  798. end
  799. # Reject custom fields not visible by the user
  800. if attrs['custom_fields'].present?
  801. editable_custom_field_ids = editable_custom_field_values(user).map {|v| v.custom_field_id.to_s}
  802. attrs['custom_fields'].reject! {|c| !editable_custom_field_ids.include?(c['id'].to_s)}
  803. end
  804. super(attrs, user)
  805. end
  806. # Returns an auto-generated project identifier based on the last identifier used
  807. def self.next_identifier
  808. p = Project.order('id DESC').first
  809. p.nil? ? nil : p.identifier.to_s.succ
  810. end
  811. # Copies and saves the Project instance based on the +project+.
  812. # Duplicates the source project's:
  813. # * Wiki
  814. # * Versions
  815. # * Categories
  816. # * Issues
  817. # * Members
  818. # * Queries
  819. #
  820. # Accepts an +options+ argument to specify what to copy
  821. #
  822. # Examples:
  823. # project.copy(1) # => copies everything
  824. # project.copy(1, :only => 'members') # => copies members only
  825. # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
  826. def copy(project, options={})
  827. project = Project.find(project) unless project.is_a?(Project)
  828. to_be_copied = %w(members wiki versions issue_categories issues queries boards documents)
  829. to_be_copied = to_be_copied & Array.wrap(options[:only]) unless options[:only].nil?
  830. Project.transaction do
  831. if save
  832. reload
  833. self.attachments = project.attachments.map do |attachment|
  834. attachment.copy(:container => self)
  835. end
  836. to_be_copied.each do |name|
  837. send :"copy_#{name}", project
  838. end
  839. Redmine::Hook.call_hook(:model_project_copy_before_save,
  840. :source_project => project,
  841. :destination_project => self)
  842. save
  843. else
  844. false
  845. end
  846. end
  847. end
  848. # Returns a new unsaved Project instance with attributes copied from +project+
  849. def self.copy_from(project)
  850. project = Project.find(project) unless project.is_a?(Project)
  851. # clear unique attributes
  852. attributes =
  853. project.attributes.dup.except('id', 'name', 'identifier',
  854. 'status', 'parent_id', 'lft', 'rgt')
  855. copy = Project.new(attributes)
  856. copy.enabled_module_names = project.enabled_module_names
  857. copy.trackers = project.trackers
  858. copy.custom_values = project.custom_values.collect {|v| v.clone}
  859. copy.issue_custom_fields = project.issue_custom_fields
  860. copy
  861. end
  862. # Yields the given block for each project with its level in the tree
  863. def self.project_tree(projects, options={}, &block)
  864. ancestors = []
  865. if options[:init_level] && projects.first
  866. ancestors = projects.first.ancestors.to_a
  867. end
  868. projects.sort_by(&:lft).each do |project|
  869. while ancestors.any? &&
  870. !project.is_descendant_of?(ancestors.last)
  871. ancestors.pop
  872. end
  873. yield project, ancestors.size
  874. ancestors << project
  875. end
  876. end
  877. # Overrides Redmine::Acts::Customizable::InstanceMethods#validate_custom_field_values
  878. # so that custom values that are not editable are not validated (eg. a custom field that
  879. # is marked as required should not trigger a validation error if the user is not allowed
  880. # to edit this field).
  881. def validate_custom_field_values
  882. user = User.current
  883. if new_record? || custom_field_values_changed?
  884. editable_custom_field_values(user).each(&:validate_value)
  885. end
  886. end
  887. # Returns the custom_field_values that can be edited by the given user
  888. def editable_custom_field_values(user=nil)
  889. visible_custom_field_values(user)
  890. end
  891. def visible_custom_field_values(user = nil)
  892. user ||= User.current
  893. custom_field_values.select do |value|
  894. value.custom_field.visible_by?(project, user)
  895. end
  896. end
  897. private
  898. def update_inherited_members
  899. if parent
  900. if inherit_members? && !inherit_members_before_last_save
  901. remove_inherited_member_roles
  902. add_inherited_member_roles
  903. elsif !inherit_members? && inherit_members_before_last_save
  904. remove_inherited_member_roles
  905. end
  906. end
  907. end
  908. def remove_inherited_member_roles
  909. member_roles = MemberRole.where(:member_id => membership_ids).to_a
  910. member_role_ids = member_roles.map(&:id)
  911. member_roles.each do |member_role|
  912. if member_role.inherited_from && !member_role_ids.include?(member_role.inherited_from)
  913. member_role.destroy
  914. end
  915. end
  916. end
  917. def add_inherited_member_roles
  918. if inherit_members? && parent
  919. parent.memberships.each do |parent_member|
  920. member = Member.find_or_initialize_by(:project_id => self.id, :user_id => parent_member.user_id)
  921. parent_member.member_roles.each do |parent_member_role|
  922. member.member_roles <<
  923. MemberRole.new(:role => parent_member_role.role,
  924. :inherited_from => parent_member_role.id)
  925. end
  926. member.save!
  927. end
  928. memberships.reset
  929. end
  930. end
  931. def update_versions_from_hierarchy_change
  932. Issue.update_versions_from_hierarchy_change(self)
  933. end
  934. def validate_parent
  935. if @unallowed_parent_id
  936. errors.add(:parent_id, :invalid)
  937. elsif parent_id_changed?
  938. unless parent.nil? || (parent.active? && move_possible?(parent))
  939. errors.add(:parent_id, :invalid)
  940. end
  941. end
  942. end
  943. # Copies wiki from +project+
  944. def copy_wiki(project)
  945. # Check that the source project has a wiki first
  946. unless project.wiki.nil?
  947. wiki = self.wiki || Wiki.new
  948. wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
  949. wiki_pages_map = {}
  950. project.wiki.pages.each do |page|
  951. # Skip pages without content
  952. next if page.content.nil?
  953. new_wiki_content =
  954. WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
  955. new_wiki_page =
  956. WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
  957. new_wiki_page.content = new_wiki_content
  958. wiki.pages << new_wiki_page
  959. new_wiki_page.attachments =
  960. page.attachments.map{|attachement| attachement.copy(:container => new_wiki_page)}
  961. wiki_pages_map[page.id] = new_wiki_page
  962. end
  963. self.wiki = wiki
  964. wiki.save
  965. # Reproduce page hierarchy
  966. project.wiki.pages.each do |page|
  967. if page.parent_id && wiki_pages_map[page.id]
  968. wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id]
  969. wiki_pages_map[page.id].save
  970. end
  971. end
  972. end
  973. end
  974. # Copies versions from +project+
  975. def copy_versions(project)
  976. project.versions.each do |version|
  977. new_version = Version.new
  978. new_version.attributes =
  979. version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
  980. new_version.attachments = version.attachments.map do |attachment|
  981. attachment.copy(:container => new_version)
  982. end
  983. self.versions << new_version
  984. end
  985. end
  986. # Copies issue categories from +project+
  987. def copy_issue_categories(project)
  988. project.issue_categories.each do |issue_category|
  989. new_issue_category = IssueCategory.new
  990. new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
  991. self.issue_categories << new_issue_category
  992. end
  993. end
  994. # Copies issues from +project+
  995. def copy_issues(project)
  996. # Stores the source issue id as a key and the copied issues as the
  997. # value. Used to map the two together for issue relations.
  998. issues_map = {}
  999. # Store status and reopen locked/closed versions
  1000. version_statuses = versions.reject(&:open?).map {|version| [version, version.status]}
  1001. version_statuses.each do |version, _| # rubocop:disable Style/HashEachMethods
  1002. version.update_attribute :status, 'open'
  1003. end
  1004. # Get issues sorted by root_id, lft so that parent issues
  1005. # get copied before their children
  1006. project.issues.reorder('root_id, lft').each do |issue|
  1007. new_issue = Issue.new
  1008. new_issue.copy_from(issue, :subtasks => false, :link => false, :keep_status => true)
  1009. new_issue.project = self
  1010. # Changing project resets the custom field values
  1011. # TODO: handle this in Issue#project=
  1012. new_issue.custom_field_values = issue.custom_field_values.inject({}) do |h, v|
  1013. h[v.custom_field_id] = v.value
  1014. h
  1015. end
  1016. # Reassign fixed_versions by name, since names are unique per project
  1017. if issue.fixed_version && issue.fixed_version.project == project
  1018. new_issue.fixed_version = self.versions.detect {|v| v.name == issue.fixed_version.name}
  1019. end
  1020. # Reassign version custom field values
  1021. new_issue.custom_field_values.each do |custom_value|
  1022. if custom_value.custom_field.field_format == 'version' && custom_value.value.present?
  1023. versions = Version.where(:id => custom_value.value).to_a
  1024. new_value = versions.map do |version|
  1025. if version.project == project
  1026. self.versions.detect {|v| v.name == version.name}.try(:id)
  1027. else
  1028. version.id
  1029. end
  1030. end
  1031. new_value.compact!
  1032. new_value = new_value.first unless custom_value.custom_field.multiple?
  1033. custom_value.value = new_value
  1034. end
  1035. end
  1036. # Reassign the category by name, since names are unique per project
  1037. if issue.category
  1038. new_issue.category = self.issue_categories.detect {|c| c.name == issue.category.name}
  1039. end
  1040. # Parent issue
  1041. if issue.parent_id
  1042. if copied_parent = issues_map[issue.parent_id]
  1043. new_issue.parent_issue_id = copied_parent.id
  1044. end
  1045. end
  1046. self.issues << new_issue
  1047. if new_issue.new_record?
  1048. if logger && logger.info?
  1049. logger.info(
  1050. "Project#copy_issues: issue ##{issue.id} could not be copied: " \
  1051. "#{new_issue.errors.full_messages}"
  1052. )
  1053. end
  1054. else
  1055. issues_map[issue.id] = new_issue unless new_issue.new_record?
  1056. end
  1057. end
  1058. # Restore locked/closed version statuses
  1059. version_statuses.each do |version, status|
  1060. version.update_attribute :status, status
  1061. end
  1062. # Relations after in case issues related each other
  1063. project.issues.each do |issue|
  1064. new_issue = issues_map[issue.id]
  1065. unless new_issue
  1066. # Issue was not copied
  1067. next
  1068. end
  1069. # Relations
  1070. issue.relations_from.each do |source_relation|
  1071. new_issue_relation = IssueRelation.new
  1072. new_issue_relation.attributes =
  1073. source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
  1074. new_issue_relation.issue_to = issues_map[source_relation.issue_to_id]
  1075. if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
  1076. new_issue_relation.issue_to = source_relation.issue_to
  1077. end
  1078. new_issue.relations_from << new_issue_relation
  1079. end
  1080. issue.relations_to.each do |source_relation|
  1081. new_issue_relation = IssueRelation.new
  1082. new_issue_relation.attributes =
  1083. source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
  1084. new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
  1085. if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
  1086. new_issue_relation.issue_from = source_relation.issue_from
  1087. end
  1088. new_issue.relations_to << new_issue_relation
  1089. end
  1090. end
  1091. end
  1092. # Copies members from +project+
  1093. def copy_members(project)
  1094. # Copy users first, then groups to handle members with inherited and given roles
  1095. members_to_copy = []
  1096. members_to_copy += project.memberships.select {|m| m.principal.is_a?(User)}
  1097. members_to_copy += project.memberships.select {|m| !m.principal.is_a?(User)}
  1098. members_to_copy.each do |member|
  1099. new_member = Member.new
  1100. new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
  1101. # only copy non inherited roles
  1102. # inherited roles will be added when copying the group membership
  1103. role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)
  1104. next if role_ids.empty?
  1105. new_member.role_ids = role_ids
  1106. new_member.project = self
  1107. self.members << new_member
  1108. end
  1109. end
  1110. # Copies queries from +project+
  1111. def copy_queries(project)
  1112. project.queries.each do |query|
  1113. new_query = query.class.new
  1114. new_query.attributes =
  1115. query.attributes.dup.except("id", "project_id", "sort_criteria",
  1116. "user_id", "type")
  1117. new_query.sort_criteria = query.sort_criteria if query.sort_criteria
  1118. new_query.project = self
  1119. new_query.user_id = query.user_id
  1120. new_query.role_ids = query.role_ids if query.visibility == ::Query::VISIBILITY_ROLES
  1121. self.queries << new_query
  1122. if query == project.default_issue_query
  1123. self.default_issue_query = new_query
  1124. end
  1125. end
  1126. end
  1127. # Copies boards from +project+
  1128. def copy_boards(project)
  1129. project.boards.each do |board|
  1130. new_board = Board.new
  1131. new_board.attributes =
  1132. board.attributes.dup.except("id", "project_id", "topics_count",
  1133. "messages_count", "last_message_id")
  1134. new_board.project = self
  1135. self.boards << new_board
  1136. end
  1137. end
  1138. # Copies documents from +project+
  1139. def copy_documents(project)
  1140. project.documents.each do |document|
  1141. new_document = Document.new
  1142. new_document.attributes = document.attributes.dup.except("id", "project_id")
  1143. new_document.project = self
  1144. new_document.attachments = document.attachments.map do |attachement|
  1145. attachement.copy(:container => new_document)
  1146. end
  1147. self.documents << new_document
  1148. end
  1149. end
  1150. def allowed_permissions
  1151. @allowed_permissions ||= begin
  1152. module_names =
  1153. if enabled_modules.loaded?
  1154. enabled_modules.map(&:name)
  1155. else
  1156. enabled_modules.pluck(:name)
  1157. end
  1158. Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
  1159. end
  1160. end
  1161. def allowed_actions
  1162. @actions_allowed ||= allowed_permissions.inject([]) do |actions, permission|
  1163. actions += Redmine::AccessControl.allowed_actions(permission)
  1164. end.flatten
  1165. end
  1166. # Archives subprojects recursively
  1167. def archive!
  1168. children.each do |subproject|
  1169. subproject.send :archive!
  1170. end
  1171. update_attribute :status, STATUS_ARCHIVED
  1172. end
  1173. end