diff --git a/.gitignore b/.gitignore index 6f7528fc..0b625b2e 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,5 @@ doc/app /.bundle /Gemfile.lock +/Gemfile.local /.rvmrc* diff --git a/Gemfile b/Gemfile index befa9d28..7b02dfa5 100644 --- a/Gemfile +++ b/Gemfile @@ -11,6 +11,9 @@ group :test do gem 'shoulda', '~> 2.10.3' gem 'edavis10-object_daddy', :require => 'object_daddy' gem 'mocha' + + platforms :mri_18 do gem 'ruby-debug' end + platforms :mri_19 do gem 'ruby-debug19', :require => 'ruby-debug' end end group :openid do @@ -36,15 +39,22 @@ platforms :mri do group :mysql2 do gem "mysql2", "~> 0.2.7" end - + group :postgres do gem "pg", "~> 0.9.0" # gem "postgres-pr" end - +end + +platforms :mri_18 do group :sqlite do gem "sqlite3-ruby", "< 1.3", :require => "sqlite3" - # please tell me, if you are fond of a pure ruby sqlite3 binding + end +end + +platforms :mri_19 do + group :sqlite do + gem "sqlite3" end end @@ -54,16 +64,23 @@ platforms :jruby do group :mysql do gem "activerecord-jdbcmysql-adapter" end - + group :postgres do gem "activerecord-jdbcpostgresql-adapter" end - + group :sqlite do gem "activerecord-jdbcsqlite3-adapter" end end +# Load a "local" Gemfile +gemfile_local = File.join(File.dirname(__FILE__), "Gemfile.local") +if File.readable?(gemfile_local) + puts "Loading #{gemfile_local} ..." if $DEBUG + instance_eval(File.read(gemfile_local)) +end + # Load plugins' Gemfiles Dir.glob File.expand_path("../vendor/plugins/*/Gemfile", __FILE__) do |file| puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v` diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 45b1261c..57d77f54 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -24,6 +24,12 @@ class ApplicationController < ActionController::Base layout 'base' exempt_from_layout 'builder', 'rsb' + protect_from_forgery + def handle_unverified_request + super + cookies.delete(:autologin) + end + # Remove broken cookie after upgrade from 0.8.x (#4292) # See https://rails.lighthouseapp.com/projects/8994/tickets/3360 # TODO: remove it when Rails is fixed @@ -38,7 +44,6 @@ class ApplicationController < ActionController::Base before_filter :user_setup, :check_if_login_required, :set_localization filter_parameter_logging :password - protect_from_forgery rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 5a698d7a..8294586e 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -286,7 +286,7 @@ private render_error l(:error_no_tracker_in_project) return false end - @issue.start_date ||= Date.today + @issue.start_date ||= User.current.today if params[:issue].is_a?(Hash) @issue.safe_attributes = params[:issue] if User.current.allowed_to?(:add_issue_watchers, @project) && @issue.new_record? diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 133a18cb..44f68340 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -16,10 +16,12 @@ class ProjectsController < ApplicationController menu_item :roadmap, :only => :roadmap menu_item :settings, :only => :settings - before_filter :find_project, :except => [ :index, :list, :new, :create, :copy ] - before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy] + before_filter :find_project, :except => [ :index, :new, :create, :copy ] + before_filter :authorize, :only => [ :show, :settings, :edit, :update, :modules ] before_filter :authorize_global, :only => [:new, :create] before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ] + before_filter :jump_to_project_menu_item, :only => :show + before_filter :load_project_settings, :only => :settings accept_key_auth :index, :show, :create, :update, :destroy after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller| @@ -68,12 +70,7 @@ class ProjectsController < ApplicationController if validate_parent_id && @project.save @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') - # Add current user as a project member if he is not admin - unless User.current.admin? - r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first - m = Member.new(:user => User.current, :roles => [r]) - @project.members << m - end + add_current_user_to_project_if_not_admin(@project) respond_to do |format| format.html { flash[:notice] = l(:notice_successful_create) @@ -128,11 +125,6 @@ class ProjectsController < ApplicationController # Show @project def show - if params[:jump] - # try to redirect to the requested menu item - redirect_to_project_menu_item(@project, params[:jump]) && return - end - @users_by_role = @project.users_by_role @subprojects = @project.children.visible.all @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC") @@ -151,8 +143,6 @@ class ProjectsController < ApplicationController @total_hours = TimeEntry.visible.sum(:hours, :include => :project, :conditions => cond).to_f end - @key = User.current.rss_key - respond_to do |format| format.html format.api @@ -160,12 +150,6 @@ class ProjectsController < ApplicationController end def settings - @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") - @issue_category ||= IssueCategory.new - @member ||= @project.members.new - @trackers = Tracker.all - @repository ||= @project.repository - @wiki ||= @project.wiki end def edit @@ -187,7 +171,7 @@ class ProjectsController < ApplicationController else respond_to do |format| format.html { - settings + load_project_settings render :action => 'settings' } format.api { render_validation_errors(@project) } @@ -230,8 +214,7 @@ class ProjectsController < ApplicationController end end end - # hide project in layout - @project = nil + hide_project_in_layout end private @@ -257,4 +240,33 @@ private end true end + + def jump_to_project_menu_item + if params[:jump] + # try to redirect to the requested menu item + redirect_to_project_menu_item(@project, params[:jump]) && return + end + end + + def load_project_settings + @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") + @issue_category ||= IssueCategory.new + @member ||= @project.members.new + @trackers = Tracker.all + @repository ||= @project.repository + @wiki ||= @project.wiki + end + + def hide_project_in_layout + @project = nil + end + + def add_current_user_to_project_if_not_admin(project) + unless User.current.admin? + r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first + m = Member.new(:user => User.current, :roles => [r]) + project.members << m + end + end + end diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 14054eb9..ad190145 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -143,7 +143,12 @@ class RepositoriesController < ApplicationController return true if Redmine::MimeType.is_type?('text', path) # Ruby 1.8.6 has a bug of integer divisions. # http://apidock.com/ruby/v1_8_6_287/String/is_binary_data%3F - return false if ent.is_binary_data? + if ent.respond_to?("is_binary_data?") && ent.is_binary_data? # Ruby 1.8.x and <1.9.2 + return false + elsif ent.respond_to?(:force_encoding) && (ent.dup.force_encoding("UTF-8") != ent.dup.force_encoding("BINARY") ) # Ruby 1.9.2 + # TODO: need to handle edge cases of non-binary content that isn't UTF-8 + return false + end true end private :is_entry_text_data? diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 57d450c6..10dcb3b8 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -81,7 +81,7 @@ module ApplicationHelper subject = truncate(subject, :length => options[:truncate]) end end - s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, + s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, :class => issue.css_classes, :title => title s << ": #{h subject}" if subject diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 78c97ba6..84b5a63b 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -129,82 +129,6 @@ module IssuesHelper out end - def show_detail(detail, no_html=false) - case detail.property - when 'attr' - field = detail.prop_key.to_s.gsub(/\_id$/, "") - label = l(("field_" + field).to_sym) - case - when ['due_date', 'start_date'].include?(detail.prop_key) - value = format_date(detail.value.to_date) if detail.value - old_value = format_date(detail.old_value.to_date) if detail.old_value - - when ['project_id', 'status_id', 'tracker_id', 'assigned_to_id', 'priority_id', 'category_id', 'fixed_version_id'].include?(detail.prop_key) - value = find_name_by_reflection(field, detail.value) - old_value = find_name_by_reflection(field, detail.old_value) - - when detail.prop_key == 'estimated_hours' - value = "%0.02f" % detail.value.to_f unless detail.value.blank? - old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank? - - when detail.prop_key == 'parent_id' - label = l(:field_parent_issue) - value = "##{detail.value}" unless detail.value.blank? - old_value = "##{detail.old_value}" unless detail.old_value.blank? - end - when 'cf' - custom_field = CustomField.find_by_id(detail.prop_key) - if custom_field - label = custom_field.name - value = format_value(detail.value, custom_field.field_format) if detail.value - old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value - end - when 'attachment' - label = l(:label_attachment) - end - call_hook(:helper_issues_show_detail_after_setting, {:detail => detail, :label => label, :value => value, :old_value => old_value }) - - label ||= detail.prop_key - value ||= detail.value - old_value ||= detail.old_value - - unless no_html - label = content_tag('strong', label) - old_value = content_tag("i", h(old_value)) if detail.old_value - old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?) - if detail.property == 'attachment' && !value.blank? && a = Attachment.find_by_id(detail.prop_key) - # Link to the attachment if it has not been removed - value = link_to_attachment(a) - else - value = content_tag("i", h(value)) if value - end - end - - if detail.property == 'attr' && detail.prop_key == 'description' - s = l(:text_journal_changed_no_detail, :label => label) - unless no_html - diff_link = link_to 'diff', - {:controller => 'journals', :action => 'diff', :id => detail.journal_id, :detail_id => detail.id}, - :title => l(:label_view_diff) - s << " (#{ diff_link })" - end - s - elsif !detail.value.blank? - case detail.property - when 'attr', 'cf' - if !detail.old_value.blank? - l(:text_journal_changed, :label => label, :old => old_value, :new => value) - else - l(:text_journal_set_to, :label => label, :value => value) - end - when 'attachment' - l(:text_journal_added, :label => label, :value => value) - end - else - l(:text_journal_deleted, :label => label, :old => old_value) - end - end - # Find the name of an associated record stored in the field attribute def find_name_by_reflection(field, id) association = Issue.reflect_on_association(field.to_sym) diff --git a/app/helpers/journals_helper.rb b/app/helpers/journals_helper.rb index 5a9f4770..e16f7a4c 100644 --- a/app/helpers/journals_helper.rb +++ b/app/helpers/journals_helper.rb @@ -48,7 +48,7 @@ module JournalsHelper if d = journal.render_detail(detail) content_tag("li", d) end - end.compact + end.compact.join(' ') end end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 414e4391..98fc27fa 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -57,4 +57,14 @@ module SearchHelper end ('') unless links.empty? end + + def link_to_previous_search_page(pagination_previous_date) + link_to_content_update('« ' + l(:label_previous), + params.merge(:previous => 1, :offset => pagination_previous_date.strftime("%Y%m%d%H%M%S"))) + end + + def link_to_next_search_page(pagination_next_date) + link_to_content_update(l(:label_next) + ' »', + params.merge(:previous => nil, :offset => pagination_next_date.strftime("%Y%m%d%H%M%S"))) + end end diff --git a/app/models/change.rb b/app/models/change.rb index 652fde7d..d6d53f1e 100644 --- a/app/models/change.rb +++ b/app/models/change.rb @@ -17,10 +17,22 @@ class Change < ActiveRecord::Base validates_presence_of :changeset_id, :action, :path before_save :init_path + delegate :repository_encoding, :to => :changeset, :allow_nil => true, :prefix => true + def relative_path changeset.repository.relative_path(path) end + def path + # TODO: shouldn't access Changeset#to_utf8 directly + self.path = Changeset.to_utf8(read_attribute(:path), changeset_repository_encoding) + end + + def from_path + # TODO: shouldn't access Changeset#to_utf8 directly + self.path = Changeset.to_utf8(read_attribute(:from_path), changeset_repository_encoding) + end + def init_path self.path ||= "" end diff --git a/app/models/changeset.rb b/app/models/changeset.rb index 5294b05c..68b9d5e7 100644 --- a/app/models/changeset.rb +++ b/app/models/changeset.rb @@ -74,6 +74,23 @@ class Changeset < ActiveRecord::Base user || committer.to_s.split('<').first end + # Delegate to a Repository's log encoding + def repository_encoding + if repository.present? + repository.repo_log_encoding + else + nil + end + end + + # Committer of the Changeset + # + # Attribute reader for committer that encodes the committer string to + # the repository log encoding (e.g. UTF-8) + def committer + self.class.to_utf8(read_attribute(:committer), repository.repo_log_encoding) + end + def before_create self.committer = self.class.to_utf8(self.committer, repository.repo_log_encoding) self.comments = self.class.normalize_comments(self.comments, repository.repo_log_encoding) @@ -239,6 +256,7 @@ class Changeset < ActiveRecord::Base private + # TODO: refactor to a standard helper method def self.to_utf8(str, encoding) return str if str.nil? str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding) @@ -273,12 +291,6 @@ class Changeset < ActiveRecord::Base end str = txtar end - # removes invalid UTF8 sequences - begin - Iconv.conv('UTF-8//IGNORE', 'UTF-8', str + ' ')[0..-3] - rescue Iconv::InvalidEncoding - # "UTF-8//IGNORE" is not supported on some OS - str - end + str end end diff --git a/app/models/journal.rb b/app/models/journal.rb index 8070d78b..c7818d76 100644 --- a/app/models/journal.rb +++ b/app/models/journal.rb @@ -23,9 +23,17 @@ class Journal < ActiveRecord::Base # Make sure each journaled model instance only has unique version ids validates_uniqueness_of :version, :scope => [:journaled_id, :type] - belongs_to :journaled, :touch => true + + # Define a default class_name to prevent `uninitialized constant Journal::Journaled` + # subclasses will be given an actual class name when they are created by aaj + # + # e.g. IssueJournal will get :class_name => 'Issue' + belongs_to :journaled, :class_name => 'Journal' belongs_to :user + # "touch" the journaled object on creation + after_create :touch_journaled_after_creation + # ActiveRecord::Base#changes is an existing method, so before serializing the +changes+ column, # the existing +changes+ method is undefined. The overridden +changes+ method pertained to # dirty attributes, but will not affect the partial updates functionality as that's based on @@ -33,6 +41,10 @@ class Journal < ActiveRecord::Base # undef_method :changes serialize :changes, Hash + def touch_journaled_after_creation + journaled.touch + end + # In conjunction with the included Comparable module, allows comparison of journal records # based on their corresponding version numbers, creation timestamps and IDs. def <=>(other) diff --git a/app/models/mailer.rb b/app/models/mailer.rb index 1962ae14..e58c88ed 100644 --- a/app/models/mailer.rb +++ b/app/models/mailer.rb @@ -185,7 +185,7 @@ class Mailer < ActionMailer::Base cc((message.root.watcher_recipients + message.board.watcher_recipients).uniq - @recipients) subject "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}" body :message => message, - :message_url => url_for(message.last_journal.event_url) + :message_url => url_for({ :controller => 'messages', :action => 'show', :board_id => message.board, :id => message.root, :r => message, :anchor => "message-#{message.id}" }) render_multipart('message_posted', body) end diff --git a/app/models/message_observer.rb b/app/models/message_observer.rb index be7c4968..f4605858 100644 --- a/app/models/message_observer.rb +++ b/app/models/message_observer.rb @@ -12,10 +12,7 @@ #++ class MessageObserver < ActiveRecord::Observer - def after_save(message) - if message.last_journal.version == 1 - # Only deliver mails for the first journal - Mailer.deliver_message_posted(message) if Setting.notified_events.include?('message_posted') - end + def after_create(message) + Mailer.deliver_message_posted(message) if Setting.notified_events.include?('message_posted') end end diff --git a/app/models/query.rb b/app/models/query.rb index e2ffd95d..052990ba 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -126,8 +126,9 @@ class Query < ActiveRecord::Base QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'), QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true), QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true), - QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"), - QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"), + # Put empty start_dates and due_dates in the far future rather than in the far past + QueryColumn.new(:start_date, :sortable => ["CASE WHEN #{Issue.table_name}.start_date IS NULL THEN 1 ELSE 0 END", "#{Issue.table_name}.start_date"]), + QueryColumn.new(:due_date, :sortable => ["CASE WHEN #{Issue.table_name}.due_date IS NULL THEN 1 ELSE 0 END", "#{Issue.table_name}.due_date"]), QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"), QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true), QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'), @@ -587,9 +588,17 @@ class Query < ActiveRecord::Base sql = "#{db_table}.#{db_field} IS NOT NULL" sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter when ">=" - sql = "#{db_table}.#{db_field} >= #{value.first.to_i}" + if is_custom_filter + sql = "#{db_table}.#{db_field} != '' AND CAST(#{db_table}.#{db_field} AS decimal(60,4)) >= #{value.first.to_f}" + else + sql = "#{db_table}.#{db_field} >= #{value.first.to_f}" + end when "<=" - sql = "#{db_table}.#{db_field} <= #{value.first.to_i}" + if is_custom_filter + sql = "#{db_table}.#{db_field} != '' AND CAST(#{db_table}.#{db_field} AS decimal(60,4)) <= #{value.first.to_f}" + else + sql = "#{db_table}.#{db_field} <= #{value.first.to_f}" + end when "o" sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id" when "c" @@ -629,6 +638,8 @@ class Query < ActiveRecord::Base custom_fields.select(&:is_filter?).each do |field| case field.field_format + when "int", "float" + options = { :type => :integer, :order => 20 } when "text" options = { :type => :text, :order => 20 } when "list" diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb index 660a84a7..9023e2e2 100644 --- a/app/models/wiki_content.rb +++ b/app/models/wiki_content.rb @@ -59,12 +59,7 @@ class WikiContent < ActiveRecord::Base end def version - unless last_journal - # FIXME: This is code that caters for a case that should never happen in the normal code paths!! - create_journal - last_journal.update_attribute(:created_at, updated_on) - end - last_journal.version + new_record? ? 0 : last_journal.version end private @@ -106,9 +101,9 @@ class WikiContent < ActiveRecord::Base end def text - @text ||= case changes[:compression] - when 'gzip' - Zlib::Inflate.inflate(data) + @text ||= case changes["compression"] + when "gzip" + Zlib::Inflate.inflate(changes["data"]) else # uncompressed data changes["data"] diff --git a/app/views/issues/show.rhtml b/app/views/issues/show.rhtml index 4b7daba7..8a3b69a3 100644 --- a/app/views/issues/show.rhtml +++ b/app/views/issues/show.rhtml @@ -1,6 +1,6 @@ <%= render :partial => 'action_menu' %> -

