diff --git a/app/controllers/file_controller.rb b/app/controllers/file_controller.rb index aa0dc9aa..17885ab1 100644 --- a/app/controllers/file_controller.rb +++ b/app/controllers/file_controller.rb @@ -129,7 +129,7 @@ class FileController < ApplicationController next else logger.info "Page '#{page_name}' already exists. Adding a new revision to it." - wiki.revise_page(@web.address, page_name, page_content, Time.now, @author, PageRenderer.new) + wiki.revise_page(@web.address, page_name, page_name, page_content, Time.now, @author, PageRenderer.new) end else wiki.write_page(@web.address, page_name, page_content, Time.now, @author, PageRenderer.new) diff --git a/app/controllers/revision_sweeper.rb b/app/controllers/revision_sweeper.rb index a2c0a972..b769db59 100644 --- a/app/controllers/revision_sweeper.rb +++ b/app/controllers/revision_sweeper.rb @@ -22,8 +22,9 @@ class RevisionSweeper < ActionController::Caching::Sweeper def expire_caches(page) expire_cached_summary_pages(page.web) - pages_to_expire = ([page.name] + WikiReference.pages_that_reference(page.web, page.name) + - WikiReference.pages_that_include(page.web, page.name)).uniq + pages_to_expire = ([page.name] + WikiReference.pages_that_reference(page.web, page.name) + + WikiReference.pages_redirected_to(page.web, page.name) + + WikiReference.pages_that_include(page.web, page.name)).uniq pages_to_expire.each { |page_name| expire_cached_page(page.web, page_name) } end diff --git a/app/controllers/web_sweeper.rb b/app/controllers/web_sweeper.rb index 838aa328..85451897 100644 --- a/app/controllers/web_sweeper.rb +++ b/app/controllers/web_sweeper.rb @@ -12,7 +12,7 @@ class WebSweeper < ActionController::Caching::Sweeper web.pages.each { |page| expire_cached_page(web, page.name) } expire_cached_summary_pages(web) elsif record.is_a?(WikiFile) - record.web.pages_that_link_to(record.file_name).each do |page| + record.web.pages_that_link_to_file(record.file_name).each do |page| expire_cached_page(record.web, page) end expire_cached_summary_pages(record.web) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index c2bfad42..4407be0b 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -268,9 +268,13 @@ class WikiController < ApplicationController raise Instiki::ValidationError.new('Your content was not valid utf-8.') end if @page - wiki.revise_page(@web_name, @page_name, the_content, Time.now, + new_name = params['new_name'] || @page_name + raise Instiki::ValidationError.new('Your new title was not valid utf-8.') unless new_name.is_utf8? + raise Instiki::ValidationError.new('A page named "' + new_name.escapeHTML + '" already exists.') if @page_name != new_name && @web.has_page?(new_name) + wiki.revise_page(@web_name, @page_name, new_name, the_content, Time.now, Author.new(author_name, remote_ip), PageRenderer.new) @page.unlock + @page_name = new_name else wiki.write_page(@web_name, @page_name, the_content, Time.now, Author.new(author_name, remote_ip), PageRenderer.new) diff --git a/app/models/page.rb b/app/models/page.rb index 809015a0..7f8946dd 100644 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -4,13 +4,14 @@ class Page < ActiveRecord::Base has_many :wiki_references, :order => 'referenced_name' has_one :current_revision, :class_name => 'Revision', :order => 'id DESC' - def revise(content, time, author, renderer) + def revise(content, name, time, author, renderer) revisions_size = new_record? ? 0 : revisions.size - if (revisions_size > 0) and content == current_revision.content + if (revisions_size > 0) and content == current_revision.content and name == self.name raise Instiki::ValidationError.new( "You have tried to save page '#{name}' without changing its content") end + self.name = name author = Author.new(author.to_s) unless author.is_a?(Author) # Try to render content to make sure that markup engine can take it, @@ -37,7 +38,7 @@ class Page < ActiveRecord::Base raise Instiki::ValidationError.new("Revision #{revision_number} not found") end author = Author.new(roll_back_revision.author.name, author_ip) - revise(roll_back_revision.content, time, author, renderer) + revise(roll_back_revision.content, self.name, time, author, renderer) end def revisions? @@ -70,6 +71,10 @@ class Page < ActiveRecord::Base def linked_from web.select.pages_that_link_to(name) end + + def redirects_for + wiki_references.select { |ref| ref.redirected_page?}.map { |ref| ref.referenced_name } + end def included_from web.select.pages_that_include(name) diff --git a/app/models/web.rb b/app/models/web.rb index 7024e155..b2e7e6c2 100644 --- a/app/models/web.rb +++ b/app/models/web.rb @@ -14,7 +14,7 @@ class Web < ActiveRecord::Base def add_page(name, content, time, author, renderer) page = page(name) || Page.new(:web => self, :name => name) - page.revise(content, time, author, renderer) + page.revise(content, name, time, author, renderer) end def authors @@ -42,6 +42,14 @@ class Web < ActiveRecord::Base def has_page?(name) Page.count(:conditions => ['web_id = ? AND name = ?', id, name]) > 0 end + + def has_redirect_for?(name) + WikiReference.page_that_redirects_for(self, name) + end + + def page_that_redirects_for(name) + page(WikiReference.page_that_redirects_for(self, name)) + end def has_file?(file_name) WikiFile.find_by_file_name(file_name) != nil @@ -51,10 +59,14 @@ class Web < ActiveRecord::Base WikiFile.all(:order => sort_order, :conditions => ['web_id = ?', id]) end - def pages_that_link_to(file_name) + def pages_that_link_to(page_name) + WikiReference.pages_that_link_to(self, page_name) + end + + def pages_that_link_to_file(file_name) WikiReference.pages_that_link_to_file(self, file_name) end - + def description(file_name) file = WikiFile.find_by_file_name(file_name) file.description if file diff --git a/app/models/wiki.rb b/app/models/wiki.rb index dc58e222..cf5cfd18 100644 --- a/app/models/wiki.rb +++ b/app/models/wiki.rb @@ -60,9 +60,9 @@ class Wiki web.remove_pages(pages_in_category.orphaned_pages) end - def revise_page(web_address, page_name, content, revised_at, author, renderer) + def revise_page(web_address, page_name, new_name, content, revised_at, author, renderer) page = read_page(web_address, page_name) - page.revise(content, revised_at, author, renderer) + page.revise(content, new_name, revised_at, author, renderer) end def rollback_page(web_address, page_name, revision_number, time, author_id = nil) diff --git a/app/models/wiki_reference.rb b/app/models/wiki_reference.rb index b00f37cd..a4b79c41 100644 --- a/app/models/wiki_reference.rb +++ b/app/models/wiki_reference.rb @@ -2,6 +2,7 @@ class WikiReference < ActiveRecord::Base LINKED_PAGE = 'L' WANTED_PAGE = 'W' + REDIRECTED_PAGE = 'R' INCLUDED_PAGE = 'I' CATEGORY = 'C' AUTHOR = 'A' @@ -9,7 +10,7 @@ class WikiReference < ActiveRecord::Base WANTED_FILE = 'E' belongs_to :page - validates_inclusion_of :link_type, :in => [LINKED_PAGE, WANTED_PAGE, INCLUDED_PAGE, CATEGORY, AUTHOR, FILE, WANTED_FILE] + validates_inclusion_of :link_type, :in => [LINKED_PAGE, WANTED_PAGE, REDIRECTED_PAGE, INCLUDED_PAGE, CATEGORY, AUTHOR, FILE, WANTED_FILE] def self.link_type(web, page_name) web.has_page?(page_name) ? LINKED_PAGE : WANTED_PAGE @@ -51,6 +52,28 @@ class WikiReference < ActiveRecord::Base names = connection.select_all(sanitize_sql([query, page_name])).map { |row| row['name'] } end + def self.pages_redirected_to(web, page_name) + names = [] + if web.has_page?(page_name) + page = web.page(page_name) + redirected_names = page.redirects_for + redirected_names.each do |name| + names = names | self.pages_that_reference(web, name) + end + end + names + end + + def self.page_that_redirects_for(web, page_name) + query = 'SELECT name FROM pages JOIN wiki_references ' + + 'ON pages.id = wiki_references.page_id ' + + 'WHERE wiki_references.referenced_name = ? ' + + "AND wiki_references.link_type = '#{REDIRECTED_PAGE}' " + + "AND pages.web_id = '#{web.id}'" + names = connection.select_all(sanitize_sql([query, page_name])).map { |row| row['name'] } + names[0] + end + def self.pages_in_category(web, category) query = "SELECT name FROM pages JOIN wiki_references " + @@ -82,6 +105,10 @@ class WikiReference < ActiveRecord::Base link_type == LINKED_PAGE end + def redirected_page? + link_type == REDIRECTED_PAGE + end + def wanted_page? link_type == WANTED_PAGE end diff --git a/app/views/wiki/edit.rhtml b/app/views/wiki/edit.rhtml index 76b36415..ee5725a1 100644 --- a/app/views/wiki/edit.rhtml +++ b/app/views/wiki/edit.rhtml @@ -12,6 +12,16 @@ <% form_tag({ :action => 'save', :web => @web.address, :id => @page.name }, { 'id' => 'editForm', 'method' => 'post', 'onsubmit' => 'cleanAuthorName()', 'accept-charset' => 'utf-8' }) do %> +<% if @page_name != 'HomePage' -%> +

+ <%= check_box_tag :alter_title, value = "1", checked=false, + 'onchange' => "toggleVisibility();" %>
+ +

+<% else -%> + <%= hidden_field_tag 'new_name', @page_name %> +<% end%>
@@ -31,10 +41,30 @@ <%- end -%> diff --git a/app/views/wiki/file_list.html.erb b/app/views/wiki/file_list.html.erb index 09db001d..86c95c1b 100644 --- a/app/views/wiki/file_list.html.erb +++ b/app/views/wiki/file_list.html.erb @@ -19,8 +19,8 @@ <%= file.file_name%> (<%= file.created_at.asctime %>) <%= "Linked to by: " unless - @web.pages_that_link_to(file.file_name).empty? -%> - <%= @web.pages_that_link_to(file.file_name).collect { |referring_page| link_to_page(referring_page) }.join(", ") %> + @web.pages_that_link_to_file(file.file_name).empty? -%> + <%= @web.pages_that_link_to_file(file.file_name).collect { |referring_page| link_to_page(referring_page) }.join(", ") %> <%- end -%> diff --git a/lib/chunks/redirect.rb b/lib/chunks/redirect.rb new file mode 100644 index 00000000..f2b58f82 --- /dev/null +++ b/lib/chunks/redirect.rb @@ -0,0 +1,21 @@ +require 'chunks/wiki' + +# [[!redirects Foo]] +# redirects Wikilinks for the (nonexistent) page "Foo" to this page. +# If "Foo" exists, then the Redirect has no effect. But if "Foo" +# does not exist, then a Wikilink [[Foo]] will produce a link to this +# page, rather than produce a create-a-new-page link. + +class Redirect < WikiChunk::WikiReference + + REDIRECT_PATTERN = /\[\[!redirects\s+([^\]\s][^\]]*?)\s*\]\]/i + def self.pattern() REDIRECT_PATTERN end + + def initialize(match_data, content) + super + @page_name = match_data[1].strip + @link_type = :redirect + @unmask_text = '' + end + +end diff --git a/lib/page_renderer.rb b/lib/page_renderer.rb index 4047b651..222762a9 100644 --- a/lib/page_renderer.rb +++ b/lib/page_renderer.rb @@ -173,6 +173,13 @@ class PageRenderer :link_type => WikiReference::INCLUDED_PAGE end + redirect_chunks = rendering_result.find_chunks(Redirect) + redirects = redirect_chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq + redirects.each do |redirected_page_name| + references.build :referenced_name => redirected_page_name, + :link_type => WikiReference::REDIRECTED_PAGE + end + categories = rendering_result.find_chunks(Category).map { |cat| cat.list }.flatten categories.each do |category| references.build :referenced_name => category, :link_type => WikiReference::CATEGORY diff --git a/lib/url_generator.rb b/lib/url_generator.rb index 1a2a357d..e7e48ef0 100644 --- a/lib/url_generator.rb +++ b/lib/url_generator.rb @@ -10,23 +10,30 @@ class AbstractUrlGenerator # Create a link for the given page (or file) name and link text based # on the render mode in options and whether the page (file) exists # in the web. - def make_link(name, web, text = nil, options = {}) + def make_link(asked_name, web, text = nil, options = {}) mode = (options[:mode] || :show).to_sym link_type = (options[:link_type] || :show).to_sym if (link_type == :show) - known_page = web.has_page?(name) + page_exists = web.has_page?(asked_name) + known_page = page_exists || web.has_redirect_for?(asked_name) + if known_page && !page_exists + name = web.page_that_redirects_for(asked_name) + else + name = asked_name + end else + name = asked_name known_page = web.has_file?(name) description = web.description(name) description = description.unescapeHTML.escapeHTML if description end - if (text == name) + if (text == asked_name) text = description || text else text = text || description end - text = (text || WikiWords.separate(name)).unescapeHTML.escapeHTML + text = (text || WikiWords.separate(asked_name)).unescapeHTML.escapeHTML case link_type when :show diff --git a/lib/wiki_content.rb b/lib/wiki_content.rb index 9c5405a2..cc0784dc 100644 --- a/lib/wiki_content.rb +++ b/lib/wiki_content.rb @@ -2,6 +2,7 @@ require 'cgi' require 'chunks/engines' require 'chunks/category' require_dependency 'chunks/include' +require_dependency 'chunks/redirect' require_dependency 'chunks/wiki' require_dependency 'chunks/literal' require 'chunks/nowiki' @@ -38,7 +39,7 @@ require 'sanitizer' module ChunkManager attr_reader :chunks_by_type, :chunks_by_id, :chunks, :chunk_id - ACTIVE_CHUNKS = [ NoWiki, Category, WikiChunk::Link, + ACTIVE_CHUNKS = [ NoWiki, Category, Redirect, WikiChunk::Link, WikiChunk::Word ] HIDE_CHUNKS = [ Literal::Pre, Literal::Tags, Literal::Math ] diff --git a/test/functional/file_controller_test.rb b/test/functional/file_controller_test.rb index add7c0c8..0fbb6944 100755 --- a/test/functional/file_controller_test.rb +++ b/test/functional/file_controller_test.rb @@ -118,7 +118,7 @@ class FileControllerTest < ActionController::TestCase # edit and re-render a page so that it has an "unknown file" link to 'rails-e2e.gif' PageRenderer.setup_url_generator(StubUrlGenerator.new) renderer = PageRenderer.new - @wiki.revise_page('wiki1', 'Oak', '[[rails-e2e.gif:pic]]', + @wiki.revise_page('wiki1', 'Oak', 'Oak', '[[rails-e2e.gif:pic]]', Time.now, 'AnonymousBrave', renderer) assert_equal "

rails-e2e.gif" + "?

", @@ -146,7 +146,7 @@ class FileControllerTest < ActionController::TestCase assert @web.has_file?('rails-e2e.gif') assert_equal(picture, WikiFile.find_by_file_name('rails-e2e.gif').content) PageRenderer.setup_url_generator(StubUrlGenerator.new) - @wiki.revise_page('wiki1', 'Oak', 'Try [[rails-e2e.gif:pic]] again.', + @wiki.revise_page('wiki1', 'Oak', 'Oak', 'Try [[rails-e2e.gif:pic]] again.', Time.now, 'AnonymousBrave', renderer) assert_equal "

Try Rails, end-to-end again.

", renderer.display_content diff --git a/test/unit/page_test.rb b/test/unit/page_test.rb index 38efd80a..e290a98b 100644 --- a/test/unit/page_test.rb +++ b/test/unit/page_test.rb @@ -32,7 +32,7 @@ class PageTest < ActiveSupport::TestCase end def test_revise - @page.revise('HisWay would be MyWay in kinda lame', Time.local(2004, 4, 4, 16, 55), + @page.revise('HisWay would be MyWay in kinda lame', @page.name, Time.local(2004, 4, 4, 16, 52), 'MarianneSyhler', test_renderer) @page.reload @@ -44,14 +44,14 @@ class PageTest < ActiveSupport::TestCase end def test_revise_continous_revision - @page.revise('HisWay would be MyWay in kinda lame', Time.local(2004, 4, 4, 16, 55), + @page.revise('HisWay would be MyWay in kinda lame', @page.name, Time.local(2004, 4, 4, 16, 55), 'MarianneSyhler', test_renderer) @page.reload assert_equal 2, @page.revisions.length assert_equal 'HisWay would be MyWay in kinda lame', @page.content # consecutive revision by the same author within 30 minutes doesn't create a new revision - @page.revise('HisWay would be MyWay in kinda update', Time.local(2004, 4, 4, 16, 57), + @page.revise('HisWay would be MyWay in kinda update', @page.name, Time.local(2004, 4, 4, 16, 57), 'MarianneSyhler', test_renderer) @page.reload assert_equal 2, @page.revisions.length @@ -59,7 +59,7 @@ class PageTest < ActiveSupport::TestCase assert_equal Time.local(2004, 4, 4, 16, 57), @page.revised_at # but consecutive revision by another author results in a new revision - @page.revise('HisWay would be MyWay in the house', Time.local(2004, 4, 4, 16, 58), + @page.revise('HisWay would be MyWay in the house', @page.name, Time.local(2004, 4, 4, 16, 58), 'DavidHeinemeierHansson', test_renderer) @page.reload assert_equal 3, @page.revisions.length @@ -67,18 +67,31 @@ class PageTest < ActiveSupport::TestCase # consecutive update after 30 minutes since the last one also creates a new revision, # even when it is by the same author - @page.revise('HisWay would be MyWay in my way', Time.local(2004, 4, 4, 17, 30), + @page.revise('HisWay would be MyWay in my way', @page.name, Time.local(2004, 4, 4, 17, 30), 'DavidHeinemeierHansson', test_renderer) @page.reload assert_equal 4, @page.revisions.length end + + def test_change_name + @page.revise('HisWay would be MyWay in my way', 'SecondPage', Time.local(2004, 4, 5, 17, 56), + 'MarianneSyhler', test_renderer) + @page.reload + + assert_equal "Second Page", @page.plain_name + assert_equal 2, @page.revisions.length, 'Should have two revisions' + assert_equal 'MarianneSyhler', @page.current_revision.author.to_s, + 'Marianne should be the author now' + assert_equal 'DavidHeinemeierHansson', @page.revisions.first.author.to_s, + 'David was the first author' + end def test_revise_content_unchanged last_revision_before = @page.current_revision revisions_number_before = @page.revisions.size assert_raises(Instiki::ValidationError) { - @page.revise(@page.current_revision.content, Time.now, 'AlexeyVerkhovsky', test_renderer) + @page.revise(@page.current_revision.content, @page.name, Time.now, 'AlexeyVerkhovsky', test_renderer) } assert_equal last_revision_before, @page.current_revision(true) @@ -88,7 +101,7 @@ class PageTest < ActiveSupport::TestCase def test_revise_changes_references_from_wanted_to_linked_for_new_pages web = Web.find(1) new_page = Page.new(:web => web, :name => 'NewPage') - new_page.revise('Reference to WantedPage, and to WantedPage2', Time.now, 'AlexeyVerkhovsky', + new_page.revise('Reference to WantedPage, and to WantedPage2', 'NewPage', Time.now, 'AlexeyVerkhovsky', test_renderer) references = new_page.wiki_references(true) @@ -99,7 +112,7 @@ class PageTest < ActiveSupport::TestCase assert_equal WikiReference::WANTED_PAGE, references[1].link_type wanted_page = Page.new(:web => web, :name => 'WantedPage') - wanted_page.revise('And here it is!', Time.now, 'AlexeyVerkhovsky', test_renderer) + wanted_page.revise('And here it is!', 'WantedPage', Time.now, 'AlexeyVerkhovsky', test_renderer) # link type stored for NewPage -> WantedPage reference should change from WANTED to LINKED # reference NewPage -> WantedPage2 should remain the same @@ -112,8 +125,8 @@ class PageTest < ActiveSupport::TestCase end def test_rollback - @page.revise("spot two", Time.now, "David", test_renderer) - @page.revise("spot three", Time.now + 2000, "David", test_renderer) + @page.revise("spot two", @page.name, Time.now, "David", test_renderer) + @page.revise("spot three", @page.name, Time.now + 2000, "David", test_renderer) assert_equal 3, @page.revisions(true).length, "Should have three revisions" @page.current_revision(true) @page.rollback(0, Time.now, '127.0.0.1', test_renderer)