changeset 929:5f33065ddc4b redmine-1.3

Update to Redmine SVN rev 9414 on 1.3-stable branch
author Chris Cannam
date Wed, 27 Jun 2012 14:54:18 +0100
parents cbb26bc654de
children ec1c49528f36 433d4f72a19b
files .svn/wc.db app/controllers/admin_controller.rb app/controllers/boards_controller.rb app/controllers/comments_controller.rb app/controllers/documents_controller.rb app/controllers/issue_categories_controller.rb app/controllers/members_controller.rb app/controllers/messages_controller.rb app/controllers/news_controller.rb app/controllers/projects_controller.rb app/controllers/timelog_controller.rb app/controllers/versions_controller.rb app/controllers/wikis_controller.rb app/helpers/application_helper.rb app/models/board.rb app/models/changeset.rb app/models/comment.rb app/models/document.rb app/models/issue_category.rb app/models/mailer.rb app/models/member.rb app/models/message.rb app/models/news.rb app/models/principal.rb app/models/project.rb app/models/time_entry.rb app/models/user.rb app/models/user_preference.rb app/models/version.rb app/models/wiki.rb app/views/issues/_relations.html.erb app/views/projects/settings/_members.html.erb app/views/projects/settings/_repository.html.erb config/locales/it.yml config/locales/nl.yml config/locales/zh.yml doc/CHANGELOG extra/svn/Redmine.pm lib/redmine/unified_diff.rb lib/redmine/version.rb public/stylesheets/application.css test/functional/boards_controller_test.rb test/functional/issues_controller_test.rb test/functional/messages_controller_test.rb test/functional/project_enumerations_controller_test.rb test/functional/trackers_controller_test.rb test/unit/changeset_test.rb test/unit/helpers/application_helper_test.rb test/unit/lib/redmine/unified_diff_test.rb test/unit/mailer_test.rb test/unit/member_test.rb test/unit/news_test.rb test/unit/principal_test.rb test/unit/project_test.rb test/unit/user_test.rb vendor/gems/rubytree-0.5.2/.specification vendor/plugins/rfpdf/lib/tcpdf.rb
diffstat 57 files changed, 476 insertions(+), 129 deletions(-) [+]
line wrap: on
line diff
Binary file .svn/wc.db has changed
--- a/app/controllers/admin_controller.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/controllers/admin_controller.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -73,9 +73,7 @@
   def info
     @db_adapter_name = ActiveRecord::Base.connection.adapter_name
     @checklist = [
-      [:text_default_administrator_account_changed,
-          User.find(:first,
-                    :conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?],
+      [:text_default_administrator_account_changed, User.default_admin_account_changed?],
       [:text_file_repository_writable, File.writable?(Attachment.storage_path)],
       [:text_plugin_assets_writable,   File.writable?(Engines.public_directory)],
       [:text_rmagick_available,        Object.const_defined?(:Magick)]
--- a/app/controllers/boards_controller.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/controllers/boards_controller.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -65,7 +65,8 @@
   verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :index }
 
   def new
-    @board = Board.new(params[:board])
+    @board = Board.new
+    @board.safe_attributes = params[:board]
     @board.project = @project
     if request.post? && @board.save
       flash[:notice] = l(:notice_successful_create)
@@ -74,7 +75,8 @@
   end
 
   def edit
-    if request.post? && @board.update_attributes(params[:board])
+    @board.safe_attributes = params[:board]
+    if request.post? && @board.save
       redirect_to_settings_in_projects
     end
   end
--- a/app/controllers/comments_controller.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/controllers/comments_controller.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -7,7 +7,8 @@
 
   verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
   def create
-    @comment = Comment.new(params[:comment])
+    @comment = Comment.new
+    @comment.safe_attributes = params[:comment]
     @comment.author = User.current
     if @news.comments << @comment
       flash[:notice] = l(:label_comment_added)
--- a/app/controllers/documents_controller.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/controllers/documents_controller.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -47,7 +47,8 @@
   end
 
   def new
-    @document = @project.documents.build(params[:document])
+    @document = @project.documents.build
+    @document.safe_attributes = params[:document]
     if request.post? and @document.save	
       attachments = Attachment.attach_files(@document, params[:attachments])
       render_attachment_warning_if_needed(@document)
@@ -58,7 +59,8 @@
 
   def edit
     @categories = DocumentCategory.active #TODO: use it in the views
-    if request.post? and @document.update_attributes(params[:document])
+    @document.safe_attributes = params[:document]
+    if request.post? and @document.save
       flash[:notice] = l(:notice_successful_update)
       redirect_to :action => 'show', :id => @document
     end
--- a/app/controllers/issue_categories_controller.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/controllers/issue_categories_controller.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -39,12 +39,14 @@
   end
 
   def new
-    @category = @project.issue_categories.build(params[:issue_category])
+    @category = @project.issue_categories.build
+    @category.safe_attributes = params[:issue_category]
   end
 
   verify :method => :post, :only => :create
   def create
-    @category = @project.issue_categories.build(params[:issue_category])
+    @category = @project.issue_categories.build
+    @category.safe_attributes = params[:issue_category]
     if @category.save
       respond_to do |format|
         format.html do
@@ -75,7 +77,8 @@
 
   verify :method => :put, :only => :update
   def update
-    if @category.update_attributes(params[:issue_category])
+    @category.safe_attributes = params[:issue_category]
+    if @category.save
       respond_to do |format|
         format.html {
           flash[:notice] = l(:notice_successful_update)
--- a/app/controllers/members_controller.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/controllers/members_controller.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -28,10 +28,10 @@
       attrs = params[:member].dup
       if (user_ids = attrs.delete(:user_ids))
         user_ids.each do |user_id|
-          members << Member.new(attrs.merge(:user_id => user_id))
+          members << Member.new(:role_ids => params[:member][:role_ids], :user_id => user_id)
         end
       else
-        members << Member.new(attrs)
+        members << Member.new(:role_ids => params[:member][:role_ids], :user_id => params[:member][:user_id])
       end
       @project.members << members
     end
@@ -64,7 +64,10 @@
   end
 
   def edit
-    if request.post? and @member.update_attributes(params[:member])
+    if params[:member]
+      @member.role_ids = params[:member][:role_ids]
+    end
+    if request.post? and @member.save
   	 respond_to do |format|
         format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
         format.js {
@@ -93,7 +96,7 @@
   end
 
   def autocomplete_for_member
-    @principals = Principal.active.like(params[:q]).find(:all, :limit => 100) - @project.principals
+    @principals = Principal.active.not_member_of(@project).like(params[:q]).all(:limit => 100)
     render :layout => false
   end
 
--- a/app/controllers/messages_controller.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/controllers/messages_controller.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -53,13 +53,10 @@
 
   # Create a new topic
   def new
-    @message = Message.new(params[:message])
+    @message = Message.new
     @message.author = User.current
     @message.board = @board
-    if params[:message] && User.current.allowed_to?(:edit_messages, @project)
-      @message.locked = params[:message]['locked']
-      @message.sticky = params[:message]['sticky']
-    end
+    @message.safe_attributes = params[:message]
     if request.post? && @message.save
       call_hook(:controller_messages_new_after_save, { :params => params, :message => @message})
       attachments = Attachment.attach_files(@message, params[:attachments])
@@ -70,9 +67,10 @@
 
   # Reply to a topic
   def reply
-    @reply = Message.new(params[:reply])
+    @reply = Message.new
     @reply.author = User.current
     @reply.board = @board
+    @reply.safe_attributes = params[:reply]
     @topic.children << @reply
     if !@reply.new_record?
       call_hook(:controller_messages_reply_after_save, { :params => params, :message => @reply})
@@ -85,11 +83,8 @@
   # Edit a message
   def edit
     (render_403; return false) unless @message.editable_by?(User.current)
-    if params[:message]
-      @message.locked = params[:message]['locked']
-      @message.sticky = params[:message]['sticky']
-    end
-    if request.post? && @message.update_attributes(params[:message])
+    @message.safe_attributes = params[:message]
+    if request.post? && @message.save
       attachments = Attachment.attach_files(@message, params[:attachments])
       render_attachment_warning_if_needed(@message)
       flash[:notice] = l(:notice_successful_update)
--- a/app/controllers/news_controller.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/controllers/news_controller.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -67,8 +67,8 @@
 
   def create
     @news = News.new(:project => @project, :author => User.current)
+    @news.safe_attributes = params[:news]
     if request.post?
-      @news.attributes = params[:news]
       if @news.save
         flash[:notice] = l(:notice_successful_create)
         redirect_to :controller => 'news', :action => 'index', :project_id => @project
@@ -82,7 +82,8 @@
   end
 
   def update
-    if request.put? and @news.update_attributes(params[:news])
+    @news.safe_attributes = params[:news]
+    if request.put? and @news.save
       flash[:notice] = l(:notice_successful_update)
       redirect_to :action => 'show', :id => @news
     else
--- a/app/controllers/projects_controller.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/controllers/projects_controller.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -66,7 +66,8 @@
   def new
     @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
     @trackers = Tracker.all
-    @project = Project.new(params[:project])
+    @project = Project.new
+    @project.safe_attributes = params[:project]
   end
 
   verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
--- a/app/controllers/timelog_controller.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/controllers/timelog_controller.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -105,7 +105,7 @@
 
   def new
     @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
-    @time_entry.attributes = params[:time_entry]
+    @time_entry.safe_attributes = params[:time_entry]
 
     call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
     render :action => 'edit'
@@ -114,7 +114,7 @@
   verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
   def create
     @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
-    @time_entry.attributes = params[:time_entry]
+    @time_entry.safe_attributes = params[:time_entry]
 
     call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
 
@@ -135,14 +135,14 @@
   end
 
   def edit
-    @time_entry.attributes = params[:time_entry]
+    @time_entry.safe_attributes = params[:time_entry]
 
     call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
   end
 
   verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
   def update
-    @time_entry.attributes = params[:time_entry]
+    @time_entry.safe_attributes = params[:time_entry]
 
     call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
 
@@ -173,7 +173,7 @@
     unsaved_time_entry_ids = []
     @time_entries.each do |time_entry|
       time_entry.reload
-      time_entry.attributes = attributes
+      time_entry.safe_attributes = attributes
       call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
       unless time_entry.save
         # Keep unsaved time_entry ids to display them in flash error
--- a/app/controllers/versions_controller.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/controllers/versions_controller.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -23,7 +23,7 @@
   before_filter :find_project, :only => [:index, :new, :create, :close_completed]
   before_filter :authorize
 
-  accept_api_auth :index, :create, :update, :destroy
+  accept_api_auth :index, :show, :create, :update, :destroy
 
   helper :custom_fields
   helper :projects
@@ -75,7 +75,7 @@
     if params[:version]
       attributes = params[:version].dup
       attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
-      @version.attributes = attributes
+      @version.safe_attributes = attributes
     end
   end
 
@@ -85,7 +85,7 @@
     if params[:version]
       attributes = params[:version].dup
       attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
-      @version.attributes = attributes
+      @version.safe_attributes = attributes
     end
 
     if request.post?
@@ -124,7 +124,8 @@
     if request.put? && params[:version]
       attributes = params[:version].dup
       attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing'])
-      if @version.update_attributes(attributes)
+      @version.safe_attributes = attributes
+      if @version.save
         respond_to do |format|
           format.html {
             flash[:notice] = l(:notice_successful_update)
--- a/app/controllers/wikis_controller.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/controllers/wikis_controller.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -22,7 +22,7 @@
   # Create or update a project's wiki
   def edit
     @wiki = @project.wiki || Wiki.new(:project => @project)
-    @wiki.attributes = params[:wiki]
+    @wiki.safe_attributes = params[:wiki]
     @wiki.save if request.post?
     render(:update) {|page| page.replace_html "tab-content-wiki", :partial => 'projects/settings/wiki'}
   end
--- a/app/helpers/application_helper.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/helpers/application_helper.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -490,12 +490,16 @@
     text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
 
     @parsed_headings = []
+    @heading_anchors = {}
     @current_section = 0 if options[:edit_section_links]
+
+    parse_sections(text, project, obj, attr, only_path, options)
     text = parse_non_pre_blocks(text) do |text|
-      [:parse_sections, :parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros, :parse_headings].each do |method_name|
+      [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros].each do |method_name|
         send method_name, text, project, obj, attr, only_path, options
       end
     end
+    parse_headings(text, project, obj, attr, only_path, options)
 
     if @parsed_headings.any?
       replace_toc(text, @parsed_headings)
@@ -778,6 +782,11 @@
       anchor = sanitize_anchor_name(item)
       # used for single-file wiki export
       anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
+      @heading_anchors[anchor] ||= 0
+      idx = (@heading_anchors[anchor] += 1)
+      if idx > 1
+        anchor = "#{anchor}-#{idx}"
+      end
       @parsed_headings << [level, anchor, item]
       "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
     end
--- a/app/models/board.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/models/board.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -16,6 +16,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 class Board < ActiveRecord::Base
+  include Redmine::SafeAttributes
   belongs_to :project
   has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC"
   has_many :messages, :dependent => :destroy, :order => "#{Message.table_name}.created_on DESC"
@@ -30,6 +31,8 @@
   named_scope :visible, lambda {|*args| { :include => :project,
                                           :conditions => Project.allowed_to_condition(args.shift || User.current, :view_messages, *args) } }
 
+  safe_attributes 'name', 'description', 'move_to'
+
   def visible?(user=User.current)
     !user.nil? && user.allowed_to?(:view_messages, project)
   end
--- a/app/models/changeset.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/models/changeset.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -151,12 +151,16 @@
     @long_comments || split_comments.last
   end
 
-  def text_tag
-    if scmid?
+  def text_tag(ref_project=nil)
+    tag = if scmid?
       "commit:#{scmid}"
     else
       "r#{revision}"
     end
+    if ref_project && project && ref_project != project
+      tag = "#{project.identifier}:#{tag}" 
+    end
+    tag
   end
 
   # Returns the previous changeset
@@ -213,7 +217,7 @@
     # don't change the status is the issue is closed
     return if issue.status && issue.status.is_closed?
 
-    journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag))
+    journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag(issue.project)))
     issue.status = status
     unless Setting.commit_fix_done_ratio.blank?
       issue.done_ratio = Setting.commit_fix_done_ratio.to_i
