0874dc746ad6e4a77cd629f92491db3d6c549f0a
[tine20] / tine20 / Tinebase / Export / Spreadsheet / Xls.php
1 <?php
2 /**
3  * Tinebase xls generation class
4  *
5  * @package     Tinebase
6  * @subpackage  Export
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @author      Philipp Schüle <p.schuele@metaways.de>
9  * @copyright   Copyright (c) 2009-2011 Metaways Infosystems GmbH (http://www.metaways.de)
10  * 
11  */
12
13 // set include path for phpexcel
14 set_include_path(dirname(dirname(dirname(dirname(__FILE__)))) . '/library/PHPExcel' . PATH_SEPARATOR . get_include_path() );
15
16 /**
17  * Tinebase xls generation class
18  * 
19  * @package     Tinebase
20  * @subpackage  Export
21  * 
22  */
23 class Tinebase_Export_Spreadsheet_Xls extends Tinebase_Export_Spreadsheet_Abstract implements Tinebase_Record_IteratableInterface
24 {
25     /**
26      * current row number
27      * 
28      * @var integer
29      */
30     protected $_currentRowIndex = 0;
31     
32     /**
33      * the phpexcel object
34      * 
35      * @var PHPExcel
36      */
37     protected $_excelObject = NULL;
38     
39     /**
40      * format strings
41      * 
42      * @var string
43      */
44     protected $_format = 'xls';
45
46     /**
47      * generate export
48      * 
49      * @return PHPExcel
50      */
51     public function generate()
52     {
53         $this->_createDocument();
54         $this->_setDocumentProperties();
55         
56         $this->_addHeader();
57         $this->_exportRecords();
58         
59         $this->_setColumnWidths();
60         
61         return $this->getDocument();
62     }
63     
64     /**
65      * sets the colunm widths by config column->width
66      */
67     protected function _setColumnWidths()
68     {
69         $index = 0;
70         foreach($this->_config->columns->column as $field) {
71             if ($this->_groupBy !== NULL && $this->_groupBy == $field->identifier) {
72                 continue;
73             }
74             
75             if (isset($field->width)) {
76                 $this->_excelObject->getActiveSheet()->getColumnDimensionByColumn($index)->setWidth((string) $field->width);
77             }
78             
79             $index++;
80         }
81     }
82     
83     /**
84      * add header
85      */
86     protected function _addHeader()
87     {
88         $patterns = array(
89             '/\{date\}/',
90             '/\{user\}/',
91         );
92         
93         $replacements = array(
94             Zend_Date::now()->toString(Zend_Locale_Format::getDateFormat($this->_locale), $this->_locale),
95             Tinebase_Core::getUser()->accountDisplayName,
96         );
97         
98         $this->_currentRowIndex = 1;
99         
100         $columnId = 0;
101         
102         if ($this->_config->headers) {
103             foreach($this->_config->headers->header as $headerCell) {
104                 // replace data
105                 $value = preg_replace($patterns, $replacements, $headerCell);
106                 
107                 $this->_excelObject->getActiveSheet()->setCellValueByColumnAndRow(0, $this->_currentRowIndex, $value);
108                 
109                 $this->_currentRowIndex++;
110             }
111         
112             $this->_currentRowIndex++;
113         }
114         
115         if (isset($this->_config->header) && $this->_config->header) {
116             $this->_addHead();
117         }
118     }
119     
120     /**
121      * get excel object
122      * 
123      * @return PHPExcel
124      */
125     public function getDocument()
126     {
127         return $this->_excelObject;
128     }
129     
130     /**
131      * get export content type
132      * 
133      * @return string
134      */
135     public function getDownloadContentType()
136     {
137         $contentType = ($this->_config->writer == 'Excel2007') 
138             // Excel 2007 content type
139             ? 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
140             // Excel 5 content type or other
141             : 'application/vnd.ms-excel';
142                 
143         return $contentType;
144     }
145     
146     /**
147      * return download filename
148      * @param string $_appName
149      * @param string $_format
150      */
151     public function getDownloadFilename($_appName, $_format)
152     {
153         $result = parent::getDownloadFilename($_appName, $_format);
154         
155         if ($this->_config->writer == 'Excel2007') {
156             // excel2007 extension is .xlsx
157             $result .= 'x';
158         }
159         
160         return $result;
161     }
162     
163     /**
164      * output result
165      */
166     public function write()
167     {
168         $xlsFormat = ($this->_config->writer) ? $this->_config->writer : 'Excel5';
169         Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Creating and sending xls to client (Format: ' . $xlsFormat . ').');
170         $xlswriter = PHPExcel_IOFactory::createWriter($this->_excelObject, $xlsFormat);
171         
172         // precalcualting formula values costs tons of time, because sum formulas are like SUM C1:C65000
173         $xlswriter->setPreCalculateFormulas(FALSE);
174         
175         $xlswriter->save('php://output');
176     }
177     
178     /**
179      * create new excel document
180      * 
181      * @return void
182      */
183     protected function _createDocument()
184     {
185         $templateFile = $this->_getTemplateFilename();
186         
187         if ($templateFile !== NULL) {
188             
189             if (! $this->_config->reader || $this->_config->reader == 'autodetection') {
190                 $this->_excelObject = PHPExcel_IOFactory::load($templateFile);
191             } else {
192                 $reader = PHPExcel_IOFactory::createReader($this->_config->reader);
193                 $this->_excelObject = $reader->load($templateFile);
194             }
195             
196             // need to unregister the zip stream wrapper because it is overwritten by PHPExcel!
197             // TODO file a bugreport to PHPExcel 
198             @stream_wrapper_restore("zip");
199             
200             $activeSheet = isset($this->_config->sheet) ? $this->_config->sheet : 1;
201             $this->_excelObject->setActiveSheetIndex($activeSheet);
202         } else {
203             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Creating new PHPExcel object.');
204             $this->_excelObject = new PHPExcel();
205         }
206     }
207     
208     /**
209      * get cell value
210      * 
211      * @param Zend_Config $_field
212      * @param Tinebase_Record_Interface $_record
213      * @param string $_cellType
214      * @return string
215      */
216     protected function _getCellValue(Zend_Config $_field, Tinebase_Record_Interface $_record, &$_cellType)
217     {
218         switch ($_field->type) {
219             case 'datetime':
220             case 'date':
221                 if ($_record->{$_field->identifier} instanceof DateTime) {
222                     if (! isset($_field->timestamp) || $_field->timestamp == 1) {
223                         $result = PHPExcel_Shared_Date::PHPToExcel($_record->{$_field->identifier}->getTimestamp());
224                     } else {
225                         $result = parent::_getCellValue($_field, $_record, $_cellType);
226                     }
227                 } else {
228                     $result = $_record->{$_field->identifier};
229                 }
230                 
231                 // empty date cells, get displayed as 30.12.1899
232                 if (empty($result)) {
233                     $result = NULL;
234                 }
235                 break;
236             default:
237                 $result = parent::_getCellValue($_field, $_record, $_cellType);
238                 break;
239         }
240         
241         return $result;
242     }
243     
244     /**
245      * set properties
246      * 
247      * @return void
248      */
249     protected function _setDocumentProperties()
250     {
251         // set metadata/properties
252         if ($this->_config->writer == 'Excel2007') {
253             $this->_excelObject->getProperties()
254                 ->setCreator(Tinebase_Core::getUser()->accountDisplayName)
255                 ->setLastModifiedBy(Tinebase_Core::getUser()->accountDisplayName)
256                 ->setTitle('Tine 2.0 ' . $this->_applicationName . ' Export')
257                 ->setSubject('Office 2007 XLSX Test Document')
258                 ->setDescription('Export for ' . $this->_applicationName . ', generated using PHP classes.')
259                 ->setKeywords("tine20 openxml php")
260                 ->setCreated(Zend_Date::now()->get());
261             //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($this->_excelObject->getProperties(), true));
262         }
263     }
264     
265     /**
266      * add xls head (headline, column styles)
267      */
268     protected function _addHead()
269     {
270         $columnId = 0;
271         
272         foreach($this->_config->columns->column as $field) {
273             if ($this->_groupBy !== NULL && $this->_groupBy == $field->identifier) {
274                 continue;
275             }
276             $headerValue = ($field->header) ? $this->_translate->translate($field->header) : $field->identifier;
277             $this->_excelObject->getActiveSheet()->setCellValueByColumnAndRow($columnId++, $this->_currentRowIndex, $headerValue);
278         }
279         
280         $this->_currentRowIndex++;
281     }
282     
283     /**
284      * adds a header for each group
285      * 
286      * @param Tinebase_Record_Interface $record
287      */
288     protected function _addGroupHeader($group)
289     {
290         // find out fieldconfig, if not found already
291         if (! $this->_groupByFieldConfig) {
292             $this->_columnCount = 0;
293             foreach ($this->_config->columns->column as $field) {
294                 if ($field->identifier == $this->_groupBy) {
295                     $this->_groupByFieldConfig = $field;
296                     $this->_groupByFieldType = (isset($field->type)) ? $field->type : 'string';
297                 }
298                 
299                 $this->_columnCount++;
300             }
301         } else {
302             $this->_currentRowIndex++;
303             $this->_currentRowIndex++;
304         }
305         
306         $fontColor       = 'b79511';
307         $backgroundColor = '008bcf';
308         $fontSize        = 16;
309         
310         if ($this->_config->grouping->groupheader) {#
311             $gh = $this->_config->grouping->groupheader;
312             
313             $fontColor       = $gh->fontcolor ? (string) $gh->fontcolor : $fontColor;
314             $backgroundColor = $gh->backgroundcolor ? (string) $gh->backgroundcolor : $backgroundColor;
315             $fontSize        = $gh->fontsize ? (int) $gh->fontsize : $fontSize;
316         }
317         
318         $cell = $this->_excelObject->getActiveSheet()->setCellValueByColumnAndRow(0, $this->_currentRowIndex, $group, TRUE);
319         
320         $styleArray = array(
321             'font'  => array(
322                 'bold'  => true,
323                 'color' => array('rgb' => $fontColor),
324                 'size'  => $fontSize,
325             ),
326             'fill' => array(
327                 'type' => PHPExcel_Style_Fill::FILL_SOLID,
328                 'color' => array('rgb' => $backgroundColor)
329             )
330         );
331         
332         $this->_excelObject->getActiveSheet()->getStyle($cell->getCoordinate())->applyFromArray($styleArray);
333         
334         $this->_excelObject->getActiveSheet()->mergeCellsByColumnAndRow(0, $this->_currentRowIndex, ($this->_columnCount - 2), $this->_currentRowIndex);
335         
336         $this->_currentRowIndex++;
337         
338         if ($this->_config->grouping->header) {
339             $this->_addHead();
340         }
341         
342         $this->_currentRowIndex++;
343     }
344
345     /**
346      * (non-PHPdoc)
347      * @see Tinebase_Export_Abstract::_onAfterExportRecords()
348      */
349     protected function _onAfterExportRecords($result)
350     {
351         // save number of records (only if we have more than 1 sheets / records are on the second sheet by default)
352         if ($this->_excelObject->getSheetCount() > 1) {
353             $this->_excelObject->setActiveSheetIndex(0);
354             $this->_excelObject->getActiveSheet()->setCellValueByColumnAndRow(5, 2, $result['totalcount']);
355         }
356     }
357     
358     /**
359      * add body rows
360      *
361      * @param Tinebase_Record_RecordSet $records
362      * 
363      * @todo add formulas
364      */
365     public function processIteration($_records)
366     {
367         $this->_resolveRecords($_records);
368         
369         $lastGroup = NULL;
370         $woString = $this->_translate->_('Without company assigned');
371         
372         // add record rows
373         $i = 0;
374         foreach ($_records as $record) {
375             if ($this->_groupBy !== NULL && $lastGroup !== $record->{$this->_groupBy} 
376                 && (! (empty($record->{$this->_groupBy}) && $record->{$this->_groupBy} == $woString))) 
377             {
378                 $lastGroup = empty($record->{$this->_groupBy}) ? $woString : $record->{$this->_groupBy};
379                 $this->_addGroupHeader($lastGroup);
380             }
381             
382             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
383                 . ' ' . print_r($record->toArray(), true));
384             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
385                 . ' ' . print_r($this->_config->columns->toArray(), true));
386             
387             $columnId = 0;
388             
389             foreach ($this->_config->columns->column as $field) {
390                 // don't show group by field
391                 if ($this->_groupBy !== NULL && $field->identifier == $this->_groupBy) {
392                     continue;
393                 }
394                 
395                 // get type and value for cell
396                 $cellType = (isset($field->type)) ? $field->type : 'string';
397                 $cellValue = $this->_getCellValue($field, $record, $cellType);
398                 
399                 // add formula
400                 if ($field->formula) {
401                     if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
402                         . ' Adding formula: ' . $field->formula);
403                     $cellValue = $field->formula;
404                 }
405                 
406                 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
407                     . ' Setting col/row' . $columnId . ' / ' . $this->_currentRowIndex . ' = ' . $cellValue);
408                 
409                 $this->_excelObject->getActiveSheet()->setCellValueByColumnAndRow($columnId++, $this->_currentRowIndex, $cellValue);
410             }
411             
412             $i++;
413             $this->_currentRowIndex++;
414         }
415     }
416     
417     /**
418      * (non-PHPdoc)
419      * @see Tinebase_Export_Abstract::_exportRecords()
420      */
421     protected function _exportRecords()
422     {
423         parent::_exportRecords();
424         
425         $sheet = $this->_excelObject->getActiveSheet();
426         
427         for ($i = 0; $i < $this->_columnCount; $i++) {
428             $sheet->getColumnDimension($i)->setAutoSize(TRUE);
429         }
430     }
431 }