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