diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 4a72b022b8f3082186f399acf43bfc19d9136616..2487046093f998d9228fc5d2638c00406b499c1b 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -119,6 +119,22 @@ module ApplicationHelper end end + def render_page_hierarchy(pages, node=nil) + content = '' + if pages[node] + content << "<ul class=\"pages-hierarchy\">\n" + pages[node].each do |page| + content << "<li>" + content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title}, + :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil)) + content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id] + content << "</li>\n" + end + content << "</ul>\n" + end + content + end + # Truncates and returns the string as a single line def truncate_single_line(string, *args) truncate(string, *args).gsub(%r{[\r\n]+}m, ' ') diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb index 0a6b810de939239de33976bc8af46c3c5fac30fd..c692c748b483eb2ff5b218d74d8abe14587599b3 100644 --- a/app/helpers/wiki_helper.rb +++ b/app/helpers/wiki_helper.rb @@ -16,22 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. module WikiHelper - - def render_page_hierarchy(pages, node=nil) - content = '' - if pages[node] - content << "<ul class=\"pages-hierarchy\">\n" - pages[node].each do |page| - content << "<li>" - content << link_to(h(page.pretty_title), {:action => 'index', :page => page.title}, - :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil)) - content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id] - content << "</li>\n" - end - content << "</ul>\n" - end - content - end def html_diff(wdiff) words = wdiff.words.collect{|word| h(word)} diff --git a/app/models/wiki.rb b/app/models/wiki.rb index 3432a2bc775060cd18ab9be7b6ab9a117307945d..be048775aaa0b4e5d8ae76d9fc7bf36250abd3d2 100644 --- a/app/models/wiki.rb +++ b/app/models/wiki.rb @@ -43,6 +43,25 @@ class Wiki < ActiveRecord::Base page end + # Finds a page by title + # The given string can be of one of the forms: "title" or "project:title" + # Examples: + # Wiki.find_page("bar", project => foo) + # Wiki.find_page("foo:bar") + def self.find_page(title, options = {}) + project = options[:project] + if title.to_s =~ %r{^([^\:]+)\:(.*)$} + project_identifier, title = $1, $2 + project = Project.find_by_identifier(project_identifier) || Project.find_by_name(project_identifier) + end + if project && project.wiki + page = project.wiki.find_page(title) + if page && page.content + page + end + end + end + # turn a string into a valid page title def self.titleize(title) # replace spaces with _ and remove unwanted caracters diff --git a/lib/redmine/wiki_formatting/macros.rb b/lib/redmine/wiki_formatting/macros.rb index adfc590e484873e4c2e3f2ebb675269ad79a46fa..abc07b947e266ae59c6e676516d3f3b8ecb0b943 100644 --- a/lib/redmine/wiki_formatting/macros.rb +++ b/lib/redmine/wiki_formatting/macros.rb @@ -23,6 +23,15 @@ module Redmine method_name = "macro_#{name}" send(method_name, obj, args) if respond_to?(method_name) end + + def extract_macro_options(args, *keys) + options = {} + while args.last.to_s.strip =~ %r{^(.+)\=(.+)$} && keys.include?($1.downcase.to_sym) + options[$1.downcase.to_sym] = $2 + args.pop + end + return [args, options] + end end @@available_macros = {} @@ -77,24 +86,29 @@ module Redmine content_tag('dl', out) end - desc "Displays a list of child pages." + desc "Displays a list of child pages. With no argument, it displays the child pages of the current wiki page. Examples:\n\n" + + " !{{child_pages}} -- can be used from a wiki page only\n" + + " !{{child_pages(Foo)}} -- lists all children of page Foo\n" + + " !{{child_pages(Foo, parent=1)}} -- same as above with a link to page Foo" macro :child_pages do |obj, args| - raise 'This macro applies to wiki pages only.' unless obj.is_a?(WikiContent) - render_page_hierarchy(obj.page.descendants.group_by(&:parent_id), obj.page.id) + args, options = extract_macro_options(args, :parent) + page = nil + if args.size > 0 + page = Wiki.find_page(args.first.to_s, :project => @project) + elsif obj.is_a?(WikiContent) + page = obj.page + else + raise 'With no argument, this macro can be called from wiki pages only.' + end + raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project) + pages = ([page] + page.descendants).group_by(&:parent_id) + render_page_hierarchy(pages, options[:parent] ? page.parent_id : page.id) end desc "Include a wiki page. Example:\n\n !{{include(Foo)}}\n\nor to include a page of a specific project wiki:\n\n !{{include(projectname:Foo)}}" macro :include do |obj, args| - project = @project - title = args.first.to_s - if title =~ %r{^([^\:]+)\:(.*)$} - project_identifier, title = $1, $2 - project = Project.find_by_identifier(project_identifier) || Project.find_by_name(project_identifier) - end - raise 'Unknow project' unless project && User.current.allowed_to?(:view_wiki_pages, project) - raise 'No wiki for this project' unless !project.wiki.nil? - page = project.wiki.find_page(title) - raise "Page #{args.first} doesn't exist" unless page && page.content + page = Wiki.find_page(args.first.to_s, :project => @project) + raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project) @included_wiki_pages ||= [] raise 'Circular inclusion detected' if @included_wiki_pages.include?(page.title) @included_wiki_pages << page.title diff --git a/test/fixtures/wiki_contents.yml b/test/fixtures/wiki_contents.yml index 8c53d4d97583e709173d4a75d07a8dbf47cb8937..8798ff22984c38e3347c04fc4ba0c84f1be65dd5 100644 --- a/test/fixtures/wiki_contents.yml +++ b/test/fixtures/wiki_contents.yml @@ -47,4 +47,26 @@ wiki_contents_004: version: 1 author_id: 1 comments: +wiki_contents_005: + text: |- + h1. Child page 1 + + This is a child page + updated_on: 2007-03-08 00:18:07 +01:00 + page_id: 5 + id: 5 + version: 1 + author_id: 1 + comments: +wiki_contents_006: + text: |- + h1. Child page 2 + + This is a child page + updated_on: 2007-03-08 00:18:07 +01:00 + page_id: 6 + id: 6 + version: 1 + author_id: 1 + comments: \ No newline at end of file diff --git a/test/fixtures/wiki_pages.yml b/test/fixtures/wiki_pages.yml index e285441ff90c0b6ed3c6a51f71b8b7de89eae85f..8d29c2f724cf4604e05ec27131fa074c7c67b531 100644 --- a/test/fixtures/wiki_pages.yml +++ b/test/fixtures/wiki_pages.yml @@ -27,4 +27,18 @@ wiki_pages_004: wiki_id: 1 protected: false parent_id: 1 +wiki_pages_005: + created_on: 2007-03-08 00:18:07 +01:00 + title: Child_1 + id: 5 + wiki_id: 1 + protected: false + parent_id: 2 +wiki_pages_006: + created_on: 2007-03-08 00:18:07 +01:00 + title: Child_2 + id: 6 + wiki_id: 1 + protected: false + parent_id: 2 \ No newline at end of file diff --git a/test/unit/helpers/application_helper_test.rb b/test/unit/helpers/application_helper_test.rb index d233003096fe66586e2cb0a00f70c7751fb359c7..31bc1e49da392f5ce2b9cb83623b380a099928c1 100644 --- a/test/unit/helpers/application_helper_test.rb +++ b/test/unit/helpers/application_helper_test.rb @@ -359,32 +359,6 @@ EXPECTED assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '') end - def test_macro_hello_world - text = "{{hello_world}}" - assert textilizable(text).match(/Hello world!/) - # escaping - text = "!{{hello_world}}" - assert_equal '<p>{{hello_world}}</p>', textilizable(text) - end - - def test_macro_include - @project = Project.find(1) - # include a page of the current project wiki - text = "{{include(Another page)}}" - assert textilizable(text).match(/This is a link to a ticket/) - - @project = nil - # include a page of a specific project wiki - text = "{{include(ecookbook:Another page)}}" - assert textilizable(text).match(/This is a link to a ticket/) - - text = "{{include(ecookbook:)}}" - assert textilizable(text).match(/CookBook documentation/) - - text = "{{include(unknowidentifier:somepage)}}" - assert textilizable(text).match(/Unknow project/) - end - def test_default_formatter Setting.text_formatting = 'unknown' text = 'a *link*: http://www.example.net/' diff --git a/test/unit/lib/redmine/wiki_formatting/macros_test.rb b/test/unit/lib/redmine/wiki_formatting/macros_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..b5284acd30afedf7474893a23a36d6caaafbb1b5 --- /dev/null +++ b/test/unit/lib/redmine/wiki_formatting/macros_test.rb @@ -0,0 +1,98 @@ +# Redmine - project management software +# Copyright (C) 2006-2008 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 Redmine::WikiFormatting::MacrosTest < HelperTestCase + include ApplicationHelper + include ActionView::Helpers::TextHelper + fixtures :projects, :roles, :enabled_modules, :users, + :repositories, :changesets, + :trackers, :issue_statuses, :issues, + :versions, :documents, + :wikis, :wiki_pages, :wiki_contents, + :boards, :messages, + :attachments + + def setup + super + @project = nil + end + + def teardown + end + + def test_macro_hello_world + text = "{{hello_world}}" + assert textilizable(text).match(/Hello world!/) + # escaping + text = "!{{hello_world}}" + assert_equal '<p>{{hello_world}}</p>', textilizable(text) + end + + def test_macro_include + @project = Project.find(1) + # include a page of the current project wiki + text = "{{include(Another page)}}" + assert textilizable(text).match(/This is a link to a ticket/) + + @project = nil + # include a page of a specific project wiki + text = "{{include(ecookbook:Another page)}}" + assert textilizable(text).match(/This is a link to a ticket/) + + text = "{{include(ecookbook:)}}" + assert textilizable(text).match(/CookBook documentation/) + + text = "{{include(unknowidentifier:somepage)}}" + assert textilizable(text).match(/Page not found/) + end + + def test_macro_child_pages + expected = "<p><ul class=\"pages-hierarchy\">\n" + + "<li><a href=\"/wiki/ecookbook/Child_1\">Child 1</a></li>\n" + + "<li><a href=\"/wiki/ecookbook/Child_2\">Child 2</a></li>\n" + + "</ul>\n</p>" + + @project = Project.find(1) + # child pages of the current wiki page + assert_equal expected, textilizable("{{child_pages}}", :object => WikiPage.find(2).content) + # child pages of another page + assert_equal expected, textilizable("{{child_pages(Another_page)}}", :object => WikiPage.find(1).content) + + @project = Project.find(2) + assert_equal expected, textilizable("{{child_pages(ecookbook:Another_page)}}", :object => WikiPage.find(1).content) + end + + def test_macro_child_pages_with_option + expected = "<p><ul class=\"pages-hierarchy\">\n" + + "<li><a href=\"/wiki/ecookbook/Another_page\">Another page</a>\n" + + "<ul class=\"pages-hierarchy\">\n" + + "<li><a href=\"/wiki/ecookbook/Child_1\">Child 1</a></li>\n" + + "<li><a href=\"/wiki/ecookbook/Child_2\">Child 2</a></li>\n" + + "</ul>\n</li>\n</ul>\n</p>" + + @project = Project.find(1) + # child pages of the current wiki page + assert_equal expected, textilizable("{{child_pages(parent=1)}}", :object => WikiPage.find(2).content) + # child pages of another page + assert_equal expected, textilizable("{{child_pages(Another_page, parent=1)}}", :object => WikiPage.find(1).content) + + @project = Project.find(2) + assert_equal expected, textilizable("{{child_pages(ecookbook:Another_page, parent=1)}}", :object => WikiPage.find(1).content) + end +end