diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 2669c60e6..0151066fa 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -36,6 +36,7 @@ class WikiController < ApplicationController before_action :find_wiki, :authorize before_action :find_existing_or_new_page, :only => [:show, :edit] before_action :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy, :destroy_version] + before_action :find_copy_from, :only => [:show, :update] before_action :find_attachments, :only => [:preview] accept_api_auth :index, :show, :update, :destroy @@ -63,7 +64,11 @@ class WikiController < ApplicationController end def new - @page = WikiPage.new(:wiki => @wiki, :title => params[:title]) + @page = if params[:copy_to_project_id] + WikiPage.new(:wiki => Project.find(params[:copy_to_project_id]).wiki, :title => (params[:title] || params[:copy_from])) + else + WikiPage.new(:wiki => @wiki, :title => (params[:title] || params[:copy_from])) + end unless User.current.allowed_to?(:edit_wiki_pages, @project) render_403 return @@ -72,12 +77,21 @@ class WikiController < ApplicationController @page.title = '' unless editable? @page.validate if @page.errors[:title].blank? - path = project_wiki_page_path(@project, @page.title, :parent => params[:parent]) + if params[:copy_from] + copy_to_project = Project.find(params[:copy_to_project_id]) + path = project_wiki_page_path(copy_to_project, @page.title, :parent => params[:parent], :copy_from => params[:copy_from], :copy_from_project_id => @project.id) + else + path = project_wiki_page_path(@project, @page.title, :parent => params[:parent]) + end respond_to do |format| format.html {redirect_to path} format.js {render :js => "window.location = #{path.to_json}"} end end + else + if params[:copy_from] + @page = @page.copy + end end end @@ -136,6 +150,9 @@ class WikiController < ApplicationController @content = @page.content_for_version(params[:version]) @content ||= WikiContent.new(:page => @page) + if @copy_from + @content.text = @copy_from.content.try(:text) + end @content.text = initial_page_content(@page) if @content.text.blank? # don't keep previous comment @content.comments = nil @@ -178,7 +195,7 @@ class WikiController < ApplicationController end @content.author = User.current - if @page.save_with_content(@content) + if @page.save_with_content(@content, :copy_from => @copy_from, :copy_attachments => params[:copy_attachments], :copy_child_pages => params[:copy_child_pages]) attachments = Attachment.attach_files(@page, params[:attachments] || (params[:wiki_page] && params[:wiki_page][:uploads])) render_attachment_warning_if_needed(@page) call_hook(:controller_wiki_edit_after_save, {:params => params, :page => @page}) @@ -373,6 +390,14 @@ class WikiController < ApplicationController end end + def find_copy_from + @copy_from = if params[:copy_from_project_id] && params[:copy_from] + Project.find(params[:copy_from_project_id]).wiki.try(:find_page, params[:copy_from]) rescue nil + else + nil + end + end + def redirect_to_page(page) if page.project && page.project.visible? redirect_to :action => action_name, :project_id => page.project, :id => page.title diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb index 0b5f3b8f4..730e9c773 100644 --- a/app/helpers/wiki_helper.rb +++ b/app/helpers/wiki_helper.rb @@ -46,6 +46,14 @@ module WikiHelper end end + def wiki_page_project_options_for_select(page) + projects = Project.allowed_to(:add_wiki_pages).joins(:wiki).preload(:wiki).to_a + projects.select! {|p| p.enabled_module_names.include?('wiki') } + projects << page.project unless projects.include?(page.project) + + project_tree_options_for_select(projects, :selected => page.project) + end + def wiki_page_breadcrumb(page) breadcrumb( page.ancestors.reverse.collect do |parent| diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index 0ee06673e..f33c787e4 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -243,13 +243,34 @@ class WikiPage < ActiveRecord::Base # Saves the page and its content if text was changed # Return true if the page was saved - def save_with_content(content) + def save_with_content(content, options={ :copy_from => nil, :copy_attachments => false, :copy_child_pages => false}) ret = nil transaction do ret = save if content.text_changed? begin self.content = content + copy_from, copy_attachments, copy_child_pages = options.values_at(:copy_from, :copy_attachments, :copy_child_pages) + if copy_from && copy_attachments + self.attachments = copy_from.attachments.collect do |attachment| + attachment.copy(:container => self) + end + end + if copy_from && copy_child_pages + self.children = copy_from.children.collect do |child_page| + page = child_page.copy(:parent => self) + unless page.save_with_content(child_page.content.try(:dup), + :copy_from => child_page, + :copy_attachments => copy_attachments, + :copy_child_pages => copy_child_pages) + page.errors.full_messages.each do |message| + self.errors.add :base, message + end + raise ActiveRecord::RecordNotSaved + end + page + end + end rescue ActiveRecord::RecordNotSaved ret = false end @@ -259,6 +280,28 @@ class WikiPage < ActiveRecord::Base ret end + def copy(options={ :parent => nil }) + wiki_page = self.class.new + wiki_page.parent = options[:parent] + wiki_page.wiki = options[:parent].try(:wiki) || self.wiki + wiki_page.title = self.title + wiki_page.content = self.content.try(:dup) + suffix = 1 + while wiki_page.validate == false && wiki_page.errors[:title].present? + wiki_page.title = self.title + "_#{suffix}" + suffix += 1 + end + wiki_page + end + + def has_attachments?(options={ :include_childrens => false }) + if self.children && options[:include_childrens] + self.attachments.any? || self.children.any? { |c| c.has_attachments?(options) } + else + self.attachments.any? + end + end + def deleted_attachment_ids Array(@deleted_attachment_ids).map(&:to_i) end diff --git a/app/views/wiki/_copy_modal.html.erb b/app/views/wiki/_copy_modal.html.erb new file mode 100644 index 000000000..a23a6c77e --- /dev/null +++ b/app/views/wiki/_copy_modal.html.erb @@ -0,0 +1,42 @@ +

