From 702b521b453ac5abfe9ae6336ff129cbd83f1eca Mon Sep 17 00:00:00 2001
From: Jean-Philippe Lang <jp_lang@yahoo.fr>
Date: Tue, 15 Jan 2008 18:12:12 +0000
Subject: [PATCH] Redmine links can be used to link to documents, versions and
 attachments. For now, attachments of the current object can be referenced
 only (if you're on an issue, it's possible reference attachments of this
 issue only).

git-svn-id: http://redmine.rubyforge.org/svn/trunk@1064 e93f8b46-1217-0410-a6f0-8f06a7374b81
---
 app/helpers/application_helper.rb            | 118 +++++++++++++------
 lib/redcloth.rb                              |  12 +-
 test/fixtures/wiki_contents.yml              |  12 +-
 test/fixtures/wiki_pages.yml                 |   5 +
 test/fixtures/wikis.yml                      |   6 +
 test/unit/helpers/application_helper_test.rb |  44 ++++++-
 6 files changed, 153 insertions(+), 44 deletions(-)

diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index e3ac2498f..b34b5b502 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -220,8 +220,9 @@ module ApplicationHelper
     
     project = options[:project] || @project
     
-    # turn wiki links into html links
-    # example:
+    # Wiki links
+    # 
+    # Examples:
     #   [[mypage]]
     #   [[mypage|mytext]]
     # wiki links can refer other project wikis, using project name or identifier:
@@ -229,47 +230,94 @@ module ApplicationHelper
     #   [[project:|mytext]]
     #   [[project:mypage]]
     #   [[project:mypage|mytext]]
-    text = text.gsub(/\[\[([^\]\|]+)(\|([^\]\|]+))?\]\]/) do |m|
+    text = text.gsub(/(!)?(\[\[([^\]\|]+)(\|([^\]\|]+))?\]\])/) do |m|
       link_project = project
-      page = $1
-      title = $3
-      if page =~ /^([^\:]+)\:(.*)$/
-        link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
-        page = title || $2
-        title = $1 if page.blank?
-      end
-      
-      if link_project && link_project.wiki
-        # check if page exists
-        wiki_page = link_project.wiki.find_page(page)
-        link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
-                                 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
+      esc, all, page, title = $1, $2, $3, $5
+      if esc.nil?
+        if page =~ /^([^\:]+)\:(.*)$/
+          link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
+          page = $2
+          title ||= $1 if page.blank?
+        end
+        
+        if link_project && link_project.wiki
+          # check if page exists
+          wiki_page = link_project.wiki.find_page(page)
+          link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
+                                   :class => ('wiki-page' + (wiki_page ? '' : ' new')))
+        else
+          # project or wiki doesn't exist
+          title || page
+        end
       else
-        # project or wiki doesn't exist
-        title || page
+        all
       end
     end
 
