##// END OF EJS Templates
Merged r2226 and r2233 from trunk....
Jean-Philippe Lang -
r2243:3155b8ccad80
parent child
Show More
@@ -0,0 +1,459
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require 'iconv'
19 require 'rfpdf/chinese'
20
21 module Redmine
22 module Export
23 module PDF
24 class IFPDF < FPDF
25 include GLoc
26 attr_accessor :footer_date
27
28 def initialize(lang)
29 super()
30 set_language_if_valid lang
31 case current_language.to_s
32 when 'ja'
33 extend(PDF_Japanese)
34 AddSJISFont()
35 @font_for_content = 'SJIS'
36 @font_for_footer = 'SJIS'
37 when 'zh'
38 extend(PDF_Chinese)
39 AddGBFont()
40 @font_for_content = 'GB'
41 @font_for_footer = 'GB'
42 when 'zh-tw'
43 extend(PDF_Chinese)
44 AddBig5Font()
45 @font_for_content = 'Big5'
46 @font_for_footer = 'Big5'
47 else
48 @font_for_content = 'Arial'
49 @font_for_footer = 'Helvetica'
50 end
51 SetCreator(Redmine::Info.app_name)
52 SetFont(@font_for_content)
53 end
54
55 def SetFontStyle(style, size)
56 SetFont(@font_for_content, style, size)
57 end
58
59 def SetTitle(txt)
60 txt = begin
61 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
62 hextxt = "<FEFF" # FEFF is BOM
63 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
64 hextxt << ">"
65 rescue
66 txt
67 end || ''
68 super(txt)
69 end
70
71 def textstring(s)
72 # Format a text string
73 if s =~ /^</ # This means the string is hex-dumped.
74 return s
75 else
76 return '('+escape(s)+')'
77 end
78 end
79
80 def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
81 @ic ||= Iconv.new(l(:general_pdf_encoding), 'UTF-8')
82 # these quotation marks are not correctly rendered in the pdf
83 txt = txt.gsub(/[“�]/, '"') if txt
84 txt = begin
85 # 0x5c char handling
86 txtar = txt.split('\\')
87 txtar << '' if txt[-1] == ?\\
88 txtar.collect {|x| @ic.iconv(x)}.join('\\').gsub(/\\/, "\\\\\\\\")
89 rescue
90 txt
91 end || ''
92 super w,h,txt,border,ln,align,fill,link
93 end
94
95 def Footer
96 SetFont(@font_for_footer, 'I', 8)
97 SetY(-15)
98 SetX(15)
99 Cell(0, 5, @footer_date, 0, 0, 'L')
100 SetY(-15)
101 SetX(-30)
102 Cell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
103 end
104 end
105
106 # Returns a PDF string of a list of issues
107 def issues_to_pdf(issues, project)
108 pdf = IFPDF.new(current_language)
109 title = project ? "#{project} - #{l(:label_issue_plural)}" : "#{l(:label_issue_plural)}"
110 pdf.SetTitle(title)
111 pdf.AliasNbPages
112 pdf.footer_date = format_date(Date.today)
113 pdf.AddPage("L")
114 row_height = 7
115
116 # title
117 pdf.SetFontStyle('B',11)
118 pdf.Cell(190,10, title)
119 pdf.Ln
120
121 # headers
122 pdf.SetFontStyle('B',10)
123 pdf.SetFillColor(230, 230, 230)
124 pdf.Cell(15, row_height, "#", 0, 0, 'L', 1)
125 pdf.Cell(30, row_height, l(:field_tracker), 0, 0, 'L', 1)
126 pdf.Cell(30, row_height, l(:field_status), 0, 0, 'L', 1)
127 pdf.Cell(30, row_height, l(:field_priority), 0, 0, 'L', 1)
128 pdf.Cell(40, row_height, l(:field_assigned_to), 0, 0, 'L', 1)
129 pdf.Cell(25, row_height, l(:field_updated_on), 0, 0, 'L', 1)
130 pdf.Cell(0, row_height, l(:field_subject), 0, 0, 'L', 1)
131 pdf.Line(10, pdf.GetY, 287, pdf.GetY)
132 pdf.Ln
133 pdf.Line(10, pdf.GetY, 287, pdf.GetY)
134 pdf.SetY(pdf.GetY() + 1)
135
136 # rows
137 pdf.SetFontStyle('',9)
138 pdf.SetFillColor(255, 255, 255)
139 issues.each do |issue|
140 pdf.Cell(15, row_height, issue.id.to_s, 0, 0, 'L', 1)
141 pdf.Cell(30, row_height, issue.tracker.name, 0, 0, 'L', 1)
142 pdf.Cell(30, row_height, issue.status.name, 0, 0, 'L', 1)
143 pdf.Cell(30, row_height, issue.priority.name, 0, 0, 'L', 1)
144 pdf.Cell(40, row_height, issue.assigned_to ? issue.assigned_to.to_s : '', 0, 0, 'L', 1)
145 pdf.Cell(25, row_height, format_date(issue.updated_on), 0, 0, 'L', 1)
146 pdf.MultiCell(0, row_height, (project == issue.project ? issue.subject : "#{issue.project} - #{issue.subject}"))
147 pdf.Line(10, pdf.GetY, 287, pdf.GetY)
148 pdf.SetY(pdf.GetY() + 1)
149 end
150 pdf.Output
151 end
152
153 # Returns a PDF string of a single issue
154 def issue_to_pdf(issue)
155 pdf = IFPDF.new(current_language)
156 pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}")
157 pdf.AliasNbPages
158 pdf.footer_date = format_date(Date.today)
159 pdf.AddPage
160
161 pdf.SetFontStyle('B',11)
162 pdf.Cell(190,10, "#{issue.project} - #{issue.tracker} # #{issue.id}: #{issue.subject}")
163 pdf.Ln
164
165 y0 = pdf.GetY
166
167 pdf.SetFontStyle('B',9)
168 pdf.Cell(35,5, l(:field_status) + ":","LT")
169 pdf.SetFontStyle('',9)
170 pdf.Cell(60,5, issue.status.to_s,"RT")
171 pdf.SetFontStyle('B',9)
172 pdf.Cell(35,5, l(:field_priority) + ":","LT")
173 pdf.SetFontStyle('',9)
174 pdf.Cell(60,5, issue.priority.to_s,"RT")
175 pdf.Ln
176
177 pdf.SetFontStyle('B',9)
178 pdf.Cell(35,5, l(:field_author) + ":","L")
179 pdf.SetFontStyle('',9)
180 pdf.Cell(60,5, issue.author.to_s,"R")
181 pdf.SetFontStyle('B',9)
182 pdf.Cell(35,5, l(:field_category) + ":","L")
183 pdf.SetFontStyle('',9)
184 pdf.Cell(60,5, issue.category.to_s,"R")
185 pdf.Ln
186
187 pdf.SetFontStyle('B',9)
188 pdf.Cell(35,5, l(:field_created_on) + ":","L")
189 pdf.SetFontStyle('',9)
190 pdf.Cell(60,5, format_date(issue.created_on),"R")
191 pdf.SetFontStyle('B',9)
192 pdf.Cell(35,5, l(:field_assigned_to) + ":","L")
193 pdf.SetFontStyle('',9)
194 pdf.Cell(60,5, issue.assigned_to.to_s,"R")
195 pdf.Ln
196
197 pdf.SetFontStyle('B',9)
198 pdf.Cell(35,5, l(:field_updated_on) + ":","LB")
199 pdf.SetFontStyle('',9)
200 pdf.Cell(60,5, format_date(issue.updated_on),"RB")
201 pdf.SetFontStyle('B',9)
202 pdf.Cell(35,5, l(:field_due_date) + ":","LB")
203 pdf.SetFontStyle('',9)
204 pdf.Cell(60,5, format_date(issue.due_date),"RB")
205 pdf.Ln
206
207 for custom_value in issue.custom_values
208 pdf.SetFontStyle('B',9)
209 pdf.Cell(35,5, custom_value.custom_field.name + ":","L")
210 pdf.SetFontStyle('',9)
211 pdf.MultiCell(155,5, (show_value custom_value),"R")
212 end
213
214 pdf.SetFontStyle('B',9)
215 pdf.Cell(35,5, l(:field_subject) + ":","LTB")
216 pdf.SetFontStyle('',9)
217 pdf.Cell(155,5, issue.subject,"RTB")
218 pdf.Ln
219
220 pdf.SetFontStyle('B',9)
221 pdf.Cell(35,5, l(:field_description) + ":")
222 pdf.SetFontStyle('',9)
223 pdf.MultiCell(155,5, @issue.description,"BR")
224
225 pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
226 pdf.Line(pdf.GetX, pdf.GetY, 170, pdf.GetY)
227 pdf.Ln
228
229 if issue.changesets.any? && User.current.allowed_to?(:view_changesets, issue.project)
230 pdf.SetFontStyle('B',9)
231 pdf.Cell(190,5, l(:label_associated_revisions), "B")
232 pdf.Ln
233 for changeset in issue.changesets
234 pdf.SetFontStyle('B',8)
235 pdf.Cell(190,5, format_time(changeset.committed_on) + " - " + changeset.author.to_s)
236 pdf.Ln
237 unless changeset.comments.blank?
238 pdf.SetFontStyle('',8)
239 pdf.MultiCell(190,5, changeset.comments)
240 end
241 pdf.Ln
242 end
243 end
244
245 pdf.SetFontStyle('B',9)
246 pdf.Cell(190,5, l(:label_history), "B")
247 pdf.Ln
248 for journal in issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
249 pdf.SetFontStyle('B',8)
250 pdf.Cell(190,5, format_time(journal.created_on) + " - " + journal.user.name)
251 pdf.Ln
252 pdf.SetFontStyle('I',8)
253 for detail in journal.details
254 pdf.Cell(190,5, "- " + show_detail(detail, true))
255 pdf.Ln
256 end
257 if journal.notes?
258 pdf.SetFontStyle('',8)
259 pdf.MultiCell(190,5, journal.notes)
260 end
261 pdf.Ln
262 end
263
264 if issue.attachments.any?
265 pdf.SetFontStyle('B',9)
266 pdf.Cell(190,5, l(:label_attachment_plural), "B")
267 pdf.Ln
268 for attachment in issue.attachments
269 pdf.SetFontStyle('',8)
270 pdf.Cell(80,5, attachment.filename)
271 pdf.Cell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
272 pdf.Cell(25,5, format_date(attachment.created_on),0,0,"R")
273 pdf.Cell(65,5, attachment.author.name,0,0,"R")
274 pdf.Ln
275 end
276 end
277 pdf.Output
278 end
279
280 # Returns a PDF string of a gantt chart
281 def gantt_to_pdf(gantt, project)
282 pdf = IFPDF.new(current_language)
283 pdf.SetTitle("#{l(:label_gantt)} #{project}")
284 pdf.AliasNbPages
285 pdf.footer_date = format_date(Date.today)
286 pdf.AddPage("L")
287 pdf.SetFontStyle('B',12)
288 pdf.SetX(15)
289 pdf.Cell(70, 20, project.to_s)
290 pdf.Ln
291 pdf.SetFontStyle('B',9)
292
293 subject_width = 70
294 header_heigth = 5
295
296 headers_heigth = header_heigth
297 show_weeks = false
298 show_days = false
299
300 if gantt.months < 7
301 show_weeks = true
302 headers_heigth = 2*header_heigth
303 if gantt.months < 3
304 show_days = true
305 headers_heigth = 3*header_heigth
306 end
307 end
308
309 g_width = 210
310 zoom = (g_width) / (gantt.date_to - gantt.date_from + 1)
311 g_height = 120
312 t_height = g_height + headers_heigth
313
314 y_start = pdf.GetY
315
316 # Months headers
317 month_f = gantt.date_from
318 left = subject_width
319 height = header_heigth
320 gantt.months.times do
321 width = ((month_f >> 1) - month_f) * zoom
322 pdf.SetY(y_start)
323 pdf.SetX(left)
324 pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
325 left = left + width
326 month_f = month_f >> 1
327 end
328
329 # Weeks headers
330 if show_weeks
331 left = subject_width
332 height = header_heigth
333 if gantt.date_from.cwday == 1
334 # gantt.date_from is monday
335 week_f = gantt.date_from
336 else
337 # find next monday after gantt.date_from
338 week_f = gantt.date_from + (7 - gantt.date_from.cwday + 1)
339 width = (7 - gantt.date_from.cwday + 1) * zoom-1
340 pdf.SetY(y_start + header_heigth)
341 pdf.SetX(left)
342 pdf.Cell(width + 1, height, "", "LTR")
343 left = left + width+1
344 end
345 while week_f <= gantt.date_to
346 width = (week_f + 6 <= gantt.date_to) ? 7 * zoom : (gantt.date_to - week_f + 1) * zoom
347 pdf.SetY(y_start + header_heigth)
348 pdf.SetX(left)
349 pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
350 left = left + width
351 week_f = week_f+7
352 end
353 end
354
355 # Days headers
356 if show_days
357 left = subject_width
358 height = header_heigth
359 wday = gantt.date_from.cwday
360 pdf.SetFontStyle('B',7)
361 (gantt.date_to - gantt.date_from + 1).to_i.times do
362 width = zoom
363 pdf.SetY(y_start + 2 * header_heigth)
364 pdf.SetX(left)
365 pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C")
366 left = left + width
367 wday = wday + 1
368 wday = 1 if wday > 7
369 end
370 end
371
372 pdf.SetY(y_start)
373 pdf.SetX(15)
374 pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1)
375
376 # Tasks
377 top = headers_heigth + y_start
378 pdf.SetFontStyle('B',7)
379 gantt.events.each do |i|
380 pdf.SetY(top)
381 pdf.SetX(15)
382
383 if i.is_a? Issue
384 pdf.Cell(subject_width-15, 5, "#{i.tracker} #{i.id}: #{i.subject}".sub(/^(.{30}[^\s]*\s).*$/, '\1 (...)'), "LR")
385 else
386 pdf.Cell(subject_width-15, 5, "#{l(:label_version)}: #{i.name}", "LR")
387 end
388
389 pdf.SetY(top)
390 pdf.SetX(subject_width)
391 pdf.Cell(g_width, 5, "", "LR")
392
393 pdf.SetY(top+1.5)
394
395 if i.is_a? Issue
396 i_start_date = (i.start_date >= gantt.date_from ? i.start_date : gantt.date_from )
397 i_end_date = (i.due_before <= gantt.date_to ? i.due_before : gantt.date_to )
398
399 i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor
400 i_done_date = (i_done_date <= gantt.date_from ? gantt.date_from : i_done_date )
401 i_done_date = (i_done_date >= gantt.date_to ? gantt.date_to : i_done_date )
402
403 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
404
405 i_left = ((i_start_date - gantt.date_from)*zoom)
406 i_width = ((i_end_date - i_start_date + 1)*zoom)
407 d_width = ((i_done_date - i_start_date)*zoom)
408 l_width = ((i_late_date - i_start_date+1)*zoom) if i_late_date
409 l_width ||= 0
410
411 pdf.SetX(subject_width + i_left)
412 pdf.SetFillColor(200,200,200)
413 pdf.Cell(i_width, 2, "", 0, 0, "", 1)
414
415 if l_width > 0
416 pdf.SetY(top+1.5)
417 pdf.SetX(subject_width + i_left)
418 pdf.SetFillColor(255,100,100)
419 pdf.Cell(l_width, 2, "", 0, 0, "", 1)
420 end
421 if d_width > 0
422 pdf.SetY(top+1.5)
423 pdf.SetX(subject_width + i_left)
424 pdf.SetFillColor(100,100,255)
425 pdf.Cell(d_width, 2, "", 0, 0, "", 1)
426 end
427
428 pdf.SetY(top+1.5)
429 pdf.SetX(subject_width + i_left + i_width)
430 pdf.Cell(30, 2, "#{i.status} #{i.done_ratio}%")
431 else
432 i_left = ((i.start_date - gantt.date_from)*zoom)
433
434 pdf.SetX(subject_width + i_left)
435 pdf.SetFillColor(50,200,50)
436 pdf.Cell(2, 2, "", 0, 0, "", 1)
437
438 pdf.SetY(top+1.5)
439 pdf.SetX(subject_width + i_left + 3)
440 pdf.Cell(30, 2, "#{i.name}")
441 end
442
443 top = top + 5
444 pdf.SetDrawColor(200, 200, 200)
445 pdf.Line(15, top, subject_width+g_width, top)
446 if pdf.GetY() > 180
447 pdf.AddPage("L")
448 top = 20
449 pdf.Line(15, top, subject_width+g_width, top)
450 end
451 pdf.SetDrawColor(0, 0, 0)
452 end
453
454 pdf.Line(15, top, subject_width+g_width, top)
455 pdf.Output
456 end
457 end
458 end
459 end
@@ -30,8 +30,6 class IssuesController < ApplicationController
30 include ProjectsHelper
30 include ProjectsHelper
31 helper :custom_fields
31 helper :custom_fields
32 include CustomFieldsHelper
32 include CustomFieldsHelper
33 helper :ifpdf
34 include IfpdfHelper
35 helper :issue_relations
33 helper :issue_relations
36 include IssueRelationsHelper
34 include IssueRelationsHelper
37 helper :watchers
35 helper :watchers
@@ -43,6 +41,7 class IssuesController < ApplicationController
43 include SortHelper
41 include SortHelper
44 include IssuesHelper
42 include IssuesHelper
45 helper :timelog
43 helper :timelog
44 include Redmine::Export::PDF
46
45
47 def index
46 def index
48 retrieve_query
47 retrieve_query
@@ -68,7 +67,7 class IssuesController < ApplicationController
68 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
67 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
69 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
68 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
70 format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
69 format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
71 format.pdf { send_data(render(:template => 'issues/index.rfpdf', :layout => false), :type => 'application/pdf', :filename => 'export.pdf') }
70 format.pdf { send_data(issues_to_pdf(@issues, @project), :type => 'application/pdf', :filename => 'export.pdf') }
72 end
71 end
73 else
72 else
74 # Send html if the query is not valid
73 # Send html if the query is not valid
@@ -106,7 +105,7 class IssuesController < ApplicationController
106 respond_to do |format|
105 respond_to do |format|
107 format.html { render :template => 'issues/show.rhtml' }
106 format.html { render :template => 'issues/show.rhtml' }
108 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
107 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
109 format.pdf { send_data(render(:template => 'issues/show.rfpdf', :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
108 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
110 end
109 end
111 end
110 end
112
111
@@ -346,7 +345,7 class IssuesController < ApplicationController
346 respond_to do |format|
345 respond_to do |format|
347 format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
346 format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
348 format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png") } if @gantt.respond_to?('to_image')
347 format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png") } if @gantt.respond_to?('to_image')
349 format.pdf { send_data(render(:template => "issues/gantt.rfpdf", :layout => false), :type => 'application/pdf', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.pdf") }
348 format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.pdf") }
350 end
349 end
351 end
350 end
352
351
@@ -33,8 +33,6 class ProjectsController < ApplicationController
33 include SortHelper
33 include SortHelper
34 helper :custom_fields
34 helper :custom_fields
35 include CustomFieldsHelper
35 include CustomFieldsHelper
36 helper :ifpdf
37 include IfpdfHelper
38 helper :issues
36 helper :issues
39 helper IssuesHelper
37 helper IssuesHelper
40 helper :queries
38 helper :queries
@@ -177,16 +177,16 class IssuesControllerTest < Test::Unit::TestCase
177 def test_gantt_export_to_pdf
177 def test_gantt_export_to_pdf
178 get :gantt, :project_id => 1, :format => 'pdf'
178 get :gantt, :project_id => 1, :format => 'pdf'
179 assert_response :success
179 assert_response :success
180 assert_template 'gantt.rfpdf'
181 assert_equal 'application/pdf', @response.content_type
180 assert_equal 'application/pdf', @response.content_type
181 assert @response.body.starts_with?('%PDF')
182 assert_not_nil assigns(:gantt)
182 assert_not_nil assigns(:gantt)
183 end
183 end
184
184
185 def test_cross_project_gantt_export_to_pdf
185 def test_cross_project_gantt_export_to_pdf
186 get :gantt, :format => 'pdf'
186 get :gantt, :format => 'pdf'
187 assert_response :success
187 assert_response :success
188 assert_template 'gantt.rfpdf'
189 assert_equal 'application/pdf', @response.content_type
188 assert_equal 'application/pdf', @response.content_type
189 assert @response.body.starts_with?('%PDF')
190 assert_not_nil assigns(:gantt)
190 assert_not_nil assigns(:gantt)
191 end
191 end
192
192
@@ -252,6 +252,14 class IssuesControllerTest < Test::Unit::TestCase
252 :content => /Notes/ } }
252 :content => /Notes/ } }
253 end
253 end
254
254
255 def test_show_export_to_pdf
256 get :show, :id => 1, :format => 'pdf'
257 assert_response :success
258 assert_equal 'application/pdf', @response.content_type
259 assert @response.body.starts_with?('%PDF')
260 assert_not_nil assigns(:issue)
261 end
262
255 def test_get_new
263 def test_get_new
256 @request.session[:user_id] = 2
264 @request.session[:user_id] = 2
257 get :new, :project_id => 1, :tracker_id => 1
265 get :new, :project_id => 1, :tracker_id => 1
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now