Commits

Anonymous committed 351639d

0.3 unstable

  • Participants
  • Parent commits d342fe8

Comments (0)

Files changed (184)

File redmine/app/controllers/account_controller.rb

 
 class AccountController < ApplicationController
   layout 'base'	
+  helper :custom_fields
+  include CustomFieldsHelper   
   
   # prevents login action to be filtered by check_if_login_required application scope filter
-  skip_before_filter :check_if_login_required, :only => :login
-  before_filter :require_login, :except => [:show, :login]
+  skip_before_filter :check_if_login_required, :only => [:login, :lost_password, :register]
+  before_filter :require_login, :except => [:show, :login, :lost_password, :register]
 
+  # Show user's account
   def show
     @user = User.find(params[:id])
   end
   # Login request and validation
   def login
     if request.get?
-      session[:user] = nil
+      # Logout user
+      self.logged_in_user = nil
     else
-      logged_in_user = User.try_to_login(params[:login], params[:password])
-      if logged_in_user
-        session[:user] = logged_in_user
+      # Authenticate user
+      user = User.try_to_login(params[:login], params[:password])
+      if user
+        self.logged_in_user = user
         redirect_back_or_default :controller => 'account', :action => 'my_page'
       else
-        flash[:notice] = _('Invalid user/password')
+        flash[:notice] = l(:notice_account_invalid_creditentials)
       end
     end
   end
+
+  # Log out current user and redirect to welcome page
+  def logout
+    self.logged_in_user = nil
+    redirect_to :controller => ''
+  end
+
+  # Show logged in user's page
+  def my_page
+    @user = self.logged_in_user
+    @reported_issues = Issue.find(:all, :conditions => ["author_id=?", @user.id], :limit => 10, :include => [ :status, :project, :tracker ], :order => 'issues.updated_on DESC')
+    @assigned_issues = Issue.find(:all, :conditions => ["assigned_to_id=?", @user.id], :limit => 10, :include => [ :status, :project, :tracker ], :order => 'issues.updated_on DESC')
+  end
+
+  # Edit logged in user's account
+  def my_account
+    @user = self.logged_in_user
+    if request.post? and @user.update_attributes(@params[:user])
+      set_localization
+      flash[:notice] = l(:notice_account_updated)
+      self.logged_in_user.reload
+    end
+  end
 	
-	# Log out current user and redirect to welcome page
-	def logout
-		session[:user] = nil
-		redirect_to(:controller => '')
-	end
-
-	def my_page
-		@user = session[:user]		
-		@reported_issues = Issue.find(:all, :conditions => ["author_id=?", @user.id], :limit => 10, :include => [ :status, :project, :tracker ], :order => 'issues.updated_on DESC')
-		@assigned_issues = Issue.find(:all, :conditions => ["assigned_to_id=?", @user.id], :limit => 10, :include => [ :status, :project, :tracker ], :order => 'issues.updated_on DESC')
-	end
-  
-	# Edit current user's account
-	def my_account
-		@user = User.find(session[:user].id)
-		if request.post? and @user.update_attributes(@params[:user])
-			flash[:notice] = 'Account was successfully updated.'
-      session[:user] = @user
-      set_localization
-		end
-	end
-	
-  # Change current user's password
+  # Change logged in user's password
   def change_password
-    @user = User.find(session[:user].id)
+    @user = self.logged_in_user
     if @user.check_password?(@params[:password])
       @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
-      flash[:notice] = 'Password was successfully updated.' if @user.save
+      flash[:notice] = l(:notice_account_password_updated) if @user.save
     else
-      flash[:notice] = 'Wrong password'
+      flash[:notice] = l(:notice_account_wrong_password)
     end
     render :action => 'my_account'
-  end
+  end
+  
+  # Enable user to choose a new password
+  def lost_password
+    if params[:token]
+      @token = Token.find_by_action_and_value("recovery", params[:token])
+      redirect_to :controller => '' and return unless @token and !@token.expired?
+      @user = @token.user
+      if request.post?
+        @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
+        if @user.save
+          @token.destroy
+          flash[:notice] = l(:notice_account_password_updated)
+          redirect_to :action => 'login'
+          return
+        end 
+      end
+      render :template => "account/password_recovery"
+      return
+    else
+      if request.post?
+        user = User.find_by_mail(params[:mail])      
+        flash[:notice] = l(:notice_account_unknown_email) and return unless user
+        token = Token.new(:user => user, :action => "recovery")
+        if token.save
+          Mailer.set_language_if_valid(Localization.lang)
+          Mailer.deliver_lost_password(token)
+          flash[:notice] = l(:notice_account_lost_email_sent)
+          redirect_to :action => 'login'
+          return
+        end
+      end
+    end
+  end
+  
+  # User self-registration
+  def register
+    redirect_to :controller => '' and return if $RDM_SELF_REGISTRATION == false
+    if params[:token]
+      token = Token.find_by_action_and_value("register", params[:token])
+      redirect_to :controller => '' and return unless token and !token.expired?
+      user = token.user
+      redirect_to :controller => '' and return unless user.status == User::STATUS_REGISTERED
+      user.status = User::STATUS_ACTIVE
+      if user.save
+        token.destroy
+        flash[:notice] = l(:notice_account_activated)
+        redirect_to :action => 'login'
+        return
+      end      
+    else
+      if request.get?
+        @user = User.new(:language => $RDM_DEFAULT_LANG)
+        @custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user) }
+      else
+        @user = User.new(params[:user])
+        @user.admin = false
+        @user.login = params[:user][:login]
+        @user.status = User::STATUS_REGISTERED
+        @user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
+        @custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => params["custom_fields"][x.id.to_s]) }
+        @user.custom_values = @custom_values
+        token = Token.new(:user => @user, :action => "register")
+        if @user.save and token.save
+          Mailer.set_language_if_valid(Localization.lang)
+          Mailer.deliver_register(token)
+          flash[:notice] = l(:notice_account_register_done)
+          redirect_to :controller => ''
+        end
+      end
+    end
+  end
 end

