diff -urN redmine-1.3.0/app/helpers/application_helper.rb redmine-1.3.0_macros_escaping/app/helpers/application_helper.rb --- redmine-1.3.0/app/helpers/application_helper.rb 2011-12-10 16:53:33.000000000 +0200 +++ redmine-1.3.0_macros_escaping/app/helpers/application_helper.rb 2011-12-27 00:21:55.751959515 +0200 @@ -19,6 +19,7 @@ require 'forwardable' require 'cgi' +require 'digest/md5' module ApplicationHelper include Redmine::WikiFormatting::Macros::Definitions @@ -487,6 +488,8 @@ project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) only_path = options.delete(:only_path) == false ? false : true + text, macros_grabbed = preprocess_macros(text) + options[:macros_grabbed] = macros_grabbed text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) @parsed_headings = [] @@ -497,6 +500,10 @@ end end + # Any macros not parsed by this point must be in
 blocks - and, hence, should be
+    # restored to their original state.
+    restore_macros(text, macros_grabbed) unless text.nil?
+
     if @parsed_headings.any?
       replace_toc(text, @parsed_headings)
     end
@@ -788,16 +795,35 @@
                 (
                 \{\{                        # opening tag
                 ([\w]+)                     # macro name
-                (\(([^\}]*)\))?             # optional arguments
+		(\((.*?)\))?                # optional arguments
                 \}\}                        # closing tag
-                )
-              /x unless const_defined?(:MACROS_RE)
+		)
+              /xm unless const_defined?(:MACROS_RE)
+
+  def preprocess_macros(text)
+    macros_grabbed = {}
+    text = text.gsub(MACROS_RE) do |s|
+     esc, all, macro = $1, $2, $3.downcase
+     if esc.nil? and (WikiExternalFilterHelper.has_macro macro rescue false)
+       args = $5
+       key = Digest::MD5.hexdigest("#{macro}:#{args}")
+       macros_grabbed[key] = {:macro => macro, :args => args, :raw => s}
+       "{{_macros_grabbed(#{key})}}"
+     else
+       s
+     end
+   end
+   [text, macros_grabbed]
+  end
 
   # Macros substitution
   def parse_macros(text, project, obj, attr, only_path, options)
     text.gsub!(MACROS_RE) do
       esc, all, macro = $1, $2, $3.downcase
       args = ($5 || '').split(',').each(&:strip)
+      if macro == '_macros_grabbed' and options[:macros_grabbed].member? args.first
+        macro, args = options[:macros_grabbed][args.first].values_at(:macro, :args)
+      end
       if esc.nil?
         begin
           exec_macro(macro, obj, args)
@@ -807,6 +833,18 @@
       else
         all
       end
+    end
+  end
+
+  # Macros restoration to their original state - used for 
 blocks.
+  def restore_macros(text, macros_grabbed)
+    text.gsub!(MACROS_RE) do
+      all, macro, args = $&, $3.downcase, $5
+      if macro == '_macros_grabbed' and macros_grabbed.member? args
+        "#{macros_grabbed[args].values_at(:raw)}"
+      else
+        all
+      end
     end
   end