@@ -232,7 +236,7 @@
       :hours => hours,
       :issue => issue,
       :spent_on => commit_date,
-      :comments => l(:text_time_logged_by_changeset, :value => text_tag,
+      :comments => l(:text_time_logged_by_changeset, :value => text_tag(issue.project),
                      :locale => Setting.default_language)
       )
     time_entry.activity = log_time_activity unless log_time_activity.nil?
--- a/app/models/comment.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/models/comment.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -16,8 +16,11 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 class Comment < ActiveRecord::Base
+  include Redmine::SafeAttributes
   belongs_to :commented, :polymorphic => true, :counter_cache => true
   belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
 
   validates_presence_of :commented, :author, :comments
+
+  safe_attributes 'comments'
 end
--- a/app/models/document.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/models/document.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -16,6 +16,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 class Document < ActiveRecord::Base
+  include Redmine::SafeAttributes
   belongs_to :project
   belongs_to :category, :class_name => "DocumentCategory", :foreign_key => "category_id"
   acts_as_attachable :delete_permission => :manage_documents
@@ -32,6 +33,8 @@
   named_scope :visible, lambda {|*args| { :include => :project,
                                           :conditions => Project.allowed_to_condition(args.shift || User.current, :view_documents, *args) } }
 
+  safe_attributes 'category_id', 'title', 'description'
+
   def visible?(user=User.current)
     !user.nil? && user.allowed_to?(:view_documents, project)
   end
