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