From: Nik Okuntseff Date: Sun, 6 Mar 2016 07:03:47 +0000 (-0800) Subject: Added PDF export option for reports X-Git-Tag: timetracker_1.19-1~1845 X-Git-Url: http://wagnertech.de/git?a=commitdiff_plain;h=ed6483fa05ff6f489a7cfc4ff72fdc4d4d8f3889;p=timetracker.git Added PDF export option for reports This is an initial implementation of PDF export option for reports with help of TCPDF library. Some rendering issues remain for East European (Russian) and possibly other languages. --- diff --git a/WEB-INF/resources/en.lang.php b/WEB-INF/resources/en.lang.php index d3834a4b..7a29222a 100644 --- a/WEB-INF/resources/en.lang.php +++ b/WEB-INF/resources/en.lang.php @@ -182,6 +182,7 @@ $i18n_key_words = array( 'label.role_manager' => '(manager)', 'label.role_comanager' => '(co-manager)', 'label.role_admin' => '(administrator)', +'label.page' => 'Page', // Labels for plugins (extensions to Time Tracker that provide additional features). 'label.custom_fields' => 'Custom fields', 'label.type' => 'Type', diff --git a/WEB-INF/resources/fi.lang.php b/WEB-INF/resources/fi.lang.php index b8f9055a..9315a545 100644 --- a/WEB-INF/resources/fi.lang.php +++ b/WEB-INF/resources/fi.lang.php @@ -192,6 +192,9 @@ $i18n_key_words = array( 'label.cron_schedule' => 'Cron-ajoitus', 'label.what_is_it' => 'Mikä se on?', +// TODO? missing? +'label.page' => 'Sivu', + // Form titles. 'title.login' => 'Kirjautuminen', 'title.teams' => 'Tiimit', diff --git a/WEB-INF/resources/ru.lang.php b/WEB-INF/resources/ru.lang.php index af55936f..be8c3951 100644 --- a/WEB-INF/resources/ru.lang.php +++ b/WEB-INF/resources/ru.lang.php @@ -182,6 +182,7 @@ $i18n_key_words = array( 'label.role_manager' => '(менеджер)', 'label.role_comanager' => '(ассистент менеджера)', 'label.role_admin' => '(администратор)', +'label.page' => 'Стр', // Labels for plugins (extensions to Time Tracker that provide additional features). 'label.custom_fields' => 'Дополнительные поля', 'label.type' => 'Тип', diff --git a/WEB-INF/templates/footer.tpl b/WEB-INF/templates/footer.tpl index 9ac13ccb..ab1dcf04 100644 --- a/WEB-INF/templates/footer.tpl +++ b/WEB-INF/templates/footer.tpl @@ -14,7 +14,7 @@
- diff --git a/WEB-INF/templates/report.tpl b/WEB-INF/templates/report.tpl index eb7ed473..6192b146 100644 --- a/WEB-INF/templates/report.tpl +++ b/WEB-INF/templates/report.tpl @@ -7,7 +7,7 @@ '; + $html .= ""; + $html .= ''; + if ($user->canManageTeam() || $user->isClient()) { $colspan++; $html .= ''; } + if ($bean->getAttribute('chclient')) { $colspan++; $html .= ''; } + if ($bean->getAttribute('chproject')) { $colspan++; $html .= ''; } + if ($bean->getAttribute('chtask')) { $colspan++; $html .= ''; } + if ($bean->getAttribute('chcf_1')) { $colspan++; $html .= ''; } + if ($bean->getAttribute('chstart')) { $colspan++; $html .= "'; } + if ($bean->getAttribute('chfinish')) { $colspan++; $html .= "'; } + if ($bean->getAttribute('chduration')) { $colspan++; $html .= "'; } + if ($bean->getAttribute('chnote')) { $colspan++; $html .= ''; } + if ($bean->getAttribute('chcost')) { $colspan++; $html .= "'; } + if ($bean->getAttribute('chinvoice')) { $colspan++; $html .= ''; } + $html .= ''; + $html .= ''; + + foreach ($items as $item) { + // Print a subtotal for a block of grouped values. + $cur_date = $item['date']; + if ($print_subtotals) { + $cur_grouped_by = $item['grouped_by']; + if ($cur_grouped_by != $prev_grouped_by && !$first_pass) { + $html .= ''; + $html .= ''; + if ($user->canManageTeam() || $user->isClient()) { + $html .= ''; + } + if ($bean->getAttribute('chclient')) { + $html .= ''; + } + if ($bean->getAttribute('chproject')) { + $html .= ''; + } + if ($bean->getAttribute('chtask')) { + $html .= ''; + } + if ($bean->getAttribute('chcf_1')) { + $html .= ''; + } + if ($bean->getAttribute('chstart')) $html .= ''; + if ($bean->getAttribute('chfinish')) $html .= ''; + if ($bean->getAttribute('chduration')) $html .= "'; + if ($bean->getAttribute('chnote')) $html .= ''; + if ($bean->getAttribute('chcost')) { + $html .= "'; + } + if ($bean->getAttribute('chinvoice')) $html .= ''; + $html .= ''; + $html .= ''; + } + $first_pass = false; + } + + // Print a regular row. + $html .= ''; + $html .= ''; + if ($user->canManageTeam() || $user->isClient()) $html .= ''; + if ($bean->getAttribute('chclient')) $html .= ''; + if ($bean->getAttribute('chproject')) $html .= ''; + if ($bean->getAttribute('chtask')) $html .= ''; + if ($bean->getAttribute('chcf_1')) $html .= ''; + if ($bean->getAttribute('chstart')) $html .= "'; + if ($bean->getAttribute('chfinish')) $html .= "'; + if ($bean->getAttribute('chduration')) $html .= "'; + if ($bean->getAttribute('chnote')) $html .= ''; + if ($bean->getAttribute('chcost')) { + $html .= "'; + } + if ($bean->getAttribute('chinvoice')) $html .= ''; + $html .= ''; + + $prev_date = $item['date']; + if ($print_subtotals) $prev_grouped_by = $item['grouped_by']; + } + + // Print a terminating subtotal. + if ($print_subtotals) { + $html .= ''; + $html .= ''; + if ($user->canManageTeam() || $user->isClient()) { + $html .= ''; + } + if ($bean->getAttribute('chclient')) { + $html .= ''; + } + if ($bean->getAttribute('chproject')) { + $html .= ''; + } + if ($bean->getAttribute('chtask')) { + $html .= ''; + } + if ($bean->getAttribute('chcf_1')) { + $html .= ''; + } + if ($bean->getAttribute('chstart')) $html .= ''; + if ($bean->getAttribute('chfinish')) $html .= ''; + if ($bean->getAttribute('chduration')) $html .= "'; + if ($bean->getAttribute('chnote')) $html .= ''; + if ($bean->getAttribute('chcost')) { + $html .= "'; + } + if ($bean->getAttribute('chinvoice')) $html .= ''; + $html .= ''; + } + + // Print totals. + $html .= ''; + $html .= ''; + $html .= ''; + if ($user->canManageTeam() || $user->isClient()) $html .= ''; + if ($bean->getAttribute('chclient')) $html .= ''; + if ($bean->getAttribute('chproject')) $html .= ''; + if ($bean->getAttribute('chtask')) $html .= ''; + if ($bean->getAttribute('chcf_1')) $html .= ''; + if ($bean->getAttribute('chstart')) $html .= ''; + if ($bean->getAttribute('chfinish')) $html .= ''; + if ($bean->getAttribute('chduration')) $html .= "'; + if ($bean->getAttribute('chnote')) $html .= ''; + if ($bean->getAttribute('chcost')) { + $html .= "'; + } + if ($bean->getAttribute('chinvoice')) $html .= ''; + $html .= ''; + $html .= '
 Anuko Time Tracker 1.9.12.3387 | Copyright © Anuko | +  Anuko Time Tracker 1.9.13.3389 | Copyright © Anuko | {$i18n.footer.credits} | {$i18n.footer.license} - +
{$i18n.form.report.export} XML {$i18n.label.or} CSV{$i18n.form.report.export} {if file_exists('WEB-INF/lib/tcpdf')}PDF,{/if} XML {$i18n.label.or} CSV
diff --git a/topdf.php b/topdf.php new file mode 100644 index 00000000..744ef4a2 --- /dev/null +++ b/topdf.php @@ -0,0 +1,407 @@ +plugins))) { + require_once('plugins/CustomFields.class.php'); + $custom_fields = new CustomFields($user->team_id); +} + +// Report settings are stored in session bean before we get here. +$bean = new ActionForm('reportBean', new Form('reportForm'), $request); + +// There are 2 variations of report: totals only, or normal. Totals only means that the report +// is grouped by either date, user, client, project, task or cf_1 and user only needs to see subtotals by group. +$totals_only = ($bean->getAttribute('chtotalsonly') == '1'); + +// Determine group by header. +$group_by = $bean->getAttribute('group_by'); +if ('no_grouping' != $group_by) { + if ('cf_1' == $group_by) + $group_by_header = $custom_fields->fields[0]['label']; + else { + $key = 'label.'.$group_by; + $group_by_header = $i18n->getKey($key); + } +} + +// Obtain items for report. +if (!$totals_only) + $items = ttReportHelper::getItems($bean); // Individual entries. +if ($totals_only || 'no_grouping' != $group_by) + $subtotals = ttReportHelper::getSubtotals($bean); // Subtotals for groups of items. +$totals = ttReportHelper::getTotals($bean); // Totals for the entire report. + +// Assign variables that are used to print subtotals. +if ($items && 'no_grouping' != $group_by) { + $print_subtotals = true; + $first_pass = true; + $prev_grouped_by = ''; + $cur_grouped_by = ''; +} + +// Start preparing HTML to build PDF from. +$styleHeader = 'style="background-color:#a6ccf7;"'; +$styleSubtotal = 'style="background-color:#e0e0e0;"'; +$styleCentered = 'style="text-align:center;"'; +$styleRightAligned = 'style="text-align:right;"'; + +$title = $i18n->getKey('title.report').": ".$totals['start_date']." - ".$totals['end_date']; +$html = '

