diff app/helpers/application_helper.rb @ 909:cbb26bc654de redmine-1.3

Update to Redmine 1.3-stable branch (Redmine SVN rev 8964)
author Chris Cannam
date Fri, 24 Feb 2012 19:09:32 +0000
parents cbce1fd3b1b7
children 5e80956cc792 5f33065ddc4b
line wrap: on
line diff
--- a/app/helpers/application_helper.rb	Fri Feb 24 18:36:29 2012 +0000
+++ b/app/helpers/application_helper.rb	Fri Feb 24 19:09:32 2012 +0000
@@ -1,3 +1,5 @@
+# encoding: utf-8
+#
 # Redmine - project management software
 # Copyright (C) 2006-2011  Jean-Philippe Lang
 #
@@ -80,7 +82,7 @@
         subject = truncate(subject, :length => options[:truncate])
       end
     end
-    s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
+    s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
                                                  :class => issue.css_classes,
                                                  :title => title
     s << ": #{h subject}" if subject
@@ -95,8 +97,10 @@
   def link_to_attachment(attachment, options={})
     text = options.delete(:text) || attachment.filename
     action = options.delete(:download) ? 'download' : 'show'
-
-    link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
+    link_to(h(text),
+           {:controller => 'attachments', :action => action,
+            :id => attachment, :filename => attachment.filename },
+           options)
   end
 
   # Generates a link to a SCM revision
@@ -106,7 +110,7 @@
     text = options.delete(:text) || format_revision(revision)
     rev = revision.respond_to?(:identifier) ? revision.identifier : revision
 
-    link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev},
+    link_to(h(text), {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev},
             :title => l(:label_revision_id, format_revision(revision)))
   end
 
@@ -200,7 +204,7 @@
       end
       content << "</ul>\n"
     end
-    content
+    content.html_safe
   end
 
   # Renders flash messages
@@ -209,7 +213,7 @@
     flash.each do |k,v|
       s << content_tag('div', v, :class => "flash #{k}")
     end
-    s
+    s.html_safe
   end
 
   # Renders tabs and their content
@@ -233,7 +237,7 @@
         { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
       end
       s << '</select>'
-      s
+      s.html_safe
     end
   end
 
@@ -250,7 +254,7 @@
       tag_options.merge!(yield(project)) if block_given?
       s << content_tag('option', name_prefix + h(project), tag_options)
     end
-    s
+    s.html_safe
   end
 
   # Yields the given block for each project with its level in the tree
@@ -281,7 +285,7 @@
       end
       s << ("</li></ul>\n" * ancestors.size)
     end
-    s
+    s.html_safe
   end
 
   def principals_check_box_tags(name, principals)
@@ -289,6 +293,20 @@
     principals.sort.each do |principal|
       s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
     end
+    s.html_safe
+  end
+
+  # Returns a string for users/groups option tags
+  def principals_options_for_select(collection, selected=nil)
+    s = ''
+    groups = ''
+    collection.sort.each do |element|
+      selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
+      (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
+    end
+    unless groups.empty?
+      s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
+    end
     s
   end
 
@@ -308,11 +326,11 @@
   end
 
   def html_hours(text)
-    text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
+    text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
   end
 
   def authoring(created, author, options={})
-    l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
+    l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
   end
 
   def time_tag(time)
@@ -339,7 +357,10 @@
 
     html = ''
     if paginator.current.previous
-      html << link_to_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
+      # \xc2\xab(utf-8) = &#171;
+      html << link_to_content_update(
+                   "\xc2\xab " + l(:label_previous),
+                   url_param.merge(page_param => paginator.current.previous)) + ' '
     end
 
     html << (pagination_links_each(paginator, options) do |n|
@@ -347,7 +368,10 @@
     end || '')
 
     if paginator.current.next
-      html << ' ' + link_to_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
+      # \xc2\xbb(utf-8) = &#187;
+      html << ' ' + link_to_content_update(
+                      (l(:label_next) + " \xc2\xbb"),
+                      url_param.merge(page_param => paginator.current.next))
     end
 
     unless count.nil?
@@ -357,7 +381,7 @@
       end
     end
 
-    html
+    html.html_safe
   end
 
   def per_page_links(selected=nil)
@@ -367,22 +391,30 @@
     links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
   end
 
-  def reorder_links(name, url)
-    link_to(image_tag('2uparrow.png',   :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
-    link_to(image_tag('1uparrow.png',   :alt => l(:label_sort_higher)),  url.merge({"#{name}[move_to]" => 'higher'}),  :method => :post, :title => l(:label_sort_higher)) +
-    link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),   url.merge({"#{name}[move_to]" => 'lower'}),   :method => :post, :title => l(:label_sort_lower)) +
-    link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),  url.merge({"#{name}[move_to]" => 'lowest'}),  :method => :post, :title => l(:label_sort_lowest))
+  def reorder_links(name, url, method = :post)
+    link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
+            url.merge({"#{name}[move_to]" => 'highest'}),
+            :method => method, :title => l(:label_sort_highest)) +
+    link_to(image_tag('1uparrow.png',   :alt => l(:label_sort_higher)),
+            url.merge({"#{name}[move_to]" => 'higher'}),
+           :method => method, :title => l(:label_sort_higher)) +
+    link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
+            url.merge({"#{name}[move_to]" => 'lower'}),
+            :method => method, :title => l(:label_sort_lower)) +
+    link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
+            url.merge({"#{name}[move_to]" => 'lowest'}),
+           :method => method, :title => l(:label_sort_lowest))
   end
 
   def breadcrumb(*args)
     elements = args.flatten
