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; } |