diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index e9d060870..6e747ecda 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -35,7 +35,7 @@ class WikiController < ApplicationController default_search_scope :wiki_pages before_filter :find_wiki, :authorize before_filter :find_existing_or_new_page, :only => [:show, :edit, :update] - before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy] + before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy, :destroy_version] helper :attachments include AttachmentsHelper @@ -237,6 +237,14 @@ class WikiController < ApplicationController redirect_to :action => 'index', :project_id => @project end + def destroy_version + return render_403 unless editable? + + @content = @page.content_for_version(params[:version]) + @content.destroy + redirect_to_referer_or :action => 'history', :id => @page.title, :project_id => @project + end + # Export wiki to a single pdf or html file def export @pages = @wiki.pages.all(:order => 'title', :include => [:content, :attachments], :limit => 75) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index e3126a358..048b7ae6f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -147,6 +147,10 @@ module ApplicationHelper end end + def wiki_page_path(page, options={}) + url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options)) + end + def thumbnail_tag(attachment) link_to image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment)), {:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename}, diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb index ec1cd8234..7c5704902 100644 --- a/app/models/wiki_content.rb +++ b/app/models/wiki_content.rb @@ -73,6 +73,8 @@ class WikiContent < ActiveRecord::Base "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " + "LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id"} + after_destroy :page_update_after_destroy + def text=(plain) case Setting.wiki_compression when 'gzip' @@ -128,5 +130,18 @@ class WikiContent < ActiveRecord::Base includes(:author). where("wiki_content_id = ? AND version > ?", wiki_content_id, version).first end + + private + + # Updates page's content if the latest version is removed + # or destroys the page if it was the only version + def page_update_after_destroy + latest = page.content.versions.reorder("#{self.class.table_name}.version DESC").first + if latest && page.content.version != latest.version + raise ActiveRecord::Rollback unless page.content.revert_to!(latest) + elsif latest.nil? + raise ActiveRecord::Rollback unless page.destroy + end + end end end diff --git a/app/views/wiki/history.html.erb b/app/views/wiki/history.html.erb index 005219e04..b03af266a 100644 --- a/app/views/wiki/history.html.erb +++ b/app/views/wiki/history.html.erb @@ -28,7 +28,10 @@ <%= format_time(ver.updated_on) %> <%= link_to_user ver.author %> <%=h ver.comments %> - <%= link_to l(:button_annotate), :action => 'annotate', :id => @page.title, :version => ver.version %> + + <%= link_to l(:button_annotate), :action => 'annotate', :id => @page.title, :version => ver.version %> + <%= delete_link wiki_page_path(@page, :version => ver.version) if User.current.allowed_to?(:delete_wiki_pages, @page.project) && @version_count > 1 %> + <% line_num += 1 %> <% end %> diff --git a/config/routes.rb b/config/routes.rb index b78cb3dff..ca9d77590 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -160,6 +160,7 @@ RedmineApp::Application.routes.draw do end match 'wiki', :controller => 'wiki', :action => 'show', :via => :get get 'wiki/:id/:version', :to => 'wiki#show' + delete 'wiki/:id/:version', :to => 'wiki#destroy_version' get 'wiki/:id/:version/annotate', :to => 'wiki#annotate' get 'wiki/:id/:version/diff', :to => 'wiki#diff' end diff --git a/lib/plugins/acts_as_versioned/lib/acts_as_versioned.rb b/lib/plugins/acts_as_versioned/lib/acts_as_versioned.rb index 5ac65e35a..7510462bd 100644 --- a/lib/plugins/acts_as_versioned/lib/acts_as_versioned.rb +++ b/lib/plugins/acts_as_versioned/lib/acts_as_versioned.rb @@ -374,7 +374,7 @@ module ActiveRecord #:nodoc: # Clones a model. Used when saving a new version or reverting a model's version. def clone_versioned_model(orig_model, new_model) self.versioned_attributes.each do |key| - new_model.send("#{key}=", orig_model.send(key)) if orig_model.has_attribute?(key) + new_model.send("#{key}=", orig_model.send(key)) if orig_model.respond_to?(key) end if self.class.columns_hash.include?(self.class.inheritance_column) diff --git a/lib/redmine.rb b/lib/redmine.rb index e120aa802..3901b8e5d 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -118,7 +118,7 @@ Redmine::AccessControl.map do |map| map.project_module :wiki do |map| map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member - map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member + map.permission :delete_wiki_pages, {:wiki => [:destroy, :destroy_version]}, :require => :member map.permission :view_wiki_pages, {:wiki => [:index, :show, :special, :date_index]}, :read => true map.permission :export_wiki_pages, {:wiki => [:export]}, :read => true map.permission :view_wiki_edits, {:wiki => [:history, :diff, :annotate]}, :read => true diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index abfb33f11..720a9b6f8 100644 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -499,6 +499,7 @@ class WikiControllerTest < ActionController::TestCase end def test_history + @request.session[:user_id] = 2 get :history, :project_id => 'ecookbook', :id => 'CookBook_documentation' assert_response :success assert_template 'history' @@ -508,17 +509,24 @@ class WikiControllerTest < ActionController::TestCase assert_select "input[type=submit][name=commit]" assert_select 'td' do assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2', :text => '2' - assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2/annotate' + assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2/annotate', :text => 'Annotate' + assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2', :text => 'Delete' end end def test_history_with_one_version - get :history, :project_id => 1, :id => 'Another_page' + @request.session[:user_id] = 2 + get :history, :project_id => 'ecookbook', :id => 'Another_page' assert_response :success assert_template 'history' assert_not_nil assigns(:versions) assert_equal 1, assigns(:versions).size assert_select "input[type=submit][name=commit]", false + assert_select 'td' do + assert_select 'a[href=?]', '/projects/ecookbook/wiki/Another_page/1', :text => '1' + assert_select 'a[href=?]', '/projects/ecookbook/wiki/Another_page/1/annotate', :text => 'Annotate' + assert_select 'a[href=?]', '/projects/ecookbook/wiki/Another_page/1', :text => 'Delete', :count => 0 + end end def test_diff @@ -681,6 +689,18 @@ class WikiControllerTest < ActionController::TestCase assert_equal WikiPage.find(1), WikiPage.find_by_id(5).parent end + def test_destroy_version + @request.session[:user_id] = 2 + assert_difference 'WikiContent::Version.count', -1 do + assert_no_difference 'WikiContent.count' do + assert_no_difference 'WikiPage.count' do + delete :destroy_version, :project_id => 'ecookbook', :id => 'CookBook_documentation', :version => 2 + assert_redirected_to '/projects/ecookbook/wiki/CookBook_documentation/history' + end + end + end + end + def test_index get :index, :project_id => 'ecookbook' assert_response :success diff --git a/test/integration/routing/wiki_test.rb b/test/integration/routing/wiki_test.rb index 2afc29eea..e7f6fa0f7 100644 --- a/test/integration/routing/wiki_test.rb +++ b/test/integration/routing/wiki_test.rb @@ -38,6 +38,11 @@ class RoutingWikiTest < ActionController::IntegrationTest { :controller => 'wiki', :action => 'diff', :project_id => '1', :id => 'CookBook_documentation' } ) + assert_routing( + { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/2" }, + { :controller => 'wiki', :action => 'show', :project_id => '1', + :id => 'CookBook_documentation', :version => '2' } + ) assert_routing( { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/2/diff" }, { :controller => 'wiki', :action => 'diff', :project_id => '1', @@ -117,5 +122,10 @@ class RoutingWikiTest < ActionController::IntegrationTest { :controller => 'wiki', :action => 'destroy', :project_id => '22', :id => 'ladida' } ) + assert_routing( + { :method => 'delete', :path => "/projects/22/wiki/ladida/3" }, + { :controller => 'wiki', :action => 'destroy_version', :project_id => '22', + :id => 'ladida', :version => '3' } + ) end end diff --git a/test/unit/wiki_content_version_test.rb b/test/unit/wiki_content_version_test.rb new file mode 100644 index 000000000..fa7ba82c7 --- /dev/null +++ b/test/unit/wiki_content_version_test.rb @@ -0,0 +1,68 @@ +# Redmine - project management software +# Copyright (C) 2006-2012 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.expand_path('../../test_helper', __FILE__) + +class WikiContentTest < ActiveSupport::TestCase + fixtures :projects, :users, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions + + def setup + end + + def test_destroy + v = WikiContent::Version.find(2) + + assert_difference 'WikiContent::Version.count', -1 do + v.destroy + end + end + + def test_destroy_last_version_should_revert_content + v = WikiContent::Version.find(3) + + assert_no_difference 'WikiPage.count' do + assert_no_difference 'WikiContent.count' do + assert_difference 'WikiContent::Version.count', -1 do + assert v.destroy + end + end + end + c = WikiContent.find(1) + v = c.versions.last + assert_equal 2, c.version + assert_equal v.version, c.version + assert_equal v.comments, c.comments + assert_equal v.text, c.text + assert_equal v.author, c.author + assert_equal v.updated_on, c.updated_on + end + + def test_destroy_all_versions_should_delete_page + WikiContent::Version.find(1).destroy + WikiContent::Version.find(2).destroy + v = WikiContent::Version.find(3) + + assert_difference 'WikiPage.count', -1 do + assert_difference 'WikiContent.count', -1 do + assert_difference 'WikiContent::Version.count', -1 do + assert v.destroy + end + end + end + assert_nil WikiPage.find_by_id(1) + end +end