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