##// END OF EJS Templates
Moved #context_menu_link to a new helper....
Jean-Philippe Lang -
r8703:de7c49c6ca79
parent child
Show More
@@ -0,0 +1,36
1 # encoding: utf-8
2 #
3 # Redmine - project management software
4 # Copyright (C) 2006-2012 Jean-Philippe Lang
5 #
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
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
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
20 module ContextMenusHelper
21 def context_menu_link(name, url, options={})
22 options[:class] ||= ''
23 if options.delete(:selected)
24 options[:class] << ' icon-checked disabled'
25 options[:disabled] = true
26 end
27 if options.delete(:disabled)
28 options.delete(:method)
29 options.delete(:confirm)
30 options.delete(:onclick)
31 options[:class] << ' disabled'
32 url = '#'
33 end
34 link_to h(name), url, options
35 end
36 end
@@ -1,1112 +1,1096
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
4 # Copyright (C) 2006-2011 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
27
28 extend Forwardable
28 extend Forwardable
29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
30
30
31 # Return true if user is authorized for controller/action, otherwise false
31 # Return true if user is authorized for controller/action, otherwise false
32 def authorize_for(controller, action)
32 def authorize_for(controller, action)
33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
34 end
34 end
35
35
36 # Display a link if user is authorized
36 # Display a link if user is authorized
37 #
37 #
38 # @param [String] name Anchor text (passed to link_to)
38 # @param [String] name Anchor text (passed to link_to)
39 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
39 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
40 # @param [optional, Hash] html_options Options passed to link_to
40 # @param [optional, Hash] html_options Options passed to link_to
41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
44 end
44 end
45
45
46 # Display a link to remote if user is authorized
46 # Display a link to remote if user is authorized
47 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
47 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
48 url = options[:url] || {}
48 url = options[:url] || {}
49 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
49 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
50 end
50 end
51
51
52 # Displays a link to user's account page if active
52 # Displays a link to user's account page if active
53 def link_to_user(user, options={})
53 def link_to_user(user, options={})
54 if user.is_a?(User)
54 if user.is_a?(User)
55 name = h(user.name(options[:format]))
55 name = h(user.name(options[:format]))
56 if user.active?
56 if user.active?
57 link_to name, :controller => 'users', :action => 'show', :id => user
57 link_to name, :controller => 'users', :action => 'show', :id => user
58 else
58 else
59 name
59 name
60 end
60 end
61 else
61 else
62 h(user.to_s)
62 h(user.to_s)
63 end
63 end
64 end
64 end
65
65
66 # Displays a link to +issue+ with its subject.
66 # Displays a link to +issue+ with its subject.
67 # Examples:
67 # Examples:
68 #
68 #
69 # link_to_issue(issue) # => Defect #6: This is the subject
69 # link_to_issue(issue) # => Defect #6: This is the subject
70 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
70 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
71 # link_to_issue(issue, :subject => false) # => Defect #6
71 # link_to_issue(issue, :subject => false) # => Defect #6
72 # link_to_issue(issue, :project => true) # => Foo - Defect #6
72 # link_to_issue(issue, :project => true) # => Foo - Defect #6
73 #
73 #
74 def link_to_issue(issue, options={})
74 def link_to_issue(issue, options={})
75 title = nil
75 title = nil
76 subject = nil
76 subject = nil
77 if options[:subject] == false
77 if options[:subject] == false
78 title = truncate(issue.subject, :length => 60)
78 title = truncate(issue.subject, :length => 60)
79 else
79 else
80 subject = issue.subject
80 subject = issue.subject
81 if options[:truncate]
81 if options[:truncate]
82 subject = truncate(subject, :length => options[:truncate])
82 subject = truncate(subject, :length => options[:truncate])
83 end
83 end
84 end
84 end
85 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
85 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
86 :class => issue.css_classes,
86 :class => issue.css_classes,
87 :title => title
87 :title => title
88 s << h(": #{subject}") if subject
88 s << h(": #{subject}") if subject
89 s = h("#{issue.project} - ") + s if options[:project]
89 s = h("#{issue.project} - ") + s if options[:project]
90 s
90 s
91 end
91 end
92
92
93 # Generates a link to an attachment.
93 # Generates a link to an attachment.
94 # Options:
94 # Options:
95 # * :text - Link text (default to attachment filename)
95 # * :text - Link text (default to attachment filename)
96 # * :download - Force download (default: false)
96 # * :download - Force download (default: false)
97 def link_to_attachment(attachment, options={})
97 def link_to_attachment(attachment, options={})
98 text = options.delete(:text) || attachment.filename
98 text = options.delete(:text) || attachment.filename
99 action = options.delete(:download) ? 'download' : 'show'
99 action = options.delete(:download) ? 'download' : 'show'
100 link_to(h(text),
100 link_to(h(text),
101 {:controller => 'attachments', :action => action,
101 {:controller => 'attachments', :action => action,
102 :id => attachment, :filename => attachment.filename },
102 :id => attachment, :filename => attachment.filename },
103 options)
103 options)
104 end
104 end
105
105
106 # Generates a link to a SCM revision
106 # Generates a link to a SCM revision
107 # Options:
107 # Options:
108 # * :text - Link text (default to the formatted revision)
108 # * :text - Link text (default to the formatted revision)
109 def link_to_revision(revision, repository, options={})
109 def link_to_revision(revision, repository, options={})
110 if repository.is_a?(Project)
110 if repository.is_a?(Project)
111 repository = repository.repository
111 repository = repository.repository
112 end
112 end
113 text = options.delete(:text) || format_revision(revision)
113 text = options.delete(:text) || format_revision(revision)
114 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
114 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
115 link_to(
115 link_to(
116 h(text),
116 h(text),
117 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
117 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
118 :title => l(:label_revision_id, format_revision(revision))
118 :title => l(:label_revision_id, format_revision(revision))
119 )
119 )
120 end
120 end
121
121
122 # Generates a link to a message
122 # Generates a link to a message
123 def link_to_message(message, options={}, html_options = nil)
123 def link_to_message(message, options={}, html_options = nil)
124 link_to(
124 link_to(
125 h(truncate(message.subject, :length => 60)),
125 h(truncate(message.subject, :length => 60)),
126 { :controller => 'messages', :action => 'show',
126 { :controller => 'messages', :action => 'show',
127 :board_id => message.board_id,
127 :board_id => message.board_id,
128 :id => message.root,
128 :id => message.root,
129 :r => (message.parent_id && message.id),
129 :r => (message.parent_id && message.id),
130 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
130 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
131 }.merge(options),
131 }.merge(options),
132 html_options
132 html_options
133 )
133 )
134 end
134 end
135
135
136 # Generates a link to a project if active
136 # Generates a link to a project if active
137 # Examples:
137 # Examples:
138 #
138 #
139 # link_to_project(project) # => link to the specified project overview
139 # link_to_project(project) # => link to the specified project overview
140 # link_to_project(project, :action=>'settings') # => link to project settings
140 # link_to_project(project, :action=>'settings') # => link to project settings
141 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
141 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
142 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
142 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
143 #
143 #
144 def link_to_project(project, options={}, html_options = nil)
144 def link_to_project(project, options={}, html_options = nil)
145 if project.active?
145 if project.active?
146 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
146 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
147 link_to(h(project), url, html_options)
147 link_to(h(project), url, html_options)
148 else
148 else
149 h(project)
149 h(project)
150 end
150 end
151 end
151 end
152
152
153 def toggle_link(name, id, options={})
153 def toggle_link(name, id, options={})
154 onclick = "Element.toggle('#{id}'); "
154 onclick = "Element.toggle('#{id}'); "
155 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
155 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
156 onclick << "return false;"
156 onclick << "return false;"
157 link_to(name, "#", :onclick => onclick)
157 link_to(name, "#", :onclick => onclick)
158 end
158 end
159
159
160 def image_to_function(name, function, html_options = {})
160 def image_to_function(name, function, html_options = {})
161 html_options.symbolize_keys!
161 html_options.symbolize_keys!
162 tag(:input, html_options.merge({
162 tag(:input, html_options.merge({
163 :type => "image", :src => image_path(name),
163 :type => "image", :src => image_path(name),
164 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
164 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
165 }))
165 }))
166 end
166 end
167
167
168 def prompt_to_remote(name, text, param, url, html_options = {})
168 def prompt_to_remote(name, text, param, url, html_options = {})
169 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
169 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
170 link_to name, {}, html_options
170 link_to name, {}, html_options
171 end
171 end
172
172
173 def format_activity_title(text)
173 def format_activity_title(text)
174 h(truncate_single_line(text, :length => 100))
174 h(truncate_single_line(text, :length => 100))
175 end
175 end
176
176
177 def format_activity_day(date)
177 def format_activity_day(date)
178 date == Date.today ? l(:label_today).titleize : format_date(date)
178 date == Date.today ? l(:label_today).titleize : format_date(date)
179 end
179 end
180
180
181 def format_activity_description(text)
181 def format_activity_description(text)
182 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
182 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
183 ).gsub(/[\r\n]+/, "<br />").html_safe
183 ).gsub(/[\r\n]+/, "<br />").html_safe
184 end
184 end
185
185
186 def format_version_name(version)
186 def format_version_name(version)
187 if version.project == @project
187 if version.project == @project
188 h(version)
188 h(version)
189 else
189 else
190 h("#{version.project} - #{version}")
190 h("#{version.project} - #{version}")
191 end
191 end
192 end
192 end
193
193
194 def due_date_distance_in_words(date)
194 def due_date_distance_in_words(date)
195 if date
195 if date
196 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
196 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
197 end
197 end
198 end
198 end
199
199
200 def render_page_hierarchy(pages, node=nil, options={})
200 def render_page_hierarchy(pages, node=nil, options={})
201 content = ''
201 content = ''
202 if pages[node]
202 if pages[node]
203 content << "<ul class=\"pages-hierarchy\">\n"
203 content << "<ul class=\"pages-hierarchy\">\n"
204 pages[node].each do |page|
204 pages[node].each do |page|
205 content << "<li>"
205 content << "<li>"
206 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
206 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
207 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
207 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
208 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
208 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
209 content << "</li>\n"
209 content << "</li>\n"
210 end
210 end
211 content << "</ul>\n"
211 content << "</ul>\n"
212 end
212 end
213 content.html_safe
213 content.html_safe
214 end
214 end
215
215
216 # Renders flash messages
216 # Renders flash messages
217 def render_flash_messages
217 def render_flash_messages
218 s = ''
218 s = ''
219 flash.each do |k,v|
219 flash.each do |k,v|
220 s << (content_tag('div', v.html_safe, :class => "flash #{k}"))
220 s << (content_tag('div', v.html_safe, :class => "flash #{k}"))
221 end
221 end
222 s.html_safe
222 s.html_safe
223 end
223 end
224
224
225 # Renders tabs and their content
225 # Renders tabs and their content
226 def render_tabs(tabs)
226 def render_tabs(tabs)
227 if tabs.any?
227 if tabs.any?
228 render :partial => 'common/tabs', :locals => {:tabs => tabs}
228 render :partial => 'common/tabs', :locals => {:tabs => tabs}
229 else
229 else
230 content_tag 'p', l(:label_no_data), :class => "nodata"
230 content_tag 'p', l(:label_no_data), :class => "nodata"
231 end
231 end
232 end
232 end
233
233
234 # Renders the project quick-jump box
234 # Renders the project quick-jump box
235 def render_project_jump_box
235 def render_project_jump_box
236 return unless User.current.logged?
236 return unless User.current.logged?
237 projects = User.current.memberships.collect(&:project).compact.uniq
237 projects = User.current.memberships.collect(&:project).compact.uniq
238 if projects.any?
238 if projects.any?
239 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
239 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
240 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
240 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
241 '<option value="" disabled="disabled">---</option>'
241 '<option value="" disabled="disabled">---</option>'
242 s << project_tree_options_for_select(projects, :selected => @project) do |p|
242 s << project_tree_options_for_select(projects, :selected => @project) do |p|
243 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
243 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
244 end
244 end
245 s << '</select>'
245 s << '</select>'
246 s.html_safe
246 s.html_safe
247 end
247 end
248 end
248 end
249
249
250 def project_tree_options_for_select(projects, options = {})
250 def project_tree_options_for_select(projects, options = {})
251 s = ''
251 s = ''
252 project_tree(projects) do |project, level|
252 project_tree(projects) do |project, level|
253 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
253 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
254 tag_options = {:value => project.id}
254 tag_options = {:value => project.id}
255 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
255 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
256 tag_options[:selected] = 'selected'
256 tag_options[:selected] = 'selected'
257 else
257 else
258 tag_options[:selected] = nil
258 tag_options[:selected] = nil
259 end
259 end
260 tag_options.merge!(yield(project)) if block_given?
260 tag_options.merge!(yield(project)) if block_given?
261 s << content_tag('option', name_prefix + h(project), tag_options)
261 s << content_tag('option', name_prefix + h(project), tag_options)
262 end
262 end
263 s.html_safe
263 s.html_safe
264 end
264 end
265
265
266 # Yields the given block for each project with its level in the tree
266 # Yields the given block for each project with its level in the tree
267 #
267 #
268 # Wrapper for Project#project_tree
268 # Wrapper for Project#project_tree
269 def project_tree(projects, &block)
269 def project_tree(projects, &block)
270 Project.project_tree(projects, &block)
270 Project.project_tree(projects, &block)
271 end
271 end
272
272
273 def project_nested_ul(projects, &block)
273 def project_nested_ul(projects, &block)
274 s = ''
274 s = ''
275 if projects.any?
275 if projects.any?
276 ancestors = []
276 ancestors = []
277 projects.sort_by(&:lft).each do |project|
277 projects.sort_by(&:lft).each do |project|
278 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
278 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
279 s << "<ul>\n"
279 s << "<ul>\n"
280 else
280 else
281 ancestors.pop
281 ancestors.pop
282 s << "</li>"
282 s << "</li>"
283 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
283 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
284 ancestors.pop
284 ancestors.pop
285 s << "</ul></li>\n"
285 s << "</ul></li>\n"
286 end
286 end
287 end
287 end
288 s << "<li>"
288 s << "<li>"
289 s << yield(project).to_s
289 s << yield(project).to_s
290 ancestors << project
290 ancestors << project
291 end
291 end
292 s << ("</li></ul>\n" * ancestors.size)
292 s << ("</li></ul>\n" * ancestors.size)
293 end
293 end
294 s.html_safe
294 s.html_safe
295 end
295 end
296
296
297 def principals_check_box_tags(name, principals)
297 def principals_check_box_tags(name, principals)
298 s = ''
298 s = ''
299 principals.sort.each do |principal|
299 principals.sort.each do |principal|
300 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
300 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
301 end
301 end
302 s.html_safe
302 s.html_safe
303 end
303 end
304
304
305 # Returns a string for users/groups option tags
305 # Returns a string for users/groups option tags
306 def principals_options_for_select(collection, selected=nil)
306 def principals_options_for_select(collection, selected=nil)
307 s = ''
307 s = ''
308 if collection.include?(User.current)
308 if collection.include?(User.current)
309 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
309 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
310 end
310 end
311 groups = ''
311 groups = ''
312 collection.sort.each do |element|
312 collection.sort.each do |element|
313 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
313 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
314 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
314 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
315 end
315 end
316 unless groups.empty?
316 unless groups.empty?
317 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
317 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
318 end
318 end
319 s
319 s
320 end
320 end
321
321
322 # Truncates and returns the string as a single line
322 # Truncates and returns the string as a single line
323 def truncate_single_line(string, *args)
323 def truncate_single_line(string, *args)
324 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
324 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
325 end
325 end
326
326
327 # Truncates at line break after 250 characters or options[:length]
327 # Truncates at line break after 250 characters or options[:length]
328 def truncate_lines(string, options={})
328 def truncate_lines(string, options={})
329 length = options[:length] || 250
329 length = options[:length] || 250
330 if string.to_s =~ /\A(.{#{length}}.*?)$/m
330 if string.to_s =~ /\A(.{#{length}}.*?)$/m
331 "#{$1}..."
331 "#{$1}..."
332 else
332 else
333 string
333 string
334 end
334 end
335 end
335 end
336
336
337 def anchor(text)
337 def anchor(text)
338 text.to_s.gsub(' ', '_')
338 text.to_s.gsub(' ', '_')
339 end
339 end
340
340
341 def html_hours(text)
341 def html_hours(text)
342 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
342 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
343 end
343 end
344
344
345 def authoring(created, author, options={})
345 def authoring(created, author, options={})
346 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
346 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
347 end
347 end
348
348
349 def time_tag(time)
349 def time_tag(time)
350 text = distance_of_time_in_words(Time.now, time)
350 text = distance_of_time_in_words(Time.now, time)
351 if @project
351 if @project
352 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
352 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
353 else
353 else
354 content_tag('acronym', text, :title => format_time(time))
354 content_tag('acronym', text, :title => format_time(time))
355 end
355 end
356 end
356 end
357
357
358 def syntax_highlight(name, content)
358 def syntax_highlight(name, content)
359 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
359 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
360 end
360 end
361
361
362 def to_path_param(path)
362 def to_path_param(path)
363 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
363 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
364 end
364 end
365
365
366 def pagination_links_full(paginator, count=nil, options={})
366 def pagination_links_full(paginator, count=nil, options={})
367 page_param = options.delete(:page_param) || :page
367 page_param = options.delete(:page_param) || :page
368 per_page_links = options.delete(:per_page_links)
368 per_page_links = options.delete(:per_page_links)
369 url_param = params.dup
369 url_param = params.dup
370
370
371 html = ''
371 html = ''
372 if paginator.current.previous
372 if paginator.current.previous
373 # \xc2\xab(utf-8) = &#171;
373 # \xc2\xab(utf-8) = &#171;
374 html << link_to_content_update(
374 html << link_to_content_update(
375 "\xc2\xab " + l(:label_previous),
375 "\xc2\xab " + l(:label_previous),
376 url_param.merge(page_param => paginator.current.previous)) + ' '
376 url_param.merge(page_param => paginator.current.previous)) + ' '
377 end
377 end
378
378
379 html << (pagination_links_each(paginator, options) do |n|
379 html << (pagination_links_each(paginator, options) do |n|
380 link_to_content_update(n.to_s, url_param.merge(page_param => n))
380 link_to_content_update(n.to_s, url_param.merge(page_param => n))
381 end || '')
381 end || '')
382
382
383 if paginator.current.next
383 if paginator.current.next
384 # \xc2\xbb(utf-8) = &#187;
384 # \xc2\xbb(utf-8) = &#187;
385 html << ' ' + link_to_content_update(
385 html << ' ' + link_to_content_update(
386 (l(:label_next) + " \xc2\xbb"),
386 (l(:label_next) + " \xc2\xbb"),
387 url_param.merge(page_param => paginator.current.next))
387 url_param.merge(page_param => paginator.current.next))
388 end
388 end
389
389
390 unless count.nil?
390 unless count.nil?
391 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
391 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
392 if per_page_links != false && links = per_page_links(paginator.items_per_page)
392 if per_page_links != false && links = per_page_links(paginator.items_per_page)
393 html << " | #{links}"
393 html << " | #{links}"
394 end
394 end
395 end
395 end
396
396
397 html.html_safe
397 html.html_safe
398 end
398 end
399
399
400 def per_page_links(selected=nil)
400 def per_page_links(selected=nil)
401 links = Setting.per_page_options_array.collect do |n|
401 links = Setting.per_page_options_array.collect do |n|
402 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
402 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
403 end
403 end
404 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
404 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
405 end
405 end
406
406
407 def reorder_links(name, url, method = :post)
407 def reorder_links(name, url, method = :post)
408 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
408 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
409 url.merge({"#{name}[move_to]" => 'highest'}),
409 url.merge({"#{name}[move_to]" => 'highest'}),
410 :method => method, :title => l(:label_sort_highest)) +
410 :method => method, :title => l(:label_sort_highest)) +
411 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
411 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
412 url.merge({"#{name}[move_to]" => 'higher'}),
412 url.merge({"#{name}[move_to]" => 'higher'}),
413 :method => method, :title => l(:label_sort_higher)) +
413 :method => method, :title => l(:label_sort_higher)) +
414 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
414 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
415 url.merge({"#{name}[move_to]" => 'lower'}),
415 url.merge({"#{name}[move_to]" => 'lower'}),
416 :method => method, :title => l(:label_sort_lower)) +
416 :method => method, :title => l(:label_sort_lower)) +
417 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
417 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
418 url.merge({"#{name}[move_to]" => 'lowest'}),
418 url.merge({"#{name}[move_to]" => 'lowest'}),
419 :method => method, :title => l(:label_sort_lowest))
419 :method => method, :title => l(:label_sort_lowest))
420 end
420 end
421
421
422 def breadcrumb(*args)
422 def breadcrumb(*args)
423 elements = args.flatten
423 elements = args.flatten
424 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
424 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
425 end
425 end
426
426
427 def other_formats_links(&block)
427 def other_formats_links(&block)
428 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
428 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
429 yield Redmine::Views::OtherFormatsBuilder.new(self)
429 yield Redmine::Views::OtherFormatsBuilder.new(self)
430 concat('</p>'.html_safe)
430 concat('</p>'.html_safe)
431 end
431 end
432
432
433 def page_header_title
433 def page_header_title
434 if @project.nil? || @project.new_record?
434 if @project.nil? || @project.new_record?
435 h(Setting.app_title)
435 h(Setting.app_title)
436 else
436 else
437 b = []
437 b = []
438 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
438 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
439 if ancestors.any?
439 if ancestors.any?
440 root = ancestors.shift
440 root = ancestors.shift
441 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
441 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
442 if ancestors.size > 2
442 if ancestors.size > 2
443 b << "\xe2\x80\xa6"
443 b << "\xe2\x80\xa6"
444 ancestors = ancestors[-2, 2]
444 ancestors = ancestors[-2, 2]
445 end
445 end
446 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
446 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
447 end
447 end
448 b << h(@project)
448 b << h(@project)
449 b.join(" \xc2\xbb ").html_safe
449 b.join(" \xc2\xbb ").html_safe
450 end
450 end
451 end
451 end
452
452
453 def html_title(*args)
453 def html_title(*args)
454 if args.empty?
454 if args.empty?
455 title = @html_title || []
455 title = @html_title || []
456 title << @project.name if @project
456 title << @project.name if @project
457 title << Setting.app_title unless Setting.app_title == title.last
457 title << Setting.app_title unless Setting.app_title == title.last
458 title.select {|t| !t.blank? }.join(' - ')
458 title.select {|t| !t.blank? }.join(' - ')
459 else
459 else
460 @html_title ||= []
460 @html_title ||= []
461 @html_title += args
461 @html_title += args
462 end
462 end
463 end
463 end
464
464
465 # Returns the theme, controller name, and action as css classes for the
465 # Returns the theme, controller name, and action as css classes for the
466 # HTML body.
466 # HTML body.
467 def body_css_classes
467 def body_css_classes
468 css = []
468 css = []
469 if theme = Redmine::Themes.theme(Setting.ui_theme)
469 if theme = Redmine::Themes.theme(Setting.ui_theme)
470 css << 'theme-' + theme.name
470 css << 'theme-' + theme.name
471 end
471 end
472
472
473 css << 'controller-' + params[:controller]
473 css << 'controller-' + params[:controller]
474 css << 'action-' + params[:action]
474 css << 'action-' + params[:action]
475 css.join(' ')
475 css.join(' ')
476 end
476 end
477
477
478 def accesskey(s)
478 def accesskey(s)
479 Redmine::AccessKeys.key_for s
479 Redmine::AccessKeys.key_for s
480 end
480 end
481
481
482 # Formats text according to system settings.
482 # Formats text according to system settings.
483 # 2 ways to call this method:
483 # 2 ways to call this method:
484 # * with a String: textilizable(text, options)
484 # * with a String: textilizable(text, options)
485 # * with an object and one of its attribute: textilizable(issue, :description, options)
485 # * with an object and one of its attribute: textilizable(issue, :description, options)
486 def textilizable(*args)
486 def textilizable(*args)
487 options = args.last.is_a?(Hash) ? args.pop : {}
487 options = args.last.is_a?(Hash) ? args.pop : {}
488 case args.size
488 case args.size
489 when 1
489 when 1
490 obj = options[:object]
490 obj = options[:object]
491 text = args.shift
491 text = args.shift
492 when 2
492 when 2
493 obj = args.shift
493 obj = args.shift
494 attr = args.shift
494 attr = args.shift
495 text = obj.send(attr).to_s
495 text = obj.send(attr).to_s
496 else
496 else
497 raise ArgumentError, 'invalid arguments to textilizable'
497 raise ArgumentError, 'invalid arguments to textilizable'
498 end
498 end
499 return '' if text.blank?
499 return '' if text.blank?
500 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
500 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
501 only_path = options.delete(:only_path) == false ? false : true
501 only_path = options.delete(:only_path) == false ? false : true
502
502
503 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
503 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
504
504
505 @parsed_headings = []
505 @parsed_headings = []
506 @current_section = 0 if options[:edit_section_links]
506 @current_section = 0 if options[:edit_section_links]
507 text = parse_non_pre_blocks(text) do |text|
507 text = parse_non_pre_blocks(text) do |text|
508 [:parse_sections, :parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros, :parse_headings].each do |method_name|
508 [:parse_sections, :parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros, :parse_headings].each do |method_name|
509 send method_name, text, project, obj, attr, only_path, options
509 send method_name, text, project, obj, attr, only_path, options
510 end
510 end
511 end
511 end
512
512
513 if @parsed_headings.any?
513 if @parsed_headings.any?
514 replace_toc(text, @parsed_headings)
514 replace_toc(text, @parsed_headings)
515 end
515 end
516
516
517 text.html_safe
517 text.html_safe
518 end
518 end
519
519
520 def parse_non_pre_blocks(text)
520 def parse_non_pre_blocks(text)
521 s = StringScanner.new(text)
521 s = StringScanner.new(text)
522 tags = []
522 tags = []
523 parsed = ''
523 parsed = ''
524 while !s.eos?
524 while !s.eos?
525 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
525 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
526 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
526 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
527 if tags.empty?
527 if tags.empty?
528 yield text
528 yield text
529 end
529 end
530 parsed << text
530 parsed << text
531 if tag
531 if tag
532 if closing
532 if closing
533 if tags.last == tag.downcase
533 if tags.last == tag.downcase
534 tags.pop
534 tags.pop
535 end
535 end
536 else
536 else
537 tags << tag.downcase
537 tags << tag.downcase
538 end
538 end
539 parsed << full_tag
539 parsed << full_tag
540 end
540 end
541 end
541 end
542 # Close any non closing tags
542 # Close any non closing tags
543 while tag = tags.pop
543 while tag = tags.pop
544 parsed << "</#{tag}>"
544 parsed << "</#{tag}>"
545 end
545 end
546 parsed.html_safe
546 parsed.html_safe
547 end
547 end
548
548
549 def parse_inline_attachments(text, project, obj, attr, only_path, options)
549 def parse_inline_attachments(text, project, obj, attr, only_path, options)
550 # when using an image link, try to use an attachment, if possible
550 # when using an image link, try to use an attachment, if possible
551 if options[:attachments] || (obj && obj.respond_to?(:attachments))
551 if options[:attachments] || (obj && obj.respond_to?(:attachments))
552 attachments = options[:attachments] || obj.attachments
552 attachments = options[:attachments] || obj.attachments
553 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
553 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
554 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
554 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
555 # search for the picture in attachments
555 # search for the picture in attachments
556 if found = Attachment.latest_attach(attachments, filename)
556 if found = Attachment.latest_attach(attachments, filename)
557 image_url = url_for :only_path => only_path, :controller => 'attachments',
557 image_url = url_for :only_path => only_path, :controller => 'attachments',
558 :action => 'download', :id => found
558 :action => 'download', :id => found
559 desc = found.description.to_s.gsub('"', '')
559 desc = found.description.to_s.gsub('"', '')
560 if !desc.blank? && alttext.blank?
560 if !desc.blank? && alttext.blank?
561 alt = " title=\"#{desc}\" alt=\"#{desc}\""
561 alt = " title=\"#{desc}\" alt=\"#{desc}\""
562 end
562 end
563 "src=\"#{image_url}\"#{alt}".html_safe
563 "src=\"#{image_url}\"#{alt}".html_safe
564 else
564 else
565 m.html_safe
565 m.html_safe
566 end
566 end
567 end
567 end
568 end
568 end
569 end
569 end
570
570
571 # Wiki links
571 # Wiki links
572 #
572 #
573 # Examples:
573 # Examples:
574 # [[mypage]]
574 # [[mypage]]
575 # [[mypage|mytext]]
575 # [[mypage|mytext]]
576 # wiki links can refer other project wikis, using project name or identifier:
576 # wiki links can refer other project wikis, using project name or identifier:
577 # [[project:]] -> wiki starting page
577 # [[project:]] -> wiki starting page
578 # [[project:|mytext]]
578 # [[project:|mytext]]
579 # [[project:mypage]]
579 # [[project:mypage]]
580 # [[project:mypage|mytext]]
580 # [[project:mypage|mytext]]
581 def parse_wiki_links(text, project, obj, attr, only_path, options)
581 def parse_wiki_links(text, project, obj, attr, only_path, options)
582 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
582 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
583 link_project = project
583 link_project = project
584 esc, all, page, title = $1, $2, $3, $5
584 esc, all, page, title = $1, $2, $3, $5
585 if esc.nil?
585 if esc.nil?
586 if page =~ /^([^\:]+)\:(.*)$/
586 if page =~ /^([^\:]+)\:(.*)$/
587 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
587 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
588 page = $2
588 page = $2
589 title ||= $1 if page.blank?
589 title ||= $1 if page.blank?
590 end
590 end
591
591
592 if link_project && link_project.wiki
592 if link_project && link_project.wiki
593 # extract anchor
593 # extract anchor
594 anchor = nil
594 anchor = nil
595 if page =~ /^(.+?)\#(.+)$/
595 if page =~ /^(.+?)\#(.+)$/
596 page, anchor = $1, $2
596 page, anchor = $1, $2
597 end
597 end
598 anchor = sanitize_anchor_name(anchor) if anchor.present?
598 anchor = sanitize_anchor_name(anchor) if anchor.present?
599 # check if page exists
599 # check if page exists
600 wiki_page = link_project.wiki.find_page(page)
600 wiki_page = link_project.wiki.find_page(page)
601 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
601 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
602 "##{anchor}"
602 "##{anchor}"
603 else
603 else
604 case options[:wiki_links]
604 case options[:wiki_links]
605 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
605 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
606 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
606 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
607 else
607 else
608 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
608 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
609 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
609 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
610 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
610 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
611 :id => wiki_page_id, :anchor => anchor, :parent => parent)
611 :id => wiki_page_id, :anchor => anchor, :parent => parent)
612 end
612 end
613 end
613 end
614 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
614 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
615 else
615 else
616 # project or wiki doesn't exist
616 # project or wiki doesn't exist
617 all.html_safe
617 all.html_safe
618 end
618 end
619 else
619 else
620 all.html_safe
620 all.html_safe
621 end
621 end
622 end
622 end
623 end
623 end
624
624
625 # Redmine links
625 # Redmine links
626 #
626 #
627 # Examples:
627 # Examples:
628 # Issues:
628 # Issues:
629 # #52 -> Link to issue #52
629 # #52 -> Link to issue #52
630 # Changesets:
630 # Changesets:
631 # r52 -> Link to revision 52
631 # r52 -> Link to revision 52
632 # commit:a85130f -> Link to scmid starting with a85130f
632 # commit:a85130f -> Link to scmid starting with a85130f
633 # Documents:
633 # Documents:
634 # document#17 -> Link to document with id 17
634 # document#17 -> Link to document with id 17
635 # document:Greetings -> Link to the document with title "Greetings"
635 # document:Greetings -> Link to the document with title "Greetings"
636 # document:"Some document" -> Link to the document with title "Some document"
636 # document:"Some document" -> Link to the document with title "Some document"
637 # Versions:
637 # Versions:
638 # version#3 -> Link to version with id 3
638 # version#3 -> Link to version with id 3
639 # version:1.0.0 -> Link to version named "1.0.0"
639 # version:1.0.0 -> Link to version named "1.0.0"
640 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
640 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
641 # Attachments:
641 # Attachments:
642 # attachment:file.zip -> Link to the attachment of the current object named file.zip
642 # attachment:file.zip -> Link to the attachment of the current object named file.zip
643 # Source files:
643 # Source files:
644 # source:some/file -> Link to the file located at /some/file in the project's repository
644 # source:some/file -> Link to the file located at /some/file in the project's repository
645 # source:some/file@52 -> Link to the file's revision 52
645 # source:some/file@52 -> Link to the file's revision 52
646 # source:some/file#L120 -> Link to line 120 of the file
646 # source:some/file#L120 -> Link to line 120 of the file
647 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
647 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
648 # export:some/file -> Force the download of the file
648 # export:some/file -> Force the download of the file
649 # Forum messages:
649 # Forum messages:
650 # message#1218 -> Link to message with id 1218
650 # message#1218 -> Link to message with id 1218
651 #
651 #
652 # Links can refer other objects from other projects, using project identifier:
652 # Links can refer other objects from other projects, using project identifier:
653 # identifier:r52
653 # identifier:r52
654 # identifier:document:"Some document"
654 # identifier:document:"Some document"
655 # identifier:version:1.0.0
655 # identifier:version:1.0.0
656 # identifier:source:some/file
656 # identifier:source:some/file
657 def parse_redmine_links(text, project, obj, attr, only_path, options)
657 def parse_redmine_links(text, project, obj, attr, only_path, options)
658 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-]+)\|)?(r)))(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
658 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-]+)\|)?(r)))(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
659 leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $14, $13 || $15
659 leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $14, $13 || $15
660 link = nil
660 link = nil
661 if project_identifier
661 if project_identifier
662 project = Project.visible.find_by_identifier(project_identifier)
662 project = Project.visible.find_by_identifier(project_identifier)
663 end
663 end
664 if esc.nil?
664 if esc.nil?
665 if prefix.nil? && sep == 'r'
665 if prefix.nil? && sep == 'r'
666 if project
666 if project
667 repository = nil
667 repository = nil
668 if repo_identifier
668 if repo_identifier
669 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
669 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
670 else
670 else
671 repository = project.repository
671 repository = project.repository
672 end
672 end
673 # project.changesets.visible raises an SQL error because of a double join on repositories
673 # project.changesets.visible raises an SQL error because of a double join on repositories
674 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
674 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
675 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision},
675 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision},
676 :class => 'changeset',
676 :class => 'changeset',
677 :title => truncate_single_line(changeset.comments, :length => 100))
677 :title => truncate_single_line(changeset.comments, :length => 100))
678 end
678 end
679 end
679 end
680 elsif sep == '#'
680 elsif sep == '#'
681 oid = identifier.to_i
681 oid = identifier.to_i
682 case prefix
682 case prefix
683 when nil
683 when nil
684 if issue = Issue.visible.find_by_id(oid, :include => :status)
684 if issue = Issue.visible.find_by_id(oid, :include => :status)
685 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
685 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
686 :class => issue.css_classes,
686 :class => issue.css_classes,
687 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
687 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
688 end
688 end
689 when 'document'
689 when 'document'
690 if document = Document.visible.find_by_id(oid)
690 if document = Document.visible.find_by_id(oid)
691 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
691 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
692 :class => 'document'
692 :class => 'document'
693 end
693 end
694 when 'version'
694 when 'version'
695 if version = Version.visible.find_by_id(oid)
695 if version = Version.visible.find_by_id(oid)
696 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
696 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
697 :class => 'version'
697 :class => 'version'
698 end
698 end
699 when 'message'
699 when 'message'
700 if message = Message.visible.find_by_id(oid, :include => :parent)
700 if message = Message.visible.find_by_id(oid, :include => :parent)
701 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
701 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
702 end
702 end
703 when 'forum'
703 when 'forum'
704 if board = Board.visible.find_by_id(oid)
704 if board = Board.visible.find_by_id(oid)
705 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
705 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
706 :class => 'board'
706 :class => 'board'
707 end
707 end
708 when 'news'
708 when 'news'
709 if news = News.visible.find_by_id(oid)
709 if news = News.visible.find_by_id(oid)
710 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
710 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
711 :class => 'news'
711 :class => 'news'
712 end
712 end
713 when 'project'
713 when 'project'
714 if p = Project.visible.find_by_id(oid)
714 if p = Project.visible.find_by_id(oid)
715 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
715 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
716 end
716 end
717 end
717 end
718 elsif sep == ':'
718 elsif sep == ':'
719 # removes the double quotes if any
719 # removes the double quotes if any
720 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
720 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
721 case prefix
721 case prefix
722 when 'document'
722 when 'document'
723 if project && document = project.documents.visible.find_by_title(name)
723 if project && document = project.documents.visible.find_by_title(name)
724 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
724 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
725 :class => 'document'
725 :class => 'document'
726 end
726 end
727 when 'version'
727 when 'version'
728 if project && version = project.versions.visible.find_by_name(name)
728 if project && version = project.versions.visible.find_by_name(name)
729 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
729 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
730 :class => 'version'
730 :class => 'version'
731 end
731 end
732 when 'forum'
732 when 'forum'
733 if project && board = project.boards.visible.find_by_name(name)
733 if project && board = project.boards.visible.find_by_name(name)
734 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
734 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
735 :class => 'board'
735 :class => 'board'
736 end
736 end
737 when 'news'
737 when 'news'
738 if project && news = project.news.visible.find_by_title(name)
738 if project && news = project.news.visible.find_by_title(name)
739 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
739 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
740 :class => 'news'
740 :class => 'news'
741 end
741 end
742 when 'commit', 'source', 'export'
742 when 'commit', 'source', 'export'
743 if project
743 if project
744 repository = nil
744 repository = nil
745 if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
745 if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
746 repo_prefix, repo_identifier, name = $1, $2, $3
746 repo_prefix, repo_identifier, name = $1, $2, $3
747 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
747 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
748 else
748 else
749 repository = project.repository
749 repository = project.repository
750 end
750 end
751 if prefix == 'commit'
751 if prefix == 'commit'
752 if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
752 if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
753 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},
753 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},
754 :class => 'changeset',
754 :class => 'changeset',
755 :title => truncate_single_line(h(changeset.comments), :length => 100)
755 :title => truncate_single_line(h(changeset.comments), :length => 100)
756 end
756 end
757 else
757 else
758 if repository && User.current.allowed_to?(:browse_repository, project)
758 if repository && User.current.allowed_to?(:browse_repository, project)
759 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
759 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
760 path, rev, anchor = $1, $3, $5
760 path, rev, anchor = $1, $3, $5
761 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :repository_id => repository.identifier_param,
761 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :repository_id => repository.identifier_param,
762 :path => to_path_param(path),
762 :path => to_path_param(path),
763 :rev => rev,
763 :rev => rev,
764 :anchor => anchor,
764 :anchor => anchor,
765 :format => (prefix == 'export' ? 'raw' : nil)},
765 :format => (prefix == 'export' ? 'raw' : nil)},
766 :class => (prefix == 'export' ? 'source download' : 'source')
766 :class => (prefix == 'export' ? 'source download' : 'source')
767 end
767 end
768 end
768 end
769 repo_prefix = nil
769 repo_prefix = nil
770 end
770 end
771 when 'attachment'
771 when 'attachment'
772 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
772 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
773 if attachments && attachment = attachments.detect {|a| a.filename == name }
773 if attachments && attachment = attachments.detect {|a| a.filename == name }
774 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
774 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
775 :class => 'attachment'
775 :class => 'attachment'
776 end
776 end
777 when 'project'
777 when 'project'
778 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
778 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
779 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
779 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
780 end
780 end
781 end
781 end
782 end
782 end
783 end
783 end
784 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}")).html_safe
784 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}")).html_safe
785 end
785 end
786 end
786 end
787
787
788 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
788 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
789
789
790 def parse_sections(text, project, obj, attr, only_path, options)
790 def parse_sections(text, project, obj, attr, only_path, options)
791 return unless options[:edit_section_links]
791 return unless options[:edit_section_links]
792 text.gsub!(HEADING_RE) do
792 text.gsub!(HEADING_RE) do
793 @current_section += 1
793 @current_section += 1
794 if @current_section > 1
794 if @current_section > 1
795 content_tag('div',
795 content_tag('div',
796 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
796 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
797 :class => 'contextual',
797 :class => 'contextual',
798 :title => l(:button_edit_section)) + $1
798 :title => l(:button_edit_section)) + $1
799 else
799 else
800 $1
800 $1
801 end
801 end
802 end
802 end
803 end
803 end
804
804
805 # Headings and TOC
805 # Headings and TOC
806 # Adds ids and links to headings unless options[:headings] is set to false
806 # Adds ids and links to headings unless options[:headings] is set to false
807 def parse_headings(text, project, obj, attr, only_path, options)
807 def parse_headings(text, project, obj, attr, only_path, options)
808 return if options[:headings] == false
808 return if options[:headings] == false
809
809
810 text.gsub!(HEADING_RE) do
810 text.gsub!(HEADING_RE) do
811 level, attrs, content = $2.to_i, $3, $4
811 level, attrs, content = $2.to_i, $3, $4
812 item = strip_tags(content).strip
812 item = strip_tags(content).strip
813 anchor = sanitize_anchor_name(item)
813 anchor = sanitize_anchor_name(item)
814 # used for single-file wiki export
814 # used for single-file wiki export
815 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
815 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
816 @parsed_headings << [level, anchor, item]
816 @parsed_headings << [level, anchor, item]
817 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
817 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
818 end
818 end
819 end
819 end
820
820
821 MACROS_RE = /
821 MACROS_RE = /
822 (!)? # escaping
822 (!)? # escaping
823 (
823 (
824 \{\{ # opening tag
824 \{\{ # opening tag
825 ([\w]+) # macro name
825 ([\w]+) # macro name
826 (\(([^\}]*)\))? # optional arguments
826 (\(([^\}]*)\))? # optional arguments
827 \}\} # closing tag
827 \}\} # closing tag
828 )
828 )
829 /x unless const_defined?(:MACROS_RE)
829 /x unless const_defined?(:MACROS_RE)
830
830
831 # Macros substitution
831 # Macros substitution
832 def parse_macros(text, project, obj, attr, only_path, options)
832 def parse_macros(text, project, obj, attr, only_path, options)
833 text.gsub!(MACROS_RE) do
833 text.gsub!(MACROS_RE) do
834 esc, all, macro = $1, $2, $3.downcase
834 esc, all, macro = $1, $2, $3.downcase
835 args = ($5 || '').split(',').each(&:strip)
835 args = ($5 || '').split(',').each(&:strip)
836 if esc.nil?
836 if esc.nil?
837 begin
837 begin
838 exec_macro(macro, obj, args)
838 exec_macro(macro, obj, args)
839 rescue => e
839 rescue => e
840 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
840 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
841 end || all
841 end || all
842 else
842 else
843 all
843 all
844 end
844 end
845 end
845 end
846 end
846 end
847
847
848 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
848 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
849
849
850 # Renders the TOC with given headings
850 # Renders the TOC with given headings
851 def replace_toc(text, headings)
851 def replace_toc(text, headings)
852 text.gsub!(TOC_RE) do
852 text.gsub!(TOC_RE) do
853 if headings.empty?
853 if headings.empty?
854 ''
854 ''
855 else
855 else
856 div_class = 'toc'
856 div_class = 'toc'
857 div_class << ' right' if $1 == '>'
857 div_class << ' right' if $1 == '>'
858 div_class << ' left' if $1 == '<'
858 div_class << ' left' if $1 == '<'
859 out = "<ul class=\"#{div_class}\"><li>"
859 out = "<ul class=\"#{div_class}\"><li>"
860 root = headings.map(&:first).min
860 root = headings.map(&:first).min
861 current = root
861 current = root
862 started = false
862 started = false
863 headings.each do |level, anchor, item|
863 headings.each do |level, anchor, item|
864 if level > current
864 if level > current
865 out << '<ul><li>' * (level - current)
865 out << '<ul><li>' * (level - current)
866 elsif level < current
866 elsif level < current
867 out << "</li></ul>\n" * (current - level) + "</li><li>"
867 out << "</li></ul>\n" * (current - level) + "</li><li>"
868 elsif started
868 elsif started
869 out << '</li><li>'
869 out << '</li><li>'
870 end
870 end
871 out << "<a href=\"##{anchor}\">#{item}</a>"
871 out << "<a href=\"##{anchor}\">#{item}</a>"
872 current = level
872 current = level
873 started = true
873 started = true
874 end
874 end
875 out << '</li></ul>' * (current - root)
875 out << '</li></ul>' * (current - root)
876 out << '</li></ul>'
876 out << '</li></ul>'
877 end
877 end
878 end
878 end
879 end
879 end
880
880
881 # Same as Rails' simple_format helper without using paragraphs
881 # Same as Rails' simple_format helper without using paragraphs
882 def simple_format_without_paragraph(text)
882 def simple_format_without_paragraph(text)
883 text.to_s.
883 text.to_s.
884 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
884 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
885 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
885 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
886 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
886 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
887 html_safe
887 html_safe
888 end
888 end
889
889
890 def lang_options_for_select(blank=true)
890 def lang_options_for_select(blank=true)
891 (blank ? [["(auto)", ""]] : []) +
891 (blank ? [["(auto)", ""]] : []) +
892 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
892 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
893 end
893 end
894
894
895 def label_tag_for(name, option_tags = nil, options = {})
895 def label_tag_for(name, option_tags = nil, options = {})
896 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
896 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
897 content_tag("label", label_text)
897 content_tag("label", label_text)
898 end
898 end
899
899
900 def labelled_tabular_form_for(*args, &proc)
900 def labelled_tabular_form_for(*args, &proc)
901 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_tabular_form_for is deprecated and will be removed in Redmine 1.5. Use #labelled_form_for instead."
901 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_tabular_form_for is deprecated and will be removed in Redmine 1.5. Use #labelled_form_for instead."
902 args << {} unless args.last.is_a?(Hash)
902 args << {} unless args.last.is_a?(Hash)
903 options = args.last
903 options = args.last
904 options[:html] ||= {}
904 options[:html] ||= {}
905 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
905 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
906 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
906 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
907 form_for(*args, &proc)
907 form_for(*args, &proc)
908 end
908 end
909
909
910 def labelled_form_for(*args, &proc)
910 def labelled_form_for(*args, &proc)
911 args << {} unless args.last.is_a?(Hash)
911 args << {} unless args.last.is_a?(Hash)
912 options = args.last
912 options = args.last
913 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
913 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
914 form_for(*args, &proc)
914 form_for(*args, &proc)
915 end
915 end
916
916
917 def labelled_fields_for(*args, &proc)
917 def labelled_fields_for(*args, &proc)
918 args << {} unless args.last.is_a?(Hash)
918 args << {} unless args.last.is_a?(Hash)
919 options = args.last
919 options = args.last
920 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
920 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
921 fields_for(*args, &proc)
921 fields_for(*args, &proc)
922 end
922 end
923
923
924 def labelled_remote_form_for(*args, &proc)
924 def labelled_remote_form_for(*args, &proc)
925 args << {} unless args.last.is_a?(Hash)
925 args << {} unless args.last.is_a?(Hash)
926 options = args.last
926 options = args.last
927 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
927 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
928 remote_form_for(*args, &proc)
928 remote_form_for(*args, &proc)
929 end
929 end
930
930
931 def back_url_hidden_field_tag
931 def back_url_hidden_field_tag
932 back_url = params[:back_url] || request.env['HTTP_REFERER']
932 back_url = params[:back_url] || request.env['HTTP_REFERER']
933 back_url = CGI.unescape(back_url.to_s)
933 back_url = CGI.unescape(back_url.to_s)
934 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
934 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
935 end
935 end
936
936
937 def check_all_links(form_name)
937 def check_all_links(form_name)
938 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
938 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
939 " | ".html_safe +
939 " | ".html_safe +
940 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
940 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
941 end
941 end
942
942
943 def progress_bar(pcts, options={})
943 def progress_bar(pcts, options={})
944 pcts = [pcts, pcts] unless pcts.is_a?(Array)
944 pcts = [pcts, pcts] unless pcts.is_a?(Array)
945 pcts = pcts.collect(&:round)
945 pcts = pcts.collect(&:round)
946 pcts[1] = pcts[1] - pcts[0]
946 pcts[1] = pcts[1] - pcts[0]
947 pcts << (100 - pcts[1] - pcts[0])
947 pcts << (100 - pcts[1] - pcts[0])
948 width = options[:width] || '100px;'
948 width = options[:width] || '100px;'
949 legend = options[:legend] || ''
949 legend = options[:legend] || ''
950 content_tag('table',
950 content_tag('table',
951 content_tag('tr',
951 content_tag('tr',
952 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
952 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
953 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
953 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
954 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
954 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
955 ), :class => 'progress', :style => "width: #{width};").html_safe +
955 ), :class => 'progress', :style => "width: #{width};").html_safe +
956 content_tag('p', legend, :class => 'pourcent').html_safe
956 content_tag('p', legend, :class => 'pourcent').html_safe
957 end
957 end
958
958
959 def checked_image(checked=true)
959 def checked_image(checked=true)
960 if checked
960 if checked
961 image_tag 'toggle_check.png'
961 image_tag 'toggle_check.png'
962 end
962 end
963 end
963 end
964
964
965 def context_menu(url)
965 def context_menu(url)
966 unless @context_menu_included
966 unless @context_menu_included
967 content_for :header_tags do
967 content_for :header_tags do
968 javascript_include_tag('context_menu') +
968 javascript_include_tag('context_menu') +
969 stylesheet_link_tag('context_menu')
969 stylesheet_link_tag('context_menu')
970 end
970 end
971 if l(:direction) == 'rtl'
971 if l(:direction) == 'rtl'
972 content_for :header_tags do
972 content_for :header_tags do
973 stylesheet_link_tag('context_menu_rtl')
973 stylesheet_link_tag('context_menu_rtl')
974 end
974 end
975 end
975 end
976 @context_menu_included = true
976 @context_menu_included = true
977 end
977 end
978 javascript_tag "new ContextMenu('#{ url_for(url) }')"
978 javascript_tag "new ContextMenu('#{ url_for(url) }')"
979 end
979 end
980
980
981 def context_menu_link(name, url, options={})
982 options[:class] ||= ''
983 if options.delete(:selected)
984 options[:class] << ' icon-checked disabled'
985 options[:disabled] = true
986 end
987 if options.delete(:disabled)
988 options.delete(:method)
989 options.delete(:confirm)
990 options.delete(:onclick)
991 options[:class] << ' disabled'
992 url = '#'
993 end
994 link_to h(name), url, options
995 end
996
997 def calendar_for(field_id)
981 def calendar_for(field_id)
998 include_calendar_headers_tags
982 include_calendar_headers_tags
999 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
983 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
1000 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
984 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
1001 end
985 end
1002
986
1003 def include_calendar_headers_tags
987 def include_calendar_headers_tags
1004 unless @calendar_headers_tags_included
988 unless @calendar_headers_tags_included
1005 @calendar_headers_tags_included = true
989 @calendar_headers_tags_included = true
1006 content_for :header_tags do
990 content_for :header_tags do
1007 start_of_week = case Setting.start_of_week.to_i
991 start_of_week = case Setting.start_of_week.to_i
1008 when 1
992 when 1
1009 'Calendar._FD = 1;' # Monday
993 'Calendar._FD = 1;' # Monday
1010 when 7
994 when 7
1011 'Calendar._FD = 0;' # Sunday
995 'Calendar._FD = 0;' # Sunday
1012 when 6
996 when 6
1013 'Calendar._FD = 6;' # Saturday
997 'Calendar._FD = 6;' # Saturday
1014 else
998 else
1015 '' # use language
999 '' # use language
1016 end
1000 end
1017
1001
1018 javascript_include_tag('calendar/calendar') +
1002 javascript_include_tag('calendar/calendar') +
1019 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
1003 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
1020 javascript_tag(start_of_week) +
1004 javascript_tag(start_of_week) +
1021 javascript_include_tag('calendar/calendar-setup') +
1005 javascript_include_tag('calendar/calendar-setup') +
1022 stylesheet_link_tag('calendar')
1006 stylesheet_link_tag('calendar')
1023 end
1007 end
1024 end
1008 end
1025 end
1009 end
1026
1010
1027 def content_for(name, content = nil, &block)
1011 def content_for(name, content = nil, &block)
1028 @has_content ||= {}
1012 @has_content ||= {}
1029 @has_content[name] = true
1013 @has_content[name] = true
1030 super(name, content, &block)
1014 super(name, content, &block)
1031 end
1015 end
1032
1016
1033 def has_content?(name)
1017 def has_content?(name)
1034 (@has_content && @has_content[name]) || false
1018 (@has_content && @has_content[name]) || false
1035 end
1019 end
1036
1020
1037 def email_delivery_enabled?
1021 def email_delivery_enabled?
1038 !!ActionMailer::Base.perform_deliveries
1022 !!ActionMailer::Base.perform_deliveries
1039 end
1023 end
1040
1024
1041 # Returns the avatar image tag for the given +user+ if avatars are enabled
1025 # Returns the avatar image tag for the given +user+ if avatars are enabled
1042 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1026 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1043 def avatar(user, options = { })
1027 def avatar(user, options = { })
1044 if Setting.gravatar_enabled?
1028 if Setting.gravatar_enabled?
1045 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
1029 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
1046 email = nil
1030 email = nil
1047 if user.respond_to?(:mail)
1031 if user.respond_to?(:mail)
1048 email = user.mail
1032 email = user.mail
1049 elsif user.to_s =~ %r{<(.+?)>}
1033 elsif user.to_s =~ %r{<(.+?)>}
1050 email = $1
1034 email = $1
1051 end
1035 end
1052 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1036 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1053 else
1037 else
1054 ''
1038 ''
1055 end
1039 end
1056 end
1040 end
1057
1041
1058 def sanitize_anchor_name(anchor)
1042 def sanitize_anchor_name(anchor)
1059 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1043 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1060 end
1044 end
1061
1045
1062 # Returns the javascript tags that are included in the html layout head
1046 # Returns the javascript tags that are included in the html layout head
1063 def javascript_heads
1047 def javascript_heads
1064 tags = javascript_include_tag(:defaults)
1048 tags = javascript_include_tag(:defaults)
1065 unless User.current.pref.warn_on_leaving_unsaved == '0'
1049 unless User.current.pref.warn_on_leaving_unsaved == '0'
1066 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
1050 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
1067 end
1051 end
1068 tags
1052 tags
1069 end
1053 end
1070
1054
1071 def favicon
1055 def favicon
1072 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1056 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1073 end
1057 end
1074
1058
1075 def robot_exclusion_tag
1059 def robot_exclusion_tag
1076 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1060 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1077 end
1061 end
1078
1062
1079 # Returns true if arg is expected in the API response
1063 # Returns true if arg is expected in the API response
1080 def include_in_api_response?(arg)
1064 def include_in_api_response?(arg)
1081 unless @included_in_api_response
1065 unless @included_in_api_response
1082 param = params[:include]
1066 param = params[:include]
1083 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1067 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1084 @included_in_api_response.collect!(&:strip)
1068 @included_in_api_response.collect!(&:strip)
1085 end
1069 end
1086 @included_in_api_response.include?(arg.to_s)
1070 @included_in_api_response.include?(arg.to_s)
1087 end
1071 end
1088
1072
1089 # Returns options or nil if nometa param or X-Redmine-Nometa header
1073 # Returns options or nil if nometa param or X-Redmine-Nometa header
1090 # was set in the request
1074 # was set in the request
1091 def api_meta(options)
1075 def api_meta(options)
1092 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1076 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1093 # compatibility mode for activeresource clients that raise
1077 # compatibility mode for activeresource clients that raise
1094 # an error when unserializing an array with attributes
1078 # an error when unserializing an array with attributes
1095 nil
1079 nil
1096 else
1080 else
1097 options
1081 options
1098 end
1082 end
1099 end
1083 end
1100
1084
1101 private
1085 private
1102
1086
1103 def wiki_helper
1087 def wiki_helper
1104 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1088 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1105 extend helper
1089 extend helper
1106 return self
1090 return self
1107 end
1091 end
1108
1092
1109 def link_to_content_update(text, url_params = {}, html_options = {})
1093 def link_to_content_update(text, url_params = {}, html_options = {})
1110 link_to(text, url_params, html_options)
1094 link_to(text, url_params, html_options)
1111 end
1095 end
1112 end
1096 end
General Comments 0
You need to be logged in to leave comments. Login now