File redmine/app/controllers/application.rb

 class ApplicationController < ActionController::Base
   before_filter :check_if_login_required, :set_localization
   
+  def logged_in_user=(user)
+    @logged_in_user = user
+    session[:user_id] = (user ? user.id : nil)
+  end
+  
+  def logged_in_user
+    if session[:user_id]
+      @logged_in_user ||= User.find(session[:user_id], :include => :memberships)
+    else
+      nil
+    end
+  end
+  
   # check if login is globally required to access the application
   def check_if_login_required
-    require_login if RDM_LOGIN_REQUIRED
+    require_login if $RDM_LOGIN_REQUIRED
   end 
   
   def set_localization
     Localization.lang = begin
-      if session[:user]
-        session[:user].language
+      if self.logged_in_user and Localization.langs.keys.include? self.logged_in_user.language
+        self.logged_in_user.language
       elsif request.env['HTTP_ACCEPT_LANGUAGE']
         accept_lang = HTTPUtils.parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.split('-').first
-        if Localization.langs.collect{ |l| l[1] }.include? accept_lang
+        if Localization.langs.keys.include? accept_lang
           accept_lang
         end
       end
     rescue
       nil
-    end || RDM_DEFAULT_LANG
+    end || $RDM_DEFAULT_LANG
+
+    set_language_if_valid(Localization.lang)
+    
   end
   
   def require_login
-    unless session[:user]
+    unless self.logged_in_user
       store_location
       redirect_to(:controller => "account", :action => "login")
+      return false
     end
+    true
   end
 
   def require_admin
-    if session[:user].nil?
-      store_location
-      redirect_to(:controller => "account", :action => "login")
-    else
-      unless session[:user].admin?
-        flash[:notice] = "Acces not allowed"
-        redirect_to(:controller => "projects", :action => "list")
-      end
+    return unless require_login
+    unless self.logged_in_user.admin?
+      flash[:notice] = "Acces denied"
+      redirect_to:controller => ''
+      return false
     end
+    true
   end
 
   # authorizes the user for the requested action.
     # check if action is allowed on public projects
     if @project.is_public? and Permission.allowed_to_public "%s/%s" % [ @params[:controller], @params[:action] ]
       return true
-    end  
-    # if user not logged in, redirect to login form
-    unless session[:user]
-      store_location
-      redirect_to(:controller => "account", :action => "login")
-      return false
-    end
-    # if logged in, check if authorized    
-    if session[:user].admin? or Permission.allowed_to_role( "%s/%s" % [ @params[:controller], @params[:action] ], session[:user].role_for_project(@project.id)  )    
+    end    
+    # if action is not public, force login
+    return unless require_login    
+    # admin is always authorized
+    return true if self.logged_in_user.admin?
+    # if not admin, check membership permission    
+    @user_membership ||= Member.find(:first, :conditions => ["user_id=? and project_id=?", self.logged_in_user.id, @project.id])    
+    if @user_membership and Permission.allowed_to_role( "%s/%s" % [ @params[:controller], @params[:action] ], @user_membership.role_id )    
       return true		
     end		
     flash[:notice] = "Acces denied"
-    redirect_to(:controller => "")
+    redirect_to :controller => ''
     false
   end
 	

File redmine/app/controllers/auth_sources_controller.rb

+# 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 AuthSourcesController < ApplicationController
+  layout 'base'	
+  before_filter :require_admin
+
+  def index
+    list
+    render :action => 'list'
+  end
+
+  # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
+  verify :method => :post, :only => [ :destroy, :create, :update ],
+         :redirect_to => { :action => :list }
+
+  def list
+    @auth_source_pages, @auth_sources = paginate :auth_sources, :per_page => 10
+  end
+
+  def new
+    @auth_source = AuthSourceLdap.new
+  end
+
+  def create
+    @auth_source = AuthSourceLdap.new(params[:auth_source])
+    if @auth_source.save
+      flash[:notice] = l(:notice_successful_create)
+      redirect_to :action => 'list'
+    else
+      render :action => 'new'
+    end
+  end
+
+  def edit
+    @auth_source = AuthSource.find(params[:id])
+  end
+
+  def update
+    @auth_source = AuthSource.find(params[:id])
+    if @auth_source.update_attributes(params[:auth_source])
+      flash[:notice] = l(:notice_successful_update)
+      redirect_to :action => 'list'
+    else
+      render :action => 'edit'
+    end
+  end
+  
+  def test_connection
+    @auth_method = AuthSource.find(params[:id])
+    begin
+      @auth_method.test_connection
+    rescue => text
+      flash[:notice] = text
+    end
+    flash[:notice] ||= l(:notice_successful_connection)
+    redirect_to :action => 'list'
+  end
+
+  def destroy
+    @auth_source = AuthSource.find(params[:id])
+    unless @auth_source.users.find(:first)
+      @auth_source.destroy
+      flash[:notice] = l(:notice_successful_delete)
+    end
+    redirect_to :action => 'list'
+  end
+end

