##// END OF EJS Templates
Adds noindex,noarchive robots meta tag on form pages (#7582)....
Jean-Philippe Lang -
r5323:ffd0a9c72c24
parent child
Show More
@@ -1,937 +1,941
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 226 projects = User.current.memberships.collect(&:project).compact.uniq
227 227 if projects.any?
228 228 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
229 229 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
230 230 '<option value="" disabled="disabled">---</option>'
231 231 s << project_tree_options_for_select(projects, :selected => @project) do |p|
232 232 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
233 233 end
234 234 s << '</select>'
235 235 s
236 236 end
237 237 end
238 238
239 239 def project_tree_options_for_select(projects, options = {})
240 240 s = ''
241 241 project_tree(projects) do |project, level|
242 242 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
243 243 tag_options = {:value => project.id}
244 244 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
245 245 tag_options[:selected] = 'selected'
246 246 else
247 247 tag_options[:selected] = nil
248 248 end
249 249 tag_options.merge!(yield(project)) if block_given?
250 250 s << content_tag('option', name_prefix + h(project), tag_options)
251 251 end
252 252 s
253 253 end
254 254
255 255 # Yields the given block for each project with its level in the tree
256 256 #
257 257 # Wrapper for Project#project_tree
258 258 def project_tree(projects, &block)
259 259 Project.project_tree(projects, &block)
260 260 end
261 261
262 262 def project_nested_ul(projects, &block)
263 263 s = ''
264 264 if projects.any?
265 265 ancestors = []
266 266 projects.sort_by(&:lft).each do |project|
267 267 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
268 268 s << "<ul>\n"
269 269 else
270 270 ancestors.pop
271 271 s << "</li>"
272 272 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
273 273 ancestors.pop
274 274 s << "</ul></li>\n"
275 275 end
276 276 end
277 277 s << "<li>"
278 278 s << yield(project).to_s
279 279 ancestors << project
280 280 end
281 281 s << ("</li></ul>\n" * ancestors.size)
282 282 end
283 283 s
284 284 end
285 285
286 286 def principals_check_box_tags(name, principals)
287 287 s = ''
288 288 principals.sort.each do |principal|
289 289 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
290 290 end
291 291 s
292 292 end
293 293
294 294 # Truncates and returns the string as a single line
295 295 def truncate_single_line(string, *args)
296 296 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
297 297 end
298 298
299 299 # Truncates at line break after 250 characters or options[:length]
300 300 def truncate_lines(string, options={})
301 301 length = options[:length] || 250
302 302 if string.to_s =~ /\A(.{#{length}}.*?)$/m
303 303 "#{$1}..."
304 304 else
305 305 string
306 306 end
307 307 end
308 308
309 309 def html_hours(text)
310 310 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
311 311 end
312 312
313 313 def authoring(created, author, options={})
314 314 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
315 315 end
316 316
317 317 def time_tag(time)
318 318 text = distance_of_time_in_words(Time.now, time)
319 319 if @project
320 320 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
321 321 else
322 322 content_tag('acronym', text, :title => format_time(time))
323 323 end
324 324 end
325 325
326 326 def syntax_highlight(name, content)
327 327 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
328 328 end
329 329
330 330 def to_path_param(path)
331 331 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
332 332 end
333 333
334 334 def pagination_links_full(paginator, count=nil, options={})
335 335 page_param = options.delete(:page_param) || :page
336 336 per_page_links = options.delete(:per_page_links)
337 337 url_param = params.dup
338 338
339 339 html = ''
340 340 if paginator.current.previous
341 341 html << link_to_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
342 342 end
343 343
344 344 html << (pagination_links_each(paginator, options) do |n|
345 345 link_to_content_update(n.to_s, url_param.merge(page_param => n))
346 346 end || '')
347 347
348 348 if paginator.current.next
349 349 html << ' ' + link_to_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
350 350 end
351 351
352 352 unless count.nil?
353 353 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
354 354 if per_page_links != false && links = per_page_links(paginator.items_per_page)
355 355 html << " | #{links}"
356 356 end
357 357 end
358 358
359 359 html
360 360 end
361 361
362 362 def per_page_links(selected=nil)
363 363 links = Setting.per_page_options_array.collect do |n|
364 364 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
365 365 end
366 366 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
367 367 end
368 368
369 369 def reorder_links(name, url)
370 370 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 371 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 372 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 373 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 374 end
375 375
376 376 def breadcrumb(*args)
377 377 elements = args.flatten
378 378 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
379 379 end
380 380
381 381 def other_formats_links(&block)
382 382 concat('<p class="other-formats">' + l(:label_export_to))
383 383 yield Redmine::Views::OtherFormatsBuilder.new(self)
384 384 concat('</p>')
385 385 end
386 386
387 387 def page_header_title
388 388 if @project.nil? || @project.new_record?
389 389 h(Setting.app_title)
390 390 else
391 391 b = []
392 392 ancestors = (@project.root? ? [] : @project.ancestors.visible)
393 393 if ancestors.any?
394 394 root = ancestors.shift
395 395 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
396 396 if ancestors.size > 2
397 397 b << '&#8230;'
398 398 ancestors = ancestors[-2, 2]
399 399 end
400 400 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
401 401 end
402 402 b << h(@project)
403 403 b.join(' &#187; ')
404 404 end
405 405 end
406 406
407 407 def html_title(*args)
408 408 if args.empty?
409 409 title = []
410 410 title << @project.name if @project
411 411 title += @html_title if @html_title
412 412 title << Setting.app_title
413 413 title.select {|t| !t.blank? }.join(' - ')
414 414 else
415 415 @html_title ||= []
416 416 @html_title += args
417 417 end
418 418 end
419 419
420 420 # Returns the theme, controller name, and action as css classes for the
421 421 # HTML body.
422 422 def body_css_classes
423 423 css = []
424 424 if theme = Redmine::Themes.theme(Setting.ui_theme)
425 425 css << 'theme-' + theme.name
426 426 end
427 427
428 428 css << 'controller-' + params[:controller]
429 429 css << 'action-' + params[:action]
430 430 css.join(' ')
431 431 end
432 432
433 433 def accesskey(s)
434 434 Redmine::AccessKeys.key_for s
435 435 end
436 436
437 437 # Formats text according to system settings.
438 438 # 2 ways to call this method:
439 439 # * with a String: textilizable(text, options)
440 440 # * with an object and one of its attribute: textilizable(issue, :description, options)
441 441 def textilizable(*args)
442 442 options = args.last.is_a?(Hash) ? args.pop : {}
443 443 case args.size
444 444 when 1
445 445 obj = options[:object]
446 446 text = args.shift
447 447 when 2
448 448 obj = args.shift
449 449 attr = args.shift
450 450 text = obj.send(attr).to_s
451 451 else
452 452 raise ArgumentError, 'invalid arguments to textilizable'
453 453 end
454 454 return '' if text.blank?
455 455 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
456 456 only_path = options.delete(:only_path) == false ? false : true
457 457
458 458 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
459 459
460 460 @parsed_headings = []
461 461 text = parse_non_pre_blocks(text) do |text|
462 462 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings].each do |method_name|
463 463 send method_name, text, project, obj, attr, only_path, options
464 464 end
465 465 end
466 466
467 467 if @parsed_headings.any?
468 468 replace_toc(text, @parsed_headings)
469 469 end
470 470
471 471 text
472 472 end
473 473
474 474 def parse_non_pre_blocks(text)
475 475 s = StringScanner.new(text)
476 476 tags = []
477 477 parsed = ''
478 478 while !s.eos?
479 479 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
480 480 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
481 481 if tags.empty?
482 482 yield text
483 483 end
484 484 parsed << text
485 485 if tag
486 486 if closing
487 487 if tags.last == tag.downcase
488 488 tags.pop
489 489 end
490 490 else
491 491 tags << tag.downcase
492 492 end
493 493 parsed << full_tag
494 494 end
495 495 end
496 496 # Close any non closing tags
497 497 while tag = tags.pop
498 498 parsed << "</#{tag}>"
499 499 end
500 500 parsed
501 501 end
502 502
503 503 def parse_inline_attachments(text, project, obj, attr, only_path, options)
504 504 # when using an image link, try to use an attachment, if possible
505 505 if options[:attachments] || (obj && obj.respond_to?(:attachments))
506 506 attachments = nil
507 507 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
508 508 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
509 509 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
510 510 # search for the picture in attachments
511 511 if found = attachments.detect { |att| att.filename.downcase == filename }
512 512 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
513 513 desc = found.description.to_s.gsub('"', '')
514 514 if !desc.blank? && alttext.blank?
515 515 alt = " title=\"#{desc}\" alt=\"#{desc}\""
516 516 end
517 517 "src=\"#{image_url}\"#{alt}"
518 518 else
519 519 m
520 520 end
521 521 end
522 522 end
523 523 end
524 524
525 525 # Wiki links
526 526 #
527 527 # Examples:
528 528 # [[mypage]]
529 529 # [[mypage|mytext]]
530 530 # wiki links can refer other project wikis, using project name or identifier:
531 531 # [[project:]] -> wiki starting page
532 532 # [[project:|mytext]]
533 533 # [[project:mypage]]
534 534 # [[project:mypage|mytext]]
535 535 def parse_wiki_links(text, project, obj, attr, only_path, options)
536 536 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
537 537 link_project = project
538 538 esc, all, page, title = $1, $2, $3, $5
539 539 if esc.nil?
540 540 if page =~ /^([^\:]+)\:(.*)$/
541 541 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
542 542 page = $2
543 543 title ||= $1 if page.blank?
544 544 end
545 545
546 546 if link_project && link_project.wiki
547 547 # extract anchor
548 548 anchor = nil
549 549 if page =~ /^(.+?)\#(.+)$/
550 550 page, anchor = $1, $2
551 551 end
552 552 # check if page exists
553 553 wiki_page = link_project.wiki.find_page(page)
554 554 url = case options[:wiki_links]
555 555 when :local; "#{title}.html"
556 556 when :anchor; "##{title}" # used for single-file wiki export
557 557 else
558 558 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
559 559 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
560 560 end
561 561 link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
562 562 else
563 563 # project or wiki doesn't exist
564 564 all
565 565 end
566 566 else
567 567 all
568 568 end
569 569 end
570 570 end
571 571
572 572 # Redmine links
573 573 #
574 574 # Examples:
575 575 # Issues:
576 576 # #52 -> Link to issue #52
577 577 # Changesets:
578 578 # r52 -> Link to revision 52
579 579 # commit:a85130f -> Link to scmid starting with a85130f
580 580 # Documents:
581 581 # document#17 -> Link to document with id 17
582 582 # document:Greetings -> Link to the document with title "Greetings"
583 583 # document:"Some document" -> Link to the document with title "Some document"
584 584 # Versions:
585 585 # version#3 -> Link to version with id 3
586 586 # version:1.0.0 -> Link to version named "1.0.0"
587 587 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
588 588 # Attachments:
589 589 # attachment:file.zip -> Link to the attachment of the current object named file.zip
590 590 # Source files:
591 591 # source:some/file -> Link to the file located at /some/file in the project's repository
592 592 # source:some/file@52 -> Link to the file's revision 52
593 593 # source:some/file#L120 -> Link to line 120 of the file
594 594 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
595 595 # export:some/file -> Force the download of the file
596 596 # Forum messages:
597 597 # message#1218 -> Link to message with id 1218
598 598 #
599 599 # Links can refer other objects from other projects, using project identifier:
600 600 # identifier:r52
601 601 # identifier:document:"Some document"
602 602 # identifier:version:1.0.0
603 603 # identifier:source:some/file
604 604 def parse_redmine_links(text, project, obj, attr, only_path, options)
605 605 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 606 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
607 607 link = nil
608 608 if project_identifier
609 609 project = Project.visible.find_by_identifier(project_identifier)
610 610 end
611 611 if esc.nil?
612 612 if prefix.nil? && sep == 'r'
613 613 # project.changesets.visible raises an SQL error because of a double join on repositories
614 614 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
615 615 link = link_to("#{project_prefix}r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
616 616 :class => 'changeset',
617 617 :title => truncate_single_line(changeset.comments, :length => 100))
618 618 end
619 619 elsif sep == '#'
620 620 oid = identifier.to_i
621 621 case prefix
622 622 when nil
623 623 if issue = Issue.visible.find_by_id(oid, :include => :status)
624 624 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
625 625 :class => issue.css_classes,
626 626 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
627 627 end
628 628 when 'document'
629 629 if document = Document.visible.find_by_id(oid)
630 630 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
631 631 :class => 'document'
632 632 end
633 633 when 'version'
634 634 if version = Version.visible.find_by_id(oid)
635 635 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
636 636 :class => 'version'
637 637 end
638 638 when 'message'
639 639 if message = Message.visible.find_by_id(oid, :include => :parent)
640 640 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
641 641 end
642 642 when 'project'
643 643 if p = Project.visible.find_by_id(oid)
644 644 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
645 645 end
646 646 end
647 647 elsif sep == ':'
648 648 # removes the double quotes if any
649 649 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
650 650 case prefix
651 651 when 'document'
652 652 if project && document = project.documents.visible.find_by_title(name)
653 653 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
654 654 :class => 'document'
655 655 end
656 656 when 'version'
657 657 if project && version = project.versions.visible.find_by_name(name)
658 658 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
659 659 :class => 'version'
660 660 end
661 661 when 'commit'
662 662 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
663 663 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
664 664 :class => 'changeset',
665 665 :title => truncate_single_line(changeset.comments, :length => 100)
666 666 end
667 667 when 'source', 'export'
668 668 if project && project.repository && User.current.allowed_to?(:browse_repository, project)
669 669 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
670 670 path, rev, anchor = $1, $3, $5
671 671 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
672 672 :path => to_path_param(path),
673 673 :rev => rev,
674 674 :anchor => anchor,
675 675 :format => (prefix == 'export' ? 'raw' : nil)},
676 676 :class => (prefix == 'export' ? 'source download' : 'source')
677 677 end
678 678 when 'attachment'
679 679 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
680 680 if attachments && attachment = attachments.detect {|a| a.filename == name }
681 681 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
682 682 :class => 'attachment'
683 683 end
684 684 when 'project'
685 685 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
686 686 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
687 687 end
688 688 end
689 689 end
690 690 end
691 691 leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")
692 692 end
693 693 end
694 694
695 695 HEADING_RE = /<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>/i unless const_defined?(:HEADING_RE)
696 696
697 697 # Headings and TOC
698 698 # Adds ids and links to headings unless options[:headings] is set to false
699 699 def parse_headings(text, project, obj, attr, only_path, options)
700 700 return if options[:headings] == false
701 701
702 702 text.gsub!(HEADING_RE) do
703 703 level, attrs, content = $1.to_i, $2, $3
704 704 item = strip_tags(content).strip
705 705 anchor = item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
706 706 @parsed_headings << [level, anchor, item]
707 707 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
708 708 end
709 709 end
710 710
711 711 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
712 712
713 713 # Renders the TOC with given headings
714 714 def replace_toc(text, headings)
715 715 text.gsub!(TOC_RE) do
716 716 if headings.empty?
717 717 ''
718 718 else
719 719 div_class = 'toc'
720 720 div_class << ' right' if $1 == '>'
721 721 div_class << ' left' if $1 == '<'
722 722 out = "<ul class=\"#{div_class}\"><li>"
723 723 root = headings.map(&:first).min
724 724 current = root
725 725 started = false
726 726 headings.each do |level, anchor, item|
727 727 if level > current
728 728 out << '<ul><li>' * (level - current)
729 729 elsif level < current
730 730 out << "</li></ul>\n" * (current - level) + "</li><li>"
731 731 elsif started
732 732 out << '</li><li>'
733 733 end
734 734 out << "<a href=\"##{anchor}\">#{item}</a>"
735 735 current = level
736 736 started = true
737 737 end
738 738 out << '</li></ul>' * (current - root)
739 739 out << '</li></ul>'
740 740 end
741 741 end
742 742 end
743 743
744 744 # Same as Rails' simple_format helper without using paragraphs
745 745 def simple_format_without_paragraph(text)
746 746 text.to_s.
747 747 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
748 748 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
749 749 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
750 750 end
751 751
752 752 def lang_options_for_select(blank=true)
753 753 (blank ? [["(auto)", ""]] : []) +
754 754 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
755 755 end
756 756
757 757 def label_tag_for(name, option_tags = nil, options = {})
758 758 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
759 759 content_tag("label", label_text)
760 760 end
761 761
762 762 def labelled_tabular_form_for(name, object, options, &proc)
763 763 options[:html] ||= {}
764 764 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
765 765 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
766 766 end
767 767
768 768 def back_url_hidden_field_tag
769 769 back_url = params[:back_url] || request.env['HTTP_REFERER']
770 770 back_url = CGI.unescape(back_url.to_s)
771 771 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
772 772 end
773 773
774 774 def check_all_links(form_name)
775 775 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
776 776 " | " +
777 777 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
778 778 end
779 779
780 780 def progress_bar(pcts, options={})
781 781 pcts = [pcts, pcts] unless pcts.is_a?(Array)
782 782 pcts = pcts.collect(&:round)
783 783 pcts[1] = pcts[1] - pcts[0]
784 784 pcts << (100 - pcts[1] - pcts[0])
785 785 width = options[:width] || '100px;'
786 786 legend = options[:legend] || ''
787 787 content_tag('table',
788 788 content_tag('tr',
789 789 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
790 790 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
791 791 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
792 792 ), :class => 'progress', :style => "width: #{width};") +
793 793 content_tag('p', legend, :class => 'pourcent')
794 794 end
795 795
796 796 def checked_image(checked=true)
797 797 if checked
798 798 image_tag 'toggle_check.png'
799 799 end
800 800 end
801 801
802 802 def context_menu(url)
803 803 unless @context_menu_included
804 804 content_for :header_tags do
805 805 javascript_include_tag('context_menu') +
806 806 stylesheet_link_tag('context_menu')
807 807 end
808 808 if l(:direction) == 'rtl'
809 809 content_for :header_tags do
810 810 stylesheet_link_tag('context_menu_rtl')
811 811 end
812 812 end
813 813 @context_menu_included = true
814 814 end
815 815 javascript_tag "new ContextMenu('#{ url_for(url) }')"
816 816 end
817 817
818 818 def context_menu_link(name, url, options={})
819 819 options[:class] ||= ''
820 820 if options.delete(:selected)
821 821 options[:class] << ' icon-checked disabled'
822 822 options[:disabled] = true
823 823 end
824 824 if options.delete(:disabled)
825 825 options.delete(:method)
826 826 options.delete(:confirm)
827 827 options.delete(:onclick)
828 828 options[:class] << ' disabled'
829 829 url = '#'
830 830 end
831 831 link_to name, url, options
832 832 end
833 833
834 834 def calendar_for(field_id)
835 835 include_calendar_headers_tags
836 836 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
837 837 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
838 838 end
839 839
840 840 def include_calendar_headers_tags
841 841 unless @calendar_headers_tags_included
842 842 @calendar_headers_tags_included = true
843 843 content_for :header_tags do
844 844 start_of_week = case Setting.start_of_week.to_i
845 845 when 1
846 846 'Calendar._FD = 1;' # Monday
847 847 when 7
848 848 'Calendar._FD = 0;' # Sunday
849 849 when 6
850 850 'Calendar._FD = 6;' # Saturday
851 851 else
852 852 '' # use language
853 853 end
854 854
855 855 javascript_include_tag('calendar/calendar') +
856 856 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
857 857 javascript_tag(start_of_week) +
858 858 javascript_include_tag('calendar/calendar-setup') +
859 859 stylesheet_link_tag('calendar')
860 860 end
861 861 end
862 862 end
863 863
864 864 def content_for(name, content = nil, &block)
865 865 @has_content ||= {}
866 866 @has_content[name] = true
867 867 super(name, content, &block)
868 868 end
869 869
870 870 def has_content?(name)
871 871 (@has_content && @has_content[name]) || false
872 872 end
873 873
874 874 # Returns the avatar image tag for the given +user+ if avatars are enabled
875 875 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
876 876 def avatar(user, options = { })
877 877 if Setting.gravatar_enabled?
878 878 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
879 879 email = nil
880 880 if user.respond_to?(:mail)
881 881 email = user.mail
882 882 elsif user.to_s =~ %r{<(.+?)>}
883 883 email = $1
884 884 end
885 885 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
886 886 else
887 887 ''
888 888 end
889 889 end
890 890
891 891 # Returns the javascript tags that are included in the html layout head
892 892 def javascript_heads
893 893 tags = javascript_include_tag(:defaults)
894 894 unless User.current.pref.warn_on_leaving_unsaved == '0'
895 895 tags << "\n" + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
896 896 end
897 897 tags
898 898 end
899 899
900 900 def favicon
901 901 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
902 902 end
903
904 def robot_exclusion_tag
905 '<meta name="robots" content="noindex,follow,noarchive" />'
906 end
903 907
904 908 # Returns true if arg is expected in the API response
905 909 def include_in_api_response?(arg)
906 910 unless @included_in_api_response
907 911 param = params[:include]
908 912 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
909 913 @included_in_api_response.collect!(&:strip)
910 914 end
911 915 @included_in_api_response.include?(arg.to_s)
912 916 end
913 917
914 918 # Returns options or nil if nometa param or X-Redmine-Nometa header
915 919 # was set in the request
916 920 def api_meta(options)
917 921 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
918 922 # compatibility mode for activeresource clients that raise
919 923 # an error when unserializing an array with attributes
920 924 nil
921 925 else
922 926 options
923 927 end
924 928 end
925 929
926 930 private
927 931
928 932 def wiki_helper
929 933 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
930 934 extend helper
931 935 return self
932 936 end
933 937
934 938 def link_to_content_update(text, url_params = {}, html_options = {})
935 939 link_to(text, url_params, html_options)
936 940 end
937 941 end
@@ -1,76 +1,79
1 1 <h2><%= @copy ? l(:button_copy) : l(:button_move) %></h2>
2 2
3 3 <ul>
4 4 <% @issues.each do |issue| -%>
5 5 <li><%= link_to_issue issue %></li>
6 6 <% end -%>
7 7 </ul>
8 8
9 9 <% form_tag({:action => 'create'}, :id => 'move_form') do %>
10 10 <%= @issues.collect {|i| hidden_field_tag('ids[]', i.id)}.join %>
11 11
12 12 <div class="box tabular">
13 13 <fieldset class="attributes">
14 14 <legend><%= l(:label_change_properties) %></legend>
15 15
16 16 <div class="splitcontentleft">
17 17 <p><label for="new_project_id"><%=l(:field_project)%>:</label>
18 18 <%= select_tag "new_project_id",
19 19 project_tree_options_for_select(@allowed_projects, :selected => @target_project),
20 20 :onchange => remote_function(:url => { :action => 'new' },
21 21 :method => :get,
22 22 :update => 'content',
23 23 :with => "Form.serialize('move_form')") %></p>
24 24
25 25 <p><label for="new_tracker_id"><%=l(:field_tracker)%>:</label>
26 26 <%= select_tag "new_tracker_id", "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@trackers, "id", "name") %></p>
27 27
28 28 <p>
29 29 <label><%= l(:field_status) %></label>
30 30 <%= select_tag('status_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@available_statuses, :id, :name)) %>
31 31 </p>
32 32
33 33 <p>
34 34 <label><%= l(:field_priority) %></label>
35 35 <%= select_tag('priority_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(IssuePriority.all, :id, :name)) %>
36 36 </p>
37 37
38 38 <p>
39 39 <label><%= l(:field_assigned_to) %></label>
40 40 <%= select_tag('assigned_to_id', content_tag('option', l(:label_no_change_option), :value => '') +
41 41 content_tag('option', l(:label_nobody), :value => 'none') +
42 42 options_from_collection_for_select(@target_project.assignable_users, :id, :name)) %>
43 43 </p>
44 44 </div>
45 45
46 46 <div class="splitcontentright">
47 47 <p>
48 48 <label><%= l(:field_start_date) %></label>
49 49 <%= text_field_tag 'start_date', '', :size => 10 %><%= calendar_for('start_date') %>
50 50 </p>
51 51
52 52 <p>
53 53 <label><%= l(:field_due_date) %></label>
54 54 <%= text_field_tag 'due_date', '', :size => 10 %><%= calendar_for('due_date') %>
55 55 </p>
56 56 </div>
57 57
58 58 </fieldset>
59 59
60 60 <fieldset><legend><%= l(:field_notes) %></legend>
61 61 <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
62 62 <%= wikitoolbar_for 'notes' %>
63 63 </fieldset>
64 64
65 65 <%= call_hook(:view_issues_move_bottom, :issues => @issues, :target_project => @target_project, :copy => !!@copy) %>
66 66 </div>
67 67
68 68 <% if @copy %>
69 69 <%= hidden_field_tag("copy_options[copy]", "1") %>
70 70 <%= submit_tag l(:button_copy) %>
71 71 <%= submit_tag l(:button_copy_and_follow), :name => 'follow' %>
72 72 <% else %>
73 73 <%= submit_tag l(:button_move) %>
74 74 <%= submit_tag l(:button_move_and_follow), :name => 'follow' %>
75 75 <% end %>
76 76 <% end %>
77 <% content_for :header_tags do %>
78 <%= robot_exclusion_tag %>
79 <% end %>
@@ -1,3 +1,6
1 1 <h2><%=h "#{@issue.tracker.name} ##{@issue.id}" %></h2>
2 2
3 3 <%= render :partial => 'edit' %>
4 <% content_for :header_tags do %>
5 <%= robot_exclusion_tag %>
6 <% end %>
@@ -1,26 +1,27
1 1 <h2><%=l(:label_issue_new)%></h2>
2 2
3 3 <% labelled_tabular_form_for :issue, @issue, :url => {:controller => 'issues', :action => 'create', :project_id => @project},
4 4 :html => {:multipart => true, :id => 'issue-form', :class => 'tabular new-issue-form'} do |f| %>
5 5 <%= error_messages_for 'issue' %>
6 6 <div class="box">
7 7 <%= render :partial => 'issues/form', :locals => {:f => f} %>
8 8 </div>
9 9 <%= submit_tag l(:button_create) %>
10 10 <%= submit_tag l(:button_create_and_continue), :name => 'continue' %>
11 11 <%= link_to_remote l(:label_preview),
12 12 { :url => preview_issue_path(:project_id => @project),
13 13 :method => 'post',
14 14 :update => 'preview',
15 15 :with => "Form.serialize('issue-form')",
16 16 :complete => "Element.scrollTo('preview')"
17 17 }, :accesskey => accesskey(:preview) %>
18 18
19 19 <%= javascript_tag "Form.Element.focus('issue_subject');" %>
20 20 <% end %>
21 21
22 22 <div id="preview" class="wiki"></div>
23 23
24 24 <% content_for :header_tags do %>
25 25 <%= stylesheet_link_tag 'scm' %>
26 <%= robot_exclusion_tag %>
26 27 <% end %>
@@ -1,28 +1,29
1 1 <h2><%=h @page.pretty_title %></h2>
2 2
3 3 <% form_for :content, @content, :url => {:action => 'update', :id => @page.title}, :html => {:method => :put, :multipart => true, :id => 'wiki_form'} do |f| %>
4 4 <%= f.hidden_field :version %>
5 5 <%= error_messages_for 'content' %>
6 6
7 7 <p><%= f.text_area :text, :cols => 100, :rows => 25, :class => 'wiki-edit', :accesskey => accesskey(:edit) %></p>
8 8 <p><label><%= l(:field_comments) %></label><br /><%= f.text_field :comments, :size => 120 %></p>
9 9 <p><label><%=l(:label_attachment_plural)%></label><br /><%= render :partial => 'attachments/form' %></p>
10 10
11 11 <p><%= submit_tag l(:button_save) %>
12 12 <%= link_to_remote l(:label_preview),
13 13 { :url => { :controller => 'wiki', :action => 'preview', :project_id => @project, :id => @page.title },
14 14 :method => :post,
15 15 :update => 'preview',
16 16 :with => "Form.serialize('wiki_form')",
17 17 :complete => "Element.scrollTo('preview')"
18 18 }, :accesskey => accesskey(:preview) %></p>
19 19 <%= wikitoolbar_for 'content_text' %>
20 20 <% end %>
21 21
22 22 <div id="preview" class="wiki"></div>
23 23
24 24 <% content_for :header_tags do %>
25 25 <%= stylesheet_link_tag 'scm' %>
26 <%= robot_exclusion_tag %>
26 27 <% end %>
27 28
28 29 <% html_title @page.pretty_title %>
General Comments 0
You need to be logged in to leave comments. Login now