--- a/app/models/issue_category.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/models/issue_category.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -16,6 +16,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 class IssueCategory < ActiveRecord::Base
+  include Redmine::SafeAttributes
   belongs_to :project
   belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id'
   has_many :issues, :foreign_key => 'category_id', :dependent => :nullify
@@ -24,7 +25,7 @@
   validates_uniqueness_of :name, :scope => [:project_id]
   validates_length_of :name, :maximum => 30
   
-  attr_protected :project_id
+  safe_attributes 'name', 'assigned_to_id'
 
   named_scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
 
--- a/app/models/mailer.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/models/mailer.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -354,7 +354,7 @@
                                           :conditions => s.conditions
                                     ).group_by(&:assigned_to)
     issues_by_assignee.each do |assignee, issues|
-      deliver_reminder(assignee, issues, days) if assignee && assignee.active?
+      deliver_reminder(assignee, issues, days) if assignee.is_a?(User) && assignee.active?
     end
   end
 
--- a/app/models/member.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/models/member.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -50,7 +50,17 @@
 
   def <=>(member)
     a, b = roles.sort.first, member.roles.sort.first
-    a == b ? (principal <=> member.principal) : (a <=> b)
+    if a == b
+      if principal
+        principal <=> member.principal
+      else
+        1
+      end
+    elsif a
+      a <=> b
+    else
+      1
+    end
   end
 
   def deletable?
--- a/app/models/message.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/models/message.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -16,6 +16,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 class Message < ActiveRecord::Base
+  include Redmine::SafeAttributes
   belongs_to :board
   belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
   acts_as_tree :counter_cache => :replies_count, :order => "#{Message.table_name}.created_on ASC"
@@ -36,7 +37,6 @@
                             :author_key => :author_id
   acts_as_watchable
 
-  attr_protected :locked, :sticky
   validates_presence_of :board, :subject, :content
   validates_length_of :subject, :maximum => 255
   validate :cannot_reply_to_locked_topic, :on => :create
@@ -48,6 +48,12 @@
   named_scope :visible, lambda {|*args| { :include => {:board => :project},
                                           :conditions => Project.allowed_to_condition(args.shift || User.current, :view_messages, *args) } }
 
+  safe_attributes 'subject', 'content'
+  safe_attributes 'locked', 'sticky', 'board_id',
+    :if => lambda {|message, user|
+      user.allowed_to?(:edit_messages, message.project)
+    }
+
   def visible?(user=User.current)
     !user.nil? && user.allowed_to?(:view_messages, project)
   end
--- a/app/models/news.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/models/news.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -16,6 +16,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 class News < ActiveRecord::Base
+  include Redmine::SafeAttributes
   belongs_to :project
   belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
   has_many :comments, :as => :commented, :dependent => :delete_all, :order => "created_on"
@@ -37,6 +38,8 @@
     :conditions => Project.allowed_to_condition(args.shift || User.current, :view_news, *args)
   }}
 
+  safe_attributes 'title', 'summary', 'description'
+
   def visible?(user=User.current)
     !user.nil? && user.allowed_to?(:view_news, project)
   end
--- a/app/models/principal.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/models/principal.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -32,6 +32,16 @@
      :order => 'type, login, lastname, firstname, mail'
     }
   }
+  # Principals that are not members of projects
+  named_scope :not_member_of, lambda {|projects|
+    projects = [projects] unless projects.is_a?(Array)
+    if projects.empty?
+      {:conditions => "1=0"}
+    else
+      ids = projects.map(&:id)
+      {:conditions => ["#{Principal.table_name}.id NOT IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids]}
+    end
+  }
 
   before_create :set_default_empty_values
 
@@ -40,7 +50,9 @@
   end
 
   def <=>(principal)
-    if self.class.name == principal.class.name
+    if principal.nil?
+      -1
+    elsif self.class.name == principal.class.name
       self.to_s.downcase <=> principal.to_s.downcase
     else
       # groups after users
--- a/app/models/project.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/models/project.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -253,7 +253,7 @@
 
   def to_param
     # id is used for projects with a numeric identifier (compatibility)
-    @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
+    @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id.to_s : identifier)
   end
 
   def active?
@@ -390,16 +390,21 @@
 
   # Returns a scope of the Versions used by the project
   def shared_versions
-    @shared_versions ||= begin
-      r = root? ? self : root
+    if new_record?
       Version.scoped(:include => :project,
-                     :conditions => "#{Project.table_name}.id = #{id}" +
-                                    " OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" +
+                     :conditions => "#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND #{Version.table_name}.sharing = 'system'")
+    else
+      @shared_versions ||= begin
+        r = root? ? self : root
+        Version.scoped(:include => :project,
+                       :conditions => "#{Project.table_name}.id = #{id}" +
+                                      " OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" +
                                           " #{Version.table_name}.sharing = 'system'" +
                                           " OR (#{Project.table_name}.lft >= #{r.lft} AND #{Project.table_name}.rgt <= #{r.rgt} AND #{Version.table_name}.sharing = 'tree')" +
                                           " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
                                           " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
                                           "))")
+      end
     end
   end
 
--- a/app/models/time_entry.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/models/time_entry.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -16,6 +16,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 class TimeEntry < ActiveRecord::Base
+  include Redmine::SafeAttributes
   # could have used polymorphic association
   # project association here allows easy loading of time entries at project level with one database trip
   belongs_to :project
@@ -46,6 +47,8 @@
     :conditions => Project.allowed_to_condition(args.shift || User.current, :view_time_entries, *args)
   }}
 
