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:
parent
f6fe15716e
commit
b4d9ca8875
|
@ -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])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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 %>
|
|
@ -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') %>
|
||||
|
|
|
@ -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
|
|
@ -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: регистриран
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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é
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: 登録
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: 已注册
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue