diff -rupN redmine-2.1.0/app/controllers/journals_controller.rb redmine-2.1.0-modified/app/controllers/journals_controller.rb --- redmine-2.1.0/app/controllers/journals_controller.rb 2012-09-16 06:54:12.000000000 -0600 +++ redmine-2.1.0-modified/app/controllers/journals_controller.rb 2012-10-04 13:06:21.013138052 -0600 @@ -16,10 +16,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class JournalsController < ApplicationController - before_filter :find_journal, :only => [:edit, :diff] + before_filter :find_journal, :only => [:edit, :diff, :rollback] before_filter :find_issue, :only => [:new] before_filter :find_optional_project, :only => [:index] - before_filter :authorize, :only => [:new, :edit, :diff] + before_filter :authorize, :only => [:new, :edit, :diff, :rollback] accept_rss_auth :index menu_item :issues @@ -92,7 +92,24 @@ class JournalsController < ApplicationCo end end - private + def rollback + (render_403; return false) unless @journal.can_rollback?(User.current) + + if @journal.rollback + flash[:notice] = l(:notice_successful_update) + else + # can't seem to bring in the helper method 'error_messages_for' + # and injecting it into show.rhtml doesn't seem to work, since + # the @issue loses the errors on redirect (due to issue reload) + flash[:error] = "" + end + respond_to do |format| + format.html { redirect_to :controller => 'issues', :action => 'show', :id => @journal.journalized_id } + format.api { render_validation_errors(@issue) } + end + end + +private def find_journal @journal = Journal.find(params[:id]) diff -rupN redmine-2.1.0/app/models/issue.rb redmine-2.1.0-modified/app/models/issue.rb --- redmine-2.1.0/app/models/issue.rb 2012-09-16 06:54:11.000000000 -0600 +++ redmine-2.1.0-modified/app/models/issue.rb 2012-10-05 10:00:05.257588764 -0600 @@ -597,6 +597,36 @@ class Issue < ActiveRecord::Base scope end + # rollback the changes in a journal, the journal is destroyed on success + def rollback(journal) + # only allow rollback of journal details for the last journal + (errors.add_to_base(l(:notice_locking_conflict)); return) if !journal.last_valid_journal?(journals) + + # avoid the creation of journals during rollback (see 'attachment removed') + @rolling_back = true + + # roll back each change detailed by the journal + journal.details.each do |d| + case d.property + # issue attribute change + when 'attr'; send "#{d.prop_key}=", d.old_value + # rollback custom field change + when 'cf'; custom_field_values.each {|v| v.value = d.old_value if v.custom_field_id == d.prop_key.to_i} + # remove any added attachments (we can't recover removed attachments) + when 'attachment'; attachments.each {|v| attachments.delete(v) if v.id == d.prop_key.to_i} + end + end + + # allow the creation of journals again + remove_instance_variable(:@rolling_back) + + # destroy the journal once we save the issue changes + if save(:validate => false) + journal.rolled_back = true + journal.save + end + end + # Return true if the issue is closed, otherwise false def closed? self.status.is_closed? diff -rupN redmine-2.1.0/app/models/journal.rb redmine-2.1.0-modified/app/models/journal.rb --- redmine-2.1.0/app/models/journal.rb 2012-09-16 06:54:11.000000000 -0600 +++ redmine-2.1.0-modified/app/models/journal.rb 2012-10-05 10:47:26.589561668 -0600 @@ -62,6 +62,27 @@ class Journal < ActiveRecord::Base usr && usr.logged? && (usr.allowed_to?(:edit_issue_notes, project) || (self.user == usr && usr.allowed_to?(:edit_own_issue_notes, project))) end + def last_valid_journal?(journals) + self == journals.last + end + + def can_rollback?(usr = nil) + user ||= User.current + editable_by?(user) && (details.empty? || user.allowed_to?(:rollback_issue_notes, project)) + end + + def show? + !self.rolled_back || User.current.pref.hide_rolled_back_issue_notes == '0' + end + + # rollback the changes in a journal, the journal is destroyed on success + def rollback + # we could have details to rollback, so let the issue take care of this more complicated task + journalized.rollback(self) || + # on failure, collect the error messages from the issue on failure + (journalized.errors.each_full {|msg| errors.add_to_base(msg)}; false) + end + def project journalized.respond_to?(:project) ? journalized.project : nil end diff -rupN redmine-2.1.0/app/models/user_preference.rb redmine-2.1.0-modified/app/models/user_preference.rb --- redmine-2.1.0/app/models/user_preference.rb 2012-09-16 06:54:11.000000000 -0600 +++ redmine-2.1.0-modified/app/models/user_preference.rb 2012-10-05 10:48:30.269561061 -0600 @@ -56,4 +56,7 @@ class UserPreference < ActiveRecord::Bas def warn_on_leaving_unsaved; self[:warn_on_leaving_unsaved] || '1'; end def warn_on_leaving_unsaved=(value); self[:warn_on_leaving_unsaved]=value; end + + def hide_rolled_back_issue_notes; self[:hide_rolled_back_issue_notes] || '0'; end + def hide_rolled_back_issue_notes=(value); self[:hide_rolled_back_issue_notes]=value; end end diff -rupN redmine-2.1.0/app/views/issues/_history.html.erb redmine-2.1.0-modified/app/views/issues/_history.html.erb --- redmine-2.1.0/app/views/issues/_history.html.erb 2012-09-16 06:54:13.000000000 -0600 +++ redmine-2.1.0-modified/app/views/issues/_history.html.erb 2012-10-05 10:51:24.237559399 -0600 @@ -1,8 +1,17 @@ <% reply_links = authorize_for('issues', 'edit') -%> <% for journal in journals %> + <% if journal.show? %> + + <% if journal.rolled_back? %> + + <% end %> +