+  safe_attributes 'hours', 'comments', 'issue_id', 'activity_id', 'spent_on', 'custom_field_values'
+
   def after_initialize
     if new_record? && self.activity.nil?
       if default_activity = TimeEntryActivity.default
--- a/app/models/user.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/models/user.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -343,6 +343,11 @@
     find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
   end
 
+  # Returns true if the default admin account can no longer be used
+  def self.default_admin_account_changed?
+    !User.active.find_by_login("admin").try(:check_password?, "admin")
+  end
+
   def to_s
     name
   end
--- a/app/models/user_preference.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/models/user_preference.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -19,7 +19,7 @@
   belongs_to :user
   serialize :others
 
-  attr_protected :others
+  attr_protected :others, :user_id
 
   def initialize(attributes = nil)
     super
--- a/app/models/version.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/models/version.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -16,6 +16,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 class Version < ActiveRecord::Base
+  include Redmine::SafeAttributes
   after_update :update_issues_from_sharing_change
   belongs_to :project
   has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify
@@ -38,6 +39,15 @@
   named_scope :visible, lambda {|*args| { :include => :project,
                                           :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
 
+  safe_attributes 'name', 
+    'description',
+    'effective_date',
+    'due_date',
+    'wiki_page_title',
+    'status',
+    'sharing',
+    'custom_field_values'
+
   # Returns true if +user+ or current user is allowed to view the version
   def visible?(user=User.current)
     user.allowed_to?(:view_issues, self.project)
--- a/app/models/wiki.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/models/wiki.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -16,6 +16,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 class Wiki < ActiveRecord::Base
+  include Redmine::SafeAttributes
   belongs_to :project
   has_many :pages, :class_name => 'WikiPage', :dependent => :destroy, :order => 'title'
   has_many :redirects, :class_name => 'WikiRedirect', :dependent => :delete_all
@@ -25,6 +26,8 @@
   validates_presence_of :start_page
   validates_format_of :start_page, :with => /^[^,\.\/\?\;\|\:]*$/
 
+  safe_attributes 'start_page'
+
   def visible?(user=User.current)
     !user.nil? && user.allowed_to?(:view_wiki_pages, project)
   end
--- a/app/views/issues/_relations.html.erb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/views/issues/_relations.html.erb	Wed Jun 27 14:54:18 2012 +0100
@@ -20,7 +20,8 @@
 <td class="start_date"><%= format_date(relation.other_issue(@issue).start_date) %></td>
 <td class="due_date"><%= format_date(relation.other_issue(@issue).due_date) %></td>
 <td class="buttons"><%= link_to_remote(image_tag('link_break.png'), { :url => {:controller => 'issue_relations', :action => 'destroy', :id => relation},
-                                                  :method => :delete
+                                                  :method => :delete,
+                                                  :confirm => l(:text_are_you_sure)
                                                 }, :title => l(:label_relation_delete)) if authorize_for('issue_relations', 'destroy') %></td>
 </tr>
 <% end %>
--- a/app/views/projects/settings/_members.html.erb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/views/projects/settings/_members.html.erb	Wed Jun 27 14:54:18 2012 +0100
@@ -50,7 +50,7 @@
 <% end %>
 </div>
 
-<% principals = Principal.active.find(:all, :limit => 100, :order => 'type, login, lastname ASC') - @project.principals %>
+<% principals = Principal.active.not_member_of(@project).all(:limit => 100, :order => 'type, login, lastname ASC') %>
 
 <div class="splitcontentright">
 <% if roles.any? && principals.any? %>
--- a/app/views/projects/settings/_repository.html.erb	Fri Feb 24 19:09:32 2012 +0000
+++ b/app/views/projects/settings/_repository.html.erb	Wed Jun 27 14:54:18 2012 +0100
@@ -10,7 +10,7 @@
 <%= label_tag('repository_scm', l(:label_scm)) %><%= scm_select_tag(@repository) %>
 <% if @repository && ! @repository.class.scm_available %>
   <br />
-  <em><%= content_tag 'span', l(:text_scm_command_not_available), :class => 'error' %></em>
+  <em class="info error"><%= content_tag 'span', l(:text_scm_command_not_available) %></em>
 <% end %>
 </p>
 <% button_disabled = true %>
--- a/config/locales/it.yml	Fri Feb 24 19:09:32 2012 +0000
+++ b/config/locales/it.yml	Wed Jun 27 14:54:18 2012 +0100
@@ -979,7 +979,7 @@
   label_between: tra
   setting_issue_group_assignment: Permetti di assegnare una segnalazione a gruppi
   label_diff: diff
-  text_git_repository_note: Il repository è bare e locale (e.g. /gitrepo, c:\gitrepo)
+  text_git_repository_note: Il repository è spoglio e locale (e.g. /gitrepo, c:\gitrepo)
   description_query_sort_criteria_direction: Ordinamento
   description_project_scope: Search scope
   description_filter: Filtro
--- a/config/locales/nl.yml	Fri Feb 24 19:09:32 2012 +0000
+++ b/config/locales/nl.yml	Wed Jun 27 14:54:18 2012 +0100
@@ -399,7 +399,7 @@
   label_f_hour_plural: "%{value} uren"
   label_feed_plural: Feeds
   label_feeds_access_key_created_on: "RSS toegangssleutel %{value} geleden gemaakt."
-  label_file_added: Bericht toegevoegd
+  label_file_added: Bestand toegevoegd
   label_file_plural: Bestanden
   label_filter_add: Voeg filter toe
   label_filter_plural: Filters
--- a/config/locales/zh.yml	Fri Feb 24 19:09:32 2012 +0000
+++ b/config/locales/zh.yml	Wed Jun 27 14:54:18 2012 +0100
@@ -1002,9 +1002,9 @@
   label_child_revision: 子修订
   error_scm_annotate_big_text_file: 输入文本内容超长,无法输入。
   setting_default_issue_start_date_to_creation_date: 使用当前日期作为新问题的开始日期
-  button_edit_section: Edit this section
-  setting_repositories_encodings: Attachments and repositories encodings
-  description_all_columns: All Columns
-  button_export: Export
-  label_export_options: "%{export_format} export options"
-  error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size})
+  button_edit_section: 编辑此区域
+  setting_repositories_encodings: 附件和版本库编码
+  description_all_columns: 所有列
+  button_export: 导出
+  label_export_options: "%{export_format} 导出选项"
+  error_attachment_too_big: 该文件无法上传。超过文件大小限制 (%{max_size})
--- a/doc/CHANGELOG	Fri Feb 24 19:09:32 2012 +0000
+++ b/doc/CHANGELOG	Wed Jun 27 14:54:18 2012 +0100
@@ -4,6 +4,36 @@
 Copyright (C) 2006-2012  Jean-Philippe Lang
 http://www.redmine.org/
 
