summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJean-Philippe Lang <jp_lang@yahoo.fr>2007-09-09 17:05:38 +0000
committerJean-Philippe Lang <jp_lang@yahoo.fr>2007-09-09 17:05:38 +0000
commitb4d9ca8875898500ca198bff501dfc7d1c28bd71 (patch)
tree8d9ebcf80cd56b427211b6b769514ab8d66f33a2
parentf6fe15716e4714c5f6d4d88149927b7dfa5c6fce (diff)
downloadredmine-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.rb12
-rw-r--r--app/models/wiki.rb15
-rw-r--r--app/models/wiki_page.rb30
-rw-r--r--app/models/wiki_redirect.rb23
-rw-r--r--app/views/wiki/rename.rhtml11
-rw-r--r--app/views/wiki/show.rhtml5
-rw-r--r--db/migrate/067_create_wiki_redirects.rb15
-rw-r--r--lang/bg.yml2
-rw-r--r--lang/de.yml2
-rw-r--r--lang/en.yml2
-rw-r--r--lang/es.yml2
-rw-r--r--lang/fr.yml2
-rw-r--r--lang/it.yml2
-rw-r--r--lang/ja.yml2
-rw-r--r--lang/nl.yml2
-rw-r--r--lang/pt-br.yml2
-rw-r--r--lang/pt.yml2
-rw-r--r--lang/sv.yml2
-rw-r--r--lang/zh.yml2
-rw-r--r--lib/redmine.rb1
-rw-r--r--test/unit/wiki_redirect_test.rb73
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