Tinebase_Export - introduce definition templateFileId, add vfs access check
[tine20] / tine20 / Tinebase / ImportExportDefinition.php
1 <?php
2 /**
3  * Tine 2.0
4  *
5  * @package     Tinebase
6  * @subpackage  Controller
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) 2008-2017 Metaways Infosystems GmbH (http://www.metaways.de)
10  * 
11  */
12
13 /**
14  * backend for import/export definitions
15  *
16  * @package     Tinebase
17  * @subpackage  Controller
18  */
19 class Tinebase_ImportExportDefinition extends Tinebase_Controller_Record_Abstract
20 {
21     const SCOPE_SINGLE = 'single';
22     const SCOPE_MULTI = 'multi';
23
24     /**
25      * holds the instance of the singleton
26      *
27      * @var Tinebase_ImportExportDefinition
28      */
29     private static $_instance = NULL;
30         
31     /**
32      * the constructor
33      *
34      * don't use the constructor. use the singleton 
35      */
36     private function __construct() {
37         $this->_modelName = 'Tinebase_Model_ImportExportDefinition';
38         $this->_applicationName = 'Tinebase';
39         $this->_purgeRecords = FALSE;
40         $this->_doContainerACLChecks = FALSE;
41
42         // set backend with activated modlog
43         $this->_backend = new Tinebase_Backend_Sql(array(
44             'modelName'     => $this->_modelName,
45             'tableName'     => 'importexport_definition',
46             'modlogActive'  => TRUE,
47         ));
48     }
49     
50     /**
51      * the singleton pattern
52      *
53      * @return Tinebase_ImportExportDefinition
54      */
55     public static function getInstance()
56     {
57         if (self::$_instance === NULL) {
58             self::$_instance = new Tinebase_ImportExportDefinition();
59         }
60         return self::$_instance;
61     }
62     
63     /**
64      * get definition by name
65      * 
66      * @param string $_name
67      * @return Tinebase_Model_ImportExportDefinition
68      * 
69      * @todo replace this with search function
70      */
71     public function getByName($_name)
72     {
73         /** @noinspection PhpIncompatibleReturnTypeInspection */
74         return $this->_backend->getByProperty($_name);
75     }
76     
77     /**
78      * get application export definitions
79      *
80      * @param Tinebase_Model_Application $_application
81      * @return Tinebase_Record_RecordSet of Tinebase_Model_ImportExportDefinition
82      */
83     public function getExportDefinitionsForApplication(Tinebase_Model_Application $_application)
84     {
85         $filter = new Tinebase_Model_ImportExportDefinitionFilter(array(
86             array('field' => 'application_id',  'operator' => 'equals',  'value' => $_application->getId()),
87             array('field' => 'type',            'operator' => 'equals',  'value' => 'export'),
88         ));
89         $result = $this->search($filter);
90
91         $fileSystem = Tinebase_FileSystem::getInstance();
92         $toRemove = new Tinebase_Record_RecordSet('Tinebase_Model_ImportExportDefinition');
93         /** @var Tinebase_Model_ImportExportDefinition $definition */
94         foreach($result as $definition) {
95             if ($definition->plugin_options) {
96                 $config = Tinebase_ImportExportDefinition::getInstance()->
97                     getOptionsAsZendConfigXml($definition, array());
98                 if (!empty($config->template)) {
99                     if (strpos($config->template, 'tine20://') === false) {
100                         continue;
101                     }
102                     try {
103                         $node = $fileSystem->stat(substr($config->template, 9));
104                         if (false === $fileSystem->hasGrant(Tinebase_Core::getUser()->getId(), $node->getId(),
105                                 Tinebase_Model_Grants::GRANT_READ)) {
106                             $toRemove[] = $definition;
107                         }
108                     } catch (Exception $e) {
109                         $toRemove[] = $definition;
110                     }
111                 } elseif (!empty($config->templateFileId)) {
112                     if (false === $fileSystem->hasGrant(Tinebase_Core::getUser()->getId(), $config->templateFileId,
113                             Tinebase_Model_Grants::GRANT_READ)) {
114                         $toRemove[] = $definition;
115                     }
116                 }
117             }
118         }
119
120         $result->removeRecords($toRemove);
121         
122         return $result;
123     }
124     
125     /**
126      * get definition from file
127      *
128      * @param string $_filename
129      * @param string $_applicationId
130      * @param string $_name [optional]
131      * @return Tinebase_Model_ImportExportDefinition
132      * @throws Tinebase_Exception_NotFound
133      */
134     public function getFromFile($_filename, $_applicationId, $_name = NULL)
135     {
136         if (file_exists($_filename)) {
137             $basename = basename($_filename);
138             $content = file_get_contents($_filename);
139             $config = new Zend_Config_Xml($_filename);
140             
141             if ($_name === NULL) {
142                 $name = ($config->name) ? $config->name : preg_replace("/\.xml/", '', $basename);
143             } else {
144                 $name = $_name;
145             }
146
147             $format = null;
148             if (class_exists($config->plugin)) {
149                 try {
150                     $plugin = $config->plugin;
151                     if (is_subclass_of($plugin, 'Tinebase_Export_Abstract')) {
152                         $format = $plugin::getDefaultFormat();
153                     }
154                 } catch(Exception $e) {}
155             }
156             
157             $definition = new Tinebase_Model_ImportExportDefinition(array(
158                 'application_id'              => $_applicationId,
159                 'name'                        => $name,
160                 'label'                       => empty($config->label) ? $name : $config->label,
161                 'description'                 => $config->description,
162                 'type'                        => $config->type,
163                 'model'                       => $config->model,
164                 'plugin'                      => $config->plugin,
165                 'icon_class'                  => $config->icon_class,
166                 'scope'                       => (empty($config->scope) ||
167                         !in_array($config->scope, array(self::SCOPE_SINGLE, self::SCOPE_MULTI))) ? '' : $config->scope,
168                 'plugin_options'              => $content,
169                 'filename'                    => $basename,
170                 'favorite'                    => false == $config->favorite ? 0 : 1,
171                 'format'                      => $format,
172                 'order'                       => intval($config->order),
173                 'mapUndefinedFieldsEnable'    => $config->mapUndefinedFieldsEnable,
174                 'mapUndefinedFieldsTo'        => $config->mapUndefinedFieldsTo,
175                 'postMappingHook'             => $config->postMappingHook
176             ));
177             
178             return $definition;
179         } else {
180             throw new Tinebase_Exception_NotFound('Definition file "' . $_filename . '" not found.');
181         }
182     }
183     
184     /**
185      * get config options as Zend_Config_Xml object
186      * 
187      * @param Tinebase_Model_ImportExportDefinition $_definition
188      * @param array $_additionalOptions additional options
189      * @return Zend_Config_Xml
190      */
191     public static function getOptionsAsZendConfigXml(Tinebase_Model_ImportExportDefinition $_definition, $_additionalOptions = array())
192     {
193         $cacheId = 'ZendConfigXml_' . md5($_definition);
194         $cache = Tinebase_Core::getCache();
195         if (! $cache->test($cacheId)) {
196             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
197                 . ' Generate new Zend_Config_Xml object' . $cacheId);
198
199             $xmlConfig = (empty($_definition->plugin_options))
200                 ? '<?xml version="1.0" encoding="UTF-8"?><config></config>'
201                 : $_definition->plugin_options;
202             $config = new Zend_Config_Xml($xmlConfig, /* section = */ null, /* runtime mods allowed = */ true);
203             $cache->save($config, $cacheId);
204         } else {
205             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
206                 . ' Get Zend_Config_Xml from cache' . $cacheId);
207             $config = $cache->load($cacheId);
208         }
209         
210         if (! empty($_additionalOptions)) {
211             $config->merge(new Zend_Config($_additionalOptions));
212         }
213         
214         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
215             . ' Config: ' . print_r($config->toArray(), true));
216         
217         return $config;
218     }
219     
220     /**
221      * update existing definition or create new from file
222      * - use backend functions (create/update) directly because we do not want any default controller handling here
223      * - calling function needs to make sure that user has admin right!
224      * 
225      * @param string $_filename
226      * @param Tinebase_Model_Application $_application
227      * @param string $_name
228      * @return Tinebase_Model_ImportExportDefinition
229      */
230     public function updateOrCreateFromFilename($_filename, $_application, $_name = NULL)
231     {
232         $definition = $this->getFromFile(
233             $_filename,
234             $_application->getId(),
235             $_name
236         );
237         
238         // try to get definition and update if it exists
239         try {
240             $existing = $this->getByName($definition->name);
241             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Updating definition: ' . $definition->name);
242             $copyFields = array('filename', 'plugin_options', 'description', 'label');
243             foreach ($copyFields as $field) {
244                 $existing->{$field} = $definition->{$field};
245             }
246             $result = $this->_backend->update($existing);
247             
248         } catch (Tinebase_Exception_NotFound $tenf) {
249             // does not exist
250             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Creating import/export definion from file: ' . $_filename);
251             $result = $this->_backend->create($definition);
252         }
253         
254         return $result;
255     }
256
257     /**
258      * repair definitions tables
259      * 
260      * - fixes application_ids
261      * 
262      * @todo should be moved to generic (?) backend
263      */
264     public function repairTable()
265     {
266         $definitions = $this->_backend->getAll();
267         
268         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
269             . ' Got ' . count($definitions) . ' definitions. Checking definitions table ...');
270         
271         foreach ($definitions as $definition) {
272             $appName = substr($definition->model, 0, strpos($definition->model, '_Model'));
273             echo $appName;
274             $application = Tinebase_Application::getInstance()->getApplicationByName($appName);
275             if ($application->getId() !== $definition->application_id) {
276                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
277                     . ' Fixing application_id for definition ' . $definition->name);
278                 $definition->application_id = $application->getId();
279                 $this->update($definition);
280             }
281         }
282     }
283
284     /**
285      * get generic import definition
286      *
287      * @param string $model
288      * @return Tinebase_Model_ImportExportDefinition
289      */
290     public function getGenericImport($model)
291     {
292         $extract = Tinebase_Application::extractAppAndModel($model);
293         $appName = $extract['appName'];
294
295         $xmlOptions = '<?xml version="1.0" encoding="UTF-8"?>
296         <config>
297             <headline>1</headline>
298             <dryrun>0</dryrun>
299             <delimiter>,</delimiter>
300             <extension>csv</extension>
301         </config>';
302         $definition = new Tinebase_Model_ImportExportDefinition(array(
303             'application_id'              => Tinebase_Application::getInstance()->getApplicationByName($appName)->getId(),
304             'name'                        => $model . '_tine_generic_import_csv',
305             // TODO translate
306             'label'                       => 'Tine 2.0 ' . $model . ' import',
307             'type'                        => 'import',
308             'model'                       => $model,
309             'plugin'                      => 'Tinebase_Import_Csv_Generic',
310             'headline'                    => 1,
311             'delimiter'                   => ',',
312             'plugin_options'              => $xmlOptions,
313 //            'description'                 => $config->description,
314 //            'filename'                    => $basename,
315         ));
316
317         return $definition;
318     }
319 }