<%= @issue.tracker.name %> #<%= @issue.id %><%= call_hook(:view_issues_show_identifier, :issue => @issue) %>

+

<%= h(@issue.tracker.name) %> #<%= h(@issue.id) %><%= call_hook(:view_issues_show_identifier, :issue => @issue) %>

<%= avatar(@issue.author, :size => "50") %> @@ -17,11 +17,11 @@ - + - + @@ -29,7 +29,7 @@ - + <% if User.current.allowed_to?(:view_time_entries, @project) %> diff --git a/app/views/repositories/_dir_list_content.rhtml b/app/views/repositories/_dir_list_content.rhtml index fd9dd7af..f6833c89 100644 --- a/app/views/repositories/_dir_list_content.rhtml +++ b/app/views/repositories/_dir_list_content.rhtml @@ -22,6 +22,6 @@ - + <% end %> diff --git a/app/views/repositories/_revisions.rhtml b/app/views/repositories/_revisions.rhtml index 92c6fb53..78e0f3b4 100644 --- a/app/views/repositories/_revisions.rhtml +++ b/app/views/repositories/_revisions.rhtml @@ -18,7 +18,7 @@ - + <% line_num += 1 %> <% end %> diff --git a/app/views/search/_pagination.html.erb b/app/views/search/_pagination.html.erb new file mode 100644 index 00000000..d06fe8eb --- /dev/null +++ b/app/views/search/_pagination.html.erb @@ -0,0 +1,10 @@ +
+