'.$title.'

'; +$html .= '
'; + +if ($totals_only) { + // We are building a "totals only" report with only subtotals and total. + $colspan = 1; // Column span for an empty row. + // Table header. + $html .= ''; + $html .= ""; + $html .= ''; + if ($bean->getAttribute('chduration')) { $colspan++; $html .= "'; } + if ($bean->getAttribute('chcost')) { $colspan++; $html .= "'; } + $html .= ''; + $html .= ''; + // Print subtotals. + foreach ($subtotals as $subtotal) { + $html .= ''; + $html .= ''; + if ($bean->getAttribute('chduration')) $html .= "'; + if ($bean->getAttribute('chcost')) { + $html .= "'; + } + $html .= ''; + } + // Print totals. + $html .= ''; + $html .= ""; + $html .= ''; + if ($bean->getAttribute('chduration')) $html .= "'; + if ($bean->getAttribute('chcost')) { + $html .= "'; + } + $html .= ''; + $html .= '
'.htmlspecialchars($group_by_header).'".$i18n->getKey('label.duration').'".$i18n->getKey('label.cost').'
'.htmlspecialchars($subtotal['name']).'".$subtotal['time'].'"; + if ($user->canManageTeam() || $user->isClient()) + $html .= $subtotal['cost']; + else + $html .= $subtotal['expenses']; + $html .= '
 
