0011282: Translate salutation in xml export
[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         // init member vars
158         $this->_modelName = $_filter->getModelName();
159         $this->_filter = $_filter;
160         $this->_controller = ($_controller !== NULL) ? $_controller : Tinebase_Core::getApplicationInstance($this->_applicationName, $this->_modelName);
161         $this->_translate = Tinebase_Translation::getTranslation($this->_applicationName);
162         $this->_config = $this->_getExportConfig($_additionalOptions);
163         $this->_locale = Tinebase_Core::get(Tinebase_Core::LOCALE);
164     }
165     
166     /**
167      * generate export
168      * 
169      * @return mixed filename/generated object/...
170      */
171     abstract public function generate();
172     
173     /**
174      * get custom field names for this app
175      * 
176      * @return array
177      */
178     protected function _getCustomFieldNames()
179     {
180         if ($this->_customFieldNames === NULL) {
181             $this->_customFieldNames = Tinebase_CustomField::getInstance()->getCustomFieldsForApplication($this->_applicationName, $this->_modelName)->name;
182         }
183         
184         return $this->_customFieldNames;
185     }
186     
187     /**
188      * get export format string (csv, ...)
189      * 
190      * @return string
191      */
192     public function getFormat()
193     {
194         if ($this->_format === NULL) {
195             throw new Tinebase_Exception_NotFound('Format string not found.');
196         }
197         
198         return $this->_format;
199     }
200     
201     /**
202      * get download content type
203      * 
204      * @return string
205      */
206     abstract public function getDownloadContentType();
207     
208     /**
209      * return download filename
210      * 
211      * @param string $_appName
212      * @param string $_format
213      */
214     public function getDownloadFilename($_appName, $_format)
215     {
216         return 'export_' . strtolower($_appName) . '.' . $_format;
217     }
218     
219     /**
220      * export records
221      */
222     protected function _exportRecords()
223     {
224         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
225             . ' Starting export of ' . $this->_modelName . ' with filter: ' . print_r($this->_filter->toArray(), true));
226         
227         $iterator = new Tinebase_Record_Iterator(array(
228             'iteratable' => $this,
229             'controller' => $this->_controller,
230             'filter'     => $this->_filter,
231             'options'     => array(
232                 'searchAction' => 'export',
233                 'sortInfo'       => $this->_sortInfo,
234                 'getRelations' => $this->_getRelations,
235             ),
236         ));
237         
238         $result = $iterator->iterate();
239         
240         $this->_onAfterExportRecords($result);
241         
242         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ 
243             . ' Exported ' . $result['totalcount'] . ' records.');
244     }
245     
246     /**
247      * template method, gets called after _exportRecords
248      * 
249      * @param array $result
250      */
251     protected function _onAfterExportRecords($result)
252     {
253         
254     }
255     
256     /**
257      * resolve records and prepare for export (set user timezone, ...)
258      *
259      * @param Tinebase_Record_RecordSet $_records
260      */
261     protected function _resolveRecords(Tinebase_Record_RecordSet $_records)
262     {
263         // get field types/identifiers from config
264         $identifiers = array();
265         if ($this->_config->columns) {
266             $types = array();
267             foreach ($this->_config->columns->column as $column) {
268                 $types[] = $column->type;
269                 $identifiers[] = $column->identifier;
270             }
271             $types = array_unique($types);
272         } else {
273             $types = $this->_resolvedFields;
274         }
275
276         // resolve users
277         foreach ($this->_userFields as $field) {
278             if (in_array($field, $types) || in_array($field, $identifiers)) {
279                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Resolving users for ' . $field);
280                 Tinebase_User::getInstance()->resolveMultipleUsers($_records, $field, TRUE);
281             }
282         }
283
284         // add notes
285         if (in_array('notes', $types)) {
286             Tinebase_Notes::getInstance()->getMultipleNotesOfRecords($_records, 'notes', 'Sql', FALSE);
287         }
288         
289         // add container
290         if (in_array('container_id', $types)) {
291             Tinebase_Container::getInstance()->getGrantsOfRecords($_records, Tinebase_Core::getUser());
292         }
293         
294         $_records->setTimezone(Tinebase_Core::getUserTimezone());
295     }
296
297     /**
298      * return template filename if set
299      * 
300      * @return string|NULL
301      */
302     protected function _getTemplateFilename()
303     {
304         $templateFile = $this->_config->get('template', NULL);
305         if ($templateFile !== NULL) {
306             
307             // check if template file has absolute path
308             if (strpos($templateFile, '/') !== 0) {
309                 $templateFile = dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . $this->_applicationName . 
310                     DIRECTORY_SEPARATOR . 'Export' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . $templateFile;
311             }
312             if (file_exists($templateFile)) {
313                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Using template file "' . $templateFile . '" for ' . $this->_modelName . ' export.');
314             } else {
315                 throw new Tinebase_Exception_NotFound('Template file ' . $templateFile . ' not found');
316             }
317         }
318         
319         return $templateFile;
320     }
321     
322     /**
323      * get export config
324      *
325      * @param array $_additionalOptions additional options
326      * @return Zend_Config_Xml
327      * @throws Tinebase_Exception_NotFound
328      */
329     protected function _getExportConfig($_additionalOptions = array())
330     {
331         if ((isset($_additionalOptions['definitionFilename']) || array_key_exists('definitionFilename', $_additionalOptions))) {
332             // get definition from file
333             $definition = Tinebase_ImportExportDefinition::getInstance()->getFromFile(
334                 $_additionalOptions['definitionFilename'],
335                 Tinebase_Application::getInstance()->getApplicationByName($this->_applicationName)->getId() 
336             );
337             
338         } else if ((isset($_additionalOptions['definitionId']) || array_key_exists('definitionId', $_additionalOptions))) {
339             $definition = Tinebase_ImportExportDefinition::getInstance()->get($_additionalOptions['definitionId']);
340             
341         } else {
342             // get preference from db and set export definition name
343             $exportName = $this->_defaultExportname;
344             if ($this->_prefKey !== NULL) {
345                 $exportName = Tinebase_Core::getPreference($this->_applicationName)->getValue($this->_prefKey, $exportName);
346             }
347             
348             // get export definition by name / model
349             $filter = new Tinebase_Model_ImportExportDefinitionFilter(array(
350                 array('field' => 'model', 'operator' => 'equals', 'value' => $this->_modelName),
351                 array('field' => 'name',  'operator' => 'equals', 'value' => $exportName),
352             ));
353             $definitions = Tinebase_ImportExportDefinition::getInstance()->search($filter);
354             if (count($definitions) == 0) {
355                 throw new Tinebase_Exception_NotFound('Export definition for model ' . $this->_modelName . ' not found.');
356             }
357             $definition = $definitions->getFirstRecord();
358             
359             if (! empty($definition->filename)) {
360                 // check if file with plugin options exists and use that
361                 // TODO: this is confusing when imported an extra definition from a file having the same name as the default -> the default will be used
362                 $completeFilename = dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . $this->_applicationName . 
363                     DIRECTORY_SEPARATOR . 'Export' . DIRECTORY_SEPARATOR . 'definitions' . DIRECTORY_SEPARATOR . $definition->filename;
364                 try {
365                     $fileDefinition = Tinebase_ImportExportDefinition::getInstance()->getFromFile(
366                         $completeFilename,
367                         Tinebase_Application::getInstance()->getApplicationByName($this->_applicationName)->getId() 
368                     );
369                     Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Using definition from file ' . $definition->filename);
370                     $definition->plugin_options = $fileDefinition->plugin_options;
371                 } catch (Tinebase_Exception_NotFound $tenf) {
372                     Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' ' . $tenf->getMessage());
373                 }
374             }
375         }
376         
377         $config = Tinebase_ImportExportDefinition::getInstance()->getOptionsAsZendConfigXml($definition, $_additionalOptions);
378         
379         $this->_addMatrices($config);
380         
381         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) {
382             Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' export config: ' . print_r($config->toArray(), TRUE));
383         }
384         
385         return $config;
386     }
387     
388     /**
389      * 
390      * @param Zend_Config_Xml $config
391      * @param Zend_Config_Xml $fieldConfig
392      * @throws Tinebase_Exception_Data
393      */
394     protected function _addMatrixHeaders(Zend_Config_Xml $config, Zend_Config $fieldConfig)
395     {
396         switch ($fieldConfig->type) {
397             case 'tags':
398                 $filter = new Tinebase_Model_TagFilter(array('application' => $this->_applicationName));
399                 $tags = Tinebase_Tags::getInstance()->searchTags($filter);
400                 
401                 $count = $config->columns->column->count();
402                 
403                 foreach($tags as $tag) {
404                     $cfg = new Zend_Config(array($count => array('identifier' => $tag->name, 'type' => 'tags', 'isMatrixField' => TRUE)));
405                     $config->columns->column->merge($cfg);
406                     $count++;
407                 }
408                 
409                 $this->_matrixRecords['tags'] = $tags;
410                 
411                 break;
412             default:
413                 throw new Tinebase_Exception_Data('Other types than tags are not supported at the moment.');
414         }
415     }
416     
417     /**
418      * if there are matrix fields configured, add them as columns to config
419      * 
420      * @param Zend_Config_Xml $config
421      */
422     protected function _addMatrices(Zend_Config_Xml $config)
423     {
424         if (! isset($config->columns) || ! isset($config->columns->column)) {
425             return;
426         }
427         
428         for ($i = 0; $i < $config->columns->column->count(); $i++) {
429             $column = $config->columns->column->{$i};
430             if ($column->separateColumns) {
431                 $this->_addMatrixHeaders($config, $config->columns->column->{$i});
432                 unset($config->columns->column->{$i});
433             }
434         }
435     }
436
437     /**
438      * return tag names of record
439      * 
440      * @param Tinebase_Record_Abstract $_record
441      * @return string
442      */
443     protected function _getTags(Tinebase_Record_Abstract $_record)
444     {
445         $tags = Tinebase_Tags::getInstance()->getTagsOfRecord($_record);
446         return implode(', ', $tags->name);
447     }
448     
449     /**
450      * return translated Keyfield string
451      *
452      * @param String $_property
453      * @param String $_keyfield
454      * @param String $_application
455      * @return string
456      */
457     protected function _getResolvedKeyfield($_property, $_keyfield, $_application)
458     {
459          $i18nApplication = Tinebase_Translation::getTranslation($_application);
460          $config = Tinebase_Config::getAppConfig($_application);
461          $result = $config::getInstance()->get($_keyfield)->records->getById($_property);
462          return isset($result->value) ? $i18nApplication->translate($result->value) : $_property;
463     }
464     
465     /**
466      * 
467      * return container name (or other field)
468      * 
469      * @param Tinebase_Record_Abstract $_record
470      * @param string $_field
471      * @param string $_property
472      * @return string
473      */
474     protected function _getContainer(Tinebase_Record_Abstract $_record, $_field = 'id', $_property = 'container_id')
475     {
476         $container = $_record->{$_property};
477         return $container[$_field];
478     }
479     
480     /**
481      * add relation values from related records
482      * 
483      * @param Tinebase_Record_Abstract $record
484      * @param string $relationType
485      * @param string $recordField
486      * @param boolean $onlyFirstRelation
487      * @return string
488      */
489     protected function _addRelations(Tinebase_Record_Abstract $record, $relationType, $recordField = NULL, $onlyFirstRelation = FALSE)
490     {
491         $record->relations->addIndices(array('type'));
492         $matchingRelations = $record->relations->filter('type', $relationType);
493         
494         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' '
495             . 'Found ' . count($matchingRelations) . ' relations of type ' . $relationType . ' (' . $recordField . ')');
496         
497         $resultArray = array();
498         foreach ($matchingRelations as $relation) {
499             if ($recordField !== NULL) {
500                 $resultArray[] = $relation->related_record->{$recordField};
501             } else {
502                 $resultArray[] = $this->_getRelationSummary($relation->related_record);
503             }
504             
505             if ($onlyFirstRelation) {
506                 break;
507             }
508         }
509         
510         $result = implode(';', $resultArray);
511         
512         return $result;
513     }
514     
515     /**
516      * add relation summary (such as n_fileas, title, ...)
517      * 
518      * @param Tinebase_Record_Abstract $_record
519      * @param string $_type
520      * @return string
521      */
522     protected function _getRelationSummary(Tinebase_Record_Abstract $_record)
523     {
524         $result = '';
525         switch(get_class($_record)) {
526             case 'Addressbook_Model_Contact':
527                 $result = $_record->n_fileas;
528                 break;
529             case 'Tasks_Model_Task':
530                 $result = $_record->summary;
531                 break;
532         }
533         
534         return $result;
535     }
536     
537     /**
538      * add relation values from related records
539      * 
540      * @param Tinebase_Record_Abstract $_record
541      * @param string $_fieldName
542      * @param string $_recordField
543      * @return string
544      */
545     protected function _addNotes(Tinebase_Record_Abstract $_record)
546     {
547         //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_record->notes->toArray(), true));
548         
549         $resultArray = array();
550         foreach ($_record->notes as $note) {
551             $date = Tinebase_Translation::dateToStringInTzAndLocaleFormat($note->creation_time);
552             $resultArray[] = $date . ' - ' . $note->note;
553         }
554         
555         $result = implode(';', $resultArray);
556         return $result;
557     }
558
559     /**
560      * get special field value / overwrite this to add special values
561      *
562      * @param Tinebase_Record_Interface $_record
563      * @param array $_param
564      * @param string || null $_key [may be used by child methods e.g. {@see Timetracker_Export_Abstract::_getSpecialFieldValue)]
565      * @param string $_cellType
566      * @return string
567      */
568     protected function _getSpecialFieldValue(Tinebase_Record_Interface $_record, $_param, $_key = NULL, &$_cellType = NULL)
569     {
570         return '';
571     }
572
573     /**
574      * replace and match strings in value
575      * 
576      * @param string $_value
577      * @param Zend_Config $_fieldConfig
578      * @return string
579      */
580     protected function _replaceAndMatchvalue($_value, Zend_Config $_fieldConfig)
581     {
582         $value = $_value;
583         
584         // check for replacements
585         if (isset($_fieldConfig->replace) && isset($_fieldConfig->replace->patterns) && isset($_fieldConfig->replace->replacements)) {
586             //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_fieldConfig->replace->patterns->toArray(), true));
587             //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_fieldConfig->replace->replacements->toArray(), true));
588             $patterns =     (count($_fieldConfig->replace->patterns->pattern) > 1)          
589                 ? $_fieldConfig->replace->patterns->pattern->toArray()          
590                 : $_fieldConfig->replace->patterns->toArray();
591             $replacements = (count($_fieldConfig->replace->replacements->replacement) > 1)  
592                 ? $_fieldConfig->replace->replacements->replacement->toArray()  
593                 : $_fieldConfig->replace->replacements->toArray();
594             $value = preg_replace($patterns, $replacements, $value);
595         }
596
597         // check for matches
598         if (isset($_fieldConfig->match)) {
599             //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_fieldConfig->match, true));
600             preg_match($_fieldConfig->match, $value, $matches);
601             $value = (isset($matches[1])) ? $matches[1] : '';
602         }
603         
604         return $value;
605     }
606 }