<%= link_to "##{journal.indice}", {:anchor => "note-#{journal.indice}"}, :class => "journal-link" %> + <% if journal.last_valid_journal?(journals) && journal.can_rollback? %> +
<%= link_to(image_tag('cancel.png'), {:controller => 'journals', :action => 'rollback', :id => journal}, :confirm => l(:text_are_you_sure), :method => :post, :title => l(:button_rollback_journal)) %> 
+ <% end %> <%= avatar(journal.user, :size => "24") %> <%= authoring journal.created_on, journal.user, :label => :label_updated_time_by %>

@@ -17,6 +26,12 @@
<%= call_hook(:view_issues_history_journal_bottom, { :journal => journal }) %> + + <% if journal.rolled_back? %> +
+ <% end %> + +<% end %> <% end %> <% heads_for_wiki_formatter if User.current.allowed_to?(:edit_issue_notes, issue.project) || User.current.allowed_to?(:edit_own_issue_notes, issue.project) %> diff -rupN redmine-2.1.0/app/views/users/_preferences.html.erb redmine-2.1.0-modified/app/views/users/_preferences.html.erb --- redmine-2.1.0/app/views/users/_preferences.html.erb 2012-09-16 06:54:12.000000000 -0600 +++ redmine-2.1.0-modified/app/views/users/_preferences.html.erb 2012-10-05 10:40:22.869565707 -0600 @@ -3,4 +3,5 @@

<%= pref_fields.time_zone_select :time_zone, nil, :include_blank => true %>

<%= pref_fields.select :comments_sorting, [[l(:label_chronological_order), 'asc'], [l(:label_reverse_chronological_order), 'desc']] %>

<%= pref_fields.check_box :warn_on_leaving_unsaved %>

+

<%= pref_fields.check_box :hide_rolled_back_issue_notes %>

<% end %> diff -rupN redmine-2.1.0/config/locales/en.yml redmine-2.1.0-modified/config/locales/en.yml --- redmine-2.1.0/config/locales/en.yml 2012-09-16 06:54:19.000000000 -0600 +++ redmine-2.1.0-modified/config/locales/en.yml 2012-10-05 10:57:21.185555995 -0600 @@ -331,6 +331,7 @@ en: field_core_fields: Standard fields field_timeout: "Timeout (in seconds)" field_board_parent: Parent forum + field_hide_rolled_back_issue_notes: "Hide rolled-back issue notes" setting_app_title: Application title setting_app_subtitle: Application subtitle @@ -457,6 +458,7 @@ en: permission_export_wiki_pages: Export wiki pages permission_manage_subtasks: Manage subtasks permission_manage_related_issues: Manage related issues + permission_rollback_issue_notes: Rollback issue notes project_module_issue_tracking: Issue tracking project_module_time_tracking: Time tracking @@ -923,6 +925,7 @@ en: button_delete_my_account: Delete my account button_close: Close button_reopen: Reopen + button_rollback: Rollback status_active: active status_registered: registered diff -rupN redmine-2.1.0/db/migrate/20130128120000_add_journals_rolled_back.rb redmine-2.1.0-modified/db/migrate/20130128120000_add_journals_rolled_back.rb --- redmine-2.1.0/db/migrate/20130128120000_add_journals_rolled_back.rb 1969-12-31 17:00:00.000000000 -0700 +++ redmine-2.1.0-modified/db/migrate/20130128120000_add_journals_rolled_back.rb 2013-01-28 12:19:17.517695320 -0700 @@ -0,0 +1,9 @@ +class AddJournalsRolledBack < ActiveRecord::Migration + def up + add_column :journals, :rolled_back, :boolean, :default => 0 + end + + def down + remove_column :journals, :rolled_back + end +end