##// END OF EJS Templates
Adds a title on the sort handle (#12909)....
Jean-Philippe Lang -
r14960:aed0350b0184
parent child
Show More
@@ -1,1359 +1,1360
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 # TODO: remove associated styles from application.css too
457 # TODO: remove associated styles from application.css too
458 ActiveSupport::Deprecation.warn "Application#reorder_links will be removed in Redmine 4."
458 ActiveSupport::Deprecation.warn "Application#reorder_links will be removed in Redmine 4."
459
459
460 link_to(l(:label_sort_highest),
460 link_to(l(:label_sort_highest),
461 url.merge({"#{name}[move_to]" => 'highest'}), :method => method,
461 url.merge({"#{name}[move_to]" => 'highest'}), :method => method,
462 :title => l(:label_sort_highest), :class => 'icon-only icon-move-top') +
462 :title => l(:label_sort_highest), :class => 'icon-only icon-move-top') +
463 link_to(l(:label_sort_higher),
463 link_to(l(:label_sort_higher),
464 url.merge({"#{name}[move_to]" => 'higher'}), :method => method,
464 url.merge({"#{name}[move_to]" => 'higher'}), :method => method,
465 :title => l(:label_sort_higher), :class => 'icon-only icon-move-up') +
465 :title => l(:label_sort_higher), :class => 'icon-only icon-move-up') +
466 link_to(l(:label_sort_lower),
466 link_to(l(:label_sort_lower),
467 url.merge({"#{name}[move_to]" => 'lower'}), :method => method,
467 url.merge({"#{name}[move_to]" => 'lower'}), :method => method,
468 :title => l(:label_sort_lower), :class => 'icon-only icon-move-down') +
468 :title => l(:label_sort_lower), :class => 'icon-only icon-move-down') +
469 link_to(l(:label_sort_lowest),
469 link_to(l(:label_sort_lowest),
470 url.merge({"#{name}[move_to]" => 'lowest'}), :method => method,
470 url.merge({"#{name}[move_to]" => 'lowest'}), :method => method,
471 :title => l(:label_sort_lowest), :class => 'icon-only icon-move-bottom')
471 :title => l(:label_sort_lowest), :class => 'icon-only icon-move-bottom')
472 end
472 end
473
473
474 def reorder_handle(object, options={})
474 def reorder_handle(object, options={})
475 data = {
475 data = {
476 :reorder_url => options[:url] || url_for(object),
476 :reorder_url => options[:url] || url_for(object),
477 :reorder_param => options[:param] || object.class.name.underscore
477 :reorder_param => options[:param] || object.class.name.underscore
478 }
478 }
479 content_tag('span', '',
479 content_tag('span', '',
480 :class => "sort-handle",
480 :class => "sort-handle",
481 :data => data)
481 :data => data,
482 :title => l(:button_sort))
482 end
483 end
483
484
484 def breadcrumb(*args)
485 def breadcrumb(*args)
485 elements = args.flatten
486 elements = args.flatten
486 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
487 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
487 end
488 end
488
489
489 def other_formats_links(&block)
490 def other_formats_links(&block)
490 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
491 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
491 yield Redmine::Views::OtherFormatsBuilder.new(self)
492 yield Redmine::Views::OtherFormatsBuilder.new(self)
492 concat('</p>'.html_safe)
493 concat('</p>'.html_safe)
493 end
494 end
494
495
495 def page_header_title
496 def page_header_title
496 if @project.nil? || @project.new_record?
497 if @project.nil? || @project.new_record?
497 h(Setting.app_title)
498 h(Setting.app_title)
498 else
499 else
499 b = []
500 b = []
500 ancestors = (@project.root? ? [] : @project.ancestors.visible.to_a)
501 ancestors = (@project.root? ? [] : @project.ancestors.visible.to_a)
501 if ancestors.any?
502 if ancestors.any?
502 root = ancestors.shift
503 root = ancestors.shift
503 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
504 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
504 if ancestors.size > 2
505 if ancestors.size > 2
505 b << "\xe2\x80\xa6"
506 b << "\xe2\x80\xa6"
506 ancestors = ancestors[-2, 2]
507 ancestors = ancestors[-2, 2]
507 end
508 end
508 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
509 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
509 end
510 end
510 b << content_tag(:span, h(@project), class: 'current-project')
511 b << content_tag(:span, h(@project), class: 'current-project')
511 if b.size > 1
512 if b.size > 1
512 separator = content_tag(:span, ' &raquo; '.html_safe, class: 'separator')
513 separator = content_tag(:span, ' &raquo; '.html_safe, class: 'separator')
513 path = safe_join(b[0..-2], separator) + separator
514 path = safe_join(b[0..-2], separator) + separator
514 b = [content_tag(:span, path.html_safe, class: 'breadcrumbs'), b[-1]]
515 b = [content_tag(:span, path.html_safe, class: 'breadcrumbs'), b[-1]]
515 end
516 end
516 safe_join b
517 safe_join b
517 end
518 end
518 end
519 end
519
520
520 # Returns a h2 tag and sets the html title with the given arguments
521 # Returns a h2 tag and sets the html title with the given arguments
521 def title(*args)
522 def title(*args)
522 strings = args.map do |arg|
523 strings = args.map do |arg|
523 if arg.is_a?(Array) && arg.size >= 2
524 if arg.is_a?(Array) && arg.size >= 2
524 link_to(*arg)
525 link_to(*arg)
525 else
526 else
526 h(arg.to_s)
527 h(arg.to_s)
527 end
528 end
528 end
529 end
529 html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
530 html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
530 content_tag('h2', strings.join(' &#187; ').html_safe)
531 content_tag('h2', strings.join(' &#187; ').html_safe)
531 end
532 end
532
533
533 # Sets the html title
534 # Sets the html title
534 # Returns the html title when called without arguments
535 # Returns the html title when called without arguments
535 # Current project name and app_title and automatically appended
536 # Current project name and app_title and automatically appended
536 # Exemples:
537 # Exemples:
537 # html_title 'Foo', 'Bar'
538 # html_title 'Foo', 'Bar'
538 # html_title # => 'Foo - Bar - My Project - Redmine'
539 # html_title # => 'Foo - Bar - My Project - Redmine'
539 def html_title(*args)
540 def html_title(*args)
540 if args.empty?
541 if args.empty?
541 title = @html_title || []
542 title = @html_title || []
542 title << @project.name if @project
543 title << @project.name if @project
543 title << Setting.app_title unless Setting.app_title == title.last
544 title << Setting.app_title unless Setting.app_title == title.last
544 title.reject(&:blank?).join(' - ')
545 title.reject(&:blank?).join(' - ')
545 else
546 else
546 @html_title ||= []
547 @html_title ||= []
547 @html_title += args
548 @html_title += args
548 end
549 end
549 end
550 end
550
551
551 # Returns the theme, controller name, and action as css classes for the
552 # Returns the theme, controller name, and action as css classes for the
552 # HTML body.
553 # HTML body.
553 def body_css_classes
554 def body_css_classes
554 css = []
555 css = []
555 if theme = Redmine::Themes.theme(Setting.ui_theme)
556 if theme = Redmine::Themes.theme(Setting.ui_theme)
556 css << 'theme-' + theme.name
557 css << 'theme-' + theme.name
557 end
558 end
558
559
559 css << 'project-' + @project.identifier if @project && @project.identifier.present?
560 css << 'project-' + @project.identifier if @project && @project.identifier.present?
560 css << 'controller-' + controller_name
561 css << 'controller-' + controller_name
561 css << 'action-' + action_name
562 css << 'action-' + action_name
562 css.join(' ')
563 css.join(' ')
563 end
564 end
564
565
565 def accesskey(s)
566 def accesskey(s)
566 @used_accesskeys ||= []
567 @used_accesskeys ||= []
567 key = Redmine::AccessKeys.key_for(s)
568 key = Redmine::AccessKeys.key_for(s)
568 return nil if @used_accesskeys.include?(key)
569 return nil if @used_accesskeys.include?(key)
569 @used_accesskeys << key
570 @used_accesskeys << key
570 key
571 key
571 end
572 end
572
573
573 # Formats text according to system settings.
574 # Formats text according to system settings.
574 # 2 ways to call this method:
575 # 2 ways to call this method:
575 # * with a String: textilizable(text, options)
576 # * with a String: textilizable(text, options)
576 # * with an object and one of its attribute: textilizable(issue, :description, options)
577 # * with an object and one of its attribute: textilizable(issue, :description, options)
577 def textilizable(*args)
578 def textilizable(*args)
578 options = args.last.is_a?(Hash) ? args.pop : {}
579 options = args.last.is_a?(Hash) ? args.pop : {}
579 case args.size
580 case args.size
580 when 1
581 when 1
581 obj = options[:object]
582 obj = options[:object]
582 text = args.shift
583 text = args.shift
583 when 2
584 when 2
584 obj = args.shift
585 obj = args.shift
585 attr = args.shift
586 attr = args.shift
586 text = obj.send(attr).to_s
587 text = obj.send(attr).to_s
587 else
588 else
588 raise ArgumentError, 'invalid arguments to textilizable'
589 raise ArgumentError, 'invalid arguments to textilizable'
589 end
590 end
590 return '' if text.blank?
591 return '' if text.blank?
591 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
592 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
592 @only_path = only_path = options.delete(:only_path) == false ? false : true
593 @only_path = only_path = options.delete(:only_path) == false ? false : true
593
594
594 text = text.dup
595 text = text.dup
595 macros = catch_macros(text)
596 macros = catch_macros(text)
596 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
597 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
597
598
598 @parsed_headings = []
599 @parsed_headings = []
599 @heading_anchors = {}
600 @heading_anchors = {}
600 @current_section = 0 if options[:edit_section_links]
601 @current_section = 0 if options[:edit_section_links]
601
602
602 parse_sections(text, project, obj, attr, only_path, options)
603 parse_sections(text, project, obj, attr, only_path, options)
603 text = parse_non_pre_blocks(text, obj, macros) do |text|
604 text = parse_non_pre_blocks(text, obj, macros) do |text|
604 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
605 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
605 send method_name, text, project, obj, attr, only_path, options
606 send method_name, text, project, obj, attr, only_path, options
606 end
607 end
607 end
608 end
608 parse_headings(text, project, obj, attr, only_path, options)
609 parse_headings(text, project, obj, attr, only_path, options)
609
610
610 if @parsed_headings.any?
611 if @parsed_headings.any?
611 replace_toc(text, @parsed_headings)
612 replace_toc(text, @parsed_headings)
612 end
613 end
613
614
614 text.html_safe
615 text.html_safe
615 end
616 end
616
617
617 def parse_non_pre_blocks(text, obj, macros)
618 def parse_non_pre_blocks(text, obj, macros)
618 s = StringScanner.new(text)
619 s = StringScanner.new(text)
619 tags = []
620 tags = []
620 parsed = ''
621 parsed = ''
621 while !s.eos?
622 while !s.eos?
622 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
623 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
623 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
624 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
624 if tags.empty?
625 if tags.empty?
625 yield text
626 yield text
626 inject_macros(text, obj, macros) if macros.any?
627 inject_macros(text, obj, macros) if macros.any?
627 else
628 else
628 inject_macros(text, obj, macros, false) if macros.any?
629 inject_macros(text, obj, macros, false) if macros.any?
629 end
630 end
630 parsed << text
631 parsed << text
631 if tag
632 if tag
632 if closing
633 if closing
633 if tags.last && tags.last.casecmp(tag) == 0
634 if tags.last && tags.last.casecmp(tag) == 0
634 tags.pop
635 tags.pop
635 end
636 end
636 else
637 else
637 tags << tag.downcase
638 tags << tag.downcase
638 end
639 end
639 parsed << full_tag
640 parsed << full_tag
640 end
641 end
641 end
642 end
642 # Close any non closing tags
643 # Close any non closing tags
643 while tag = tags.pop
644 while tag = tags.pop
644 parsed << "</#{tag}>"
645 parsed << "</#{tag}>"
645 end
646 end
646 parsed
647 parsed
647 end
648 end
648
649
649 def parse_inline_attachments(text, project, obj, attr, only_path, options)
650 def parse_inline_attachments(text, project, obj, attr, only_path, options)
650 return if options[:inline_attachments] == false
651 return if options[:inline_attachments] == false
651
652
652 # when using an image link, try to use an attachment, if possible
653 # when using an image link, try to use an attachment, if possible
653 attachments = options[:attachments] || []
654 attachments = options[:attachments] || []
654 attachments += obj.attachments if obj.respond_to?(:attachments)
655 attachments += obj.attachments if obj.respond_to?(:attachments)
655 if attachments.present?
656 if attachments.present?
656 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
657 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
657 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
658 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
658 # search for the picture in attachments
659 # search for the picture in attachments
659 if found = Attachment.latest_attach(attachments, CGI.unescape(filename))
660 if found = Attachment.latest_attach(attachments, CGI.unescape(filename))
660 image_url = download_named_attachment_url(found, found.filename, :only_path => only_path)
661 image_url = download_named_attachment_url(found, found.filename, :only_path => only_path)
661 desc = found.description.to_s.gsub('"', '')
662 desc = found.description.to_s.gsub('"', '')
662 if !desc.blank? && alttext.blank?
663 if !desc.blank? && alttext.blank?
663 alt = " title=\"#{desc}\" alt=\"#{desc}\""
664 alt = " title=\"#{desc}\" alt=\"#{desc}\""
664 end
665 end
665 "src=\"#{image_url}\"#{alt}"
666 "src=\"#{image_url}\"#{alt}"
666 else
667 else
667 m
668 m
668 end
669 end
669 end
670 end
670 end
671 end
671 end
672 end
672
673
673 # Wiki links
674 # Wiki links
674 #
675 #
675 # Examples:
676 # Examples:
676 # [[mypage]]
677 # [[mypage]]
677 # [[mypage|mytext]]
678 # [[mypage|mytext]]
678 # wiki links can refer other project wikis, using project name or identifier:
679 # wiki links can refer other project wikis, using project name or identifier:
679 # [[project:]] -> wiki starting page
680 # [[project:]] -> wiki starting page
680 # [[project:|mytext]]
681 # [[project:|mytext]]
681 # [[project:mypage]]
682 # [[project:mypage]]
682 # [[project:mypage|mytext]]
683 # [[project:mypage|mytext]]
683 def parse_wiki_links(text, project, obj, attr, only_path, options)
684 def parse_wiki_links(text, project, obj, attr, only_path, options)
684 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
685 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
685 link_project = project
686 link_project = project
686 esc, all, page, title = $1, $2, $3, $5
687 esc, all, page, title = $1, $2, $3, $5
687 if esc.nil?
688 if esc.nil?
688 if page =~ /^([^\:]+)\:(.*)$/
689 if page =~ /^([^\:]+)\:(.*)$/
689 identifier, page = $1, $2
690 identifier, page = $1, $2
690 link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
691 link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
691 title ||= identifier if page.blank?
692 title ||= identifier if page.blank?
692 end
693 end
693
694
694 if link_project && link_project.wiki
695 if link_project && link_project.wiki
695 # extract anchor
696 # extract anchor
696 anchor = nil
697 anchor = nil
697 if page =~ /^(.+?)\#(.+)$/
698 if page =~ /^(.+?)\#(.+)$/
698 page, anchor = $1, $2
699 page, anchor = $1, $2
699 end
700 end
700 anchor = sanitize_anchor_name(anchor) if anchor.present?
701 anchor = sanitize_anchor_name(anchor) if anchor.present?
701 # check if page exists
702 # check if page exists
702 wiki_page = link_project.wiki.find_page(page)
703 wiki_page = link_project.wiki.find_page(page)
703 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
704 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
704 "##{anchor}"
705 "##{anchor}"
705 else
706 else
706 case options[:wiki_links]
707 case options[:wiki_links]
707 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
708 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
708 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
709 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
709 else
710 else
710 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
711 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
711 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
712 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
712 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
713 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
713 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
714 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
714 end
715 end
715 end
716 end
716 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
717 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
717 else
718 else
718 # project or wiki doesn't exist
719 # project or wiki doesn't exist
719 all
720 all
720 end
721 end
721 else
722 else
722 all
723 all
723 end
724 end
724 end
725 end
725 end
726 end
726
727
727 # Redmine links
728 # Redmine links
728 #
729 #
729 # Examples:
730 # Examples:
730 # Issues:
731 # Issues:
731 # #52 -> Link to issue #52
732 # #52 -> Link to issue #52
732 # Changesets:
733 # Changesets:
733 # r52 -> Link to revision 52
734 # r52 -> Link to revision 52
734 # commit:a85130f -> Link to scmid starting with a85130f
735 # commit:a85130f -> Link to scmid starting with a85130f
735 # Documents:
736 # Documents:
736 # document#17 -> Link to document with id 17
737 # document#17 -> Link to document with id 17
737 # document:Greetings -> Link to the document with title "Greetings"
738 # document:Greetings -> Link to the document with title "Greetings"
738 # document:"Some document" -> Link to the document with title "Some document"
739 # document:"Some document" -> Link to the document with title "Some document"
739 # Versions:
740 # Versions:
740 # version#3 -> Link to version with id 3
741 # version#3 -> Link to version with id 3
741 # version:1.0.0 -> Link to version named "1.0.0"
742 # version:1.0.0 -> Link to version named "1.0.0"
742 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
743 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
743 # Attachments:
744 # Attachments:
744 # attachment:file.zip -> Link to the attachment of the current object named file.zip
745 # attachment:file.zip -> Link to the attachment of the current object named file.zip
745 # Source files:
746 # Source files:
746 # source:some/file -> Link to the file located at /some/file in the project's repository
747 # source:some/file -> Link to the file located at /some/file in the project's repository
747 # source:some/file@52 -> Link to the file's revision 52
748 # source:some/file@52 -> Link to the file's revision 52
748 # source:some/file#L120 -> Link to line 120 of the file
749 # source:some/file#L120 -> Link to line 120 of the file
749 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
750 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
750 # export:some/file -> Force the download of the file
751 # export:some/file -> Force the download of the file
751 # Forum messages:
752 # Forum messages:
752 # message#1218 -> Link to message with id 1218
753 # message#1218 -> Link to message with id 1218
753 # Projects:
754 # Projects:
754 # project:someproject -> Link to project named "someproject"
755 # project:someproject -> Link to project named "someproject"
755 # project#3 -> Link to project with id 3
756 # project#3 -> Link to project with id 3
756 #
757 #
757 # Links can refer other objects from other projects, using project identifier:
758 # Links can refer other objects from other projects, using project identifier:
758 # identifier:r52
759 # identifier:r52
759 # identifier:document:"Some document"
760 # identifier:document:"Some document"
760 # identifier:version:1.0.0
761 # identifier:version:1.0.0
761 # identifier:source:some/file
762 # identifier:source:some/file
762 def parse_redmine_links(text, default_project, obj, attr, only_path, options)
763 def parse_redmine_links(text, default_project, obj, attr, only_path, options)
763 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|
764 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|
764 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
765 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
765 if tag_content
766 if tag_content
766 $&
767 $&
767 else
768 else
768 link = nil
769 link = nil
769 project = default_project
770 project = default_project
770 if project_identifier
771 if project_identifier
771 project = Project.visible.find_by_identifier(project_identifier)
772 project = Project.visible.find_by_identifier(project_identifier)
772 end
773 end
773 if esc.nil?
774 if esc.nil?
774 if prefix.nil? && sep == 'r'
775 if prefix.nil? && sep == 'r'
775 if project
776 if project
776 repository = nil
777 repository = nil
777 if repo_identifier
778 if repo_identifier
778 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
779 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
779 else
780 else
780 repository = project.repository
781 repository = project.repository
781 end
782 end
782 # project.changesets.visible raises an SQL error because of a double join on repositories
783 # project.changesets.visible raises an SQL error because of a double join on repositories
783 if repository &&
784 if repository &&
784 (changeset = Changeset.visible.
785 (changeset = Changeset.visible.
785 find_by_repository_id_and_revision(repository.id, identifier))
786 find_by_repository_id_and_revision(repository.id, identifier))
786 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"),
787 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"),
787 {:only_path => only_path, :controller => 'repositories',
788 {:only_path => only_path, :controller => 'repositories',
788 :action => 'revision', :id => project,
789 :action => 'revision', :id => project,
789 :repository_id => repository.identifier_param,
790 :repository_id => repository.identifier_param,
790 :rev => changeset.revision},
791 :rev => changeset.revision},
791 :class => 'changeset',
792 :class => 'changeset',
792 :title => truncate_single_line_raw(changeset.comments, 100))
793 :title => truncate_single_line_raw(changeset.comments, 100))
793 end
794 end
794 end
795 end
795 elsif sep == '#'
796 elsif sep == '#'
796 oid = identifier.to_i
797 oid = identifier.to_i
797 case prefix
798 case prefix
798 when nil
799 when nil
799 if oid.to_s == identifier &&
800 if oid.to_s == identifier &&
800 issue = Issue.visible.find_by_id(oid)
801 issue = Issue.visible.find_by_id(oid)
801 anchor = comment_id ? "note-#{comment_id}" : nil
802 anchor = comment_id ? "note-#{comment_id}" : nil
802 link = link_to("##{oid}#{comment_suffix}",
803 link = link_to("##{oid}#{comment_suffix}",
803 issue_url(issue, :only_path => only_path, :anchor => anchor),
804 issue_url(issue, :only_path => only_path, :anchor => anchor),
804 :class => issue.css_classes,
805 :class => issue.css_classes,
805 :title => "#{issue.tracker.name}: #{issue.subject.truncate(100)} (#{issue.status.name})")
806 :title => "#{issue.tracker.name}: #{issue.subject.truncate(100)} (#{issue.status.name})")
806 end
807 end
807 when 'document'
808 when 'document'
808 if document = Document.visible.find_by_id(oid)
809 if document = Document.visible.find_by_id(oid)
809 link = link_to(document.title, document_url(document, :only_path => only_path), :class => 'document')
810 link = link_to(document.title, document_url(document, :only_path => only_path), :class => 'document')
810 end
811 end
811 when 'version'
812 when 'version'
812 if version = Version.visible.find_by_id(oid)
813 if version = Version.visible.find_by_id(oid)
813 link = link_to(version.name, version_url(version, :only_path => only_path), :class => 'version')
814 link = link_to(version.name, version_url(version, :only_path => only_path), :class => 'version')
814 end
815 end
815 when 'message'
816 when 'message'
816 if message = Message.visible.find_by_id(oid)
817 if message = Message.visible.find_by_id(oid)
817 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
818 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
818 end
819 end
819 when 'forum'
820 when 'forum'
820 if board = Board.visible.find_by_id(oid)
821 if board = Board.visible.find_by_id(oid)
821 link = link_to(board.name, project_board_url(board.project, board, :only_path => only_path), :class => 'board')
822 link = link_to(board.name, project_board_url(board.project, board, :only_path => only_path), :class => 'board')
822 end
823 end
823 when 'news'
824 when 'news'
824 if news = News.visible.find_by_id(oid)
825 if news = News.visible.find_by_id(oid)
825 link = link_to(news.title, news_url(news, :only_path => only_path), :class => 'news')
826 link = link_to(news.title, news_url(news, :only_path => only_path), :class => 'news')
826 end
827 end
827 when 'project'
828 when 'project'
828 if p = Project.visible.find_by_id(oid)
829 if p = Project.visible.find_by_id(oid)
829 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
830 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
830 end
831 end
831 end
832 end
832 elsif sep == ':'
833 elsif sep == ':'
833 # removes the double quotes if any
834 # removes the double quotes if any
834 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
835 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
835 name = CGI.unescapeHTML(name)
836 name = CGI.unescapeHTML(name)
836 case prefix
837 case prefix
837 when 'document'
838 when 'document'
838 if project && document = project.documents.visible.find_by_title(name)
839 if project && document = project.documents.visible.find_by_title(name)
839 link = link_to(document.title, document_url(document, :only_path => only_path), :class => 'document')
840 link = link_to(document.title, document_url(document, :only_path => only_path), :class => 'document')
840 end
841 end
841 when 'version'
842 when 'version'
842 if project && version = project.versions.visible.find_by_name(name)
843 if project && version = project.versions.visible.find_by_name(name)
843 link = link_to(version.name, version_url(version, :only_path => only_path), :class => 'version')
844 link = link_to(version.name, version_url(version, :only_path => only_path), :class => 'version')
844 end
845 end
845 when 'forum'
846 when 'forum'
846 if project && board = project.boards.visible.find_by_name(name)
847 if project && board = project.boards.visible.find_by_name(name)
847 link = link_to(board.name, project_board_url(board.project, board, :only_path => only_path), :class => 'board')
848 link = link_to(board.name, project_board_url(board.project, board, :only_path => only_path), :class => 'board')
848 end
849 end
849 when 'news'
850 when 'news'
850 if project && news = project.news.visible.find_by_title(name)
851 if project && news = project.news.visible.find_by_title(name)
851 link = link_to(news.title, news_url(news, :only_path => only_path), :class => 'news')
852 link = link_to(news.title, news_url(news, :only_path => only_path), :class => 'news')
852 end
853 end
853 when 'commit', 'source', 'export'
854 when 'commit', 'source', 'export'
854 if project
855 if project
855 repository = nil
856 repository = nil
856 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
857 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
857 repo_prefix, repo_identifier, name = $1, $2, $3
858 repo_prefix, repo_identifier, name = $1, $2, $3
858 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
859 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
859 else
860 else
860 repository = project.repository
861 repository = project.repository
861 end
862 end
862 if prefix == 'commit'
863 if prefix == 'commit'
863 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
864 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
864 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},
865 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},
865 :class => 'changeset',
866 :class => 'changeset',
866 :title => truncate_single_line_raw(changeset.comments, 100)
867 :title => truncate_single_line_raw(changeset.comments, 100)
867 end
868 end
868 else
869 else
869 if repository && User.current.allowed_to?(:browse_repository, project)
870 if repository && User.current.allowed_to?(:browse_repository, project)
870 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
871 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
871 path, rev, anchor = $1, $3, $5
872 path, rev, anchor = $1, $3, $5
872 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,
873 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,
873 :path => to_path_param(path),
874 :path => to_path_param(path),
874 :rev => rev,
875 :rev => rev,
875 :anchor => anchor},
876 :anchor => anchor},
876 :class => (prefix == 'export' ? 'source download' : 'source')
877 :class => (prefix == 'export' ? 'source download' : 'source')
877 end
878 end
878 end
879 end
879 repo_prefix = nil
880 repo_prefix = nil
880 end
881 end
881 when 'attachment'
882 when 'attachment'
882 attachments = options[:attachments] || []
883 attachments = options[:attachments] || []
883 attachments += obj.attachments if obj.respond_to?(:attachments)
884 attachments += obj.attachments if obj.respond_to?(:attachments)
884 if attachments && attachment = Attachment.latest_attach(attachments, name)
885 if attachments && attachment = Attachment.latest_attach(attachments, name)
885 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
886 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
886 end
887 end
887 when 'project'
888 when 'project'
888 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
889 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
889 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
890 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
890 end
891 end
891 end
892 end
892 end
893 end
893 end
894 end
894 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
895 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
895 end
896 end
896 end
897 end
897 end
898 end
898
899
899 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
900 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
900
901
901 def parse_sections(text, project, obj, attr, only_path, options)
902 def parse_sections(text, project, obj, attr, only_path, options)
902 return unless options[:edit_section_links]
903 return unless options[:edit_section_links]
903 text.gsub!(HEADING_RE) do
904 text.gsub!(HEADING_RE) do
904 heading, level = $1, $2
905 heading, level = $1, $2
905 @current_section += 1
906 @current_section += 1
906 if @current_section > 1
907 if @current_section > 1
907 content_tag('div',
908 content_tag('div',
908 link_to(l(:button_edit_section), options[:edit_section_links].merge(:section => @current_section),
909 link_to(l(:button_edit_section), options[:edit_section_links].merge(:section => @current_section),
909 :class => 'icon-only icon-edit'),
910 :class => 'icon-only icon-edit'),
910 :class => "contextual heading-#{level}",
911 :class => "contextual heading-#{level}",
911 :title => l(:button_edit_section),
912 :title => l(:button_edit_section),
912 :id => "section-#{@current_section}") + heading.html_safe
913 :id => "section-#{@current_section}") + heading.html_safe
913 else
914 else
914 heading
915 heading
915 end
916 end
916 end
917 end
917 end
918 end
918
919
919 # Headings and TOC
920 # Headings and TOC
920 # Adds ids and links to headings unless options[:headings] is set to false
921 # Adds ids and links to headings unless options[:headings] is set to false
921 def parse_headings(text, project, obj, attr, only_path, options)
922 def parse_headings(text, project, obj, attr, only_path, options)
922 return if options[:headings] == false
923 return if options[:headings] == false
923
924
924 text.gsub!(HEADING_RE) do
925 text.gsub!(HEADING_RE) do
925 level, attrs, content = $2.to_i, $3, $4
926 level, attrs, content = $2.to_i, $3, $4
926 item = strip_tags(content).strip
927 item = strip_tags(content).strip
927 anchor = sanitize_anchor_name(item)
928 anchor = sanitize_anchor_name(item)
928 # used for single-file wiki export
929 # used for single-file wiki export
929 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
930 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
930 @heading_anchors[anchor] ||= 0
931 @heading_anchors[anchor] ||= 0
931 idx = (@heading_anchors[anchor] += 1)
932 idx = (@heading_anchors[anchor] += 1)
932 if idx > 1
933 if idx > 1
933 anchor = "#{anchor}-#{idx}"
934 anchor = "#{anchor}-#{idx}"
934 end
935 end
935 @parsed_headings << [level, anchor, item]
936 @parsed_headings << [level, anchor, item]
936 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
937 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
937 end
938 end
938 end
939 end
939
940
940 MACROS_RE = /(
941 MACROS_RE = /(
941 (!)? # escaping
942 (!)? # escaping
942 (
943 (
943 \{\{ # opening tag
944 \{\{ # opening tag
944 ([\w]+) # macro name
945 ([\w]+) # macro name
945 (\(([^\n\r]*?)\))? # optional arguments
946 (\(([^\n\r]*?)\))? # optional arguments
946 ([\n\r].*?[\n\r])? # optional block of text
947 ([\n\r].*?[\n\r])? # optional block of text
947 \}\} # closing tag
948 \}\} # closing tag
948 )
949 )
949 )/mx unless const_defined?(:MACROS_RE)
950 )/mx unless const_defined?(:MACROS_RE)
950
951
951 MACRO_SUB_RE = /(
952 MACRO_SUB_RE = /(
952 \{\{
953 \{\{
953 macro\((\d+)\)
954 macro\((\d+)\)
954 \}\}
955 \}\}
955 )/x unless const_defined?(:MACRO_SUB_RE)
956 )/x unless const_defined?(:MACRO_SUB_RE)
956
957
957 # Extracts macros from text
958 # Extracts macros from text
958 def catch_macros(text)
959 def catch_macros(text)
959 macros = {}
960 macros = {}
960 text.gsub!(MACROS_RE) do
961 text.gsub!(MACROS_RE) do
961 all, macro = $1, $4.downcase
962 all, macro = $1, $4.downcase
962 if macro_exists?(macro) || all =~ MACRO_SUB_RE
963 if macro_exists?(macro) || all =~ MACRO_SUB_RE
963 index = macros.size
964 index = macros.size
964 macros[index] = all
965 macros[index] = all
965 "{{macro(#{index})}}"
966 "{{macro(#{index})}}"
966 else
967 else
967 all
968 all
968 end
969 end
969 end
970 end
970 macros
971 macros
971 end
972 end
972
973
973 # Executes and replaces macros in text
974 # Executes and replaces macros in text
974 def inject_macros(text, obj, macros, execute=true)
975 def inject_macros(text, obj, macros, execute=true)
975 text.gsub!(MACRO_SUB_RE) do
976 text.gsub!(MACRO_SUB_RE) do
976 all, index = $1, $2.to_i
977 all, index = $1, $2.to_i
977 orig = macros.delete(index)
978 orig = macros.delete(index)
978 if execute && orig && orig =~ MACROS_RE
979 if execute && orig && orig =~ MACROS_RE
979 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
980 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
980 if esc.nil?
981 if esc.nil?
981 h(exec_macro(macro, obj, args, block) || all)
982 h(exec_macro(macro, obj, args, block) || all)
982 else
983 else
983 h(all)
984 h(all)
984 end
985 end
985 elsif orig
986 elsif orig
986 h(orig)
987 h(orig)
987 else
988 else
988 h(all)
989 h(all)
989 end
990 end
990 end
991 end
991 end
992 end
992
993
993 TOC_RE = /<p>\{\{((<|&lt;)|(>|&gt;))?toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
994 TOC_RE = /<p>\{\{((<|&lt;)|(>|&gt;))?toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
994
995
995 # Renders the TOC with given headings
996 # Renders the TOC with given headings
996 def replace_toc(text, headings)
997 def replace_toc(text, headings)
997 text.gsub!(TOC_RE) do
998 text.gsub!(TOC_RE) do
998 left_align, right_align = $2, $3
999 left_align, right_align = $2, $3
999 # Keep only the 4 first levels
1000 # Keep only the 4 first levels
1000 headings = headings.select{|level, anchor, item| level <= 4}
1001 headings = headings.select{|level, anchor, item| level <= 4}
1001 if headings.empty?
1002 if headings.empty?
1002 ''
1003 ''
1003 else
1004 else
1004 div_class = 'toc'
1005 div_class = 'toc'
1005 div_class << ' right' if right_align
1006 div_class << ' right' if right_align
1006 div_class << ' left' if left_align
1007 div_class << ' left' if left_align
1007 out = "<ul class=\"#{div_class}\"><li>"
1008 out = "<ul class=\"#{div_class}\"><li>"
1008 root = headings.map(&:first).min
1009 root = headings.map(&:first).min
1009 current = root
1010 current = root
1010 started = false
1011 started = false
1011 headings.each do |level, anchor, item|
1012 headings.each do |level, anchor, item|
1012 if level > current
1013 if level > current
1013 out << '<ul><li>' * (level - current)
1014 out << '<ul><li>' * (level - current)
1014 elsif level < current
1015 elsif level < current
1015 out << "</li></ul>\n" * (current - level) + "</li><li>"
1016 out << "</li></ul>\n" * (current - level) + "</li><li>"
1016 elsif started
1017 elsif started
1017 out << '</li><li>'
1018 out << '</li><li>'
1018 end
1019 end
1019 out << "<a href=\"##{anchor}\">#{item}</a>"
1020 out << "<a href=\"##{anchor}\">#{item}</a>"
1020 current = level
1021 current = level
1021 started = true
1022 started = true
1022 end
1023 end
1023 out << '</li></ul>' * (current - root)
1024 out << '</li></ul>' * (current - root)
1024 out << '</li></ul>'
1025 out << '</li></ul>'
1025 end
1026 end
1026 end
1027 end
1027 end
1028 end
1028
1029
1029 # Same as Rails' simple_format helper without using paragraphs
1030 # Same as Rails' simple_format helper without using paragraphs
1030 def simple_format_without_paragraph(text)
1031 def simple_format_without_paragraph(text)
1031 text.to_s.
1032 text.to_s.
1032 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
1033 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
1033 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
1034 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
1034 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
1035 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
1035 html_safe
1036 html_safe
1036 end
1037 end
1037
1038
1038 def lang_options_for_select(blank=true)
1039 def lang_options_for_select(blank=true)
1039 (blank ? [["(auto)", ""]] : []) + languages_options
1040 (blank ? [["(auto)", ""]] : []) + languages_options
1040 end
1041 end
1041
1042
1042 def labelled_form_for(*args, &proc)
1043 def labelled_form_for(*args, &proc)
1043 args << {} unless args.last.is_a?(Hash)
1044 args << {} unless args.last.is_a?(Hash)
1044 options = args.last
1045 options = args.last
1045 if args.first.is_a?(Symbol)
1046 if args.first.is_a?(Symbol)
1046 options.merge!(:as => args.shift)
1047 options.merge!(:as => args.shift)
1047 end
1048 end
1048 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1049 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1049 form_for(*args, &proc)
1050 form_for(*args, &proc)
1050 end
1051 end
1051
1052
1052 def labelled_fields_for(*args, &proc)
1053 def labelled_fields_for(*args, &proc)
1053 args << {} unless args.last.is_a?(Hash)
1054 args << {} unless args.last.is_a?(Hash)
1054 options = args.last
1055 options = args.last
1055 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1056 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1056 fields_for(*args, &proc)
1057 fields_for(*args, &proc)
1057 end
1058 end
1058
1059
1059 def error_messages_for(*objects)
1060 def error_messages_for(*objects)
1060 html = ""
1061 html = ""
1061 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
1062 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
1062 errors = objects.map {|o| o.errors.full_messages}.flatten
1063 errors = objects.map {|o| o.errors.full_messages}.flatten
1063 if errors.any?
1064 if errors.any?
1064 html << "<div id='errorExplanation'><ul>\n"
1065 html << "<div id='errorExplanation'><ul>\n"
1065 errors.each do |error|
1066 errors.each do |error|
1066 html << "<li>#{h error}</li>\n"
1067 html << "<li>#{h error}</li>\n"
1067 end
1068 end
1068 html << "</ul></div>\n"
1069 html << "</ul></div>\n"
1069 end
1070 end
1070 html.html_safe
1071 html.html_safe
1071 end
1072 end
1072
1073
1073 def delete_link(url, options={})
1074 def delete_link(url, options={})
1074 options = {
1075 options = {
1075 :method => :delete,
1076 :method => :delete,
1076 :data => {:confirm => l(:text_are_you_sure)},
1077 :data => {:confirm => l(:text_are_you_sure)},
1077 :class => 'icon icon-del'
1078 :class => 'icon icon-del'
1078 }.merge(options)
1079 }.merge(options)
1079
1080
1080 link_to l(:button_delete), url, options
1081 link_to l(:button_delete), url, options
1081 end
1082 end
1082
1083
1083 def preview_link(url, form, target='preview', options={})
1084 def preview_link(url, form, target='preview', options={})
1084 content_tag 'a', l(:label_preview), {
1085 content_tag 'a', l(:label_preview), {
1085 :href => "#",
1086 :href => "#",
1086 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1087 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1087 :accesskey => accesskey(:preview)
1088 :accesskey => accesskey(:preview)
1088 }.merge(options)
1089 }.merge(options)
1089 end
1090 end
1090
1091
1091 def link_to_function(name, function, html_options={})
1092 def link_to_function(name, function, html_options={})
1092 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1093 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1093 end
1094 end
1094
1095
1095 # Helper to render JSON in views
1096 # Helper to render JSON in views
1096 def raw_json(arg)
1097 def raw_json(arg)
1097 arg.to_json.to_s.gsub('/', '\/').html_safe
1098 arg.to_json.to_s.gsub('/', '\/').html_safe
1098 end
1099 end
1099
1100
1100 def back_url
1101 def back_url
1101 url = params[:back_url]
1102 url = params[:back_url]
1102 if url.nil? && referer = request.env['HTTP_REFERER']
1103 if url.nil? && referer = request.env['HTTP_REFERER']
1103 url = CGI.unescape(referer.to_s)
1104 url = CGI.unescape(referer.to_s)
1104 end
1105 end
1105 url
1106 url
1106 end
1107 end
1107
1108
1108 def back_url_hidden_field_tag
1109 def back_url_hidden_field_tag
1109 url = back_url
1110 url = back_url
1110 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1111 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1111 end
1112 end
1112
1113
1113 def check_all_links(form_name)
1114 def check_all_links(form_name)
1114 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1115 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1115 " | ".html_safe +
1116 " | ".html_safe +
1116 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1117 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1117 end
1118 end
1118
1119
1119 def toggle_checkboxes_link(selector)
1120 def toggle_checkboxes_link(selector)
1120 link_to_function '',
1121 link_to_function '',
1121 "toggleCheckboxesBySelector('#{selector}')",
1122 "toggleCheckboxesBySelector('#{selector}')",
1122 :title => "#{l(:button_check_all)} / #{l(:button_uncheck_all)}",
1123 :title => "#{l(:button_check_all)} / #{l(:button_uncheck_all)}",
1123 :class => 'toggle-checkboxes'
1124 :class => 'toggle-checkboxes'
1124 end
1125 end
1125
1126
1126 def progress_bar(pcts, options={})
1127 def progress_bar(pcts, options={})
1127 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1128 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1128 pcts = pcts.collect(&:round)
1129 pcts = pcts.collect(&:round)
1129 pcts[1] = pcts[1] - pcts[0]
1130 pcts[1] = pcts[1] - pcts[0]
1130 pcts << (100 - pcts[1] - pcts[0])
1131 pcts << (100 - pcts[1] - pcts[0])
1131 titles = options[:titles].to_a
1132 titles = options[:titles].to_a
1132 titles[0] = "#{pcts[0]}%" if titles[0].blank?
1133 titles[0] = "#{pcts[0]}%" if titles[0].blank?
1133 legend = options[:legend] || ''
1134 legend = options[:legend] || ''
1134 content_tag('table',
1135 content_tag('table',
1135 content_tag('tr',
1136 content_tag('tr',
1136 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed', :title => titles[0]) : ''.html_safe) +
1137 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed', :title => titles[0]) : ''.html_safe) +
1137 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done', :title => titles[1]) : ''.html_safe) +
1138 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done', :title => titles[1]) : ''.html_safe) +
1138 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo', :title => titles[2]) : ''.html_safe)
1139 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo', :title => titles[2]) : ''.html_safe)
1139 ), :class => "progress progress-#{pcts[0]}").html_safe +
1140 ), :class => "progress progress-#{pcts[0]}").html_safe +
1140 content_tag('p', legend, :class => 'percent').html_safe
1141 content_tag('p', legend, :class => 'percent').html_safe
1141 end
1142 end
1142
1143
1143 def checked_image(checked=true)
1144 def checked_image(checked=true)
1144 if checked
1145 if checked
1145 @checked_image_tag ||= content_tag(:span, nil, :class => 'icon-only icon-checked')
1146 @checked_image_tag ||= content_tag(:span, nil, :class => 'icon-only icon-checked')
1146 end
1147 end
1147 end
1148 end
1148
1149
1149 def context_menu(url)
1150 def context_menu(url)
1150 unless @context_menu_included
1151 unless @context_menu_included
1151 content_for :header_tags do
1152 content_for :header_tags do
1152 javascript_include_tag('context_menu') +
1153 javascript_include_tag('context_menu') +
1153 stylesheet_link_tag('context_menu')
1154 stylesheet_link_tag('context_menu')
1154 end
1155 end
1155 if l(:direction) == 'rtl'
1156 if l(:direction) == 'rtl'
1156 content_for :header_tags do
1157 content_for :header_tags do
1157 stylesheet_link_tag('context_menu_rtl')
1158 stylesheet_link_tag('context_menu_rtl')
1158 end
1159 end
1159 end
1160 end
1160 @context_menu_included = true
1161 @context_menu_included = true
1161 end
1162 end
1162 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1163 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1163 end
1164 end
1164
1165
1165 def calendar_for(field_id)
1166 def calendar_for(field_id)
1166 include_calendar_headers_tags
1167 include_calendar_headers_tags
1167 javascript_tag("$(function() { $('##{field_id}').addClass('date').datepicker(datepickerOptions); });")
1168 javascript_tag("$(function() { $('##{field_id}').addClass('date').datepicker(datepickerOptions); });")
1168 end
1169 end
1169
1170
1170 def include_calendar_headers_tags
1171 def include_calendar_headers_tags
1171 unless @calendar_headers_tags_included
1172 unless @calendar_headers_tags_included
1172 tags = ''.html_safe
1173 tags = ''.html_safe
1173 @calendar_headers_tags_included = true
1174 @calendar_headers_tags_included = true
1174 content_for :header_tags do
1175 content_for :header_tags do
1175 start_of_week = Setting.start_of_week
1176 start_of_week = Setting.start_of_week
1176 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1177 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1177 # Redmine uses 1..7 (monday..sunday) in settings and locales
1178 # Redmine uses 1..7 (monday..sunday) in settings and locales
1178 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1179 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1179 start_of_week = start_of_week.to_i % 7
1180 start_of_week = start_of_week.to_i % 7
1180 tags << javascript_tag(
1181 tags << javascript_tag(
1181 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1182 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1182 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1183 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1183 path_to_image('/images/calendar.png') +
1184 path_to_image('/images/calendar.png') +
1184 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
1185 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
1185 "selectOtherMonths: true, changeMonth: true, changeYear: true, " +
1186 "selectOtherMonths: true, changeMonth: true, changeYear: true, " +
1186 "beforeShow: beforeShowDatePicker};")
1187 "beforeShow: beforeShowDatePicker};")
1187 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1188 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1188 unless jquery_locale == 'en'
1189 unless jquery_locale == 'en'
1189 tags << javascript_include_tag("i18n/datepicker-#{jquery_locale}.js")
1190 tags << javascript_include_tag("i18n/datepicker-#{jquery_locale}.js")
1190 end
1191 end
1191 tags
1192 tags
1192 end
1193 end
1193 end
1194 end
1194 end
1195 end
1195
1196
1196 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1197 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1197 # Examples:
1198 # Examples:
1198 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1199 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1199 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1200 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1200 #
1201 #
1201 def stylesheet_link_tag(*sources)
1202 def stylesheet_link_tag(*sources)
1202 options = sources.last.is_a?(Hash) ? sources.pop : {}
1203 options = sources.last.is_a?(Hash) ? sources.pop : {}
1203 plugin = options.delete(:plugin)
1204 plugin = options.delete(:plugin)
1204 sources = sources.map do |source|
1205 sources = sources.map do |source|
1205 if plugin
1206 if plugin
1206 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1207 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1207 elsif current_theme && current_theme.stylesheets.include?(source)
1208 elsif current_theme && current_theme.stylesheets.include?(source)
1208 current_theme.stylesheet_path(source)
1209 current_theme.stylesheet_path(source)
1209 else
1210 else
1210 source
1211 source
1211 end
1212 end
1212 end
1213 end
1213 super *sources, options
1214 super *sources, options
1214 end
1215 end
1215
1216
1216 # Overrides Rails' image_tag with themes and plugins support.
1217 # Overrides Rails' image_tag with themes and plugins support.
1217 # Examples:
1218 # Examples:
1218 # image_tag('image.png') # => picks image.png from the current theme or defaults
1219 # image_tag('image.png') # => picks image.png from the current theme or defaults
1219 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1220 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1220 #
1221 #
1221 def image_tag(source, options={})
1222 def image_tag(source, options={})
1222 if plugin = options.delete(:plugin)
1223 if plugin = options.delete(:plugin)
1223 source = "/plugin_assets/#{plugin}/images/#{source}"
1224 source = "/plugin_assets/#{plugin}/images/#{source}"
1224 elsif current_theme && current_theme.images.include?(source)
1225 elsif current_theme && current_theme.images.include?(source)
1225 source = current_theme.image_path(source)
1226 source = current_theme.image_path(source)
1226 end
1227 end
1227 super source, options
1228 super source, options
1228 end
1229 end
1229
1230
1230 # Overrides Rails' javascript_include_tag with plugins support
1231 # Overrides Rails' javascript_include_tag with plugins support
1231 # Examples:
1232 # Examples:
1232 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1233 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1233 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1234 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1234 #
1235 #
1235 def javascript_include_tag(*sources)
1236 def javascript_include_tag(*sources)
1236 options = sources.last.is_a?(Hash) ? sources.pop : {}
1237 options = sources.last.is_a?(Hash) ? sources.pop : {}
1237 if plugin = options.delete(:plugin)
1238 if plugin = options.delete(:plugin)
1238 sources = sources.map do |source|
1239 sources = sources.map do |source|
1239 if plugin
1240 if plugin
1240 "/plugin_assets/#{plugin}/javascripts/#{source}"
1241 "/plugin_assets/#{plugin}/javascripts/#{source}"
1241 else
1242 else
1242 source
1243 source
1243 end
1244 end
1244 end
1245 end
1245 end
1246 end
1246 super *sources, options
1247 super *sources, options
1247 end
1248 end
1248
1249
1249 def sidebar_content?
1250 def sidebar_content?
1250 content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1251 content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1251 end
1252 end
1252
1253
1253 def view_layouts_base_sidebar_hook_response
1254 def view_layouts_base_sidebar_hook_response
1254 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1255 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1255 end
1256 end
1256
1257
1257 def email_delivery_enabled?
1258 def email_delivery_enabled?
1258 !!ActionMailer::Base.perform_deliveries
1259 !!ActionMailer::Base.perform_deliveries
1259 end
1260 end
1260
1261
1261 # Returns the avatar image tag for the given +user+ if avatars are enabled
1262 # Returns the avatar image tag for the given +user+ if avatars are enabled
1262 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1263 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1263 def avatar(user, options = { })
1264 def avatar(user, options = { })
1264 if Setting.gravatar_enabled?
1265 if Setting.gravatar_enabled?
1265 options.merge!(:default => Setting.gravatar_default)
1266 options.merge!(:default => Setting.gravatar_default)
1266 email = nil
1267 email = nil
1267 if user.respond_to?(:mail)
1268 if user.respond_to?(:mail)
1268 email = user.mail
1269 email = user.mail
1269 elsif user.to_s =~ %r{<(.+?)>}
1270 elsif user.to_s =~ %r{<(.+?)>}
1270 email = $1
1271 email = $1
1271 end
1272 end
1272 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1273 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1273 else
1274 else
1274 ''
1275 ''
1275 end
1276 end
1276 end
1277 end
1277
1278
1278 # Returns a link to edit user's avatar if avatars are enabled
1279 # Returns a link to edit user's avatar if avatars are enabled
1279 def avatar_edit_link(user, options={})
1280 def avatar_edit_link(user, options={})
1280 if Setting.gravatar_enabled?
1281 if Setting.gravatar_enabled?
1281 url = "https://gravatar.com"
1282 url = "https://gravatar.com"
1282 link_to avatar(user, {:title => l(:button_edit)}.merge(options)), url, :target => '_blank'
1283 link_to avatar(user, {:title => l(:button_edit)}.merge(options)), url, :target => '_blank'
1283 end
1284 end
1284 end
1285 end
1285
1286
1286 def sanitize_anchor_name(anchor)
1287 def sanitize_anchor_name(anchor)
1287 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1288 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1288 end
1289 end
1289
1290
1290 # Returns the javascript tags that are included in the html layout head
1291 # Returns the javascript tags that are included in the html layout head
1291 def javascript_heads
1292 def javascript_heads
1292 tags = javascript_include_tag('jquery-1.11.1-ui-1.11.0-ujs-3.1.4', 'application', 'responsive')
1293 tags = javascript_include_tag('jquery-1.11.1-ui-1.11.0-ujs-3.1.4', 'application', 'responsive')
1293 unless User.current.pref.warn_on_leaving_unsaved == '0'
1294 unless User.current.pref.warn_on_leaving_unsaved == '0'
1294 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1295 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1295 end
1296 end
1296 tags
1297 tags
1297 end
1298 end
1298
1299
1299 def favicon
1300 def favicon
1300 "<link rel='shortcut icon' href='#{favicon_path}' />".html_safe
1301 "<link rel='shortcut icon' href='#{favicon_path}' />".html_safe
1301 end
1302 end
1302
1303
1303 # Returns the path to the favicon
1304 # Returns the path to the favicon
1304 def favicon_path
1305 def favicon_path
1305 icon = (current_theme && current_theme.favicon?) ? current_theme.favicon_path : '/favicon.ico'
1306 icon = (current_theme && current_theme.favicon?) ? current_theme.favicon_path : '/favicon.ico'
1306 image_path(icon)
1307 image_path(icon)
1307 end
1308 end
1308
1309
1309 # Returns the full URL to the favicon
1310 # Returns the full URL to the favicon
1310 def favicon_url
1311 def favicon_url
1311 # TODO: use #image_url introduced in Rails4
1312 # TODO: use #image_url introduced in Rails4
1312 path = favicon_path
1313 path = favicon_path
1313 base = url_for(:controller => 'welcome', :action => 'index', :only_path => false)
1314 base = url_for(:controller => 'welcome', :action => 'index', :only_path => false)
1314 base.sub(%r{/+$},'') + '/' + path.sub(%r{^/+},'')
1315 base.sub(%r{/+$},'') + '/' + path.sub(%r{^/+},'')
1315 end
1316 end
1316
1317
1317 def robot_exclusion_tag
1318 def robot_exclusion_tag
1318 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1319 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1319 end
1320 end
1320
1321
1321 # Returns true if arg is expected in the API response
1322 # Returns true if arg is expected in the API response
1322 def include_in_api_response?(arg)
1323 def include_in_api_response?(arg)
1323 unless @included_in_api_response
1324 unless @included_in_api_response
1324 param = params[:include]
1325 param = params[:include]
1325 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1326 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1326 @included_in_api_response.collect!(&:strip)
1327 @included_in_api_response.collect!(&:strip)
1327 end
1328 end
1328 @included_in_api_response.include?(arg.to_s)
1329 @included_in_api_response.include?(arg.to_s)
1329 end
1330 end
1330
1331
1331 # Returns options or nil if nometa param or X-Redmine-Nometa header
1332 # Returns options or nil if nometa param or X-Redmine-Nometa header
1332 # was set in the request
1333 # was set in the request
1333 def api_meta(options)
1334 def api_meta(options)
1334 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1335 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1335 # compatibility mode for activeresource clients that raise
1336 # compatibility mode for activeresource clients that raise
1336 # an error when deserializing an array with attributes
1337 # an error when deserializing an array with attributes
1337 nil
1338 nil
1338 else
1339 else
1339 options
1340 options
1340 end
1341 end
1341 end
1342 end
1342
1343
1343 def generate_csv(&block)
1344 def generate_csv(&block)
1344 decimal_separator = l(:general_csv_decimal_separator)
1345 decimal_separator = l(:general_csv_decimal_separator)
1345 encoding = l(:general_csv_encoding)
1346 encoding = l(:general_csv_encoding)
1346 end
1347 end
1347
1348
1348 private
1349 private
1349
1350
1350 def wiki_helper
1351 def wiki_helper
1351 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1352 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1352 extend helper
1353 extend helper
1353 return self
1354 return self
1354 end
1355 end
1355
1356
1356 def link_to_content_update(text, url_params = {}, html_options = {})
1357 def link_to_content_update(text, url_params = {}, html_options = {})
1357 link_to(text, url_params, html_options)
1358 link_to(text, url_params, html_options)
1358 end
1359 end
1359 end
1360 end
General Comments 0
You need to be logged in to leave comments. Login now