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