##// END OF EJS Templates
Don't use Iconv with ruby1.9 (#12787)....
Jean-Philippe Lang -
r10947:ff53a9cfe18d
parent child
Show More
@@ -1,149 +1,158
1 require 'iconv'
1 if RUBY_VERSION < '1.9'
2 require 'iconv'
3 end
2 4
3 5 module Redmine
4 6 module CodesetUtil
5 7
6 8 def self.replace_invalid_utf8(str)
7 9 return str if str.nil?
8 10 if str.respond_to?(:force_encoding)
9 11 str.force_encoding('UTF-8')
10 12 if ! str.valid_encoding?
11 13 str = str.encode("US-ASCII", :invalid => :replace,
12 14 :undef => :replace, :replace => '?').encode("UTF-8")
13 15 end
14 16 elsif RUBY_PLATFORM == 'java'
15 17 begin
16 18 ic = Iconv.new('UTF-8', 'UTF-8')
17 19 str = ic.iconv(str)
18 20 rescue
19 21 str = str.gsub(%r{[^\r\n\t\x20-\x7e]}, '?')
20 22 end
21 23 else
22 24 ic = Iconv.new('UTF-8', 'UTF-8')
23 25 txtar = ""
24 26 begin
25 27 txtar += ic.iconv(str)
26 28 rescue Iconv::IllegalSequence
27 29 txtar += $!.success
28 30 str = '?' + $!.failed[1,$!.failed.length]
29 31 retry
30 32 rescue
31 33 txtar += $!.success
32 34 end
33 35 str = txtar
34 36 end
35 37 str
36 38 end
37 39
38 40 def self.to_utf8(str, encoding)
39 41 return str if str.nil?
40 42 str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding)
41 43 if str.empty?
42 44 str.force_encoding("UTF-8") if str.respond_to?(:force_encoding)
43 45 return str
44 46 end
45 47 enc = encoding.blank? ? "UTF-8" : encoding
46 48 if str.respond_to?(:force_encoding)
47 49 if enc.upcase != "UTF-8"
48 50 str.force_encoding(enc)
49 51 str = str.encode("UTF-8", :invalid => :replace,
50 52 :undef => :replace, :replace => '?')
51 53 else
52 54 str.force_encoding("UTF-8")
53 55 if ! str.valid_encoding?
54 56 str = str.encode("US-ASCII", :invalid => :replace,
55 57 :undef => :replace, :replace => '?').encode("UTF-8")
56 58 end
57 59 end
58 60 elsif RUBY_PLATFORM == 'java'
59 61 begin
60 62 ic = Iconv.new('UTF-8', enc)
61 63 str = ic.iconv(str)
62 64 rescue
63 65 str = str.gsub(%r{[^\r\n\t\x20-\x7e]}, '?')
64 66 end
65 67 else
66 68 ic = Iconv.new('UTF-8', enc)
67 69 txtar = ""
68 70 begin
69 71 txtar += ic.iconv(str)
70 72 rescue Iconv::IllegalSequence
71 73 txtar += $!.success
72 74 str = '?' + $!.failed[1,$!.failed.length]
73 75 retry
74 76 rescue
75 77 txtar += $!.success
76 78 end
77 79 str = txtar
78 80 end
79 81 str
80 82 end
81 83
82 84 def self.to_utf8_by_setting(str)
83 85 return str if str.nil?
84 86 str = self.to_utf8_by_setting_internal(str)
85 87 if str.respond_to?(:force_encoding)
86 88 str.force_encoding('UTF-8')
87 89 end
88 90 str
89 91 end
90 92
91 93 def self.to_utf8_by_setting_internal(str)
92 94 return str if str.nil?
93 95 if str.respond_to?(:force_encoding)
94 96 str.force_encoding('ASCII-8BIT')
95 97 end
96 98 return str if str.empty?
97 99 return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
98 100 if str.respond_to?(:force_encoding)
99 101 str.force_encoding('UTF-8')
100 102 end
101 103 encodings = Setting.repositories_encodings.split(',').collect(&:strip)
102 104 encodings.each do |encoding|
103 begin
104 return Iconv.conv('UTF-8', encoding, str)
105 rescue Iconv::Failure
106 # do nothing here and try the next encoding
105 if str.respond_to?(:force_encoding)
106 str.force_encoding(encoding)
107 if str.valid_encoding?
108 return str.encode('UTF-8')
109 end
110 else
111 begin
112 return Iconv.conv('UTF-8', encoding, str)
113 rescue Iconv::Failure
114 # do nothing here and try the next encoding
115 end
107 116 end
108 117 end
109 118 str = self.replace_invalid_utf8(str)
110 119 if str.respond_to?(:force_encoding)
111 120 str.force_encoding('UTF-8')
112 121 end
113 122 str
114 123 end
115 124
116 125 def self.from_utf8(str, encoding)
117 126 str ||= ''
118 127 if str.respond_to?(:force_encoding)
119 128 str.force_encoding('UTF-8')
120 129 if encoding.upcase != 'UTF-8'
121 130 str = str.encode(encoding, :invalid => :replace,
122 131 :undef => :replace, :replace => '?')
123 132 else
124 133 str = self.replace_invalid_utf8(str)
125 134 end
126 135 elsif RUBY_PLATFORM == 'java'
127 136 begin
128 137 ic = Iconv.new(encoding, 'UTF-8')
129 138 str = ic.iconv(str)
130 139 rescue
131 140 str = str.gsub(%r{[^\r\n\t\x20-\x7e]}, '?')
132 141 end
133 142 else
134 143 ic = Iconv.new(encoding, 'UTF-8')
135 144 txtar = ""
136 145 begin
137 146 txtar += ic.iconv(str)
138 147 rescue Iconv::IllegalSequence
139 148 txtar += $!.success
140 149 str = '?' + $!.failed[1, $!.failed.length]
141 150 retry
142 151 rescue
143 152 txtar += $!.success
144 153 end
145 154 str = txtar
146 155 end
147 156 end
148 157 end
149 158 end
@@ -1,792 +1,804
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2013 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 require 'iconv'
21 20 require 'tcpdf'
22 21 require 'fpdf/chinese'
23 22 require 'fpdf/japanese'
24 23 require 'fpdf/korean'
25 24
25 if RUBY_VERSION < '1.9'
26 require 'iconv'
27 end
28
26 29 module Redmine
27 30 module Export
28 31 module PDF
29 32 include ActionView::Helpers::TextHelper
30 33 include ActionView::Helpers::NumberHelper
31 34 include IssuesHelper
32 35
33 36 class ITCPDF < TCPDF
34 37 include Redmine::I18n
35 38 attr_accessor :footer_date
36 39
37 40 def initialize(lang, orientation='P')
38 41 @@k_path_cache = Rails.root.join('tmp', 'pdf')
39 42 FileUtils.mkdir_p @@k_path_cache unless File::exist?(@@k_path_cache)
40 43 set_language_if_valid lang
41 44 pdf_encoding = l(:general_pdf_encoding).upcase
42 45 super(orientation, 'mm', 'A4', (pdf_encoding == 'UTF-8'), pdf_encoding)
43 46 case current_language.to_s.downcase
44 47 when 'vi'
45 48 @font_for_content = 'DejaVuSans'
46 49 @font_for_footer = 'DejaVuSans'
47 50 else
48 51 case pdf_encoding
49 52 when 'UTF-8'
50 53 @font_for_content = 'FreeSans'
51 54 @font_for_footer = 'FreeSans'
52 55 when 'CP949'
53 56 extend(PDF_Korean)
54 57 AddUHCFont()
55 58 @font_for_content = 'UHC'
56 59 @font_for_footer = 'UHC'
57 60 when 'CP932', 'SJIS', 'SHIFT_JIS'
58 61 extend(PDF_Japanese)
59 62 AddSJISFont()
60 63 @font_for_content = 'SJIS'
61 64 @font_for_footer = 'SJIS'
62 65 when 'GB18030'
63 66 extend(PDF_Chinese)
64 67 AddGBFont()
65 68 @font_for_content = 'GB'
66 69 @font_for_footer = 'GB'
67 70 when 'BIG5'
68 71 extend(PDF_Chinese)
69 72 AddBig5Font()
70 73 @font_for_content = 'Big5'
71 74 @font_for_footer = 'Big5'
72 75 else
73 76 @font_for_content = 'Arial'
74 77 @font_for_footer = 'Helvetica'
75 78 end
76 79 end
77 80 SetCreator(Redmine::Info.app_name)
78 81 SetFont(@font_for_content)
79 82 @outlines = []
80 83 @outlineRoot = nil
81 84 end
82 85
83 86 def SetFontStyle(style, size)
84 87 SetFont(@font_for_content, style, size)
85 88 end
86 89
87 90 def SetTitle(txt)
88 91 txt = begin
89 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
92 utf16txt = to_utf16(txt)
90 93 hextxt = "<FEFF" # FEFF is BOM
91 94 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
92 95 hextxt << ">"
93 96 rescue
94 97 txt
95 98 end || ''
96 99 super(txt)
97 100 end
98 101
99 102 def textstring(s)
100 103 # Format a text string
101 104 if s =~ /^</ # This means the string is hex-dumped.
102 105 return s
103 106 else
104 107 return '('+escape(s)+')'
105 108 end
106 109 end
107 110
108 111 def fix_text_encoding(txt)
109 112 RDMPdfEncoding::rdm_from_utf8(txt, l(:general_pdf_encoding))
110 113 end
111 114
115 # Encodes an UTF-8 string to UTF-16BE
116 def to_utf16(str)
117 if str.respond_to?(:encode)
118 str.encode('UTF-16BE')
119 else
120 Iconv.conv('UTF-16BE', 'UTF-8', str)
121 end
122 end
123
112 124 def RDMCell(w ,h=0, txt='', border=0, ln=0, align='', fill=0, link='')
113 125 Cell(w, h, fix_text_encoding(txt), border, ln, align, fill, link)
114 126 end
115 127
116 128 def RDMMultiCell(w, h=0, txt='', border=0, align='', fill=0, ln=1)
117 129 MultiCell(w, h, fix_text_encoding(txt), border, align, fill, ln)
118 130 end
119 131
120 132 def RDMwriteHTMLCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0)
121 133 @attachments = attachments
122 134 writeHTMLCell(w, h, x, y,
123 135 fix_text_encoding(
124 136 Redmine::WikiFormatting.to_html(Setting.text_formatting, txt)),
125 137 border, ln, fill)
126 138 end
127 139
128 140 def getImageFilename(attrname)
129 141 # attrname: general_pdf_encoding string file/uri name
130 142 atta = RDMPdfEncoding.attach(@attachments, attrname, l(:general_pdf_encoding))
131 143 if atta
132 144 return atta.diskfile
133 145 else
134 146 return nil
135 147 end
136 148 end
137 149
138 150 def Footer
139 151 SetFont(@font_for_footer, 'I', 8)
140 152 SetY(-15)
141 153 SetX(15)
142 154 RDMCell(0, 5, @footer_date, 0, 0, 'L')
143 155 SetY(-15)
144 156 SetX(-30)
145 157 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
146 158 end
147 159
148 160 def Bookmark(txt, level=0, y=0)
149 161 if (y == -1)
150 162 y = GetY()
151 163 end
152 164 @outlines << {:t => txt, :l => level, :p => PageNo(), :y => (@h - y)*@k}
153 165 end
154 166
155 167 def bookmark_title(txt)
156 168 txt = begin
157 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
169 utf16txt = to_utf16(txt)
158 170 hextxt = "<FEFF" # FEFF is BOM
159 171 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
160 172 hextxt << ">"
161 173 rescue
162 174 txt
163 175 end || ''
164 176 end
165 177
166 178 def putbookmarks
167 179 nb=@outlines.size
168 180 return if (nb==0)
169 181 lru=[]
170 182 level=0
171 183 @outlines.each_with_index do |o, i|
172 184 if(o[:l]>0)
173 185 parent=lru[o[:l]-1]
174 186 #Set parent and last pointers
175 187 @outlines[i][:parent]=parent
176 188 @outlines[parent][:last]=i
177 189 if (o[:l]>level)
178 190 #Level increasing: set first pointer
179 191 @outlines[parent][:first]=i
180 192 end
181 193 else
182 194 @outlines[i][:parent]=nb
183 195 end
184 196 if (o[:l]<=level && i>0)
185 197 #Set prev and next pointers
186 198 prev=lru[o[:l]]
187 199 @outlines[prev][:next]=i
188 200 @outlines[i][:prev]=prev
189 201 end
190 202 lru[o[:l]]=i
191 203 level=o[:l]
192 204 end
193 205 #Outline items
194 206 n=self.n+1
195 207 @outlines.each_with_index do |o, i|
196 208 newobj()
197 209 out('<</Title '+bookmark_title(o[:t]))
198 210 out("/Parent #{n+o[:parent]} 0 R")
199 211 if (o[:prev])
200 212 out("/Prev #{n+o[:prev]} 0 R")
201 213 end
202 214 if (o[:next])
203 215 out("/Next #{n+o[:next]} 0 R")
204 216 end
205 217 if (o[:first])
206 218 out("/First #{n+o[:first]} 0 R")
207 219 end
208 220 if (o[:last])
209 221 out("/Last #{n+o[:last]} 0 R")
210 222 end
211 223 out("/Dest [%d 0 R /XYZ 0 %.2f null]" % [1+2*o[:p], o[:y]])
212 224 out('/Count 0>>')
213 225 out('endobj')
214 226 end
215 227 #Outline root
216 228 newobj()
217 229 @outlineRoot=self.n
218 230 out("<</Type /Outlines /First #{n} 0 R");
219 231 out("/Last #{n+lru[0]} 0 R>>");
220 232 out('endobj');
221 233 end
222 234
223 235 def putresources()
224 236 super
225 237 putbookmarks()
226 238 end
227 239
228 240 def putcatalog()
229 241 super
230 242 if(@outlines.size > 0)
231 243 out("/Outlines #{@outlineRoot} 0 R");
232 244 out('/PageMode /UseOutlines');
233 245 end
234 246 end
235 247 end
236 248
237 249 # fetch row values
238 250 def fetch_row_values(issue, query, level)
239 251 query.inline_columns.collect do |column|
240 252 s = if column.is_a?(QueryCustomFieldColumn)
241 253 cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id}
242 254 show_value(cv)
243 255 else
244 256 value = issue.send(column.name)
245 257 if column.name == :subject
246 258 value = " " * level + value
247 259 end
248 260 if value.is_a?(Date)
249 261 format_date(value)
250 262 elsif value.is_a?(Time)
251 263 format_time(value)
252 264 else
253 265 value
254 266 end
255 267 end
256 268 s.to_s
257 269 end
258 270 end
259 271
260 272 # calculate columns width
261 273 def calc_col_width(issues, query, table_width, pdf)
262 274 # calculate statistics
263 275 # by captions
264 276 pdf.SetFontStyle('B',8)
265 277 col_padding = pdf.GetStringWidth('OO')
266 278 col_width_min = query.inline_columns.map {|v| pdf.GetStringWidth(v.caption) + col_padding}
267 279 col_width_max = Array.new(col_width_min)
268 280 col_width_avg = Array.new(col_width_min)
269 281 word_width_max = query.inline_columns.map {|c|
270 282 n = 10
271 283 c.caption.split.each {|w|
272 284 x = pdf.GetStringWidth(w) + col_padding
273 285 n = x if n < x
274 286 }
275 287 n
276 288 }
277 289
278 290 # by properties of issues
279 291 pdf.SetFontStyle('',8)
280 292 col_padding = pdf.GetStringWidth('OO')
281 293 k = 1
282 294 issue_list(issues) {|issue, level|
283 295 k += 1
284 296 values = fetch_row_values(issue, query, level)
285 297 values.each_with_index {|v,i|
286 298 n = pdf.GetStringWidth(v) + col_padding
287 299 col_width_max[i] = n if col_width_max[i] < n
288 300 col_width_min[i] = n if col_width_min[i] > n
289 301 col_width_avg[i] += n
290 302 v.split.each {|w|
291 303 x = pdf.GetStringWidth(w) + col_padding
292 304 word_width_max[i] = x if word_width_max[i] < x
293 305 }
294 306 }
295 307 }
296 308 col_width_avg.map! {|x| x / k}
297 309
298 310 # calculate columns width
299 311 ratio = table_width / col_width_avg.inject(0) {|s,w| s += w}
300 312 col_width = col_width_avg.map {|w| w * ratio}
301 313
302 314 # correct max word width if too many columns
303 315 ratio = table_width / word_width_max.inject(0) {|s,w| s += w}
304 316 word_width_max.map! {|v| v * ratio} if ratio < 1
305 317
306 318 # correct and lock width of some columns
307 319 done = 1
308 320 col_fix = []
309 321 col_width.each_with_index do |w,i|
310 322 if w > col_width_max[i]
311 323 col_width[i] = col_width_max[i]
312 324 col_fix[i] = 1
313 325 done = 0
314 326 elsif w < word_width_max[i]
315 327 col_width[i] = word_width_max[i]
316 328 col_fix[i] = 1
317 329 done = 0
318 330 else
319 331 col_fix[i] = 0
320 332 end
321 333 end
322 334
323 335 # iterate while need to correct and lock coluns width
324 336 while done == 0
325 337 # calculate free & locked columns width
326 338 done = 1
327 339 fix_col_width = 0
328 340 free_col_width = 0
329 341 col_width.each_with_index do |w,i|
330 342 if col_fix[i] == 1
331 343 fix_col_width += w
332 344 else
333 345 free_col_width += w
334 346 end
335 347 end
336 348
337 349 # calculate column normalizing ratio
338 350 if free_col_width == 0
339 351 ratio = table_width / col_width.inject(0) {|s,w| s += w}
340 352 else
341 353 ratio = (table_width - fix_col_width) / free_col_width
342 354 end
343 355
344 356 # correct columns width
345 357 col_width.each_with_index do |w,i|
346 358 if col_fix[i] == 0
347 359 col_width[i] = w * ratio
348 360
349 361 # check if column width less then max word width
350 362 if col_width[i] < word_width_max[i]
351 363 col_width[i] = word_width_max[i]
352 364 col_fix[i] = 1
353 365 done = 0
354 366 elsif col_width[i] > col_width_max[i]
355 367 col_width[i] = col_width_max[i]
356 368 col_fix[i] = 1
357 369 done = 0
358 370 end
359 371 end
360 372 end
361 373 end
362 374 col_width
363 375 end
364 376
365 377 def render_table_header(pdf, query, col_width, row_height, col_id_width, table_width)
366 378 # headers
367 379 pdf.SetFontStyle('B',8)
368 380 pdf.SetFillColor(230, 230, 230)
369 381
370 382 # render it background to find the max height used
371 383 base_x = pdf.GetX
372 384 base_y = pdf.GetY
373 385 max_height = issues_to_pdf_write_cells(pdf, query.inline_columns, col_width, row_height, true)
374 386 pdf.Rect(base_x, base_y, table_width + col_id_width, max_height, 'FD');
375 387 pdf.SetXY(base_x, base_y);
376 388
377 389 # write the cells on page
378 390 pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1)
379 391 issues_to_pdf_write_cells(pdf, query.inline_columns, col_width, row_height, true)
380 392 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
381 393 pdf.SetY(base_y + max_height);
382 394
383 395 # rows
384 396 pdf.SetFontStyle('',8)
385 397 pdf.SetFillColor(255, 255, 255)
386 398 end
387 399
388 400 # Returns a PDF string of a list of issues
389 401 def issues_to_pdf(issues, project, query)
390 402 pdf = ITCPDF.new(current_language, "L")
391 403 title = query.new_record? ? l(:label_issue_plural) : query.name
392 404 title = "#{project} - #{title}" if project
393 405 pdf.SetTitle(title)
394 406 pdf.alias_nb_pages
395 407 pdf.footer_date = format_date(Date.today)
396 408 pdf.SetAutoPageBreak(false)
397 409 pdf.AddPage("L")
398 410
399 411 # Landscape A4 = 210 x 297 mm
400 412 page_height = 210
401 413 page_width = 297
402 414 right_margin = 10
403 415 bottom_margin = 20
404 416 col_id_width = 10
405 417 row_height = 4
406 418
407 419 # column widths
408 420 table_width = page_width - right_margin - 10 # fixed left margin
409 421 col_width = []
410 422 unless query.inline_columns.empty?
411 423 col_width = calc_col_width(issues, query, table_width - col_id_width, pdf)
412 424 table_width = col_width.inject(0) {|s,v| s += v}
413 425 end
414 426
415 427 # use full width if the description is displayed
416 428 if table_width > 0 && query.has_column?(:description)
417 429 col_width = col_width.map {|w| w = w * (page_width - right_margin - 10 - col_id_width) / table_width}
418 430 table_width = col_width.inject(0) {|s,v| s += v}
419 431 end
420 432
421 433 # title
422 434 pdf.SetFontStyle('B',11)
423 435 pdf.RDMCell(190,10, title)
424 436 pdf.Ln
425 437 render_table_header(pdf, query, col_width, row_height, col_id_width, table_width)
426 438 previous_group = false
427 439 issue_list(issues) do |issue, level|
428 440 if query.grouped? &&
429 441 (group = query.group_by_column.value(issue)) != previous_group
430 442 pdf.SetFontStyle('B',10)
431 443 group_label = group.blank? ? 'None' : group.to_s.dup
432 444 group_label << " (#{query.issue_count_by_group[group]})"
433 445 pdf.Bookmark group_label, 0, -1
434 446 pdf.RDMCell(table_width + col_id_width, row_height * 2, group_label, 1, 1, 'L')
435 447 pdf.SetFontStyle('',8)
436 448 previous_group = group
437 449 end
438 450
439 451 # fetch row values
440 452 col_values = fetch_row_values(issue, query, level)
441 453
442 454 # render it off-page to find the max height used
443 455 base_x = pdf.GetX
444 456 base_y = pdf.GetY
445 457 pdf.SetY(2 * page_height)
446 458 max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
447 459 pdf.SetXY(base_x, base_y)
448 460
449 461 # make new page if it doesn't fit on the current one
450 462 space_left = page_height - base_y - bottom_margin
451 463 if max_height > space_left
452 464 pdf.AddPage("L")
453 465 render_table_header(pdf, query, col_width, row_height, col_id_width, table_width)
454 466 base_x = pdf.GetX
455 467 base_y = pdf.GetY
456 468 end
457 469
458 470 # write the cells on page
459 471 pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1)
460 472 issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
461 473 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
462 474 pdf.SetY(base_y + max_height);
463 475
464 476 if query.has_column?(:description) && issue.description?
465 477 pdf.SetX(10)
466 478 pdf.SetAutoPageBreak(true, 20)
467 479 pdf.RDMwriteHTMLCell(0, 5, 10, 0, issue.description.to_s, issue.attachments, "LRBT")
468 480 pdf.SetAutoPageBreak(false)
469 481 end
470 482 end
471 483
472 484 if issues.size == Setting.issues_export_limit.to_i
473 485 pdf.SetFontStyle('B',10)
474 486 pdf.RDMCell(0, row_height, '...')
475 487 end
476 488 pdf.Output
477 489 end
478 490
479 491 # Renders MultiCells and returns the maximum height used
480 492 def issues_to_pdf_write_cells(pdf, col_values, col_widths,
481 493 row_height, head=false)
482 494 base_y = pdf.GetY
483 495 max_height = row_height
484 496 col_values.each_with_index do |column, i|
485 497 col_x = pdf.GetX
486 498 if head == true
487 499 pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1)
488 500 else
489 501 pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1)
490 502 end
491 503 max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height
492 504 pdf.SetXY(col_x + col_widths[i], base_y);
493 505 end
494 506 return max_height
495 507 end
496 508
497 509 # Draw lines to close the row (MultiCell border drawing in not uniform)
498 510 def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y,
499 511 id_width, col_widths)
500 512 col_x = top_x + id_width
501 513 pdf.Line(col_x, top_y, col_x, lower_y) # id right border
502 514 col_widths.each do |width|
503 515 col_x += width
504 516 pdf.Line(col_x, top_y, col_x, lower_y) # columns right border
505 517 end
506 518 pdf.Line(top_x, top_y, top_x, lower_y) # left border
507 519 pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border
508 520 end
509 521
510 522 # Returns a PDF string of a single issue
511 523 def issue_to_pdf(issue, assoc={})
512 524 pdf = ITCPDF.new(current_language)
513 525 pdf.SetTitle("#{issue.project} - #{issue.tracker} ##{issue.id}")
514 526 pdf.alias_nb_pages
515 527 pdf.footer_date = format_date(Date.today)
516 528 pdf.AddPage
517 529 pdf.SetFontStyle('B',11)
518 530 buf = "#{issue.project} - #{issue.tracker} ##{issue.id}"
519 531 pdf.RDMMultiCell(190, 5, buf)
520 532 pdf.SetFontStyle('',8)
521 533 base_x = pdf.GetX
522 534 i = 1
523 535 issue.ancestors.visible.each do |ancestor|
524 536 pdf.SetX(base_x + i)
525 537 buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}"
526 538 pdf.RDMMultiCell(190 - i, 5, buf)
527 539 i += 1 if i < 35
528 540 end
529 541 pdf.SetFontStyle('B',11)
530 542 pdf.RDMMultiCell(190 - i, 5, issue.subject.to_s)
531 543 pdf.SetFontStyle('',8)
532 544 pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}")
533 545 pdf.Ln
534 546
535 547 left = []
536 548 left << [l(:field_status), issue.status]
537 549 left << [l(:field_priority), issue.priority]
538 550 left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id')
539 551 left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id')
540 552 left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id')
541 553
542 554 right = []
543 555 right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date')
544 556 right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date')
545 557 right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio')
546 558 right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours')
547 559 right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project)
548 560
549 561 rows = left.size > right.size ? left.size : right.size
550 562 while left.size < rows
551 563 left << nil
552 564 end
553 565 while right.size < rows
554 566 right << nil
555 567 end
556 568
557 569 half = (issue.custom_field_values.size / 2.0).ceil
558 570 issue.custom_field_values.each_with_index do |custom_value, i|
559 571 (i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value)]
560 572 end
561 573
562 574 rows = left.size > right.size ? left.size : right.size
563 575 rows.times do |i|
564 576 item = left[i]
565 577 pdf.SetFontStyle('B',9)
566 578 pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L")
567 579 pdf.SetFontStyle('',9)
568 580 pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R")
569 581
570 582 item = right[i]
571 583 pdf.SetFontStyle('B',9)
572 584 pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L")
573 585 pdf.SetFontStyle('',9)
574 586 pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R")
575 587 pdf.Ln
576 588 end
577 589
578 590 pdf.SetFontStyle('B',9)
579 591 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)
580 592 pdf.SetFontStyle('',9)
581 593
582 594 # Set resize image scale
583 595 pdf.SetImageScale(1.6)
584 596 pdf.RDMwriteHTMLCell(35+155, 5, 0, 0,
585 597 issue.description.to_s, issue.attachments, "LRB")
586 598
587 599 unless issue.leaf?
588 600 # for CJK
589 601 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 )
590 602
591 603 pdf.SetFontStyle('B',9)
592 604 pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR")
593 605 pdf.Ln
594 606 issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level|
595 607 buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}",
596 608 :length => truncate_length)
597 609 level = 10 if level >= 10
598 610 pdf.SetFontStyle('',8)
599 611 pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L")
600 612 pdf.SetFontStyle('B',8)
601 613 pdf.RDMCell(20,5, child.status.to_s, "R")
602 614 pdf.Ln
603 615 end
604 616 end
605 617
606 618 relations = issue.relations.select { |r| r.other_issue(issue).visible? }
607 619 unless relations.empty?
608 620 # for CJK
609 621 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 )
610 622
611 623 pdf.SetFontStyle('B',9)
612 624 pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR")
613 625 pdf.Ln
614 626 relations.each do |relation|
615 627 buf = ""
616 628 buf += "#{l(relation.label_for(issue))} "
617 629 if relation.delay && relation.delay != 0
618 630 buf += "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)}) "
619 631 end
620 632 if Setting.cross_project_issue_relations?
621 633 buf += "#{relation.other_issue(issue).project} - "
622 634 end
623 635 buf += "#{relation.other_issue(issue).tracker}" +
624 636 " # #{relation.other_issue(issue).id}: #{relation.other_issue(issue).subject}"
625 637 buf = truncate(buf, :length => truncate_length)
626 638 pdf.SetFontStyle('', 8)
627 639 pdf.RDMCell(35+155-60, 5, buf, "L")
628 640 pdf.SetFontStyle('B',8)
629 641 pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "")
630 642 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "")
631 643 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), "R")
632 644 pdf.Ln
633 645 end
634 646 end
635 647 pdf.RDMCell(190,5, "", "T")
636 648 pdf.Ln
637 649
638 650 if issue.changesets.any? &&
639 651 User.current.allowed_to?(:view_changesets, issue.project)
640 652 pdf.SetFontStyle('B',9)
641 653 pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
642 654 pdf.Ln
643 655 for changeset in issue.changesets
644 656 pdf.SetFontStyle('B',8)
645 657 csstr = "#{l(:label_revision)} #{changeset.format_identifier} - "
646 658 csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s
647 659 pdf.RDMCell(190, 5, csstr)
648 660 pdf.Ln
649 661 unless changeset.comments.blank?
650 662 pdf.SetFontStyle('',8)
651 663 pdf.RDMwriteHTMLCell(190,5,0,0,
652 664 changeset.comments.to_s, issue.attachments, "")
653 665 end
654 666 pdf.Ln
655 667 end
656 668 end
657 669
658 670 if assoc[:journals].present?
659 671 pdf.SetFontStyle('B',9)
660 672 pdf.RDMCell(190,5, l(:label_history), "B")
661 673 pdf.Ln
662 674 assoc[:journals].each do |journal|
663 675 pdf.SetFontStyle('B',8)
664 676 title = "##{journal.indice} - #{format_time(journal.created_on)} - #{journal.user}"
665 677 title << " (#{l(:field_private_notes)})" if journal.private_notes?
666 678 pdf.RDMCell(190,5, title)
667 679 pdf.Ln
668 680 pdf.SetFontStyle('I',8)
669 681 details_to_strings(journal.details, true).each do |string|
670 682 pdf.RDMMultiCell(190,5, "- " + string)
671 683 end
672 684 if journal.notes?
673 685 pdf.Ln unless journal.details.empty?
674 686 pdf.SetFontStyle('',8)
675 687 pdf.RDMwriteHTMLCell(190,5,0,0,
676 688 journal.notes.to_s, issue.attachments, "")
677 689 end
678 690 pdf.Ln
679 691 end
680 692 end
681 693
682 694 if issue.attachments.any?
683 695 pdf.SetFontStyle('B',9)
684 696 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
685 697 pdf.Ln
686 698 for attachment in issue.attachments
687 699 pdf.SetFontStyle('',8)
688 700 pdf.RDMCell(80,5, attachment.filename)
689 701 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
690 702 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
691 703 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
692 704 pdf.Ln
693 705 end
694 706 end
695 707 pdf.Output
696 708 end
697 709
698 710 # Returns a PDF string of a set of wiki pages
699 711 def wiki_pages_to_pdf(pages, project)
700 712 pdf = ITCPDF.new(current_language)
701 713 pdf.SetTitle(project.name)
702 714 pdf.alias_nb_pages
703 715 pdf.footer_date = format_date(Date.today)
704 716 pdf.AddPage
705 717 pdf.SetFontStyle('B',11)
706 718 pdf.RDMMultiCell(190,5, project.name)
707 719 pdf.Ln
708 720 # Set resize image scale
709 721 pdf.SetImageScale(1.6)
710 722 pdf.SetFontStyle('',9)
711 723 write_page_hierarchy(pdf, pages.group_by(&:parent_id))
712 724 pdf.Output
713 725 end
714 726
715 727 # Returns a PDF string of a single wiki page
716 728 def wiki_page_to_pdf(page, project)
717 729 pdf = ITCPDF.new(current_language)
718 730 pdf.SetTitle("#{project} - #{page.title}")
719 731 pdf.alias_nb_pages
720 732 pdf.footer_date = format_date(Date.today)
721 733 pdf.AddPage
722 734 pdf.SetFontStyle('B',11)
723 735 pdf.RDMMultiCell(190,5,
724 736 "#{project} - #{page.title} - # #{page.content.version}")
725 737 pdf.Ln
726 738 # Set resize image scale
727 739 pdf.SetImageScale(1.6)
728 740 pdf.SetFontStyle('',9)
729 741 write_wiki_page(pdf, page)
730 742 pdf.Output
731 743 end
732 744
733 745 def write_page_hierarchy(pdf, pages, node=nil, level=0)
734 746 if pages[node]
735 747 pages[node].each do |page|
736 748 if @new_page
737 749 pdf.AddPage
738 750 else
739 751 @new_page = true
740 752 end
741 753 pdf.Bookmark page.title, level
742 754 write_wiki_page(pdf, page)
743 755 write_page_hierarchy(pdf, pages, page.id, level + 1) if pages[page.id]
744 756 end
745 757 end
746 758 end
747 759
748 760 def write_wiki_page(pdf, page)
749 761 pdf.RDMwriteHTMLCell(190,5,0,0,
750 762 page.content.text.to_s, page.attachments, 0)
751 763 if page.attachments.any?
752 764 pdf.Ln
753 765 pdf.SetFontStyle('B',9)
754 766 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
755 767 pdf.Ln
756 768 for attachment in page.attachments
757 769 pdf.SetFontStyle('',8)
758 770 pdf.RDMCell(80,5, attachment.filename)
759 771 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
760 772 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
761 773 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
762 774 pdf.Ln
763 775 end
764 776 end
765 777 end
766 778
767 779 class RDMPdfEncoding
768 780 def self.rdm_from_utf8(txt, encoding)
769 781 txt ||= ''
770 782 txt = Redmine::CodesetUtil.from_utf8(txt, encoding)
771 783 if txt.respond_to?(:force_encoding)
772 784 txt.force_encoding('ASCII-8BIT')
773 785 end
774 786 txt
775 787 end
776 788
777 789 def self.attach(attachments, filename, encoding)
778 790 filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding)
779 791 atta = nil
780 792 if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i
781 793 atta = Attachment.latest_attach(attachments, filename_utf8)
782 794 end
783 795 if atta && atta.readable? && atta.visible?
784 796 return atta
785 797 else
786 798 return nil
787 799 end
788 800 end
789 801 end
790 802 end
791 803 end
792 804 end
@@ -1,420 +1,424
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'cgi'
19 19
20 if RUBY_VERSION < '1.9'
21 require 'iconv'
22 end
23
20 24 module Redmine
21 25 module Scm
22 26 module Adapters
23 27 class CommandFailed < StandardError #:nodoc:
24 28 end
25 29
26 30 class AbstractAdapter #:nodoc:
27 31
28 32 # raised if scm command exited with error, e.g. unknown revision.
29 33 class ScmCommandAborted < CommandFailed; end
30 34
31 35 class << self
32 36 def client_command
33 37 ""
34 38 end
35 39
36 40 def shell_quote_command
37 41 if Redmine::Platform.mswin? && RUBY_PLATFORM == 'java'
38 42 client_command
39 43 else
40 44 shell_quote(client_command)
41 45 end
42 46 end
43 47
44 48 # Returns the version of the scm client
45 49 # Eg: [1, 5, 0] or [] if unknown
46 50 def client_version
47 51 []
48 52 end
49 53
50 54 # Returns the version string of the scm client
51 55 # Eg: '1.5.0' or 'Unknown version' if unknown
52 56 def client_version_string
53 57 v = client_version || 'Unknown version'
54 58 v.is_a?(Array) ? v.join('.') : v.to_s
55 59 end
56 60
57 61 # Returns true if the current client version is above
58 62 # or equals the given one
59 63 # If option is :unknown is set to true, it will return
60 64 # true if the client version is unknown
61 65 def client_version_above?(v, options={})
62 66 ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown])
63 67 end
64 68
65 69 def client_available
66 70 true
67 71 end
68 72
69 73 def shell_quote(str)
70 74 if Redmine::Platform.mswin?
71 75 '"' + str.gsub(/"/, '\\"') + '"'
72 76 else
73 77 "'" + str.gsub(/'/, "'\"'\"'") + "'"
74 78 end
75 79 end
76 80 end
77 81
78 82 def initialize(url, root_url=nil, login=nil, password=nil,
79 83 path_encoding=nil)
80 84 @url = url
81 85 @login = login if login && !login.empty?
82 86 @password = (password || "") if @login
83 87 @root_url = root_url.blank? ? retrieve_root_url : root_url
84 88 end
85 89
86 90 def adapter_name
87 91 'Abstract'
88 92 end
89 93
90 94 def supports_cat?
91 95 true
92 96 end
93 97
94 98 def supports_annotate?
95 99 respond_to?('annotate')
96 100 end
97 101
98 102 def root_url
99 103 @root_url
100 104 end
101 105
102 106 def url
103 107 @url
104 108 end
105 109
106 110 def path_encoding
107 111 nil
108 112 end
109 113
110 114 # get info about the svn repository
111 115 def info
112 116 return nil
113 117 end
114 118
115 119 # Returns the entry identified by path and revision identifier
116 120 # or nil if entry doesn't exist in the repository
117 121 def entry(path=nil, identifier=nil)
118 122 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
119 123 search_path = parts[0..-2].join('/')
120 124 search_name = parts[-1]
121 125 if search_path.blank? && search_name.blank?
122 126 # Root entry
123 127 Entry.new(:path => '', :kind => 'dir')
124 128 else
125 129 # Search for the entry in the parent directory
126 130 es = entries(search_path, identifier)
127 131 es ? es.detect {|e| e.name == search_name} : nil
128 132 end
129 133 end
130 134
131 135 # Returns an Entries collection
132 136 # or nil if the given path doesn't exist in the repository
133 137 def entries(path=nil, identifier=nil, options={})
134 138 return nil
135 139 end
136 140
137 141 def branches
138 142 return nil
139 143 end
140 144
141 145 def tags
142 146 return nil
143 147 end
144 148
145 149 def default_branch
146 150 return nil
147 151 end
148 152
149 153 def properties(path, identifier=nil)
150 154 return nil
151 155 end
152 156
153 157 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
154 158 return nil
155 159 end
156 160
157 161 def diff(path, identifier_from, identifier_to=nil)
158 162 return nil
159 163 end
160 164
161 165 def cat(path, identifier=nil)
162 166 return nil
163 167 end
164 168
165 169 def with_leading_slash(path)
166 170 path ||= ''
167 171 (path[0,1]!="/") ? "/#{path}" : path
168 172 end
169 173
170 174 def with_trailling_slash(path)
171 175 path ||= ''
172 176 (path[-1,1] == "/") ? path : "#{path}/"
173 177 end
174 178
175 179 def without_leading_slash(path)
176 180 path ||= ''
177 181 path.gsub(%r{^/+}, '')
178 182 end
179 183
180 184 def without_trailling_slash(path)
181 185 path ||= ''
182 186 (path[-1,1] == "/") ? path[0..-2] : path
183 187 end
184 188
185 189 def shell_quote(str)
186 190 self.class.shell_quote(str)
187 191 end
188 192
189 193 private
190 194 def retrieve_root_url
191 195 info = self.info
192 196 info ? info.root_url : nil
193 197 end
194 198
195 199 def target(path, sq=true)
196 200 path ||= ''
197 201 base = path.match(/^\//) ? root_url : url
198 202 str = "#{base}/#{path}".gsub(/[?<>\*]/, '')
199 203 if sq
200 204 str = shell_quote(str)
201 205 end
202 206 str
203 207 end
204 208
205 209 def logger
206 210 self.class.logger
207 211 end
208 212
209 213 def shellout(cmd, options = {}, &block)
210 214 self.class.shellout(cmd, options, &block)
211 215 end
212 216
213 217 def self.logger
214 218 Rails.logger
215 219 end
216 220
217 221 def self.shellout(cmd, options = {}, &block)
218 222 if logger && logger.debug?
219 223 logger.debug "Shelling out: #{strip_credential(cmd)}"
220 224 end
221 225 if Rails.env == 'development'
222 226 # Capture stderr when running in dev environment
223 227 cmd = "#{cmd} 2>>#{shell_quote(Rails.root.join('log/scm.stderr.log').to_s)}"
224 228 end
225 229 begin
226 230 mode = "r+"
227 231 IO.popen(cmd, mode) do |io|
228 232 io.set_encoding("ASCII-8BIT") if io.respond_to?(:set_encoding)
229 233 io.close_write unless options[:write_stdin]
230 234 block.call(io) if block_given?
231 235 end
232 236 ## If scm command does not exist,
233 237 ## Linux JRuby 1.6.2 (ruby-1.8.7-p330) raises java.io.IOException
234 238 ## in production environment.
235 239 # rescue Errno::ENOENT => e
236 240 rescue Exception => e
237 241 msg = strip_credential(e.message)
238 242 # The command failed, log it and re-raise
239 243 logmsg = "SCM command failed, "
240 244 logmsg += "make sure that your SCM command (e.g. svn) is "
241 245 logmsg += "in PATH (#{ENV['PATH']})\n"
242 246 logmsg += "You can configure your scm commands in config/configuration.yml.\n"
243 247 logmsg += "#{strip_credential(cmd)}\n"
244 248 logmsg += "with: #{msg}"
245 249 logger.error(logmsg)
246 250 raise CommandFailed.new(msg)
247 251 end
248 252 end
249 253
250 254 # Hides username/password in a given command
251 255 def self.strip_credential(cmd)
252 256 q = (Redmine::Platform.mswin? ? '"' : "'")
253 257 cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx')
254 258 end
255 259
256 260 def strip_credential(cmd)
257 261 self.class.strip_credential(cmd)
258 262 end
259 263
260 264 def scm_iconv(to, from, str)
261 265 return nil if str.nil?
262 266 return str if to == from
263 267 if str.respond_to?(:force_encoding)
264 268 str.force_encoding(from)
265 269 begin
266 270 str.encode(to)
267 271 rescue Exception => err
268 272 logger.error("failed to convert from #{from} to #{to}. #{err}")
269 273 nil
270 274 end
271 275 else
272 276 begin
273 277 Iconv.conv(to, from, str)
274 278 rescue Iconv::Failure => err
275 279 logger.error("failed to convert from #{from} to #{to}. #{err}")
276 280 nil
277 281 end
278 282 end
279 283 end
280 284
281 285 def parse_xml(xml)
282 286 if RUBY_PLATFORM == 'java'
283 287 xml = xml.sub(%r{<\?xml[^>]*\?>}, '')
284 288 end
285 289 ActiveSupport::XmlMini.parse(xml)
286 290 end
287 291 end
288 292
289 293 class Entries < Array
290 294 def sort_by_name
291 295 dup.sort! {|x,y|
292 296 if x.kind == y.kind
293 297 x.name.to_s <=> y.name.to_s
294 298 else
295 299 x.kind <=> y.kind
296 300 end
297 301 }
298 302 end
299 303
300 304 def revisions
301 305 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
302 306 end
303 307 end
304 308
305 309 class Info
306 310 attr_accessor :root_url, :lastrev
307 311 def initialize(attributes={})
308 312 self.root_url = attributes[:root_url] if attributes[:root_url]
309 313 self.lastrev = attributes[:lastrev]
310 314 end
311 315 end
312 316
313 317 class Entry
314 318 attr_accessor :name, :path, :kind, :size, :lastrev, :changeset
315 319
316 320 def initialize(attributes={})
317 321 self.name = attributes[:name] if attributes[:name]
318 322 self.path = attributes[:path] if attributes[:path]
319 323 self.kind = attributes[:kind] if attributes[:kind]
320 324 self.size = attributes[:size].to_i if attributes[:size]
321 325 self.lastrev = attributes[:lastrev]
322 326 end
323 327
324 328 def is_file?
325 329 'file' == self.kind
326 330 end
327 331
328 332 def is_dir?
329 333 'dir' == self.kind
330 334 end
331 335
332 336 def is_text?
333 337 Redmine::MimeType.is_type?('text', name)
334 338 end
335 339
336 340 def author
337 341 if changeset
338 342 changeset.author.to_s
339 343 elsif lastrev
340 344 Redmine::CodesetUtil.replace_invalid_utf8(lastrev.author.to_s.split('<').first)
341 345 end
342 346 end
343 347 end
344 348
345 349 class Revisions < Array
346 350 def latest
347 351 sort {|x,y|
348 352 unless x.time.nil? or y.time.nil?
349 353 x.time <=> y.time
350 354 else
351 355 0
352 356 end
353 357 }.last
354 358 end
355 359 end
356 360
357 361 class Revision
358 362 attr_accessor :scmid, :name, :author, :time, :message,
359 363 :paths, :revision, :branch, :identifier,
360 364 :parents
361 365
362 366 def initialize(attributes={})
363 367 self.identifier = attributes[:identifier]
364 368 self.scmid = attributes[:scmid]
365 369 self.name = attributes[:name] || self.identifier
366 370 self.author = attributes[:author]
367 371 self.time = attributes[:time]
368 372 self.message = attributes[:message] || ""
369 373 self.paths = attributes[:paths]
370 374 self.revision = attributes[:revision]
371 375 self.branch = attributes[:branch]
372 376 self.parents = attributes[:parents]
373 377 end
374 378
375 379 # Returns the readable identifier.
376 380 def format_identifier
377 381 self.identifier.to_s
378 382 end
379 383
380 384 def ==(other)
381 385 if other.nil?
382 386 false
383 387 elsif scmid.present?
384 388 scmid == other.scmid
385 389 elsif identifier.present?
386 390 identifier == other.identifier
387 391 elsif revision.present?
388 392 revision == other.revision
389 393 end
390 394 end
391 395 end
392 396
393 397 class Annotate
394 398 attr_reader :lines, :revisions
395 399
396 400 def initialize
397 401 @lines = []
398 402 @revisions = []
399 403 end
400 404
401 405 def add_line(line, revision)
402 406 @lines << line
403 407 @revisions << revision
404 408 end
405 409
406 410 def content
407 411 content = lines.join("\n")
408 412 end
409 413
410 414 def empty?
411 415 lines.empty?
412 416 end
413 417 end
414 418
415 419 class Branch < String
416 420 attr_accessor :revision, :scmid
417 421 end
418 422 end
419 423 end
420 424 end
@@ -1,129 +1,128
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../../../../test_helper', __FILE__)
19 require 'iconv'
20 19
21 20 class PdfTest < ActiveSupport::TestCase
22 21 fixtures :users, :projects, :roles, :members, :member_roles,
23 22 :enabled_modules, :issues, :trackers, :attachments
24 23
25 24 def test_fix_text_encoding_nil
26 25 assert_equal '', Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(nil, "UTF-8")
27 26 assert_equal '', Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(nil, "ISO-8859-1")
28 27 end
29 28
30 29 def test_rdm_pdf_iconv_cannot_convert_ja_cp932
31 30 encoding = ( RUBY_PLATFORM == 'java' ? "SJIS" : "CP932" )
32 31 utf8_txt_1 = "\xe7\x8b\x80\xe6\x85\x8b"
33 32 utf8_txt_2 = "\xe7\x8b\x80\xe6\x85\x8b\xe7\x8b\x80"
34 33 utf8_txt_3 = "\xe7\x8b\x80\xe7\x8b\x80\xe6\x85\x8b\xe7\x8b\x80"
35 34 if utf8_txt_1.respond_to?(:force_encoding)
36 35 txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_1, encoding)
37 36 txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_2, encoding)
38 37 txt_3 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_3, encoding)
39 38 assert_equal "?\x91\xd4", txt_1
40 39 assert_equal "?\x91\xd4?", txt_2
41 40 assert_equal "??\x91\xd4?", txt_3
42 41 assert_equal "ASCII-8BIT", txt_1.encoding.to_s
43 42 assert_equal "ASCII-8BIT", txt_2.encoding.to_s
44 43 assert_equal "ASCII-8BIT", txt_3.encoding.to_s
45 44 elsif RUBY_PLATFORM == 'java'
46 45 assert_equal "??",
47 46 Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_1, encoding)
48 47 assert_equal "???",
49 48 Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_2, encoding)
50 49 assert_equal "????",
51 50 Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_3, encoding)
52 51 else
53 52 assert_equal "???\x91\xd4",
54 53 Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_1, encoding)
55 54 assert_equal "???\x91\xd4???",
56 55 Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_2, encoding)
57 56 assert_equal "??????\x91\xd4???",
58 57 Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_3, encoding)
59 58 end
60 59 end
61 60
62 61 def test_rdm_pdf_iconv_invalid_utf8_should_be_replaced_en
63 62 str1 = "Texte encod\xe9 en ISO-8859-1"
64 63 str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test"
65 64 str1.force_encoding("UTF-8") if str1.respond_to?(:force_encoding)
66 65 str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding)
67 66 txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str1, 'UTF-8')
68 67 txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str2, 'UTF-8')
69 68 if txt_1.respond_to?(:force_encoding)
70 69 assert_equal "ASCII-8BIT", txt_1.encoding.to_s
71 70 assert_equal "ASCII-8BIT", txt_2.encoding.to_s
72 71 end
73 72 assert_equal "Texte encod? en ISO-8859-1", txt_1
74 73 assert_equal "?a?b?c?d?e test", txt_2
75 74 end
76 75
77 76 def test_rdm_pdf_iconv_invalid_utf8_should_be_replaced_ja
78 77 str1 = "Texte encod\xe9 en ISO-8859-1"
79 78 str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test"
80 79 str1.force_encoding("UTF-8") if str1.respond_to?(:force_encoding)
81 80 str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding)
82 81 encoding = ( RUBY_PLATFORM == 'java' ? "SJIS" : "CP932" )
83 82 txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str1, encoding)
84 83 txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str2, encoding)
85 84 if txt_1.respond_to?(:force_encoding)
86 85 assert_equal "ASCII-8BIT", txt_1.encoding.to_s
87 86 assert_equal "ASCII-8BIT", txt_2.encoding.to_s
88 87 end
89 88 assert_equal "Texte encod? en ISO-8859-1", txt_1
90 89 assert_equal "?a?b?c?d?e test", txt_2
91 90 end
92 91
93 92 def test_attach
94 93 set_fixtures_attachments_directory
95 94
96 95 str2 = "\x83e\x83X\x83g"
97 96 str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding)
98 97 encoding = ( RUBY_PLATFORM == 'java' ? "SJIS" : "CP932" )
99 98
100 99 a1 = Attachment.find(17)
101 100 a2 = Attachment.find(19)
102 101
103 102 User.current = User.find(1)
104 103 assert a1.readable?
105 104 assert a1.visible?
106 105 assert a2.readable?
107 106 assert a2.visible?
108 107
109 108 aa1 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "Testfile.PNG", "UTF-8")
110 109 assert_not_nil aa1
111 110 assert_equal 17, aa1.id
112 111 aa2 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "test#{str2}.png", encoding)
113 112 assert_not_nil aa2
114 113 assert_equal 19, aa2.id
115 114
116 115 User.current = nil
117 116 assert a1.readable?
118 117 assert (! a1.visible?)
119 118 assert a2.readable?
120 119 assert (! a2.visible?)
121 120
122 121 aa1 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "Testfile.PNG", "UTF-8")
123 122 assert_equal nil, aa1
124 123 aa2 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "test#{str2}.png", encoding)
125 124 assert_equal nil, aa2
126 125
127 126 set_tmp_attachments_directory
128 127 end
129 128 end
General Comments 0
You need to be logged in to leave comments. Login now