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
This commit is contained in:
Jean-Philippe Lang 2007-09-09 17:05:38 +00:00
parent f6fe15716e
commit b4d9ca8875
21 changed files with 201 additions and 8 deletions

View 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])

View 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

View 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

View File

@ -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

View File

@ -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 %>

View 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') %>

View File

@ -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

View 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: регистриран

View 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

View 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

View 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

View 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é

View 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

View 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: 登録

View 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

View 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: 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

View 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

View 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

View 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: 已注册

View 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

View File

@ -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