Patch #3614 » export_issues_to_pdf_csv.diff
| app/helpers/issues_helper.rb (working copy) | ||
|---|---|---|
| 15 | 15 |
# along with this program; if not, write to the Free Software |
| 16 | 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 17 | 17 | |
| 18 |
# Matthew Keniston & Alex Mendes |
|
| 19 |
# BackOffice Associates |
|
| 20 |
# 7/7/09 fixes for: 1) export to PDF/CSV ignoring custom queries; 2) query display problems |
|
| 21 | ||
| 18 | 22 |
require 'csv' |
| 19 | 23 | |
| 20 | 24 |
module IssuesHelper |
| ... | ... | |
| 129 | 133 |
end |
| 130 | 134 |
end |
| 131 | 135 |
|
| 132 |
def issues_to_csv(issues, project = nil) |
|
| 136 |
def retrieveHeaders(query, custom_fields) |
|
| 137 |
headers = Array.new(query.column_names.length()) |
|
| 138 |
0.upto(query.column_names.length() - 1) do |counter| |
|
| 139 |
headerName = nil |
|
| 140 |
columnName = query.column_names[counter].to_s() |
|
| 141 |
case columnName |
|
| 142 |
when 'status' |
|
| 143 |
headerName = l(:field_status) |
|
| 144 |
when 'project' |
|
| 145 |
headerName = l(:field_project) |
|
| 146 |
when 'tracker' |
|
| 147 |
headerName = l(:field_tracker) |
|
| 148 |
when 'priority' |
|
| 149 |
headerName = l(:field_priority) |
|
| 150 |
when 'subject' |
|
| 151 |
headerName = l(:field_subject) |
|
| 152 |
when 'assigned_to' |
|
| 153 |
headerName = l(:field_assigned_to) |
|
| 154 |
when 'category' |
|
| 155 |
headerName = l(:field_category) |
|
| 156 |
when 'fixed_version' |
|
| 157 |
headerName = l(:field_fixed_version) |
|
| 158 |
when 'author' |
|
| 159 |
headerName = l(:field_author) |
|
| 160 |
when 'start_date' |
|
| 161 |
headerName = l(:field_start_date) |
|
| 162 |
when 'due_date' |
|
| 163 |
headerName = l(:field_due_date) |
|
| 164 |
when 'done_ratio' |
|
| 165 |
headerName = l(:field_done_ratio) |
|
| 166 |
when 'estimated_hours' |
|
| 167 |
headerName = l(:field_estimated_hours) |
|
| 168 |
when 'created_on' |
|
| 169 |
headerName = l(:field_created_on) |
|
| 170 |
when 'updated_on' |
|
| 171 |
headerName = l(:field_updated_on) |
|
| 172 |
else |
|
| 173 |
#custom field case... |
|
| 174 |
|
|
| 175 |
#parse the custom column id # from the column header |
|
| 176 |
id = getIDFromCustomColName(columnName) |
|
| 177 |
|
|
| 178 |
#match it with a custom field id to extract the correct header and field data |
|
| 179 |
custom_fields.each do |custom_field| #cycle through all possible custom fields |
|
| 180 |
if id == custom_field.id.to_s() |
|
| 181 |
headerName = custom_field.name.to_s() |
|
| 182 |
break |
|
| 183 |
end |
|
| 184 |
end |
|
| 185 |
end |
|
| 186 |
headers[counter] = headerName != nil ? headerName : '' |
|
| 187 |
end |
|
| 188 |
return headers |
|
| 189 |
end |
|
| 190 |
|
|
| 191 |
def retrieveFieldData(issues, query, decimal_separator,custom_fields) |
|
| 192 |
fields = Array.new(query.column_names.length()){[]}
|
|
| 193 |
#cycle through each issue (row) in the table.. |
|
| 194 |
issues.each do |issue| |
|
| 195 |
#cycle through each column in the row... |
|
| 196 |
#loading the column names from the passed query... |
|
| 197 |
#in order to load the correct column data for each issue --> fields2[column][issue] |
|
| 198 |
#this method also loads the headers for each column --> headers2[column] |
|
| 199 |
0.upto(query.column_names.length() - 1) do |counter| |
|
| 200 |
columnName = query.column_names[counter].to_s() |
|
| 201 |
case columnName |
|
| 202 |
when 'status' |
|
| 203 |
fieldData = issue.status.name |
|
| 204 |
when 'project' |
|
| 205 |
fieldData = issue.project.name |
|
| 206 |
when 'tracker' |
|
| 207 |
fieldData = issue.tracker.name |
|
| 208 |
when 'priority' |
|
| 209 |
fieldData = issue.priority.name |
|
| 210 |
when 'subject' |
|
| 211 |
fieldData= issue.subject |
|
| 212 |
when 'assigned_to' |
|
| 213 |
fieldData = issue.assigned_to |
|
| 214 |
when 'category' |
|
| 215 |
fieldData = issue.category |
|
| 216 |
when 'fixed_version' |
|
| 217 |
fieldData = issue.fixed_version |
|
| 218 |
when 'author' |
|
| 219 |
fieldData = issue.author.name |
|
| 220 |
when 'start_date' |
|
| 221 |
fieldData = format_date(issue.start_date) |
|
| 222 |
when 'due_date' |
|
| 223 |
fieldData = format_date(issue.due_date) |
|
| 224 |
when 'done_ratio' |
|
| 225 |
fieldData = issue.done_ratio |
|
| 226 |
when 'estimated_hours' |
|
| 227 |
fieldData = issue.estimated_hours.to_s.gsub('.', decimal_separator)
|
|
| 228 |
when 'created_on' |
|
| 229 |
fieldData = format_time(issue.created_on) |
|
| 230 |
when 'updated_on' |
|
| 231 |
fieldData = format_time(issue.updated_on) |
|
| 232 |
else |
|
| 233 |
#custom field case... |
|
| 234 |
|
|
| 235 |
#parse the custom column id # from the column header |
|
| 236 |
id = getIDFromCustomColName(columnName) |
|
| 237 |
|
|
| 238 |
#match it with a custom field id to extract the correct header and field data |
|
| 239 |
custom_fields.each do |custom_field| #cycle through all possible custom fields |
|
| 240 |
if id == custom_field.id.to_s() |
|
| 241 |
fieldData = show_value(issue.custom_value_for(custom_field)) |
|
| 242 |
break |
|
| 243 |
end |
|
| 244 |
end |
|
| 245 |
end #end case |
|
| 246 |
fields[counter][issue.id] = fieldData |
|
| 247 |
end #end upto |
|
| 248 |
end #end issues.each |
|
| 249 |
return fields |
|
| 250 |
end |
|
| 251 |
|
|
| 252 |
#Method changed 7/7/09 |
|
| 253 |
def issues_to_csv(issues, query, project = nil) |
|
| 133 | 254 |
ic = Iconv.new(l(:general_csv_encoding), 'UTF-8') |
| 134 | 255 |
decimal_separator = l(:general_csv_decimal_separator) |
| 135 | 256 |
export = StringIO.new |
| 136 | 257 |
CSV::Writer.generate(export, l(:general_csv_separator)) do |csv| |
| 137 |
# csv header fields |
|
| 138 |
headers = [ "#", |
|
| 258 |
# csv header fields |
|
| 259 |
|
|
| 260 |
#if the user has specified columns in the query... |
|
| 261 |
if(@query.column_names != nil) |
|
| 262 |
headers = Array.new(query.column_names.length()) #array for column headers |
|
| 263 |
fields = Array.new(query.column_names.length()){[]} #array for column fields
|
|
| 264 |
custom_fields = project.nil? ? IssueCustomField.for_all : project.all_issue_custom_fields |
|
| 265 |
|
|
| 266 |
# - _ - Retrieve the header and column data. |
|
| 267 |
|
|
| 268 |
fields = retrieveFieldData(issues,query,decimal_separator,custom_fields) |
|
| 269 |
headers = retrieveHeaders(query,custom_fields) |
|
| 270 |
#fields[col][row] is a multi-dimensional array which holds all the field data for the csv table. |
|
| 271 |
#...The first index is the column, and the second is the issue or row. |
|
| 272 |
# |
|
| 273 |
#headers[col] is an array which holds data for the column headers |
|
| 274 |
|
|
| 275 |
# - _ - Now, Print the headers and fields |
|
| 276 |
|
|
| 277 |
#Add bug number to the front of column headers list, via new array headersTemp |
|
| 278 |
#Make '#' the first entry, then append with the 'headers' array |
|
| 279 |
headersTemp = Array.new(1) |
|
| 280 |
headersTemp[0] = "#" |
|
| 281 |
0.upto(headers.length()-1) do |counter| |
|
| 282 |
headersTemp << headers[counter] |
|
| 283 |
end |
|
| 284 |
|
|
| 285 |
#print headers, via headersTemp |
|
| 286 |
csv << headersTemp.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
|
|
| 287 |
|
|
| 288 |
#add bug number to the field arrays, via a new array called together |
|
| 289 |
together = Array.new(issues.length()) |
|
| 290 |
issues.each do |issue| #for each issue... |
|
| 291 |
together[issue.id] = [issue.id.to_s()] #add the issue id |
|
| 292 |
#for each column in the issue... |
|
| 293 |
0.upto(@query.column_names.length() - 1) do |counter| |
|
| 294 |
# construct the issue rows from the columns. |
|
| 295 |
together[issue.id] << fields[counter][issue.id] |
|
| 296 |
end |
|
| 297 |
#print the fields to the screen, issue at a time, via together |
|
| 298 |
csv << together[issue.id].collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
|
|
| 299 |
end |
|
| 300 |
else # if no fields specified --> then display default fields in csv |
|
| 301 |
# csv default header fields |
|
| 302 |
headers = [ "#", |
|
| 139 | 303 |
l(:field_status), |
| 140 | 304 |
l(:field_project), |
| 141 |
l(:field_tracker),
|
|
| 305 |
l(:field_tracker),
|
|
| 142 | 306 |
l(:field_priority), |
| 143 | 307 |
l(:field_subject), |
| 144 |
l(:field_assigned_to), |
|
| 308 |
l(:field_assigned_to),
|
|
| 145 | 309 |
l(:field_category), |
| 146 | 310 |
l(:field_fixed_version), |
| 147 | 311 |
l(:field_author), |
| ... | ... | |
| 156 | 320 |
# otherwise export custom fields marked as "For all projects" |
| 157 | 321 |
custom_fields = project.nil? ? IssueCustomField.for_all : project.all_issue_custom_fields |
| 158 | 322 |
custom_fields.each {|f| headers << f.name}
|
| 159 |
# Description in the last column
|
|
| 323 |
# Description in the last column
|
|
| 160 | 324 |
headers << l(:field_description) |
| 161 |
csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
|
|
| 162 |
# csv lines
|
|
| 163 |
issues.each do |issue|
|
|
| 164 |
fields = [issue.id,
|
|
| 165 |
issue.status.name,
|
|
| 166 |
issue.project.name,
|
|
| 167 |
issue.tracker.name,
|
|
| 168 |
issue.priority.name,
|
|
| 169 |
issue.subject,
|
|
| 170 |
issue.assigned_to,
|
|
| 171 |
issue.category,
|
|
| 172 |
issue.fixed_version,
|
|
| 173 |
issue.author.name,
|
|
| 174 |
format_date(issue.start_date),
|
|
| 175 |
format_date(issue.due_date),
|
|
| 176 |
issue.done_ratio,
|
|
| 177 |
issue.estimated_hours.to_s.gsub('.', decimal_separator),
|
|
| 178 |
format_time(issue.created_on),
|
|
| 179 |
format_time(issue.updated_on)
|
|
| 180 |
]
|
|
| 325 |
csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
|
|
| 326 |
# csv lines
|
|
| 327 |
issues.each do |issue|
|
|
| 328 |
fields = [issue.id,
|
|
| 329 |
issue.status.name, |
|
| 330 |
issue.project.name, |
|
| 331 |
issue.tracker.name,
|
|
| 332 |
issue.priority.name,
|
|
| 333 |
issue.subject,
|
|
| 334 |
issue.assigned_to,
|
|
| 335 |
issue.category, |
|
| 336 |
issue.fixed_version, |
|
| 337 |
issue.author.name, |
|
| 338 |
format_date(issue.start_date), |
|
| 339 |
format_date(issue.due_date), |
|
| 340 |
issue.done_ratio, |
|
| 341 |
issue.estimated_hours.to_s.gsub('.', decimal_separator),
|
|
| 342 |
format_time(issue.created_on), |
|
| 343 |
format_time(issue.updated_on)
|
|
| 344 |
]
|
|
| 181 | 345 |
custom_fields.each {|f| fields << show_value(issue.custom_value_for(f)) }
|
| 182 | 346 |
fields << issue.description |
| 183 |
csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
|
|
| 184 |
end |
|
| 347 |
csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
|
|
| 348 |
end |
|
| 349 |
end |
|
| 185 | 350 |
end |
| 186 | 351 |
export.rewind |
| 187 | 352 |
export |
| app/helpers/custom_fields_helper.rb (working copy) | ||
|---|---|---|
| 15 | 15 |
# along with this program; if not, write to the Free Software |
| 16 | 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 17 | 17 | |
| 18 |
# Matthew Keniston & Alex Mendes |
|
| 19 |
# BackOffice Associates |
|
| 20 |
# 7/7/09 fixes for: 1) export to PDF/CSV ignoring custom queries; 2) query display problems |
|
| 21 | ||
| 18 | 22 |
module CustomFieldsHelper |
| 19 | 23 | |
| 20 | 24 |
def custom_fields_tabs |
| ... | ... | |
| 85 | 89 |
def custom_field_formats_for_select |
| 86 | 90 |
CustomField::FIELD_FORMATS.sort {|a,b| a[1][:order]<=>b[1][:order]}.collect { |k| [ l(k[1][:name]), k[0] ] }
|
| 87 | 91 |
end |
| 92 |
|
|
| 93 |
#Method Added 7/7/09 |
|
| 94 |
#Extracts the id from a custom column name |
|
| 95 |
#E.g. "cf_6" becomes "6", which is the id for the custom column |
|
| 96 |
def getIDFromCustomColName(colName) |
|
| 97 |
return colName.gsub(/.*_(.*)/, '\1') |
|
| 98 |
end |
|
| 99 |
|
|
| 100 |
#Method Added 7/7/09 |
|
| 101 |
#Capitalizes each word in a title |
|
| 102 |
def capitalizeEachWord(title) |
|
| 103 |
spaceFlag = nil |
|
| 104 |
newTitle = "" |
|
| 105 |
0.upto(title.length() - 1) do |cntr| |
|
| 106 |
char = title.slice(cntr..cntr) |
|
| 107 |
if(cntr == 0) |
|
| 108 |
char.capitalize! |
|
| 109 |
elsif(char == " ") |
|
| 110 |
spaceFlag = true |
|
| 111 |
elsif(spaceFlag) |
|
| 112 |
char.capitalize! |
|
| 113 |
spaceFlag = nil |
|
| 114 |
end |
|
| 115 |
newTitle = newTitle + char |
|
| 116 |
end |
|
| 117 |
return newTitle |
|
| 118 |
end |
|
| 119 |
|
|
| 120 |
#Method Added 7/7/09 |
|
| 121 |
#Returns the type of the custom field or the column name for default fields |
|
| 122 |
def getType(issue,pos) |
|
| 123 |
fieldType = "" |
|
| 124 |
|
|
| 125 |
if (@query.column_names != nil) # make sure we have user query |
|
| 126 |
|
|
| 127 |
col = @query.column_names[pos].to_s() |
|
| 128 |
|
|
| 129 |
if (issue.respond_to?(col)) |
|
| 130 |
fieldType = col.to_s() |
|
| 131 |
|
|
| 132 |
else # custom field |
|
| 133 |
custom_fields = @project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields |
|
| 134 |
columnName = @query.column_names[pos].to_s() |
|
| 135 |
id = getIDFromCustomColName(columnName) |
|
| 136 |
custom_fields.each do |custom_field| |
|
| 137 |
if id == custom_field.id.to_s() |
|
| 138 |
fieldType = custom_field.field_format.to_s() |
|
| 139 |
break |
|
| 140 |
end |
|
| 141 |
end |
|
| 142 |
end |
|
| 143 |
end |
|
| 144 |
return fieldType |
|
| 145 |
end |
|
| 146 | ||
| 147 |
#Method Added 7/2/09 |
|
| 148 |
#Given an issue and a column this returns the correct data |
|
| 149 |
def getValue(issue, pos) |
|
| 150 |
fieldValue = "" |
|
| 151 |
col = @query.column_names[pos].to_s() |
|
| 152 |
|
|
| 153 |
#Default field |
|
| 154 |
if(issue.respond_to?(col)) |
|
| 155 |
type = (issue.send(col)).type.to_s() |
|
| 156 |
case type |
|
| 157 |
when "Time" |
|
| 158 |
fieldValue = format_time(issue.send(col)) |
|
| 159 |
when "Date" |
|
| 160 |
fieldValue = format_date(issue.send(col)) |
|
| 161 |
when "Float", "String", "int", "Fixnum", "" |
|
| 162 |
fieldValue = issue.send(col).to_s() |
|
| 163 |
when "Text", "Tracker","IssueStatus", "IssueCategory", "Enumeration", "Version", "User", "Project" |
|
| 164 |
fieldValue = issue.send(col).name |
|
| 165 |
end |
|
| 166 |
|
|
| 167 |
#Custom Field |
|
| 168 |
else |
|
| 169 |
custom_fields = @project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields |
|
| 170 |
columnName = @query.column_names[pos].to_s() |
|
| 171 |
id = getIDFromCustomColName(columnName) |
|
| 172 |
custom_fields.each do |custom_field| |
|
| 173 |
if id == custom_field.id.to_s() |
|
| 174 |
type = custom_field.field_format.to_s() |
|
| 175 |
fieldValue = show_value(issue.custom_value_for(custom_field)) |
|
| 176 |
break |
|
| 177 |
end |
|
| 178 |
end |
|
| 179 | ||
| 180 |
#If type is a boolean value |
|
| 181 |
if type == "bool" |
|
| 182 |
fieldValue = (fieldValue == "1") ? "YES" : "NO" |
|
| 183 |
end |
|
| 184 |
end |
|
| 185 |
|
|
| 186 |
return fieldValue |
|
| 187 |
end |
|
| 88 | 188 |
end |
| app/controllers/issues_controller.rb (working copy) | ||
|---|---|---|
| 15 | 15 |
# along with this program; if not, write to the Free Software |
| 16 | 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 17 | 17 | |
| 18 |
# Matthew Keniston & Alex Mendes |
|
| 19 |
# BackOffice Associates |
|
| 20 |
# 7/7/09 fixes for: 1) export to PDF/CSV ignoring custom queries; 2) query display problems |
|
| 21 | ||
| 18 | 22 |
class IssuesController < ApplicationController |
| 19 | 23 |
menu_item :new_issue, :only => :new |
| 20 | 24 |
|
| ... | ... | |
| 77 | 81 |
render :template => 'issues/index.rhtml', :layout => !request.xhr? |
| 78 | 82 |
} |
| 79 | 83 |
format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
|
| 80 |
format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
|
|
| 81 |
format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
|
|
| 84 |
#Changed 7/7/09 - call smarter csv and pdf functions |
|
| 85 |
format.csv { send_data(issues_to_csv(@issues, @query, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
|
|
| 86 |
# format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
|
|
| 87 |
format.pdf { send_data(issues_to_pdf(@issues, @query, @project), :type => 'application/pdf', :filename => 'export.pdf') }
|
|
| 88 |
# format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
|
|
| 89 | ||
| 82 | 90 |
end |
| 83 | 91 |
else |
| 84 | 92 |
# Send html if the query is not valid |
| app/views/issues/_list.rhtml (working copy) | ||
|---|---|---|
| 25 | 25 |
<tr id="issue-<%= issue.id %>" class="hascontextmenu <%= cycle('odd', 'even') %> <%= issue.css_classes %>">
|
| 26 | 26 |
<td class="checkbox"><%= check_box_tag("ids[]", issue.id, false, :id => nil) %></td>
|
| 27 | 27 |
<td><%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %></td> |
| 28 |
<% query.columns.each do |column| %><%= content_tag 'td', column_content(column, issue), :class => column.name %><% end %> |
|
| 28 |
<!-- Changed 7/8/09 - support smarter css class for custom fields --> |
|
| 29 |
|
|
| 30 |
<% pos = 0 %> |
|
| 31 |
<% query.columns.each do |column| %> |
|
| 32 |
<% value = column.name %> |
|
| 33 |
|
|
| 34 |
<% if query.column_names != nil %> <!-- it's a user query, so may contain custom fields --> |
|
| 35 |
<% value = getType(issue, pos) %> |
|
| 36 |
<% pos = pos + 1 %> |
|
| 37 |
<% end %> |
|
| 38 |
|
|
| 39 |
<%= content_tag 'td', column_content(column, issue), :class => value %> |
|
| 40 |
<% end -%> |
|
| 29 | 41 |
</tr> |
| 30 | 42 |
<% end -%> |
| 31 | 43 |
</tbody> |
| lib/redmine/export/pdf.rb (working copy) | ||
|---|---|---|
| 15 | 15 |
# along with this program; if not, write to the Free Software |
| 16 | 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 17 | 17 | |
| 18 |
# Matthew Keniston & Alex Mendes |
|
| 19 |
# BackOffice Associates |
|
| 20 |
# 7/7/09 fixes for: 1) export to PDF/CSV ignoring custom queries; 2) query display problems |
|
| 21 | ||
| 18 | 22 |
require 'iconv' |
| 19 | 23 |
require 'rfpdf/fpdf' |
| 20 | 24 |
require 'rfpdf/chinese' |
| ... | ... | |
| 22 | 26 |
module Redmine |
| 23 | 27 |
module Export |
| 24 | 28 |
module PDF |
| 29 |
#Added 7/2/09 |
|
| 30 |
#***************************************************************************** |
|
| 31 |
MAXCOLSIZE = 60 #Maximum column size before wrappping (string length) |
|
| 32 |
MAXPDFWIDTH = 250 #Approximate width of pdf document |
|
| 33 |
#***************************************************************************** |
|
| 25 | 34 |
include ActionView::Helpers::TextHelper |
| 26 | 35 |
include ActionView::Helpers::NumberHelper |
| 27 | 36 |
|
| ... | ... | |
| 95 | 104 |
end || '' |
| 96 | 105 |
super w,h,txt,border,ln,align,fill,link |
| 97 | 106 |
end |
| 98 |
|
|
| 107 |
|
|
| 108 |
#Method Changed 7/2/09 |
|
| 99 | 109 |
def Footer |
| 110 |
tempSize = getFontSize() |
|
| 111 |
tempFamily = getFontFamily() |
|
| 112 |
tempStyle = getFontStyle() |
|
| 100 | 113 |
SetFont(@font_for_footer, 'I', 8) |
| 101 | 114 |
SetY(-15) |
| 102 | 115 |
SetX(15) |
| ... | ... | |
| 104 | 117 |
SetY(-15) |
| 105 | 118 |
SetX(-30) |
| 106 | 119 |
Cell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
|
| 120 |
SetFont(tempFamily, tempStyle, tempSize) |
|
| 107 | 121 |
end |
| 108 | 122 |
end |
| 109 |
|
|
| 123 |
|
|
| 124 |
#Method Added 7/2/09 |
|
| 125 |
def printColumnHeadersPDF(query, issues, colWidth, row_height, fontSize, pdf, maxCustomContent) |
|
| 126 |
# headers |
|
| 127 |
pdf.SetFontStyle('B',fontSize)
|
|
| 128 |
pdf.SetFillColor(230, 230, 230) |
|
| 129 |
|
|
| 130 |
#Print the column header |
|
| 131 |
pdf.Cell(20, row_height, "#", 0, 0, 'L', 1) |
|
| 132 |
|
|
| 133 |
0.upto(@query.column_names.length - 1) do |cntr| |
|
| 134 |
colName = query.column_names[cntr].to_s() |
|
| 135 |
re = /^cf.*/ #identifier for the column names of custom fields |
|
| 136 |
|
|
| 137 |
# custom field |
|
| 138 |
if (re.match(colName)) |
|
| 139 | ||
| 140 |
custom_fields = @project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields |
|
| 141 |
id = getIDFromCustomColName(colName) |
|
| 142 |
custom_fields.each do |custom_field| #find the name |
|
| 143 |
if id == custom_field.id.to_s() |
|
| 144 |
colName = custom_field.name.to_s() |
|
| 145 |
break |
|
| 146 |
end |
|
| 147 |
end |
|
| 148 |
end |
|
| 149 |
|
|
| 150 |
colName = colName.gsub(/[_]/, ' ').to_s() # convert underscores to spaces |
|
| 151 |
colName = capitalizeEachWord(colName) |
|
| 152 |
pdf.Cell(colWidth[cntr], row_height, colName, 0, 0, 'L', 1) |
|
| 153 |
end |
|
| 154 |
|
|
| 155 |
end |
|
| 156 |
|
|
| 157 |
#Method Added 7/2/09 |
|
| 158 |
def printFieldsPDF(query, issues, colWidth, row_height, fontSize, pdf, rowHeightMultiCell, maxMultiColWidth) |
|
| 159 |
|
|
| 160 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 161 |
pdf.Ln |
|
| 162 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 163 |
pdf.SetY(pdf.GetY() + 1) |
|
| 164 |
pdf.SetFontStyle('',fontSize)
|
|
| 165 |
pdf.SetFillColor(255, 255, 255) |
|
| 166 |
|
|
| 167 |
#Print the rows |
|
| 168 |
issues.each do |issue| |
|
| 169 |
flag = 0 #How many lines occur during a text wrapping |
|
| 170 |
flagPageBreak = 0 #Check if a page break has occurred |
|
| 171 |
pageBreakPos = 0 #Position after the page break used for restoring |
|
| 172 |
|
|
| 173 |
pdf.Cell(20, row_height, issue.id.to_s, 0, 0, 'L', 1) |
|
| 174 |
|
|
| 175 |
0.upto(query.column_names.length - 1) do |cntr| |
|
| 176 |
|
|
| 177 |
if(getValue(issue,cntr).length < MAXCOLSIZE) #No text wrapping |
|
| 178 |
pdf.SetFontStyle('',fontSize)
|
|
| 179 |
pdf.Cell(colWidth[cntr], row_height, getValue(issue,cntr)) |
|
| 180 |
else #For text wrapping |
|
| 181 |
beforeX = pdf.GetX() #Before X position |
|
| 182 |
beforeY = pdf.GetY() #Before Y position |
|
| 183 |
pad = 1 #Extra Height of muli-cells |
|
| 184 |
|
|
| 185 |
pdf.SetY(pdf.GetY() + pad) |
|
| 186 |
pdf.SetX(beforeX) |
|
| 187 |
pdf.MultiCell(colWidth[cntr], rowHeightMultiCell, getValue(issue,cntr)) |
|
| 188 |
|
|
| 189 |
afterX = pdf.GetX() |
|
| 190 |
pdf.SetY(pdf.GetY() + pad) |
|
| 191 |
pdf.SetX(afterX) |
|
| 192 |
|
|
| 193 |
currentPage = pdf.getPage() |
|
| 194 |
afterY = pdf.GetY() #After Y position |
|
| 195 |
dif = afterY - beforeY #Difference after text wrapping |
|
| 196 |
if(afterY < beforeY) |
|
| 197 |
pdf.setPage(pdf.getPage - 1) |
|
| 198 |
flagPageBreak = 1 |
|
| 199 |
if(pageBreakPos < afterY) |
|
| 200 |
pageBreakPos = afterY |
|
| 201 |
end |
|
| 202 |
end |
|
| 203 |
|
|
| 204 |
#Count the number of lines skipped |
|
| 205 |
if(dif/rowHeightMultiCell > flag) |
|
| 206 |
flag = 0 |
|
| 207 |
while (dif > 0) |
|
| 208 |
dif = dif - rowHeightMultiCell |
|
| 209 |
flag = flag + 1 |
|
| 210 |
end |
|
| 211 |
end |
|
| 212 |
|
|
| 213 |
pdf.SetY(beforeY) |
|
| 214 |
pdf.SetX(beforeX + maxMultiColWidth) |
|
| 215 |
|
|
| 216 |
end |
|
| 217 |
end |
|
| 218 |
if(flagPageBreak == 1) #Restore Page after break |
|
| 219 |
pdf.setPage(pdf.getPage() + 1) |
|
| 220 |
pdf.SetY(pageBreakPos - row_height) |
|
| 221 |
flagPageBreak = 0 |
|
| 222 |
else |
|
| 223 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 224 |
end |
|
| 225 |
#Restore after wrapping |
|
| 226 |
if(flag > 0) |
|
| 227 |
pdf.SetY(pdf.GetY() + rowHeightMultiCell * flag) |
|
| 228 |
else |
|
| 229 |
pdf.SetY(pdf.GetY() + 10) |
|
| 230 |
end |
|
| 231 |
end |
|
| 232 |
end |
|
| 233 |
|
|
| 234 |
#Method Added 7/7/09 |
|
| 235 |
def printUserQuery(pdf, row_height) |
|
| 236 |
|
|
| 237 |
rowHeightMultiCell = 4 #Default height for multi-cell |
|
| 238 |
fontSize = 10 #Font size before shinking |
|
| 239 |
maxMultiColWidth = 120 #Maximium column width before wrapping |
|
| 240 |
maxCustomContent = 0 #Issue that contains the most custom fields |
|
| 241 |
|
|
| 242 |
arrMaxColLen = Array.new(@query.column_names.length) #Column Width in terms of characters based on the longest string |
|
| 243 |
colWidth = Array.new(@query.column_names.length) #Contains the width of each column in pdf units |
|
| 244 |
|
|
| 245 |
|
|
| 246 |
#Create an array containing the lengths of the largest fields in each column |
|
| 247 |
0.upto(@query.column_names.length - 1) do |cntr| |
|
| 248 |
currentMax = 0; |
|
| 249 |
@issues.each do |issue| |
|
| 250 |
valueLength = getValue(issue,cntr).length() |
|
| 251 |
if(valueLength > currentMax) |
|
| 252 |
currentMax = valueLength |
|
| 253 |
end |
|
| 254 |
arrMaxColLen[cntr] = currentMax |
|
| 255 |
end |
|
| 256 |
end |
|
| 257 |
|
|
| 258 |
#See if any of the headers are longer than the largest value |
|
| 259 |
0.upto(@query.column_names.length - 1) do |cntr| |
|
| 260 |
len = 0 #Length of the column name header |
|
| 261 |
re = /^cf.*/ #Identify column fields by their column headers |
|
| 262 |
|
|
| 263 |
#Finds the length of the column name header |
|
| 264 |
if (!re.match(@query.column_names[cntr].to_s())) |
|
| 265 |
len = @query.column_names[cntr].to_s().length() |
|
| 266 |
else #Custom Field |
|
| 267 |
max = 0 |
|
| 268 |
0.upto(@issues.length - 1) do |issue| |
|
| 269 |
if(@issues[issue].custom_values.length() > max) |
|
| 270 |
max = @issues[issue].custom_values.length() |
|
| 271 |
maxCustomContent = issue #Issue that contains the most custom fields |
|
| 272 |
end |
|
| 273 |
end |
|
| 274 |
columnNameId = getIDFromCustomColName(@query.column_names[cntr].to_s()) |
|
| 275 |
0.upto(@issues[maxCustomContent].custom_values.length - 1) do |custom_value| |
|
| 276 |
customField = @issues[maxCustomContent].custom_values[custom_value].custom_field |
|
| 277 |
|
|
| 278 |
if (customField.id.to_s() == columnNameId) |
|
| 279 |
len = customField.name.length() #Length of the header of the custom fields |
|
| 280 |
end |
|
| 281 |
end |
|
| 282 |
end |
|
| 283 |
|
|
| 284 |
#Check if column name headers are larger than the fields |
|
| 285 |
if(arrMaxColLen[cntr] < len) |
|
| 286 |
arrMaxColLen[cntr] = len |
|
| 287 |
end |
|
| 288 |
end |
|
| 289 |
|
|
| 290 |
#Set each column width given the value lengths |
|
| 291 |
0.upto(@query.column_names.length - 1) do |cntr| |
|
| 292 |
ourColumnWidth = arrMaxColLen[cntr] * 1.5 + 10 |
|
| 293 |
|
|
| 294 |
colWidth[cntr] = (ourColumnWidth < maxMultiColWidth) ? ourColumnWidth : maxMultiColWidth |
|
| 295 |
end |
|
| 296 |
|
|
| 297 |
#Total column length of the whole page |
|
| 298 |
total = 0 |
|
| 299 |
0.upto(colWidth.length - 1) do |cntr| |
|
| 300 |
total = total + colWidth[cntr] |
|
| 301 |
end |
|
| 302 |
|
|
| 303 |
#Scales the font sizes based on column data width |
|
| 304 |
percent = 1.0 |
|
| 305 |
if(total > MAXPDFWIDTH) |
|
| 306 |
percent = MAXPDFWIDTH.to_f()/total.to_f() |
|
| 307 |
maxMultiColWidth = percent * maxMultiColWidth |
|
| 308 |
0.upto(@query.column_names.length - 1) do |cntr| |
|
| 309 |
colWidth[cntr] = colWidth[cntr] * percent |
|
| 310 |
end |
|
| 311 |
fontSize = fontSize * 0.75 * percent |
|
| 312 |
pdf.SetFontStyle('B',fontSize)
|
|
| 313 |
end |
|
| 314 |
|
|
| 315 |
printColumnHeadersPDF(@query, @issues, colWidth, row_height, fontSize, pdf, maxCustomContent) |
|
| 316 |
printFieldsPDF (@query, @issues, colWidth, row_height, fontSize, pdf, rowHeightMultiCell, maxMultiColWidth) |
|
| 317 |
end |
|
| 318 |
|
|
| 110 | 319 |
# Returns a PDF string of a list of issues |
| 111 |
def issues_to_pdf(issues, project, query)
|
|
| 320 |
def issues_to_pdf(issues, query, project)
|
|
| 112 | 321 |
pdf = IFPDF.new(current_language) |
| 113 |
title = project ? "#{project} - #{l(:label_issue_plural)}" : "#{l(:label_issue_plural)}"
|
|
| 322 |
if(query.name != "_") |
|
| 323 |
title = project ? "#{project} - #{@query.name.to_s()}" : " #{@query.name.to_s()}"
|
|
| 324 |
else |
|
| 325 |
title = project ? "#{project} - #{l(:label_issue_plural)}" : "#{l(:label_issue_plural)}"
|
|
| 326 |
end |
|
| 114 | 327 |
pdf.SetTitle(title) |
| 115 | 328 |
pdf.AliasNbPages |
| 116 | 329 |
pdf.footer_date = format_date(Date.today) |
| ... | ... | |
| 122 | 335 |
pdf.Cell(190,10, title) |
| 123 | 336 |
pdf.Ln |
| 124 | 337 |
|
| 125 |
# headers |
|
| 126 |
pdf.SetFontStyle('B',10)
|
|
| 127 |
pdf.SetFillColor(230, 230, 230) |
|
| 128 |
pdf.Cell(15, row_height, "#", 0, 0, 'L', 1) |
|
| 129 |
pdf.Cell(30, row_height, l(:field_tracker), 0, 0, 'L', 1) |
|
| 130 |
pdf.Cell(30, row_height, l(:field_status), 0, 0, 'L', 1) |
|
| 131 |
pdf.Cell(30, row_height, l(:field_priority), 0, 0, 'L', 1) |
|
| 132 |
pdf.Cell(40, row_height, l(:field_assigned_to), 0, 0, 'L', 1) |
|
| 133 |
pdf.Cell(25, row_height, l(:field_updated_on), 0, 0, 'L', 1) |
|
| 134 |
pdf.Cell(0, row_height, l(:field_subject), 0, 0, 'L', 1) |
|
| 135 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 136 |
pdf.Ln |
|
| 137 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 138 |
pdf.SetY(pdf.GetY() + 1) |
|
| 338 |
# headers |
|
| 339 |
#If there are column names |
|
| 340 |
if(@query.column_names != nil) |
|
| 341 |
printUserQuery(pdf, row_height) |
|
| 342 |
else #If there are no columns |
|
| 343 |
pdf.SetFontStyle('B',10)
|
|
| 344 |
pdf.SetFillColor(230, 230, 230) |
|
| 345 |
pdf.Cell(15, row_height, "#", 0, 0, 'L', 1) |
|
| 346 |
pdf.Cell(30, row_height, l(:field_tracker), 0, 0, 'L', 1) |
|
| 347 |
pdf.Cell(30, row_height, l(:field_status), 0, 0, 'L', 1) |
|
| 348 |
pdf.Cell(30, row_height, l(:field_priority), 0, 0, 'L', 1) |
|
| 349 |
pdf.Cell(40, row_height, l(:field_assigned_to), 0, 0, 'L', 1) |
|
| 350 |
pdf.Cell(25, row_height, l(:field_updated_on), 0, 0, 'L', 1) |
|
| 351 |
pdf.Cell(0, row_height, l(:field_subject), 0, 0, 'L', 1) |
|
| 352 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 353 |
pdf.Ln |
|
| 354 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 355 |
pdf.SetY(pdf.GetY() + 1) |
|
| 139 | 356 |
|
| 140 |
# rows |
|
| 141 |
pdf.SetFontStyle('',9)
|
|
| 142 |
pdf.SetFillColor(255, 255, 255) |
|
| 143 |
group = false |
|
| 144 |
issues.each do |issue| |
|
| 145 |
if query.grouped? && issue.send(query.group_by) != group |
|
| 146 |
group = issue.send(query.group_by) |
|
| 147 |
pdf.SetFontStyle('B',10)
|
|
| 148 |
pdf.Cell(0, row_height, "#{group.blank? ? 'None' : group.to_s}", 0, 1, 'L')
|
|
| 357 |
# rows |
|
| 358 |
pdf.SetFontStyle('',9)
|
|
| 359 |
pdf.SetFillColor(255, 255, 255) |
|
| 360 |
group = false |
|
| 361 |
issues.each do |issue| |
|
| 362 |
if query.grouped? && issue.send(query.group_by) != group |
|
| 363 |
group = issue.send(query.group_by) |
|
| 364 |
pdf.SetFontStyle('B',10)
|
|
| 365 |
pdf.Cell(0, row_height, "#{group.blank? ? 'None' : group.to_s}", 0, 1, 'L')
|
|
| 366 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 367 |
pdf.SetY(pdf.GetY() + 0.5) |
|
| 368 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 369 |
pdf.SetY(pdf.GetY() + 1) |
|
| 370 |
pdf.SetFontStyle('',9)
|
|
| 371 |
end |
|
| 372 |
pdf.Cell(15, row_height, issue.id.to_s, 0, 0, 'L', 1) |
|
| 373 |
pdf.Cell(30, row_height, issue.tracker.name, 0, 0, 'L', 1) |
|
| 374 |
pdf.Cell(30, row_height, issue.status.name, 0, 0, 'L', 1) |
|
| 375 |
pdf.Cell(30, row_height, issue.priority.name, 0, 0, 'L', 1) |
|
| 376 |
pdf.Cell(40, row_height, issue.assigned_to ? issue.assigned_to.to_s : '', 0, 0, 'L', 1) |
|
| 377 |
pdf.Cell(25, row_height, format_date(issue.updated_on), 0, 0, 'L', 1) |
|
| 378 |
pdf.MultiCell(0, row_height, (project == issue.project ? issue.subject : "#{issue.project} - #{issue.subject}"))
|
|
| 149 | 379 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
| 150 |
pdf.SetY(pdf.GetY() + 0.5) |
|
| 151 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 152 | 380 |
pdf.SetY(pdf.GetY() + 1) |
| 153 |
pdf.SetFontStyle('',9)
|
|
| 154 | 381 |
end |
| 155 |
pdf.Cell(15, row_height, issue.id.to_s, 0, 0, 'L', 1) |
|
| 156 |
pdf.Cell(30, row_height, issue.tracker.name, 0, 0, 'L', 1) |
|
| 157 |
pdf.Cell(30, row_height, issue.status.name, 0, 0, 'L', 1) |
|
| 158 |
pdf.Cell(30, row_height, issue.priority.name, 0, 0, 'L', 1) |
|
| 159 |
pdf.Cell(40, row_height, issue.assigned_to ? issue.assigned_to.to_s : '', 0, 0, 'L', 1) |
|
| 160 |
pdf.Cell(25, row_height, format_date(issue.updated_on), 0, 0, 'L', 1) |
|
| 161 |
pdf.MultiCell(0, row_height, (project == issue.project ? issue.subject : "#{issue.project} - #{issue.subject}"))
|
|
| 162 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 163 |
pdf.SetY(pdf.GetY() + 1) |
|
| 164 |
end |
|
| 382 |
end |
|
| 165 | 383 |
pdf.Output |
| 166 | 384 |
end |
| 167 | 385 |
|
| vendor/plugins/rfpdf/lib/rfpdf/fpdf.rb (working copy) | ||
|---|---|---|
| 19 | 19 |
# Handle '\n' at the beginning of a string |
| 20 | 20 |
# Bookmarks contributed by Sylvain Lafleur. |
| 21 | 21 | |
| 22 |
# Matthew Keniston & Alex Mendes |
|
| 23 |
# BackOffice Associates |
|
| 24 |
# 7/7/09 fixes for: 1) export to PDF/CSV ignoring custom queries; 2) query display problems |
|
| 25 | ||
| 22 | 26 |
require 'date' |
| 23 | 27 |
require 'zlib' |
| 24 | 28 | |
| ... | ... | |
| 279 | 283 |
end |
| 280 | 284 | |
| 281 | 285 |
def AddPage(orientation='') |
| 286 |
#Added 7/2/09 |
|
| 287 |
#************************************ |
|
| 288 |
#If next page already exists |
|
| 289 |
if(getPage() < @pages.length() - 1) |
|
| 290 |
setPage(getPage() + 1) |
|
| 291 |
@y = 0 + @tMargin |
|
| 292 |
#************************************ |
|
| 293 |
else |
|
| 282 | 294 |
# Start a new page |
| 283 | 295 |
self.Open if @state==0 |
| 284 | 296 |
family=@FontFamily |
| ... | ... | |
| 333 | 345 |
end |
| 334 | 346 |
@TextColor=tc |
| 335 | 347 |
@ColorFlag=cf |
| 348 |
end |
|
| 336 | 349 |
end |
| 337 | 350 | |
| 338 | 351 |
def Header |
| ... | ... | |
| 872 | 885 |
@y=@y+h |
| 873 | 886 |
end |
| 874 | 887 |
end |
| 888 |
|
|
| 889 |
#Added 7/2/09 for pdf.rb |
|
| 890 |
#*********************** |
|
| 891 |
def getFontFamily |
|
| 892 |
@FontFamily |
|
| 893 |
end |
|
| 894 |
|
|
| 895 |
def getFontStyle |
|
| 896 |
@FontStyle |
|
| 897 |
end |
|
| 898 |
|
|
| 899 |
def getFontSize |
|
| 900 |
@FontSizePt |
|
| 901 |
end |
|
| 902 |
def getPage() |
|
| 903 |
@page |
|
| 904 |
end |
|
| 905 |
|
|
| 906 |
def setPage(page) |
|
| 907 |
@page = page |
|
| 908 |
end |
|
| 909 |
#********************** |
|
| 875 | 910 | |
| 876 | 911 |
def GetX |
| 877 | 912 |
# Get x position |
| public/stylesheets/application.css (working copy) | ||
|---|---|---|
| 94 | 94 |
tr.project td.name a { padding-left: 16px; white-space:nowrap; }
|
| 95 | 95 |
tr.project.parent td.name a { background: url('../images/bullet_toggle_minus.png') no-repeat; }
|
| 96 | 96 | |
| 97 | ||
| 98 |
/*** Changed 7/7/09 - nowrap was a bad idea! ***/ |
|
| 99 |
tr.issue { text-align: center; white-space: normal; }
|
|
| 100 |
/* |
|
| 97 | 101 |
tr.issue { text-align: center; white-space: nowrap; }
|
| 98 | 102 |
tr.issue td.subject, tr.issue td.category, td.assigned_to { white-space: normal; }
|
| 103 |
*/ |
|
| 104 | ||
| 105 |
/*** Changed 7/7/09 - Added left alignment for text-based custom columns ***/ |
|
| 106 |
tr.issue td.subject, tr.issue td.text, tr.issue td.string { text-align: left; }
|
|
| 107 |
/* |
|
| 99 | 108 |
tr.issue td.subject { text-align: left; }
|
| 109 |
*/ |
|
| 110 | ||
| 100 | 111 |
tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
|
| 101 | 112 | |
| 102 | 113 |
tr.entry { border: 1px solid #f8f8f8; }
|