-    elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
+    elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
   end
 
   def other_formats_links(&block)
-    concat('<p class="other-formats">' + l(:label_export_to))
+    concat('<p class="other-formats">'.html_safe + l(:label_export_to))
     yield Redmine::Views::OtherFormatsBuilder.new(self)
-    concat('</p>')
+    concat('</p>'.html_safe)
   end
 
   def page_header_title
@@ -395,22 +427,21 @@
         root = ancestors.shift
         b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
         if ancestors.size > 2
-          b << '&#8230;'
+          b << "\xe2\x80\xa6"
           ancestors = ancestors[-2, 2]
         end
         b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
       end
       b << h(@project)
-      b.join(' &#187; ')
+      b.join(" \xc2\xbb ").html_safe
     end
   end
 
   def html_title(*args)
     if args.empty?
-      title = []
+      title = @html_title || []
       title << @project.name if @project
-      title += @html_title if @html_title
-      title << Setting.app_title
+      title << Setting.app_title unless Setting.app_title == title.last
       title.select {|t| !t.blank? }.join(' - ')
     else
       @html_title ||= []
@@ -456,11 +487,12 @@
     project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
     only_path = options.delete(:only_path) == false ? false : true
 
-    text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
+    text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
 
     @parsed_headings = []
+    @current_section = 0 if options[:edit_section_links]
     text = parse_non_pre_blocks(text) do |text|
-      [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings].each do |method_name|
+      [:parse_sections, :parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros, :parse_headings].each do |method_name|
         send method_name, text, project, obj, attr, only_path, options
       end
     end
@@ -498,26 +530,26 @@
     while tag = tags.pop
       parsed << "</#{tag}>"
     end
-    parsed
+    parsed.html_safe
   end
 
   def parse_inline_attachments(text, project, obj, attr, only_path, options)
     # when using an image link, try to use an attachment, if possible
     if options[:attachments] || (obj && obj.respond_to?(:attachments))
-      attachments = nil
-      text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
+      attachments = options[:attachments] || obj.attachments
+      text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
         filename, ext, alt, alttext = $1.downcase, $2, $3, $4
-        attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
         # search for the picture in attachments