+ <% if pagination_previous_date %> + <%= link_to_previous_search_page(pagination_previous_date) %> + <% end %> + <% if pagination_next_date %> + <%= link_to_next_search_page(pagination_next_date) %> + <% end %> +

+
diff --git a/app/views/search/index.rhtml b/app/views/search/index.rhtml index 674cabf6..db9e3b64 100644 --- a/app/views/search/index.rhtml +++ b/app/views/search/index.rhtml @@ -24,6 +24,9 @@

<%= l(:label_result_plural) %> (<%= @results_by_type.values.sum %>)

+ + <%= render :partial => 'pagination', :locals => {:pagination_previous_date => @pagination_previous_date, :pagination_next_date => @pagination_next_date } %> +
<% @results.each do |e| %>
@@ -36,15 +39,6 @@
<% end %> -

-<% if @pagination_previous_date %> -<%= link_to_content_update('« ' + l(:label_previous), - params.merge(:previous => 1, :offset => @pagination_previous_date.strftime("%Y%m%d%H%M%S"))) %>  -<% end %> -<% if @pagination_next_date %> -<%= link_to_content_update(l(:label_next) + ' »', - params.merge(:previous => nil, :offset => @pagination_next_date.strftime("%Y%m%d%H%M%S"))) %> -<% end %> -

