diff options
author | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2007-09-09 17:05:38 +0000 |
---|---|---|
committer | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2007-09-09 17:05:38 +0000 |
commit | b4d9ca8875898500ca198bff501dfc7d1c28bd71 (patch) | |
tree | 8d9ebcf80cd56b427211b6b769514ab8d66f33a2 | |
parent | f6fe15716e4714c5f6d4d88149927b7dfa5c6fce (diff) | |
download | redmine-b4d9ca8875898500ca198bff501dfc7d1c28bd71.tar.gz redmine-b4d9ca8875898500ca198bff501dfc7d1c28bd71.zip |
Added the ability to rename wiki pages (specific permission required).
Existing links that point to the old page are preserved and automatically redirected to the new page (this behaviour can be disabled when renaming the page).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@720 e93f8b46-1217-0410-a6f0-8f06a7374b81
-rw-r--r-- | app/controllers/wiki_controller.rb | 12 | ||||
-rw-r--r-- | app/models/wiki.rb | 15 | ||||
-rw-r--r-- | app/models/wiki_page.rb | 30 | ||||
-rw-r--r-- | app/models/wiki_redirect.rb | 23 | ||||
-rw-r--r-- | app/views/wiki/rename.rhtml | 11 | ||||
-rw-r--r-- | app/views/wiki/show.rhtml | 5 | ||||
-rw-r--r-- | db/migrate/067_create_wiki_redirects.rb | 15 | ||||
-rw-r--r-- | lang/bg.yml | 2 | ||||
-rw-r--r-- | lang/de.yml | 2 | ||||
-rw-r--r-- | lang/en.yml | 2 | ||||
-rw-r--r-- | lang/es.yml | 2 | ||||
-rw-r--r-- | lang/fr.yml | 2 | ||||
-rw-r--r-- | lang/it.yml | 2 | ||||
-rw-r--r-- | lang/ja.yml | 2 | ||||
-rw-r--r-- | lang/nl.yml | 2 | ||||
-rw-r--r-- | lang/pt-br.yml | 2 | ||||
-rw-r--r-- | lang/pt.yml | 2 | ||||
-rw-r--r-- | lang/sv.yml | 2 | ||||
-rw-r--r-- | lang/zh.yml | 2 | ||||
-rw-r--r-- | lib/redmine.rb | 1 | ||||
-rw-r--r-- | test/unit/wiki_redirect_test.rb | 73 |
21 files changed, 201 insertions, 8 deletions
diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index e9212a1c7..95f792c5e 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -75,6 +75,18 @@ class WikiController < ApplicationController flash[:error] = l(:notice_locking_conflict) end + # rename a page + def rename + @page = @wiki.find_page(params[:page]) + @page.redirect_existing_links = true + # used to display the *original* title if some AR validation errors occur + @original_title = @page.pretty_title + if request.post? && @page.update_attributes(params[:wiki_page]) + flash[:notice] = l(:notice_successful_update) + redirect_to :action => 'index', :id => @project, :page => @page.title + end + end + # show page history def history @page = @wiki.find_page(params[:page]) diff --git a/app/models/wiki.rb b/app/models/wiki.rb index ed473c7c0..b6cd661ff 100644 --- a/app/models/wiki.rb +++ b/app/models/wiki.rb @@ -18,6 +18,7 @@ class Wiki < ActiveRecord::Base belongs_to :project has_many :pages, :class_name => 'WikiPage', :dependent => :destroy + has_many :redirects, :class_name => 'WikiRedirect', :dependent => :delete_all validates_presence_of :start_page validates_format_of :start_page, :with => /^[^,\.\/\?\;\|\:]*$/ @@ -25,14 +26,20 @@ class Wiki < ActiveRecord::Base # find the page with the given title # if page doesn't exist, return a new page def find_or_new_page(title) - title = Wiki.titleize(title || start_page) - find_page(title) || WikiPage.new(:wiki => self, :title => title) + find_page(title) || WikiPage.new(:wiki => self, :title => Wiki.titleize(title)) end # find the page with the given title - def find_page(title) + def find_page(title, options = {}) title = start_page if title.blank? - pages.find_by_title(Wiki.titleize(title)) + title = Wiki.titleize(title) + page = pages.find_by_title(title) + if !page && !(options[:with_redirect] == false) + # search for a redirect + redirect = redirects.find_by_title(title) + page = find_page(redirect.redirects_to, :with_redirect => false) if redirect + end + page end # turn a string into a valid page title diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index 074d36daa..1ef8b7db4 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -22,13 +22,39 @@ class WikiPage < ActiveRecord::Base has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy has_many :attachments, :as => :container, :dependent => :destroy + attr_accessor :redirect_existing_links + validates_presence_of :title validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/ validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false validates_associated :content - + + def title=(value) + value = Wiki.titleize(value) + @previous_title = read_attribute(:title) if @previous_title.blank? + write_attribute(:title, value) + end + def before_save - self.title = Wiki.titleize(title) + self.title = Wiki.titleize(title) + # Manage redirects if the title has changed + if !@previous_title.blank? && (@previous_title != title) && !new_record? + # Update redirects that point to the old title + wiki.redirects.find_all_by_redirects_to(@previous_title).each do |r| + r.redirects_to = title + r.title == r.redirects_to ? r.destroy : r.save + end + # Remove redirects for the new title + wiki.redirects.find_all_by_title(title).each(&:destroy) + # Create a redirect to the new title + wiki.redirects << WikiRedirect.new(:title => @previous_title, :redirects_to => title) unless redirect_existing_links == "0" + @previous_title = nil + end + end + + def before_destroy + # Remove redirects to this page + wiki.redirects.find_all_by_redirects_to(title).each(&:destroy) end def pretty_title diff --git a/app/models/wiki_redirect.rb b/app/models/wiki_redirect.rb new file mode 100644 index 000000000..adc2b24c1 --- /dev/null +++ b/app/models/wiki_redirect.rb @@ -0,0 +1,23 @@ +# redMine - project management software +# Copyright (C) 2006-2007 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. + +class WikiRedirect < ActiveRecord::Base + belongs_to :wiki + + validates_presence_of :title, :redirects_to + validates_length_of :title, :redirects_to, :maximum => 255 +end diff --git a/app/views/wiki/rename.rhtml b/app/views/wiki/rename.rhtml new file mode 100644 index 000000000..0c069f43d --- /dev/null +++ b/app/views/wiki/rename.rhtml @@ -0,0 +1,11 @@ +<h2><%= l(:button_rename) %>: <%= @original_title %></h2> + +<%= error_messages_for 'page' %> + +<% labelled_tabular_form_for :wiki_page, @page, :url => { :action => 'rename' } do |f| %> +<div class="box"> +<p><%= f.text_field :title, :required => true, :size => 255 %></p> +<p><%= f.check_box :redirect_existing_links %></p> +</div> +<%= submit_tag l(:button_rename) %> +<% end %> diff --git a/app/views/wiki/show.rhtml b/app/views/wiki/show.rhtml index 4041ec018..de28ff1e2 100644 --- a/app/views/wiki/show.rhtml +++ b/app/views/wiki/show.rhtml @@ -1,5 +1,6 @@ <div class="contextual"> <%= link_to_if_authorized(l(:button_edit), {:action => 'edit', :page => @page.title}, :class => 'icon icon-edit') if @content.version == @page.content.version %> +<%= link_to_if_authorized(l(:button_rename), {:action => 'rename', :page => @page.title}, :class => 'icon icon-move') if @content.version == @page.content.version %> <%= link_to_if_authorized(l(:button_delete), {:action => 'destroy', :page => @page.title}, :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') %> <%= link_to_if_authorized(l(:button_rollback), {:action => 'edit', :page => @page.title, :version => @content.version }, :class => 'icon icon-cancel') if @content.version < @page.content.version %> <%= link_to(l(:label_history), {:action => 'history', :page => @page.title}, :class => 'icon icon-history') %> @@ -26,8 +27,8 @@ <div class="contextual"> <%= l(:label_export_to) %> -<%= link_to 'HTML', {:export => 'html', :version => @content.version}, :class => 'icon icon-html' %>, -<%= link_to 'TXT', {:export => 'txt', :version => @content.version}, :class => 'icon icon-txt' %> +<%= link_to 'HTML', {:page => @page.title, :export => 'html', :version => @content.version}, :class => 'icon icon-html' %>, +<%= link_to 'TXT', {:page => @page.title, :export => 'txt', :version => @content.version}, :class => 'icon icon-txt' %> </div> <% if authorize_for('wiki', 'add_attachment') %> diff --git a/db/migrate/067_create_wiki_redirects.rb b/db/migrate/067_create_wiki_redirects.rb new file mode 100644 index 000000000..dda6ba6d5 --- /dev/null +++ b/db/migrate/067_create_wiki_redirects.rb @@ -0,0 +1,15 @@ +class CreateWikiRedirects < ActiveRecord::Migration + def self.up + create_table :wiki_redirects do |t| + t.column :wiki_id, :integer, :null => false + t.column :title, :string + t.column :redirects_to, :string + t.column :created_on, :datetime, :null => false + end + add_index :wiki_redirects, [:wiki_id, :title], :name => :wiki_redirects_wiki_id_title + end + + def self.down + drop_table :wiki_redirects + end +end diff --git a/lang/bg.yml b/lang/bg.yml index f34c3d451..57846c3de 100644 --- a/lang/bg.yml +++ b/lang/bg.yml @@ -157,6 +157,7 @@ field_is_filter: Използва се за филтър field_issue_to_id: Related issue field_delay: Delay field_assignable: Issues can be assigned to this role +field_redirect_existing_links: Redirect existing links setting_app_title: Заглавие setting_app_subtitle: Описание @@ -446,6 +447,7 @@ button_reply: Reply button_archive: Archive button_unarchive: Unarchive button_reset: Reset +button_rename: Rename status_active: активен status_registered: регистриран diff --git a/lang/de.yml b/lang/de.yml index f40b8e1cc..a367c0886 100644 --- a/lang/de.yml +++ b/lang/de.yml @@ -157,6 +157,7 @@ field_is_filter: Used as a filter field_issue_to_id: Related issue field_delay: Delay field_assignable: Issues can be assigned to this role +field_redirect_existing_links: Redirect existing links setting_app_title: Applikation Titel setting_app_subtitle: Applikation Untertitel @@ -446,6 +447,7 @@ button_reply: Reply button_archive: Archive button_unarchive: Unarchive button_reset: Reset +button_rename: Rename status_active: aktiv status_registered: angemeldet diff --git a/lang/en.yml b/lang/en.yml index 0e3b74720..fd74c853a 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -157,6 +157,7 @@ field_is_filter: Used as a filter field_issue_to_id: Related issue field_delay: Delay field_assignable: Issues can be assigned to this role +field_redirect_existing_links: Redirect existing links setting_app_title: Application title setting_app_subtitle: Application subtitle @@ -446,6 +447,7 @@ button_reply: Reply button_archive: Archive button_unarchive: Unarchive button_reset: Reset +button_rename: Rename status_active: active status_registered: registered diff --git a/lang/es.yml b/lang/es.yml index b1a92fd44..366dfa01c 100644 --- a/lang/es.yml +++ b/lang/es.yml @@ -157,6 +157,7 @@ field_is_filter: Used as a filter field_issue_to_id: Related issue field_delay: Delay field_assignable: Issues can be assigned to this role +field_redirect_existing_links: Redirect existing links setting_app_title: Título del aplicación setting_app_subtitle: Subtítulo del aplicación @@ -446,6 +447,7 @@ button_reply: Reply button_archive: Archive button_unarchive: Unarchive button_reset: Reset +button_rename: Rename status_active: active status_registered: registered diff --git a/lang/fr.yml b/lang/fr.yml index 35fc48d69..b7ad0b0b4 100644 --- a/lang/fr.yml +++ b/lang/fr.yml @@ -157,6 +157,7 @@ field_is_filter: Utilisé comme filtre field_issue_to_id: Demande liée field_delay: Retard field_assignable: Demandes assignables à ce rôle +field_redirect_existing_links: Rediriger les liens existants setting_app_title: Titre de l'application setting_app_subtitle: Sous-titre de l'application @@ -446,6 +447,7 @@ button_reply: Répondre button_archive: Archiver button_unarchive: Désarchiver button_reset: Réinitialiser +button_rename: Renommer status_active: actif status_registered: enregistré diff --git a/lang/it.yml b/lang/it.yml index 996f6d862..b30d2ab9c 100644 --- a/lang/it.yml +++ b/lang/it.yml @@ -157,6 +157,7 @@ field_is_filter: Used as a filter field_issue_to_id: Related issue field_delay: Delay field_assignable: Issues can be assigned to this role +field_redirect_existing_links: Redirect existing links setting_app_title: Titolo applicazione setting_app_subtitle: Sottotitolo applicazione @@ -446,6 +447,7 @@ button_reply: Reply button_archive: Archive button_unarchive: Unarchive button_reset: Reset +button_rename: Rename status_active: attivo status_registered: registrato diff --git a/lang/ja.yml b/lang/ja.yml index bbadf5c91..a0d6c168b 100644 --- a/lang/ja.yml +++ b/lang/ja.yml @@ -158,6 +158,7 @@ field_is_filter: フィルタとして使う field_issue_to_id: 関連する問題 field_delay: 遅延 field_assignable: Issues can be assigned to this role +field_redirect_existing_links: Redirect existing links setting_app_title: アプリケーションのタイトル setting_app_subtitle: アプリケーションのサブタイトル @@ -447,6 +448,7 @@ button_reply: 返答 button_archive: 書庫に保存 button_unarchive: 書庫から戻す button_reset: Reset +button_rename: Rename status_active: 有効 status_registered: 登録 diff --git a/lang/nl.yml b/lang/nl.yml index b6163e29e..ba9817bbd 100644 --- a/lang/nl.yml +++ b/lang/nl.yml @@ -157,6 +157,7 @@ field_is_filter: Gebruikt als een filter field_issue_to_id: Gerelateerd issue field_delay: Vertraging field_assignable: Issues can be assigned to this role +field_redirect_existing_links: Redirect existing links setting_app_title: Applicatie titel setting_app_subtitle: Applicatie ondertitel @@ -446,6 +447,7 @@ button_reply: Antwoord button_archive: Archive button_unarchive: Unarchive button_reset: Reset +button_rename: Rename status_active: Actief status_registered: geregistreerd diff --git a/lang/pt-br.yml b/lang/pt-br.yml index 2667a51f9..15089070a 100644 --- a/lang/pt-br.yml +++ b/lang/pt-br.yml @@ -157,6 +157,7 @@ field_is_filter: Used as a filter field_issue_to_id: Related issue
field_delay: Delay
field_assignable: Issues can be assigned to this role
+field_redirect_existing_links: Redirect existing links
setting_app_title: Titulo da aplicacao
setting_app_subtitle: Sub-titulo da aplicacao
@@ -446,6 +447,7 @@ button_reply: Reply button_archive: Archive
button_unarchive: Unarchive
button_reset: Reset
+button_rename: Rename
status_active: ativo
status_registered: registrado
diff --git a/lang/pt.yml b/lang/pt.yml index 76f03657e..d4b0e0cad 100644 --- a/lang/pt.yml +++ b/lang/pt.yml @@ -157,6 +157,7 @@ field_is_filter: Usado como filtro field_issue_to_id: Tarefa relacionada field_delay: Atraso field_assignable: Issues can be assigned to this role +field_redirect_existing_links: Redirect existing links setting_app_title: Título da aplicação setting_app_subtitle: Sub-título da aplicação @@ -446,6 +447,7 @@ button_reply: Reply button_archive: Archive button_unarchive: Unarchive button_reset: Reset +button_rename: Rename status_active: ativo status_registered: registrado diff --git a/lang/sv.yml b/lang/sv.yml index c91dcfaa3..8e2c034dc 100644 --- a/lang/sv.yml +++ b/lang/sv.yml @@ -157,6 +157,7 @@ field_is_filter: Used as a filter field_issue_to_id: Related issue field_delay: Delay field_assignable: Issues can be assigned to this role +field_redirect_existing_links: Redirect existing links setting_app_title: Applikationstitel setting_app_subtitle: Applicationsunderrubrik @@ -446,6 +447,7 @@ button_reply: Reply button_archive: Archive button_unarchive: Unarchive button_reset: Reset +button_rename: Rename status_active: activ status_registered: registrerad diff --git a/lang/zh.yml b/lang/zh.yml index 3fc2319e3..e9f8cb7d9 100644 --- a/lang/zh.yml +++ b/lang/zh.yml @@ -160,6 +160,7 @@ field_is_filter: Used as a filter field_issue_to_id: Related issue field_delay: Delay field_assignable: Issues can be assigned to this role +field_redirect_existing_links: Redirect existing links setting_app_title: 应用程序标题 setting_app_subtitle: 应用程序子标题 @@ -448,6 +449,7 @@ button_reply: Reply button_archive: Archive button_unarchive: Unarchive button_reset: Reset +button_rename: Rename status_active: 激活 status_registered: 已注册 diff --git a/lib/redmine.rb b/lib/redmine.rb index 1d27dd420..a0da981e4 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -53,6 +53,7 @@ Redmine::AccessControl.map do |map| # Wiki map.permission :view_wiki_pages, :wiki => [:index, :history, :diff, :special] map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment, :destroy_attachment] + map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member # Message boards map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true diff --git a/test/unit/wiki_redirect_test.rb b/test/unit/wiki_redirect_test.rb new file mode 100644 index 000000000..12f6b7d89 --- /dev/null +++ b/test/unit/wiki_redirect_test.rb @@ -0,0 +1,73 @@ +# redMine - project management software +# Copyright (C) 2006-2007 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.dirname(__FILE__) + '/../test_helper' + +class WikiRedirectTest < Test::Unit::TestCase + fixtures :projects, :wikis + + def setup + @wiki = Wiki.find(1) + @original = WikiPage.create(:wiki => @wiki, :title => 'Original title') + end + + def test_create_redirect + @original.title = 'New title' + assert @original.save + @original.reload + + assert_equal 'New_title', @original.title + assert @wiki.redirects.find_by_title('Original_title') + assert @wiki.find_page('Original title') + end + + def test_update_redirect + # create a redirect that point to this page + assert WikiRedirect.create(:wiki => @wiki, :title => 'An_old_page', :redirects_to => 'Original_title') + + @original.title = 'New title' + @original.save + # make sure the old page now points to the new page + assert_equal 'New_title', @wiki.find_page('An old page').title + end + + def test_reverse_rename + # create a redirect that point to this page + assert WikiRedirect.create(:wiki => @wiki, :title => 'An_old_page', :redirects_to => 'Original_title') + + @original.title = 'An old page' + @original.save + assert !@wiki.redirects.find_by_title_and_redirects_to('An_old_page', 'An_old_page') + assert @wiki.redirects.find_by_title_and_redirects_to('Original_title', 'An_old_page') + end + + def test_rename_to_already_redirected + assert WikiRedirect.create(:wiki => @wiki, :title => 'An_old_page', :redirects_to => 'Other_page') + + @original.title = 'An old page' + @original.save + # this redirect have to be removed since 'An old page' page now exists + assert !@wiki.redirects.find_by_title_and_redirects_to('An_old_page', 'Other_page') + end + + def test_redirects_removed_when_deleting_page + assert WikiRedirect.create(:wiki => @wiki, :title => 'An_old_page', :redirects_to => 'Original_title') + + @original.destroy + assert !@wiki.redirects.find(:first) + end +end |