-        if found = attachments.detect { |att| att.filename.downcase == filename }
-          image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
+        if found = Attachment.latest_attach(attachments, filename)
+          image_url = url_for :only_path => only_path, :controller => 'attachments',
+                              :action => 'download', :id => found
           desc = found.description.to_s.gsub('"', '')
           if !desc.blank? && alttext.blank?
             alt = " title=\"#{desc}\" alt=\"#{desc}\""
           end
-          "src=\"#{image_url}\"#{alt}"
+          "src=\"#{image_url}\"#{alt}".html_safe
         else
-          m
+          m.html_safe
         end
       end
     end
@@ -550,22 +582,27 @@
           if page =~ /^(.+?)\#(.+)$/
             page, anchor = $1, $2
           end
+          anchor = sanitize_anchor_name(anchor) if anchor.present?
           # check if page exists
           wiki_page = link_project.wiki.find_page(page)
-          url = case options[:wiki_links]
-            when :local; "#{title}.html"
-            when :anchor; "##{title}"   # used for single-file wiki export
+          url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
+            "##{anchor}"
+          else
+            case options[:wiki_links]
+            when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
+            when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
             else
               wiki_page_id = page.present? ? Wiki.titleize(page) : nil
               url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
             end
-          link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
+          end
+          link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
         else
           # project or wiki doesn't exist
-          all
+          all.html_safe
         end
       else
-        all
+        all.html_safe
       end
     end
   end
@@ -603,7 +640,7 @@
   #     identifier:version:1.0.0
   #     identifier:source:some/file
   def parse_redmine_links(text, project, obj, attr, only_path, options)
-    text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
+    text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|forum|news|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
       leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
       link = nil
       if project_identifier
@@ -613,7 +650,7 @@
         if prefix.nil? && sep == 'r'
           # project.changesets.visible raises an SQL error because of a double join on repositories
           if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
-            link = link_to("#{project_prefix}r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
+            link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
                                       :class => 'changeset',
                                       :title => truncate_single_line(changeset.comments, :length => 100))
           end
@@ -640,6 +677,16 @@
             if message = Message.visible.find_by_id(oid, :include => :parent)
               link = link_to_message(message, {:only_path => only_path}, :class => 'message')
             end
+          when 'forum'
+            if board = Board.visible.find_by_id(oid)
+              link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
+                                             :class => 'board'
+            end
+          when 'news'
+            if news = News.visible.find_by_id(oid)
+              link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
+                                            :class => 'news'
+            end
           when 'project'
             if p = Project.visible.find_by_id(oid)
               link = link_to_project(p, {:only_path => only_path}, :class => 'project')
@@ -659,11 +706,21 @@
               link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
                                               :class => 'version'
             end
+          when 'forum'
+            if project && board = project.boards.visible.find_by_name(name)
+              link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
+                                             :class => 'board'
+            end
+          when 'news'
+            if project && news = project.news.visible.find_by_title(name)
+              link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
+                                            :class => 'news'
+            end
           when 'commit'
             if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
               link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
                                            :class => 'changeset',
-                                           :title => truncate_single_line(changeset.comments, :length => 100)
+                                           :title => truncate_single_line(h(changeset.comments), :length => 100)
             end
           when 'source', 'export'
             if project && project.repository && User.current.allowed_to?(:browse_repository, project)
@@ -689,11 +746,26 @@
           end
         end
       end
