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