File redmine/app/controllers/custom_fields_controller.rb

 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 class CustomFieldsController < ApplicationController
-	layout 'base'		
-	before_filter :require_admin
-	
+  layout 'base'		
+  before_filter :require_admin
+
   def index
     list
     render :action => 'list'
   end
 
   def list
-    @custom_field_pages, @custom_fields = paginate :custom_fields, :per_page => 10
+    @custom_field_pages, @custom_fields = paginate :custom_fields, :per_page => 15
   end
-
+  
   def new
-    if request.get?
-      @custom_field = CustomField.new
-    else
-      @custom_field = CustomField.new(params[:custom_field])
-      if @custom_field.save
-        flash[:notice] = 'CustomField was successfully created.'
-        redirect_to :action => 'list'
+    case params[:type]
+      when "IssueCustomField" 
+        @custom_field = IssueCustomField.new(params[:custom_field])
+        @custom_field.trackers = Tracker.find(params[:tracker_ids]) if params[:tracker_ids]
+      when "UserCustomField" 
+        @custom_field = UserCustomField.new(params[:custom_field])
+      when "ProjectCustomField" 
+        @custom_field = ProjectCustomField.new(params[:custom_field])
+      else
+        redirect_to :action => 'list'
+        return
+    end  
+    if request.post? and @custom_field.save
+      redirect_to :action => 'list'
+    end
+    @trackers = Tracker.find(:all)
+  end
+
+  def edit
+    @custom_field = CustomField.find(params[:id])
+    if request.post? and @custom_field.update_attributes(params[:custom_field])
+      if @custom_field.is_a? IssueCustomField
+        @custom_field.trackers = params[:tracker_ids] ? Tracker.find(params[:tracker_ids]) : []
       end
-    end
-  end
-
-  def edit
-    @custom_field = CustomField.find(params[:id])
-    if request.post? and @custom_field.update_attributes(params[:custom_field])
-      flash[:notice] = 'CustomField was successfully updated.'
-      redirect_to :action => 'list'
-    end
-  end
+      flash[:notice] = 'Custom field was successfully updated.'
+      redirect_to :action => 'list'
+    end
+    @trackers = Tracker.find(:all)
+  end
 
   def destroy
     CustomField.find(params[:id]).destroy
   rescue
     flash[:notice] = "Unable to delete custom field"
     redirect_to :action => 'list'
-  end
+  end
 end

File redmine/app/controllers/documents_controller.rb

     # Save the attachment
     if params[:attachment][:file].size > 0
       @attachment = @document.attachments.build(params[:attachment])      
-      @attachment.author_id = session[:user].id unless session[:user].nil?
+      @attachment.author_id = self.logged_in_user.id if self.logged_in_user
       @attachment.save
     end
     render :action => 'show'

File redmine/app/controllers/issues_controller.rb

 	include CustomFieldsHelper
 	
 	def show
-    @status_options = @issue.status.workflows.find(:all, :conditions => ["role_id=? and tracker_id=?", session[:user].role_for_project(@project.id), @issue.tracker.id]).collect{ |w| w.new_status } if session[:user]
+    @status_options = @issue.status.workflows.find(:all, :conditions => ["role_id=? and tracker_id=?", self.logged_in_user.role_for_project(@project.id), @issue.tracker.id]).collect{ |w| w.new_status } if self.logged_in_user
+    @custom_values = @issue.custom_values.find(:all, :include => :custom_field)
 	end
 
-	def edit
-		@trackers = Tracker.find(:all)
+	def edit
 		@priorities = Enumeration::get_values('IPRI')
 		
 		if request.get?
-			@custom_values = @project.custom_fields_for_issues.collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
+			@custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
 		else
 			# Retrieve custom fields and values
-			@custom_values = @project.custom_fields_for_issues.collect { |x| CustomValue.new(:custom_field => x, :value => params["custom_fields"][x.id.to_s]) }
-
+			@custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
 			@issue.custom_values = @custom_values
-			if @issue.update_attributes(params[:issue])
+			@issue.attributes = params[:issue]
+			if @issue.save
 				flash[:notice] = 'Issue was successfully updated.'
 				redirect_to :action => 'show', :id => @issue
 			end
 	
 	def change_status
 		@history = @issue.histories.build(params[:history])	
-    @status_options = @issue.status.workflows.find(:all, :conditions => ["role_id=? and tracker_id=?", session[:user].role_for_project(@project.id), @issue.tracker.id]).collect{ |w| w.new_status } if session[:user]
+    @status_options = @issue.status.workflows.find(:all, :conditions => ["role_id=? and tracker_id=?", self.logged_in_user.role_for_project(@project.id), @issue.tracker.id]).collect{ |w| w.new_status } if self.logged_in_user
 		
 		if params[:confirm]
