]> source.dussan.org Git - redmine.git/commitdiff
Added the ability to rename wiki pages (specific permission required).
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Sun, 9 Sep 2007 17:05:38 +0000 (17:05 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Sun, 9 Sep 2007 17:05:38 +0000 (17:05 +0000)
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

21 files changed:
app/controllers/wiki_controller.rb
app/models/wiki.rb
app/models/wiki_page.rb
app/models/wiki_redirect.rb [new file with mode: 0644]
app/views/wiki/rename.rhtml [new file with mode: 0644]
app/views/wiki/show.rhtml
db/migrate/067_create_wiki_redirects.rb [new file with mode: 0644]
lang/bg.yml
lang/de.yml
lang/en.yml
lang/es.yml
lang/fr.yml
lang/it.yml
lang/ja.yml
lang/nl.yml
lang/pt-br.yml
lang/pt.yml
lang/sv.yml
lang/zh.yml
lib/redmine.rb
test/unit/wiki_redirect_test.rb [new file with mode: 0644]

index e9212a1c763c2806f45f52e1f2c55800062e0c95..95f792c5e85abb0d84700066a227aec0eaae1710 100644 (file)
@@ -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])
index ed473c7c06ac2a85275eb6c0e9c5a88281ebfa7d..b6cd661ff19cf8dadeced0b72434fb5e00e90377 100644 (file)
@@ -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
index 074d36daaf125f6398ee20976397d0f48449c13a..1ef8b7db4ae412b5d4104ad2ca36d460f771f8fa 100644 (file)
@@ -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 (file)
index 0000000..adc2b24
--- /dev/null
@@ -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 (file)
index 0000000..0c069f4
--- /dev/null
@@ -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 %>
index 4041ec018bde1cfd340618e4567d77b17c29080e..de28ff1e22044a295f2e700dcfecfae3ff2df2a4 100644 (file)
@@ -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 (file)
index 0000000..dda6ba6
--- /dev/null
@@ -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
index f34c3d4519a4b594668994f5669a13854d9762fa..57846c3de3e76e1c9af3af38a93f4013c9d5971e 100644 (file)
@@ -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: регистриран
index f40b8e1ccb821e380e6230fb3896d1a37cb9d856..a367c088652c463b420737cfff6f45e2d44a1ea6 100644 (file)
@@ -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
index 0e3b74720789dc44a3fda29249a2473ebc35228b..fd74c853a0ed6c162c825da81a8a6f6eef0c7939 100644 (file)
@@ -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
index b1a92fd44f296beff6a9db0fa757b4a4e3f922aa..366dfa01c4aa8bc227746a39347979a019339f0b 100644 (file)
@@ -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
index 35fc48d69b2a1d11f0fe24b9ed2f3a62bd09deb4..b7ad0b0b416a639698ddb3c169f5a2329d1ea96b 100644 (file)
@@ -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é
index 996f6d86201890f7361b44e32f50420bdecddc00..b30d2ab9c6314f41ff01e7f92317330d26ec21b2 100644 (file)
@@ -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
index bbadf5c91d9788c14f9de7f4d1ad2324145fab46..a0d6c168bb254f69dfae5f1f419f2948aa1ef0e0 100644 (file)
@@ -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: 登録
index b6163e29e2eaf1fb8570bf26dc5c19c030b9508a..ba9817bbd463fb1a0f7b6edac35b00dd05f6cfb7 100644 (file)
@@ -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
index 2667a51f915e51896e8220ef367d0d373d04e773..15089070ab485c4506c9394ce2a372aa508db076 100644 (file)
@@ -157,6 +157,7 @@ field_is_filter: Used as a filter
 field_issue_to_id: Related issue\r
 field_delay: Delay\r
 field_assignable: Issues can be assigned to this role\r
+field_redirect_existing_links: Redirect existing links\r
 \r
 setting_app_title: Titulo da aplicacao\r
 setting_app_subtitle: Sub-titulo da aplicacao\r
@@ -446,6 +447,7 @@ button_reply: Reply
 button_archive: Archive\r
 button_unarchive: Unarchive\r
 button_reset: Reset\r
+button_rename: Rename\r
 \r
 status_active: ativo\r
 status_registered: registrado\r
index 76f03657e57456241638cf011b4ec03ad41d2da2..d4b0e0cad53f6a749ccdbb259fc03ef0b0493195 100644 (file)
@@ -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
index c91dcfaa3a93ff6d8858283515f9708ac6255637..8e2c034dca7119c25f04a138698e970ca682d6b9 100644 (file)
@@ -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
index 3fc2319e319e26791f2e71a0290cd901625d48a5..e9f8cb7d95bab53f24beb3fc9cfec464e354bc0e 100644 (file)
@@ -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: 已注册
index 1d27dd420169a4ab05993d67dc1a5d288500d789..a0da981e4a8d7fc60b40065cd2e79fc6d06a4946 100644 (file)
@@ -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 (file)
index 0000000..12f6b7d
--- /dev/null
@@ -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