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