-			unless session[:user].nil?
-				@history.author = session[:user]
-			end			
+				@history.author_id = self.logged_in_user.id if self.logged_in_user
+	
 			if @history.save			
 				@issue.status = @history.status
 				@issue.fixed_version_id = (params[:issue][:fixed_version_id])
     # Save the attachment
     if params[:attachment][:file].size > 0
       @attachment = @issue.attachments.build(params[:attachment])      
-      @attachment.author_id = session[:user].id unless session[:user].nil?
+      @attachment.author_id = self.logged_in_user.id if self.logged_in_user
       @attachment.save
     end
     redirect_to :action => 'show', :id => @issue
     @issue.attachments.find(params[:attachment_id]).destroy
     redirect_to :action => 'show', :id => @issue
   end
-  
-	# Send the file in stream mode
-	def download
-		@attachment = @issue.attachments.find(params[:attachment_id])
-		send_file @attachment.diskfile, :filename => @attachment.filename
-	end
-	
+
+  # Send the file in stream mode
+  def download
+    @attachment = @issue.attachments.find(params[:attachment_id])
+    send_file @attachment.diskfile, :filename => @attachment.filename
+  end
+
 private
-	def find_project
+  def find_project
     @issue = Issue.find(params[:id])
-		@project = @issue.project
-	end  
-  
+    @project = @issue.project
+  end  
 end

File redmine/app/controllers/projects_controller.rb

           
   # Add a new project
   def add
-    @custom_fields = CustomField::find_all
-    @root_projects = Project::find(:all, :conditions => "parent_id is null")
+    @custom_fields = IssueCustomField.find(:all)
+    @root_projects = Project.find(:all, :conditions => "parent_id is null")
     @project = Project.new(params[:project])
-    if request.post?
-      @project.custom_fields = CustomField.find(@params[:custom_field_ids]) if @params[:custom_field_ids]
+    if request.get?
+      @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project) }
+    else
+      @project.custom_fields = CustomField.find(@params[:custom_field_ids]) if @params[:custom_field_ids]
+      @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
+      @project.custom_values = @custom_values			
       if @project.save
         flash[:notice] = 'Project was successfully created.'
         redirect_to :controller => 'admin', :action => 'projects'
     end	
   end
 	
-	# Show @project
+  # Show @project
   def show
+    @custom_values = @project.custom_values.find(:all, :include => :custom_field)
     @members = @project.members.find(:all, :include => [:user, :role])
     @subprojects = @project.children if @project.children_count > 0
-    @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "news.created_on DESC")
+    @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "news.created_on DESC")
+    @trackers = Tracker.find(:all)
   end
 
   def settings
     @root_projects = Project::find(:all, :conditions => ["parent_id is null and id <> ?", @project.id])
-    @custom_fields = CustomField::find_all
+    @custom_fields = IssueCustomField::find_all
     @issue_category ||= IssueCategory.new
     @member ||= @project.members.new
     @roles = Role.find_all
     @users = User.find_all - @project.members.find(:all, :include => :user).collect{|m| m.user }
+    @custom_values = ProjectCustomField.find(:all).collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
   end
   
   # Edit @project
   def edit
     if request.post?
-      @project.custom_fields = CustomField.find(@params[:custom_field_ids]) if @params[:custom_field_ids]
+      @project.custom_fields = IssueCustomField.find(@params[:custom_field_ids]) if @params[:custom_field_ids]
+      if params[:custom_fields]
+        @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
+        @project.custom_values = @custom_values
+      end
       if @project.update_attributes(params[:project])
         flash[:notice] = 'Project was successfully updated.'
         redirect_to :action => 'settings', :id => @project
       end
     end
   end
-  
-	# Delete @project
-	def destroy
+
+  # Delete @project
+  def destroy
     if request.post? and params[:confirm]
       @project.destroy
       redirect_to :controller => 'admin', :action => 'projects'
     end
-	end
+  end
 	
-	# Add a new issue category to @project
-	def add_issue_category
-		if request.post?
-			@issue_category = @project.issue_categories.build(params[:issue_category])
-			if @issue_category.save
-				redirect_to :action => 'settings', :id => @project
-			else
-        settings
-        render :action => 'settings'
-			end
-		end
-	end	
-	
-	# Add a new version to @project
-	def add_version
-		@version = @project.versions.build(params[:version])
-		if request.post? and @version.save
-      redirect_to :action => 'settings', :id => @project
-		end
-	end
-
-	# Add a new member to @project
-	def add_member
-    @member = @project.members.build(params[:member])
-		if request.post?
-			if @member.save
-        flash[:notice] = 'Member was successfully added.'
-				redirect_to :action => 'settings', :id => @project
-			else		
+  # Add a new issue category to @project
+  def add_issue_category
+    if request.post?
+      @issue_category = @project.issue_categories.build(params[:issue_category])
+      if @issue_category.save
+        redirect_to :action => 'settings', :id => @project
+      else
         settings
         render :action => 'settings'
       end
-		end
-	end
+    end
+  end	
+	
+  # Add a new version to @project
+  def add_version
+  	@version = @project.versions.build(params[:version])
+  	if request.post? and @version.save
+      redirect_to :action => 'settings', :id => @project
+  	end
+  end
 
-	# Show members list of @project
-	def list_members
-		@members = @project.members
-	end
+  # Add a new member to @project
+  def add_member
+    @member = @project.members.build(params[:member])
+  	if request.post?
+      if @member.save
+        flash[:notice] = 'Member was successfully added.'
+        redirect_to :action => 'settings', :id => @project
+      else		
+        settings
+        render :action => 'settings'
+      end
+    end
+  end
 
