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