+== 2012-04-14 v1.3.3
+
+* Defect #10505: Error when exporting to PDF with NoMethodError (undefined method `downcase' for nil:NilClass)
+* Defect #10554: Defect symbols when exporting tasks in pdf
+* Defect #10564: Unable to change locked, sticky flags and board when editing a message
+* Defect #10591: Dutch "label_file_added" translation is wrong
+* Defect #10622: "Default administrator account changed" is always true
+* Patch #10555: rake redmine:send_reminders aborted if issue assigned to group
+* Patch #10611: Simplified Chinese translations for 1.3-stable
+
+== 2012-03-11 v1.3.2
+
+* Defect #8194: {{toc}} uses identical anchors for subsections with the same name
+* Defect #9143: Partial diff comparison should be done on actual code, not on html
+* Defect #9523: {{toc}} does not display headers with @ code markup
+* Defect #9815: Release 1.3.0 does not detect rubytree with rubgems 1.8
+* Defect #10053: undefined method `<=>' for nil:NilClass when accessing the settings of a project
+* Defect #10135: ActionView::TemplateError (can't convert Fixnum into String)
+* Defect #10193: Unappropriate icons in highlighted code block
+* Defect #10199: No wiki section edit when title contains code
+* Defect #10218: Error when creating a project with a version custom field
+* Defect #10241: "get version by ID" fails with "401 not authorized" error when using API access key
+* Defect #10284: Note added by commit from a subproject does not contain project identifier
+* Defect #10374: User list is empty when adding users to project / group if remaining users are added late
+* Defect #10390: Mass assignment security vulnerability
+* Patch #8413: Confirmation message before deleting a relationship
+* Patch #10160: Bulgarian translation (r8777)
+* Patch #10242: Migrate Redmine.pm from Digest::Sha1 to Digest::Sha
+* Patch #10258: Italian translation for 1.3-stable
+
 == 2012-02-06 v1.3.1
 
 * Defect #9775: app/views/repository/_revision_graph.html.erb sets window.onload directly..
--- a/extra/svn/Redmine.pm	Fri Feb 24 19:09:32 2012 +0000
+++ b/extra/svn/Redmine.pm	Wed Jun 27 14:54:18 2012 +0100
@@ -99,7 +99,7 @@
 use warnings FATAL => 'all', NONFATAL => 'redefine';
 
 use DBI;
-use Digest::SHA1;
+use Digest::SHA;
 # optional module for LDAP authentication
 my $CanUseLDAPAuth = eval("use Authen::Simple::LDAP; 1");
 
@@ -327,7 +327,7 @@
   my $dbh         = connect_database($r);
   my $project_id  = get_project_identifier($r);
 
-  my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass);
+  my $pass_digest = Digest::SHA::sha1_hex($redmine_pass);
 
   my $access_mode = defined $read_only_methods{$r->method} ? "R" : "W";
 