-      leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")
+      (leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")).html_safe
     end
   end
 
-  HEADING_RE = /<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>/i unless const_defined?(:HEADING_RE)
+  HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
+
+  def parse_sections(text, project, obj, attr, only_path, options)
+    return unless options[:edit_section_links]
+    text.gsub!(HEADING_RE) do
+      @current_section += 1
+      if @current_section > 1
+        content_tag('div',
+          link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
+          :class => 'contextual',
+          :title => l(:button_edit_section)) + $1
+      else
+        $1
+      end
+    end
+  end
 
   # Headings and TOC
   # Adds ids and links to headings unless options[:headings] is set to false
@@ -701,14 +773,43 @@
     return if options[:headings] == false
 
     text.gsub!(HEADING_RE) do
-      level, attrs, content = $1.to_i, $2, $3
+      level, attrs, content = $2.to_i, $3, $4
       item = strip_tags(content).strip
-      anchor = item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
+      anchor = sanitize_anchor_name(item)
+      # used for single-file wiki export
+      anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
       @parsed_headings << [level, anchor, item]
       "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
     end
   end
 
+  MACROS_RE = /
+                (!)?                        # escaping
+                (
+                \{\{                        # opening tag
+                ([\w]+)                     # macro name
+                (\(([^\}]*)\))?             # optional arguments
+                \}\}                        # closing tag
+                )
+              /x unless const_defined?(:MACROS_RE)
+
+  # 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 esc.nil?
+        begin
+          exec_macro(macro, obj, args)
+        rescue => e
+          "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
+        end || all
+      else
+        all
+      end
+    end
+  end
+
   TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
 
   # Renders the TOC with given headings
@@ -747,7 +848,8 @@
     text.to_s.
       gsub(/\r\n?/, "\n").                    # \r\n and \r -> \n
       gsub(/\n\n+/, "<br /><br />").          # 2+ newline  -> 2 br
-      gsub(/([^\n]\n)(?=[^\n])/, '\1<br />')  # 1 newline   -> br
+      gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline   -> br
+      html_safe
   end
 
   def lang_options_for_select(blank=true)
@@ -760,10 +862,20 @@
     content_tag("label", label_text)
   end
 
-  def labelled_tabular_form_for(name, object, options, &proc)
+  def labelled_tabular_form_for(*args, &proc)
+    args << {} unless args.last.is_a?(Hash)
+    options = args.last
     options[:html] ||= {}
     options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
-    form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
+    options.merge!({:builder => TabularFormBuilder})
+    form_for(*args, &proc)
+  end
+
+  def labelled_form_for(*args, &proc)
+    args << {} unless args.last.is_a?(Hash)
+    options = args.last
+    options.merge!({:builder => TabularFormBuilder})
+    form_for(*args, &proc)
   end
 
   def back_url_hidden_field_tag
@@ -774,7 +886,7 @@
 
   def check_all_links(form_name)
     link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
-    " | " +
+    " | ".html_safe +
     link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
   end
 
@@ -787,11 +899,11 @@
     legend = options[:legend] || ''
     content_tag('table',
       content_tag('tr',
-        (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
-        (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
-        (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
-      ), :class => 'progress', :style => "width: #{width};") +
-      content_tag('p', legend, :class => 'pourcent')
+        (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
+        (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
+        (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
+      ), :class => 'progress', :style => "width: #{width};").html_safe +
+      content_tag('p', legend, :class => 'pourcent').html_safe
   end
 
   def checked_image(checked=true)
@@ -829,7 +941,7 @@
       options[:class] << ' disabled'
       url = '#'
     end
-    link_to name, url, options
+    link_to h(name), url, options
   end
 
   def calendar_for(field_id)
@@ -872,6 +984,10 @@
     (@has_content && @has_content[name]) || false
   end
 
+  def email_delivery_enabled?
+    !!ActionMailer::Base.perform_deliveries
+  end
+
   # Returns the avatar image tag for the given +user+ if avatars are enabled
   # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
   def avatar(user, options = { })
@@ -889,21 +1005,25 @@
     end
   end
 
+  def sanitize_anchor_name(anchor)
+    anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
+  end
+
   # Returns the javascript tags that are included in the html layout head
   def javascript_heads
     tags = javascript_include_tag(:defaults)
     unless User.current.pref.warn_on_leaving_unsaved == '0'
-      tags << "\n" + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
+      tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
     end
     tags
   end
 
   def favicon
-    "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
+    "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
   end
 
   def robot_exclusion_tag
-    '<meta name="robots" content="noindex,follow,noarchive" />'
+    '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
   end
 
   # Returns true if arg is expected in the API response