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])
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 => /^[^,\.\/\?\;\|\:]*$/
# 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
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
--- /dev/null
+# 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
--- /dev/null
+<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 %>
<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') %>
<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') %>
--- /dev/null
+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
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: Описание
button_archive: Archive
button_unarchive: Unarchive
button_reset: Reset
+button_rename: Rename
status_active: активен
status_registered: регистриран
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
button_archive: Archive
button_unarchive: Unarchive
button_reset: Reset
+button_rename: Rename
status_active: aktiv
status_registered: angemeldet
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
button_archive: Archive
button_unarchive: Unarchive
button_reset: Reset
+button_rename: Rename
status_active: active
status_registered: registered
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
button_archive: Archive
button_unarchive: Unarchive
button_reset: Reset
+button_rename: Rename
status_active: active
status_registered: registered
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
button_archive: Archiver
button_unarchive: Désarchiver
button_reset: Réinitialiser
+button_rename: Renommer
status_active: actif
status_registered: enregistré
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
button_archive: Archive
button_unarchive: Unarchive
button_reset: Reset
+button_rename: Rename
status_active: attivo
status_registered: registrato
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: アプリケーションのサブタイトル
button_archive: 書庫に保存
button_unarchive: 書庫から戻す
button_reset: Reset
+button_rename: Rename
status_active: 有効
status_registered: 登録
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
button_archive: Archive
button_unarchive: Unarchive
button_reset: Reset
+button_rename: Rename
status_active: Actief
status_registered: geregistreerd
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
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
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
button_archive: Archive
button_unarchive: Unarchive
button_reset: Reset
+button_rename: Rename
status_active: ativo
status_registered: registrado
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
button_archive: Archive
button_unarchive: Unarchive
button_reset: Reset
+button_rename: Rename
status_active: activ
status_registered: registrerad
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: 应用程序子标题
button_archive: Archive
button_unarchive: Unarchive
button_reset: Reset
+button_rename: Rename
status_active: 激活
status_registered: 已注册
# 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
--- /dev/null
+# 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