<%=l(:label_wiki_page_copy)%>

+ +<%= labelled_form_for :page, @page, + :url => project_copy_wiki_page_path(@project), + :method => 'post', + :remote => true do |f| %> + + <%= render_error_messages @page.errors.full_messages_for(:title) %> + <%= hidden_field_tag 'copy_from_project_id', @project.id %> + <%= hidden_field_tag 'copy_from', params[:copy_from] %> + +
+

+ + <%= select_tag :copy_to_project_id, wiki_page_project_options_for_select(@page), :label => :label_project %>

+

+ <%= f.text_field :title, :name => 'title', :size => 60, :required => true %> + <%= l(:text_unallowed_characters) %>: , . / ? ; : | +

+ <% if params[:parent].present? %> +

+ +

+ <% end %> +
+

+ <%= submit_tag l(:label_next), :name => nil %> + <%= submit_tag l(:button_cancel), :name => nil, :onclick => "hideModal(this);", :type => 'button' %> +

+<% end %> +<%= javascript_tag do %> +$('#copy_to_project_id').change(function() { + if ($('#copy_from_project_id').val()==$(this).val()) { + $('#copy_parent_page').show(); + }else{ + $('#copy_parent_page').hide(); + } +}); +<% end %> diff --git a/app/views/wiki/edit.html.erb b/app/views/wiki/edit.html.erb index 7e9cf42af..c4d8d5d86 100644 --- a/app/views/wiki/edit.html.erb +++ b/app/views/wiki/edit.html.erb @@ -1,5 +1,7 @@ <%= wiki_page_breadcrumb(@page) %> +<%= error_messages_for 'page' %> +

<%= @page.pretty_title %>

<%= form_for @content, :as => :content, @@ -11,6 +13,8 @@ <%= hidden_field_tag 'section_hash', @section_hash %> <% end %> <%= error_messages_for 'content' %> +<%= hidden_field_tag 'copy_from_project_id', params[:copy_from_project_id] if params[:copy_from_project_id] %> +<%= hidden_field_tag 'copy_from', params[:copy_from] if params[:copy_from] %>
<%= text_area_tag 'content[text]', @text, :cols => 100, :rows => 25, :accesskey => accesskey(:edit), @@ -34,6 +38,18 @@ <% end %>

<%= f.text_field :comments, :size => 120, :maxlength => 1024 %>

+<% if @copy_from && @copy_from.has_attachments?(:include_childrens => true) %> +

+ + <%= check_box_tag 'copy_attachments', '1', true %> +

+<% end %> +<% if @copy_from && @copy_from.children.any? %> +

+ + <%= check_box_tag 'copy_child_pages', '1', true %> +

+<% end %>
<%=l(:label_attachment_plural)%> <% if @page.attachments.any? && @page.safe_attribute?('deleted_attachment_ids') %> diff --git a/app/views/wiki/new.js.erb b/app/views/wiki/new.js.erb index c12b35340..009e79cf8 100644 --- a/app/views/wiki/new.js.erb +++ b/app/views/wiki/new.js.erb @@ -1,2 +1,2 @@ -$('#ajax-modal').html('<%= escape_javascript(render :partial => 'wiki/new_modal') %>'); +$('#ajax-modal').html('<%= escape_javascript(render :partial => params['copy_from'] ? 'wiki/copy_modal' : 'wiki/new_modal') %>'); showModal('ajax-modal', '600px'); diff --git a/app/views/wiki/show.html.erb b/app/views/wiki/show.html.erb index 6a0245809..5405b04a5 100644 --- a/app/views/wiki/show.html.erb +++ b/app/views/wiki/show.html.erb @@ -23,6 +23,7 @@ <% if User.current.allowed_to?(:edit_wiki_pages, @project) %> <%= link_to l(:label_wiki_page_new), new_project_wiki_page_path(@project, :parent => @page.title), :remote => true, :class => 'icon icon-add' %> + <%= link_to l(:button_copy), project_copy_wiki_page_path(@project, :copy_from => @page.title, :parent => @page.parent.try(:title)), :remote => true, :class => 'icon icon-copy' %> <% end %> <% end %>
diff --git a/config/locales/en.yml b/config/locales/en.yml index 5a950a7ff..7673cbde5 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -857,6 +857,7 @@ en: label_wiki_page: Wiki page label_wiki_page_plural: Wiki pages label_wiki_page_new: New wiki page + label_wiki_page_copy: Copy wiki page label_index_by_title: Index by title label_index_by_date: Index by date label_current_version: Current version @@ -1022,6 +1023,7 @@ en: label_export_options: "%{export_format} export options" label_copy_attachments: Copy attachments label_copy_subtasks: Copy subtasks + label_copy_child_pages: Copy child pages label_item_position: "%{position} of %{count}" label_completed_versions: Completed versions label_search_for_watchers: Search for watchers to add diff --git a/config/locales/ja.yml b/config/locales/ja.yml index c16a48224..8a39aed30 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -1063,6 +1063,7 @@ ja: label_attribute_of_assigned_to: 担当者の %{name} label_attribute_of_fixed_version: 対象バージョンの %{name} label_copy_subtasks: 子チケットをコピー + label_copy_child_pages: 子ページをコピー label_copied_to: コピー先 label_copied_from: コピー元 label_any_issues_in_project: 次のプロジェクト内のチケット @@ -1199,6 +1200,7 @@ ja: mail_body_settings_updated: ! '下記の設定が変更されました:' field_remote_ip: IPアドレス label_wiki_page_new: 新しいWikiページ + label_wiki_page_copy: Wikiページのコピー label_relations: 関係 button_filter: フィルタ mail_body_password_updated: パスワードが変更されました。 diff --git a/config/routes.rb b/config/routes.rb index 00eadf679..e10b2a819 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -190,6 +190,7 @@ Rails.application.routes.draw do end match 'wiki/index', :controller => 'wiki', :action => 'index', :via => :get + match 'wiki/:copy_from/copy', :to => 'wiki#new', :as => 'copy_wiki_page', via: [:get, :post] resources :wiki, :except => [:index, :create], :as => 'wiki_page' do member do get 'rename' diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 3765ac867..2bdc9f98d 100644 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -1279,4 +1279,73 @@ class WikiControllerTest < Redmine::ControllerTest assert_response :success assert_select 'head>meta[name="robots"]', false end + + def test_get_new_with_copy_from + @request.session[:user_id] = 2 + + get :new, :params => {:project_id => 'ecookbook', :copy_from => 'CookBook_documentation'} + assert_response :success + assert_select 'input[name=?][value=?]', 'title', 'CookBook_documentation_1' + end + + def test_post_new_with_copy_from_should_redirect_to_edit_wity_copy_params + @request.session[:user_id] = 2 + + post :new, :params => {:project_id => 'ecookbook', :title => 'CookBook documentation 1', :copy_from => 'CookBook_documentation', :copy_to_project_id => 'ecookbook'} + assert_redirected_to '/projects/ecookbook/wiki/CookBook_documentation_1?copy_from=CookBook_documentation©_from_project_id=1' + end + + def test_show_with_copy_from_should_copy_content + @request.session[:user_id] = 2 + + get :show, :params => {:project_id => 1, :id => 'CookBook_documentation_1', :copy_from => 'CookBook_documentation', :copy_from_project_id => 1} + assert_response :success + assert_select 'textarea[name=?]', 'content[text]', :text => /h1. CookBook documentation/ + assert_select 'textarea[name=?]', 'content[text]', :text => /{{child_pages}}/ + assert_select 'textarea[name=?]', 'content[text]', :text => /Some updated \[\[documentation\]\] here with gzipped history/ + end + + def test_update_with_copy_from_and_copy_attachments_should_copy_attachments + @request.session[:user_id] = 2 + + assert_difference 'WikiPage.count', 1 do + assert_difference 'WikiContent.count', 1 do + assert_difference 'Attachment.count', 2 do + post :update, :params => { + :project_id => 1, + :id => 'CookBook_documentation_1', + :content => { + :comments => 'Copy Wiki Page', + :text => 'h1. CookBook documentation', + :varsion => 0 + }, + :copy_from => 'CookBook_documentation', + :copy_from_project_id => 1, + :copy_attachments => '1' + } + end + end + end + end + + def test_update_with_copy_from_and_copy_child_pages + @request.session[:user_id] = 2 + + assert_difference 'WikiPage.count', 4 do + assert_difference 'WikiContent.count', 4 do + post :update, :params => { + :project_id => 1, + :id => 'Another_page_1', + :content => { + :comments => 'Copy Wiki Page', + :text => 'h1. Another page', + :varsion => 0 + }, + :copy_from => 'Another_page', + :copy_from_project_id => 1, + :copy_child_pages => '1' + } + end + end + end end diff --git a/test/unit/wiki_page_test.rb b/test/unit/wiki_page_test.rb index 9e7633f08..a61b8f00d 100644 --- a/test/unit/wiki_page_test.rb +++ b/test/unit/wiki_page_test.rb @@ -20,7 +20,7 @@ require_relative '../test_helper' class WikiPageTest < ActiveSupport::TestCase - fixtures :projects, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions + fixtures :projects, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions, :users def setup User.current = nil @@ -207,4 +207,82 @@ class WikiPageTest < ActiveSupport::TestCase assert_equal 3, diff.content_to.version assert_equal 1, diff.content_from.version end + + def test_copy + page = WikiPage.create!(:wiki => @wiki, :title => 'CopyFrom') + page.content = WikiContent.new(:text => '#h1 Copy From', :comments => 'test') + + copy_page_1 = page.copy + assert_equal page.wiki, copy_page_1.wiki + assert_equal '#h1 Copy From', copy_page_1.content.text + assert_equal 'CopyFrom_1', copy_page_1.title + copy_page_1.save + + copy_page_2 = page.copy + assert_equal page.wiki, copy_page_2.wiki + assert_equal '#h1 Copy From', copy_page_2.content.text + assert_equal 'CopyFrom_2', copy_page_2.title + end + + def test_copy_spacify_parent + parent_page = WikiPage.create!(:wiki => @wiki, :title => 'CopyFrom Parent') + parent_page.content = WikiContent.new(:text => '#h1 Copy From Parent', :comments => 'test') + page = WikiPage.create!(:wiki => @wiki, :title => 'CopyFrom', :parent => parent_page) + page.content = WikiContent.new(:text => '#h1 Copy From', :comments => 'test') + assert_equal parent_page, page.parent + assert_equal 1, parent_page.children.count + + copy_page_1 = page.copy(:parent => parent_page) + assert_equal page.wiki, copy_page_1.wiki + assert_equal '#h1 Copy From', copy_page_1.content.text + assert_equal 'CopyFrom_1', copy_page_1.title + assert_equal parent_page, copy_page_1.parent + copy_page_1.save + assert_equal 2, parent_page.children.count + + copy_page_2 = page.copy(:parent => nil) + assert_equal page.wiki, copy_page_2.wiki + assert_equal '#h1 Copy From', copy_page_2.content.text + assert_equal 'CopyFrom_2', copy_page_2.title + assert_nil copy_page_2.parent + copy_page_2.save + assert_equal 2, parent_page.children.count + end + + def test_has_attachments + page = WikiPage.create!(:wiki => @wiki, :title => 'Parent') + child1 = WikiPage.create!(:wiki => @wiki, :title => 'Child1', :parent => page) + child11 = WikiPage.create!(:wiki => @wiki, :title => 'Child11', :parent => child1) + Attachment.generate!(:container => child11, :filename => 'test.txt') + page.reload + + assert_equal true, page.has_attachments?(:include_childrens => true) + assert_equal false, page.has_attachments?(:include_childrens => false) + assert_equal false, page.has_attachments? + assert_equal false, child1.has_attachments?(:include_childrens => true) + assert_equal false, child1.has_attachments?(:include_childrens => false) + assert_equal false, child1.has_attachments? + assert_equal true, child11.has_attachments?(:include_childrens => true) + assert_equal true, child11.has_attachments?(:include_childrens => false) + assert_equal true, child11.has_attachments? + end + + def test_save_with_content_with_copy_options + page = WikiPage.create!(:wiki => @wiki, :title => 'Parent') + page.content = WikiContent.new(:text => '#h1 Copy From Content', :comments => 'test') + page.save + child1 = WikiPage.create!(:wiki => @wiki, :title => 'Child1', :parent => page) + child1.content = WikiContent.new(:text => '#h1 Copy From Child Content', :comments => 'test') + Attachment.generate!(:container => child1, :filename => 'test.txt') + page.reload + + new_page = WikiPage.new(:wiki => @wiki, :title => 'Parent_1') + content = page.content.dup + content.text = '#h1 Copy to Content' + assert new_page.save_with_content(content, :copy_from => page, :copy_attachments => true, :copy_child_pages => true) + assert_equal '#h1 Copy to Content', new_page.content.text + assert_equal 1, new_page.children.count + assert_equal 'Child1_1', new_page.children.first.title + assert_equal 1, new_page.children.first.attachments.count + end end