##// END OF EJS Templates
Fixed that CSS class for done ratio is not properly generated (#15523)....
Jean-Philippe Lang -
r12098:e494983e1f74
parent child
Show More
@@ -1,1297 +1,1297
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2013 Jean-Philippe Lang
4 # Copyright (C) 2006-2013 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require 'forwardable'
20 require 'forwardable'
21 require 'cgi'
21 require 'cgi'
22
22
23 module ApplicationHelper
23 module ApplicationHelper
24 include Redmine::WikiFormatting::Macros::Definitions
24 include Redmine::WikiFormatting::Macros::Definitions
25 include Redmine::I18n
25 include Redmine::I18n
26 include GravatarHelper::PublicMethods
26 include GravatarHelper::PublicMethods
27 include Redmine::Pagination::Helper
27 include Redmine::Pagination::Helper
28
28
29 extend Forwardable
29 extend Forwardable
30 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
30 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
31
31
32 # Return true if user is authorized for controller/action, otherwise false
32 # Return true if user is authorized for controller/action, otherwise false
33 def authorize_for(controller, action)
33 def authorize_for(controller, action)
34 User.current.allowed_to?({:controller => controller, :action => action}, @project)
34 User.current.allowed_to?({:controller => controller, :action => action}, @project)
35 end
35 end
36
36
37 # Display a link if user is authorized
37 # Display a link if user is authorized
38 #
38 #
39 # @param [String] name Anchor text (passed to link_to)
39 # @param [String] name Anchor text (passed to link_to)
40 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
40 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
41 # @param [optional, Hash] html_options Options passed to link_to
41 # @param [optional, Hash] html_options Options passed to link_to
42 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
42 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
43 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
43 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
44 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
44 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
45 end
45 end
46
46
47 # Displays a link to user's account page if active
47 # Displays a link to user's account page if active
48 def link_to_user(user, options={})
48 def link_to_user(user, options={})
49 if user.is_a?(User)
49 if user.is_a?(User)
50 name = h(user.name(options[:format]))
50 name = h(user.name(options[:format]))
51 if user.active? || (User.current.admin? && user.logged?)
51 if user.active? || (User.current.admin? && user.logged?)
52 link_to name, user_path(user), :class => user.css_classes
52 link_to name, user_path(user), :class => user.css_classes
53 else
53 else
54 name
54 name
55 end
55 end
56 else
56 else
57 h(user.to_s)
57 h(user.to_s)
58 end
58 end
59 end
59 end
60
60
61 # Displays a link to +issue+ with its subject.
61 # Displays a link to +issue+ with its subject.
62 # Examples:
62 # Examples:
63 #
63 #
64 # link_to_issue(issue) # => Defect #6: This is the subject
64 # link_to_issue(issue) # => Defect #6: This is the subject
65 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
65 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
66 # link_to_issue(issue, :subject => false) # => Defect #6
66 # link_to_issue(issue, :subject => false) # => Defect #6
67 # link_to_issue(issue, :project => true) # => Foo - Defect #6
67 # link_to_issue(issue, :project => true) # => Foo - Defect #6
68 # link_to_issue(issue, :subject => false, :tracker => false) # => #6
68 # link_to_issue(issue, :subject => false, :tracker => false) # => #6
69 #
69 #
70 def link_to_issue(issue, options={})
70 def link_to_issue(issue, options={})
71 title = nil
71 title = nil
72 subject = nil
72 subject = nil
73 text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}"
73 text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}"
74 if options[:subject] == false
74 if options[:subject] == false
75 title = truncate(issue.subject, :length => 60)
75 title = truncate(issue.subject, :length => 60)
76 else
76 else
77 subject = issue.subject
77 subject = issue.subject
78 if options[:truncate]
78 if options[:truncate]
79 subject = truncate(subject, :length => options[:truncate])
79 subject = truncate(subject, :length => options[:truncate])
80 end
80 end
81 end
81 end
82 s = link_to text, issue_path(issue), :class => issue.css_classes, :title => title
82 s = link_to text, issue_path(issue), :class => issue.css_classes, :title => title
83 s << h(": #{subject}") if subject
83 s << h(": #{subject}") if subject
84 s = h("#{issue.project} - ") + s if options[:project]
84 s = h("#{issue.project} - ") + s if options[:project]
85 s
85 s
86 end
86 end
87
87
88 # Generates a link to an attachment.
88 # Generates a link to an attachment.
89 # Options:
89 # Options:
90 # * :text - Link text (default to attachment filename)
90 # * :text - Link text (default to attachment filename)
91 # * :download - Force download (default: false)
91 # * :download - Force download (default: false)
92 def link_to_attachment(attachment, options={})
92 def link_to_attachment(attachment, options={})
93 text = options.delete(:text) || attachment.filename
93 text = options.delete(:text) || attachment.filename
94 route_method = options.delete(:download) ? :download_named_attachment_path : :named_attachment_path
94 route_method = options.delete(:download) ? :download_named_attachment_path : :named_attachment_path
95 html_options = options.slice!(:only_path)
95 html_options = options.slice!(:only_path)
96 url = send(route_method, attachment, attachment.filename, options)
96 url = send(route_method, attachment, attachment.filename, options)
97 link_to text, url, html_options
97 link_to text, url, html_options
98 end
98 end
99
99
100 # Generates a link to a SCM revision
100 # Generates a link to a SCM revision
101 # Options:
101 # Options:
102 # * :text - Link text (default to the formatted revision)
102 # * :text - Link text (default to the formatted revision)
103 def link_to_revision(revision, repository, options={})
103 def link_to_revision(revision, repository, options={})
104 if repository.is_a?(Project)
104 if repository.is_a?(Project)
105 repository = repository.repository
105 repository = repository.repository
106 end
106 end
107 text = options.delete(:text) || format_revision(revision)
107 text = options.delete(:text) || format_revision(revision)
108 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
108 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
109 link_to(
109 link_to(
110 h(text),
110 h(text),
111 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
111 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
112 :title => l(:label_revision_id, format_revision(revision))
112 :title => l(:label_revision_id, format_revision(revision))
113 )
113 )
114 end
114 end
115
115
116 # Generates a link to a message
116 # Generates a link to a message
117 def link_to_message(message, options={}, html_options = nil)
117 def link_to_message(message, options={}, html_options = nil)
118 link_to(
118 link_to(
119 truncate(message.subject, :length => 60),
119 truncate(message.subject, :length => 60),
120 board_message_path(message.board_id, message.parent_id || message.id, {
120 board_message_path(message.board_id, message.parent_id || message.id, {
121 :r => (message.parent_id && message.id),
121 :r => (message.parent_id && message.id),
122 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
122 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
123 }.merge(options)),
123 }.merge(options)),
124 html_options
124 html_options
125 )
125 )
126 end
126 end
127
127
128 # Generates a link to a project if active
128 # Generates a link to a project if active
129 # Examples:
129 # Examples:
130 #
130 #
131 # link_to_project(project) # => link to the specified project overview
131 # link_to_project(project) # => link to the specified project overview
132 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
132 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
133 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
133 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
134 #
134 #
135 def link_to_project(project, options={}, html_options = nil)
135 def link_to_project(project, options={}, html_options = nil)
136 if project.archived?
136 if project.archived?
137 h(project.name)
137 h(project.name)
138 elsif options.key?(:action)
138 elsif options.key?(:action)
139 ActiveSupport::Deprecation.warn "#link_to_project with :action option is deprecated and will be removed in Redmine 3.0."
139 ActiveSupport::Deprecation.warn "#link_to_project with :action option is deprecated and will be removed in Redmine 3.0."
140 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
140 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
141 link_to project.name, url, html_options
141 link_to project.name, url, html_options
142 else
142 else
143 link_to project.name, project_path(project, options), html_options
143 link_to project.name, project_path(project, options), html_options
144 end
144 end
145 end
145 end
146
146
147 # Generates a link to a project settings if active
147 # Generates a link to a project settings if active
148 def link_to_project_settings(project, options={}, html_options=nil)
148 def link_to_project_settings(project, options={}, html_options=nil)
149 if project.active?
149 if project.active?
150 link_to project.name, settings_project_path(project, options), html_options
150 link_to project.name, settings_project_path(project, options), html_options
151 elsif project.archived?
151 elsif project.archived?
152 h(project.name)
152 h(project.name)
153 else
153 else
154 link_to project.name, project_path(project, options), html_options
154 link_to project.name, project_path(project, options), html_options
155 end
155 end
156 end
156 end
157
157
158 # Helper that formats object for html or text rendering
158 # Helper that formats object for html or text rendering
159 def format_object(object, html=true)
159 def format_object(object, html=true)
160 case object.class.name
160 case object.class.name
161 when 'Time'
161 when 'Time'
162 format_time(object)
162 format_time(object)
163 when 'Date'
163 when 'Date'
164 format_date(object)
164 format_date(object)
165 when 'Fixnum'
165 when 'Fixnum'
166 object.to_s
166 object.to_s
167 when 'Float'
167 when 'Float'
168 sprintf "%.2f", object
168 sprintf "%.2f", object
169 when 'User'
169 when 'User'
170 html ? link_to_user(object) : object.to_s
170 html ? link_to_user(object) : object.to_s
171 when 'Project'
171 when 'Project'
172 html ? link_to_project(object) : object.to_s
172 html ? link_to_project(object) : object.to_s
173 when 'Version'
173 when 'Version'
174 html ? link_to(object.name, version_path(object)) : version.to_s
174 html ? link_to(object.name, version_path(object)) : version.to_s
175 when 'TrueClass'
175 when 'TrueClass'
176 l(:general_text_Yes)
176 l(:general_text_Yes)
177 when 'FalseClass'
177 when 'FalseClass'
178 l(:general_text_No)
178 l(:general_text_No)
179 when 'Issue'
179 when 'Issue'
180 object.visible? && html ? link_to_issue(object) : "##{object.id}"
180 object.visible? && html ? link_to_issue(object) : "##{object.id}"
181 else
181 else
182 html ? h(object) : object.to_s
182 html ? h(object) : object.to_s
183 end
183 end
184 end
184 end
185
185
186 def wiki_page_path(page, options={})
186 def wiki_page_path(page, options={})
187 url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
187 url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
188 end
188 end
189
189
190 def thumbnail_tag(attachment)
190 def thumbnail_tag(attachment)
191 link_to image_tag(thumbnail_path(attachment)),
191 link_to image_tag(thumbnail_path(attachment)),
192 named_attachment_path(attachment, attachment.filename),
192 named_attachment_path(attachment, attachment.filename),
193 :title => attachment.filename
193 :title => attachment.filename
194 end
194 end
195
195
196 def toggle_link(name, id, options={})
196 def toggle_link(name, id, options={})
197 onclick = "$('##{id}').toggle(); "
197 onclick = "$('##{id}').toggle(); "
198 onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ")
198 onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ")
199 onclick << "return false;"
199 onclick << "return false;"
200 link_to(name, "#", :onclick => onclick)
200 link_to(name, "#", :onclick => onclick)
201 end
201 end
202
202
203 def image_to_function(name, function, html_options = {})
203 def image_to_function(name, function, html_options = {})
204 html_options.symbolize_keys!
204 html_options.symbolize_keys!
205 tag(:input, html_options.merge({
205 tag(:input, html_options.merge({
206 :type => "image", :src => image_path(name),
206 :type => "image", :src => image_path(name),
207 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
207 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
208 }))
208 }))
209 end
209 end
210
210
211 def format_activity_title(text)
211 def format_activity_title(text)
212 h(truncate_single_line(text, :length => 100))
212 h(truncate_single_line(text, :length => 100))
213 end
213 end
214
214
215 def format_activity_day(date)
215 def format_activity_day(date)
216 date == User.current.today ? l(:label_today).titleize : format_date(date)
216 date == User.current.today ? l(:label_today).titleize : format_date(date)
217 end
217 end
218
218
219 def format_activity_description(text)
219 def format_activity_description(text)
220 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
220 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
221 ).gsub(/[\r\n]+/, "<br />").html_safe
221 ).gsub(/[\r\n]+/, "<br />").html_safe
222 end
222 end
223
223
224 def format_version_name(version)
224 def format_version_name(version)
225 if version.project == @project
225 if version.project == @project
226 h(version)
226 h(version)
227 else
227 else
228 h("#{version.project} - #{version}")
228 h("#{version.project} - #{version}")
229 end
229 end
230 end
230 end
231
231
232 def due_date_distance_in_words(date)
232 def due_date_distance_in_words(date)
233 if date
233 if date
234 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
234 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
235 end
235 end
236 end
236 end
237
237
238 # Renders a tree of projects as a nested set of unordered lists
238 # Renders a tree of projects as a nested set of unordered lists
239 # The given collection may be a subset of the whole project tree
239 # The given collection may be a subset of the whole project tree
240 # (eg. some intermediate nodes are private and can not be seen)
240 # (eg. some intermediate nodes are private and can not be seen)
241 def render_project_nested_lists(projects)
241 def render_project_nested_lists(projects)
242 s = ''
242 s = ''
243 if projects.any?
243 if projects.any?
244 ancestors = []
244 ancestors = []
245 original_project = @project
245 original_project = @project
246 projects.sort_by(&:lft).each do |project|
246 projects.sort_by(&:lft).each do |project|
247 # set the project environment to please macros.
247 # set the project environment to please macros.
248 @project = project
248 @project = project
249 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
249 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
250 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
250 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
251 else
251 else
252 ancestors.pop
252 ancestors.pop
253 s << "</li>"
253 s << "</li>"
254 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
254 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
255 ancestors.pop
255 ancestors.pop
256 s << "</ul></li>\n"
256 s << "</ul></li>\n"
257 end
257 end
258 end
258 end
259 classes = (ancestors.empty? ? 'root' : 'child')
259 classes = (ancestors.empty? ? 'root' : 'child')
260 s << "<li class='#{classes}'><div class='#{classes}'>"
260 s << "<li class='#{classes}'><div class='#{classes}'>"
261 s << h(block_given? ? yield(project) : project.name)
261 s << h(block_given? ? yield(project) : project.name)
262 s << "</div>\n"
262 s << "</div>\n"
263 ancestors << project
263 ancestors << project
264 end
264 end
265 s << ("</li></ul>\n" * ancestors.size)
265 s << ("</li></ul>\n" * ancestors.size)
266 @project = original_project
266 @project = original_project
267 end
267 end
268 s.html_safe
268 s.html_safe
269 end
269 end
270
270
271 def render_page_hierarchy(pages, node=nil, options={})
271 def render_page_hierarchy(pages, node=nil, options={})
272 content = ''
272 content = ''
273 if pages[node]
273 if pages[node]
274 content << "<ul class=\"pages-hierarchy\">\n"
274 content << "<ul class=\"pages-hierarchy\">\n"
275 pages[node].each do |page|
275 pages[node].each do |page|
276 content << "<li>"
276 content << "<li>"
277 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil},
277 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil},
278 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
278 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
279 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
279 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
280 content << "</li>\n"
280 content << "</li>\n"
281 end
281 end
282 content << "</ul>\n"
282 content << "</ul>\n"
283 end
283 end
284 content.html_safe
284 content.html_safe
285 end
285 end
286
286
287 # Renders flash messages
287 # Renders flash messages
288 def render_flash_messages
288 def render_flash_messages
289 s = ''
289 s = ''
290 flash.each do |k,v|
290 flash.each do |k,v|
291 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
291 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
292 end
292 end
293 s.html_safe
293 s.html_safe
294 end
294 end
295
295
296 # Renders tabs and their content
296 # Renders tabs and their content
297 def render_tabs(tabs)
297 def render_tabs(tabs)
298 if tabs.any?
298 if tabs.any?
299 render :partial => 'common/tabs', :locals => {:tabs => tabs}
299 render :partial => 'common/tabs', :locals => {:tabs => tabs}
300 else
300 else
301 content_tag 'p', l(:label_no_data), :class => "nodata"
301 content_tag 'p', l(:label_no_data), :class => "nodata"
302 end
302 end
303 end
303 end
304
304
305 # Renders the project quick-jump box
305 # Renders the project quick-jump box
306 def render_project_jump_box
306 def render_project_jump_box
307 return unless User.current.logged?
307 return unless User.current.logged?
308 projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq
308 projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq
309 if projects.any?
309 if projects.any?
310 options =
310 options =
311 ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
311 ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
312 '<option value="" disabled="disabled">---</option>').html_safe
312 '<option value="" disabled="disabled">---</option>').html_safe
313
313
314 options << project_tree_options_for_select(projects, :selected => @project) do |p|
314 options << project_tree_options_for_select(projects, :selected => @project) do |p|
315 { :value => project_path(:id => p, :jump => current_menu_item) }
315 { :value => project_path(:id => p, :jump => current_menu_item) }
316 end
316 end
317
317
318 select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
318 select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
319 end
319 end
320 end
320 end
321
321
322 def project_tree_options_for_select(projects, options = {})
322 def project_tree_options_for_select(projects, options = {})
323 s = ''
323 s = ''
324 project_tree(projects) do |project, level|
324 project_tree(projects) do |project, level|
325 name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
325 name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
326 tag_options = {:value => project.id}
326 tag_options = {:value => project.id}
327 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
327 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
328 tag_options[:selected] = 'selected'
328 tag_options[:selected] = 'selected'
329 else
329 else
330 tag_options[:selected] = nil
330 tag_options[:selected] = nil
331 end
331 end
332 tag_options.merge!(yield(project)) if block_given?
332 tag_options.merge!(yield(project)) if block_given?
333 s << content_tag('option', name_prefix + h(project), tag_options)
333 s << content_tag('option', name_prefix + h(project), tag_options)
334 end
334 end
335 s.html_safe
335 s.html_safe
336 end
336 end
337
337
338 # Yields the given block for each project with its level in the tree
338 # Yields the given block for each project with its level in the tree
339 #
339 #
340 # Wrapper for Project#project_tree
340 # Wrapper for Project#project_tree
341 def project_tree(projects, &block)
341 def project_tree(projects, &block)
342 Project.project_tree(projects, &block)
342 Project.project_tree(projects, &block)
343 end
343 end
344
344
345 def principals_check_box_tags(name, principals)
345 def principals_check_box_tags(name, principals)
346 s = ''
346 s = ''
347 principals.each do |principal|
347 principals.each do |principal|
348 s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } #{h principal}</label>\n"
348 s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } #{h principal}</label>\n"
349 end
349 end
350 s.html_safe
350 s.html_safe
351 end
351 end
352
352
353 # Returns a string for users/groups option tags
353 # Returns a string for users/groups option tags
354 def principals_options_for_select(collection, selected=nil)
354 def principals_options_for_select(collection, selected=nil)
355 s = ''
355 s = ''
356 if collection.include?(User.current)
356 if collection.include?(User.current)
357 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
357 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
358 end
358 end
359 groups = ''
359 groups = ''
360 collection.sort.each do |element|
360 collection.sort.each do |element|
361 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) || element.id.to_s == selected
361 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) || element.id.to_s == selected
362 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
362 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
363 end
363 end
364 unless groups.empty?
364 unless groups.empty?
365 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
365 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
366 end
366 end
367 s.html_safe
367 s.html_safe
368 end
368 end
369
369
370 # Options for the new membership projects combo-box
370 # Options for the new membership projects combo-box
371 def options_for_membership_project_select(principal, projects)
371 def options_for_membership_project_select(principal, projects)
372 options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
372 options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
373 options << project_tree_options_for_select(projects) do |p|
373 options << project_tree_options_for_select(projects) do |p|
374 {:disabled => principal.projects.to_a.include?(p)}
374 {:disabled => principal.projects.to_a.include?(p)}
375 end
375 end
376 options
376 options
377 end
377 end
378
378
379 def option_tag(name, text, value, selected=nil, options={})
379 def option_tag(name, text, value, selected=nil, options={})
380 content_tag 'option', value, options.merge(:value => value, :selected => (value == selected))
380 content_tag 'option', value, options.merge(:value => value, :selected => (value == selected))
381 end
381 end
382
382
383 # Truncates and returns the string as a single line
383 # Truncates and returns the string as a single line
384 def truncate_single_line(string, *args)
384 def truncate_single_line(string, *args)
385 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
385 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
386 end
386 end
387
387
388 # Truncates at line break after 250 characters or options[:length]
388 # Truncates at line break after 250 characters or options[:length]
389 def truncate_lines(string, options={})
389 def truncate_lines(string, options={})
390 length = options[:length] || 250
390 length = options[:length] || 250
391 if string.to_s =~ /\A(.{#{length}}.*?)$/m
391 if string.to_s =~ /\A(.{#{length}}.*?)$/m
392 "#{$1}..."
392 "#{$1}..."
393 else
393 else
394 string
394 string
395 end
395 end
396 end
396 end
397
397
398 def anchor(text)
398 def anchor(text)
399 text.to_s.gsub(' ', '_')
399 text.to_s.gsub(' ', '_')
400 end
400 end
401
401
402 def html_hours(text)
402 def html_hours(text)
403 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
403 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
404 end
404 end
405
405
406 def authoring(created, author, options={})
406 def authoring(created, author, options={})
407 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
407 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
408 end
408 end
409
409
410 def time_tag(time)
410 def time_tag(time)
411 text = distance_of_time_in_words(Time.now, time)
411 text = distance_of_time_in_words(Time.now, time)
412 if @project
412 if @project
413 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
413 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
414 else
414 else
415 content_tag('abbr', text, :title => format_time(time))
415 content_tag('abbr', text, :title => format_time(time))
416 end
416 end
417 end
417 end
418
418
419 def syntax_highlight_lines(name, content)
419 def syntax_highlight_lines(name, content)
420 lines = []
420 lines = []
421 syntax_highlight(name, content).each_line { |line| lines << line }
421 syntax_highlight(name, content).each_line { |line| lines << line }
422 lines
422 lines
423 end
423 end
424
424
425 def syntax_highlight(name, content)
425 def syntax_highlight(name, content)
426 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
426 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
427 end
427 end
428
428
429 def to_path_param(path)
429 def to_path_param(path)
430 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
430 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
431 str.blank? ? nil : str
431 str.blank? ? nil : str
432 end
432 end
433
433
434 def reorder_links(name, url, method = :post)
434 def reorder_links(name, url, method = :post)
435 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
435 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
436 url.merge({"#{name}[move_to]" => 'highest'}),
436 url.merge({"#{name}[move_to]" => 'highest'}),
437 :method => method, :title => l(:label_sort_highest)) +
437 :method => method, :title => l(:label_sort_highest)) +
438 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
438 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
439 url.merge({"#{name}[move_to]" => 'higher'}),
439 url.merge({"#{name}[move_to]" => 'higher'}),
440 :method => method, :title => l(:label_sort_higher)) +
440 :method => method, :title => l(:label_sort_higher)) +
441 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
441 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
442 url.merge({"#{name}[move_to]" => 'lower'}),
442 url.merge({"#{name}[move_to]" => 'lower'}),
443 :method => method, :title => l(:label_sort_lower)) +
443 :method => method, :title => l(:label_sort_lower)) +
444 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
444 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
445 url.merge({"#{name}[move_to]" => 'lowest'}),
445 url.merge({"#{name}[move_to]" => 'lowest'}),
446 :method => method, :title => l(:label_sort_lowest))
446 :method => method, :title => l(:label_sort_lowest))
447 end
447 end
448
448
449 def breadcrumb(*args)
449 def breadcrumb(*args)
450 elements = args.flatten
450 elements = args.flatten
451 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
451 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
452 end
452 end
453
453
454 def other_formats_links(&block)
454 def other_formats_links(&block)
455 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
455 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
456 yield Redmine::Views::OtherFormatsBuilder.new(self)
456 yield Redmine::Views::OtherFormatsBuilder.new(self)
457 concat('</p>'.html_safe)
457 concat('</p>'.html_safe)
458 end
458 end
459
459
460 def page_header_title
460 def page_header_title
461 if @project.nil? || @project.new_record?
461 if @project.nil? || @project.new_record?
462 h(Setting.app_title)
462 h(Setting.app_title)
463 else
463 else
464 b = []
464 b = []
465 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
465 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
466 if ancestors.any?
466 if ancestors.any?
467 root = ancestors.shift
467 root = ancestors.shift
468 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
468 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
469 if ancestors.size > 2
469 if ancestors.size > 2
470 b << "\xe2\x80\xa6"
470 b << "\xe2\x80\xa6"
471 ancestors = ancestors[-2, 2]
471 ancestors = ancestors[-2, 2]
472 end
472 end
473 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
473 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
474 end
474 end
475 b << h(@project)
475 b << h(@project)
476 b.join(" \xc2\xbb ").html_safe
476 b.join(" \xc2\xbb ").html_safe
477 end
477 end
478 end
478 end
479
479
480 # Returns a h2 tag and sets the html title with the given arguments
480 # Returns a h2 tag and sets the html title with the given arguments
481 def title(*args)
481 def title(*args)
482 strings = args.map do |arg|
482 strings = args.map do |arg|
483 if arg.is_a?(Array) && arg.size >= 2
483 if arg.is_a?(Array) && arg.size >= 2
484 link_to(*arg)
484 link_to(*arg)
485 else
485 else
486 h(arg.to_s)
486 h(arg.to_s)
487 end
487 end
488 end
488 end
489 html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
489 html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
490 content_tag('h2', strings.join(' &#187; ').html_safe)
490 content_tag('h2', strings.join(' &#187; ').html_safe)
491 end
491 end
492
492
493 # Sets the html title
493 # Sets the html title
494 # Returns the html title when called without arguments
494 # Returns the html title when called without arguments
495 # Current project name and app_title and automatically appended
495 # Current project name and app_title and automatically appended
496 # Exemples:
496 # Exemples:
497 # html_title 'Foo', 'Bar'
497 # html_title 'Foo', 'Bar'
498 # html_title # => 'Foo - Bar - My Project - Redmine'
498 # html_title # => 'Foo - Bar - My Project - Redmine'
499 def html_title(*args)
499 def html_title(*args)
500 if args.empty?
500 if args.empty?
501 title = @html_title || []
501 title = @html_title || []
502 title << @project.name if @project
502 title << @project.name if @project
503 title << Setting.app_title unless Setting.app_title == title.last
503 title << Setting.app_title unless Setting.app_title == title.last
504 title.reject(&:blank?).join(' - ')
504 title.reject(&:blank?).join(' - ')
505 else
505 else
506 @html_title ||= []
506 @html_title ||= []
507 @html_title += args
507 @html_title += args
508 end
508 end
509 end
509 end
510
510
511 # Returns the theme, controller name, and action as css classes for the
511 # Returns the theme, controller name, and action as css classes for the
512 # HTML body.
512 # HTML body.
513 def body_css_classes
513 def body_css_classes
514 css = []
514 css = []
515 if theme = Redmine::Themes.theme(Setting.ui_theme)
515 if theme = Redmine::Themes.theme(Setting.ui_theme)
516 css << 'theme-' + theme.name
516 css << 'theme-' + theme.name
517 end
517 end
518
518
519 css << 'project-' + @project.identifier if @project && @project.identifier.present?
519 css << 'project-' + @project.identifier if @project && @project.identifier.present?
520 css << 'controller-' + controller_name
520 css << 'controller-' + controller_name
521 css << 'action-' + action_name
521 css << 'action-' + action_name
522 css.join(' ')
522 css.join(' ')
523 end
523 end
524
524
525 def accesskey(s)
525 def accesskey(s)
526 @used_accesskeys ||= []
526 @used_accesskeys ||= []
527 key = Redmine::AccessKeys.key_for(s)
527 key = Redmine::AccessKeys.key_for(s)
528 return nil if @used_accesskeys.include?(key)
528 return nil if @used_accesskeys.include?(key)
529 @used_accesskeys << key
529 @used_accesskeys << key
530 key
530 key
531 end
531 end
532
532
533 # Formats text according to system settings.
533 # Formats text according to system settings.
534 # 2 ways to call this method:
534 # 2 ways to call this method:
535 # * with a String: textilizable(text, options)
535 # * with a String: textilizable(text, options)
536 # * with an object and one of its attribute: textilizable(issue, :description, options)
536 # * with an object and one of its attribute: textilizable(issue, :description, options)
537 def textilizable(*args)
537 def textilizable(*args)
538 options = args.last.is_a?(Hash) ? args.pop : {}
538 options = args.last.is_a?(Hash) ? args.pop : {}
539 case args.size
539 case args.size
540 when 1
540 when 1
541 obj = options[:object]
541 obj = options[:object]
542 text = args.shift
542 text = args.shift
543 when 2
543 when 2
544 obj = args.shift
544 obj = args.shift
545 attr = args.shift
545 attr = args.shift
546 text = obj.send(attr).to_s
546 text = obj.send(attr).to_s
547 else
547 else
548 raise ArgumentError, 'invalid arguments to textilizable'
548 raise ArgumentError, 'invalid arguments to textilizable'
549 end
549 end
550 return '' if text.blank?
550 return '' if text.blank?
551 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
551 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
552 only_path = options.delete(:only_path) == false ? false : true
552 only_path = options.delete(:only_path) == false ? false : true
553
553
554 text = text.dup
554 text = text.dup
555 macros = catch_macros(text)
555 macros = catch_macros(text)
556 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
556 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
557
557
558 @parsed_headings = []
558 @parsed_headings = []
559 @heading_anchors = {}
559 @heading_anchors = {}
560 @current_section = 0 if options[:edit_section_links]
560 @current_section = 0 if options[:edit_section_links]
561
561
562 parse_sections(text, project, obj, attr, only_path, options)
562 parse_sections(text, project, obj, attr, only_path, options)
563 text = parse_non_pre_blocks(text, obj, macros) do |text|
563 text = parse_non_pre_blocks(text, obj, macros) do |text|
564 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
564 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
565 send method_name, text, project, obj, attr, only_path, options
565 send method_name, text, project, obj, attr, only_path, options
566 end
566 end
567 end
567 end
568 parse_headings(text, project, obj, attr, only_path, options)
568 parse_headings(text, project, obj, attr, only_path, options)
569
569
570 if @parsed_headings.any?
570 if @parsed_headings.any?
571 replace_toc(text, @parsed_headings)
571 replace_toc(text, @parsed_headings)
572 end
572 end
573
573
574 text.html_safe
574 text.html_safe
575 end
575 end
576
576
577 def parse_non_pre_blocks(text, obj, macros)
577 def parse_non_pre_blocks(text, obj, macros)
578 s = StringScanner.new(text)
578 s = StringScanner.new(text)
579 tags = []
579 tags = []
580 parsed = ''
580 parsed = ''
581 while !s.eos?
581 while !s.eos?
582 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
582 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
583 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
583 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
584 if tags.empty?
584 if tags.empty?
585 yield text
585 yield text
586 inject_macros(text, obj, macros) if macros.any?
586 inject_macros(text, obj, macros) if macros.any?
587 else
587 else
588 inject_macros(text, obj, macros, false) if macros.any?
588 inject_macros(text, obj, macros, false) if macros.any?
589 end
589 end
590 parsed << text
590 parsed << text
591 if tag
591 if tag
592 if closing
592 if closing
593 if tags.last == tag.downcase
593 if tags.last == tag.downcase
594 tags.pop
594 tags.pop
595 end
595 end
596 else
596 else
597 tags << tag.downcase
597 tags << tag.downcase
598 end
598 end
599 parsed << full_tag
599 parsed << full_tag
600 end
600 end
601 end
601 end
602 # Close any non closing tags
602 # Close any non closing tags
603 while tag = tags.pop
603 while tag = tags.pop
604 parsed << "</#{tag}>"
604 parsed << "</#{tag}>"
605 end
605 end
606 parsed
606 parsed
607 end
607 end
608
608
609 def parse_inline_attachments(text, project, obj, attr, only_path, options)
609 def parse_inline_attachments(text, project, obj, attr, only_path, options)
610 # when using an image link, try to use an attachment, if possible
610 # when using an image link, try to use an attachment, if possible
611 attachments = options[:attachments] || []
611 attachments = options[:attachments] || []
612 attachments += obj.attachments if obj.respond_to?(:attachments)
612 attachments += obj.attachments if obj.respond_to?(:attachments)
613 if attachments.present?
613 if attachments.present?
614 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
614 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
615 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
615 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
616 # search for the picture in attachments
616 # search for the picture in attachments
617 if found = Attachment.latest_attach(attachments, filename)
617 if found = Attachment.latest_attach(attachments, filename)
618 image_url = download_named_attachment_path(found, found.filename, :only_path => only_path)
618 image_url = download_named_attachment_path(found, found.filename, :only_path => only_path)
619 desc = found.description.to_s.gsub('"', '')
619 desc = found.description.to_s.gsub('"', '')
620 if !desc.blank? && alttext.blank?
620 if !desc.blank? && alttext.blank?
621 alt = " title=\"#{desc}\" alt=\"#{desc}\""
621 alt = " title=\"#{desc}\" alt=\"#{desc}\""
622 end
622 end
623 "src=\"#{image_url}\"#{alt}"
623 "src=\"#{image_url}\"#{alt}"
624 else
624 else
625 m
625 m
626 end
626 end
627 end
627 end
628 end
628 end
629 end
629 end
630
630
631 # Wiki links
631 # Wiki links
632 #
632 #
633 # Examples:
633 # Examples:
634 # [[mypage]]
634 # [[mypage]]
635 # [[mypage|mytext]]
635 # [[mypage|mytext]]
636 # wiki links can refer other project wikis, using project name or identifier:
636 # wiki links can refer other project wikis, using project name or identifier:
637 # [[project:]] -> wiki starting page
637 # [[project:]] -> wiki starting page
638 # [[project:|mytext]]
638 # [[project:|mytext]]
639 # [[project:mypage]]
639 # [[project:mypage]]
640 # [[project:mypage|mytext]]
640 # [[project:mypage|mytext]]
641 def parse_wiki_links(text, project, obj, attr, only_path, options)
641 def parse_wiki_links(text, project, obj, attr, only_path, options)
642 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
642 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
643 link_project = project
643 link_project = project
644 esc, all, page, title = $1, $2, $3, $5
644 esc, all, page, title = $1, $2, $3, $5
645 if esc.nil?
645 if esc.nil?
646 if page =~ /^([^\:]+)\:(.*)$/
646 if page =~ /^([^\:]+)\:(.*)$/
647 identifier, page = $1, $2
647 identifier, page = $1, $2
648 link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
648 link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
649 title ||= identifier if page.blank?
649 title ||= identifier if page.blank?
650 end
650 end
651
651
652 if link_project && link_project.wiki
652 if link_project && link_project.wiki
653 # extract anchor
653 # extract anchor
654 anchor = nil
654 anchor = nil
655 if page =~ /^(.+?)\#(.+)$/
655 if page =~ /^(.+?)\#(.+)$/
656 page, anchor = $1, $2
656 page, anchor = $1, $2
657 end
657 end
658 anchor = sanitize_anchor_name(anchor) if anchor.present?
658 anchor = sanitize_anchor_name(anchor) if anchor.present?
659 # check if page exists
659 # check if page exists
660 wiki_page = link_project.wiki.find_page(page)
660 wiki_page = link_project.wiki.find_page(page)
661 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
661 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
662 "##{anchor}"
662 "##{anchor}"
663 else
663 else
664 case options[:wiki_links]
664 case options[:wiki_links]
665 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
665 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
666 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
666 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
667 else
667 else
668 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
668 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
669 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
669 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
670 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
670 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
671 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
671 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
672 end
672 end
673 end
673 end
674 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
674 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
675 else
675 else
676 # project or wiki doesn't exist
676 # project or wiki doesn't exist
677 all
677 all
678 end
678 end
679 else
679 else
680 all
680 all
681 end
681 end
682 end
682 end
683 end
683 end
684
684
685 # Redmine links
685 # Redmine links
686 #
686 #
687 # Examples:
687 # Examples:
688 # Issues:
688 # Issues:
689 # #52 -> Link to issue #52
689 # #52 -> Link to issue #52
690 # Changesets:
690 # Changesets:
691 # r52 -> Link to revision 52
691 # r52 -> Link to revision 52
692 # commit:a85130f -> Link to scmid starting with a85130f
692 # commit:a85130f -> Link to scmid starting with a85130f
693 # Documents:
693 # Documents:
694 # document#17 -> Link to document with id 17
694 # document#17 -> Link to document with id 17
695 # document:Greetings -> Link to the document with title "Greetings"
695 # document:Greetings -> Link to the document with title "Greetings"
696 # document:"Some document" -> Link to the document with title "Some document"
696 # document:"Some document" -> Link to the document with title "Some document"
697 # Versions:
697 # Versions:
698 # version#3 -> Link to version with id 3
698 # version#3 -> Link to version with id 3
699 # version:1.0.0 -> Link to version named "1.0.0"
699 # version:1.0.0 -> Link to version named "1.0.0"
700 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
700 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
701 # Attachments:
701 # Attachments:
702 # attachment:file.zip -> Link to the attachment of the current object named file.zip
702 # attachment:file.zip -> Link to the attachment of the current object named file.zip
703 # Source files:
703 # Source files:
704 # source:some/file -> Link to the file located at /some/file in the project's repository
704 # source:some/file -> Link to the file located at /some/file in the project's repository
705 # source:some/file@52 -> Link to the file's revision 52
705 # source:some/file@52 -> Link to the file's revision 52
706 # source:some/file#L120 -> Link to line 120 of the file
706 # source:some/file#L120 -> Link to line 120 of the file
707 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
707 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
708 # export:some/file -> Force the download of the file
708 # export:some/file -> Force the download of the file
709 # Forum messages:
709 # Forum messages:
710 # message#1218 -> Link to message with id 1218
710 # message#1218 -> Link to message with id 1218
711 # Projects:
711 # Projects:
712 # project:someproject -> Link to project named "someproject"
712 # project:someproject -> Link to project named "someproject"
713 # project#3 -> Link to project with id 3
713 # project#3 -> Link to project with id 3
714 #
714 #
715 # Links can refer other objects from other projects, using project identifier:
715 # Links can refer other objects from other projects, using project identifier:
716 # identifier:r52
716 # identifier:r52
717 # identifier:document:"Some document"
717 # identifier:document:"Some document"
718 # identifier:version:1.0.0
718 # identifier:version:1.0.0
719 # identifier:source:some/file
719 # identifier:source:some/file
720 def parse_redmine_links(text, default_project, obj, attr, only_path, options)
720 def parse_redmine_links(text, default_project, obj, attr, only_path, options)
721 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
721 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
722 leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
722 leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
723 link = nil
723 link = nil
724 project = default_project
724 project = default_project
725 if project_identifier
725 if project_identifier
726 project = Project.visible.find_by_identifier(project_identifier)
726 project = Project.visible.find_by_identifier(project_identifier)
727 end
727 end
728 if esc.nil?
728 if esc.nil?
729 if prefix.nil? && sep == 'r'
729 if prefix.nil? && sep == 'r'
730 if project
730 if project
731 repository = nil
731 repository = nil
732 if repo_identifier
732 if repo_identifier
733 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
733 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
734 else
734 else
735 repository = project.repository
735 repository = project.repository
736 end
736 end
737 # project.changesets.visible raises an SQL error because of a double join on repositories
737 # project.changesets.visible raises an SQL error because of a double join on repositories
738 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
738 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
739 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision},
739 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision},
740 :class => 'changeset',
740 :class => 'changeset',
741 :title => truncate_single_line(changeset.comments, :length => 100))
741 :title => truncate_single_line(changeset.comments, :length => 100))
742 end
742 end
743 end
743 end
744 elsif sep == '#'
744 elsif sep == '#'
745 oid = identifier.to_i
745 oid = identifier.to_i
746 case prefix
746 case prefix
747 when nil
747 when nil
748 if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status)
748 if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status)
749 anchor = comment_id ? "note-#{comment_id}" : nil
749 anchor = comment_id ? "note-#{comment_id}" : nil
750 link = link_to(h("##{oid}#{comment_suffix}"), {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
750 link = link_to(h("##{oid}#{comment_suffix}"), {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
751 :class => issue.css_classes,
751 :class => issue.css_classes,
752 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
752 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
753 end
753 end
754 when 'document'
754 when 'document'
755 if document = Document.visible.find_by_id(oid)
755 if document = Document.visible.find_by_id(oid)
756 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
756 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
757 :class => 'document'
757 :class => 'document'
758 end
758 end
759 when 'version'
759 when 'version'
760 if version = Version.visible.find_by_id(oid)
760 if version = Version.visible.find_by_id(oid)
761 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
761 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
762 :class => 'version'
762 :class => 'version'
763 end
763 end
764 when 'message'
764 when 'message'
765 if message = Message.visible.find_by_id(oid, :include => :parent)
765 if message = Message.visible.find_by_id(oid, :include => :parent)
766 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
766 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
767 end
767 end
768 when 'forum'
768 when 'forum'
769 if board = Board.visible.find_by_id(oid)
769 if board = Board.visible.find_by_id(oid)
770 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
770 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
771 :class => 'board'
771 :class => 'board'
772 end
772 end
773 when 'news'
773 when 'news'
774 if news = News.visible.find_by_id(oid)
774 if news = News.visible.find_by_id(oid)
775 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
775 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
776 :class => 'news'
776 :class => 'news'
777 end
777 end
778 when 'project'
778 when 'project'
779 if p = Project.visible.find_by_id(oid)
779 if p = Project.visible.find_by_id(oid)
780 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
780 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
781 end
781 end
782 end
782 end
783 elsif sep == ':'
783 elsif sep == ':'
784 # removes the double quotes if any
784 # removes the double quotes if any
785 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
785 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
786 case prefix
786 case prefix
787 when 'document'
787 when 'document'
788 if project && document = project.documents.visible.find_by_title(name)
788 if project && document = project.documents.visible.find_by_title(name)
789 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
789 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
790 :class => 'document'
790 :class => 'document'
791 end
791 end
792 when 'version'
792 when 'version'
793 if project && version = project.versions.visible.find_by_name(name)
793 if project && version = project.versions.visible.find_by_name(name)
794 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
794 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
795 :class => 'version'
795 :class => 'version'
796 end
796 end
797 when 'forum'
797 when 'forum'
798 if project && board = project.boards.visible.find_by_name(name)
798 if project && board = project.boards.visible.find_by_name(name)
799 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
799 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
800 :class => 'board'
800 :class => 'board'
801 end
801 end
802 when 'news'
802 when 'news'
803 if project && news = project.news.visible.find_by_title(name)
803 if project && news = project.news.visible.find_by_title(name)
804 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
804 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
805 :class => 'news'
805 :class => 'news'
806 end
806 end
807 when 'commit', 'source', 'export'
807 when 'commit', 'source', 'export'
808 if project
808 if project
809 repository = nil
809 repository = nil
810 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
810 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
811 repo_prefix, repo_identifier, name = $1, $2, $3
811 repo_prefix, repo_identifier, name = $1, $2, $3
812 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
812 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
813 else
813 else
814 repository = project.repository
814 repository = project.repository
815 end
815 end
816 if prefix == 'commit'
816 if prefix == 'commit'
817 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
817 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
818 link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
818 link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
819 :class => 'changeset',
819 :class => 'changeset',
820 :title => truncate_single_line(changeset.comments, :length => 100)
820 :title => truncate_single_line(changeset.comments, :length => 100)
821 end
821 end
822 else
822 else
823 if repository && User.current.allowed_to?(:browse_repository, project)
823 if repository && User.current.allowed_to?(:browse_repository, project)
824 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
824 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
825 path, rev, anchor = $1, $3, $5
825 path, rev, anchor = $1, $3, $5
826 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param,
826 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param,
827 :path => to_path_param(path),
827 :path => to_path_param(path),
828 :rev => rev,
828 :rev => rev,
829 :anchor => anchor},
829 :anchor => anchor},
830 :class => (prefix == 'export' ? 'source download' : 'source')
830 :class => (prefix == 'export' ? 'source download' : 'source')
831 end
831 end
832 end
832 end
833 repo_prefix = nil
833 repo_prefix = nil
834 end
834 end
835 when 'attachment'
835 when 'attachment'
836 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
836 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
837 if attachments && attachment = Attachment.latest_attach(attachments, name)
837 if attachments && attachment = Attachment.latest_attach(attachments, name)
838 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
838 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
839 end
839 end
840 when 'project'
840 when 'project'
841 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
841 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
842 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
842 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
843 end
843 end
844 end
844 end
845 end
845 end
846 end
846 end
847 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
847 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
848 end
848 end
849 end
849 end
850
850
851 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
851 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
852
852
853 def parse_sections(text, project, obj, attr, only_path, options)
853 def parse_sections(text, project, obj, attr, only_path, options)
854 return unless options[:edit_section_links]
854 return unless options[:edit_section_links]
855 text.gsub!(HEADING_RE) do
855 text.gsub!(HEADING_RE) do
856 heading = $1
856 heading = $1
857 @current_section += 1
857 @current_section += 1
858 if @current_section > 1
858 if @current_section > 1
859 content_tag('div',
859 content_tag('div',
860 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
860 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
861 :class => 'contextual',
861 :class => 'contextual',
862 :title => l(:button_edit_section),
862 :title => l(:button_edit_section),
863 :id => "section-#{@current_section}") + heading.html_safe
863 :id => "section-#{@current_section}") + heading.html_safe
864 else
864 else
865 heading
865 heading
866 end
866 end
867 end
867 end
868 end
868 end
869
869
870 # Headings and TOC
870 # Headings and TOC
871 # Adds ids and links to headings unless options[:headings] is set to false
871 # Adds ids and links to headings unless options[:headings] is set to false
872 def parse_headings(text, project, obj, attr, only_path, options)
872 def parse_headings(text, project, obj, attr, only_path, options)
873 return if options[:headings] == false
873 return if options[:headings] == false
874
874
875 text.gsub!(HEADING_RE) do
875 text.gsub!(HEADING_RE) do
876 level, attrs, content = $2.to_i, $3, $4
876 level, attrs, content = $2.to_i, $3, $4
877 item = strip_tags(content).strip
877 item = strip_tags(content).strip
878 anchor = sanitize_anchor_name(item)
878 anchor = sanitize_anchor_name(item)
879 # used for single-file wiki export
879 # used for single-file wiki export
880 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
880 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
881 @heading_anchors[anchor] ||= 0
881 @heading_anchors[anchor] ||= 0
882 idx = (@heading_anchors[anchor] += 1)
882 idx = (@heading_anchors[anchor] += 1)
883 if idx > 1
883 if idx > 1
884 anchor = "#{anchor}-#{idx}"
884 anchor = "#{anchor}-#{idx}"
885 end
885 end
886 @parsed_headings << [level, anchor, item]
886 @parsed_headings << [level, anchor, item]
887 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
887 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
888 end
888 end
889 end
889 end
890
890
891 MACROS_RE = /(
891 MACROS_RE = /(
892 (!)? # escaping
892 (!)? # escaping
893 (
893 (
894 \{\{ # opening tag
894 \{\{ # opening tag
895 ([\w]+) # macro name
895 ([\w]+) # macro name
896 (\(([^\n\r]*?)\))? # optional arguments
896 (\(([^\n\r]*?)\))? # optional arguments
897 ([\n\r].*?[\n\r])? # optional block of text
897 ([\n\r].*?[\n\r])? # optional block of text
898 \}\} # closing tag
898 \}\} # closing tag
899 )
899 )
900 )/mx unless const_defined?(:MACROS_RE)
900 )/mx unless const_defined?(:MACROS_RE)
901
901
902 MACRO_SUB_RE = /(
902 MACRO_SUB_RE = /(
903 \{\{
903 \{\{
904 macro\((\d+)\)
904 macro\((\d+)\)
905 \}\}
905 \}\}
906 )/x unless const_defined?(:MACRO_SUB_RE)
906 )/x unless const_defined?(:MACRO_SUB_RE)
907
907
908 # Extracts macros from text
908 # Extracts macros from text
909 def catch_macros(text)
909 def catch_macros(text)
910 macros = {}
910 macros = {}
911 text.gsub!(MACROS_RE) do
911 text.gsub!(MACROS_RE) do
912 all, macro = $1, $4.downcase
912 all, macro = $1, $4.downcase
913 if macro_exists?(macro) || all =~ MACRO_SUB_RE
913 if macro_exists?(macro) || all =~ MACRO_SUB_RE
914 index = macros.size
914 index = macros.size
915 macros[index] = all
915 macros[index] = all
916 "{{macro(#{index})}}"
916 "{{macro(#{index})}}"
917 else
917 else
918 all
918 all
919 end
919 end
920 end
920 end
921 macros
921 macros
922 end
922 end
923
923
924 # Executes and replaces macros in text
924 # Executes and replaces macros in text
925 def inject_macros(text, obj, macros, execute=true)
925 def inject_macros(text, obj, macros, execute=true)
926 text.gsub!(MACRO_SUB_RE) do
926 text.gsub!(MACRO_SUB_RE) do
927 all, index = $1, $2.to_i
927 all, index = $1, $2.to_i
928 orig = macros.delete(index)
928 orig = macros.delete(index)
929 if execute && orig && orig =~ MACROS_RE
929 if execute && orig && orig =~ MACROS_RE
930 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
930 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
931 if esc.nil?
931 if esc.nil?
932 h(exec_macro(macro, obj, args, block) || all)
932 h(exec_macro(macro, obj, args, block) || all)
933 else
933 else
934 h(all)
934 h(all)
935 end
935 end
936 elsif orig
936 elsif orig
937 h(orig)
937 h(orig)
938 else
938 else
939 h(all)
939 h(all)
940 end
940 end
941 end
941 end
942 end
942 end
943
943
944 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
944 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
945
945
946 # Renders the TOC with given headings
946 # Renders the TOC with given headings
947 def replace_toc(text, headings)
947 def replace_toc(text, headings)
948 text.gsub!(TOC_RE) do
948 text.gsub!(TOC_RE) do
949 # Keep only the 4 first levels
949 # Keep only the 4 first levels
950 headings = headings.select{|level, anchor, item| level <= 4}
950 headings = headings.select{|level, anchor, item| level <= 4}
951 if headings.empty?
951 if headings.empty?
952 ''
952 ''
953 else
953 else
954 div_class = 'toc'
954 div_class = 'toc'
955 div_class << ' right' if $1 == '>'
955 div_class << ' right' if $1 == '>'
956 div_class << ' left' if $1 == '<'
956 div_class << ' left' if $1 == '<'
957 out = "<ul class=\"#{div_class}\"><li>"
957 out = "<ul class=\"#{div_class}\"><li>"
958 root = headings.map(&:first).min
958 root = headings.map(&:first).min
959 current = root
959 current = root
960 started = false
960 started = false
961 headings.each do |level, anchor, item|
961 headings.each do |level, anchor, item|
962 if level > current
962 if level > current
963 out << '<ul><li>' * (level - current)
963 out << '<ul><li>' * (level - current)
964 elsif level < current
964 elsif level < current
965 out << "</li></ul>\n" * (current - level) + "</li><li>"
965 out << "</li></ul>\n" * (current - level) + "</li><li>"
966 elsif started
966 elsif started
967 out << '</li><li>'
967 out << '</li><li>'
968 end
968 end
969 out << "<a href=\"##{anchor}\">#{item}</a>"
969 out << "<a href=\"##{anchor}\">#{item}</a>"
970 current = level
970 current = level
971 started = true
971 started = true
972 end
972 end
973 out << '</li></ul>' * (current - root)
973 out << '</li></ul>' * (current - root)
974 out << '</li></ul>'
974 out << '</li></ul>'
975 end
975 end
976 end
976 end
977 end
977 end
978
978
979 # Same as Rails' simple_format helper without using paragraphs
979 # Same as Rails' simple_format helper without using paragraphs
980 def simple_format_without_paragraph(text)
980 def simple_format_without_paragraph(text)
981 text.to_s.
981 text.to_s.
982 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
982 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
983 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
983 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
984 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
984 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
985 html_safe
985 html_safe
986 end
986 end
987
987
988 def lang_options_for_select(blank=true)
988 def lang_options_for_select(blank=true)
989 (blank ? [["(auto)", ""]] : []) + languages_options
989 (blank ? [["(auto)", ""]] : []) + languages_options
990 end
990 end
991
991
992 def label_tag_for(name, option_tags = nil, options = {})
992 def label_tag_for(name, option_tags = nil, options = {})
993 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
993 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
994 content_tag("label", label_text)
994 content_tag("label", label_text)
995 end
995 end
996
996
997 def labelled_form_for(*args, &proc)
997 def labelled_form_for(*args, &proc)
998 args << {} unless args.last.is_a?(Hash)
998 args << {} unless args.last.is_a?(Hash)
999 options = args.last
999 options = args.last
1000 if args.first.is_a?(Symbol)
1000 if args.first.is_a?(Symbol)
1001 options.merge!(:as => args.shift)
1001 options.merge!(:as => args.shift)
1002 end
1002 end
1003 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1003 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1004 form_for(*args, &proc)
1004 form_for(*args, &proc)
1005 end
1005 end
1006
1006
1007 def labelled_fields_for(*args, &proc)
1007 def labelled_fields_for(*args, &proc)
1008 args << {} unless args.last.is_a?(Hash)
1008 args << {} unless args.last.is_a?(Hash)
1009 options = args.last
1009 options = args.last
1010 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1010 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1011 fields_for(*args, &proc)
1011 fields_for(*args, &proc)
1012 end
1012 end
1013
1013
1014 def labelled_remote_form_for(*args, &proc)
1014 def labelled_remote_form_for(*args, &proc)
1015 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
1015 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
1016 args << {} unless args.last.is_a?(Hash)
1016 args << {} unless args.last.is_a?(Hash)
1017 options = args.last
1017 options = args.last
1018 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
1018 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
1019 form_for(*args, &proc)
1019 form_for(*args, &proc)
1020 end
1020 end
1021
1021
1022 def error_messages_for(*objects)
1022 def error_messages_for(*objects)
1023 html = ""
1023 html = ""
1024 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
1024 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
1025 errors = objects.map {|o| o.errors.full_messages}.flatten
1025 errors = objects.map {|o| o.errors.full_messages}.flatten
1026 if errors.any?
1026 if errors.any?
1027 html << "<div id='errorExplanation'><ul>\n"
1027 html << "<div id='errorExplanation'><ul>\n"
1028 errors.each do |error|
1028 errors.each do |error|
1029 html << "<li>#{h error}</li>\n"
1029 html << "<li>#{h error}</li>\n"
1030 end
1030 end
1031 html << "</ul></div>\n"
1031 html << "</ul></div>\n"
1032 end
1032 end
1033 html.html_safe
1033 html.html_safe
1034 end
1034 end
1035
1035
1036 def delete_link(url, options={})
1036 def delete_link(url, options={})
1037 options = {
1037 options = {
1038 :method => :delete,
1038 :method => :delete,
1039 :data => {:confirm => l(:text_are_you_sure)},
1039 :data => {:confirm => l(:text_are_you_sure)},
1040 :class => 'icon icon-del'
1040 :class => 'icon icon-del'
1041 }.merge(options)
1041 }.merge(options)
1042
1042
1043 link_to l(:button_delete), url, options
1043 link_to l(:button_delete), url, options
1044 end
1044 end
1045
1045
1046 def preview_link(url, form, target='preview', options={})
1046 def preview_link(url, form, target='preview', options={})
1047 content_tag 'a', l(:label_preview), {
1047 content_tag 'a', l(:label_preview), {
1048 :href => "#",
1048 :href => "#",
1049 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1049 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1050 :accesskey => accesskey(:preview)
1050 :accesskey => accesskey(:preview)
1051 }.merge(options)
1051 }.merge(options)
1052 end
1052 end
1053
1053
1054 def link_to_function(name, function, html_options={})
1054 def link_to_function(name, function, html_options={})
1055 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1055 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1056 end
1056 end
1057
1057
1058 # Helper to render JSON in views
1058 # Helper to render JSON in views
1059 def raw_json(arg)
1059 def raw_json(arg)
1060 arg.to_json.to_s.gsub('/', '\/').html_safe
1060 arg.to_json.to_s.gsub('/', '\/').html_safe
1061 end
1061 end
1062
1062
1063 def back_url
1063 def back_url
1064 url = params[:back_url]
1064 url = params[:back_url]
1065 if url.nil? && referer = request.env['HTTP_REFERER']
1065 if url.nil? && referer = request.env['HTTP_REFERER']
1066 url = CGI.unescape(referer.to_s)
1066 url = CGI.unescape(referer.to_s)
1067 end
1067 end
1068 url
1068 url
1069 end
1069 end
1070
1070
1071 def back_url_hidden_field_tag
1071 def back_url_hidden_field_tag
1072 url = back_url
1072 url = back_url
1073 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1073 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1074 end
1074 end
1075
1075
1076 def check_all_links(form_name)
1076 def check_all_links(form_name)
1077 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1077 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1078 " | ".html_safe +
1078 " | ".html_safe +
1079 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1079 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1080 end
1080 end
1081
1081
1082 def progress_bar(pcts, options={})
1082 def progress_bar(pcts, options={})
1083 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1083 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1084 pcts = pcts.collect(&:round)
1084 pcts = pcts.collect(&:round)
1085 pcts[1] = pcts[1] - pcts[0]
1085 pcts[1] = pcts[1] - pcts[0]
1086 pcts << (100 - pcts[1] - pcts[0])
1086 pcts << (100 - pcts[1] - pcts[0])
1087 width = options[:width] || '100px;'
1087 width = options[:width] || '100px;'
1088 legend = options[:legend] || ''
1088 legend = options[:legend] || ''
1089 content_tag('table',
1089 content_tag('table',
1090 content_tag('tr',
1090 content_tag('tr',
1091 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1091 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1092 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1092 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1093 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1093 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1094 ), :class => 'progress progress-#{pcts[0]}', :style => "width: #{width};").html_safe +
1094 ), :class => "progress progress-#{pcts[0]}", :style => "width: #{width};").html_safe +
1095 content_tag('p', legend, :class => 'percent').html_safe
1095 content_tag('p', legend, :class => 'percent').html_safe
1096 end
1096 end
1097
1097
1098 def checked_image(checked=true)
1098 def checked_image(checked=true)
1099 if checked
1099 if checked
1100 image_tag 'toggle_check.png'
1100 image_tag 'toggle_check.png'
1101 end
1101 end
1102 end
1102 end
1103
1103
1104 def context_menu(url)
1104 def context_menu(url)
1105 unless @context_menu_included
1105 unless @context_menu_included
1106 content_for :header_tags do
1106 content_for :header_tags do
1107 javascript_include_tag('context_menu') +
1107 javascript_include_tag('context_menu') +
1108 stylesheet_link_tag('context_menu')
1108 stylesheet_link_tag('context_menu')
1109 end
1109 end
1110 if l(:direction) == 'rtl'
1110 if l(:direction) == 'rtl'
1111 content_for :header_tags do
1111 content_for :header_tags do
1112 stylesheet_link_tag('context_menu_rtl')
1112 stylesheet_link_tag('context_menu_rtl')
1113 end
1113 end
1114 end
1114 end
1115 @context_menu_included = true
1115 @context_menu_included = true
1116 end
1116 end
1117 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1117 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1118 end
1118 end
1119
1119
1120 def calendar_for(field_id)
1120 def calendar_for(field_id)
1121 include_calendar_headers_tags
1121 include_calendar_headers_tags
1122 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
1122 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
1123 end
1123 end
1124
1124
1125 def include_calendar_headers_tags
1125 def include_calendar_headers_tags
1126 unless @calendar_headers_tags_included
1126 unless @calendar_headers_tags_included
1127 tags = javascript_include_tag("datepicker")
1127 tags = javascript_include_tag("datepicker")
1128 @calendar_headers_tags_included = true
1128 @calendar_headers_tags_included = true
1129 content_for :header_tags do
1129 content_for :header_tags do
1130 start_of_week = Setting.start_of_week
1130 start_of_week = Setting.start_of_week
1131 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1131 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1132 # Redmine uses 1..7 (monday..sunday) in settings and locales
1132 # Redmine uses 1..7 (monday..sunday) in settings and locales
1133 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1133 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1134 start_of_week = start_of_week.to_i % 7
1134 start_of_week = start_of_week.to_i % 7
1135 tags << javascript_tag(
1135 tags << javascript_tag(
1136 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1136 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1137 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1137 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1138 path_to_image('/images/calendar.png') +
1138 path_to_image('/images/calendar.png') +
1139 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
1139 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
1140 "selectOtherMonths: true, changeMonth: true, changeYear: true, " +
1140 "selectOtherMonths: true, changeMonth: true, changeYear: true, " +
1141 "beforeShow: beforeShowDatePicker};")
1141 "beforeShow: beforeShowDatePicker};")
1142 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1142 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1143 unless jquery_locale == 'en'
1143 unless jquery_locale == 'en'
1144 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1144 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1145 end
1145 end
1146 tags
1146 tags
1147 end
1147 end
1148 end
1148 end
1149 end
1149 end
1150
1150
1151 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1151 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1152 # Examples:
1152 # Examples:
1153 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1153 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1154 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1154 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1155 #
1155 #
1156 def stylesheet_link_tag(*sources)
1156 def stylesheet_link_tag(*sources)
1157 options = sources.last.is_a?(Hash) ? sources.pop : {}
1157 options = sources.last.is_a?(Hash) ? sources.pop : {}
1158 plugin = options.delete(:plugin)
1158 plugin = options.delete(:plugin)
1159 sources = sources.map do |source|
1159 sources = sources.map do |source|
1160 if plugin
1160 if plugin
1161 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1161 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1162 elsif current_theme && current_theme.stylesheets.include?(source)
1162 elsif current_theme && current_theme.stylesheets.include?(source)
1163 current_theme.stylesheet_path(source)
1163 current_theme.stylesheet_path(source)
1164 else
1164 else
1165 source
1165 source
1166 end
1166 end
1167 end
1167 end
1168 super sources, options
1168 super sources, options
1169 end
1169 end
1170
1170
1171 # Overrides Rails' image_tag with themes and plugins support.
1171 # Overrides Rails' image_tag with themes and plugins support.
1172 # Examples:
1172 # Examples:
1173 # image_tag('image.png') # => picks image.png from the current theme or defaults
1173 # image_tag('image.png') # => picks image.png from the current theme or defaults
1174 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1174 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1175 #
1175 #
1176 def image_tag(source, options={})
1176 def image_tag(source, options={})
1177 if plugin = options.delete(:plugin)
1177 if plugin = options.delete(:plugin)
1178 source = "/plugin_assets/#{plugin}/images/#{source}"
1178 source = "/plugin_assets/#{plugin}/images/#{source}"
1179 elsif current_theme && current_theme.images.include?(source)
1179 elsif current_theme && current_theme.images.include?(source)
1180 source = current_theme.image_path(source)
1180 source = current_theme.image_path(source)
1181 end
1181 end
1182 super source, options
1182 super source, options
1183 end
1183 end
1184
1184
1185 # Overrides Rails' javascript_include_tag with plugins support
1185 # Overrides Rails' javascript_include_tag with plugins support
1186 # Examples:
1186 # Examples:
1187 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1187 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1188 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1188 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1189 #
1189 #
1190 def javascript_include_tag(*sources)
1190 def javascript_include_tag(*sources)
1191 options = sources.last.is_a?(Hash) ? sources.pop : {}
1191 options = sources.last.is_a?(Hash) ? sources.pop : {}
1192 if plugin = options.delete(:plugin)
1192 if plugin = options.delete(:plugin)
1193 sources = sources.map do |source|
1193 sources = sources.map do |source|
1194 if plugin
1194 if plugin
1195 "/plugin_assets/#{plugin}/javascripts/#{source}"
1195 "/plugin_assets/#{plugin}/javascripts/#{source}"
1196 else
1196 else
1197 source
1197 source
1198 end
1198 end
1199 end
1199 end
1200 end
1200 end
1201 super sources, options
1201 super sources, options
1202 end
1202 end
1203
1203
1204 # TODO: remove this in 2.5.0
1204 # TODO: remove this in 2.5.0
1205 def has_content?(name)
1205 def has_content?(name)
1206 content_for?(name)
1206 content_for?(name)
1207 end
1207 end
1208
1208
1209 def sidebar_content?
1209 def sidebar_content?
1210 content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1210 content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1211 end
1211 end
1212
1212
1213 def view_layouts_base_sidebar_hook_response
1213 def view_layouts_base_sidebar_hook_response
1214 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1214 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1215 end
1215 end
1216
1216
1217 def email_delivery_enabled?
1217 def email_delivery_enabled?
1218 !!ActionMailer::Base.perform_deliveries
1218 !!ActionMailer::Base.perform_deliveries
1219 end
1219 end
1220
1220
1221 # Returns the avatar image tag for the given +user+ if avatars are enabled
1221 # Returns the avatar image tag for the given +user+ if avatars are enabled
1222 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1222 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1223 def avatar(user, options = { })
1223 def avatar(user, options = { })
1224 if Setting.gravatar_enabled?
1224 if Setting.gravatar_enabled?
1225 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1225 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1226 email = nil
1226 email = nil
1227 if user.respond_to?(:mail)
1227 if user.respond_to?(:mail)
1228 email = user.mail
1228 email = user.mail
1229 elsif user.to_s =~ %r{<(.+?)>}
1229 elsif user.to_s =~ %r{<(.+?)>}
1230 email = $1
1230 email = $1
1231 end
1231 end
1232 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1232 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1233 else
1233 else
1234 ''
1234 ''
1235 end
1235 end
1236 end
1236 end
1237
1237
1238 def sanitize_anchor_name(anchor)
1238 def sanitize_anchor_name(anchor)
1239 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
1239 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
1240 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1240 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1241 else
1241 else
1242 # TODO: remove when ruby1.8 is no longer supported
1242 # TODO: remove when ruby1.8 is no longer supported
1243 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1243 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1244 end
1244 end
1245 end
1245 end
1246
1246
1247 # Returns the javascript tags that are included in the html layout head
1247 # Returns the javascript tags that are included in the html layout head
1248 def javascript_heads
1248 def javascript_heads
1249 tags = javascript_include_tag('jquery-1.8.3-ui-1.9.2-ujs-2.0.3', 'application')
1249 tags = javascript_include_tag('jquery-1.8.3-ui-1.9.2-ujs-2.0.3', 'application')
1250 unless User.current.pref.warn_on_leaving_unsaved == '0'
1250 unless User.current.pref.warn_on_leaving_unsaved == '0'
1251 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1251 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1252 end
1252 end
1253 tags
1253 tags
1254 end
1254 end
1255
1255
1256 def favicon
1256 def favicon
1257 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1257 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1258 end
1258 end
1259
1259
1260 def robot_exclusion_tag
1260 def robot_exclusion_tag
1261 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1261 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1262 end
1262 end
1263
1263
1264 # Returns true if arg is expected in the API response
1264 # Returns true if arg is expected in the API response
1265 def include_in_api_response?(arg)
1265 def include_in_api_response?(arg)
1266 unless @included_in_api_response
1266 unless @included_in_api_response
1267 param = params[:include]
1267 param = params[:include]
1268 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1268 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1269 @included_in_api_response.collect!(&:strip)
1269 @included_in_api_response.collect!(&:strip)
1270 end
1270 end
1271 @included_in_api_response.include?(arg.to_s)
1271 @included_in_api_response.include?(arg.to_s)
1272 end
1272 end
1273
1273
1274 # Returns options or nil if nometa param or X-Redmine-Nometa header
1274 # Returns options or nil if nometa param or X-Redmine-Nometa header
1275 # was set in the request
1275 # was set in the request
1276 def api_meta(options)
1276 def api_meta(options)
1277 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1277 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1278 # compatibility mode for activeresource clients that raise
1278 # compatibility mode for activeresource clients that raise
1279 # an error when unserializing an array with attributes
1279 # an error when unserializing an array with attributes
1280 nil
1280 nil
1281 else
1281 else
1282 options
1282 options
1283 end
1283 end
1284 end
1284 end
1285
1285
1286 private
1286 private
1287
1287
1288 def wiki_helper
1288 def wiki_helper
1289 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1289 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1290 extend helper
1290 extend helper
1291 return self
1291 return self
1292 end
1292 end
1293
1293
1294 def link_to_content_update(text, url_params = {}, html_options = {})
1294 def link_to_content_update(text, url_params = {}, html_options = {})
1295 link_to(text, url_params, html_options)
1295 link_to(text, url_params, html_options)
1296 end
1296 end
1297 end
1297 end
General Comments 0
You need to be logged in to leave comments. Login now