'.$i18n->getKey('label.total').'".$totals['time'].'"; + $html .= htmlspecialchars($user->currency).' '; + if ($user->canManageTeam() || $user->isClient()) + $html .= $totals['cost']; + else + $html .= $totals['expenses']; + $html .= '
'; +} else { + // We are building a normal report with items, optionally grouped with subtotals, and total. + $colspan = 1; // Column span for an empty row. + // Table header. + $html .= '
'.$i18n->getKey('label.date').''.$i18n->getKey('label.user').''.$i18n->getKey('label.client').''.$i18n->getKey('label.project').''.$i18n->getKey('label.task').''.htmlspecialchars($custom_fields->fields[0]['label']).'".$i18n->getKey('label.start').'".$i18n->getKey('label.finish').'".$i18n->getKey('label.duration').''.$i18n->getKey('label.note').'".$i18n->getKey('label.cost').''.$i18n->getKey('label.invoice').'
'.$i18n->getKey('label.subtotal').''; + if ($group_by == 'user') $html .= htmlspecialchars($subtotals[$prev_grouped_by]['name']); + $html .= ''; + if ($group_by == 'client') $html .= htmlspecialchars($subtotals[$prev_grouped_by]['name']); + $html .= ''; + if ($group_by == 'project') $html .= htmlspecialchars($subtotals[$prev_grouped_by]['name']); + $html .= ''; + if ($group_by == 'task') $html .= htmlspecialchars($subtotals[$prev_grouped_by]['name']); + $html .= ''; + if ($group_by == 'cf_1') $html .= htmlspecialchars($subtotals[$prev_grouped_by]['name']); + $html .= '".$subtotals[$prev_grouped_by]['time'].'"; + if ($user->canManageTeam() || $user->isClient()) + $html .= $subtotals[$prev_grouped_by]['cost']; + else + $html .= $subtotals[$prev_grouped_by]['expenses']; + $html .= '
 
