##// END OF EJS Templates
Added the ability to rename wiki pages (specific permission required)....
Jean-Philippe Lang -
r709:b4d9ca887589
parent child
Show More
@@ -0,0 +1,23
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 class WikiRedirect < ActiveRecord::Base
19 belongs_to :wiki
20
21 validates_presence_of :title, :redirects_to
22 validates_length_of :title, :redirects_to, :maximum => 255
23 end
@@ -0,0 +1,11
1 <h2><%= l(:button_rename) %>: <%= @original_title %></h2>
2
3 <%= error_messages_for 'page' %>
4
5 <% labelled_tabular_form_for :wiki_page, @page, :url => { :action => 'rename' } do |f| %>
6 <div class="box">
7 <p><%= f.text_field :title, :required => true, :size => 255 %></p>
8 <p><%= f.check_box :redirect_existing_links %></p>
9 </div>
10 <%= submit_tag l(:button_rename) %>
11 <% end %>
@@ -0,0 +1,15
1 class CreateWikiRedirects < ActiveRecord::Migration
2 def self.up
3 create_table :wiki_redirects do |t|
4 t.column :wiki_id, :integer, :null => false
5 t.column :title, :string
6 t.column :redirects_to, :string
7 t.column :created_on, :datetime, :null => false
8 end
9 add_index :wiki_redirects, [:wiki_id, :title], :name => :wiki_redirects_wiki_id_title
10 end
11
12 def self.down
13 drop_table :wiki_redirects
14 end
15 end
@@ -0,0 +1,73
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.dirname(__FILE__) + '/../test_helper'
19
20 class WikiRedirectTest < Test::Unit::TestCase
21 fixtures :projects, :wikis
22
23 def setup
24 @wiki = Wiki.find(1)
25 @original = WikiPage.create(:wiki => @wiki, :title => 'Original title')
26 end
27
28 def test_create_redirect
29 @original.title = 'New title'
30 assert @original.save
31 @original.reload
32
33 assert_equal 'New_title', @original.title
34 assert @wiki.redirects.find_by_title('Original_title')
35 assert @wiki.find_page('Original title')
36 end
37
38 def test_update_redirect
39 # create a redirect that point to this page
40 assert WikiRedirect.create(:wiki => @wiki, :title => 'An_old_page', :redirects_to => 'Original_title')
41
42 @original.title = 'New title'
43 @original.save
44 # make sure the old page now points to the new page
45 assert_equal 'New_title', @wiki.find_page('An old page').title
46 end
47
48 def test_reverse_rename
49 # create a redirect that point to this page
50 assert WikiRedirect.create(:wiki => @wiki, :title => 'An_old_page', :redirects_to => 'Original_title')
51
52 @original.title = 'An old page'
53 @original.save
54 assert !@wiki.redirects.find_by_title_and_redirects_to('An_old_page', 'An_old_page')
55 assert @wiki.redirects.find_by_title_and_redirects_to('Original_title', 'An_old_page')
56 end
57
58 def test_rename_to_already_redirected
59 assert WikiRedirect.create(:wiki => @wiki, :title => 'An_old_page', :redirects_to => 'Other_page')
60
61 @original.title = 'An old page'
62 @original.save
63 # this redirect have to be removed since 'An old page' page now exists
64 assert !@wiki.redirects.find_by_title_and_redirects_to('An_old_page', 'Other_page')
65 end
66
67 def test_redirects_removed_when_deleting_page
68 assert WikiRedirect.create(:wiki => @wiki, :title => 'An_old_page', :redirects_to => 'Original_title')
69
70 @original.destroy
71 assert !@wiki.redirects.find(:first)
72 end
73 end
@@ -75,6 +75,18 class WikiController < ApplicationController
75 flash[:error] = l(:notice_locking_conflict)
75 flash[:error] = l(:notice_locking_conflict)
76 end
76 end
77
77
78 # rename a page
79 def rename
80 @page = @wiki.find_page(params[:page])
81 @page.redirect_existing_links = true
82 # used to display the *original* title if some AR validation errors occur
83 @original_title = @page.pretty_title
84 if request.post? && @page.update_attributes(params[:wiki_page])
85 flash[:notice] = l(:notice_successful_update)
86 redirect_to :action => 'index', :id => @project, :page => @page.title
87 end
88 end
89
78 # show page history
90 # show page history
79 def history
91 def history
80 @page = @wiki.find_page(params[:page])
92 @page = @wiki.find_page(params[:page])
@@ -18,6 +18,7
18 class Wiki < ActiveRecord::Base
18 class Wiki < ActiveRecord::Base
19 belongs_to :project
19 belongs_to :project
20 has_many :pages, :class_name => 'WikiPage', :dependent => :destroy
20 has_many :pages, :class_name => 'WikiPage', :dependent => :destroy
21 has_many :redirects, :class_name => 'WikiRedirect', :dependent => :delete_all
21
22
22 validates_presence_of :start_page
23 validates_presence_of :start_page
23 validates_format_of :start_page, :with => /^[^,\.\/\?\;\|\:]*$/
24 validates_format_of :start_page, :with => /^[^,\.\/\?\;\|\:]*$/
@@ -25,14 +26,20 class Wiki < ActiveRecord::Base
25 # find the page with the given title
26 # find the page with the given title
26 # if page doesn't exist, return a new page
27 # if page doesn't exist, return a new page
27 def find_or_new_page(title)
28 def find_or_new_page(title)
28 title = Wiki.titleize(title || start_page)
29 find_page(title) || WikiPage.new(:wiki => self, :title => Wiki.titleize(title))
29 find_page(title) || WikiPage.new(:wiki => self, :title => title)
30 end
30 end
31
31
32 # find the page with the given title
32 # find the page with the given title
33 def find_page(title)
33 def find_page(title, options = {})
34 title = start_page if title.blank?
34 title = start_page if title.blank?
35 pages.find_by_title(Wiki.titleize(title))
35 title = Wiki.titleize(title)
36 page = pages.find_by_title(title)
37 if !page && !(options[:with_redirect] == false)
38 # search for a redirect
39 redirect = redirects.find_by_title(title)
40 page = find_page(redirect.redirects_to, :with_redirect => false) if redirect
41 end
42 page
36 end
43 end
37
44
38 # turn a string into a valid page title
45 # turn a string into a valid page title
@@ -22,13 +22,39 class WikiPage < ActiveRecord::Base
22 has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy
22 has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy
23 has_many :attachments, :as => :container, :dependent => :destroy
23 has_many :attachments, :as => :container, :dependent => :destroy
24
24
25 attr_accessor :redirect_existing_links
26
25 validates_presence_of :title
27 validates_presence_of :title
26 validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/
28 validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/
27 validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false
29 validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false
28 validates_associated :content
30 validates_associated :content
29
31
32 def title=(value)
33 value = Wiki.titleize(value)
34 @previous_title = read_attribute(:title) if @previous_title.blank?
35 write_attribute(:title, value)
36 end
37
30 def before_save
38 def before_save
31 self.title = Wiki.titleize(title)
39 self.title = Wiki.titleize(title)
40 # Manage redirects if the title has changed
41 if !@previous_title.blank? && (@previous_title != title) && !new_record?
42 # Update redirects that point to the old title
43 wiki.redirects.find_all_by_redirects_to(@previous_title).each do |r|
44 r.redirects_to = title
45 r.title == r.redirects_to ? r.destroy : r.save
46 end
47 # Remove redirects for the new title
48 wiki.redirects.find_all_by_title(title).each(&:destroy)
49 # Create a redirect to the new title
50 wiki.redirects << WikiRedirect.new(:title => @previous_title, :redirects_to => title) unless redirect_existing_links == "0"
51 @previous_title = nil
52 end
53 end
54
55 def before_destroy
56 # Remove redirects to this page
57 wiki.redirects.find_all_by_redirects_to(title).each(&:destroy)
32 end
58 end
33
59
34 def pretty_title
60 def pretty_title
@@ -1,5 +1,6
1 <div class="contextual">
1 <div class="contextual">
2 <%= link_to_if_authorized(l(:button_edit), {:action => 'edit', :page => @page.title}, :class => 'icon icon-edit') if @content.version == @page.content.version %>
2 <%= link_to_if_authorized(l(:button_edit), {:action => 'edit', :page => @page.title}, :class => 'icon icon-edit') if @content.version == @page.content.version %>
3 <%= link_to_if_authorized(l(:button_rename), {:action => 'rename', :page => @page.title}, :class => 'icon icon-move') if @content.version == @page.content.version %>
3 <%= link_to_if_authorized(l(:button_delete), {:action => 'destroy', :page => @page.title}, :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') %>
4 <%= link_to_if_authorized(l(:button_delete), {:action => 'destroy', :page => @page.title}, :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') %>
4 <%= 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 %>
5 <%= 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 %>
5 <%= link_to(l(:label_history), {:action => 'history', :page => @page.title}, :class => 'icon icon-history') %>
6 <%= link_to(l(:label_history), {:action => 'history', :page => @page.title}, :class => 'icon icon-history') %>
@@ -26,8 +27,8
26
27
27 <div class="contextual">
28 <div class="contextual">
28 <%= l(:label_export_to) %>
29 <%= l(:label_export_to) %>
29 <%= link_to 'HTML', {:export => 'html', :version => @content.version}, :class => 'icon icon-html' %>,
30 <%= link_to 'HTML', {:page => @page.title, :export => 'html', :version => @content.version}, :class => 'icon icon-html' %>,
30 <%= link_to 'TXT', {:export => 'txt', :version => @content.version}, :class => 'icon icon-txt' %>
31 <%= link_to 'TXT', {:page => @page.title, :export => 'txt', :version => @content.version}, :class => 'icon icon-txt' %>
31 </div>
32 </div>
32
33
33 <% if authorize_for('wiki', 'add_attachment') %>
34 <% if authorize_for('wiki', 'add_attachment') %>
@@ -157,6 +157,7 field_is_filter: Използва се за филтър
157 field_issue_to_id: Related issue
157 field_issue_to_id: Related issue
158 field_delay: Delay
158 field_delay: Delay
159 field_assignable: Issues can be assigned to this role
159 field_assignable: Issues can be assigned to this role
160 field_redirect_existing_links: Redirect existing links
160
161
161 setting_app_title: Заглавие
162 setting_app_title: Заглавие
162 setting_app_subtitle: Описание
163 setting_app_subtitle: Описание
@@ -446,6 +447,7 button_reply: Reply
446 button_archive: Archive
447 button_archive: Archive
447 button_unarchive: Unarchive
448 button_unarchive: Unarchive
448 button_reset: Reset
449 button_reset: Reset
450 button_rename: Rename
449
451
450 status_active: активен
452 status_active: активен
451 status_registered: регистриран
453 status_registered: регистриран
@@ -157,6 +157,7 field_is_filter: Used as a filter
157 field_issue_to_id: Related issue
157 field_issue_to_id: Related issue
158 field_delay: Delay
158 field_delay: Delay
159 field_assignable: Issues can be assigned to this role
159 field_assignable: Issues can be assigned to this role
160 field_redirect_existing_links: Redirect existing links
160
161
161 setting_app_title: Applikation Titel
162 setting_app_title: Applikation Titel
162 setting_app_subtitle: Applikation Untertitel
163 setting_app_subtitle: Applikation Untertitel
@@ -446,6 +447,7 button_reply: Reply
446 button_archive: Archive
447 button_archive: Archive
447 button_unarchive: Unarchive
448 button_unarchive: Unarchive
448 button_reset: Reset
449 button_reset: Reset
450 button_rename: Rename
449
451
450 status_active: aktiv
452 status_active: aktiv
451 status_registered: angemeldet
453 status_registered: angemeldet
@@ -157,6 +157,7 field_is_filter: Used as a filter
157 field_issue_to_id: Related issue
157 field_issue_to_id: Related issue
158 field_delay: Delay
158 field_delay: Delay
159 field_assignable: Issues can be assigned to this role
159 field_assignable: Issues can be assigned to this role
160 field_redirect_existing_links: Redirect existing links
160
161
161 setting_app_title: Application title
162 setting_app_title: Application title
162 setting_app_subtitle: Application subtitle
163 setting_app_subtitle: Application subtitle
@@ -446,6 +447,7 button_reply: Reply
446 button_archive: Archive
447 button_archive: Archive
447 button_unarchive: Unarchive
448 button_unarchive: Unarchive
448 button_reset: Reset
449 button_reset: Reset
450 button_rename: Rename
449
451
450 status_active: active
452 status_active: active
451 status_registered: registered
453 status_registered: registered
@@ -157,6 +157,7 field_is_filter: Used as a filter
157 field_issue_to_id: Related issue
157 field_issue_to_id: Related issue
158 field_delay: Delay
158 field_delay: Delay
159 field_assignable: Issues can be assigned to this role
159 field_assignable: Issues can be assigned to this role
160 field_redirect_existing_links: Redirect existing links
160
161
161 setting_app_title: Título del aplicación
162 setting_app_title: Título del aplicación
162 setting_app_subtitle: Subtítulo del aplicación
163 setting_app_subtitle: Subtítulo del aplicación
@@ -446,6 +447,7 button_reply: Reply
446 button_archive: Archive
447 button_archive: Archive
447 button_unarchive: Unarchive
448 button_unarchive: Unarchive
448 button_reset: Reset
449 button_reset: Reset
450 button_rename: Rename
449
451
450 status_active: active
452 status_active: active
451 status_registered: registered
453 status_registered: registered
@@ -157,6 +157,7 field_is_filter: Utilisé comme filtre
157 field_issue_to_id: Demande liée
157 field_issue_to_id: Demande liée
158 field_delay: Retard
158 field_delay: Retard
159 field_assignable: Demandes assignables à ce rôle
159 field_assignable: Demandes assignables à ce rôle
160 field_redirect_existing_links: Rediriger les liens existants
160
161
161 setting_app_title: Titre de l'application
162 setting_app_title: Titre de l'application
162 setting_app_subtitle: Sous-titre de l'application
163 setting_app_subtitle: Sous-titre de l'application
@@ -446,6 +447,7 button_reply: Répondre
446 button_archive: Archiver
447 button_archive: Archiver
447 button_unarchive: Désarchiver
448 button_unarchive: Désarchiver
448 button_reset: Réinitialiser
449 button_reset: Réinitialiser
450 button_rename: Renommer
449
451
450 status_active: actif
452 status_active: actif
451 status_registered: enregistré
453 status_registered: enregistré
@@ -157,6 +157,7 field_is_filter: Used as a filter
157 field_issue_to_id: Related issue
157 field_issue_to_id: Related issue
158 field_delay: Delay
158 field_delay: Delay
159 field_assignable: Issues can be assigned to this role
159 field_assignable: Issues can be assigned to this role
160 field_redirect_existing_links: Redirect existing links
160
161
161 setting_app_title: Titolo applicazione
162 setting_app_title: Titolo applicazione
162 setting_app_subtitle: Sottotitolo applicazione
163 setting_app_subtitle: Sottotitolo applicazione
@@ -446,6 +447,7 button_reply: Reply
446 button_archive: Archive
447 button_archive: Archive
447 button_unarchive: Unarchive
448 button_unarchive: Unarchive
448 button_reset: Reset
449 button_reset: Reset
450 button_rename: Rename
449
451
450 status_active: attivo
452 status_active: attivo
451 status_registered: registrato
453 status_registered: registrato
@@ -158,6 +158,7 field_is_filter: フィルタとして使う
158 field_issue_to_id: 関連する問題
158 field_issue_to_id: 関連する問題
159 field_delay: 遅延
159 field_delay: 遅延
160 field_assignable: Issues can be assigned to this role
160 field_assignable: Issues can be assigned to this role
161 field_redirect_existing_links: Redirect existing links
161
162
162 setting_app_title: アプリケーションのタイトル
163 setting_app_title: アプリケーションのタイトル
163 setting_app_subtitle: アプリケーションのサブタイトル
164 setting_app_subtitle: アプリケーションのサブタイトル
@@ -447,6 +448,7 button_reply: 返答
447 button_archive: 書庫に保存
448 button_archive: 書庫に保存
448 button_unarchive: 書庫から戻す
449 button_unarchive: 書庫から戻す
449 button_reset: Reset
450 button_reset: Reset
451 button_rename: Rename
450
452
451 status_active: 有効
453 status_active: 有効
452 status_registered: 登録
454 status_registered: 登録
@@ -157,6 +157,7 field_is_filter: Gebruikt als een filter
157 field_issue_to_id: Gerelateerd issue
157 field_issue_to_id: Gerelateerd issue
158 field_delay: Vertraging
158 field_delay: Vertraging
159 field_assignable: Issues can be assigned to this role
159 field_assignable: Issues can be assigned to this role
160 field_redirect_existing_links: Redirect existing links
160
161
161 setting_app_title: Applicatie titel
162 setting_app_title: Applicatie titel
162 setting_app_subtitle: Applicatie ondertitel
163 setting_app_subtitle: Applicatie ondertitel
@@ -446,6 +447,7 button_reply: Antwoord
446 button_archive: Archive
447 button_archive: Archive
447 button_unarchive: Unarchive
448 button_unarchive: Unarchive
448 button_reset: Reset
449 button_reset: Reset
450 button_rename: Rename
449
451
450 status_active: Actief
452 status_active: Actief
451 status_registered: geregistreerd
453 status_registered: geregistreerd
@@ -157,6 +157,7 field_is_filter: Used as a filter
157 field_issue_to_id: Related issue
157 field_issue_to_id: Related issue
158 field_delay: Delay
158 field_delay: Delay
159 field_assignable: Issues can be assigned to this role
159 field_assignable: Issues can be assigned to this role
160 field_redirect_existing_links: Redirect existing links
160
161
161 setting_app_title: Titulo da aplicacao
162 setting_app_title: Titulo da aplicacao
162 setting_app_subtitle: Sub-titulo da aplicacao
163 setting_app_subtitle: Sub-titulo da aplicacao
@@ -446,6 +447,7 button_reply: Reply
446 button_archive: Archive
447 button_archive: Archive
447 button_unarchive: Unarchive
448 button_unarchive: Unarchive
448 button_reset: Reset
449 button_reset: Reset
450 button_rename: Rename
449
451
450 status_active: ativo
452 status_active: ativo
451 status_registered: registrado
453 status_registered: registrado
@@ -157,6 +157,7 field_is_filter: Usado como filtro
157 field_issue_to_id: Tarefa relacionada
157 field_issue_to_id: Tarefa relacionada
158 field_delay: Atraso
158 field_delay: Atraso
159 field_assignable: Issues can be assigned to this role
159 field_assignable: Issues can be assigned to this role
160 field_redirect_existing_links: Redirect existing links
160
161
161 setting_app_title: Título da aplicação
162 setting_app_title: Título da aplicação
162 setting_app_subtitle: Sub-título da aplicação
163 setting_app_subtitle: Sub-título da aplicação
@@ -446,6 +447,7 button_reply: Reply
446 button_archive: Archive
447 button_archive: Archive
447 button_unarchive: Unarchive
448 button_unarchive: Unarchive
448 button_reset: Reset
449 button_reset: Reset
450 button_rename: Rename
449
451
450 status_active: ativo
452 status_active: ativo
451 status_registered: registrado
453 status_registered: registrado
@@ -157,6 +157,7 field_is_filter: Used as a filter
157 field_issue_to_id: Related issue
157 field_issue_to_id: Related issue
158 field_delay: Delay
158 field_delay: Delay
159 field_assignable: Issues can be assigned to this role
159 field_assignable: Issues can be assigned to this role
160 field_redirect_existing_links: Redirect existing links
160
161
161 setting_app_title: Applikationstitel
162 setting_app_title: Applikationstitel
162 setting_app_subtitle: Applicationsunderrubrik
163 setting_app_subtitle: Applicationsunderrubrik
@@ -446,6 +447,7 button_reply: Reply
446 button_archive: Archive
447 button_archive: Archive
447 button_unarchive: Unarchive
448 button_unarchive: Unarchive
448 button_reset: Reset
449 button_reset: Reset
450 button_rename: Rename
449
451
450 status_active: activ
452 status_active: activ
451 status_registered: registrerad
453 status_registered: registrerad
@@ -160,6 +160,7 field_is_filter: Used as a filter
160 field_issue_to_id: Related issue
160 field_issue_to_id: Related issue
161 field_delay: Delay
161 field_delay: Delay
162 field_assignable: Issues can be assigned to this role
162 field_assignable: Issues can be assigned to this role
163 field_redirect_existing_links: Redirect existing links
163
164
164 setting_app_title: 应用程序标题
165 setting_app_title: 应用程序标题
165 setting_app_subtitle: 应用程序子标题
166 setting_app_subtitle: 应用程序子标题
@@ -448,6 +449,7 button_reply: Reply
448 button_archive: Archive
449 button_archive: Archive
449 button_unarchive: Unarchive
450 button_unarchive: Unarchive
450 button_reset: Reset
451 button_reset: Reset
452 button_rename: Rename
451
453
452 status_active: 激活
454 status_active: 激活
453 status_registered: 已注册
455 status_registered: 已注册
@@ -53,6 +53,7 Redmine::AccessControl.map do |map|
53 # Wiki
53 # Wiki
54 map.permission :view_wiki_pages, :wiki => [:index, :history, :diff, :special]
54 map.permission :view_wiki_pages, :wiki => [:index, :history, :diff, :special]
55 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment, :destroy_attachment]
55 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment, :destroy_attachment]
56 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
56 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
57 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
57 # Message boards
58 # Message boards
58 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
59 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
General Comments 0
You need to be logged in to leave comments. Login now