40d1f84f0038c39ae252e37c5bb788be3e8c9644
[tine20] / tine20 / Tinebase / Export / Abstract.php
1 <?php
2 /**
3  * Tinebase Abstract export 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) 2010-2011 Metaways Infosystems GmbH (http://www.metaways.de)
10  * 
11  */
12
13 /**
14  * Tinebase Abstract export class
15  * 
16  * @package     Tinebase
17  * @subpackage    Export
18  * 
19  */
20 abstract class Tinebase_Export_Abstract
21 {
22     /**
23      * default export definition name
24      * 
25      * @var string
26      */
27     protected $_defaultExportname = 'default';
28         
29     /**
30      * the record controller
31      *
32      * @var Tinebase_Controller_Record_Abstract
33      */
34     protected $_controller = NULL;
35     
36     /**
37      * translation object
38      *
39      * @var Zend_Translate
40      */
41     protected $_translate;
42     
43     /**
44      * locale object
45      *
46      * @var Zend_Locale
47      */
48     protected $_locale;
49
50     /**
51      * export config
52      *
53      * @var Zend_Config_Xml
54      */
55     protected $_config = array();
56     
57     /**
58      * fields with special treatment in addBody
59      *
60      * @var array
61      */
62     protected $_specialFields = array();
63     
64     /**
65      * @var string application name of this export class
66      */
67     protected $_applicationName = 'Tinebase';
68     
69     /**
70      * the record model
71      *
72      * @var string
73      */
74     protected $_modelName = NULL;
75     
76     /**
77      * filter to generate export for
78      * 
79      * @var Tinebase_Model_Filter_FilterGroup
80      */
81     protected $_filter = NULL;
82     
83     /**
84      * sort records by this field (array keys: sort / dir / ...)
85      *
86      * @var array
87      * @see Tinebase_Model_Pagination
88      */
89     protected $_sortInfo = array();
90     
91     /**
92      * preference key if users can have different export configs
93      * 
94      * @var string
95      */
96     protected $_prefKey = NULL;
97     
98     /**
99      * format strings
100      * 
101      * @var string
102      */
103     protected $_format = NULL;
104     
105     /**
106      * other resolved records
107      *
108      * @var array of Tinebase_Record_RecordSet
109      */
110     protected $_resolvedRecords = array();
111     
112     /**
113      * user fields to resolve
114      * 
115      * @var array
116      */
117     protected $_userFields = array('created_by', 'last_modified_by', 'account_id');
118     
119     /**
120      * other fields to resolve
121      * 
122      * @var array
123      */
124     protected $_resolvedFields = array('created_by', 'last_modified_by', 'container_id', 'tags', 'notes', 'relation');
125     
126     /**
127      * get record relations
128      * 
129      * @var boolean
130      */
131     protected $_getRelations = FALSE;
132     
133     /**
134      * custom field names for this model
135      * 
136      * @var array
137      */
138     protected $_customFieldNames = NULL;
139
140     /**
141      * holds resolved records for matrices. this is an array holding each recordset on 
142      * a property with the same name as the field identifier.
143      * 
144      * @var array
145      */
146     protected $_matrixRecords = NULL;
147     
148     /**
149      * the constructor
150      *
151      * @param Tinebase_Model_Filter_FilterGroup $_filter
152      * @param Tinebase_Controller_Record_Interface $_controller (optional)
153      * @param array $_additionalOptions (optional) additional options
154      */
155     public function __construct(Tinebase_Model_Filter_FilterGroup $_filter, Tinebase_Controller_Record_Interface $_controller = NULL, $_additionalOptions = array())
156     {
157         $this->_modelName = $_filter->getModelName();
158         $this->_filter = $_filter;
159         $this->_controller = ($_controller !== NULL) ? $_controller : Tinebase_Core::getApplicationInstance($this->_applicationName, $this->_modelName);
160         $this->_translate = Tinebase_Translation::getTranslation($this->_applicationName);
161         $this->_config = $this->_getExportConfig($_additionalOptions);
162         $this->_locale = Tinebase_Core::get(Tinebase_Core::LOCALE);
163         if (isset($_additionalOptions['sortInfo'])) {
164             if (isset($_additionalOptions['sortInfo']['field'])) {
165                 $this->_sortInfo['sort'] = $_additionalOptions['sortInfo']['field'];
166                 $this->_sortInfo['dir'] = isset($_additionalOptions['sortInfo']['direction']) ? $_additionalOptions['sortInfo']['direction'] : 'ASC';
167             } else {
168                 $this->_sortInfo =  $_additionalOptions['sortInfo'];
169             }
170         }
171     }
172     
173     /**
174      * generate export
175      * 
176      * @return mixed filename/generated object/...
177      */
178     abstract public function generate();
179     
180     /**
181      * get custom field names for this app
182      * 
183      * @return array
184      */
185     protected function _getCustomFieldNames()
186     {
187         if ($this->_customFieldNames === NULL) {
188             $this->_customFieldNames = Tinebase_CustomField::getInstance()->getCustomFieldsForApplication($this->_applicationName, $this->_modelName)->name;
189         }
190         
191         return $this->_customFieldNames;
192     }
193     
194     /**
195      * get export format string (csv, ...)
196      * 
197      * @return string
198      */
199     public function getFormat()
200     {
201         if ($this->_format === NULL) {
202             throw new Tinebase_Exception_NotFound('Format string not found.');
203         }
204         
205         return $this->_format;
206     }
207     
208     /**
209      * get download content type
210      * 
211      * @return string
212      */
213     abstract public function getDownloadContentType();
214     
215     /**
216      * return download filename
217      * 
218      * @param string $_appName
219      * @param string $_format
220      */
221     public function getDownloadFilename($_appName, $_format)
222     {
223         return 'export_' . strtolower($_appName) . '.' . $_format;
224     }
225     
226     /**
227      * export records
228      */
229     protected function _exportRecords()
230     {
231         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
232             . ' Starting export of ' . $this->_modelName . ' with filter: ' . print_r($this->_filter->toArray(), true)
233             . ' and sort info: ' . print_r($this->_sortInfo, true));
234         
235         $iterator = new Tinebase_Record_Iterator(array(
236             'iteratable' => $this,
237             'controller' => $this->_controller,
238             'filter'     => $this->_filter,
239             'options'     => array(
240                 'searchAction' => 'export',
241                 'sortInfo'     => $this->_sortInfo,
242                 'getRelations' => $this->_getRelations,
243             ),
244         ));
245         
246         $result = $iterator->iterate();
247         
248         $this->_onAfterExportRecords($result);
249         
250         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ 
251             . ' Exported ' . $result['totalcount'] . ' records.');
252     }
253     
254     /**
255      * template method, gets called after _exportRecords
256      * 
257      * @param array $result
258      */
259     protected function _onAfterExportRecords($result)
260     {
261         
262     }
263     
264     /**
265      * resolve records and prepare for export (set user timezone, ...)
266      *
267      * @param Tinebase_Record_RecordSet $_records
268      */
269     protected function _resolveRecords(Tinebase_Record_RecordSet $_records)
270     {
271         // get field types/identifiers from config
272         $identifiers = array();
273         if ($this->_config->columns) {
274             $types = array();
275             foreach ($this->_config->columns->column as $column) {
276                 $types[] = $column->type;
277                 $identifiers[] = $column->identifier;
278             }
279             $types = array_unique($types);
280         } else {
281             $types = $this->_resolvedFields;
282         }
283
284         // resolve users
285         foreach ($this->_userFields as $field) {
286             if (in_array($field, $types) || in_array($field, $identifiers)) {
287                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Resolving users for ' . $field);
288                 Tinebase_User::getInstance()->resolveMultipleUsers($_records, $field, TRUE);
289             }
290         }
291
292         // add notes
293         if (in_array('notes', $types)) {
294             Tinebase_Notes::getInstance()->getMultipleNotesOfRecords($_records, 'notes', 'Sql', FALSE);
295         }
296         
297         // add container
298         if (in_array('container_id', $types)) {
299             Tinebase_Container::getInstance()->getGrantsOfRecords($_records, Tinebase_Core::getUser());
300         }
301         
302         $_records->setTimezone(Tinebase_Core::getUserTimezone());
303     }
304
305     /**
306      * return template filename if set
307      * 
308      * @return string|NULL
309      */
310     protected function _getTemplateFilename()
311     {
312         $templateFile = $this->_config->get('template', NULL);
313         if ($templateFile !== NULL) {
314             
315             // check if template file has absolute path
316             if (strpos($templateFile, '/') !== 0) {
317                 $templateFile = dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . $this->_applicationName . 
318                     DIRECTORY_SEPARATOR . 'Export' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . $templateFile;
319             }
320             if (file_exists($templateFile)) {
321                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Using template file "' . $templateFile . '" for ' . $this->_modelName . ' export.');
322             } else {
323                 throw new Tinebase_Exception_NotFound('Template file ' . $templateFile . ' not found');
324             }
325         }
326         
327         return $templateFile;
328     }
329     
330     /**
331      * get export config
332      *
333      * @param array $_additionalOptions additional options
334      * @return Zend_Config_Xml
335      * @throws Tinebase_Exception_NotFound
336      */
337     protected function _getExportConfig($_additionalOptions = array())
338     {
339         if ((isset($_additionalOptions['definitionFilename']) || array_key_exists('definitionFilename', $_additionalOptions))) {
340             // get definition from file
341             $definition = Tinebase_ImportExportDefinition::getInstance()->getFromFile(
342                 $_additionalOptions['definitionFilename'],
343                 Tinebase_Application::getInstance()->getApplicationByName($this->_applicationName)->getId() 
344             );
345             
346         } else if ((isset($_additionalOptions['definitionId']) || array_key_exists('definitionId', $_additionalOptions))) {
347             $definition = Tinebase_ImportExportDefinition::getInstance()->get($_additionalOptions['definitionId']);
348             
349         } else {
350             // get preference from db and set export definition name
351             $exportName = $this->_defaultExportname;
352             if ($this->_prefKey !== NULL) {
353                 $exportName = Tinebase_Core::getPreference($this->_applicationName)->getValue($this->_prefKey, $exportName);
354             }
355             
356             // get export definition by name / model
357             $filter = new Tinebase_Model_ImportExportDefinitionFilter(array(
358                 array('field' => 'model', 'operator' => 'equals', 'value' => $this->_modelName),
359                 array('field' => 'name',  'operator' => 'equals', 'value' => $exportName),
360             ));
361             $definitions = Tinebase_ImportExportDefinition::getInstance()->search($filter);
362             if (count($definitions) == 0) {
363                 throw new Tinebase_Exception_NotFound('Export definition for model ' . $this->_modelName . ' not found.');
364             }
365             $definition = $definitions->getFirstRecord();
366             
367             if (! empty($definition->filename)) {
368                 // check if file with plugin options exists and use that
369                 // TODO: this is confusing when imported an extra definition from a file having the same name as the default -> the default will be used
370                 $completeFilename = dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . $this->_applicationName . 
371                     DIRECTORY_SEPARATOR . 'Export' . DIRECTORY_SEPARATOR . 'definitions' . DIRECTORY_SEPARATOR . $definition->filename;
372                 try {
373                     $fileDefinition = Tinebase_ImportExportDefinition::getInstance()->getFromFile(
374                         $completeFilename,
375                         Tinebase_Application::getInstance()->getApplicationByName($this->_applicationName)->getId() 
376                     );
377                     Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Using definition from file ' . $definition->filename);
378                     $definition->plugin_options = $fileDefinition->plugin_options;
379                 } catch (Tinebase_Exception_NotFound $tenf) {
380                     Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' ' . $tenf->getMessage());
381                 }
382             }
383         }
384         
385         $config = Tinebase_ImportExportDefinition::getInstance()->getOptionsAsZendConfigXml($definition, $_additionalOptions);
386         
387         $this->_addMatrices($config);
388         
389         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) {
390             Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' export config: ' . print_r($config->toArray(), TRUE));
391         }
392         
393         return $config;
394     }
395     
396     /**
397      * 
398      * @param Zend_Config_Xml $config
399      * @param Zend_Config_Xml $fieldConfig
400      * @throws Tinebase_Exception_Data
401      */
402     protected function _addMatrixHeaders(Zend_Config_Xml $config, Zend_Config $fieldConfig)
403     {
404         switch ($fieldConfig->type) {
405             case 'tags':
406                 $filter = new Tinebase_Model_TagFilter(array('application' => $this->_applicationName));
407                 $tags = Tinebase_Tags::getInstance()->searchTags($filter);
408                 
409                 $count = $config->columns->column->count();
410                 
411                 foreach($tags as $tag) {
412                     $cfg = new Zend_Config(array($count => array('identifier' => $tag->name, 'type' => 'tags', 'isMatrixField' => TRUE)));
413                     $config->columns->column->merge($cfg);
414                     $count++;
415                 }
416                 
417                 $this->_matrixRecords['tags'] = $tags;
418                 
419                 break;
420             default:
421                 throw new Tinebase_Exception_Data('Other types than tags are not supported at the moment.');
422         }
423     }
424     
425     /**
426      * if there are matrix fields configured, add them as columns to config
427      * 
428      * @param Zend_Config_Xml $config
429      */
430     protected function _addMatrices(Zend_Config_Xml $config)
431     {
432         if (! isset($config->columns) || ! isset($config->columns->column)) {
433             return;
434         }
435         
436         for ($i = 0; $i < $config->columns->column->count(); $i++) {
437             $column = $config->columns->column->{$i};
438             if ($column->separateColumns) {
439                 $this->_addMatrixHeaders($config, $config->columns->column->{$i});
440                 unset($config->columns->column->{$i});
441             }
442         }
443     }
444
445     /**
446      * return tag names of record
447      * 
448      * @param Tinebase_Record_Abstract $_record
449      * @return string
450      */
451     protected function _getTags(Tinebase_Record_Abstract $_record)
452     {
453         $tags = Tinebase_Tags::getInstance()->getTagsOfRecord($_record);
454         return implode(', ', $tags->name);
455     }
456     
457     /**
458      * return translated Keyfield string
459      *
460      * @param String $_property
461      * @param String $_keyfield
462      * @param String $_application
463      * @return string
464      */
465     protected function _getResolvedKeyfield($_property, $_keyfield, $_application)
466     {
467          $i18nApplication = Tinebase_Translation::getTranslation($_application);
468          $config = Tinebase_Config::getAppConfig($_application);
469          $result = $config->get($_keyfield)->records->getById($_property);
470          return isset($result->value) ? $i18nApplication->translate($result->value) : $_property;
471     }
472     
473     /**
474      * 
475      * return container name (or other field)
476      * 
477      * @param Tinebase_Record_Abstract $_record
478      * @param string $_field
479      * @param string $_property
480      * @return string
481      */
482     protected function _getContainer(Tinebase_Record_Abstract $_record, $_field = 'id', $_property = 'container_id')
483     {
484         $container = $_record->{$_property};
485         return $container[$_field];
486     }
487     
488     /**
489      * add relation values from related records
490      * 
491      * @param Tinebase_Record_Abstract $record
492      * @param string $relationType
493      * @param string $recordField
494      * @param boolean $onlyFirstRelation
495      * @return string
496      */
497     protected function _addRelations(Tinebase_Record_Abstract $record, $relationType, $recordField = NULL, $onlyFirstRelation = FALSE)
498     {
499         $record->relations->addIndices(array('type'));
500         $matchingRelations = $record->relations->filter('type', $relationType);
501         
502         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' '
503             . 'Found ' . count($matchingRelations) . ' relations of type ' . $relationType . ' (' . $recordField . ')');
504         
505         $resultArray = array();
506         foreach ($matchingRelations as $relation) {
507             if ($recordField !== NULL) {
508                 $resultArray[] = $relation->related_record->{$recordField};
509             } else {
510                 $resultArray[] = $this->_getRelationSummary($relation->related_record);
511             }
512             
513             if ($onlyFirstRelation) {
514                 break;
515             }
516         }
517         
518         $result = implode(';', $resultArray);
519         
520         return $result;
521     }
522     
523     /**
524      * add relation summary (such as n_fileas, title, ...)
525      * 
526      * @param Tinebase_Record_Abstract $_record
527      * @param string $_type
528      * @return string
529      */
530     protected function _getRelationSummary(Tinebase_Record_Abstract $_record)
531     {
532         $result = '';
533         switch(get_class($_record)) {
534             case 'Addressbook_Model_Contact':
535                 $result = $_record->n_fileas;
536                 break;
537             case 'Tasks_Model_Task':
538                 $result = $_record->summary;
539                 break;
540         }
541         
542         return $result;
543     }
544     
545     /**
546      * add relation values from related records
547      * 
548      * @param Tinebase_Record_Abstract $_record
549      * @param string $_fieldName
550      * @param string $_recordField
551      * @return string
552      */
553     protected function _addNotes(Tinebase_Record_Abstract $_record)
554     {
555         //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_record->notes->toArray(), true));
556         
557         $resultArray = array();
558         foreach ($_record->notes as $note) {
559             $date = Tinebase_Translation::dateToStringInTzAndLocaleFormat($note->creation_time);
560             $resultArray[] = $date . ' - ' . $note->note;
561         }
562         
563         $result = implode(';', $resultArray);
564         return $result;
565     }
566
567     /**
568      * get special field value / overwrite this to add special values
569      *
570      * @param Tinebase_Record_Interface $_record
571      * @param array $_param
572      * @param string || null $_key [may be used by child methods e.g. {@see Timetracker_Export_Abstract::_getSpecialFieldValue)]
573      * @param string $_cellType
574      * @return string
575      */
576     protected function _getSpecialFieldValue(Tinebase_Record_Interface $_record, $_param, $_key = NULL, &$_cellType = NULL)
577     {
578         return '';
579     }
580
581     /**
582      * replace and match strings in value
583      * 
584      * @param string $_value
585      * @param Zend_Config $_fieldConfig
586      * @return string
587      */
588     protected function _replaceAndMatchvalue($_value, Zend_Config $_fieldConfig)
589     {
590         $value = $_value;
591         
592         // check for replacements
593         if (isset($_fieldConfig->replace) && isset($_fieldConfig->replace->patterns) && isset($_fieldConfig->replace->replacements)) {
594             //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_fieldConfig->replace->patterns->toArray(), true));
595             //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_fieldConfig->replace->replacements->toArray(), true));
596             $patterns =     (count($_fieldConfig->replace->patterns->pattern) > 1)          
597                 ? $_fieldConfig->replace->patterns->pattern->toArray()          
598                 : $_fieldConfig->replace->patterns->toArray();
599             $replacements = (count($_fieldConfig->replace->replacements->replacement) > 1)  
600                 ? $_fieldConfig->replace->replacements->replacement->toArray()  
601                 : $_fieldConfig->replace->replacements->toArray();
602             $value = preg_replace($patterns, $replacements, $value);
603         }
604
605         // check for matches
606         if (isset($_fieldConfig->match)) {
607             //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_fieldConfig->match, true));
608             preg_match($_fieldConfig->match, $value, $matches);
609             $value = (isset($matches[1])) ? $matches[1] : '';
610         }
611         
612         return $value;
613     }
614 }