Mercurial > hg > soundsoftware-site
comparison app/helpers/application_helper.rb @ 523:0b6c82dead28 luisf
Merge from branch "cannam"
author | luisf <luis.figueira@eecs.qmul.ac.uk> |
---|---|
date | Mon, 25 Jul 2011 14:23:37 +0100 |
parents | 5d608412b003 |
children | 5e80956cc792 |
comparison
equal
deleted
inserted
replaced
318:f7c525dc7585 | 523:0b6c82dead28 |
---|---|
1 # Redmine - project management software | 1 # Redmine - project management software |
2 # Copyright (C) 2006-2010 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. |
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 # |
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 |
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 | |
114 def link_to_message(message, options={}, html_options = nil) | |
115 link_to( | |
116 h(truncate(message.subject, :length => 60)), | |
117 { :controller => 'messages', :action => 'show', | |
118 :board_id => message.board_id, | |
119 :id => message.root, | |
120 :r => (message.parent_id && message.id), | |
121 :anchor => (message.parent_id ? "message-#{message.id}" : nil) | |
122 }.merge(options), | |
123 html_options | |
124 ) | |
125 end | |
126 | |
113 # Generates a link to a project if active | 127 # Generates a link to a project if active |
114 # Examples: | 128 # Examples: |
115 # | 129 # |
116 # link_to_project(project) # => link to the specified project overview | 130 # link_to_project(project) # => link to the specified project overview |
117 # link_to_project(project, :action=>'settings') # => link to project settings | 131 # link_to_project(project, :action=>'settings') # => link to project settings |
118 # 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 |
119 # 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) |
120 # | 134 # |
144 | 158 |
145 def prompt_to_remote(name, text, param, url, html_options = {}) | 159 def prompt_to_remote(name, text, param, url, html_options = {}) |
146 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;" | 160 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;" |
147 link_to name, {}, html_options | 161 link_to name, {}, html_options |
148 end | 162 end |
149 | 163 |
150 def format_activity_title(text) | 164 def format_activity_title(text) |
151 h(truncate_single_line(text, :length => 100)) | 165 h(truncate_single_line(text, :length => 100)) |
152 end | 166 end |
153 | 167 |
154 def format_activity_day(date) | 168 def format_activity_day(date) |
155 date == Date.today ? l(:label_today).titleize : format_date(date) | 169 date == Date.today ? l(:label_today).titleize : format_date(date) |
156 end | 170 end |
157 | 171 |
158 def format_activity_description(text) | 172 def format_activity_description(text) |
159 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 />") |
160 end | 174 end |
161 | 175 |
162 def format_version_name(version) | 176 def format_version_name(version) |
164 h(version) | 178 h(version) |
165 else | 179 else |
166 h("#{version.project} - #{version}") | 180 h("#{version.project} - #{version}") |
167 end | 181 end |
168 end | 182 end |
169 | 183 |
170 def due_date_distance_in_words(date) | 184 def due_date_distance_in_words(date) |
171 if date | 185 if date |
172 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)) |
173 end | 187 end |
174 end | 188 end |
175 | 189 |
176 def render_page_hierarchy(pages, node=nil) | 190 def render_page_hierarchy(pages, node=nil, options={}) |
177 content = '' | 191 content = '' |
178 if pages[node] | 192 if pages[node] |
179 content << "<ul class=\"pages-hierarchy\">\n" | 193 content << "<ul class=\"pages-hierarchy\">\n" |
180 pages[node].each do |page| | 194 pages[node].each do |page| |
181 content << "<li>" | 195 content << "<li>" |
182 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}, |
183 :title => (page.respond_to?(: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)) |
184 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id] | 198 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id] |
185 content << "</li>\n" | 199 content << "</li>\n" |
186 end | 200 end |
187 content << "</ul>\n" | 201 content << "</ul>\n" |
188 end | 202 end |
189 content | 203 content |
190 end | 204 end |
191 | 205 |
192 # Renders flash messages | 206 # Renders flash messages |
193 def render_flash_messages | 207 def render_flash_messages |
194 s = '' | 208 s = '' |
195 flash.each do |k,v| | 209 flash.each do |k,v| |
196 s << content_tag('div', v, :class => "flash #{k}") | 210 s << content_tag('div', v, :class => "flash #{k}") |
197 end | 211 end |
198 s | 212 s |
199 end | 213 end |
200 | 214 |
201 # Renders tabs and their content | 215 # Renders tabs and their content |
202 def render_tabs(tabs) | 216 def render_tabs(tabs) |
203 if tabs.any? | 217 if tabs.any? |
204 render :partial => 'common/tabs', :locals => {:tabs => tabs} | 218 render :partial => 'common/tabs', :locals => {:tabs => tabs} |
205 else | 219 else |
206 content_tag 'p', l(:label_no_data), :class => "nodata" | 220 content_tag 'p', l(:label_no_data), :class => "nodata" |
207 end | 221 end |
208 end | 222 end |
209 | 223 |
210 # Renders the project quick-jump box | 224 # Renders the project quick-jump box |
211 def render_project_jump_box | 225 def render_project_jump_box |
212 # Retrieve them now to avoid a COUNT query | 226 return unless User.current.logged? |
213 projects = User.current.projects.all | 227 projects = User.current.memberships.collect(&:project).compact.uniq |
214 if projects.any? | 228 if projects.any? |
215 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' + | 229 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' + |
216 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" + | 230 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" + |
217 '<option value="" disabled="disabled">---</option>' | 231 '<option value="" disabled="disabled">---</option>' |
218 s << project_tree_options_for_select(projects, :selected => @project) do |p| | 232 s << project_tree_options_for_select(projects, :selected => @project) do |p| |
220 end | 234 end |
221 s << '</select>' | 235 s << '</select>' |
222 s | 236 s |
223 end | 237 end |
224 end | 238 end |
225 | 239 |
226 def project_tree_options_for_select(projects, options = {}) | 240 def project_tree_options_for_select(projects, options = {}) |
227 s = '' | 241 s = '' |
228 project_tree(projects) do |project, level| | 242 project_tree(projects) do |project, level| |
229 name_prefix = (level > 0 ? (' ' * 2 * level + '» ') : '') | 243 name_prefix = (level > 0 ? (' ' * 2 * level + '» ') : '') |
230 tag_options = {:value => project.id} | 244 tag_options = {:value => project.id} |
236 tag_options.merge!(yield(project)) if block_given? | 250 tag_options.merge!(yield(project)) if block_given? |
237 s << content_tag('option', name_prefix + h(project), tag_options) | 251 s << content_tag('option', name_prefix + h(project), tag_options) |
238 end | 252 end |
239 s | 253 s |
240 end | 254 end |
241 | 255 |
242 # Yields the given block for each project with its level in the tree | 256 # Yields the given block for each project with its level in the tree |
243 # | 257 # |
244 # Wrapper for Project#project_tree | 258 # Wrapper for Project#project_tree |
245 def project_tree(projects, &block) | 259 def project_tree(projects, &block) |
246 Project.project_tree(projects, &block) | 260 Project.project_tree(projects, &block) |
247 end | 261 end |
248 | 262 |
249 def project_nested_ul(projects, &block) | 263 def project_nested_ul(projects, &block) |
250 s = '' | 264 s = '' |
251 if projects.any? | 265 if projects.any? |
252 ancestors = [] | 266 ancestors = [] |
253 projects.sort_by(&:lft).each do |project| | 267 projects.sort_by(&:lft).each do |project| |
254 if (ancestors.empty? || project.is_descendant_of?(ancestors.last)) | 268 if (ancestors.empty? || project.is_descendant_of?(ancestors.last)) |
255 s << "<ul>\n" | 269 s << "<ul>\n" |
256 else | 270 else |
257 ancestors.pop | 271 ancestors.pop |
258 s << "</li>" | 272 s << "</li>" |
259 while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) | 273 while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) |
260 ancestors.pop | 274 ancestors.pop |
261 s << "</ul></li>\n" | 275 s << "</ul></li>\n" |
262 end | 276 end |
263 end | 277 end |
264 s << "<li>" | 278 s << "<li>" |
267 end | 281 end |
268 s << ("</li></ul>\n" * ancestors.size) | 282 s << ("</li></ul>\n" * ancestors.size) |
269 end | 283 end |
270 s | 284 s |
271 end | 285 end |
272 | 286 |
273 def principals_check_box_tags(name, principals) | 287 def principals_check_box_tags(name, principals) |
274 s = '' | 288 s = '' |
275 principals.sort.each do |principal| | 289 principals.sort.each do |principal| |
276 s << "<label>#{ check_box_tag name, principal.id, false } #{link_to_user principal}</label>\n" | 290 s << "<label>#{ check_box_tag name, principal.id, false } #{link_to_user principal}</label>\n" |
277 end | 291 end |
278 s | 292 s |
279 end | 293 end |
280 | 294 |
281 # Truncates and returns the string as a single line | 295 # Truncates and returns the string as a single line |
282 def truncate_single_line(string, *args) | 296 def truncate_single_line(string, *args) |
283 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') | 297 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') |
284 end | 298 end |
285 | 299 |
286 # Truncates at line break after 250 characters or options[:length] | 300 # Truncates at line break after 250 characters or options[:length] |
287 def truncate_lines(string, options={}) | 301 def truncate_lines(string, options={}) |
288 length = options[:length] || 250 | 302 length = options[:length] || 250 |
289 if string.to_s =~ /\A(.{#{length}}.*?)$/m | 303 if string.to_s =~ /\A(.{#{length}}.*?)$/m |
290 "#{$1}..." | 304 "#{$1}..." |
298 end | 312 end |
299 | 313 |
300 def authoring(created, author, options={}) | 314 def authoring(created, author, options={}) |
301 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)) | 315 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)) |
302 end | 316 end |
303 | 317 |
304 def time_tag(time) | 318 def time_tag(time) |
305 text = distance_of_time_in_words(Time.now, time) | 319 text = distance_of_time_in_words(Time.now, time) |
306 if @project | 320 if @project |
307 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time)) | 321 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time)) |
308 else | 322 else |
320 | 334 |
321 def pagination_links_full(paginator, count=nil, options={}) | 335 def pagination_links_full(paginator, count=nil, options={}) |
322 page_param = options.delete(:page_param) || :page | 336 page_param = options.delete(:page_param) || :page |
323 per_page_links = options.delete(:per_page_links) | 337 per_page_links = options.delete(:per_page_links) |
324 url_param = params.dup | 338 url_param = params.dup |
325 # don't reuse query params if filters are present | |
326 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter) | |
327 | 339 |
328 html = '' | 340 html = '' |
329 if paginator.current.previous | 341 if paginator.current.previous |
330 html << link_to_remote_content_update('« ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' ' | 342 html << link_to_content_update('« ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' ' |
331 end | 343 end |
332 | 344 |
333 html << (pagination_links_each(paginator, options) do |n| | 345 html << (pagination_links_each(paginator, options) do |n| |
334 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n)) | 346 link_to_content_update(n.to_s, url_param.merge(page_param => n)) |
335 end || '') | 347 end || '') |
336 | 348 |
337 if paginator.current.next | 349 if paginator.current.next |
338 html << ' ' + link_to_remote_content_update((l(:label_next) + ' »'), url_param.merge(page_param => paginator.current.next)) | 350 html << ' ' + link_to_content_update((l(:label_next) + ' »'), url_param.merge(page_param => paginator.current.next)) |
339 end | 351 end |
340 | 352 |
341 unless count.nil? | 353 unless count.nil? |
342 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})" | 354 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})" |
343 if per_page_links != false && links = per_page_links(paginator.items_per_page) | 355 if per_page_links != false && links = per_page_links(paginator.items_per_page) |
345 end | 357 end |
346 end | 358 end |
347 | 359 |
348 html | 360 html |
349 end | 361 end |
350 | 362 |
351 def per_page_links(selected=nil) | 363 def per_page_links(selected=nil) |
352 url_param = params.dup | |
353 url_param.clear if url_param.has_key?(:set_filter) | |
354 | |
355 links = Setting.per_page_options_array.collect do |n| | 364 links = Setting.per_page_options_array.collect do |n| |
356 n == selected ? n : link_to_remote(n, {:update => "content", | 365 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n)) |
357 :url => params.dup.merge(:per_page => n), | |
358 :method => :get}, | |
359 {:href => url_for(url_param.merge(:per_page => n))}) | |
360 end | 366 end |
361 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil | 367 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil |
362 end | 368 end |
363 | 369 |
364 def reorder_links(name, url) | 370 def reorder_links(name, url) |
365 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) + | 371 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) + |
366 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) + | 372 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) + |
367 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) + | 373 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) + |
368 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest)) | 374 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest)) |
370 | 376 |
371 def breadcrumb(*args) | 377 def breadcrumb(*args) |
372 elements = args.flatten | 378 elements = args.flatten |
373 elements.any? ? content_tag('p', args.join(' » ') + ' » ', :class => 'breadcrumb') : nil | 379 elements.any? ? content_tag('p', args.join(' » ') + ' » ', :class => 'breadcrumb') : nil |
374 end | 380 end |
375 | 381 |
376 def other_formats_links(&block) | 382 def other_formats_links(&block) |
377 concat('<p class="other-formats">' + l(:label_export_to)) | 383 concat('<p class="other-formats">' + l(:label_export_to)) |
378 yield Redmine::Views::OtherFormatsBuilder.new(self) | 384 yield Redmine::Views::OtherFormatsBuilder.new(self) |
379 concat('</p>') | 385 concat('</p>') |
380 end | 386 end |
381 | 387 |
382 def page_header_title | 388 def page_header_title |
383 if @project.nil? || @project.new_record? | 389 if @project.nil? || @project.new_record? |
384 a = [h(Setting.app_title), ''] | 390 a = [h(Setting.app_title), ''] |
385 | 391 |
386 else | 392 else |
387 pname = [] | 393 pname = [] |
388 b = [] | 394 b = [] |
389 ancestors = (@project.root? ? [] : @project.ancestors.visible) | 395 ancestors = (@project.root? ? [] : @project.ancestors.visible.all) |
390 if ancestors.any? | 396 if ancestors.any? |
391 root = ancestors.shift | 397 root = ancestors.shift |
392 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root') | 398 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root') |
393 if ancestors.size > 2 | 399 if ancestors.size > 2 |
394 b << '…' | 400 b << '…' |
456 return '' if text.blank? | 462 return '' if text.blank? |
457 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) |
458 only_path = options.delete(:only_path) == false ? false : true | 464 only_path = options.delete(:only_path) == false ? false : true |
459 | 465 |
460 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) } |
461 | 467 |
462 parse_non_pre_blocks(text) do |text| | 468 @parsed_headings = [] |
469 text = parse_non_pre_blocks(text) do |text| | |
463 [: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| |
464 send method_name, text, project, obj, attr, only_path, options | 471 send method_name, text, project, obj, attr, only_path, options |
465 end | 472 end |
466 end | 473 end |
467 end | 474 |
468 | 475 if @parsed_headings.any? |
476 replace_toc(text, @parsed_headings) | |
477 end | |
478 | |
479 text | |
480 end | |
481 | |
469 def parse_non_pre_blocks(text) | 482 def parse_non_pre_blocks(text) |
470 s = StringScanner.new(text) | 483 s = StringScanner.new(text) |
471 tags = [] | 484 tags = [] |
472 parsed = '' | 485 parsed = '' |
473 while !s.eos? | 486 while !s.eos? |
492 while tag = tags.pop | 505 while tag = tags.pop |
493 parsed << "</#{tag}>" | 506 parsed << "</#{tag}>" |
494 end | 507 end |
495 parsed | 508 parsed |
496 end | 509 end |
497 | 510 |
498 def parse_inline_attachments(text, project, obj, attr, only_path, options) | 511 def parse_inline_attachments(text, project, obj, attr, only_path, options) |
499 # 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 |
500 if options[:attachments] || (obj && obj.respond_to?(:attachments)) | 513 if options[:attachments] || (obj && obj.respond_to?(:attachments)) |
501 attachments = nil | 514 attachments = nil |
502 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| |
503 filename, ext, alt, alttext = $1.downcase, $2, $3, $4 | 516 filename, ext, alt, alttext = $1.downcase, $2, $3, $4 |
504 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse | 517 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse |
505 # search for the picture in attachments | 518 # search for the picture in attachments |
506 if found = attachments.detect { |att| att.filename.downcase == filename } | 519 if found = attachments.detect { |att| att.filename.downcase == filename } |
507 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 |
508 desc = found.description.to_s.gsub('"', '') | 521 desc = found.description.to_s.gsub('"', '') |
561 else | 574 else |
562 all | 575 all |
563 end | 576 end |
564 end | 577 end |
565 end | 578 end |
566 | 579 |
567 # Redmine links | 580 # Redmine links |
568 # | 581 # |
569 # Examples: | 582 # Examples: |
570 # Issues: | 583 # Issues: |
571 # #52 -> Link to issue #52 | 584 # #52 -> Link to issue #52 |
586 # 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 |
587 # source:some/file@52 -> Link to the file's revision 52 | 600 # source:some/file@52 -> Link to the file's revision 52 |
588 # source:some/file#L120 -> Link to line 120 of the file | 601 # source:some/file#L120 -> Link to line 120 of the file |
589 # 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 |
590 # export:some/file -> Force the download of the file | 603 # export:some/file -> Force the download of the file |
591 # Forum messages: | 604 # Forum messages: |
592 # message#1218 -> Link to message with id 1218 | 605 # message#1218 -> Link to message with id 1218 |
606 # | |
607 # Links can refer other objects from other projects, using project identifier: | |
608 # identifier:r52 | |
609 # identifier:document:"Some document" | |
610 # identifier:version:1.0.0 | |
611 # identifier:source:some/file | |
593 def parse_redmine_links(text, project, obj, attr, only_path, options) | 612 def parse_redmine_links(text, project, obj, attr, only_path, options) |
594 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(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| |
595 leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8 | 614 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10 |
596 link = nil | 615 link = nil |
616 if project_identifier | |
617 project = Project.visible.find_by_identifier(project_identifier) | |
618 end | |
597 if esc.nil? | 619 if esc.nil? |
598 if prefix.nil? && sep == 'r' | 620 if prefix.nil? && sep == 'r' |
599 if project && (changeset = project.changesets.find_by_revision(identifier)) | 621 # project.changesets.visible raises an SQL error because of a double join on repositories |
600 link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision}, | 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}, | |
601 :class => 'changeset', | 624 :class => 'changeset', |
602 :title => truncate_single_line(changeset.comments, :length => 100)) | 625 :title => truncate_single_line(changeset.comments, :length => 100)) |
603 end | 626 end |
604 elsif sep == '#' | 627 elsif sep == '#' |
605 oid = identifier.to_i | 628 oid = identifier.to_i |
609 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}, |
610 :class => issue.css_classes, | 633 :class => issue.css_classes, |
611 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") | 634 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") |
612 end | 635 end |
613 when 'document' | 636 when 'document' |
614 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current)) | 637 if document = Document.visible.find_by_id(oid) |
615 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}, |
616 :class => 'document' | 639 :class => 'document' |
617 end | 640 end |
618 when 'version' | 641 when 'version' |
619 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current)) | 642 if version = Version.visible.find_by_id(oid) |
620 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}, |
621 :class => 'version' | 644 :class => 'version' |
622 end | 645 end |
623 when 'message' | 646 when 'message' |
624 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current)) | 647 if message = Message.visible.find_by_id(oid, :include => :parent) |
625 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path, | 648 link = link_to_message(message, {:only_path => only_path}, :class => 'message') |
626 :controller => 'messages', | |
627 :action => 'show', | |
628 :board_id => message.board, | |
629 :id => message.root, | |
630 :anchor => (message.parent ? "message-#{message.id}" : nil)}, | |
631 :class => 'message' | |
632 end | 649 end |
633 when 'project' | 650 when 'project' |
634 if p = Project.visible.find_by_id(oid) | 651 if p = Project.visible.find_by_id(oid) |
635 link = link_to_project(p, {:only_path => only_path}, :class => 'project') | 652 link = link_to_project(p, {:only_path => only_path}, :class => 'project') |
636 end | 653 end |
638 elsif sep == ':' | 655 elsif sep == ':' |
639 # removes the double quotes if any | 656 # removes the double quotes if any |
640 name = identifier.gsub(%r{^"(.*)"$}, "\\1") | 657 name = identifier.gsub(%r{^"(.*)"$}, "\\1") |
641 case prefix | 658 case prefix |
642 when 'document' | 659 when 'document' |
643 if project && document = project.documents.find_by_title(name) | 660 if project && document = project.documents.visible.find_by_title(name) |
644 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}, |
645 :class => 'document' | 662 :class => 'document' |
646 end | 663 end |
647 when 'version' | 664 when 'version' |
648 if project && version = project.versions.find_by_name(name) | 665 if project && version = project.versions.visible.find_by_name(name) |
649 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}, |
650 :class => 'version' | 667 :class => 'version' |
651 end | 668 end |
652 when 'commit' | 669 when 'commit' |
653 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"])) | 670 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"])) |
654 link = link_to h("#{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}, |
655 :class => 'changeset', | 672 :class => 'changeset', |
656 :title => truncate_single_line(changeset.comments, :length => 100) | 673 :title => truncate_single_line(changeset.comments, :length => 100) |
657 end | 674 end |
658 when 'source', 'export' | 675 when 'source', 'export' |
659 if project && project.repository | 676 if project && project.repository && User.current.allowed_to?(:browse_repository, project) |
660 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$} | 677 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$} |
661 path, rev, anchor = $1, $3, $5 | 678 path, rev, anchor = $1, $3, $5 |
662 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, | 679 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, |
663 :path => to_path_param(path), | 680 :path => to_path_param(path), |
664 :rev => rev, | 681 :rev => rev, |
665 :anchor => anchor, | 682 :anchor => anchor, |
666 :format => (prefix == 'export' ? 'raw' : nil)}, | 683 :format => (prefix == 'export' ? 'raw' : nil)}, |
667 :class => (prefix == 'export' ? 'source download' : 'source') | 684 :class => (prefix == 'export' ? 'source download' : 'source') |
677 link = link_to_project(p, {:only_path => only_path}, :class => 'project') | 694 link = link_to_project(p, {:only_path => only_path}, :class => 'project') |
678 end | 695 end |
679 end | 696 end |
680 end | 697 end |
681 end | 698 end |
682 leading + (link || "#{prefix}#{sep}#{identifier}") | 699 leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}") |
683 end | 700 end |
684 end | 701 end |
685 | 702 |
686 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE) | |
687 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) |
688 | 704 |
689 # Headings and TOC | 705 # Headings and TOC |
690 # Adds ids and links to headings and renders the TOC if needed unless options[:headings] is set to false | 706 # Adds ids and links to headings unless options[:headings] is set to false |
691 def parse_headings(text, project, obj, attr, only_path, options) | 707 def parse_headings(text, project, obj, attr, only_path, options) |
692 headings = [] | 708 return if options[:headings] == false |
709 | |
693 text.gsub!(HEADING_RE) do | 710 text.gsub!(HEADING_RE) do |
694 level, attrs, content = $1.to_i, $2, $3 | 711 level, attrs, content = $1.to_i, $2, $3 |
695 item = strip_tags(content).strip | 712 item = strip_tags(content).strip |
696 anchor = item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') | 713 anchor = item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') |
697 headings << [level, anchor, item] | 714 @parsed_headings << [level, anchor, item] |
698 "<h#{level} #{attrs} id=\"#{anchor}\">#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">¶</a></h#{level}>" | 715 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">¶</a></h#{level}>" |
699 end unless options[:headings] == false | 716 end |
700 | 717 end |
718 | |
719 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE) | |
720 | |
721 # Renders the TOC with given headings | |
722 def replace_toc(text, headings) | |
701 text.gsub!(TOC_RE) do | 723 text.gsub!(TOC_RE) do |
702 if headings.empty? | 724 if headings.empty? |
703 '' | 725 '' |
704 else | 726 else |
705 div_class = 'toc' | 727 div_class = 'toc' |
776 (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') : '') + |
777 (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') : '') |
778 ), :class => 'progress', :style => "width: #{width};") + | 800 ), :class => 'progress', :style => "width: #{width};") + |
779 content_tag('p', legend, :class => 'pourcent') | 801 content_tag('p', legend, :class => 'pourcent') |
780 end | 802 end |
781 | 803 |
782 def checked_image(checked=true) | 804 def checked_image(checked=true) |
783 if checked | 805 if checked |
784 image_tag 'toggle_check.png' | 806 image_tag 'toggle_check.png' |
785 end | 807 end |
786 end | 808 end |
787 | 809 |
788 def context_menu(url) | 810 def context_menu(url) |
789 unless @context_menu_included | 811 unless @context_menu_included |
790 content_for :header_tags do | 812 content_for :header_tags do |
791 javascript_include_tag('context_menu') + | 813 javascript_include_tag('context_menu') + |
792 stylesheet_link_tag('context_menu') | 814 stylesheet_link_tag('context_menu') |
830 start_of_week = case Setting.start_of_week.to_i | 852 start_of_week = case Setting.start_of_week.to_i |
831 when 1 | 853 when 1 |
832 'Calendar._FD = 1;' # Monday | 854 'Calendar._FD = 1;' # Monday |
833 when 7 | 855 when 7 |
834 'Calendar._FD = 0;' # Sunday | 856 'Calendar._FD = 0;' # Sunday |
857 when 6 | |
858 'Calendar._FD = 6;' # Saturday | |
835 else | 859 else |
836 '' # use language | 860 '' # use language |
837 end | 861 end |
838 | 862 |
839 javascript_include_tag('calendar/calendar') + | 863 javascript_include_tag('calendar/calendar') + |
840 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") + |
841 javascript_tag(start_of_week) + | 865 javascript_tag(start_of_week) + |
842 javascript_include_tag('calendar/calendar-setup') + | 866 javascript_include_tag('calendar/calendar-setup') + |
843 stylesheet_link_tag('calendar') | 867 stylesheet_link_tag('calendar') |
844 end | 868 end |
845 end | 869 end |
846 end | 870 end |
870 else | 894 else |
871 '' | 895 '' |
872 end | 896 end |
873 end | 897 end |
874 | 898 |
899 # Returns the javascript tags that are included in the html layout head | |
900 def javascript_heads | |
901 tags = javascript_include_tag(:defaults) | |
902 unless User.current.pref.warn_on_leaving_unsaved == '0' | |
903 tags << "\n" + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });") | |
904 end | |
905 tags | |
906 end | |
907 | |
875 def favicon | 908 def favicon |
876 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />" | 909 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />" |
910 end | |
911 | |
912 def robot_exclusion_tag | |
913 '<meta name="robots" content="noindex,follow,noarchive" />' | |
914 end | |
915 | |
916 def stylesheet_platform_font_tag | |
917 agent = request.env['HTTP_USER_AGENT'] | |
918 name = 'fonts-generic' | |
919 if agent and agent =~ %r{Windows} | |
920 name = 'fonts-ms' | |
921 elsif agent and agent =~ %r{Macintosh} | |
922 name = 'fonts-mac' | |
923 end | |
924 stylesheet_link_tag name, :media => 'all' | |
925 end | |
926 | |
927 # Returns true if arg is expected in the API response | |
928 def include_in_api_response?(arg) | |
929 unless @included_in_api_response | |
930 param = params[:include] | |
931 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',') | |
932 @included_in_api_response.collect!(&:strip) | |
933 end | |
934 @included_in_api_response.include?(arg.to_s) | |
935 end | |
936 | |
937 # Returns options or nil if nometa param or X-Redmine-Nometa header | |
938 # was set in the request | |
939 def api_meta(options) | |
940 if params[:nometa].present? || request.headers['X-Redmine-Nometa'] | |
941 # compatibility mode for activeresource clients that raise | |
942 # an error when unserializing an array with attributes | |
943 nil | |
944 else | |
945 options | |
946 end | |
877 end | 947 end |
878 | 948 |
879 private | 949 private |
880 | 950 |
881 def wiki_helper | 951 def wiki_helper |
882 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting) | 952 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting) |
883 extend helper | 953 extend helper |
884 return self | 954 return self |
885 end | 955 end |
886 | 956 |
887 def link_to_remote_content_update(text, url_params) | 957 def link_to_content_update(text, url_params = {}, html_options = {}) |
888 link_to_remote(text, | 958 link_to(text, url_params, html_options) |
889 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'}, | 959 end |
890 {:href => url_for(:params => url_params)} | |
891 ) | |
892 end | |
893 | |
894 end | 960 end |