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