##// END OF EJS Templates
Template error when user's timezone isn't set and UTC timestamps are used (#1889)....
Jean-Philippe Lang -
r1799:8d6b32645c7b
parent child
Show More
@@ -1,558 +1,558
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'coderay'
18 require 'coderay'
19 require 'coderay/helpers/file_type'
19 require 'coderay/helpers/file_type'
20
20
21 module ApplicationHelper
21 module ApplicationHelper
22 include Redmine::WikiFormatting::Macros::Definitions
22 include Redmine::WikiFormatting::Macros::Definitions
23
23
24 def current_role
24 def current_role
25 @current_role ||= User.current.role_for_project(@project)
25 @current_role ||= User.current.role_for_project(@project)
26 end
26 end
27
27
28 # Return true if user is authorized for controller/action, otherwise false
28 # Return true if user is authorized for controller/action, otherwise false
29 def authorize_for(controller, action)
29 def authorize_for(controller, action)
30 User.current.allowed_to?({:controller => controller, :action => action}, @project)
30 User.current.allowed_to?({:controller => controller, :action => action}, @project)
31 end
31 end
32
32
33 # Display a link if user is authorized
33 # Display a link if user is authorized
34 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
34 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
35 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
35 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
36 end
36 end
37
37
38 # Display a link to remote if user is authorized
38 # Display a link to remote if user is authorized
39 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
39 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
40 url = options[:url] || {}
40 url = options[:url] || {}
41 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
41 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
42 end
42 end
43
43
44 # Display a link to user's account page
44 # Display a link to user's account page
45 def link_to_user(user)
45 def link_to_user(user)
46 user ? link_to(user, :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
46 user ? link_to(user, :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
47 end
47 end
48
48
49 def link_to_issue(issue, options={})
49 def link_to_issue(issue, options={})
50 options[:class] ||= ''
50 options[:class] ||= ''
51 options[:class] << ' issue'
51 options[:class] << ' issue'
52 options[:class] << ' closed' if issue.closed?
52 options[:class] << ' closed' if issue.closed?
53 link_to "#{issue.tracker.name} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, options
53 link_to "#{issue.tracker.name} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, options
54 end
54 end
55
55
56 # Generates a link to an attachment.
56 # Generates a link to an attachment.
57 # Options:
57 # Options:
58 # * :text - Link text (default to attachment filename)
58 # * :text - Link text (default to attachment filename)
59 # * :download - Force download (default: false)
59 # * :download - Force download (default: false)
60 def link_to_attachment(attachment, options={})
60 def link_to_attachment(attachment, options={})
61 text = options.delete(:text) || attachment.filename
61 text = options.delete(:text) || attachment.filename
62 action = options.delete(:download) ? 'download' : 'show'
62 action = options.delete(:download) ? 'download' : 'show'
63
63
64 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
64 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
65 end
65 end
66
66
67 def toggle_link(name, id, options={})
67 def toggle_link(name, id, options={})
68 onclick = "Element.toggle('#{id}'); "
68 onclick = "Element.toggle('#{id}'); "
69 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
69 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
70 onclick << "return false;"
70 onclick << "return false;"
71 link_to(name, "#", :onclick => onclick)
71 link_to(name, "#", :onclick => onclick)
72 end
72 end
73
73
74 def image_to_function(name, function, html_options = {})
74 def image_to_function(name, function, html_options = {})
75 html_options.symbolize_keys!
75 html_options.symbolize_keys!
76 tag(:input, html_options.merge({
76 tag(:input, html_options.merge({
77 :type => "image", :src => image_path(name),
77 :type => "image", :src => image_path(name),
78 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
78 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
79 }))
79 }))
80 end
80 end
81
81
82 def prompt_to_remote(name, text, param, url, html_options = {})
82 def prompt_to_remote(name, text, param, url, html_options = {})
83 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
83 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
84 link_to name, {}, html_options
84 link_to name, {}, html_options
85 end
85 end
86
86
87 def format_date(date)
87 def format_date(date)
88 return nil unless date
88 return nil unless date
89 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
89 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
90 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
90 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
91 date.strftime(@date_format)
91 date.strftime(@date_format)
92 end
92 end
93
93
94 def format_time(time, include_date = true)
94 def format_time(time, include_date = true)
95 return nil unless time
95 return nil unless time
96 time = time.to_time if time.is_a?(String)
96 time = time.to_time if time.is_a?(String)
97 zone = User.current.time_zone
97 zone = User.current.time_zone
98 local = zone ? time.in_time_zone(zone) : (time.utc? ? time.utc_to_local : time)
98 local = zone ? time.in_time_zone(zone) : (time.utc? ? time.localtime : time)
99 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
99 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
100 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
100 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
101 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
101 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
102 end
102 end
103
103
104 # Truncates and returns the string as a single line
104 # Truncates and returns the string as a single line
105 def truncate_single_line(string, *args)
105 def truncate_single_line(string, *args)
106 truncate(string, *args).gsub(%r{[\r\n]+}m, ' ')
106 truncate(string, *args).gsub(%r{[\r\n]+}m, ' ')
107 end
107 end
108
108
109 def html_hours(text)
109 def html_hours(text)
110 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
110 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
111 end
111 end
112
112
113 def authoring(created, author)
113 def authoring(created, author)
114 time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
114 time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
115 author_tag = (author.is_a?(User) && !author.anonymous?) ? link_to(h(author), :controller => 'account', :action => 'show', :id => author) : h(author || 'Anonymous')
115 author_tag = (author.is_a?(User) && !author.anonymous?) ? link_to(h(author), :controller => 'account', :action => 'show', :id => author) : h(author || 'Anonymous')
116 l(:label_added_time_by, author_tag, time_tag)
116 l(:label_added_time_by, author_tag, time_tag)
117 end
117 end
118
118
119 def l_or_humanize(s)
119 def l_or_humanize(s)
120 l_has_string?("label_#{s}".to_sym) ? l("label_#{s}".to_sym) : s.to_s.humanize
120 l_has_string?("label_#{s}".to_sym) ? l("label_#{s}".to_sym) : s.to_s.humanize
121 end
121 end
122
122
123 def day_name(day)
123 def day_name(day)
124 l(:general_day_names).split(',')[day-1]
124 l(:general_day_names).split(',')[day-1]
125 end
125 end
126
126
127 def month_name(month)
127 def month_name(month)
128 l(:actionview_datehelper_select_month_names).split(',')[month-1]
128 l(:actionview_datehelper_select_month_names).split(',')[month-1]
129 end
129 end
130
130
131 def syntax_highlight(name, content)
131 def syntax_highlight(name, content)
132 type = CodeRay::FileType[name]
132 type = CodeRay::FileType[name]
133 type ? CodeRay.scan(content, type).html : h(content)
133 type ? CodeRay.scan(content, type).html : h(content)
134 end
134 end
135
135
136 def to_path_param(path)
136 def to_path_param(path)
137 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
137 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
138 end
138 end
139
139
140 def pagination_links_full(paginator, count=nil, options={})
140 def pagination_links_full(paginator, count=nil, options={})
141 page_param = options.delete(:page_param) || :page
141 page_param = options.delete(:page_param) || :page
142 url_param = params.dup
142 url_param = params.dup
143 # don't reuse params if filters are present
143 # don't reuse params if filters are present
144 url_param.clear if url_param.has_key?(:set_filter)
144 url_param.clear if url_param.has_key?(:set_filter)
145
145
146 html = ''
146 html = ''
147 html << link_to_remote(('&#171; ' + l(:label_previous)),
147 html << link_to_remote(('&#171; ' + l(:label_previous)),
148 {:update => 'content',
148 {:update => 'content',
149 :url => url_param.merge(page_param => paginator.current.previous),
149 :url => url_param.merge(page_param => paginator.current.previous),
150 :complete => 'window.scrollTo(0,0)'},
150 :complete => 'window.scrollTo(0,0)'},
151 {:href => url_for(:params => url_param.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
151 {:href => url_for(:params => url_param.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
152
152
153 html << (pagination_links_each(paginator, options) do |n|
153 html << (pagination_links_each(paginator, options) do |n|
154 link_to_remote(n.to_s,
154 link_to_remote(n.to_s,
155 {:url => {:params => url_param.merge(page_param => n)},
155 {:url => {:params => url_param.merge(page_param => n)},
156 :update => 'content',
156 :update => 'content',
157 :complete => 'window.scrollTo(0,0)'},
157 :complete => 'window.scrollTo(0,0)'},
158 {:href => url_for(:params => url_param.merge(page_param => n))})
158 {:href => url_for(:params => url_param.merge(page_param => n))})
159 end || '')
159 end || '')
160
160
161 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
161 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
162 {:update => 'content',
162 {:update => 'content',
163 :url => url_param.merge(page_param => paginator.current.next),
163 :url => url_param.merge(page_param => paginator.current.next),
164 :complete => 'window.scrollTo(0,0)'},
164 :complete => 'window.scrollTo(0,0)'},
165 {:href => url_for(:params => url_param.merge(page_param => paginator.current.next))}) if paginator.current.next
165 {:href => url_for(:params => url_param.merge(page_param => paginator.current.next))}) if paginator.current.next
166
166
167 unless count.nil?
167 unless count.nil?
168 html << [" (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})", per_page_links(paginator.items_per_page)].compact.join(' | ')
168 html << [" (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})", per_page_links(paginator.items_per_page)].compact.join(' | ')
169 end
169 end
170
170
171 html
171 html
172 end
172 end
173
173
174 def per_page_links(selected=nil)
174 def per_page_links(selected=nil)
175 url_param = params.dup
175 url_param = params.dup
176 url_param.clear if url_param.has_key?(:set_filter)
176 url_param.clear if url_param.has_key?(:set_filter)
177
177
178 links = Setting.per_page_options_array.collect do |n|
178 links = Setting.per_page_options_array.collect do |n|
179 n == selected ? n : link_to_remote(n, {:update => "content", :url => params.dup.merge(:per_page => n)},
179 n == selected ? n : link_to_remote(n, {:update => "content", :url => params.dup.merge(:per_page => n)},
180 {:href => url_for(url_param.merge(:per_page => n))})
180 {:href => url_for(url_param.merge(:per_page => n))})
181 end
181 end
182 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
182 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
183 end
183 end
184
184
185 def breadcrumb(*args)
185 def breadcrumb(*args)
186 elements = args.flatten
186 elements = args.flatten
187 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
187 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
188 end
188 end
189
189
190 def html_title(*args)
190 def html_title(*args)
191 if args.empty?
191 if args.empty?
192 title = []
192 title = []
193 title << @project.name if @project
193 title << @project.name if @project
194 title += @html_title if @html_title
194 title += @html_title if @html_title
195 title << Setting.app_title
195 title << Setting.app_title
196 title.compact.join(' - ')
196 title.compact.join(' - ')
197 else
197 else
198 @html_title ||= []
198 @html_title ||= []
199 @html_title += args
199 @html_title += args
200 end
200 end
201 end
201 end
202
202
203 def accesskey(s)
203 def accesskey(s)
204 Redmine::AccessKeys.key_for s
204 Redmine::AccessKeys.key_for s
205 end
205 end
206
206
207 # Formats text according to system settings.
207 # Formats text according to system settings.
208 # 2 ways to call this method:
208 # 2 ways to call this method:
209 # * with a String: textilizable(text, options)
209 # * with a String: textilizable(text, options)
210 # * with an object and one of its attribute: textilizable(issue, :description, options)
210 # * with an object and one of its attribute: textilizable(issue, :description, options)
211 def textilizable(*args)
211 def textilizable(*args)
212 options = args.last.is_a?(Hash) ? args.pop : {}
212 options = args.last.is_a?(Hash) ? args.pop : {}
213 case args.size
213 case args.size
214 when 1
214 when 1
215 obj = options[:object]
215 obj = options[:object]
216 text = args.shift
216 text = args.shift
217 when 2
217 when 2
218 obj = args.shift
218 obj = args.shift
219 text = obj.send(args.shift).to_s
219 text = obj.send(args.shift).to_s
220 else
220 else
221 raise ArgumentError, 'invalid arguments to textilizable'
221 raise ArgumentError, 'invalid arguments to textilizable'
222 end
222 end
223 return '' if text.blank?
223 return '' if text.blank?
224
224
225 only_path = options.delete(:only_path) == false ? false : true
225 only_path = options.delete(:only_path) == false ? false : true
226
226
227 # when using an image link, try to use an attachment, if possible
227 # when using an image link, try to use an attachment, if possible
228 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
228 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
229
229
230 if attachments
230 if attachments
231 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
231 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
232 style = $1
232 style = $1
233 filename = $6
233 filename = $6
234 rf = Regexp.new(filename, Regexp::IGNORECASE)
234 rf = Regexp.new(filename, Regexp::IGNORECASE)
235 # search for the picture in attachments
235 # search for the picture in attachments
236 if found = attachments.detect { |att| att.filename =~ rf }
236 if found = attachments.detect { |att| att.filename =~ rf }
237 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
237 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
238 desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
238 desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
239 alt = desc.blank? ? nil : "(#{desc})"
239 alt = desc.blank? ? nil : "(#{desc})"
240 "!#{style}#{image_url}#{alt}!"
240 "!#{style}#{image_url}#{alt}!"
241 else
241 else
242 "!#{style}#{filename}!"
242 "!#{style}#{filename}!"
243 end
243 end
244 end
244 end
245 end
245 end
246
246
247 text = (Setting.text_formatting == 'textile') ?
247 text = (Setting.text_formatting == 'textile') ?
248 Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
248 Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
249 simple_format(auto_link(h(text)))
249 simple_format(auto_link(h(text)))
250
250
251 # different methods for formatting wiki links
251 # different methods for formatting wiki links
252 case options[:wiki_links]
252 case options[:wiki_links]
253 when :local
253 when :local
254 # used for local links to html files
254 # used for local links to html files
255 format_wiki_link = Proc.new {|project, title, anchor| "#{title}.html" }
255 format_wiki_link = Proc.new {|project, title, anchor| "#{title}.html" }
256 when :anchor
256 when :anchor
257 # used for single-file wiki export
257 # used for single-file wiki export
258 format_wiki_link = Proc.new {|project, title, anchor| "##{title}" }
258 format_wiki_link = Proc.new {|project, title, anchor| "##{title}" }
259 else
259 else
260 format_wiki_link = Proc.new {|project, title, anchor| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title, :anchor => anchor) }
260 format_wiki_link = Proc.new {|project, title, anchor| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title, :anchor => anchor) }
261 end
261 end
262
262
263 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
263 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
264
264
265 # Wiki links
265 # Wiki links
266 #
266 #
267 # Examples:
267 # Examples:
268 # [[mypage]]
268 # [[mypage]]
269 # [[mypage|mytext]]
269 # [[mypage|mytext]]
270 # wiki links can refer other project wikis, using project name or identifier:
270 # wiki links can refer other project wikis, using project name or identifier:
271 # [[project:]] -> wiki starting page
271 # [[project:]] -> wiki starting page
272 # [[project:|mytext]]
272 # [[project:|mytext]]
273 # [[project:mypage]]
273 # [[project:mypage]]
274 # [[project:mypage|mytext]]
274 # [[project:mypage|mytext]]
275 text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
275 text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
276 link_project = project
276 link_project = project
277 esc, all, page, title = $1, $2, $3, $5
277 esc, all, page, title = $1, $2, $3, $5
278 if esc.nil?
278 if esc.nil?
279 if page =~ /^([^\:]+)\:(.*)$/
279 if page =~ /^([^\:]+)\:(.*)$/
280 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
280 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
281 page = $2
281 page = $2
282 title ||= $1 if page.blank?
282 title ||= $1 if page.blank?
283 end
283 end
284
284
285 if link_project && link_project.wiki
285 if link_project && link_project.wiki
286 # extract anchor
286 # extract anchor
287 anchor = nil
287 anchor = nil
288 if page =~ /^(.+?)\#(.+)$/
288 if page =~ /^(.+?)\#(.+)$/
289 page, anchor = $1, $2
289 page, anchor = $1, $2
290 end
290 end
291 # check if page exists
291 # check if page exists
292 wiki_page = link_project.wiki.find_page(page)
292 wiki_page = link_project.wiki.find_page(page)
293 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page), anchor),
293 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page), anchor),
294 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
294 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
295 else
295 else
296 # project or wiki doesn't exist
296 # project or wiki doesn't exist
297 title || page
297 title || page
298 end
298 end
299 else
299 else
300 all
300 all
301 end
301 end
302 end
302 end
303
303
304 # Redmine links
304 # Redmine links
305 #
305 #
306 # Examples:
306 # Examples:
307 # Issues:
307 # Issues:
308 # #52 -> Link to issue #52
308 # #52 -> Link to issue #52
309 # Changesets:
309 # Changesets:
310 # r52 -> Link to revision 52
310 # r52 -> Link to revision 52
311 # commit:a85130f -> Link to scmid starting with a85130f
311 # commit:a85130f -> Link to scmid starting with a85130f
312 # Documents:
312 # Documents:
313 # document#17 -> Link to document with id 17
313 # document#17 -> Link to document with id 17
314 # document:Greetings -> Link to the document with title "Greetings"
314 # document:Greetings -> Link to the document with title "Greetings"
315 # document:"Some document" -> Link to the document with title "Some document"
315 # document:"Some document" -> Link to the document with title "Some document"
316 # Versions:
316 # Versions:
317 # version#3 -> Link to version with id 3
317 # version#3 -> Link to version with id 3
318 # version:1.0.0 -> Link to version named "1.0.0"
318 # version:1.0.0 -> Link to version named "1.0.0"
319 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
319 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
320 # Attachments:
320 # Attachments:
321 # attachment:file.zip -> Link to the attachment of the current object named file.zip
321 # attachment:file.zip -> Link to the attachment of the current object named file.zip
322 # Source files:
322 # Source files:
323 # source:some/file -> Link to the file located at /some/file in the project's repository
323 # source:some/file -> Link to the file located at /some/file in the project's repository
324 # source:some/file@52 -> Link to the file's revision 52
324 # source:some/file@52 -> Link to the file's revision 52
325 # source:some/file#L120 -> Link to line 120 of the file
325 # source:some/file#L120 -> Link to line 120 of the file
326 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
326 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
327 # export:some/file -> Force the download of the file
327 # export:some/file -> Force the download of the file
328 # Forum messages:
328 # Forum messages:
329 # message#1218 -> Link to message with id 1218
329 # message#1218 -> Link to message with id 1218
330 text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|\s|<|$)}) do |m|
330 text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|\s|<|$)}) do |m|
331 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
331 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
332 link = nil
332 link = nil
333 if esc.nil?
333 if esc.nil?
334 if prefix.nil? && sep == 'r'
334 if prefix.nil? && sep == 'r'
335 if project && (changeset = project.changesets.find_by_revision(oid))
335 if project && (changeset = project.changesets.find_by_revision(oid))
336 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
336 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
337 :class => 'changeset',
337 :class => 'changeset',
338 :title => truncate_single_line(changeset.comments, 100))
338 :title => truncate_single_line(changeset.comments, 100))
339 end
339 end
340 elsif sep == '#'
340 elsif sep == '#'
341 oid = oid.to_i
341 oid = oid.to_i
342 case prefix
342 case prefix
343 when nil
343 when nil
344 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
344 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
345 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
345 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
346 :class => (issue.closed? ? 'issue closed' : 'issue'),
346 :class => (issue.closed? ? 'issue closed' : 'issue'),
347 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
347 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
348 link = content_tag('del', link) if issue.closed?
348 link = content_tag('del', link) if issue.closed?
349 end
349 end
350 when 'document'
350 when 'document'
351 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
351 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
352 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
352 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
353 :class => 'document'
353 :class => 'document'
354 end
354 end
355 when 'version'
355 when 'version'
356 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
356 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
357 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
357 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
358 :class => 'version'
358 :class => 'version'
359 end
359 end
360 when 'message'
360 when 'message'
361 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
361 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
362 link = link_to h(truncate(message.subject, 60)), {:only_path => only_path,
362 link = link_to h(truncate(message.subject, 60)), {:only_path => only_path,
363 :controller => 'messages',
363 :controller => 'messages',
364 :action => 'show',
364 :action => 'show',
365 :board_id => message.board,
365 :board_id => message.board,
366 :id => message.root,
366 :id => message.root,
367 :anchor => (message.parent ? "message-#{message.id}" : nil)},
367 :anchor => (message.parent ? "message-#{message.id}" : nil)},
368 :class => 'message'
368 :class => 'message'
369 end
369 end
370 end
370 end
371 elsif sep == ':'
371 elsif sep == ':'
372 # removes the double quotes if any
372 # removes the double quotes if any
373 name = oid.gsub(%r{^"(.*)"$}, "\\1")
373 name = oid.gsub(%r{^"(.*)"$}, "\\1")
374 case prefix
374 case prefix
375 when 'document'
375 when 'document'
376 if project && document = project.documents.find_by_title(name)
376 if project && document = project.documents.find_by_title(name)
377 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
377 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
378 :class => 'document'
378 :class => 'document'
379 end
379 end
380 when 'version'
380 when 'version'
381 if project && version = project.versions.find_by_name(name)
381 if project && version = project.versions.find_by_name(name)
382 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
382 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
383 :class => 'version'
383 :class => 'version'
384 end
384 end
385 when 'commit'
385 when 'commit'
386 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
386 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
387 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
387 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
388 :class => 'changeset',
388 :class => 'changeset',
389 :title => truncate_single_line(changeset.comments, 100)
389 :title => truncate_single_line(changeset.comments, 100)
390 end
390 end
391 when 'source', 'export'
391 when 'source', 'export'
392 if project && project.repository
392 if project && project.repository
393 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
393 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
394 path, rev, anchor = $1, $3, $5
394 path, rev, anchor = $1, $3, $5
395 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
395 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
396 :path => to_path_param(path),
396 :path => to_path_param(path),
397 :rev => rev,
397 :rev => rev,
398 :anchor => anchor,
398 :anchor => anchor,
399 :format => (prefix == 'export' ? 'raw' : nil)},
399 :format => (prefix == 'export' ? 'raw' : nil)},
400 :class => (prefix == 'export' ? 'source download' : 'source')
400 :class => (prefix == 'export' ? 'source download' : 'source')
401 end
401 end
402 when 'attachment'
402 when 'attachment'
403 if attachments && attachment = attachments.detect {|a| a.filename == name }
403 if attachments && attachment = attachments.detect {|a| a.filename == name }
404 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
404 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
405 :class => 'attachment'
405 :class => 'attachment'
406 end
406 end
407 end
407 end
408 end
408 end
409 end
409 end
410 leading + (link || "#{prefix}#{sep}#{oid}")
410 leading + (link || "#{prefix}#{sep}#{oid}")
411 end
411 end
412
412
413 text
413 text
414 end
414 end
415
415
416 # Same as Rails' simple_format helper without using paragraphs
416 # Same as Rails' simple_format helper without using paragraphs
417 def simple_format_without_paragraph(text)
417 def simple_format_without_paragraph(text)
418 text.to_s.
418 text.to_s.
419 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
419 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
420 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
420 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
421 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
421 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
422 end
422 end
423
423
424 def error_messages_for(object_name, options = {})
424 def error_messages_for(object_name, options = {})
425 options = options.symbolize_keys
425 options = options.symbolize_keys
426 object = instance_variable_get("@#{object_name}")
426 object = instance_variable_get("@#{object_name}")
427 if object && !object.errors.empty?
427 if object && !object.errors.empty?
428 # build full_messages here with controller current language
428 # build full_messages here with controller current language
429 full_messages = []
429 full_messages = []
430 object.errors.each do |attr, msg|
430 object.errors.each do |attr, msg|
431 next if msg.nil?
431 next if msg.nil?
432 msg = msg.first if msg.is_a? Array
432 msg = msg.first if msg.is_a? Array
433 if attr == "base"
433 if attr == "base"
434 full_messages << l(msg)
434 full_messages << l(msg)
435 else
435 else
436 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
436 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
437 end
437 end
438 end
438 end
439 # retrieve custom values error messages
439 # retrieve custom values error messages
440 if object.errors[:custom_values]
440 if object.errors[:custom_values]
441 object.custom_values.each do |v|
441 object.custom_values.each do |v|
442 v.errors.each do |attr, msg|
442 v.errors.each do |attr, msg|
443 next if msg.nil?
443 next if msg.nil?
444 msg = msg.first if msg.is_a? Array
444 msg = msg.first if msg.is_a? Array
445 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
445 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
446 end
446 end
447 end
447 end
448 end
448 end
449 content_tag("div",
449 content_tag("div",
450 content_tag(
450 content_tag(
451 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
451 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
452 ) +
452 ) +
453 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
453 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
454 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
454 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
455 )
455 )
456 else
456 else
457 ""
457 ""
458 end
458 end
459 end
459 end
460
460
461 def lang_options_for_select(blank=true)
461 def lang_options_for_select(blank=true)
462 (blank ? [["(auto)", ""]] : []) +
462 (blank ? [["(auto)", ""]] : []) +
463 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
463 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
464 end
464 end
465
465
466 def label_tag_for(name, option_tags = nil, options = {})
466 def label_tag_for(name, option_tags = nil, options = {})
467 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
467 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
468 content_tag("label", label_text)
468 content_tag("label", label_text)
469 end
469 end
470
470
471 def labelled_tabular_form_for(name, object, options, &proc)
471 def labelled_tabular_form_for(name, object, options, &proc)
472 options[:html] ||= {}
472 options[:html] ||= {}
473 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
473 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
474 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
474 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
475 end
475 end
476
476
477 def back_url_hidden_field_tag
477 def back_url_hidden_field_tag
478 back_url = params[:back_url] || request.env['HTTP_REFERER']
478 back_url = params[:back_url] || request.env['HTTP_REFERER']
479 hidden_field_tag('back_url', back_url) unless back_url.blank?
479 hidden_field_tag('back_url', back_url) unless back_url.blank?
480 end
480 end
481
481
482 def check_all_links(form_name)
482 def check_all_links(form_name)
483 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
483 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
484 " | " +
484 " | " +
485 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
485 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
486 end
486 end
487
487
488 def progress_bar(pcts, options={})
488 def progress_bar(pcts, options={})
489 pcts = [pcts, pcts] unless pcts.is_a?(Array)
489 pcts = [pcts, pcts] unless pcts.is_a?(Array)
490 pcts[1] = pcts[1] - pcts[0]
490 pcts[1] = pcts[1] - pcts[0]
491 pcts << (100 - pcts[1] - pcts[0])
491 pcts << (100 - pcts[1] - pcts[0])
492 width = options[:width] || '100px;'
492 width = options[:width] || '100px;'
493 legend = options[:legend] || ''
493 legend = options[:legend] || ''
494 content_tag('table',
494 content_tag('table',
495 content_tag('tr',
495 content_tag('tr',
496 (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
496 (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
497 (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
497 (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
498 (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
498 (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
499 ), :class => 'progress', :style => "width: #{width};") +
499 ), :class => 'progress', :style => "width: #{width};") +
500 content_tag('p', legend, :class => 'pourcent')
500 content_tag('p', legend, :class => 'pourcent')
501 end
501 end
502
502
503 def context_menu_link(name, url, options={})
503 def context_menu_link(name, url, options={})
504 options[:class] ||= ''
504 options[:class] ||= ''
505 if options.delete(:selected)
505 if options.delete(:selected)
506 options[:class] << ' icon-checked disabled'
506 options[:class] << ' icon-checked disabled'
507 options[:disabled] = true
507 options[:disabled] = true
508 end
508 end
509 if options.delete(:disabled)
509 if options.delete(:disabled)
510 options.delete(:method)
510 options.delete(:method)
511 options.delete(:confirm)
511 options.delete(:confirm)
512 options.delete(:onclick)
512 options.delete(:onclick)
513 options[:class] << ' disabled'
513 options[:class] << ' disabled'
514 url = '#'
514 url = '#'
515 end
515 end
516 link_to name, url, options
516 link_to name, url, options
517 end
517 end
518
518
519 def calendar_for(field_id)
519 def calendar_for(field_id)
520 include_calendar_headers_tags
520 include_calendar_headers_tags
521 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
521 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
522 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
522 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
523 end
523 end
524
524
525 def include_calendar_headers_tags
525 def include_calendar_headers_tags
526 unless @calendar_headers_tags_included
526 unless @calendar_headers_tags_included
527 @calendar_headers_tags_included = true
527 @calendar_headers_tags_included = true
528 content_for :header_tags do
528 content_for :header_tags do
529 javascript_include_tag('calendar/calendar') +
529 javascript_include_tag('calendar/calendar') +
530 javascript_include_tag("calendar/lang/calendar-#{current_language}.js") +
530 javascript_include_tag("calendar/lang/calendar-#{current_language}.js") +
531 javascript_include_tag('calendar/calendar-setup') +
531 javascript_include_tag('calendar/calendar-setup') +
532 stylesheet_link_tag('calendar')
532 stylesheet_link_tag('calendar')
533 end
533 end
534 end
534 end
535 end
535 end
536
536
537 def wikitoolbar_for(field_id)
537 def wikitoolbar_for(field_id)
538 return '' unless Setting.text_formatting == 'textile'
538 return '' unless Setting.text_formatting == 'textile'
539
539
540 help_link = l(:setting_text_formatting) + ': ' +
540 help_link = l(:setting_text_formatting) + ': ' +
541 link_to(l(:label_help), compute_public_path('wiki_syntax', 'help', 'html'),
541 link_to(l(:label_help), compute_public_path('wiki_syntax', 'help', 'html'),
542 :onclick => "window.open(\"#{ compute_public_path('wiki_syntax', 'help', 'html') }\", \"\", \"resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes\"); return false;")
542 :onclick => "window.open(\"#{ compute_public_path('wiki_syntax', 'help', 'html') }\", \"\", \"resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes\"); return false;")
543
543
544 javascript_include_tag('jstoolbar/jstoolbar') +
544 javascript_include_tag('jstoolbar/jstoolbar') +
545 javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language}") +
545 javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language}") +
546 javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.setHelpLink('#{help_link}'); toolbar.draw();")
546 javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.setHelpLink('#{help_link}'); toolbar.draw();")
547 end
547 end
548
548
549 def content_for(name, content = nil, &block)
549 def content_for(name, content = nil, &block)
550 @has_content ||= {}
550 @has_content ||= {}
551 @has_content[name] = true
551 @has_content[name] = true
552 super(name, content, &block)
552 super(name, content, &block)
553 end
553 end
554
554
555 def has_content?(name)
555 def has_content?(name)
556 (@has_content && @has_content[name]) || false
556 (@has_content && @has_content[name]) || false
557 end
557 end
558 end
558 end
@@ -1,294 +1,294
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require "digest/sha1"
18 require "digest/sha1"
19
19
20 class User < ActiveRecord::Base
20 class User < ActiveRecord::Base
21
21
22 # Account statuses
22 # Account statuses
23 STATUS_ANONYMOUS = 0
23 STATUS_ANONYMOUS = 0
24 STATUS_ACTIVE = 1
24 STATUS_ACTIVE = 1
25 STATUS_REGISTERED = 2
25 STATUS_REGISTERED = 2
26 STATUS_LOCKED = 3
26 STATUS_LOCKED = 3
27
27
28 USER_FORMATS = {
28 USER_FORMATS = {
29 :firstname_lastname => '#{firstname} #{lastname}',
29 :firstname_lastname => '#{firstname} #{lastname}',
30 :firstname => '#{firstname}',
30 :firstname => '#{firstname}',
31 :lastname_firstname => '#{lastname} #{firstname}',
31 :lastname_firstname => '#{lastname} #{firstname}',
32 :lastname_coma_firstname => '#{lastname}, #{firstname}',
32 :lastname_coma_firstname => '#{lastname}, #{firstname}',
33 :username => '#{login}'
33 :username => '#{login}'
34 }
34 }
35
35
36 has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name"
36 has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name"
37 has_many :members, :dependent => :delete_all
37 has_many :members, :dependent => :delete_all
38 has_many :projects, :through => :memberships
38 has_many :projects, :through => :memberships
39 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
39 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
40 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
40 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
41 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
41 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
42 belongs_to :auth_source
42 belongs_to :auth_source
43
43
44 acts_as_customizable
44 acts_as_customizable
45
45
46 attr_accessor :password, :password_confirmation
46 attr_accessor :password, :password_confirmation
47 attr_accessor :last_before_login_on
47 attr_accessor :last_before_login_on
48 # Prevents unauthorized assignments
48 # Prevents unauthorized assignments
49 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
49 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
50
50
51 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
51 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
52 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }
52 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }
53 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }
53 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }
54 # Login must contain lettres, numbers, underscores only
54 # Login must contain lettres, numbers, underscores only
55 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
55 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
56 validates_length_of :login, :maximum => 30
56 validates_length_of :login, :maximum => 30
57 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
57 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
58 validates_length_of :firstname, :lastname, :maximum => 30
58 validates_length_of :firstname, :lastname, :maximum => 30
59 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
59 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
60 validates_length_of :mail, :maximum => 60, :allow_nil => true
60 validates_length_of :mail, :maximum => 60, :allow_nil => true
61 validates_length_of :password, :minimum => 4, :allow_nil => true
61 validates_length_of :password, :minimum => 4, :allow_nil => true
62 validates_confirmation_of :password, :allow_nil => true
62 validates_confirmation_of :password, :allow_nil => true
63
63
64 def before_create
64 def before_create
65 self.mail_notification = false
65 self.mail_notification = false
66 true
66 true
67 end
67 end
68
68
69 def before_save
69 def before_save
70 # update hashed_password if password was set
70 # update hashed_password if password was set
71 self.hashed_password = User.hash_password(self.password) if self.password
71 self.hashed_password = User.hash_password(self.password) if self.password
72 end
72 end
73
73
74 def self.active
74 def self.active
75 with_scope :find => { :conditions => [ "status = ?", STATUS_ACTIVE ] } do
75 with_scope :find => { :conditions => [ "status = ?", STATUS_ACTIVE ] } do
76 yield
76 yield
77 end
77 end
78 end
78 end
79
79
80 def self.find_active(*args)
80 def self.find_active(*args)
81 active do
81 active do
82 find(*args)
82 find(*args)
83 end
83 end
84 end
84 end
85
85
86 # Returns the user that matches provided login and password, or nil
86 # Returns the user that matches provided login and password, or nil
87 def self.try_to_login(login, password)
87 def self.try_to_login(login, password)
88 # Make sure no one can sign in with an empty password
88 # Make sure no one can sign in with an empty password
89 return nil if password.to_s.empty?
89 return nil if password.to_s.empty?
90 user = find(:first, :conditions => ["login=?", login])
90 user = find(:first, :conditions => ["login=?", login])
91 if user
91 if user
92 # user is already in local database
92 # user is already in local database
93 return nil if !user.active?
93 return nil if !user.active?
94 if user.auth_source
94 if user.auth_source
95 # user has an external authentication method
95 # user has an external authentication method
96 return nil unless user.auth_source.authenticate(login, password)
96 return nil unless user.auth_source.authenticate(login, password)
97 else
97 else
98 # authentication with local password
98 # authentication with local password
99 return nil unless User.hash_password(password) == user.hashed_password
99 return nil unless User.hash_password(password) == user.hashed_password
100 end
100 end
101 else
101 else
102 # user is not yet registered, try to authenticate with available sources
102 # user is not yet registered, try to authenticate with available sources
103 attrs = AuthSource.authenticate(login, password)
103 attrs = AuthSource.authenticate(login, password)
104 if attrs
104 if attrs
105 user = new(*attrs)
105 user = new(*attrs)
106 user.login = login
106 user.login = login
107 user.language = Setting.default_language
107 user.language = Setting.default_language
108 if user.save
108 if user.save
109 user.reload
109 user.reload
110 logger.info("User '#{user.login}' created from the LDAP") if logger
110 logger.info("User '#{user.login}' created from the LDAP") if logger
111 end
111 end
112 end
112 end
113 end
113 end
114 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
114 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
115 user
115 user
116 rescue => text
116 rescue => text
117 raise text
117 raise text
118 end
118 end
119
119
120 # Return user's full name for display
120 # Return user's full name for display
121 def name(formatter = nil)
121 def name(formatter = nil)
122 f = USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
122 f = USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
123 eval '"' + f + '"'
123 eval '"' + f + '"'
124 end
124 end
125
125
126 def active?
126 def active?
127 self.status == STATUS_ACTIVE
127 self.status == STATUS_ACTIVE
128 end
128 end
129
129
130 def registered?
130 def registered?
131 self.status == STATUS_REGISTERED
131 self.status == STATUS_REGISTERED
132 end
132 end
133
133
134 def locked?
134 def locked?
135 self.status == STATUS_LOCKED
135 self.status == STATUS_LOCKED
136 end
136 end
137
137
138 def check_password?(clear_password)
138 def check_password?(clear_password)
139 User.hash_password(clear_password) == self.hashed_password
139 User.hash_password(clear_password) == self.hashed_password
140 end
140 end
141
141
142 def pref
142 def pref
143 self.preference ||= UserPreference.new(:user => self)
143 self.preference ||= UserPreference.new(:user => self)
144 end
144 end
145
145
146 def time_zone
146 def time_zone
147 self.pref.time_zone.nil? ? nil : TimeZone[self.pref.time_zone]
147 @time_zone ||= (self.pref.time_zone.blank? ? nil : TimeZone[self.pref.time_zone])
148 end
148 end
149
149
150 def wants_comments_in_reverse_order?
150 def wants_comments_in_reverse_order?
151 self.pref[:comments_sorting] == 'desc'
151 self.pref[:comments_sorting] == 'desc'
152 end
152 end
153
153
154 # Return user's RSS key (a 40 chars long string), used to access feeds
154 # Return user's RSS key (a 40 chars long string), used to access feeds
155 def rss_key
155 def rss_key
156 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
156 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
157 token.value
157 token.value
158 end
158 end
159
159
160 # Return an array of project ids for which the user has explicitly turned mail notifications on
160 # Return an array of project ids for which the user has explicitly turned mail notifications on
161 def notified_projects_ids
161 def notified_projects_ids
162 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
162 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
163 end
163 end
164
164
165 def notified_project_ids=(ids)
165 def notified_project_ids=(ids)
166 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
166 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
167 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
167 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
168 @notified_projects_ids = nil
168 @notified_projects_ids = nil
169 notified_projects_ids
169 notified_projects_ids
170 end
170 end
171
171
172 def self.find_by_rss_key(key)
172 def self.find_by_rss_key(key)
173 token = Token.find_by_value(key)
173 token = Token.find_by_value(key)
174 token && token.user.active? ? token.user : nil
174 token && token.user.active? ? token.user : nil
175 end
175 end
176
176
177 def self.find_by_autologin_key(key)
177 def self.find_by_autologin_key(key)
178 token = Token.find_by_action_and_value('autologin', key)
178 token = Token.find_by_action_and_value('autologin', key)
179 token && (token.created_on > Setting.autologin.to_i.day.ago) && token.user.active? ? token.user : nil
179 token && (token.created_on > Setting.autologin.to_i.day.ago) && token.user.active? ? token.user : nil
180 end
180 end
181
181
182 def <=>(user)
182 def <=>(user)
183 if user.nil?
183 if user.nil?
184 -1
184 -1
185 elsif lastname.to_s.downcase == user.lastname.to_s.downcase
185 elsif lastname.to_s.downcase == user.lastname.to_s.downcase
186 firstname.to_s.downcase <=> user.firstname.to_s.downcase
186 firstname.to_s.downcase <=> user.firstname.to_s.downcase
187 else
187 else
188 lastname.to_s.downcase <=> user.lastname.to_s.downcase
188 lastname.to_s.downcase <=> user.lastname.to_s.downcase
189 end
189 end
190 end
190 end
191
191
192 def to_s
192 def to_s
193 name
193 name
194 end
194 end
195
195
196 def logged?
196 def logged?
197 true
197 true
198 end
198 end
199
199
200 def anonymous?
200 def anonymous?
201 !logged?
201 !logged?
202 end
202 end
203
203
204 # Return user's role for project
204 # Return user's role for project
205 def role_for_project(project)
205 def role_for_project(project)
206 # No role on archived projects
206 # No role on archived projects
207 return nil unless project && project.active?
207 return nil unless project && project.active?
208 if logged?
208 if logged?
209 # Find project membership
209 # Find project membership
210 membership = memberships.detect {|m| m.project_id == project.id}
210 membership = memberships.detect {|m| m.project_id == project.id}
211 if membership
211 if membership
212 membership.role
212 membership.role
213 else
213 else
214 @role_non_member ||= Role.non_member
214 @role_non_member ||= Role.non_member
215 end
215 end
216 else
216 else
217 @role_anonymous ||= Role.anonymous
217 @role_anonymous ||= Role.anonymous
218 end
218 end
219 end
219 end
220
220
221 # Return true if the user is a member of project
221 # Return true if the user is a member of project
222 def member_of?(project)
222 def member_of?(project)
223 role_for_project(project).member?
223 role_for_project(project).member?
224 end
224 end
225
225
226 # Return true if the user is allowed to do the specified action on project
226 # Return true if the user is allowed to do the specified action on project
227 # action can be:
227 # action can be:
228 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
228 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
229 # * a permission Symbol (eg. :edit_project)
229 # * a permission Symbol (eg. :edit_project)
230 def allowed_to?(action, project, options={})
230 def allowed_to?(action, project, options={})
231 if project
231 if project
232 # No action allowed on archived projects
232 # No action allowed on archived projects
233 return false unless project.active?
233 return false unless project.active?
234 # No action allowed on disabled modules
234 # No action allowed on disabled modules
235 return false unless project.allows_to?(action)
235 return false unless project.allows_to?(action)
236 # Admin users are authorized for anything else
236 # Admin users are authorized for anything else
237 return true if admin?
237 return true if admin?
238
238
239 role = role_for_project(project)
239 role = role_for_project(project)
240 return false unless role
240 return false unless role
241 role.allowed_to?(action) && (project.is_public? || role.member?)
241 role.allowed_to?(action) && (project.is_public? || role.member?)
242
242
243 elsif options[:global]
243 elsif options[:global]
244 # authorize if user has at least one role that has this permission
244 # authorize if user has at least one role that has this permission
245 roles = memberships.collect {|m| m.role}.uniq
245 roles = memberships.collect {|m| m.role}.uniq
246 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
246 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
247 else
247 else
248 false
248 false
249 end
249 end
250 end
250 end
251
251
252 def self.current=(user)
252 def self.current=(user)
253 @current_user = user
253 @current_user = user
254 end
254 end
255
255
256 def self.current
256 def self.current
257 @current_user ||= User.anonymous
257 @current_user ||= User.anonymous
258 end
258 end
259
259
260 def self.anonymous
260 def self.anonymous
261 anonymous_user = AnonymousUser.find(:first)
261 anonymous_user = AnonymousUser.find(:first)
262 if anonymous_user.nil?
262 if anonymous_user.nil?
263 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
263 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
264 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
264 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
265 end
265 end
266 anonymous_user
266 anonymous_user
267 end
267 end
268
268
269 private
269 private
270 # Return password digest
270 # Return password digest
271 def self.hash_password(clear_password)
271 def self.hash_password(clear_password)
272 Digest::SHA1.hexdigest(clear_password || "")
272 Digest::SHA1.hexdigest(clear_password || "")
273 end
273 end
274 end
274 end
275
275
276 class AnonymousUser < User
276 class AnonymousUser < User
277
277
278 def validate_on_create
278 def validate_on_create
279 # There should be only one AnonymousUser in the database
279 # There should be only one AnonymousUser in the database
280 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
280 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
281 end
281 end
282
282
283 def available_custom_fields
283 def available_custom_fields
284 []
284 []
285 end
285 end
286
286
287 # Overrides a few properties
287 # Overrides a few properties
288 def logged?; false end
288 def logged?; false end
289 def admin; false end
289 def admin; false end
290 def name; 'Anonymous' end
290 def name; 'Anonymous' end
291 def mail; nil end
291 def mail; nil end
292 def time_zone; nil end
292 def time_zone; nil end
293 def rss_key; nil end
293 def rss_key; nil end
294 end
294 end
@@ -1,354 +1,362
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../../test_helper'
18 require File.dirname(__FILE__) + '/../../test_helper'
19
19
20 class ApplicationHelperTest < HelperTestCase
20 class ApplicationHelperTest < HelperTestCase
21 include ApplicationHelper
21 include ApplicationHelper
22 include ActionView::Helpers::TextHelper
22 include ActionView::Helpers::TextHelper
23 fixtures :projects, :roles, :enabled_modules,
23 fixtures :projects, :roles, :enabled_modules,
24 :repositories, :changesets,
24 :repositories, :changesets,
25 :trackers, :issue_statuses, :issues, :versions, :documents,
25 :trackers, :issue_statuses, :issues, :versions, :documents,
26 :wikis, :wiki_pages, :wiki_contents,
26 :wikis, :wiki_pages, :wiki_contents,
27 :boards, :messages
27 :boards, :messages
28
28
29 def setup
29 def setup
30 super
30 super
31 end
31 end
32
32
33 def test_auto_links
33 def test_auto_links
34 to_test = {
34 to_test = {
35 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
35 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
36 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
36 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
37 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
37 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
38 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
38 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
39 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
39 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
40 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
40 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
41 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
41 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
42 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
42 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
43 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
43 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
44 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
44 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
45 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
45 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
46 }
46 }
47 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
47 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
48 end
48 end
49
49
50 def test_auto_mailto
50 def test_auto_mailto
51 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
51 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
52 textilizable('test@foo.bar')
52 textilizable('test@foo.bar')
53 end
53 end
54
54
55 def test_inline_images
55 def test_inline_images
56 to_test = {
56 to_test = {
57 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
57 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
58 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
58 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
59 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
59 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
60 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
60 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
61 }
61 }
62 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
62 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
63 end
63 end
64
64
65 def test_textile_external_links
65 def test_textile_external_links
66 to_test = {
66 to_test = {
67 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
67 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
68 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
68 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
69 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
69 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
70 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
70 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
71 # no multiline link text
71 # no multiline link text
72 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />\nand another on a second line\":test"
72 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />\nand another on a second line\":test"
73 }
73 }
74 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
74 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
75 end
75 end
76
76
77 def test_redmine_links
77 def test_redmine_links
78 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
78 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
79 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
79 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
80
80
81 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
81 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
82 :class => 'changeset', :title => 'My very first commit')
82 :class => 'changeset', :title => 'My very first commit')
83
83
84 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
84 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
85 :class => 'document')
85 :class => 'document')
86
86
87 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
87 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
88 :class => 'version')
88 :class => 'version')
89
89
90 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
90 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
91
91
92 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
92 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
93 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
93 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
94
94
95 to_test = {
95 to_test = {
96 # tickets
96 # tickets
97 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
97 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
98 # changesets
98 # changesets
99 'r1' => changeset_link,
99 'r1' => changeset_link,
100 # documents
100 # documents
101 'document#1' => document_link,
101 'document#1' => document_link,
102 'document:"Test document"' => document_link,
102 'document:"Test document"' => document_link,
103 # versions
103 # versions
104 'version#2' => version_link,
104 'version#2' => version_link,
105 'version:1.0' => version_link,
105 'version:1.0' => version_link,
106 'version:"1.0"' => version_link,
106 'version:"1.0"' => version_link,
107 # source
107 # source
108 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
108 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
109 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
109 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
110 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
110 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
111 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
111 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
112 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
112 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
113 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
113 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
114 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
114 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
115 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
115 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
116 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
116 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
117 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
117 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
118 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
118 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
119 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
119 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
120 # message
120 # message
121 'message#4' => link_to('Post 2', message_url, :class => 'message'),
121 'message#4' => link_to('Post 2', message_url, :class => 'message'),
122 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5'), :class => 'message'),
122 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5'), :class => 'message'),
123 # escaping
123 # escaping
124 '!#3.' => '#3.',
124 '!#3.' => '#3.',
125 '!r1' => 'r1',
125 '!r1' => 'r1',
126 '!document#1' => 'document#1',
126 '!document#1' => 'document#1',
127 '!document:"Test document"' => 'document:"Test document"',
127 '!document:"Test document"' => 'document:"Test document"',
128 '!version#2' => 'version#2',
128 '!version#2' => 'version#2',
129 '!version:1.0' => 'version:1.0',
129 '!version:1.0' => 'version:1.0',
130 '!version:"1.0"' => 'version:"1.0"',
130 '!version:"1.0"' => 'version:"1.0"',
131 '!source:/some/file' => 'source:/some/file',
131 '!source:/some/file' => 'source:/some/file',
132 # invalid expressions
132 # invalid expressions
133 'source:' => 'source:',
133 'source:' => 'source:',
134 # url hash
134 # url hash
135 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
135 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
136 }
136 }
137 @project = Project.find(1)
137 @project = Project.find(1)
138 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
138 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
139 end
139 end
140
140
141 def test_wiki_links
141 def test_wiki_links
142 to_test = {
142 to_test = {
143 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
143 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
144 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
144 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
145 # link with anchor
145 # link with anchor
146 '[[CookBook documentation#One-section]]' => '<a href="/wiki/ecookbook/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
146 '[[CookBook documentation#One-section]]' => '<a href="/wiki/ecookbook/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
147 '[[Another page#anchor|Page]]' => '<a href="/wiki/ecookbook/Another_page#anchor" class="wiki-page">Page</a>',
147 '[[Another page#anchor|Page]]' => '<a href="/wiki/ecookbook/Another_page#anchor" class="wiki-page">Page</a>',
148 # page that doesn't exist
148 # page that doesn't exist
149 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
149 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
150 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
150 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
151 # link to another project wiki
151 # link to another project wiki
152 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
152 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
153 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
153 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
154 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
154 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
155 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
155 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
156 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
156 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
157 # striked through link
157 # striked through link
158 '-[[Another page|Page]]-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a></del>',
158 '-[[Another page|Page]]-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a></del>',
159 # escaping
159 # escaping
160 '![[Another page|Page]]' => '[[Another page|Page]]',
160 '![[Another page|Page]]' => '[[Another page|Page]]',
161 }
161 }
162 @project = Project.find(1)
162 @project = Project.find(1)
163 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
163 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
164 end
164 end
165
165
166 def test_html_tags
166 def test_html_tags
167 to_test = {
167 to_test = {
168 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
168 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
169 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
169 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
170 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
170 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
171 # do not escape pre/code tags
171 # do not escape pre/code tags
172 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
172 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
173 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
173 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
174 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
174 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
175 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
175 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
176 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>"
176 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>"
177 }
177 }
178 to_test.each { |text, result| assert_equal result, textilizable(text) }
178 to_test.each { |text, result| assert_equal result, textilizable(text) }
179 end
179 end
180
180
181 def test_allowed_html_tags
181 def test_allowed_html_tags
182 to_test = {
182 to_test = {
183 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
183 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
184 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
184 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
185 }
185 }
186 to_test.each { |text, result| assert_equal result, textilizable(text) }
186 to_test.each { |text, result| assert_equal result, textilizable(text) }
187 end
187 end
188
188
189 def test_wiki_links_in_tables
189 def test_wiki_links_in_tables
190 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
190 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
191 '<tr><td><a href="/wiki/ecookbook/Page" class="wiki-page new">Link title</a></td>' +
191 '<tr><td><a href="/wiki/ecookbook/Page" class="wiki-page new">Link title</a></td>' +
192 '<td><a href="/wiki/ecookbook/Other_Page" class="wiki-page new">Other title</a></td>' +
192 '<td><a href="/wiki/ecookbook/Other_Page" class="wiki-page new">Other title</a></td>' +
193 '</tr><tr><td>Cell 21</td><td><a href="/wiki/ecookbook/Last_page" class="wiki-page new">Last page</a></td></tr>'
193 '</tr><tr><td>Cell 21</td><td><a href="/wiki/ecookbook/Last_page" class="wiki-page new">Last page</a></td></tr>'
194 }
194 }
195 @project = Project.find(1)
195 @project = Project.find(1)
196 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
196 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
197 end
197 end
198
198
199 def test_text_formatting
199 def test_text_formatting
200 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
200 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
201 '(_text within parentheses_)' => '(<em>text within parentheses</em>)'
201 '(_text within parentheses_)' => '(<em>text within parentheses</em>)'
202 }
202 }
203 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
203 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
204 end
204 end
205
205
206 def test_wiki_horizontal_rule
206 def test_wiki_horizontal_rule
207 assert_equal '<hr />', textilizable('---')
207 assert_equal '<hr />', textilizable('---')
208 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
208 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
209 end
209 end
210
210
211 def test_table_of_content
211 def test_table_of_content
212 raw = <<-RAW
212 raw = <<-RAW
213 {{toc}}
213 {{toc}}
214
214
215 h1. Title
215 h1. Title
216
216
217 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
217 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
218
218
219 h2. Subtitle
219 h2. Subtitle
220
220
221 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
221 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
222
222
223 h2. Subtitle with %{color:red}red text%
223 h2. Subtitle with %{color:red}red text%
224
224
225 h1. Another title
225 h1. Another title
226
226
227 RAW
227 RAW
228
228
229 expected = '<ul class="toc">' +
229 expected = '<ul class="toc">' +
230 '<li class="heading1"><a href="#Title">Title</a></li>' +
230 '<li class="heading1"><a href="#Title">Title</a></li>' +
231 '<li class="heading2"><a href="#Subtitle">Subtitle</a></li>' +
231 '<li class="heading2"><a href="#Subtitle">Subtitle</a></li>' +
232 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
232 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
233 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
233 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
234 '</ul>'
234 '</ul>'
235
235
236 assert textilizable(raw).gsub("\n", "").include?(expected)
236 assert textilizable(raw).gsub("\n", "").include?(expected)
237 end
237 end
238
238
239 def test_blockquote
239 def test_blockquote
240 # orig raw text
240 # orig raw text
241 raw = <<-RAW
241 raw = <<-RAW
242 John said:
242 John said:
243 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
243 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
244 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
244 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
245 > * Donec odio lorem,
245 > * Donec odio lorem,
246 > * sagittis ac,
246 > * sagittis ac,
247 > * malesuada in,
247 > * malesuada in,
248 > * adipiscing eu, dolor.
248 > * adipiscing eu, dolor.
249 >
249 >
250 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
250 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
251 > Proin a tellus. Nam vel neque.
251 > Proin a tellus. Nam vel neque.
252
252
253 He's right.
253 He's right.
254 RAW
254 RAW
255
255
256 # expected html
256 # expected html
257 expected = <<-EXPECTED
257 expected = <<-EXPECTED
258 <p>John said:</p>
258 <p>John said:</p>
259 <blockquote>
259 <blockquote>
260 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
260 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
261 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
261 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
262 <ul>
262 <ul>
263 <li>Donec odio lorem,</li>
263 <li>Donec odio lorem,</li>
264 <li>sagittis ac,</li>
264 <li>sagittis ac,</li>
265 <li>malesuada in,</li>
265 <li>malesuada in,</li>
266 <li>adipiscing eu, dolor.</li>
266 <li>adipiscing eu, dolor.</li>
267 </ul>
267 </ul>
268 <blockquote>
268 <blockquote>
269 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
269 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
270 </blockquote>
270 </blockquote>
271 <p>Proin a tellus. Nam vel neque.</p>
271 <p>Proin a tellus. Nam vel neque.</p>
272 </blockquote>
272 </blockquote>
273 <p>He's right.</p>
273 <p>He's right.</p>
274 EXPECTED
274 EXPECTED
275
275
276 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
276 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
277 end
277 end
278
278
279 def test_table
279 def test_table
280 raw = <<-RAW
280 raw = <<-RAW
281 This is a table with empty cells:
281 This is a table with empty cells:
282
282
283 |cell11|cell12||
283 |cell11|cell12||
284 |cell21||cell23|
284 |cell21||cell23|
285 |cell31|cell32|cell33|
285 |cell31|cell32|cell33|
286 RAW
286 RAW
287
287
288 expected = <<-EXPECTED
288 expected = <<-EXPECTED
289 <p>This is a table with empty cells:</p>
289 <p>This is a table with empty cells:</p>
290
290
291 <table>
291 <table>
292 <tr><td>cell11</td><td>cell12</td><td></td></tr>
292 <tr><td>cell11</td><td>cell12</td><td></td></tr>
293 <tr><td>cell21</td><td></td><td>cell23</td></tr>
293 <tr><td>cell21</td><td></td><td>cell23</td></tr>
294 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
294 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
295 </table>
295 </table>
296 EXPECTED
296 EXPECTED
297
297
298 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
298 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
299 end
299 end
300
300
301 def test_macro_hello_world
301 def test_macro_hello_world
302 text = "{{hello_world}}"
302 text = "{{hello_world}}"
303 assert textilizable(text).match(/Hello world!/)
303 assert textilizable(text).match(/Hello world!/)
304 # escaping
304 # escaping
305 text = "!{{hello_world}}"
305 text = "!{{hello_world}}"
306 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
306 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
307 end
307 end
308
308
309 def test_macro_include
309 def test_macro_include
310 @project = Project.find(1)
310 @project = Project.find(1)
311 # include a page of the current project wiki
311 # include a page of the current project wiki
312 text = "{{include(Another page)}}"
312 text = "{{include(Another page)}}"
313 assert textilizable(text).match(/This is a link to a ticket/)
313 assert textilizable(text).match(/This is a link to a ticket/)
314
314
315 @project = nil
315 @project = nil
316 # include a page of a specific project wiki
316 # include a page of a specific project wiki
317 text = "{{include(ecookbook:Another page)}}"
317 text = "{{include(ecookbook:Another page)}}"
318 assert textilizable(text).match(/This is a link to a ticket/)
318 assert textilizable(text).match(/This is a link to a ticket/)
319
319
320 text = "{{include(ecookbook:)}}"
320 text = "{{include(ecookbook:)}}"
321 assert textilizable(text).match(/CookBook documentation/)
321 assert textilizable(text).match(/CookBook documentation/)
322
322
323 text = "{{include(unknowidentifier:somepage)}}"
323 text = "{{include(unknowidentifier:somepage)}}"
324 assert textilizable(text).match(/Unknow project/)
324 assert textilizable(text).match(/Unknow project/)
325 end
325 end
326
326
327 def test_date_format_default
327 def test_date_format_default
328 today = Date.today
328 today = Date.today
329 Setting.date_format = ''
329 Setting.date_format = ''
330 assert_equal l_date(today), format_date(today)
330 assert_equal l_date(today), format_date(today)
331 end
331 end
332
332
333 def test_date_format
333 def test_date_format
334 today = Date.today
334 today = Date.today
335 Setting.date_format = '%d %m %Y'
335 Setting.date_format = '%d %m %Y'
336 assert_equal today.strftime('%d %m %Y'), format_date(today)
336 assert_equal today.strftime('%d %m %Y'), format_date(today)
337 end
337 end
338
338
339 def test_time_format_default
339 def test_time_format_default
340 now = Time.now
340 now = Time.now
341 Setting.date_format = ''
341 Setting.date_format = ''
342 Setting.time_format = ''
342 Setting.time_format = ''
343 assert_equal l_datetime(now), format_time(now)
343 assert_equal l_datetime(now), format_time(now)
344 assert_equal l_time(now), format_time(now, false)
344 assert_equal l_time(now), format_time(now, false)
345 end
345 end
346
346
347 def test_time_format
347 def test_time_format
348 now = Time.now
348 now = Time.now
349 Setting.date_format = '%d %m %Y'
349 Setting.date_format = '%d %m %Y'
350 Setting.time_format = '%H %M'
350 Setting.time_format = '%H %M'
351 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
351 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
352 assert_equal now.strftime('%H %M'), format_time(now, false)
352 assert_equal now.strftime('%H %M'), format_time(now, false)
353 end
353 end
354
355 def test_utc_time_format
356 now = Time.now.utc
357 Setting.date_format = '%d %m %Y'
358 Setting.time_format = '%H %M'
359 assert_equal Time.now.strftime('%d %m %Y %H %M'), format_time(now)
360 assert_equal Time.now.strftime('%H %M'), format_time(now, false)
361 end
354 end
362 end
General Comments 0
You need to be logged in to leave comments. Login now