-	# Add a new document to @project
-	def add_document
-		@categories = Enumeration::get_values('DCAT')
-		@document = @project.documents.build(params[:document])    
-		if request.post?			
+  # Show members list of @project
+  def list_members
+    @members = @project.members
+  end
+
+  # Add a new document to @project
+  def add_document
+    @categories = Enumeration::get_values('DCAT')
+    @document = @project.documents.build(params[:document])    
+    if request.post?			
       # Save the attachment
-			if params[:attachment][:file].size > 0
-				@attachment = @document.attachments.build(params[:attachment])				
-        @attachment.author_id = session[:user].id unless session[:user].nil?
-			end      
-			if @document.save
-				redirect_to :action => 'list_documents', :id => @project
-			end		
-		end
-	end
+      if params[:attachment][:file].size > 0
+        @attachment = @document.attachments.build(params[:attachment])				
+        @attachment.author_id = self.logged_in_user.id if self.logged_in_user
+      end      
+      if @document.save
+        redirect_to :action => 'list_documents', :id => @project
+      end		
+    end
+  end
+  
+  # Show documents list of @project
+  def list_documents
+    @documents = @project.documents
+  end
 
-	# Show documents list of @project
-	def list_documents
-		@documents = @project.documents
-	end
+  # Add a new issue to @project
+  def add_issue
+    @tracker = Tracker.find(params[:tracker_id])
+    @priorities = Enumeration::get_values('IPRI')
+    @issue = Issue.new(:project => @project, :tracker => @tracker)
+    if request.get?      
+      @custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) }
+    else
+      @issue.attributes = params[:issue]
+      @issue.author_id = self.logged_in_user.id if self.logged_in_user
+      # Create the document if a file was sent
+      if params[:attachment][:file].size > 0
+        @attachment = @issue.attachments.build(params[:attachment])				
+        @attachment.author_id = self.logged_in_user.id if self.logged_in_user
+      end
+      @custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
+      @issue.custom_values = @custom_values			
+      if @issue.save
+        flash[:notice] = "Issue was successfully added."
+        Mailer.deliver_issue_add(@issue) if Permission.find_by_controller_and_action(@params[:controller], @params[:action]).mail_enabled?
+        redirect_to :action => 'list_issues', :id => @project
+      end		
+    end	
+  end
 
-	# Add a new issue to @project
-	def add_issue
-		@trackers = Tracker.find(:all)
-		@priorities = Enumeration::get_values('IPRI')		
-		if request.get?
-			@issue = @project.issues.build
-			@custom_values = @project.custom_fields_for_issues.collect { |x| CustomValue.new(:custom_field => x) }
-		else
-			# Create the issue and set the author
-			@issue = @project.issues.build(params[:issue])
-      @issue.author = session[:user] unless session[:user].nil?			
-			# Create the document if a file was sent
-			if params[:attachment][:file].size > 0
-				@attachment = @issue.attachments.build(params[:attachment])				
-        @attachment.author_id = session[:user].id unless session[:user].nil?
-			end
-			@custom_values = @project.custom_fields_for_issues.collect { |x| CustomValue.new(:custom_field => x, :value => params["custom_fields"][x.id.to_s]) }
-			@issue.custom_values = @custom_values			
-			if @issue.save
-        flash[:notice] = "Issue was successfully added."
-				Mailer.deliver_issue_add(@issue) if Permission.find_by_controller_and_action(@params[:controller], @params[:action]).mail_enabled?
-				redirect_to :action => 'list_issues', :id => @project
-			end		
-		end	
-	end
-	
   # Show filtered/sorted issues list of @project
   def list_issues
     sort_init 'issues.id', 'desc'
 
     @issue_count = Issue.count(:include => [:status, :project], :conditions => search_filter_clause)		
     @issue_pages = Paginator.new self, @issue_count, 15, @params['page']								
-    @issues =  Issue.find :all, :order => sort_clause,
+    @issues = Issue.find :all, :order => sort_clause,
 						:include => [ :author, :status, :tracker, :project ],
 						:conditions => search_filter_clause,
 						:limit  =>  @issue_pages.items_per_page,
-						:offset =>  @issue_pages.current.offset								
+						:offset =>  @issue_pages.current.offset
   end
 
   # Export filtered/sorted issues list to CSV
       :type => 'text/csv; charset=utf-8; header=present',
       :filename => 'export.csv')
   end
-  
-	# Add a news to @project
-	def add_news
-    @news = @project.news.build(params[:news])
-		if request.post?
-			@news.author = session[:user] unless session[:user].nil?
-			if @news.save
-				redirect_to :action => 'list_news', :id => @project
-			end
-		end
-	end
 
-	# Show news list of @project
+  # Add a news to @project
+  def add_news
+    @news = News.new(:project => @project)
+    if request.post?
+      @news.attributes = params[:news]
+      @news.author_id = self.logged_in_user.id if self.logged_in_user
+      if @news.save
+        redirect_to :action => 'list_news', :id => @project
+      end
+    end
+  end
+
+  # Show news list of @project
   def list_news
     @news_pages, @news = paginate :news, :per_page => 10, :conditions => ["project_id=?", @project.id], :include => :author, :order => "news.created_on DESC"
   end