'.$item['date'].''.htmlspecialchars($item['user']).''.htmlspecialchars($item['client']).''.htmlspecialchars($item['project']).''.htmlspecialchars($item['task']).''.htmlspecialchars($item['cf_1']).'".$item['start'].'".$item['finish'].'".$item['duration'].''.htmlspecialchars($item['note']).'"; + if ($user->canManageTeam() || $user->isClient()) + $html .= $item['cost']; + else + $html .= $item['expense']; + $html .= ''.htmlspecialchars($item['invoice']).'
'.$i18n->getKey('label.subtotal').''; + if ($group_by == 'user') $html .= htmlspecialchars($subtotals[$prev_grouped_by]['name']); + $html .= ''; + if ($group_by == 'client') $html .= htmlspecialchars($subtotals[$prev_grouped_by]['name']); + $html .= ''; + if ($group_by == 'project') $html .= htmlspecialchars($subtotals[$prev_grouped_by]['name']); + $html .= ''; + if ($group_by == 'task') $html .= htmlspecialchars($subtotals[$prev_grouped_by]['name']); + $html .= ''; + if ($group_by == 'cf_1') $html .= htmlspecialchars($subtotals[$prev_grouped_by]['name']); + $html .= '".$subtotals[$prev_grouped_by]['time'].'"; + if ($user->canManageTeam() || $user->isClient()) + $html .= $subtotals[$prev_grouped_by]['cost']; + else + $html .= $subtotals[$prev_grouped_by]['expenses']; + $html .= '
 
'.$i18n->getKey('label.total').'".$totals['time'].'".htmlspecialchars($user->currency).' '; + if ($user->canManageTeam() || $user->isClient()) + $html .= $totals['cost']; + else + $html .= $totals['expenses']; + $html .= '
'; +} +// By this time we have html ready. + +// Determine title for report. +$title = $i18n->getKey('title.report').": ".$totals['start_date']." - ".$totals['end_date']; + +header('Pragma: public'); // This is needed for IE8 to download files over https. +header('Content-Type: text/html; charset=utf-8'); +header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT'); +header('Cache-Control: no-store, no-cache, must-revalidate'); +header('Cache-Control: post-check=0, pre-check=0', false); +header('Cache-Control: private', false); + +header('Content-Type: application/pdf'); +header('Content-Disposition: attachment; filename="timesheet.pdf"'); + + +// Beginning of TCPDF code here. + +// Extend TCPDF class so that we can use custom header and footer. +class MyyPDF extends TCPDF { + + public $image_file = 'images/tt_logo.png'; // Image file for the logo in header. + public $page_word = 'Page'; // Localized "Page" word in footer, ex: Page 1/2. + + // SetImageFile - sets image file name. + public function SetImageFile($imgFile) { + $this->image_file = $imgFile; + } + + // SetPageWord - sets page word for footer. + public function SetPageWord($pageWord) { + $this->page_word = $pageWord; + } + + // Page header. + public function Header() { + // Print logo, which is the only element of our custom header. + $this->Image($this->image_file, 10, 10, '', '', '', '', 'T', false, 300, 'C', false, false, 0, false, false, false); + } + + // Page footer. + public function Footer() { + // Position at 15 mm from bottom. + $this->SetY(-15); + // Set font. + $this->SetFont('helvetica', 'I', 8); + // Print localized page number. + $this->Cell(0, 10, $this->page_word.' '.$this->getAliasNumPage().'/'.$this->getAliasNbPages(), 0, false, 'C', 0, '', 0, false, 'T', 'M'); + } +} + +// Create new PDF document. +$pdf = new MyyPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false); + +// If custom logo file exists - set it. +if (file_exists('images/'.$user->team_id.'.png')) + $pdf->SetImageFile('images/'.$user->team_id.'.png'); + +// Set page word for the footer. +$pdf->SetPageWord($i18n->getKey('label.page')); +// TODO: currently, we have problems rendering PDF in some languages such as Russian (headers, page word). +// Not sure how to fix it... One option is to switch to mPDF - consider. + +// Set document information. +$pdf->SetCreator(PDF_CREATOR); +$pdf->SetAuthor('Anuko Time Tracker'); +$pdf->SetTitle('Anuko Time Tracker Report'); +$pdf->SetSubject('Anuko Time Tracker Report'); +$pdf->SetKeywords('Anuko, time, tracker, report'); + +// Set margins. +$pdf->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT); +$pdf->SetHeaderMargin(PDF_MARGIN_HEADER); +$pdf->SetFooterMargin(PDF_MARGIN_FOOTER); + +// Set auto page breaks. +$pdf->SetAutoPageBreak(TRUE, PDF_MARGIN_BOTTOM); + +// Set image scale factor. +$pdf->setImageScale(PDF_IMAGE_SCALE_RATIO); + +// Add a page. +$pdf->AddPage(); + +// Set font. +$pdf->SetFont('helvetica', '', 10); + +// Write HTML. +$pdf->writeHTML($html, true, false, false, false, ''); + +// Close and output PDF document. +// $pdf->Output('timesheet.pdf', 'I'); // This will display inline in browser. +$pdf->Output('timesheet.pdf', 'D'); // D is for downloads. + +// End of of TCPDF code. +?> \ No newline at end of file