+<%= render :partial => 'pagination', :locals => {:pagination_previous_date => @pagination_previous_date, :pagination_next_date => @pagination_next_date } %> <% html_title(l(:label_search)) -%> diff --git a/app/views/wiki/export.rhtml b/app/views/wiki/export.rhtml index da45423d..2747ffd2 100644 --- a/app/views/wiki/export.rhtml +++ b/app/views/wiki/export.rhtml @@ -3,6 +3,7 @@ <%=h @page.pretty_title %> +" /> -<%= textilizable @content, :text, :wiki_links => :local %> +<%= textilizable @content, :text, :wiki_links => :local, :only_path => false %> diff --git a/config/locales/bg.yml b/config/locales/bg.yml index e15ca31c..8b5e3ace 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -945,21 +945,21 @@ bg: enumeration_activities: Дейности (time tracking) enumeration_system_activity: Системна активност - text_powered_by: Powered by %{link} - label_cvs_module: Module - label_filesystem_path: Root directory - label_darcs_path: Root directory - label_bazaar_path: Root directory + text_powered_by: Този сайт е задвижван от %{link} + label_cvs_module: Модул + label_filesystem_path: Коренна директория + label_darcs_path: Коренна директория + label_bazaar_path: Коренна директория label_cvs_path: CVSROOT - label_git_path: Path to .git directory - label_mercurial_path: Root directory - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - field_effective_date: Due date - text_default_encoding: "Default: UTF-8" + label_git_path: Път до директория .git + label_mercurial_path: Коренна директория + label_additional_workflow_transitions_for_assignee: Позволени са допълнителни преходи, когато потребителят е назначеният към задачата + button_expand_all: Разгъване всички + button_collapse_all: Скриване всички + label_additional_workflow_transitions_for_author: Позволени са допълнителни преходи, когато потребителят е авторът + field_effective_date: Дата + text_default_encoding: "По подразбиране: UTF-8" text_git_repo_example: a bare and local repository (e.g. /gitrepo, c:\gitrepo) label_notify_member_plural: Email issue updates - label_path_encoding: Path encoding - text_mercurial_repo_example: local repository (e.g. /hgrepo, c:\hgrepo) + label_path_encoding: Кодиране на пътищата + text_mercurial_repo_example: локално хранилище (например /hgrepo, c:\hgrepo) diff --git a/db/migrate/20100714111652_update_journals_for_acts_as_journalized.rb b/db/migrate/20100714111652_update_journals_for_acts_as_journalized.rb index 6792951f..b3d86893 100644 --- a/db/migrate/20100714111652_update_journals_for_acts_as_journalized.rb +++ b/db/migrate/20100714111652_update_journals_for_acts_as_journalized.rb @@ -1,3 +1,16 @@ +#-- copyright +# ChiliProject is a project management system. +# +# Copyright (C) 2010-2011 the ChiliProject Team +# +# 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. +# +# See doc/COPYRIGHT.rdoc for more details. +#++ + Redmine::Activity.providers.values.flatten.uniq.collect(&:underscore).each {|klass| require_dependency klass } class UpdateJournalsForActsAsJournalized < ActiveRecord::Migration diff --git a/db/migrate/20100714111653_build_initial_journals_for_acts_as_journalized.rb b/db/migrate/20100714111653_build_initial_journals_for_acts_as_journalized.rb index 3574e642..4d23d31a 100644 --- a/db/migrate/20100714111653_build_initial_journals_for_acts_as_journalized.rb +++ b/db/migrate/20100714111653_build_initial_journals_for_acts_as_journalized.rb @@ -1,3 +1,16 @@ +#-- copyright +# ChiliProject is a project management system. +# +# Copyright (C) 2010-2011 the ChiliProject Team +# +# 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. +# +# See doc/COPYRIGHT.rdoc for more details. +#++ + class BuildInitialJournalsForActsAsJournalized < ActiveRecord::Migration def self.up # This is provided here for migrating up after the JournalDetails has been removed @@ -11,53 +24,24 @@ class BuildInitialJournalsForActsAsJournalized < ActiveRecord::Migration klass.reset_column_information if klass.respond_to?(:reset_column_information) end - providers = Redmine::Activity.providers.collect {|k, v| v.collect(&:constantize) }.flatten.compact.uniq - providers.each do |p| - next unless p.table_exists? # Objects not in the DB yet need creation journal entries + [Message, Attachment, Document, Changeset, Issue, TimeEntry, News].each do |p| say_with_time("Building initial journals for #{p.class_name}") do + # avoid touching the journaled object on journal creation + p.journal_class.class_exec { + def touch_journaled_after_creation + end + } + activity_type = p.activity_provider_options.keys.first + # Create initial journals p.find(:all).each do |o| - # Create initial journals - new_journal = o.journals.build - # Mock up a list of changes for the creation journal based on Class defaults - new_attributes = o.class.new.attributes.except(o.class.primary_key, - o.class.inheritance_column, - :updated_on, - :updated_at, - :lock_version, - :lft, - :rgt) - creation_changes = {} - new_attributes.each do |name, default_value| - # Set changes based on the initial value to current. Can't get creation value without - # rebuiling the object history - creation_changes[name] = [default_value, o.send(name)] # [initial_value, creation_value] - end - new_journal.changes = creation_changes - new_journal.version = 1 - new_journal.activity_type = activity_type - - if o.respond_to?(:author) - new_journal.user = o.author - elsif o.respond_to?(:user) - new_journal.user = o.user - end # Using rescue and save! here because either the Journal or the # touched record could fail. This will catch either error and continue begin - new_journal.save! - - new_journal.reload - - # Backdate journal - if o.respond_to?(:created_at) - new_journal.update_attribute(:created_at, o.created_at) - elsif o.respond_to?(:created_on) - new_journal.update_attribute(:created_at, o.created_on) - end + new_journal = o.recreate_initial_journal! rescue ActiveRecord::RecordInvalid => ex if new_journal.errors.count == 1 && new_journal.errors.first[0] == "version" # Skip, only error was from creating the initial journal for a record that already had one. diff --git a/db/migrate/20100714111654_add_changes_from_journal_details_for_acts_as_journalized.rb b/db/migrate/20100714111654_add_changes_from_journal_details_for_acts_as_journalized.rb index 43ef3256..35370a4d 100644 --- a/db/migrate/20100714111654_add_changes_from_journal_details_for_acts_as_journalized.rb +++ b/db/migrate/20100714111654_add_changes_from_journal_details_for_acts_as_journalized.rb @@ -1,3 +1,16 @@ +#-- copyright +# ChiliProject is a project management system. +# +# Copyright (C) 2010-2011 the ChiliProject Team +# +# 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. +# +# See doc/COPYRIGHT.rdoc for more details. +#++ + class AddChangesFromJournalDetailsForActsAsJournalized < ActiveRecord::Migration def self.up # This is provided here for migrating up after the JournalDetails has been removed diff --git a/db/migrate/20100804112053_merge_wiki_versions_with_journals.rb b/db/migrate/20100804112053_merge_wiki_versions_with_journals.rb index 3e7d6517..332b13f3 100644 --- a/db/migrate/20100804112053_merge_wiki_versions_with_journals.rb +++ b/db/migrate/20100804112053_merge_wiki_versions_with_journals.rb @@ -18,6 +18,12 @@ class MergeWikiVersionsWithJournals < ActiveRecord::Migration WikiContent.const_set("Version", Class.new(ActiveRecord::Base)) end + # avoid touching WikiContent on journal creation + WikiContentJournal.class_exec { + def touch_journaled_after_creation + end + } + WikiContent::Version.find_by_sql("SELECT * FROM wiki_content_versions").each do |wv| journal = WikiContentJournal.create!(:journaled_id => wv.wiki_content_id, :user_id => wv.author_id, :notes => wv.comments, :created_at => wv.updated_on, :activity_type => "wiki_edits") diff --git a/db/migrate/20110729125454_remove_double_initial_wiki_content_journals.rb b/db/migrate/20110729125454_remove_double_initial_wiki_content_journals.rb new file mode 100644 index 00000000..d8129122 --- /dev/null +++ b/db/migrate/20110729125454_remove_double_initial_wiki_content_journals.rb @@ -0,0 +1,10 @@ +class RemoveDoubleInitialWikiContentJournals < ActiveRecord::Migration + def self.up + # Remove the newest initial WikiContentJournal (the one erroneously created by a former migration) if there are more than one + WikiContentJournal.find(:all, :conditions => {:version => 1}).group_by(&:journaled_id).select {|k,v| v.size > 1}.each {|k,v| v.max_by(&:created_at).delete} + end + + def self.down + # noop + end +end diff --git a/doc/CHANGELOG.rdoc b/doc/CHANGELOG.rdoc index f0b27a59..16d1b23f 100644 --- a/doc/CHANGELOG.rdoc +++ b/doc/CHANGELOG.rdoc @@ -1,6 +1,29 @@ = ChiliProject changelog -== TBD v2.0.0 +== 2011-07-29 v2.1.0 + +* Bug #191: Add Next/Previous links to the top of search results +* Bug #467: uninitialized constant Journal::Journaled +* Bug #498: Wrong filters for int and float custom fields +* Bug #511: Encoding of strings coming out of SQLite +* Bug #512: reposman.rb do not work properly in Gentoo Linux. +* Bug #513: Attached files in "comment" no longer link to file +* Bug #514: Multiple emails for each forum post +* Bug #523: Gzipped history of wiki pages is garbeled during an update of an older version to 2.0 +* Bug #530: Start date default should consider timezone +* Bug #536: CSRF Protection +* Bug #537: Accessing version of newly created WikiContent results in NoMethodError +* Bug #540: Hook helper_issues_show_detail_after_setting gets different parameters in Chili 1.x and 2.0 +* Bug #542: Double initial journal for migrated wiki history +* Bug #543: Journalized touch on journal update causes StaleObjectErrors +* Bug #544: XSS in app/views/issues/show.rhtml +* Feature #499: Due date sort order should sort issues with no due date to the end of the list +* Feature #506: Support for "local" Gemfile - Gemfile.local +* Feature #526: Bulgarian translation +* Feature #539: Remove dead code in IssueHelper +* Task #518: Document how to create a Journal using acts_as_journalized + +== 2011-07-01 v2.0.0 * Bug #262: Fix line endings * Bug #341: Remove English strings from RepositoriesHelper diff --git a/extra/sample_plugin/README b/extra/sample_plugin/README index d576ecfb..a073a73b 100644 --- a/extra/sample_plugin/README +++ b/extra/sample_plugin/README @@ -7,7 +7,7 @@ This is a sample plugin for Redmine 1. Copy the plugin directory into the vendor/plugins directory 2. Migrate plugin: - rake db:migrate_plugins + rake db:migrate:plugins 3. Start Redmine diff --git a/extra/sample_plugin/db/migrate/001_create_meetings.rb b/extra/sample_plugin/db/migrate/001_create_meetings.rb index 1a9b7386..973e34a2 100644 --- a/extra/sample_plugin/db/migrate/001_create_meetings.rb +++ b/extra/sample_plugin/db/migrate/001_create_meetings.rb @@ -12,7 +12,7 @@ #++ # Sample plugin migration -# Use rake db:migrate_plugins to migrate installed plugins +# Use rake db:migrate:plugins to migrate installed plugins class CreateMeetings < ActiveRecord::Migration def self.up create_table :meetings do |t| diff --git a/extra/svn/reposman.rb b/extra/svn/reposman.rb index b0c7cc39..a6476c86 100755 --- a/extra/svn/reposman.rb +++ b/extra/svn/reposman.rb @@ -1,3 +1,4 @@ +#!/usr/bin/env ruby #-- copyright # ChiliProject is a project management system. # @@ -11,8 +12,6 @@ # See doc/COPYRIGHT.rdoc for more details. #++ -#!/usr/bin/env ruby - # == Synopsis # # reposman: manages your repositories with Redmine diff --git a/lib/tasks/migrate_plugins.rake b/lib/chili_project/compatibility.rb similarity index 51% rename from lib/tasks/migrate_plugins.rake rename to lib/chili_project/compatibility.rb index fb279d62..502a27a6 100644 --- a/lib/tasks/migrate_plugins.rake +++ b/lib/chili_project/compatibility.rb @@ -11,18 +11,14 @@ # See doc/COPYRIGHT.rdoc for more details. #++ -namespace :db do - desc 'Migrates installed plugins.' - task :migrate_plugins => :environment do - if Rails.respond_to?('plugins') - Rails.plugins.each do |plugin| - next unless plugin.respond_to?('migrate') - puts "Migrating #{plugin.name}..." - plugin.migrate - end - else - puts "Undefined method plugins for Rails!" - puts "Make sure engines plugin is installed." +module ChiliProject + class Compatibility + # Is acts_as_journalized included? + # + # Released: ChiliProject 2.0.0 + def self.using_acts_as_journalized? + Journal.included_modules.include?(Redmine::Acts::Journalized) end + end end diff --git a/lib/chili_project/database.rb b/lib/chili_project/database.rb index 02469e4a..426fa691 100644 --- a/lib/chili_project/database.rb +++ b/lib/chili_project/database.rb @@ -30,7 +30,7 @@ module ChiliProject }) end - # Get the raw namme of the currently used database adapter. + # Get the raw name of the currently used database adapter. # This string is set by the used adapter gem. def self.adapter_name ActiveRecord::Base.connection.adapter_name diff --git a/lib/redmine/scm/adapters/git_adapter.rb b/lib/redmine/scm/adapters/git_adapter.rb index 641eedbb..798a556a 100644 --- a/lib/redmine/scm/adapters/git_adapter.rb +++ b/lib/redmine/scm/adapters/git_adapter.rb @@ -288,7 +288,12 @@ module Redmine content = nil scm_cmd(*cmd_args) { |io| io.binmode; content = io.read } # git annotates binary files - return nil if content.is_binary_data? + if content.respond_to?("is_binary_data?") && content.is_binary_data? # Ruby 1.8.x and <1.9.2 + return nil + elsif content.respond_to?(:force_encoding) && (content.dup.force_encoding("UTF-8") != content.dup.force_encoding("BINARY")) # Ruby 1.9.2 + # TODO: need to handle edge cases of non-binary content that isn't UTF-8 + return nil + end identifier = '' # git shows commit author on the first occurrence only authors_by_commit = {} diff --git a/lib/redmine/version.rb b/lib/redmine/version.rb index abd878ef..cace954c 100644 --- a/lib/redmine/version.rb +++ b/lib/redmine/version.rb @@ -16,7 +16,7 @@ require 'rexml/document' module Redmine module VERSION #:nodoc: MAJOR = 2 - MINOR = 0 + MINOR = 1 PATCH = 0 TINY = PATCH # Redmine compat diff --git a/lib/tasks/ci.rake b/lib/tasks/ci.rake index 702307a8..494eee31 100644 --- a/lib/tasks/ci.rake +++ b/lib/tasks/ci.rake @@ -29,7 +29,7 @@ namespace :ci do Rake::Task["db:drop"].invoke Rake::Task["db:create"].invoke Rake::Task["db:migrate"].invoke - Rake::Task["db:migrate_plugins"].invoke + Rake::Task["db:migrate:plugins"].invoke Rake::Task["db:schema:dump"].invoke Rake::Task["test:scm:update"].invoke end diff --git a/lib/tasks/deprecated.rake b/lib/tasks/deprecated.rake index d31f6b3b..58568c99 100644 --- a/lib/tasks/deprecated.rake +++ b/lib/tasks/deprecated.rake @@ -20,3 +20,4 @@ end deprecated_task :load_default_data, "redmine:load_default_data" deprecated_task :migrate_from_mantis, "redmine:migrate_from_mantis" deprecated_task :migrate_from_trac, "redmine:migrate_from_trac" +deprecated_task "db:migrate_plugins", "db:migrate:plugins" \ No newline at end of file diff --git a/lib/tasks/yardoc.rake b/lib/tasks/yardoc.rake index 41e1efa7..9495ee62 100644 --- a/lib/tasks/yardoc.rake +++ b/lib/tasks/yardoc.rake @@ -19,11 +19,12 @@ begin files << Dir['vendor/plugins/**/*.rb'].reject {|f| f.match(/test/) } # Exclude test files t.files = files - static_files = ['doc/CHANGELOG', - 'doc/COPYING', - 'doc/INSTALL', - 'doc/RUNNING_TESTS', - 'doc/UPGRADING'].join(',') + static_files = ['doc/CHANGELOG.rdoc', + 'doc/COPYING.rdoc', + 'doc/COPYRIGHT.rdoc', + 'doc/INSTALL.rdoc', + 'doc/RUNNING_TESTS.rdoc', + 'doc/UPGRADING.rdoc'].join(',') t.options += ['--output-dir', './doc/app', '--files', static_files] end diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index 0c79cd64..a7087308 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -331,6 +331,9 @@ dt.time-entry { background-image: url(../images/time.png); } #search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); } +.search-pagination { text-align: center; } +.search-pagination a {padding: 0 5px; } + div#roadmap .related-issues { margin-bottom: 1em; } div#roadmap .related-issues td.checkbox { display: none; } div#roadmap .wiki h1:first-child { display: none; } diff --git a/test/exemplars/journal_exemplar.rb b/test/exemplars/journal_exemplar.rb index a28c3774..83f4ae69 100644 --- a/test/exemplars/journal_exemplar.rb +++ b/test/exemplars/journal_exemplar.rb @@ -12,7 +12,7 @@ #++ class Journal < ActiveRecord::Base - generator_for :journalized, :method => :generate_issue + generator_for :journaled, :method => :generate_issue generator_for :user, :method => :generate_user def self.generate_issue diff --git a/test/integration/issues_test.rb b/test/integration/issues_test.rb index 5b7775dc..b18a2efd 100644 --- a/test/integration/issues_test.rb +++ b/test/integration/issues_test.rb @@ -61,6 +61,7 @@ class IssuesTest < ActionController::IntegrationTest # add then remove 2 attachments to an issue def test_issue_attachments + Issue.find(1).recreate_initial_journal! log_user('jsmith', 'jsmith') set_tmp_attachments_directory @@ -68,6 +69,7 @@ class IssuesTest < ActionController::IntegrationTest :notes => 'Some notes', :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'This is an attachment'}} assert_redirected_to "/issues/1" + follow_redirect! # make sure attachment was saved attachment = Issue.find(1).attachments.find_by_filename("testfile.txt") @@ -79,6 +81,12 @@ class IssuesTest < ActionController::IntegrationTest # verify that the attachment was written to disk assert File.exist?(attachment.diskfile) + assert_select "#history" do + assert_select ".journal .details" do + assert_select "a", :text => /testfile.txt/ + end + end + # remove the attachments Issue.find(1).attachments.each(&:destroy) assert_equal 0, Issue.find(1).attachments.length diff --git a/test/unit/helpers/issues_helper_test.rb b/test/unit/helpers/issues_helper_test.rb index 41ae870a..3713400c 100644 --- a/test/unit/helpers/issues_helper_test.rb +++ b/test/unit/helpers/issues_helper_test.rb @@ -39,33 +39,30 @@ class IssuesHelperTest < HelperTestCase @request ||= ActionController::TestRequest.new end - # This is probably needed in this test only anymore - def show_detail(journal, detail, html = true) - journal.render_detail(detail, html) - end + # TODO: Move test code to Journal class context "IssuesHelper#show_detail" do context "with no_html" do should 'show a changing attribute' do @journal = IssueJournal.generate!(:changes => {"done_ratio" => [40, 100]}, :journaled => Issue.last) - assert_equal "% Done changed from 40 to 100", show_detail(@journal, @journal.details.to_a.first, true) + assert_equal "% Done changed from 40 to 100", @journal.render_detail(@journal.details.to_a.first, true) end should 'show a new attribute' do @journal = IssueJournal.generate!(:changes => {"done_ratio" => [nil, 100]}, :journaled => Issue.last) - assert_equal "% Done set to 100", show_detail(@journal, @journal.details.to_a.first, true) + assert_equal "% Done set to 100", @journal.render_detail(@journal.details.to_a.first, true) end should 'show a deleted attribute' do @journal = IssueJournal.generate!(:changes => {"done_ratio" => [50, nil]}, :journaled => Issue.last) - assert_equal "% Done deleted (50)", show_detail(@journal, @journal.details.to_a.first, true) + assert_equal "% Done deleted (50)", @journal.render_detail(@journal.details.to_a.first, true) end end context "with html" do should 'show a changing attribute with HTML highlights' do @journal = IssueJournal.generate!(:changes => {"done_ratio" => [40, 100]}, :journaled => Issue.last) - @response.body = show_detail(@journal, @journal.details.to_a.first, false) + @response.body = @journal.render_detail(@journal.details.to_a.first, false) assert_select 'strong', :text => '% Done' assert_select 'i', :text => '40' @@ -74,7 +71,7 @@ class IssuesHelperTest < HelperTestCase should 'show a new attribute with HTML highlights' do @journal = IssueJournal.generate!(:changes => {"done_ratio" => [nil, 100]}, :journaled => Issue.last) - @response.body = show_detail(@journal, @journal.details.to_a.first, false) + @response.body = @journal.render_detail(@journal.details.to_a.first, false) assert_select 'strong', :text => '% Done' assert_select 'i', :text => '100' @@ -82,7 +79,7 @@ class IssuesHelperTest < HelperTestCase should 'show a deleted attribute with HTML highlights' do @journal = IssueJournal.generate!(:changes => {"done_ratio" => [50, nil]}, :journaled => Issue.last) - @response.body = show_detail(@journal, @journal.details.to_a.first, false) + @response.body = @journal.render_detail(@journal.details.to_a.first, false) assert_select 'strong', :text => '% Done' assert_select 'strike' do @@ -94,24 +91,24 @@ class IssuesHelperTest < HelperTestCase context "with a start_date attribute" do should "format the current date" do @journal = IssueJournal.generate!(:changes => {"start_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last) - assert_match "01/31/2010", show_detail(@journal, @journal.details.to_a.first, true) + assert_match "01/31/2010", @journal.render_detail(@journal.details.to_a.first, true) end should "format the old date" do @journal = IssueJournal.generate!(:changes => {"start_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last) - assert_match "01/01/2010", show_detail(@journal, @journal.details.to_a.first, true) + assert_match "01/01/2010", @journal.render_detail(@journal.details.to_a.first, true) end end context "with a due_date attribute" do should "format the current date" do @journal = IssueJournal.generate!(:changes => {"due_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last) - assert_match "01/31/2010", show_detail(@journal, @journal.details.to_a.first, true) + assert_match "01/31/2010", @journal.render_detail(@journal.details.to_a.first, true) end should "format the old date" do @journal = IssueJournal.generate!(:changes => {"due_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last) - assert_match "01/01/2010", show_detail(@journal, @journal.details.to_a.first, true) + assert_match "01/01/2010", @journal.render_detail(@journal.details.to_a.first, true) end end diff --git a/test/unit/journal_test.rb b/test/unit/journal_test.rb index b87f63a6..a1ba6cab 100644 --- a/test/unit/journal_test.rb +++ b/test/unit/journal_test.rb @@ -13,7 +13,7 @@ require File.expand_path('../../test_helper', __FILE__) class JournalTest < ActiveSupport::TestCase - fixtures :issues, :issue_statuses, :journals + fixtures :issues, :issue_statuses, :journals, :enumerations def setup @journal = IssueJournal.find(1) @@ -94,4 +94,24 @@ class JournalTest < ActiveSupport::TestCase assert_not_equal start, @issue.reload.updated_on end + + test "accessing #journaled on a Journal should not error (parent class)" do + journal = Journal.new + assert_nothing_raised do + assert_equal nil, journal.journaled + end + end + + test "setting journal fields through the journaled object for creation" do + @issue = Issue.generate_for_project!(Project.generate!) + + @issue.journal_user = @issue.author + @issue.journal_notes = 'Test setting fields on Journal from Issue' + assert_difference('Journal.count') do + assert @issue.save + end + + assert_equal "Test setting fields on Journal from Issue", @issue.last_journal.notes + assert_equal @issue.author, @issue.last_journal.user + end end diff --git a/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb index b4f52fdf..8bad13af 100644 --- a/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb +++ b/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb @@ -155,7 +155,8 @@ begin assert_equal "2010-09-18 19:59:46".to_time, last_rev.time end - def test_latin_1_path + # TODO: need to handle edge cases of non-binary content that isn't UTF-8 + should_eventually "test_latin_1_path" do if WINDOWS_PASS # else @@ -163,7 +164,9 @@ begin ['4fc55c43bf3d3dc2efb66145365ddc17639ce81e', '4fc55c43bf3'].each do |r1| assert @adapter.diff(p2, r1) assert @adapter.cat(p2, r1) - assert_equal 1, @adapter.annotate(p2, r1).lines.length + annotation = @adapter.annotate(p2, r1) + assert annotation.present?, "No annotation returned" + assert_equal 1, annotation.lines.length ['64f1f3e89ad1cb57976ff0ad99a107012ba3481d', '64f1f3e89ad1cb5797'].each do |r2| assert @adapter.diff(p2, r1, r2) end diff --git a/test/unit/mailer_test.rb b/test/unit/mailer_test.rb index 596a1c5f..411e744b 100644 --- a/test/unit/mailer_test.rb +++ b/test/unit/mailer_test.rb @@ -179,7 +179,7 @@ class MailerTest < ActiveSupport::TestCase assert_nil mail.references assert_select_email do # link to the message - assert_select "a[href=?]", "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.id}", :text => message.subject + assert_select "a[href*=?]", "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.id}", :text => message.subject end end @@ -316,7 +316,7 @@ class MailerTest < ActiveSupport::TestCase 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 @@ -326,7 +326,7 @@ class MailerTest < ActiveSupport::TestCase 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 diff --git a/test/unit/message_test.rb b/test/unit/message_test.rb index 9f143825..6203b6a1 100644 --- a/test/unit/message_test.rb +++ b/test/unit/message_test.rb @@ -16,6 +16,7 @@ class MessageTest < ActiveSupport::TestCase fixtures :projects, :roles, :members, :member_roles, :boards, :messages, :users, :watchers def setup + Setting.notified_events = ['message_posted'] @board = Board.find(1) @user = User.find(1) end @@ -138,4 +139,12 @@ class MessageTest < ActiveSupport::TestCase message.sticky = '1' assert_equal 1, message.sticky end + + test "email notifications for creating a message" do + assert_difference("ActionMailer::Base.deliveries.count") do + message = Message.new(:board => @board, :subject => 'Test message', :content => 'Test message content', :author => @user) + assert message.save + end + + end end diff --git a/test/unit/wiki_content_test.rb b/test/unit/wiki_content_test.rb index 39c48747..0cdf2718 100644 --- a/test/unit/wiki_content_test.rb +++ b/test/unit/wiki_content_test.rb @@ -80,4 +80,11 @@ class WikiContentTest < ActiveSupport::TestCase page.reload assert_equal 500.kilobyte, page.content.text.size end + + test "new WikiContent is version 0" do + page = WikiPage.new(:wiki => @wiki, :title => "Page") + page.content = WikiContent.new(:text => "Content text", :author => User.find(1), :comments => "My comment") + + assert_equal 0, page.content.version + end end diff --git a/vendor/plugins/acts_as_journalized/lib/acts_as_journalized.rb b/vendor/plugins/acts_as_journalized/lib/acts_as_journalized.rb index 024f6dfe..2a6a9a5c 100644 --- a/vendor/plugins/acts_as_journalized/lib/acts_as_journalized.rb +++ b/vendor/plugins/acts_as_journalized/lib/acts_as_journalized.rb @@ -19,7 +19,7 @@ Dir[File.expand_path("../redmine/acts/journalized/*.rb", __FILE__)].each{|f| require f } -require_dependency File.expand_path('lib/ar_condition', Rails.root) +require "ar_condition" module Redmine module Acts @@ -140,7 +140,7 @@ module Redmine h[:find_options] ||= {} # in case it is nil h[:find_options] = {}.tap do |opts| - cond = ARCondition.new + cond = ::ARCondition.new cond.add(["#{journal_class.table_name}.activity_type = ?", h[:type]]) cond.add(h[:find_options][:conditions]) if h[:find_options][:conditions] opts[:conditions] = cond.conditions diff --git a/vendor/plugins/acts_as_journalized/lib/journal_detail.rb b/vendor/plugins/acts_as_journalized/lib/journal_detail.rb new file mode 100644 index 00000000..d924524f --- /dev/null +++ b/vendor/plugins/acts_as_journalized/lib/journal_detail.rb @@ -0,0 +1,9 @@ +class JournalDetail + attr_reader :prop_key, :value, :old_value + + def initialize(prop_key, old_value, value) + @prop_key = prop_key + @old_value = old_value + @value = value + end +end diff --git a/vendor/plugins/acts_as_journalized/lib/journal_formatter.rb b/vendor/plugins/acts_as_journalized/lib/journal_formatter.rb index 10246185..02953e3b 100644 --- a/vendor/plugins/acts_as_journalized/lib/journal_formatter.rb +++ b/vendor/plugins/acts_as_journalized/lib/journal_formatter.rb @@ -27,8 +27,18 @@ module JournalFormatter include CustomFieldsHelper include ActionView::Helpers::TagHelper include ActionView::Helpers::UrlHelper + include ActionController::UrlWriter extend Redmine::I18n + def self.included(base) + base.class_eval do + # Required to use any link_to in the formatters + def self.default_url_options + {:only_path => true } + end + end + end + def self.register(hash) if hash[:class] klazz = hash.delete(:class) @@ -90,9 +100,7 @@ module JournalFormatter def format_html_attachment_detail(key, value) if !value.blank? && a = Attachment.find_by_id(key.to_i) - # Link to the attachment if it has not been removed - # FIXME: this is broken => link_to_attachment(a) - a.filename + link_to_attachment(a) else content_tag("i", h(value)) if value.present? end @@ -163,7 +171,7 @@ module JournalFormatter end label, old_value, value = attr_detail || cv_detail || attachment_detail - Redmine::Hook.call_hook :helper_issues_show_detail_after_setting, {:detail => detail, + Redmine::Hook.call_hook :helper_issues_show_detail_after_setting, {:detail => JournalDetail.new(label, old_value, value), :label => label, :value => value, :old_value => old_value } return nil unless label || old_value || value # print nothing if there are no values label, old_value, value = [label, old_value, value].collect(&:to_s) diff --git a/vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/creation.rb b/vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/creation.rb index 3a5493bc..d24cec55 100644 --- a/vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/creation.rb +++ b/vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/creation.rb @@ -68,6 +68,48 @@ module Redmine::Acts::Journalized # Instance methods that determine whether to save a journal and actually perform the save. module InstanceMethods + # Recreates the initial journal used to track the beginning state + # of the object. Useful for objects that didn't have an initial journal + # created (e.g. legacy data) + def recreate_initial_journal! + new_journal = journals.find_by_version(1) + new_journal ||= journals.build + # Mock up a list of changes for the creation journal based on Class defaults + new_attributes = self.class.new.attributes.except(self.class.primary_key, + self.class.inheritance_column, + :updated_on, + :updated_at, + :lock_version, + :lft, + :rgt) + creation_changes = {} + new_attributes.each do |name, default_value| + # Set changes based on the initial value to current. Can't get creation value without + # rebuiling the object history + creation_changes[name] = [default_value, self.send(name)] # [initial_value, creation_value] + end + new_journal.changes = creation_changes + new_journal.version = 1 + new_journal.activity_type = self.class.send(:journalized_activity_hash, {})[:type] + + if respond_to?(:author) + new_journal.user = author + elsif respond_to?(:user) + new_journal.user = user + end + + new_journal.save! + new_journal.reload + + # Backdate journal + if respond_to?(:created_at) + new_journal.update_attribute(:created_at, created_at) + elsif respond_to?(:created_on) + new_journal.update_attribute(:created_at, created_on) + end + new_journal + end + private # Returns whether a new journal should be created upon updating the parent record. # A new journal will be created if @@ -120,7 +162,8 @@ module Redmine::Acts::Journalized def journal_attributes attributes = { :journaled_id => self.id, :activity_type => activity_type, :changes => journal_changes, :version => last_version + 1, - :notes => journal_notes, :user_id => (journal_user.try(:id) || User.current.try(:id)) } + :notes => journal_notes, :user_id => (journal_user.try(:id) || User.current.try(:id)) + }.merge(extra_journal_attributes || {}) end end end diff --git a/vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/save_hooks.rb b/vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/save_hooks.rb index 61e13986..fdeb60cc 100644 --- a/vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/save_hooks.rb +++ b/vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/save_hooks.rb @@ -28,7 +28,7 @@ module Redmine::Acts::Journalized before_save :init_journal after_save :reset_instance_variables - attr_reader :journal_notes, :journal_user + attr_accessor :journal_notes, :journal_user, :extra_journal_attributes end end
<%=l(:field_status)%>:<%= @issue.status.name %><%=l(:field_status)%>:<%= h(@issue.status.name) %> <%=l(:field_start_date)%>:<%= format_date(@issue.start_date) %>
<%=l(:field_priority)%>:<%= @issue.priority.name %><%=l(:field_priority)%>:<%= h(@issue.priority.name) %> <%=l(:field_due_date)%>:<%= format_date(@issue.due_date) %>
<%=l(:field_done_ratio)%>:<%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %>
<%=l(:field_category)%>:<%=h @issue.category ? @issue.category.name : "-" %><%=l(:field_category)%>:<%=h(@issue.category ? @issue.category.name : "-") %><%=l(:label_spent_time)%>: <%= @issue.spent_hours > 0 ? (link_to l_hours(@issue.spent_hours), {:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue}) : "-" %><%= link_to_revision(changeset, @project) if changeset %> <%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %> <%= changeset.nil? ? h(replace_invalid_utf8(entry.lastrev.author.to_s.split('<').first)) : changeset.author if entry.lastrev %><%=h truncate(changeset.comments, :length => 50) unless changeset.nil? %><%=h truncate(Changeset.to_utf8(changeset.comments, changeset.repository.repo_log_encoding), :length => 50) unless changeset.nil? %>
<%= radio_button_tag('rev_to', changeset.identifier, (line_num==2), :id => "cbto-#{line_num}", :onclick => "if ($('cb-#{line_num}').checked==true) {$('cb-#{line_num-1}').checked=true;}") if show_diff && (line_num > 1) %> <%= format_time(changeset.committed_on) %> <%=h changeset.author %><%= textilizable(truncate_at_line_break(changeset.comments)) %><%= textilizable(truncate_at_line_break(Changeset.to_utf8(changeset.comments, changeset.repository.repo_log_encoding))) %>