Mercurial > hg > soundsoftware-site
comparison .svn/pristine/e7/e77d4ee988f0ee0cedc3ea42fee306534d1cb4e6.svn-base @ 909:cbb26bc654de redmine-1.3
Update to Redmine 1.3-stable branch (Redmine SVN rev 8964)
author | Chris Cannam |
---|---|
date | Fri, 24 Feb 2012 19:09:32 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
908:c6c2cbd0afee | 909:cbb26bc654de |
---|---|
1 # encoding: utf-8 | |
2 # | |
3 # Redmine - project management software | |
4 # Copyright (C) 2006-2011 Jean-Philippe Lang | |
5 # | |
6 # This program is free software; you can redistribute it and/or | |
7 # modify it under the terms of the GNU General Public License | |
8 # as published by the Free Software Foundation; either version 2 | |
9 # of the License, or (at your option) any later version. | |
10 # | |
11 # This program is distributed in the hope that it will be useful, | |
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 # GNU General Public License for more details. | |
15 # | |
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 | |
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
19 | |
20 require 'iconv' | |
21 require 'fpdf/chinese' | |
22 require 'fpdf/japanese' | |
23 require 'fpdf/korean' | |
24 require 'core/rmagick' | |
25 | |
26 module Redmine | |
27 module Export | |
28 module PDF | |
29 include ActionView::Helpers::TextHelper | |
30 include ActionView::Helpers::NumberHelper | |
31 include IssuesHelper | |
32 | |
33 class ITCPDF < TCPDF | |
34 include Redmine::I18n | |
35 attr_accessor :footer_date | |
36 | |
37 def initialize(lang) | |
38 @@k_path_cache = Rails.root.join('tmp', 'pdf') | |
39 FileUtils.mkdir_p @@k_path_cache unless File::exist?(@@k_path_cache) | |
40 set_language_if_valid lang | |
41 pdf_encoding = l(:general_pdf_encoding).upcase | |
42 super('P', 'mm', 'A4', (pdf_encoding == 'UTF-8'), pdf_encoding) | |
43 case current_language.to_s.downcase | |
44 when 'vi' | |
45 @font_for_content = 'DejaVuSans' | |
46 @font_for_footer = 'DejaVuSans' | |
47 else | |
48 case pdf_encoding | |
49 when 'UTF-8' | |
50 @font_for_content = 'FreeSans' | |
51 @font_for_footer = 'FreeSans' | |
52 when 'CP949' | |
53 extend(PDF_Korean) | |
54 AddUHCFont() | |
55 @font_for_content = 'UHC' | |
56 @font_for_footer = 'UHC' | |
57 when 'CP932', 'SJIS', 'SHIFT_JIS' | |
58 extend(PDF_Japanese) | |
59 AddSJISFont() | |
60 @font_for_content = 'SJIS' | |
61 @font_for_footer = 'SJIS' | |
62 when 'GB18030' | |
63 extend(PDF_Chinese) | |
64 AddGBFont() | |
65 @font_for_content = 'GB' | |
66 @font_for_footer = 'GB' | |
67 when 'BIG5' | |
68 extend(PDF_Chinese) | |
69 AddBig5Font() | |
70 @font_for_content = 'Big5' | |
71 @font_for_footer = 'Big5' | |
72 else | |
73 @font_for_content = 'Arial' | |
74 @font_for_footer = 'Helvetica' | |
75 end | |
76 end | |
77 SetCreator(Redmine::Info.app_name) | |
78 SetFont(@font_for_content) | |
79 end | |
80 | |
81 def SetFontStyle(style, size) | |
82 SetFont(@font_for_content, style, size) | |
83 end | |
84 | |
85 def SetTitle(txt) | |
86 txt = begin | |
87 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt) | |
88 hextxt = "<FEFF" # FEFF is BOM | |
89 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join | |
90 hextxt << ">" | |
91 rescue | |
92 txt | |
93 end || '' | |
94 super(txt) | |
95 end | |
96 | |
97 def textstring(s) | |
98 # Format a text string | |
99 if s =~ /^</ # This means the string is hex-dumped. | |
100 return s | |
101 else | |
102 return '('+escape(s)+')' | |
103 end | |
104 end | |
105 | |
106 def fix_text_encoding(txt) | |
107 RDMPdfEncoding::rdm_from_utf8(txt, l(:general_pdf_encoding)) | |
108 end | |
109 | |
110 def RDMCell(w ,h=0, txt='', border=0, ln=0, align='', fill=0, link='') | |
111 Cell(w, h, fix_text_encoding(txt), border, ln, align, fill, link) | |
112 end | |
113 | |
114 def RDMMultiCell(w, h=0, txt='', border=0, align='', fill=0, ln=1) | |
115 MultiCell(w, h, fix_text_encoding(txt), border, align, fill, ln) | |
116 end | |
117 | |
118 def RDMwriteHTMLCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0) | |
119 @attachments = attachments | |
120 writeHTMLCell(w, h, x, y, | |
121 fix_text_encoding( | |
122 Redmine::WikiFormatting.to_html(Setting.text_formatting, txt)), | |
123 border, ln, fill) | |
124 end | |
125 | |
126 def getImageFilename(attrname) | |
127 # attrname: general_pdf_encoding string file/uri name | |
128 atta = RDMPdfEncoding.attach(@attachments, attrname, l(:general_pdf_encoding)) | |
129 if atta | |
130 return atta.diskfile | |
131 else | |
132 return nil | |
133 end | |
134 end | |
135 | |
136 def Footer | |
137 SetFont(@font_for_footer, 'I', 8) | |
138 SetY(-15) | |
139 SetX(15) | |
140 RDMCell(0, 5, @footer_date, 0, 0, 'L') | |
141 SetY(-15) | |
142 SetX(-30) | |
143 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C') | |
144 end | |
145 end | |
146 | |
147 # Returns a PDF string of a list of issues | |
148 def issues_to_pdf(issues, project, query) | |
149 pdf = ITCPDF.new(current_language) | |
150 title = query.new_record? ? l(:label_issue_plural) : query.name | |
151 title = "#{project} - #{title}" if project | |
152 pdf.SetTitle(title) | |
153 pdf.alias_nb_pages | |
154 pdf.footer_date = format_date(Date.today) | |
155 pdf.SetAutoPageBreak(false) | |
156 pdf.AddPage("L") | |
157 | |
158 # Landscape A4 = 210 x 297 mm | |
159 page_height = 210 | |
160 page_width = 297 | |
161 right_margin = 10 | |
162 bottom_margin = 20 | |
163 col_id_width = 10 | |
164 row_height = 5 | |
165 | |
166 # column widths | |
167 table_width = page_width - right_margin - 10 # fixed left margin | |
168 col_width = [] | |
169 unless query.columns.empty? | |
170 col_width = query.columns.collect do |c| | |
171 (c.name == :subject || (c.is_a?(QueryCustomFieldColumn) && | |
172 ['string', 'text'].include?(c.custom_field.field_format))) ? 4.0 : 1.0 | |
173 end | |
174 ratio = (table_width - col_id_width) / col_width.inject(0) {|s,w| s += w} | |
175 col_width = col_width.collect {|w| w * ratio} | |
176 end | |
177 | |
178 # title | |
179 pdf.SetFontStyle('B',11) | |
180 pdf.RDMCell(190,10, title) | |
181 pdf.Ln | |
182 | |
183 # headers | |
184 pdf.SetFontStyle('B',8) | |
185 pdf.SetFillColor(230, 230, 230) | |
186 | |
187 # render it background to find the max height used | |
188 base_x = pdf.GetX | |
189 base_y = pdf.GetY | |
190 max_height = issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true) | |
191 pdf.Rect(base_x, base_y, table_width, max_height, 'FD'); | |
192 pdf.SetXY(base_x, base_y); | |
193 | |
194 # write the cells on page | |
195 pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1) | |
196 issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true) | |
197 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width) | |
198 pdf.SetY(base_y + max_height); | |
199 | |
200 # rows | |
201 pdf.SetFontStyle('',8) | |
202 pdf.SetFillColor(255, 255, 255) | |
203 previous_group = false | |
204 issue_list(issues) do |issue, level| | |
205 if query.grouped? && | |
206 (group = query.group_by_column.value(issue)) != previous_group | |
207 pdf.SetFontStyle('B',9) | |
208 pdf.RDMCell(277, row_height, | |
209 (group.blank? ? 'None' : group.to_s) + " (#{query.issue_count_by_group[group]})", | |
210 1, 1, 'L') | |
211 pdf.SetFontStyle('',8) | |
212 previous_group = group | |
213 end | |
214 # fetch all the row values | |
215 col_values = query.columns.collect do |column| | |
216 s = if column.is_a?(QueryCustomFieldColumn) | |
217 cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id} | |
218 show_value(cv) | |
219 else | |
220 value = issue.send(column.name) | |
221 if column.name == :subject | |
222 value = " " * level + value | |
223 end | |
224 if value.is_a?(Date) | |
225 format_date(value) | |
226 elsif value.is_a?(Time) | |
227 format_time(value) | |
228 else | |
229 value | |
230 end | |
231 end | |
232 s.to_s | |
233 end | |
234 | |
235 # render it off-page to find the max height used | |
236 base_x = pdf.GetX | |
237 base_y = pdf.GetY | |
238 pdf.SetY(2 * page_height) | |
239 max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height) | |
240 pdf.SetXY(base_x, base_y) | |
241 | |
242 # make new page if it doesn't fit on the current one | |
243 space_left = page_height - base_y - bottom_margin | |
244 if max_height > space_left | |
245 pdf.AddPage("L") | |
246 base_x = pdf.GetX | |
247 base_y = pdf.GetY | |
248 end | |
249 | |
250 # write the cells on page | |
251 pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1) | |
252 issues_to_pdf_write_cells(pdf, col_values, col_width, row_height) | |
253 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width) | |
254 pdf.SetY(base_y + max_height); | |
255 end | |
256 | |
257 if issues.size == Setting.issues_export_limit.to_i | |
258 pdf.SetFontStyle('B',10) | |
259 pdf.RDMCell(0, row_height, '...') | |
260 end | |
261 pdf.Output | |
262 end | |
263 | |
264 # Renders MultiCells and returns the maximum height used | |
265 def issues_to_pdf_write_cells(pdf, col_values, col_widths, | |
266 row_height, head=false) | |
267 base_y = pdf.GetY | |
268 max_height = row_height | |
269 col_values.each_with_index do |column, i| | |
270 col_x = pdf.GetX | |
271 if head == true | |
272 pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1) | |
273 else | |
274 pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1) | |
275 end | |
276 max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height | |
277 pdf.SetXY(col_x + col_widths[i], base_y); | |
278 end | |
279 return max_height | |
280 end | |
281 | |
282 # Draw lines to close the row (MultiCell border drawing in not uniform) | |
283 def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y, | |
284 id_width, col_widths) | |
285 col_x = top_x + id_width | |
286 pdf.Line(col_x, top_y, col_x, lower_y) # id right border | |
287 col_widths.each do |width| | |
288 col_x += width | |
289 pdf.Line(col_x, top_y, col_x, lower_y) # columns right border | |
290 end | |
291 pdf.Line(top_x, top_y, top_x, lower_y) # left border | |
292 pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border | |
293 end | |
294 | |
295 # Returns a PDF string of a single issue | |
296 def issue_to_pdf(issue) | |
297 pdf = ITCPDF.new(current_language) | |
298 pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}") | |
299 pdf.alias_nb_pages | |
300 pdf.footer_date = format_date(Date.today) | |
301 pdf.AddPage | |
302 pdf.SetFontStyle('B',11) | |
303 buf = "#{issue.project} - #{issue.tracker} # #{issue.id}" | |
304 pdf.RDMMultiCell(190, 5, buf) | |
305 pdf.Ln | |
306 pdf.SetFontStyle('',8) | |
307 base_x = pdf.GetX | |
308 i = 1 | |
309 issue.ancestors.each do |ancestor| | |
310 pdf.SetX(base_x + i) | |
311 buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}" | |
312 pdf.RDMMultiCell(190 - i, 5, buf) | |
313 i += 1 if i < 35 | |
314 end | |
315 pdf.Ln | |
316 | |
317 pdf.SetFontStyle('B',9) | |
318 pdf.RDMCell(35,5, l(:field_status) + ":","LT") | |
319 pdf.SetFontStyle('',9) | |
320 pdf.RDMCell(60,5, issue.status.to_s,"RT") | |
321 pdf.SetFontStyle('B',9) | |
322 pdf.RDMCell(35,5, l(:field_priority) + ":","LT") | |
323 pdf.SetFontStyle('',9) | |
324 pdf.RDMCell(60,5, issue.priority.to_s,"RT") | |
325 pdf.Ln | |
326 | |
327 pdf.SetFontStyle('B',9) | |
328 pdf.RDMCell(35,5, l(:field_author) + ":","L") | |
329 pdf.SetFontStyle('',9) | |
330 pdf.RDMCell(60,5, issue.author.to_s,"R") | |
331 pdf.SetFontStyle('B',9) | |
332 pdf.RDMCell(35,5, l(:field_category) + ":","L") | |
333 pdf.SetFontStyle('',9) | |
334 pdf.RDMCell(60,5, issue.category.to_s,"R") | |
335 pdf.Ln | |
336 | |
337 pdf.SetFontStyle('B',9) | |
338 pdf.RDMCell(35,5, l(:field_created_on) + ":","L") | |
339 pdf.SetFontStyle('',9) | |
340 pdf.RDMCell(60,5, format_date(issue.created_on),"R") | |
341 pdf.SetFontStyle('B',9) | |
342 pdf.RDMCell(35,5, l(:field_assigned_to) + ":","L") | |
343 pdf.SetFontStyle('',9) | |
344 pdf.RDMCell(60,5, issue.assigned_to.to_s,"R") | |
345 pdf.Ln | |
346 | |
347 pdf.SetFontStyle('B',9) | |
348 pdf.RDMCell(35,5, l(:field_updated_on) + ":","LB") | |
349 pdf.SetFontStyle('',9) | |
350 pdf.RDMCell(60,5, format_date(issue.updated_on),"RB") | |
351 pdf.SetFontStyle('B',9) | |
352 pdf.RDMCell(35,5, l(:field_due_date) + ":","LB") | |
353 pdf.SetFontStyle('',9) | |
354 pdf.RDMCell(60,5, format_date(issue.due_date),"RB") | |
355 pdf.Ln | |
356 | |
357 for custom_value in issue.custom_field_values | |
358 pdf.SetFontStyle('B',9) | |
359 pdf.RDMCell(35,5, custom_value.custom_field.name + ":","L") | |
360 pdf.SetFontStyle('',9) | |
361 pdf.RDMMultiCell(155,5, (show_value custom_value),"R") | |
362 end | |
363 | |
364 y0 = pdf.GetY | |
365 | |
366 pdf.SetFontStyle('B',9) | |
367 pdf.RDMCell(35,5, l(:field_subject) + ":","LT") | |
368 pdf.SetFontStyle('',9) | |
369 pdf.RDMMultiCell(155,5, issue.subject,"RT") | |
370 pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY) | |
371 | |
372 pdf.SetFontStyle('B',9) | |
373 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1) | |
374 pdf.SetFontStyle('',9) | |
375 | |
376 # Set resize image scale | |
377 pdf.SetImageScale(1.6) | |
378 pdf.RDMwriteHTMLCell(35+155, 5, 0, 0, | |
379 issue.description.to_s, issue.attachments, "LRB") | |
380 | |
381 unless issue.leaf? | |
382 # for CJK | |
383 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 ) | |
384 | |
385 pdf.SetFontStyle('B',9) | |
386 pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR") | |
387 pdf.Ln | |
388 issue_list(issue.descendants.sort_by(&:lft)) do |child, level| | |
389 buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}", | |
390 :length => truncate_length) | |
391 level = 10 if level >= 10 | |
392 pdf.SetFontStyle('',8) | |
393 pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L") | |
394 pdf.SetFontStyle('B',8) | |
395 pdf.RDMCell(20,5, child.status.to_s, "R") | |
396 pdf.Ln | |
397 end | |
398 end | |
399 | |
400 relations = issue.relations.select { |r| r.other_issue(issue).visible? } | |
401 unless relations.empty? | |
402 # for CJK | |
403 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 ) | |
404 | |
405 pdf.SetFontStyle('B',9) | |
406 pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR") | |
407 pdf.Ln | |
408 relations.each do |relation| | |
409 buf = "" | |
410 buf += "#{l(relation.label_for(issue))} " | |
411 if relation.delay && relation.delay != 0 | |
412 buf += "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)}) " | |
413 end | |
414 if Setting.cross_project_issue_relations? | |
415 buf += "#{relation.other_issue(issue).project} - " | |
416 end | |
417 buf += "#{relation.other_issue(issue).tracker}" + | |
418 " # #{relation.other_issue(issue).id}: #{relation.other_issue(issue).subject}" | |
419 buf = truncate(buf, :length => truncate_length) | |
420 pdf.SetFontStyle('', 8) | |
421 pdf.RDMCell(35+155-60, 5, buf, "L") | |
422 pdf.SetFontStyle('B',8) | |
423 pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "") | |
424 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "") | |
425 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), "R") | |
426 pdf.Ln | |
427 end | |
428 end | |
429 pdf.RDMCell(190,5, "", "T") | |
430 pdf.Ln | |
431 | |
432 if issue.changesets.any? && | |
433 User.current.allowed_to?(:view_changesets, issue.project) | |
434 pdf.SetFontStyle('B',9) | |
435 pdf.RDMCell(190,5, l(:label_associated_revisions), "B") | |
436 pdf.Ln | |
437 for changeset in issue.changesets | |
438 pdf.SetFontStyle('B',8) | |
439 csstr = "#{l(:label_revision)} #{changeset.format_identifier} - " | |
440 csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s | |
441 pdf.RDMCell(190, 5, csstr) | |
442 pdf.Ln | |
443 unless changeset.comments.blank? | |
444 pdf.SetFontStyle('',8) | |
445 pdf.RDMwriteHTMLCell(190,5,0,0, | |
446 changeset.comments.to_s, issue.attachments, "") | |
447 end | |
448 pdf.Ln | |
449 end | |
450 end | |
451 | |
452 pdf.SetFontStyle('B',9) | |
453 pdf.RDMCell(190,5, l(:label_history), "B") | |
454 pdf.Ln | |
455 indice = 0 | |
456 for journal in issue.journals.find( | |
457 :all, :include => [:user, :details], | |
458 :order => "#{Journal.table_name}.created_on ASC") | |
459 indice = indice + 1 | |
460 pdf.SetFontStyle('B',8) | |
461 pdf.RDMCell(190,5, | |
462 "#" + indice.to_s + | |
463 " - " + format_time(journal.created_on) + | |
464 " - " + journal.user.name) | |
465 pdf.Ln | |
466 pdf.SetFontStyle('I',8) | |
467 for detail in journal.details | |
468 pdf.RDMMultiCell(190,5, "- " + show_detail(detail, true)) | |
469 end | |
470 if journal.notes? | |
471 pdf.Ln unless journal.details.empty? | |
472 pdf.SetFontStyle('',8) | |
473 pdf.RDMwriteHTMLCell(190,5,0,0, | |
474 journal.notes.to_s, issue.attachments, "") | |
475 end | |
476 pdf.Ln | |
477 end | |
478 | |
479 if issue.attachments.any? | |
480 pdf.SetFontStyle('B',9) | |
481 pdf.RDMCell(190,5, l(:label_attachment_plural), "B") | |
482 pdf.Ln | |
483 for attachment in issue.attachments | |
484 pdf.SetFontStyle('',8) | |
485 pdf.RDMCell(80,5, attachment.filename) | |
486 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R") | |
487 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R") | |
488 pdf.RDMCell(65,5, attachment.author.name,0,0,"R") | |
489 pdf.Ln | |
490 end | |
491 end | |
492 pdf.Output | |
493 end | |
494 | |
495 # Returns a PDF string of a single wiki page | |
496 def wiki_to_pdf(page, project) | |
497 pdf = ITCPDF.new(current_language) | |
498 pdf.SetTitle("#{project} - #{page.title}") | |
499 pdf.alias_nb_pages | |
500 pdf.footer_date = format_date(Date.today) | |
501 pdf.AddPage | |
502 pdf.SetFontStyle('B',11) | |
503 pdf.RDMMultiCell(190,5, | |
504 "#{project} - #{page.title} - # #{page.content.version}") | |
505 pdf.Ln | |
506 # Set resize image scale | |
507 pdf.SetImageScale(1.6) | |
508 pdf.SetFontStyle('',9) | |
509 pdf.RDMwriteHTMLCell(190,5,0,0, | |
510 page.content.text.to_s, page.attachments, "TLRB") | |
511 if page.attachments.any? | |
512 pdf.Ln | |
513 pdf.SetFontStyle('B',9) | |
514 pdf.RDMCell(190,5, l(:label_attachment_plural), "B") | |
515 pdf.Ln | |
516 for attachment in page.attachments | |
517 pdf.SetFontStyle('',8) | |
518 pdf.RDMCell(80,5, attachment.filename) | |
519 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R") | |
520 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R") | |
521 pdf.RDMCell(65,5, attachment.author.name,0,0,"R") | |
522 pdf.Ln | |
523 end | |
524 end | |
525 pdf.Output | |
526 end | |
527 | |
528 class RDMPdfEncoding | |
529 def self.rdm_from_utf8(txt, encoding) | |
530 txt ||= '' | |
531 txt = Redmine::CodesetUtil.from_utf8(txt, encoding) | |
532 if txt.respond_to?(:force_encoding) | |
533 txt.force_encoding('ASCII-8BIT') | |
534 end | |
535 txt | |
536 end | |
537 | |
538 def self.attach(attachments, filename, encoding) | |
539 filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding) | |
540 atta = nil | |
541 if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i | |
542 atta = Attachment.latest_attach(attachments, filename_utf8) | |
543 end | |
544 if atta && atta.readable? && atta.visible? | |
545 return atta | |
546 else | |
547 return nil | |
548 end | |
549 end | |
550 end | |
551 end | |
552 end | |
553 end |