-  
+
   def add_file  
     if request.post?
       # Save the attachment
       if params[:attachment][:file].size > 0
         @attachment = @project.versions.find(params[:version_id]).attachments.build(params[:attachment])      
-        @attachment.author_id = session[:user].id unless session[:user].nil?
+        @attachment.author_id = self.logged_in_user.id if self.logged_in_user
         if @attachment.save
           redirect_to :controller => 'projects', :action => 'list_files', :id => @project
         end
   end
 
 private
-	# Find project of id params[:id]
-	# if not found, redirect to project list
-	# used as a before_filter
-	def find_project
-		@project = Project.find(params[:id])		
-		rescue
-			flash[:notice] = 'Project not found.'
-			redirect_to :action => 'list'			
-	end
-
+  # Find project of id params[:id]
+  # if not found, redirect to project list
+  # used as a before_filter
+  def find_project
+    @project = Project.find(params[:id])		
+  rescue
+    flash[:notice] = 'Project not found.'
+    redirect_to :action => 'list'			
+  end
 end

File redmine/app/controllers/users_controller.rb

 
   helper :sort
   include SortHelper
+  helper :custom_fields
+  include CustomFieldsHelper   
 
   def index
     list
 
   def add
     if request.get?
-      @user = User.new
+      @user = User.new(:language => $RDM_DEFAULT_LANG)
+      @custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user) }
     else
       @user = User.new(params[:user])
       @user.admin = params[:user][:admin] || false
       @user.login = params[:user][:login]
       @user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
+      @custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => params["custom_fields"][x.id.to_s]) }
+      @user.custom_values = @custom_values			
       if @user.save
         flash[:notice] = 'User was successfully created.'
         redirect_to :action => 'list'
 
   def edit
     @user = User.find(params[:id])
-    if request.post?
+    if request.get?
+      @custom_values = UserCustomField.find(:all).collect { |x| @user.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
+    else
       @user.admin = params[:user][:admin] if params[:user][:admin]
       @user.login = params[:user][:login] if params[:user][:login]
       @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless params[:password].nil? or params[:password].empty?
+      if params[:custom_fields]
+        @custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => params["custom_fields"][x.id.to_s]) }
+        @user.custom_values = @custom_values
+      end
       if @user.update_attributes(params[:user])
         flash[:notice] = 'User was successfully updated.'
         redirect_to :action => 'list'

File redmine/app/controllers/welcome_controller.rb

 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 class WelcomeController < ApplicationController
-	layout 'base'
-	
-	def index
+  layout 'base'
+
+  def index
     @news = News.latest
     @projects = Project.latest
-	end
-
+  end
 end

File redmine/app/helpers/application_helper.rb

 
 module ApplicationHelper
 
-	def loggedin?
-		session[:user]
-	end
+  # return current logged in user or nil
+  def loggedin?
+    @logged_in_user
+  end
+  
+  # return true if user is loggend in and is admin, otherwise false
+  def admin_loggedin?
+    @logged_in_user and @logged_in_user.admin?
+  end
 
-	def admin_loggedin?
-		session[:user] && session[:user].admin
-	end
-	
-	def authorize_for(controller, action)  
+  def authorize_for(controller, action)  
     # check if action is allowed on public projects
     if @project.is_public? and Permission.allowed_to_public "%s/%s" % [ controller, action ]
       return true
-    end  
+    end
     # check if user is authorized    
-    if session[:user] and (session[:user].admin? or Permission.allowed_to_role( "%s/%s" % [ controller, action ], session[:user].role_for_project(@project.id)  )  )
-			return true
-		end		
-		return false	  
-	end
-	
-	def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
-		link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller], options[:action])
-	end
-	
-	# Display a link to user's account page
-	def link_to_user(user)
-		link_to user.display_name, :controller => 'account', :action => 'show', :id => user
-	end
-	
+    if @logged_in_user and (@logged_in_user.admin? or Permission.allowed_to_role( "%s/%s" % [ controller, action ], @logged_in_user.role_for_project(@project.id)  )  )
+      return true
+    end
+    return false
+  end
+
+  # Display a link if user is authorized
+  def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
+    link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller], options[:action])
+  end
+
+  # Display a link to user's account page
+  def link_to_user(user)
+    link_to user.display_name, :controller => 'account', :action => 'show', :id => user
+  end
+
   def format_date(date)
     _('(date)', date) if date
   end
     html << ' ' + link_to((_('Next') + ' &#187;'), { :page => paginator.current.next }) if paginator.current.next
     html  
   end
-
+  
+  def error_messages_for(object_name, options = {})
+    options = options.symbolize_keys
+    object = instance_variable_get("@#{object_name}")
+    if object && !object.errors.empty?
+      # build full_messages here with controller current language
+      full_messages = []
+      object.errors.each do |attr, msg|
+        next if msg.nil?
+        if attr == "base"
+          full_messages << l(msg)
+        else
+          full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg)
+        end
+      end   
+      content_tag("div",
+        content_tag(
+          options[:header_tag] || "h2", lwr(:gui_validation_error, object.errors.count) + " :"
+        ) +
+        content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
+        "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
+      )
+    else
+      ""
+    end
+  end
 end

File redmine/app/helpers/auth_sources_helper.rb

