diff -Nur redmine-1.1.0/app/controllers/journals_controller.rb redmine-1.1.0-rollback/app/controllers/journals_controller.rb
--- redmine-1.1.0/app/controllers/journals_controller.rb 2011-01-09 17:05:00.000000000 -0700
+++ redmine-1.1.0-rollback/app/controllers/journals_controller.rb 2011-02-11 17:15:13.829487200 -0700
@@ -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]
+ before_filter :find_journal, :only => [:edit, :rollback]
before_filter :find_issue, :only => [:new]
before_filter :find_optional_project, :only => [:index]
- before_filter :authorize, :only => [:new, :edit]
+ before_filter :authorize, :only => [:new, :edit, :rollback]
accept_key_auth :index
helper :issues
@@ -67,6 +67,8 @@
end
def edit
+ (render_403; return false) unless @journal.editable_by?(User.current)
+
if request.post?
@journal.update_attributes(:notes => params[:notes]) if params[:notes]
@journal.destroy if @journal.details.empty? && @journal.notes.blank?
@@ -77,11 +79,27 @@
end
end
end
+
+ 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] = "
" + @journal.errors.full_messages.map {|msg| "- " + ERB::Util.html_escape(msg) + "
"}.join + "
"
+ 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])
- (render_403; return false) unless @journal.editable_by?(User.current)
@project = @journal.journalized.project
rescue ActiveRecord::RecordNotFound
render_404
diff -Nur redmine-1.1.0/app/models/issue.rb redmine-1.1.0-rollback/app/models/issue.rb
--- redmine-1.1.0/app/models/issue.rb 2011-02-11 12:52:05.752402400 -0700
+++ redmine-1.1.0-rollback/app/models/issue.rb 2011-02-11 17:17:51.065874800 -0700
@@ -182,6 +182,11 @@
end
issue
end
+
+ # parent_id is never set directly, see :after_save
+ def parent_id=(id)
+ @parent_issue = Issue.find_by_id(id)
+ end
def status_id=(sid)
self.status = nil
@@ -358,6 +363,33 @@
@current_journal
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 != journals.last
+
+ # 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
+ journal.destroy if save(false)
+ end
+
# Return true if the issue is closed, otherwise false
def closed?
self.status.is_closed?
@@ -785,6 +817,7 @@
# Callback on attachment deletion
def attachment_removed(obj)
+ return if @rolling_back
journal = init_journal(User.current)
journal.details << JournalDetail.new(:property => 'attachment',
:prop_key => obj.id,
diff -Nur redmine-1.1.0/app/models/journal.rb redmine-1.1.0-rollback/app/models/journal.rb
--- redmine-1.1.0/app/models/journal.rb 2011-01-09 17:05:00.000000000 -0700
+++ redmine-1.1.0-rollback/app/models/journal.rb 2011-02-11 17:10:38.544713600 -0700
@@ -58,6 +58,20 @@
usr && usr.logged? && (usr.allowed_to?(:edit_issue_notes, project) || (self.user == usr && usr.allowed_to?(:edit_own_issue_notes, project)))
end
+ def can_rollback?(usr = nil)
+ user = usr || User.current
+ editable_by?(user) && (details.empty? || user.allowed_to?(:rollback_issue_notes, project))
+ 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 -Nur redmine-1.1.0/app/views/issues/_history.rhtml redmine-1.1.0-rollback/app/views/issues/_history.rhtml
--- redmine-1.1.0/app/views/issues/_history.rhtml 2011-02-11 12:52:05.752402400 -0700
+++ redmine-1.1.0-rollback/app/views/issues/_history.rhtml 2011-02-11 17:10:38.560338800 -0700
@@ -2,6 +2,9 @@
<% for journal in journals %>
<%= link_to "##{journal.indice}", :anchor => "note-#{journal.indice}" %>
+ <% if journal == journals.last && 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(:label_rollback)) %>
+ <% end %>
<%= avatar(journal.user, :size => "24") %>
<%= content_tag('a', '', :name => "note-#{journal.indice}")%>
<%= authoring journal.created_on, journal.user, :label => :label_updated_time_by %>
diff -Nur redmine-1.1.0/config/locales/en.yml redmine-1.1.0-rollback/config/locales/en.yml
--- redmine-1.1.0/config/locales/en.yml 2011-02-11 12:52:05.752402400 -0700
+++ redmine-1.1.0-rollback/config/locales/en.yml 2011-02-11 17:10:38.560338800 -0700
@@ -378,6 +378,7 @@
permission_add_issue_notes: Add notes
permission_edit_issue_notes: Edit notes
permission_edit_own_issue_notes: Edit own notes
+ permission_rollback_issue_notes: Rollback issue notes
permission_move_issues: Move issues
permission_delete_issues: Delete issues
permission_manage_public_queries: Manage public queries
@@ -793,6 +794,7 @@
label_project_copy_notifications: Send email notifications during the project copy
label_principal_search: "Search for user or group:"
label_user_search: "Search for user:"
+ label_rollback: Rollback
button_login: Login
button_submit: Submit
diff -Nur redmine-1.1.0/lib/redmine.rb redmine-1.1.0-rollback/lib/redmine.rb
--- redmine-1.1.0/lib/redmine.rb 2011-02-11 12:52:05.768027600 -0700
+++ redmine-1.1.0-rollback/lib/redmine.rb 2011-02-11 17:10:38.575964000 -0700
@@ -72,6 +72,7 @@
map.permission :add_issue_notes, {:issues => [:edit, :update], :journals => [:new]}
map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
+ map.permission :rollback_issue_notes, {:journals => [:rollback]}
map.permission :move_issues, {:issue_moves => [:new, :create]}, :require => :loggedin
map.permission :delete_issues, {:issues => :destroy}, :require => :member
# Queries
diff -Nur redmine-1.1.0/public/stylesheets/application.css redmine-1.1.0-rollback/public/stylesheets/application.css
--- redmine-1.1.0/public/stylesheets/application.css 2011-02-11 12:52:05.768027600 -0700
+++ redmine-1.1.0-rollback/public/stylesheets/application.css 2011-02-11 15:59:42.537112400 -0700
@@ -938,6 +938,10 @@
float: right;
}
+.journal-rollback {
+ float: right;
+}
+
h2 img { vertical-align:middle; }
.hascontextmenu { cursor: context-menu; }