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