@@ -346,7 +346,7 @@
 
       unless ($auth_source_id) {
 	  			my $method = $r->method;
-          my $salted_password = Digest::SHA1::sha1_hex($salt.$pass_digest);
+          my $salted_password = Digest::SHA::sha1_hex($salt.$pass_digest);
 					if ($hashed_password eq $salted_password && (($access_mode eq "R" && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/) ) {
               $ret = 1;
               last;
--- a/lib/redmine/unified_diff.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/lib/redmine/unified_diff.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -112,11 +112,6 @@
 
     private
 
-    # Escape the HTML for the diff
-    def escapeHTML(line)
-        CGI.escapeHTML(line)
-    end
-
     def diff_for_added_line
       if @type == 'sbs' && @removed > 0 && @added < @removed
         self[-(@removed - @added)]
@@ -130,7 +125,7 @@
     def parse_line(line, type="inline")
       if line[0, 1] == "+"
         diff = diff_for_added_line
-        diff.line_right = escapeHTML line[1..-1]
+        diff.line_right = line[1..-1]
         diff.nb_line_right = @line_num_r
         diff.type_diff_right = 'diff_in'
         @line_num_r += 1
@@ -138,7 +133,7 @@
         true
       elsif line[0, 1] == "-"
         diff = Diff.new
-        diff.line_left = escapeHTML line[1..-1]
+        diff.line_left = line[1..-1]
         diff.nb_line_left = @line_num_l
         diff.type_diff_left = 'diff_out'
         self << diff
@@ -149,9 +144,9 @@
         write_offsets
         if line[0, 1] =~ /\s/
           diff = Diff.new
-          diff.line_right = escapeHTML line[1..-1]
+          diff.line_right = line[1..-1]
           diff.nb_line_right = @line_num_r
-          diff.line_left = escapeHTML line[1..-1]
+          diff.line_left = line[1..-1]
           diff.nb_line_left = @line_num_l
           self << diff
           @line_num_l += 1
@@ -224,27 +219,15 @@
     end
 
     def html_line_left
-      if offsets
-        line_left.dup.insert(offsets.first, '<span>').insert(offsets.last, '</span>')
-      else
-        line_left
-      end
+      line_to_html(line_left, offsets)
     end
 
     def html_line_right
-      if offsets
-        line_right.dup.insert(offsets.first, '<span>').insert(offsets.last, '</span>')
-      else
-        line_right
-      end
+      line_to_html(line_right, offsets)
     end
 
     def html_line
-      if offsets
-        line.dup.insert(offsets.first, '<span>').insert(offsets.last, '</span>')
-      else
-        line
-      end
+      line_to_html(line, offsets)
     end
 
     def inspect
@@ -254,5 +237,23 @@
       puts self.nb_line_right
       puts self.line_right
     end
+
+    private
+
+    def line_to_html(line, offsets)
+      if offsets
+        s = ''
+        unless offsets.first == 0
+          s << CGI.escapeHTML(line[0..offsets.first-1])
+        end
+        s << '<span>' + CGI.escapeHTML(line[offsets.first..offsets.last]) + '</span>'
+        unless offsets.last == -1
+          s << CGI.escapeHTML(line[offsets.last+1..-1])
+        end
+        s
+      else
+        CGI.escapeHTML(line)
+      end
+    end
   end
 end
--- a/lib/redmine/version.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/lib/redmine/version.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -4,7 +4,7 @@
   module VERSION #:nodoc:
     MAJOR = 1
     MINOR = 3
-    TINY  = 1
+    TINY  = 3
 
     # Branch values:
     # * official release: nil
--- a/public/stylesheets/application.css	Fri Feb 24 19:09:32 2012 +0000
+++ b/public/stylesheets/application.css	Wed Jun 27 14:54:18 2012 +0100
@@ -481,6 +481,9 @@
 
 a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
 
+em.info {font-style:normal;font-size:90%;color:#888;display:block;}
+em.info.error {padding-left:20px; background:url(../images/exclamation.png) no-repeat 0 50%;}
+
 /* Project members tab */
 div#tab-content-members .splitcontentleft, div#tab-content-memberships .splitcontentleft, div#tab-content-users .splitcontentleft { width: 64% }
 div#tab-content-members .splitcontentright, div#tab-content-memberships .splitcontentright, div#tab-content-users .splitcontentright { width: 34% }
@@ -534,8 +537,6 @@
     color: #A6750C;
 }
 
-span.error {padding-left:20px; background:url(../images/exclamation.png) no-repeat 0 50%;}
-
 #errorExplanation ul { font-size: 0.9em;}
 #errorExplanation h2, #errorExplanation p { display: none; }
 
--- a/test/functional/boards_controller_test.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/test/functional/boards_controller_test.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -88,6 +88,14 @@
     assert_equal 'Testing', Board.find(2).name
   end
 
+  def test_update_position
+    @request.session[:user_id] = 2
+    post :edit, :project_id => 1, :id => 2, :board => { :move_to => 'highest'}
+    assert_redirected_to '/projects/ecookbook/settings/boards'
+    board = Board.find(2)
+    assert_equal 1, board.position
+  end
+
   def test_post_destroy
     @request.session[:user_id] = 2
     assert_difference 'Board.count', -1 do
--- a/test/functional/issues_controller_test.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/test/functional/issues_controller_test.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -147,7 +147,6 @@
   end
 
   def test_index_with_short_filters
-
     to_test = {
       'status_id' => {
         'o' => { :op => 'o', :values => [''] },
@@ -181,9 +180,9 @@
         't-2' => { :op => 't-', :values => ['2'] }},
       'created_on' => {
         '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
-        '<t+2' => { :op => '=', :values => ['<t+2'] },
-        '>t+2' => { :op => '=', :values => ['>t+2'] },
-        't+2' => { :op => 't', :values => ['+2'] }},
+        '<t-2' => { :op => '<t-', :values => ['2'] },
+        '>t-2' => { :op => '>t-', :values => ['2'] },
+        't-2' => { :op => 't-', :values => ['2'] }},
       'cf_1' => {
         'c' => { :op => '=', :values => ['c'] },
         '!c' => { :op => '!', :values => ['c'] },
@@ -215,7 +214,6 @@
         assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
       end
     end
-
   end
 
   def test_index_with_project_and_empty_filters
@@ -933,7 +931,7 @@
   def test_post_new_with_group_assignment
     group = Group.find(11)
     project = Project.find(1)
-    project.members << Member.new(:principal => group, :roles => [Role.first])
+    project.members << Member.new(:principal => group, :roles => [Role.givable.first])
 
     with_settings :issue_group_assignment => '1' do
       @request.session[:user_id] = 2
@@ -1803,7 +1801,7 @@
   def test_bulk_update_with_group_assignee
     group = Group.find(11)
     project = Project.find(1)
-    project.members << Member.new(:principal => group, :roles => [Role.first])
+    project.members << Member.new(:principal => group, :roles => [Role.givable.first])
 
     @request.session[:user_id] = 2
     # update issues assignee
--- a/test/functional/messages_controller_test.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/test/functional/messages_controller_test.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -131,6 +131,30 @@
     assert_equal 'New body', message.content
   end
 
+  def test_post_edit_sticky_and_locked
+    @request.session[:user_id] = 2
+    post :edit, :board_id => 1, :id => 1,
+                :message => { :subject => 'New subject',
+                              :content => 'New body',
+                              :locked => '1',
+                              :sticky => '1'}
+    assert_redirected_to '/boards/1/topics/1'
+    message = Message.find(1)
+    assert_equal true, message.sticky?
+    assert_equal true, message.locked?
+  end
+
+  def test_post_edit_should_allow_to_change_board
+    @request.session[:user_id] = 2
+    post :edit, :board_id => 1, :id => 1,
+                :message => { :subject => 'New subject',
+                              :content => 'New body',
+                              :board_id => 2}
+    assert_redirected_to '/boards/2/topics/1'
+    message = Message.find(1)
+    assert_equal Board.find(2), message.board
+  end
+
   def test_reply
     @request.session[:user_id] = 2
     post :reply, :board_id => 1, :id => 1, :reply => { :content => 'This is a test reply', :subject => 'Test reply' }
--- a/test/functional/project_enumerations_controller_test.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/test/functional/project_enumerations_controller_test.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -13,6 +13,8 @@
            :custom_fields_trackers, :custom_values,
            :time_entries
 
+  self.use_transactional_fixtures = false
+
   def setup
     @request.session[:user_id] = nil
     Setting.default_language = 'en'
--- a/test/functional/trackers_controller_test.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/test/functional/trackers_controller_test.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -64,7 +64,7 @@
     tracker = Tracker.first(:order => 'id DESC')
     assert_equal 'New tracker', tracker.name
     assert_equal [1], tracker.project_ids.sort
-    assert_equal [1, 6], tracker.custom_field_ids
+    assert_equal [1, 6], tracker.custom_field_ids.sort
     assert_equal 0, tracker.workflows.count
   end
 
--- a/test/unit/changeset_test.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/test/unit/changeset_test.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -178,6 +178,24 @@
     assert c.issues.first.project != c.project
   end
 
+  def test_commit_closing_a_subproject_issue
+    with_settings :commit_fix_status_id => 5, :commit_fix_keywords => 'closes' do
+      issue = Issue.find(5)
+      assert !issue.closed?
+      assert_difference 'Journal.count' do
+        c = Changeset.new(:repository   => Project.find(1).repository,
+                          :committed_on => Time.now,
+                          :comments     => 'closes #5, a subproject issue',
+                          :revision     => '12345')
+        assert c.save
+      end
+      assert issue.reload.closed?
+      journal = Journal.first(:order => 'id DESC')
+      assert_equal issue, journal.issue
+      assert_include "Applied in changeset ecookbook:r12345.", journal.notes
+    end
+  end
+
   def test_commit_referencing_a_parent_project_issue
     # repository of child project
     r = Repository::Subversion.create!(
@@ -197,6 +215,16 @@
     assert_equal 'r520', c.text_tag
   end
 
+  def test_text_tag_revision_with_same_project
+    c = Changeset.new(:revision => '520', :repository => Project.find(1).repository)
+    assert_equal 'r520', c.text_tag(Project.find(1))
+  end
+
+  def test_text_tag_revision_with_different_project
+    c = Changeset.new(:revision => '520', :repository => Project.find(1).repository)
+    assert_equal 'ecookbook:r520', c.text_tag(Project.find(2))
+  end
+
   def test_text_tag_hash
     c = Changeset.new(
           :scmid    => '7234cb2750b63f47bff735edc50a1c0a433c2518',
@@ -204,6 +232,16 @@
     assert_equal 'commit:7234cb2750b63f47bff735edc50a1c0a433c2518', c.text_tag
   end
 
+  def test_text_tag_hash_with_same_project
+    c = Changeset.new(:revision => '7234cb27', :scmid => '7234cb27', :repository => Project.find(1).repository)
+    assert_equal 'commit:7234cb27', c.text_tag(Project.find(1))
+  end
+
+  def test_text_tag_hash_with_different_project
+    c = Changeset.new(:revision => '7234cb27', :scmid => '7234cb27', :repository => Project.find(1).repository)
+    assert_equal 'ecookbook:commit:7234cb27', c.text_tag(Project.find(2))
+  end
+
   def test_text_tag_hash_all_number
     c = Changeset.new(:scmid => '0123456789', :revision => '0123456789')
     assert_equal 'commit:0123456789', c.text_tag
--- a/test/unit/helpers/application_helper_test.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/test/unit/helpers/application_helper_test.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -732,6 +732,8 @@
 
 h3. Subtitle with *some* _modifiers_
 
+h3. Subtitle with @inline code@
+
 h1. Another title
 
 h3. An "Internet link":http://www.redmine.org/ inside subtitle
@@ -748,6 +750,7 @@
                       '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
                         '<ul>' +
                           '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
+                          '<li><a href="#Subtitle-with-inline-code">Subtitle with inline code</a></li>' +
                         '</ul>' +
                       '</li>' +
                     '</ul>' +
@@ -768,6 +771,33 @@
     assert textilizable(raw).gsub("\n", "").include?(expected)
   end
 
+  def test_table_of_content_should_generate_unique_anchors
+    raw = <<-RAW
+{{toc}}
+
+h1. Title
+
+h2. Subtitle
+
+h2. Subtitle
+RAW
+
+    expected =  '<ul class="toc">' +
+                  '<li><a href="#Title">Title</a>' +
+                    '<ul>' +
+                      '<li><a href="#Subtitle">Subtitle</a></li>' +
+                      '<li><a href="#Subtitle-2">Subtitle</a></li>'
+                    '</ul>'
+                  '</li>' +
+               '</ul>'
+
+    @project = Project.find(1)
+    result = textilizable(raw).gsub("\n", "")
+    assert_include expected, result
+    assert_include '<a name="Subtitle">', result
+    assert_include '<a name="Subtitle-2">', result
+  end
+
   def test_table_of_content_should_contain_included_page_headings
     raw = <<-RAW
 {{toc}}
@@ -786,6 +816,48 @@
     assert textilizable(raw).gsub("\n", "").include?(expected)
   end
 
+  def test_section_edit_links
+    raw = <<-RAW
+h1. Title
+
+Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
+
+h2. Subtitle with a [[Wiki]] link
+
+h2. Subtitle with *some* _modifiers_
+
+h2. Subtitle with @inline code@
+
+<pre>
+some code
+
+h2. heading inside pre
+
+<h2>html heading inside pre</h2>
+</pre>
+
+h2. Subtitle after pre tag
+RAW
+
+    @project = Project.find(1)
+    set_language_if_valid 'en'
+    result = textilizable(raw, :edit_section_links => {:controller => 'wiki', :action => 'edit', :project_id => '1', :id => 'Test'}).gsub("\n", "")
+
+    # heading that contains inline code
+    assert_match Regexp.new('<div class="contextual" title="Edit this section">' +
+      '<a href="/projects/1/wiki/Test/edit\?section=4"><img alt="Edit" src="/images/edit.png(\?\d+)?" /></a></div>' +
+      '<a name="Subtitle-with-inline-code"></a>' +
+      '<h2 >Subtitle with <code>inline code</code><a href="#Subtitle-with-inline-code" class="wiki-anchor">&para;</a></h2>'),
+      result
+
+    # last heading
+    assert_match Regexp.new('<div class="contextual" title="Edit this section">' +
+      '<a href="/projects/1/wiki/Test/edit\?section=5"><img alt="Edit" src="/images/edit.png(\?\d+)?" /></a></div>' +
+      '<a name="Subtitle-after-pre-tag"></a>' +
+      '<h2 >Subtitle after pre tag<a href="#Subtitle-after-pre-tag" class="wiki-anchor">&para;</a></h2>'),
+      result
+  end
+
   def test_default_formatter
     Setting.text_formatting = 'unknown'
     text = 'a *link*: http://www.example.net/'
@@ -853,6 +925,14 @@
                  link_to_project(project, {:action => 'settings'}, :class => "project")
   end
 
+  def test_link_to_legacy_project_with_numerical_identifier_should_use_id
+    # numeric identifier are no longer allowed
+    Project.update_all "identifier=25", "id=1"
+
+    assert_equal '<a href="/projects/1">eCookbook</a>',
+                 link_to_project(Project.find(1))
+  end
+
   def test_principals_options_for_select_with_users
     users = [User.find(2), User.find(4)]
     assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
--- a/test/unit/lib/redmine/unified_diff_test.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/test/unit/lib/redmine/unified_diff_test.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -91,6 +91,29 @@
 
   end
 
+  def test_partials_with_html_entities
+    raw = <<-DIFF
+--- test.orig.txt Wed Feb 15 16:10:39 2012
++++ test.new.txt  Wed Feb 15 16:11:25 2012
+@@ -1,5 +1,5 @@
+ Semicolons were mysteriously appearing in code diffs in the repository
+ 
+-void DoSomething(std::auto_ptr<MyClass> myObj)
++void DoSomething(const MyClass& myObj)
+ 
+DIFF
+
+    diff = Redmine::UnifiedDiff.new(raw, :type => 'sbs')
+    assert_equal 1, diff.size
+    assert_equal 'void DoSomething(<span>std::auto_ptr&lt;MyClass&gt;</span> myObj)', diff.first[2].html_line_left
+    assert_equal 'void DoSomething(<span>const MyClass&amp;</span> myObj)', diff.first[2].html_line_right
+
+    diff = Redmine::UnifiedDiff.new(raw, :type => 'inline')
+    assert_equal 1, diff.size
+    assert_equal 'void DoSomething(<span>std::auto_ptr&lt;MyClass&gt;</span> myObj)', diff.first[2].html_line
+    assert_equal 'void DoSomething(<span>const MyClass&amp;</span> myObj)', diff.first[3].html_line
+  end
+
   def test_line_starting_with_dashes
     diff = Redmine::UnifiedDiff.new(<<-DIFF
 --- old.txt Wed Nov 11 14:24:58 2009
--- a/test/unit/mailer_test.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/test/unit/mailer_test.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -370,7 +370,7 @@
   end
 
   def test_wiki_content_added
-    content = WikiContent.find(:first)
+    content = WikiContent.find(1)
     valid_languages.each do |lang|
       Setting.default_language = lang.to_s
       assert_difference 'ActionMailer::Base.deliveries.size' do
@@ -380,7 +380,7 @@
   end
 
   def test_wiki_content_updated
-    content = WikiContent.find(:first)
+    content = WikiContent.find(1)
     valid_languages.each do |lang|
       Setting.default_language = lang.to_s
       assert_difference 'ActionMailer::Base.deliveries.size' do
--- a/test/unit/member_test.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/test/unit/member_test.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -82,6 +82,23 @@
     assert_raise(ActiveRecord::RecordNotFound) { Member.find(@jsmith.id) }
   end
 
+  def test_sort_without_roles
+    a = Member.new(:roles => [Role.first])
+    b = Member.new
+
+    assert_equal -1, a <=> b
+    assert_equal 1,  b <=> a
+  end
+
+  def test_sort_without_principal
+    role = Role.first
+    a = Member.new(:roles => [role], :principal => User.first)
+    b = Member.new(:roles => [role])
+
+    assert_equal -1, a <=> b
+    assert_equal 1,  b <=> a
+  end
+
   context "removing permissions" do
     setup do
       Watcher.delete_all("user_id = 9")
--- a/test/unit/news_test.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/test/unit/news_test.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -30,7 +30,7 @@
   def test_create_should_send_email_notification
     ActionMailer::Base.deliveries.clear
     Setting.notified_events << 'news_added'
-    news = Project.find(:first).news.new(valid_news)
+    news = Project.find(1).news.new(valid_news)
 
     assert news.save
     assert_equal 1, ActionMailer::Base.deliveries.size
--- a/test/unit/principal_test.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/test/unit/principal_test.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -18,6 +18,13 @@
 require File.expand_path('../../test_helper', __FILE__)
 
 class PrincipalTest < ActiveSupport::TestCase
+  fixtures :users, :projects, :members, :member_roles
+
+  def test_not_member_of_scope_should_return_users_that_have_no_memberships
+    projects = Project.find_all_by_id(1, 2)
+    expected = (Principal.all - projects.map(&:memberships).flatten.map(&:principal)).sort
+    assert_equal expected, Principal.not_member_of(projects).sort
+  end
 
   context "#like" do
     setup do
--- a/test/unit/project_test.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/test/unit/project_test.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -96,8 +96,8 @@
       assert_equal ['issue_tracking', 'repository'], Project.new.enabled_module_names
     end
 
-    assert_equal Tracker.all, Project.new.trackers
-    assert_equal Tracker.find(1, 3), Project.new(:tracker_ids => [1, 3]).trackers
+    assert_equal Tracker.all.sort, Project.new.trackers.sort
+    assert_equal Tracker.find(1, 3).sort, Project.new(:tracker_ids => [1, 3]).trackers.sort
   end
 
   def test_update
@@ -586,6 +586,13 @@
     assert !versions.collect(&:id).include?(6)
   end
 
+  def test_shared_versions_for_new_project_should_include_system_shared_versions
+    p = Project.find(5)
+    v = Version.create!(:name => 'system_sharing', :project => p, :sharing => 'system')
+
+    assert_include v, Project.new.shared_versions
+  end
+
   def test_next_identifier
     ProjectCustomField.delete_all
     Project.create!(:name => 'last', :identifier => 'p2008040')
--- a/test/unit/user_test.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/test/unit/user_test.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -574,6 +574,38 @@
     end
   end
 
+  def test_default_admin_account_changed_should_return_false_if_account_was_not_changed
+    user = User.find_by_login("admin")
+    user.password = "admin"
+    user.save!
+
+    assert_equal false, User.default_admin_account_changed?
+  end
+
+  def test_default_admin_account_changed_should_return_true_if_password_was_changed
+    user = User.find_by_login("admin")
+    user.password = "newpassword"
+    user.save!
+
+    assert_equal true, User.default_admin_account_changed?
+  end
+
+  def test_default_admin_account_changed_should_return_true_if_account_is_disabled
+    user = User.find_by_login("admin")
+    user.password = "admin"
+    user.status = User::STATUS_LOCKED
+    user.save!
+
+    assert_equal true, User.default_admin_account_changed?
+  end
+
+  def test_default_admin_account_changed_should_return_true_if_account_does_not_exist
+    user = User.find_by_login("admin")
+    user.destroy
+
+    assert_equal true, User.default_admin_account_changed?
+  end
+
   def test_roles_for_project
     # user with a role
     roles = @jsmith.roles_for_project(Project.find(1))
--- a/vendor/gems/rubytree-0.5.2/.specification	Fri Feb 24 19:09:32 2012 +0000
+++ b/vendor/gems/rubytree-0.5.2/.specification	Wed Jun 27 14:54:18 2012 +0100
@@ -11,17 +11,8 @@
 
 date: 2007-12-20 00:00:00 -08:00
 default_executable: 
-dependencies: 
-- !ruby/object:Gem::Dependency 
-  name: hoe
-  type: :runtime
-  version_requirement: 
-  version_requirements: !ruby/object:Gem::Requirement 
-    requirements: 
-    - - ">="
-      - !ruby/object:Gem::Version 
-        version: 1.3.0
-    version: 
+dependencies: []
+
 description: "Provides a generic tree data-structure with ability to store keyed node-elements in the tree. The implementation mixes in the Enumerable module.  Website:  http://rubytree.rubyforge.org/"
 email: anupamsg@gmail.com
 executables: []
--- a/vendor/plugins/rfpdf/lib/tcpdf.rb	Fri Feb 24 19:09:32 2012 +0000
+++ b/vendor/plugins/rfpdf/lib/tcpdf.rb	Wed Jun 27 14:54:18 2012 +0100
@@ -1803,7 +1803,7 @@
 			w = @w - @r_margin - @x;
 		end
 
-		wmax = (w - 2 * @c_margin);
+		wmax = (w - 3 * @c_margin);
 
 		s = txt.gsub("\r", ''); # remove carriage returns
 		nb = s.length;
@@ -1862,7 +1862,7 @@
 				ns += 1;
 			end
 
-			l = GetStringWidth(s[from_j, to_index - from_j + 1]);
+			l = GetStringWidth(s[from_j, to_index - from_j]);
 
 			if (l > wmax)
 				#Automatic line break
@@ -1945,7 +1945,7 @@
 
 		#Output text in flowing mode
 		w = @w - @r_margin - @x;
-		wmax = (w - 2 * @c_margin);
+		wmax = (w - 3 * @c_margin);
     
 		s = txt.gsub("\r", '');
 		nb = s.length;
@@ -1974,7 +1974,7 @@
 				if (nl == 1)
 					@x = @l_margin;
 					w = @w - @r_margin - @x;
-					wmax = (w - 2 * @c_margin);
+					wmax = (w - 3 * @c_margin);
 				end
 				nl += 1;
 				next
@@ -1982,7 +1982,7 @@
 			if (c == " "[0])
 				sep= i;
 			end
-			l = GetStringWidth(s[j, i - j + 1]);
+			l = GetStringWidth(s[j, i - j]);
 			if (l > wmax)
 				#Automatic line break (word wrapping)
 				if (sep == -1)
@@ -1991,7 +1991,7 @@
 						@x = @l_margin;
 						@y += h;
 						w=@w - @r_margin - @x;
-						wmax=(w - 2 * @c_margin);
+						wmax=(w - 3 * @c_margin);
 						i += 1
 						nl += 1
 						next
@@ -2010,7 +2010,7 @@
 				if (nl==1)
 					@x = @l_margin;
 					w = @w - @r_margin - @x;
-					wmax = (w - 2 * @c_margin);
+					wmax = (w - 3 * @c_margin);
 				end
 				nl += 1;
 			else
@@ -3474,7 +3474,7 @@
 					#Extract attributes
 					# get tag name
 					tag = element.scan(/([a-zA-Z0-9]*)/).flatten.delete_if {|x| x.length == 0}
-					tag = tag[0].downcase;
+					tag = tag[0].to_s.downcase;
 					
 					# get attributes
 					attr_array = element.scan(/([^=\s]*)=["\']?([^"\']*)["\']?/)
@@ -3497,7 +3497,7 @@
 					#Extract attributes
 					# get tag name
 					tag = element.scan(/([a-zA-Z0-9]*)/).flatten.delete_if {|x| x.length == 0}
-					tag = tag[0].downcase;
+					tag = tag[0].to_s.downcase;
 					
 					# get attributes
 					attr_array = element.scan(/([^=\s]*)=["\']?([^"\']*)["\']?/)