-    # turn issue and revision ids into links
-    # example:
-    #   #52 -> <a href="/issues/show/52">#52</a>
-    #   r52 -> <a href="/repositories/revision/6?rev=52">r52</a> (project.id is 6)
-    text = text.gsub(%r{([\s\(,-^])(#|r)(\d+)(?=[[:punct:]]|\s|<|$)}) do |m|
-      leading, otype, oid = $1, $2, $3
+    # Redmine links
+    # 
+    # Examples:
+    #   Issues:
+    #     #52 -> Link to issue #52
+    #   Changesets:
+    #     r52 -> Link to revision 52
+    #   Documents:
+    #     document#17 -> Link to document with id 17
+    #     document:Greetings -> Link to the document with title "Greetings"
+    #     document:"Some document" -> Link to the document with title "Some document"
+    #   Versions:
+    #     version#3 -> Link to version with id 3
+    #     version:1.0.0 -> Link to version named "1.0.0"
+    #     version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
+    #   Attachments:
+    #     attachment:file.zip -> Link to the attachment of the current object named file.zip
+    text = text.gsub(%r{([\s\(,-^])(!)?(attachment|document|version)?((#|r)(\d+)|(:)([^"][^\s<>]+|"[^"]+"))(?=[[:punct:]]|\s|<|$)}) do |m|
+      leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
       link = nil
-      if otype == 'r'
-        if project && (changeset = project.changesets.find_by_revision(oid))
-          link = link_to("r#{oid}", {:controller => 'repositories', :action => 'revision', :id => project.id, :rev => oid}, :class => 'changeset',
-                                    :title => truncate(changeset.comments, 100))
-        end
-      else
-        if issue = Issue.find_by_id(oid.to_i, :include => [:project, :status], :conditions => Project.visible_by(User.current))        
-          link = link_to("##{oid}", {:controller => 'issues', :action => 'show', :id => oid}, :class => 'issue',
-                                    :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
-          link = content_tag('del', link) if issue.closed?
+      if esc.nil?
+        if prefix.nil? && sep == 'r'
+          if project && (changeset = project.changesets.find_by_revision(oid))
+            link = link_to("r#{oid}", {:controller => 'repositories', :action => 'revision', :id => project.id, :rev => oid}, :class => 'changeset',
+                                      :title => truncate(changeset.comments, 100))
+          end
+        elsif sep == '#'
+          oid = oid.to_i
+          case prefix
+          when nil
+            if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))        
+              link = link_to("##{oid}", {:controller => 'issues', :action => 'show', :id => oid}, :class => 'issue',
+                                        :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
+              link = content_tag('del', link) if issue.closed?
+            end
+          when 'document'
+            if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
+              link = link_to h(document.title), {:controller => 'documents', :action => 'show', :id => document}, :class => 'document'
+            end
+          when 'version'
+            if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
+              link = link_to h(version.name), {:controller => 'versions', :action => 'show', :id => version}, :class => 'version'
+            end
+          end
+        elsif sep == ':'
+          # removes the double quotes if any
+          name = oid.gsub(%r{^"(.*)"$}, "\\1")
+          case prefix
+          when 'document'
+            if project && document = project.documents.find_by_title(name)
+              link = link_to h(document.title), {:controller => 'documents', :action => 'show', :id => document}, :class => 'document'
+            end
+          when 'version'
+            if project && version = project.versions.find_by_name(name)
+              link = link_to h(version.name), {:controller => 'versions', :action => 'show', :id => version}, :class => 'version'
+            end
+          when 'attachment'
+            if attachments && attachment = attachments.detect {|a| a.filename == name }
+              link = link_to h(attachment.filename), {:controller => 'attachments', :action => 'download', :id => attachment}, :class => 'attachment'
+            end
+          end
         end
       end
-      leading + (link || "#{otype}#{oid}")
+      leading + (link || "#{prefix}#{sep}#{oid}")
     end
     
     text
diff --git a/lib/redcloth.rb b/lib/redcloth.rb
index ae70db747..904701c9e 100644
--- a/lib/redcloth.rb
+++ b/lib/redcloth.rb
@@ -395,15 +395,15 @@ class RedCloth < String
     # Elements to handle
     GLYPHS = [
     #   [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1&#8217;\2' ], # single closing
-        [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1&#8217;' ], # single closing
-        [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '&#8217;' ], # single closing
-        [ /\'/, '&#8216;' ], # single opening
+    #   [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1&#8217;' ], # single closing
+    #   [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '&#8217;' ], # single closing
+    #   [ /\'/, '&#8216;' ], # single opening
         [ /</, '&lt;' ], # less-than
         [ />/, '&gt;' ], # greater-than
     #   [ /([^\s\[{(])?"(\s|:|$)/, '\1&#8221;\2' ], # double closing
-        [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1&#8221;' ], # double closing
-        [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '&#8221;' ], # double closing
-        [ /"/, '&#8220;' ], # double opening
+    #   [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1&#8221;' ], # double closing
+    #   [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '&#8221;' ], # double closing
+    #   [ /"/, '&#8220;' ], # double opening
         [ /\b( )?\.{3}/, '\1&#8230;' ], # ellipsis
         [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
         [ /(^|[^"][>\s])([A-Z][A-Z0-9 ]+[A-Z0-9])([^<A-Za-z0-9]|$)/, '\1<span class="caps">\2</span>\3', :no_span_caps ], # 3+ uppercase caps
diff --git a/test/fixtures/wiki_contents.yml b/test/fixtures/wiki_contents.yml
index a230b9c08..6937dbd14 100644
--- a/test/fixtures/wiki_contents.yml
+++ b/test/fixtures/wiki_contents.yml
@@ -21,4 +21,14 @@ wiki_contents_002:
   version: 1
   author_id: 1
   comments: 
-  
\ No newline at end of file
+wiki_contents_003: 
+  text: |-
+    h1. Start page
+    
+    E-commerce web site start page
+  updated_on: 2007-03-08 00:18:07 +01:00
+  page_id: 3
+  id: 3
+  version: 1
+  author_id: 1
+  comments: 
diff --git a/test/fixtures/wiki_pages.yml b/test/fixtures/wiki_pages.yml
index ca9d6f5dc..ee260291d 100644
--- a/test/fixtures/wiki_pages.yml
+++ b/test/fixtures/wiki_pages.yml
@@ -9,4 +9,9 @@ wiki_pages_002:
   title: Another_page
   id: 2
   wiki_id: 1
+wiki_pages_003: 
+  created_on: 2007-03-08 00:18:07 +01:00
+  title: Start_page
+  id: 3
+  wiki_id: 2
   
\ No newline at end of file
diff --git a/test/fixtures/wikis.yml b/test/fixtures/wikis.yml
index ff7b4a1ae..dd1c55cea 100644
--- a/test/fixtures/wikis.yml
+++ b/test/fixtures/wikis.yml
@@ -4,3 +4,9 @@ wikis_001:
   start_page: CookBook documentation
   project_id: 1
   id: 1
+wikis_002: 
+  status: 1
+  start_page: Start page
+  project_id: 2
+  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 2af6c5599..33509cfc0 100644
--- a/test/unit/helpers/application_helper_test.rb
+++ b/test/unit/helpers/application_helper_test.rb
@@ -20,7 +20,7 @@ require File.dirname(__FILE__) + '/../../test_helper'
 class ApplicationHelperTest < HelperTestCase
   include ApplicationHelper
   include ActionView::Helpers::TextHelper
-  fixtures :projects, :repositories, :changesets, :trackers, :issue_statuses, :issues
+  fixtures :projects, :repositories, :changesets, :trackers, :issue_statuses, :issues, :documents, :versions, :wikis, :wiki_pages, :wiki_contents
 
   def setup
     super
@@ -66,12 +66,52 @@ class ApplicationHelperTest < HelperTestCase
   def test_redmine_links
     issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3}, 
                                :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
+    
     changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 1, :rev => 1},
                                    :class => 'changeset', :title => 'My very first commit')
     
+    document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
+                                             :class => 'document')
+    
+    version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
+                                  :class => 'version')
+    
     to_test = {
       '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
-      'r1' => changeset_link
+      'r1' => changeset_link,
+      'document#1' => document_link,
+      'document:"Test document"' => document_link,
+      'version#2' => version_link,
+      'version:1.0' => version_link,
+      'version:"1.0"' => version_link,
+      # escaping
+      '!#3.' => '#3.',
+      '!r1' => 'r1',
+      '!document#1' => 'document#1',
+      '!document:"Test document"' => 'document:"Test document"',
+      '!version#2' => 'version#2',
+      '!version:1.0' => 'version:1.0',
+      '!version:"1.0"' => 'version:"1.0"',
+    }
+    @project = Project.find(1)
+    to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
+  end
+  
+  def test_wiki_links
+    to_test = {
+      '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
+      '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
+      # page that doesn't exist
+      '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
+      '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
+      # link to another project wiki
+      '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
+      '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
+      '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
+      '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
+      '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
+      # escaping
+      '![[Another page|Page]]' => '[[Another page|Page]]',
     }
     @project = Project.find(1)
     to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
-- 
GitLab