summaryrefslogtreecommitdiffstats
path: root/app/models
diff options
context:
space:
mode:
Diffstat (limited to 'app/models')
-rw-r--r--app/models/attachment.rb81
-rw-r--r--app/models/auth_source.rb47
-rw-r--r--app/models/auth_source_ldap.rb79
-rw-r--r--app/models/custom_field.rb42
-rw-r--r--app/models/custom_value.rb38
-rw-r--r--app/models/document.rb24
-rw-r--r--app/models/enumeration.rb46
-rw-r--r--app/models/issue.rb103
-rw-r--r--app/models/issue_category.rb29
-rw-r--r--app/models/issue_custom_field.rb27
-rw-r--r--app/models/issue_history.rb24
-rw-r--r--app/models/issue_status.rb49
-rw-r--r--app/models/journal.rb22
-rw-r--r--app/models/journal_detail.rb20
-rw-r--r--app/models/mailer.rb53
-rw-r--r--app/models/member.rb29
-rw-r--r--app/models/news.rb28
-rw-r--r--app/models/permission.rb63
-rw-r--r--app/models/project.rb57
-rw-r--r--app/models/project_custom_field.rb22
-rw-r--r--app/models/role.rb31
-rw-r--r--app/models/token.rb44
-rw-r--r--app/models/tracker.rb31
-rw-r--r--app/models/user.rb129
-rw-r--r--app/models/user_custom_field.rb23
-rw-r--r--app/models/user_preference.rb44
-rw-r--r--app/models/version.rb32
-rw-r--r--app/models/workflow.rb24
28 files changed, 1241 insertions, 0 deletions
diff --git a/app/models/attachment.rb b/app/models/attachment.rb
new file mode 100644
index 000000000..2e1e9b156
--- /dev/null
+++ b/app/models/attachment.rb
@@ -0,0 +1,81 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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.
+
+require "digest/md5"
+
+class Attachment < ActiveRecord::Base
+ belongs_to :container, :polymorphic => true
+ belongs_to :author, :class_name => "User", :foreign_key => "author_id"
+
+ validates_presence_of :filename
+
+ def file=(incomming_file)
+ unless incomming_file.nil?
+ @temp_file = incomming_file
+ if @temp_file.size > 0
+ self.filename = sanitize_filename(@temp_file.original_filename)
+ self.disk_filename = DateTime.now.strftime("%y%m%d%H%M%S") + "_" + self.filename
+ self.content_type = @temp_file.content_type
+ self.filesize = @temp_file.size
+ end
+ end
+ end
+
+ # Copy temp file to its final location
+ def before_save
+ if @temp_file && (@temp_file.size > 0)
+ logger.debug("saving '#{self.diskfile}'")
+ File.open(diskfile, "wb") do |f|
+ f.write(@temp_file.read)
+ end
+ self.digest = Digest::MD5.hexdigest(File.read(diskfile))
+ end
+ end
+
+ # Deletes file on the disk
+ def after_destroy
+ if self.filename?
+ File.delete(diskfile) if File.exist?(diskfile)
+ end
+ end
+
+ # Returns file's location on disk
+ def diskfile
+ "#{$RDM_STORAGE_PATH}/#{self.disk_filename}"
+ end
+
+ def increment_download
+ increment!(:downloads)
+ end
+
+ # returns last created projects
+ def self.most_downloaded
+ find(:all, :limit => 5, :order => "downloads DESC")
+ end
+
+private
+ def sanitize_filename(value)
+ # get only the filename, not the whole path
+ just_filename = value.gsub(/^.*(\\|\/)/, '')
+ # NOTE: File.basename doesn't work right with Windows paths on Unix
+ # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
+
+ # Finally, replace all non alphanumeric, underscore or periods with underscore
+ @filename = just_filename.gsub(/[^\w\.\-]/,'_')
+ end
+
+end
diff --git a/app/models/auth_source.rb b/app/models/auth_source.rb
new file mode 100644
index 000000000..47eec106d
--- /dev/null
+++ b/app/models/auth_source.rb
@@ -0,0 +1,47 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 AuthSource < ActiveRecord::Base
+ has_many :users
+
+ validates_presence_of :name
+ validates_uniqueness_of :name
+
+ def authenticate(login, password)
+ end
+
+ def test_connection
+ end
+
+ def auth_method_name
+ "Abstract"
+ end
+
+ # Try to authenticate a user not yet registered against available sources
+ def self.authenticate(login, password)
+ AuthSource.find(:all, :conditions => ["onthefly_register=?", true]).each do |source|
+ begin
+ logger.debug "Authenticating '#{login}' against '#{source.name}'" if logger && logger.debug?
+ attrs = source.authenticate(login, password)
+ rescue
+ attrs = nil
+ end
+ return attrs if attrs
+ end
+ return nil
+ end
+end
diff --git a/app/models/auth_source_ldap.rb b/app/models/auth_source_ldap.rb
new file mode 100644
index 000000000..895cf1c63
--- /dev/null
+++ b/app/models/auth_source_ldap.rb
@@ -0,0 +1,79 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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.
+
+require 'net/ldap'
+require 'iconv'
+
+class AuthSourceLdap < AuthSource
+ validates_presence_of :host, :port, :attr_login
+
+ def after_initialize
+ self.port = 389 if self.port == 0
+ end
+
+ def authenticate(login, password)
+ attrs = []
+ # get user's DN
+ ldap_con = initialize_ldap_con(self.account, self.account_password)
+ login_filter = Net::LDAP::Filter.eq( self.attr_login, login )
+ object_filter = Net::LDAP::Filter.eq( "objectClass", "*" )
+ dn = String.new
+ ldap_con.search( :base => self.base_dn,
+ :filter => object_filter & login_filter,
+ :attributes=> ['dn', self.attr_firstname, self.attr_lastname, self.attr_mail]) do |entry|
+ dn = entry.dn
+ attrs = [:firstname => AuthSourceLdap.get_attr(entry, self.attr_firstname),
+ :lastname => AuthSourceLdap.get_attr(entry, self.attr_lastname),
+ :mail => AuthSourceLdap.get_attr(entry, self.attr_mail),
+ :auth_source_id => self.id ]
+ end
+ return nil if dn.empty?
+ logger.debug "DN found for #{login}: #{dn}" if logger && logger.debug?
+ # authenticate user
+ ldap_con = initialize_ldap_con(dn, password)
+ return nil unless ldap_con.bind
+ # return user's attributes
+ logger.debug "Authentication successful for '#{login}'" if logger && logger.debug?
+ attrs
+ rescue Net::LDAP::LdapError => text
+ raise "LdapError: " + text
+ end
+
+ # test the connection to the LDAP
+ def test_connection
+ ldap_con = initialize_ldap_con(self.account, self.account_password)
+ ldap_con.open { }
+ rescue Net::LDAP::LdapError => text
+ raise "LdapError: " + text
+ end
+
+ def auth_method_name
+ "LDAP"
+ end
+
+private
+ def initialize_ldap_con(ldap_user, ldap_password)
+ Net::LDAP.new( {:host => self.host,
+ :port => self.port,
+ :auth => { :method => :simple, :username => Iconv.new('iso-8859-15', 'utf-8').iconv(ldap_user), :password => Iconv.new('iso-8859-15', 'utf-8').iconv(ldap_password) }}
+ )
+ end
+
+ def self.get_attr(entry, attr_name)
+ entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name]
+ end
+end
diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb
new file mode 100644
index 000000000..924a874a3
--- /dev/null
+++ b/app/models/custom_field.rb
@@ -0,0 +1,42 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 CustomField < ActiveRecord::Base
+ has_many :custom_values, :dependent => true
+
+ FIELD_FORMATS = { "list" => :label_list,
+ "date" => :label_date,
+ "bool" => :label_boolean,
+ "int" => :label_integer,
+ "string" => :label_string,
+ "text" => :label_text
+ }.freeze
+
+ validates_presence_of :name, :field_format
+ validates_uniqueness_of :name
+ validates_inclusion_of :field_format, :in => FIELD_FORMATS.keys
+ validates_presence_of :possible_values, :if => Proc.new { |field| field.field_format == "list" }
+
+ # to move in project_custom_field
+ def self.for_all
+ find(:all, :conditions => ["is_for_all=?", true])
+ end
+
+ def type_name
+ nil
+ end
+end
diff --git a/app/models/custom_value.rb b/app/models/custom_value.rb
new file mode 100644
index 000000000..015ccd244
--- /dev/null
+++ b/app/models/custom_value.rb
@@ -0,0 +1,38 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 CustomValue < ActiveRecord::Base
+ belongs_to :custom_field
+ belongs_to :customized, :polymorphic => true
+
+protected
+ def validate
+ errors.add(:value, :activerecord_error_blank) and return if custom_field.is_required? and value.empty?
+ errors.add(:value, :activerecord_error_invalid) unless custom_field.regexp.empty? or value =~ Regexp.new(custom_field.regexp)
+ errors.add(:value, :activerecord_error_too_short) if custom_field.min_length > 0 and value.length < custom_field.min_length and value.length > 0
+ errors.add(:value, :activerecord_error_too_long) if custom_field.max_length > 0 and value.length > custom_field.max_length
+ case custom_field.field_format
+ when "int"
+ errors.add(:value, :activerecord_error_not_a_number) unless value =~ /^[0-9]*$/
+ when "date"
+ errors.add(:value, :activerecord_error_not_a_date) unless value =~ /^\d{4}-\d{2}-\d{2}$/ or value.empty?
+ when "list"
+ errors.add(:value, :activerecord_error_inclusion) unless custom_field.possible_values.split('|').include? value or value.empty?
+ end
+ end
+end
+
diff --git a/app/models/document.rb b/app/models/document.rb
new file mode 100644
index 000000000..08e0ef607
--- /dev/null
+++ b/app/models/document.rb
@@ -0,0 +1,24 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 Document < ActiveRecord::Base
+ belongs_to :project
+ belongs_to :category, :class_name => "Enumeration", :foreign_key => "category_id"
+ has_many :attachments, :as => :container, :dependent => true
+
+ validates_presence_of :project, :title, :category
+end
diff --git a/app/models/enumeration.rb b/app/models/enumeration.rb
new file mode 100644
index 000000000..b5c8ed6e7
--- /dev/null
+++ b/app/models/enumeration.rb
@@ -0,0 +1,46 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 Enumeration < ActiveRecord::Base
+ before_destroy :check_integrity
+
+ validates_presence_of :opt, :name
+ validates_uniqueness_of :name, :scope => [:opt]
+
+ OPTIONS = {
+ "IPRI" => :enumeration_issue_priorities,
+ "DCAT" => :enumeration_doc_categories
+ }.freeze
+
+ def self.get_values(option)
+ find(:all, :conditions => ['opt=?', option])
+ end
+
+ def option_name
+ OPTIONS[self.opt]
+ end
+
+private
+ def check_integrity
+ case self.opt
+ when "IPRI"
+ raise "Can't delete enumeration" if Issue.find(:first, :conditions => ["priority_id=?", self.id])
+ when "DCAT"
+ raise "Can't delete enumeration" if Document.find(:first, :conditions => ["category_id=?", self.id])
+ end
+ end
+end
diff --git a/app/models/issue.rb b/app/models/issue.rb
new file mode 100644
index 000000000..f00eb7a9c
--- /dev/null
+++ b/app/models/issue.rb
@@ -0,0 +1,103 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 Issue < ActiveRecord::Base
+
+ belongs_to :project
+ belongs_to :tracker
+ belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
+ belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
+ belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
+ belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
+ belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
+ belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
+
+ #has_many :histories, :class_name => 'IssueHistory', :dependent => true, :order => "issue_histories.created_on DESC", :include => :status
+ has_many :journals, :as => :journalized, :dependent => true
+ has_many :attachments, :as => :container, :dependent => true
+
+ has_many :custom_values, :dependent => true, :as => :customized
+ has_many :custom_fields, :through => :custom_values
+
+ validates_presence_of :subject, :description, :priority, :tracker, :author, :status
+ validates_inclusion_of :done_ratio, :in => 0..100
+ validates_associated :custom_values, :on => :update
+
+ # set default status for new issues
+ def before_validation
+ self.status = IssueStatus.default if new_record?
+ end
+
+ def validate
+ if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
+ errors.add :due_date, :activerecord_error_not_a_date
+ end
+
+ if self.due_date and self.start_date and self.due_date < self.start_date
+ errors.add :due_date, :activerecord_error_greater_than_start_date
+ end
+ end
+
+ #def before_create
+ # build_history
+ #end
+
+ def before_save
+ if @current_journal
+ # attributes changes
+ (Issue.column_names - %w(id description)).each {|c|
+ @current_journal.details << JournalDetail.new(:property => 'attr',
+ :prop_key => c,
+ :old_value => @issue_before_change.send(c),
+ :value => send(c)) unless send(c)==@issue_before_change.send(c)
+ }
+ # custom fields changes
+ custom_values.each {|c|
+ @current_journal.details << JournalDetail.new(:property => 'cf',
+ :prop_key => c.custom_field_id,
+ :old_value => @custom_values_before_change[c.custom_field_id],
+ :value => c.value) unless @custom_values_before_change[c.custom_field_id]==c.value
+ }
+ @current_journal.save unless @current_journal.details.empty? and @current_journal.notes.empty?
+ end
+ end
+
+ def long_id
+ "%05d" % self.id
+ end
+
+ def custom_value_for(custom_field)
+ self.custom_values.each {|v| return v if v.custom_field_id == custom_field.id }
+ return nil
+ end
+
+ def init_journal(user, notes = "")
+ @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
+ @issue_before_change = self.clone
+ @custom_values_before_change = {}
+ self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
+ @current_journal
+ end
+
+private
+ # Creates an history for the issue
+ #def build_history
+ # @history = self.histories.build
+ # @history.status = self.status
+ # @history.author = self.author
+ #end
+end
diff --git a/app/models/issue_category.rb b/app/models/issue_category.rb
new file mode 100644
index 000000000..74adb8f52
--- /dev/null
+++ b/app/models/issue_category.rb
@@ -0,0 +1,29 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 IssueCategory < ActiveRecord::Base
+ before_destroy :check_integrity
+ belongs_to :project
+
+ validates_presence_of :name
+ validates_uniqueness_of :name, :scope => [:project_id]
+
+private
+ def check_integrity
+ raise "Can't delete category" if Issue.find(:first, :conditions => ["category_id=?", self.id])
+ end
+end
diff --git a/app/models/issue_custom_field.rb b/app/models/issue_custom_field.rb
new file mode 100644
index 000000000..209ae206b
--- /dev/null
+++ b/app/models/issue_custom_field.rb
@@ -0,0 +1,27 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 IssueCustomField < CustomField
+ has_and_belongs_to_many :projects, :join_table => "custom_fields_projects", :foreign_key => "custom_field_id"
+ has_and_belongs_to_many :trackers, :join_table => "custom_fields_trackers", :foreign_key => "custom_field_id"
+ has_many :issues, :through => :issue_custom_values
+
+ def type_name
+ :label_issue_plural
+ end
+end
+
diff --git a/app/models/issue_history.rb b/app/models/issue_history.rb
new file mode 100644
index 000000000..4b6682600
--- /dev/null
+++ b/app/models/issue_history.rb
@@ -0,0 +1,24 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 IssueHistory < ActiveRecord::Base
+ belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
+ belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
+ belongs_to :issue
+
+ validates_presence_of :status
+end
diff --git a/app/models/issue_status.rb b/app/models/issue_status.rb
new file mode 100644
index 000000000..c8a40d330
--- /dev/null
+++ b/app/models/issue_status.rb
@@ -0,0 +1,49 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 IssueStatus < ActiveRecord::Base
+ before_destroy :check_integrity
+ has_many :workflows, :foreign_key => "old_status_id"
+
+ validates_presence_of :name
+ validates_uniqueness_of :name
+ validates_length_of :html_color, :is => 6
+ validates_format_of :html_color, :with => /^[a-f0-9]*$/i
+
+ def before_save
+ IssueStatus.update_all "is_default=false" if self.is_default?
+ end
+
+ # Returns the default status for new issues
+ def self.default
+ find(:first, :conditions =>["is_default=?", true])
+ end
+
+ # Returns an array of all statuses the given role can switch to
+ def new_statuses_allowed_to(role, tracker)
+ statuses = []
+ for workflow in self.workflows
+ statuses << workflow.new_status if workflow.role_id == role.id and workflow.tracker_id == tracker.id
+ end unless role.nil? or tracker.nil?
+ statuses
+ end
+
+private
+ def check_integrity
+ raise "Can't delete status" if Issue.find(:first, :conditions => ["status_id=?", self.id]) or IssueHistory.find(:first, :conditions => ["status_id=?", self.id])
+ end
+end
diff --git a/app/models/journal.rb b/app/models/journal.rb
new file mode 100644
index 000000000..9d173552f
--- /dev/null
+++ b/app/models/journal.rb
@@ -0,0 +1,22 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 Journal < ActiveRecord::Base
+ belongs_to :journalized, :polymorphic => true
+ belongs_to :user
+ has_many :details, :class_name => "JournalDetail", :dependent => true
+end
diff --git a/app/models/journal_detail.rb b/app/models/journal_detail.rb
new file mode 100644
index 000000000..784e98bf7
--- /dev/null
+++ b/app/models/journal_detail.rb
@@ -0,0 +1,20 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 JournalDetail < ActiveRecord::Base
+ belongs_to :journal
+end
diff --git a/app/models/mailer.rb b/app/models/mailer.rb
new file mode 100644
index 000000000..07047c594
--- /dev/null
+++ b/app/models/mailer.rb
@@ -0,0 +1,53 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 Mailer < ActionMailer::Base
+
+ helper IssuesHelper
+
+ def issue_add(issue)
+ # Sends to all project members
+ @recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
+ @from = $RDM_MAIL_FROM
+ @subject = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{issue.status.name} - #{issue.subject}"
+ @body['issue'] = issue
+ end
+
+ def issue_edit(journal)
+ # Sends to all project members
+ issue = journal.journalized
+ @recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
+ @from = $RDM_MAIL_FROM
+ @subject = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{issue.status.name} - #{issue.subject}"
+ @body['issue'] = issue
+ @body['journal']= journal
+ end
+
+ def lost_password(token)
+ @recipients = token.user.mail
+ @from = $RDM_MAIL_FROM
+ @subject = l(:mail_subject_lost_password)
+ @body['token'] = token
+ end
+
+ def register(token)
+ @recipients = token.user.mail
+ @from = $RDM_MAIL_FROM
+ @subject = l(:mail_subject_register)
+ @body['token'] = token
+ end
+end
diff --git a/app/models/member.rb b/app/models/member.rb
new file mode 100644
index 000000000..1214b6443
--- /dev/null
+++ b/app/models/member.rb
@@ -0,0 +1,29 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 Member < ActiveRecord::Base
+ belongs_to :user
+ belongs_to :role
+ belongs_to :project
+
+ validates_presence_of :role, :user, :project
+ validates_uniqueness_of :user_id, :scope => :project_id
+
+ def name
+ self.user.display_name
+ end
+end
diff --git a/app/models/news.rb b/app/models/news.rb
new file mode 100644
index 000000000..faafa7eef
--- /dev/null
+++ b/app/models/news.rb
@@ -0,0 +1,28 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 News < ActiveRecord::Base
+ belongs_to :project
+ belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
+
+ validates_presence_of :title, :description
+
+ # returns last created news
+ def self.latest
+ find(:all, :limit => 5, :include => [ :author, :project ], :order => "news.created_on DESC")
+ end
+end
diff --git a/app/models/permission.rb b/app/models/permission.rb
new file mode 100644
index 000000000..620974608
--- /dev/null
+++ b/app/models/permission.rb
@@ -0,0 +1,63 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 Permission < ActiveRecord::Base
+ has_and_belongs_to_many :roles
+
+ validates_presence_of :controller, :action, :description
+
+ GROUPS = {
+ 100 => :label_project,
+ 200 => :label_member_plural,
+ 300 => :label_version_plural,
+ 400 => :label_issue_category_plural,
+ 1000 => :label_issue_plural,
+ 1100 => :label_news_plural,
+ 1200 => :label_document_plural,
+ 1300 => :label_attachment_plural,
+ }.freeze
+
+ @@cached_perms_for_public = nil
+ @@cached_perms_for_roles = nil
+
+ def name
+ self.controller + "/" + self.action
+ end
+
+ def group_id
+ (self.sort / 100)*100
+ end
+
+ def self.allowed_to_public(action)
+ @@cached_perms_for_public ||= find(:all, :conditions => ["is_public=?", true]).collect {|p| "#{p.controller}/#{p.action}"}
+ @@cached_perms_for_public.include? action
+ end
+
+ def self.allowed_to_role(action, role)
+ @@cached_perms_for_roles ||=
+ begin
+ perms = {}
+ find(:all, :include => :roles).each {|p| perms.store "#{p.controller}/#{p.action}", p.roles.collect {|r| r.id } }
+ perms
+ end
+ @@cached_perms_for_roles[action] and @@cached_perms_for_roles[action].include? role
+ end
+
+ def self.allowed_to_role_expired
+ @@cached_perms_for_roles = nil
+ end
+end
diff --git a/app/models/project.rb b/app/models/project.rb
new file mode 100644
index 000000000..ae7436910
--- /dev/null
+++ b/app/models/project.rb
@@ -0,0 +1,57 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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
+ has_many :versions, :dependent => true, :order => "versions.effective_date DESC, versions.name DESC"
+ has_many :members, :dependent => true
+ has_many :users, :through => :members
+ has_many :custom_values, :dependent => true, :as => :customized
+ has_many :issues, :dependent => true, :order => "issues.created_on DESC", :include => :status
+ has_many :documents, :dependent => true
+ has_many :news, :dependent => true, :include => :author
+ has_many :issue_categories, :dependent => true, :order => "issue_categories.name"
+ has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => 'custom_fields_projects', :association_foreign_key => 'custom_field_id'
+ acts_as_tree :order => "name", :counter_cache => true
+
+ validates_presence_of :name, :description
+ validates_uniqueness_of :name
+ validates_associated :custom_values, :on => :update
+
+ # returns 5 last created projects
+ def self.latest
+ find(:all, :limit => 5, :order => "created_on DESC")
+ end
+
+ # Returns an array of all custom fields enabled for project issues
+ # (explictly associated custom fields and custom fields enabled for all projects)
+ def custom_fields_for_issues(tracker)
+ tracker.custom_fields.find(:all, :include => :projects,
+ :conditions => ["is_for_all=? or project_id=?", true, self.id])
+ #(CustomField.for_all + custom_fields).uniq
+ end
+
+ def all_custom_fields
+ @all_custom_fields ||= IssueCustomField.find(:all, :include => :projects,
+ :conditions => ["is_for_all=? or project_id=?", true, self.id])
+ end
+
+protected
+ def validate
+ errors.add(parent_id, " must be a root project") if parent and parent.parent
+ errors.add_to_base("A project with subprojects can't be a subproject") if parent and projects_count > 0
+ end
+end
diff --git a/app/models/project_custom_field.rb b/app/models/project_custom_field.rb
new file mode 100644
index 000000000..baa533812
--- /dev/null
+++ b/app/models/project_custom_field.rb
@@ -0,0 +1,22 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 ProjectCustomField < CustomField
+ def type_name
+ :label_project_plural
+ end
+end
diff --git a/app/models/role.rb b/app/models/role.rb
new file mode 100644
index 000000000..6908095e9
--- /dev/null
+++ b/app/models/role.rb
@@ -0,0 +1,31 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 Role < ActiveRecord::Base
+ before_destroy :check_integrity
+ has_and_belongs_to_many :permissions
+ has_many :workflows, :dependent => true
+ has_many :members
+
+ validates_presence_of :name
+ validates_uniqueness_of :name
+
+private
+ def check_integrity
+ raise "Can't delete role" if Member.find(:first, :conditions =>["role_id=?", self.id])
+ end
+end
diff --git a/app/models/token.rb b/app/models/token.rb
new file mode 100644
index 000000000..98745d29e
--- /dev/null
+++ b/app/models/token.rb
@@ -0,0 +1,44 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 Token < ActiveRecord::Base
+ belongs_to :user
+
+ @@validity_time = 1.day
+
+ def before_create
+ self.value = Token.generate_token_value
+ end
+
+ # Return true if token has expired
+ def expired?
+ return Time.now > self.created_on + @@validity_time
+ end
+
+ # Delete all expired tokens
+ def self.destroy_expired
+ Token.delete_all ["created_on < ?", Time.now - @@validity_time]
+ end
+
+private
+ def self.generate_token_value
+ chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
+ token_value = ''
+ 40.times { |i| token_value << chars[rand(chars.size-1)] }
+ token_value
+ end
+end
diff --git a/app/models/tracker.rb b/app/models/tracker.rb
new file mode 100644
index 000000000..a4376a351
--- /dev/null
+++ b/app/models/tracker.rb
@@ -0,0 +1,31 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 Tracker < ActiveRecord::Base
+ before_destroy :check_integrity
+ has_many :issues
+ has_many :workflows, :dependent => true
+ has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => 'custom_fields_trackers', :association_foreign_key => 'custom_field_id'
+
+ validates_presence_of :name
+ validates_uniqueness_of :name
+
+private
+ def check_integrity
+ raise "Can't delete tracker" if Issue.find(:first, :conditions => ["tracker_id=?", self.id])
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
new file mode 100644
index 000000000..a82c98a88
--- /dev/null
+++ b/app/models/user.rb
@@ -0,0 +1,129 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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.
+
+require "digest/sha1"
+
+class User < ActiveRecord::Base
+ has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :dependent => true
+ has_many :projects, :through => :memberships
+ has_many :custom_values, :dependent => true, :as => :customized
+ has_one :preference, :dependent => true, :class_name => 'UserPreference'
+ belongs_to :auth_source
+
+ attr_accessor :password, :password_confirmation
+ attr_accessor :last_before_login_on
+ # Prevents unauthorized assignments
+ attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
+
+ validates_presence_of :login, :firstname, :lastname, :mail
+ validates_uniqueness_of :login, :mail
+ # Login must contain lettres, numbers, underscores only
+ validates_format_of :login, :with => /^[a-z0-9_]+$/i
+ validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
+ # Password length between 4 and 12
+ validates_length_of :password, :in => 4..12, :allow_nil => true
+ validates_confirmation_of :password, :allow_nil => true
+ validates_associated :custom_values, :on => :update
+
+ # Account statuses
+ STATUS_ACTIVE = 1
+ STATUS_REGISTERED = 2
+ STATUS_LOCKED = 3
+
+ def before_save
+ # update hashed_password if password was set
+ self.hashed_password = User.hash_password(self.password) if self.password
+ end
+
+ # Returns the user that matches provided login and password, or nil
+ def self.try_to_login(login, password)
+ user = find(:first, :conditions => ["login=?", login])
+ if user
+ # user is already in local database
+ return nil if !user.active?
+ if user.auth_source
+ # user has an external authentication method
+ return nil unless user.auth_source.authenticate(login, password)
+ else
+ # authentication with local password
+ return nil unless User.hash_password(password) == user.hashed_password
+ end
+ else
+ # user is not yet registered, try to authenticate with available sources
+ attrs = AuthSource.authenticate(login, password)
+ if attrs
+ onthefly = new(*attrs)
+ onthefly.login = login
+ onthefly.language = $RDM_DEFAULT_LANG
+ if onthefly.save
+ user = find(:first, :conditions => ["login=?", login])
+ logger.info("User '#{user.login}' created on the fly.") if logger
+ end
+ end
+ end
+ user.update_attribute(:last_login_on, Time.now) if user
+ user
+
+ rescue => text
+ raise text
+ end
+
+ # Return user's full name for display
+ def display_name
+ firstname + " " + lastname
+ end
+
+ def name
+ display_name
+ end
+
+ def active?
+ self.status == STATUS_ACTIVE
+ end
+
+ def registered?
+ self.status == STATUS_REGISTERED
+ end
+
+ def locked?
+ self.status == STATUS_LOCKED
+ end
+
+ def check_password?(clear_password)
+ User.hash_password(clear_password) == self.hashed_password
+ end
+
+ def role_for_project(project_id)
+ @role_for_projects ||=
+ begin
+ roles = {}
+ self.memberships.each { |m| roles.store m.project_id, m.role_id }
+ roles
+ end
+ @role_for_projects[project_id]
+ end
+
+ def pref
+ self.preference ||= UserPreference.new(:user => self)
+ end
+
+private
+ # Return password digest
+ def self.hash_password(clear_password)
+ Digest::SHA1.hexdigest(clear_password || "")
+ end
+end
diff --git a/app/models/user_custom_field.rb b/app/models/user_custom_field.rb
new file mode 100644
index 000000000..866234a7f
--- /dev/null
+++ b/app/models/user_custom_field.rb
@@ -0,0 +1,23 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 UserCustomField < CustomField
+ def type_name
+ :label_user_plural
+ end
+end
+
diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb
new file mode 100644
index 000000000..5240c9757
--- /dev/null
+++ b/app/models/user_preference.rb
@@ -0,0 +1,44 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 UserPreference < ActiveRecord::Base
+ belongs_to :user
+ serialize :others, Hash
+
+ attr_protected :others
+
+ def initialize(attributes = nil)
+ super
+ self.others ||= {}
+ end
+
+ def [](attr_name)
+ if attribute_present? attr_name
+ super
+ else
+ others[attr_name]
+ end
+ end
+
+ def []=(attr_name, value)
+ if attribute_present? attr_name
+ super
+ else
+ others.store attr_name, value
+ end
+ end
+end
diff --git a/app/models/version.rb b/app/models/version.rb
new file mode 100644
index 000000000..0ae1edda8
--- /dev/null
+++ b/app/models/version.rb
@@ -0,0 +1,32 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 Version < ActiveRecord::Base
+ before_destroy :check_integrity
+ belongs_to :project
+ has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id'
+ has_many :attachments, :as => :container, :dependent => true
+
+ validates_presence_of :name
+ validates_uniqueness_of :name, :scope => [:project_id]
+ validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :activerecord_error_not_a_date
+
+private
+ def check_integrity
+ raise "Can't delete version" if self.fixed_issues.find(:first)
+ end
+end
diff --git a/app/models/workflow.rb b/app/models/workflow.rb
new file mode 100644
index 000000000..22c873fc7
--- /dev/null
+++ b/app/models/workflow.rb
@@ -0,0 +1,24 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# 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.
+#
+# 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.
+#
+# 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 Workflow < ActiveRecord::Base
+ belongs_to :role
+ belongs_to :old_status, :class_name => 'IssueStatus', :foreign_key => 'old_status_id'
+ belongs_to :new_status, :class_name => 'IssueStatus', :foreign_key => 'new_status_id'
+
+ validates_presence_of :role, :old_status, :new_status
+end