+# 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.
+
+module AuthSourcesHelper
+end

File redmine/app/helpers/custom_fields_helper.rb

 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 module CustomFieldsHelper
-	def custom_field_tag(custom_value)
-	
-		custom_field = custom_value.custom_field
-		
-		field_name = "custom_fields[#{custom_field.id}]"
-		
-		case custom_field.typ
-		when 0 .. 2
-			text_field_tag field_name, custom_value.value
-		when 3
-			check_box field_name
-		when 4
-			select_tag field_name, 
-					options_for_select(custom_field.possible_values.split('|'),
-					custom_value.value)
-		end
-	end
+
+  def custom_field_tag(custom_value)	
+    custom_field = custom_value.custom_field
+    field_name = "custom_fields[#{custom_field.id}]"
+    case custom_field.field_format
+    when "string", "int", "date"
+      text_field_tag field_name, custom_value.value
+    when "text"
+      text_area_tag field_name, custom_value.value, :cols => 60, :rows => 3
+    when "bool"
+      check_box_tag(field_name, "1", custom_value.value == "1") + 
+      hidden_field_tag(field_name, "0")
+    when "list"
+      select_tag field_name, 
+                  "<option></option>" + options_for_select(custom_field.possible_values.split('|'),
+                  custom_value.value)
+    end
+  end
+  
+  def custom_field_label_tag(custom_value)
+    content_tag "label", custom_value.custom_field.name +
+	(custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>" : "")
+  end
+  
+  def custom_field_tag_with_label(custom_value)
+    case custom_value.custom_field.field_format
+    when "bool"
+      custom_field_tag(custom_value) + " " + custom_field_label_tag(custom_value)
+    else
+      custom_field_label_tag(custom_value) + "<br />" + custom_field_tag(custom_value)
+    end	  
+  end
+
+  def show_value(custom_value)
+    case custom_value.custom_field.field_format
+    when "bool"
+      l_YesNo(custom_value.value == "1")
+    else
+      custom_value.value
+    end	
+  end
+
+  def custom_field_formats_for_select
+    CustomField::FIELD_FORMATS.keys.collect { |k| [ l(CustomField::FIELD_FORMATS[k]), k ] }
+  end
 end

File redmine/app/models/attachment.rb

 	
 	# Returns file's location on disk
 	def diskfile
-		"#{RDM_STORAGE_PATH}/#{self.disk_filename}"
+		"#{$RDM_STORAGE_PATH}/#{self.disk_filename}"
 	end
   
   def increment_download

File redmine/app/models/auth_source.rb

+# 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

File redmine/app/models/auth_source_ldap.rb

+# 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", "organizationalPerson" ) 
+    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

File redmine/app/models/custom_field.rb

 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 class CustomField < ActiveRecord::Base
+  has_many :custom_values, :dependent => true
 
-	has_and_belongs_to_many :projects	
-	has_many :custom_values, :dependent => true
-	has_many :issues, :through => :issue_custom_values
+  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, :typ	
-	validates_uniqueness_of :name
+  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" }
 
-	TYPES = [
-			[ "Integer", 0 ],
-			[ "String", 1 ],
-			[ "Date", 2 ],
-			[ "Boolean", 3 ],
-			[ "List", 4 ]
-	].freeze
-	
-	def self.for_all
-		find(:all, :conditions => ["is_for_all=?", true])
-	end
-end
+  # to move in project_custom_field
+  def self.for_all
+    find(:all, :conditions => ["is_for_all=?", true])
+  end
+  
+  def type_name
+    nil
+  end
+end

File redmine/app/models/custom_value.rb

 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 class CustomValue < ActiveRecord::Base
-	belongs_to :issue
-	belongs_to :custom_field
-	
+  belongs_to :custom_field
+  belongs_to :customized, :polymorphic => true
+
 protected
   def validate
-		errors.add(custom_field.name, "can't be blank") if custom_field.is_required? and value.empty?
-		errors.add(custom_field.name, "is not valid") unless custom_field.regexp.empty? or value =~ Regexp.new(custom_field.regexp)
-		
-		case custom_field.typ
-		when 0
-			errors.add(custom_field.name, "must be an integer") unless value =~ /^[0-9]*$/	
-		when 1
-			errors.add(custom_field.name, "is too short") if custom_field.min_length > 0 and value.length < custom_field.min_length and value.length > 0
-			errors.add(custom_field.name, "is too long") if custom_field.max_length > 0 and value.length > custom_field.max_length
-		when 2
-			errors.add(custom_field.name, "must be a valid date") unless value =~ /^(\d+)\/(\d+)\/(\d+)$/ or value.empty?
-		when 3
-			
-		when 4
-			errors.add(custom_field.name, "is not a valid value") unless custom_field.possible_values.split('|').include? value or value.empty?
-		end
+    # errors are added to customized object unless it's nil
+    object = customized || self
+    
+    object.errors.add(custom_field.name, :activerecord_error_blank) if custom_field.is_required? and value.empty?
+    object.errors.add(custom_field.name, :activerecord_error_invalid) unless custom_field.regexp.empty? or value =~ Regexp.new(custom_field.regexp)
+
+    object.errors.add(custom_field.name, :activerecord_error_too_short) if custom_field.min_length > 0 and value.length < custom_field.min_length and value.length > 0
+    object.errors.add(custom_field.name, :activerecord_error_too_long) if custom_field.max_length > 0 and value.length > custom_field.max_length
+
+    case custom_field.field_format
+    when "int"
+      object.errors.add(custom_field.name, :activerecord_error_not_a_number) unless value =~ /^[0-9]*$/	
+    when "date"
+      object.errors.add(custom_field.name, :activerecord_error_invalid) unless value =~ /^(\d+)\/(\d+)\/(\d+)$/ or value.empty?
+    when "list"
+      object.errors.add(custom_field.name, :activerecord_error_inclusion) unless custom_field.possible_values.split('|').include? value or value.empty?
+    end
   end
-end
+end
+

File redmine/app/models/document.rb

   belongs_to :category, :class_name => "Enumeration", :foreign_key => "category_id"
   has_many :attachments, :as => :container, :dependent => true
 
-  validates_presence_of :title
+  validates_presence_of :project, :title, :category
 end

File redmine/app/models/issue.rb

 	has_many :histories, :class_name => 'IssueHistory', :dependent => true, :order => "issue_histories.created_on DESC", :include => :status
 	has_many :attachments, :as => :container, :dependent => true
 	
-	has_many :custom_values, :dependent => true 
+	has_many :custom_values, :dependent => true, :as => :customized
 	has_many :custom_fields, :through => :custom_values
 	
-	validates_presence_of :subject, :descr, :priority, :tracker, :author
+  validates_presence_of :subject, :description, :priority, :tracker, :author
+  validates_associated :custom_values, :on => :update
 	
 	# set default status for new issues
+	def before_validation
+		self.status = IssueStatus.default if new_record?
+	end
+	
 	def before_create
-		self.status = IssueStatus.default
-		build_history
+	 build_history
 	end
 	
 	def long_id

File redmine/app/models/issue_custom_field.rb

+# 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
+

File redmine/app/models/mailer.rb

 
 class Mailer < ActionMailer::Base
 
-	def issue_change_status(issue)
-		# Sends to all project members
-		@recipients 	= issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
-		@from       		= 'redmine@somenet.foo'
-		@subject    	= "Issue ##{issue.id} has been updated"
-		@body['issue'] = issue
-	end
-	
-	def issue_add(issue)
-		# Sends to all project members
-		@recipients 	= issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
-		@from       		= 'redmine@somenet.foo'
-		@subject    	= "Issue ##{issue.id} has been reported"
-		@body['issue'] = issue
-	end
-  
+  def issue_change_status(issue)
+    # Sends to all project members
+    @recipients     = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
+    @from           = 'redmine@somenet.foo'
+    @subject        = "Issue ##{issue.id} has been updated"
+    @body['issue']  = issue
+  end
+
+  def issue_add(issue)
+    # Sends to all project members
+    @recipients     = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
+    @from           = 'redmine@somenet.foo'
+    @subject        = "Issue ##{issue.id} has been reported"
+    @body['issue']  = issue
+  end
+
+  def lost_password(token)
+    @recipients     = token.user.mail
+    @from           = 'redmine@somenet.foo'
+    @subject        = "redMine password"
+    @body['token']  = token
+  end  
+
+  def register(token)
+    @recipients     = token.user.mail
+    @from           = 'redmine@somenet.foo'
+    @subject        = "redMine account activation"
+    @body['token']  = token
+  end  
+
 end

File redmine/app/models/news.rb

 	belongs_to :project
 	belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
 	
-	validates_presence_of :title, :shortdescr, :descr
+	validates_presence_of :title, :description
 
 	# returns last created news
 	def self.latest

File redmine/app/models/permission.rb

 class Permission < ActiveRecord::Base
 	has_and_belongs_to_many :roles
   
-	validates_presence_of :controller, :action, :descr
+	validates_presence_of :controller, :action, :description
   
   GROUPS = {
     100 => "Project",

File redmine/app/models/project.rb

 # 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"
-	has_many :members, :dependent => true
-	has_many :users, :through => :members
-	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
-	has_and_belongs_to_many :custom_fields
-	acts_as_tree :order => "name", :counter_cache => true
-	
-	validates_presence_of :name, :descr
-	validates_uniqueness_of :name
-	
-	# 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
-		(CustomField.for_all + custom_fields).uniq
-	end
-	
+  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
+
 protected
   def validate
     errors.add(parent_id, " must be a root project") if parent and parent.parent

File redmine/app/models/project_custom_field.rb

+# 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

File redmine/app/models/token.rb

+# 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

File redmine/app/models/tracker.rb

   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
   

File redmine/app/models/user.rb

 
 class User < ActiveRecord::Base
   has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :dependent => true
-	
+  has_many :custom_values, :dependent => true, :as => :customized
+  belongs_to :auth_source
+  
   attr_accessor :password, :password_confirmation
   attr_accessor :last_before_login_on
   # Prevents unauthorized assignments
   # 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
 	
   # Returns the user that matches provided login and password, or nil
   def self.try_to_login(login, password)
-    user = find(:first, :conditions => ["login=? and hashed_password=? and locked=?", login, User.hash_password(password), false])
+    user = find(:first, :conditions => ["login=?", login])
     if user
-      user.last_before_login_on = user.last_login_on
-      user.update_attribute(:last_login_on, Time.now)
-    end
+      # 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
+        # local authentication
+        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])