Merge branch '2015.11' into 2015.11-develop
[tine20] / tine20 / Tinebase / Controller / ScheduledImport.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      Michael Spahn <m.spahn@metaways.de>
9  * @copyright   Copyright (c) 2014 Metaways Infosystems GmbH (http://www.metaways.de)
10  */
11
12 /**
13  * Tinebase Import Controller
14  * 
15  * @package Tinebase
16  * @subpackage  Controller
17  */
18 class Tinebase_Controller_ScheduledImport extends Tinebase_Controller_Record_Abstract
19 {
20     /**
21      * holds the instance of the singleton
22      *
23      * @var Tinebase_Controller_ScheduledImport
24      */
25     private static $instance = null;
26
27     const MAXFAILCOUNT = 5;
28     
29     /**
30      * the constructor
31      *
32      * don't use the constructor. use the singleton 
33      */
34     private function __construct()
35     {
36         $this->_applicationName = 'Tinebase';
37         $this->_modelName = 'Tinebase_Model_Import';
38         $this->_backend = new Tinebase_Backend_Sql(array(
39             'modelName' => $this->_modelName, 
40             'tableName' => 'import',
41             'modlogActive' => true,
42         ));
43         $this->_purgeRecords = false;
44         // activate this if you want to use containers
45         $this->_doContainerACLChecks = false;
46         $this->_resolveCustomFields = false;
47     }
48     
49     /**
50      * the singleton pattern
51      *
52      * @return Tinebase_Controller_ScheduledImport
53      */
54     public static function getInstance() 
55     {
56         if (self::$instance === null) {
57             self::$instance = new self();
58         }
59         return self::$instance;
60     }
61     
62     /**
63      * Search and executed the next scheduled import
64      * 
65      * @return null|array
66      */
67     public function runNextScheduledImport()
68     {
69         if ($record = $this->_getNextScheduledImport()) {
70             return $this->_doScheduledImport($record)->toArray();
71         }
72         
73         return null;
74     }
75
76     /**
77      * @return Tinebase_Model_ImportFilter
78      */
79     public function getScheduledImportFilter()
80     {
81         $timestampBefore = null;
82
83         $now = new Tinebase_DateTime();
84
85         $anHourAgo = clone $now;
86         $anHourAgo->subHour(1);
87
88         $aDayAgo = clone $now;
89         $aDayAgo->subDay(1);
90
91         $aWeekAgo = clone $now;
92         $aWeekAgo->subWeek(1);
93
94         $filter = new Tinebase_Model_ImportFilter(array(array(
95                 array('field' => 'failcount', 'operator' => 'greater', 'value' => 5),
96             ),
97             array(
98             'condition' => 'OR', 'filters' => array(
99                 array('field' => 'timestamp', 'operator' => 'isnull', 'value' => null),
100                 array('condition' => 'AND', 'filters' => array(
101                     array('field' => 'interval', 'operator' => 'equals', 'value' => Tinebase_Model_Import::INTERVAL_HOURLY),
102                     array('field' => 'timestamp', 'operator' => 'before', 'value' => $anHourAgo),
103                 )),
104                 array('condition' => 'AND', 'filters' => array(
105                     array('field' => 'interval', 'operator' => 'equals', 'value' => Tinebase_Model_Import::INTERVAL_DAILY),
106                     array('field' => 'timestamp', 'operator' => 'before', 'value' => $aDayAgo),
107                 )),
108                 array('condition' => 'AND', 'filters' => array(
109                     array('field' => 'interval', 'operator' => 'equals', 'value' => Tinebase_Model_Import::INTERVAL_WEEKLY),
110                     array('field' => 'timestamp', 'operator' => 'before', 'value' => $aWeekAgo),
111                 )),
112             )
113         )));
114
115         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) {
116             Tinebase_Core::getLogger()->trace(__METHOD__ . ' ' . __LINE__ . ' Filter used: ' . print_r($filter->toArray(), true));
117         }
118
119         return $filter;
120     }
121
122     /**
123      * Get the next scheduled import
124      * 
125      * @param interval
126      * @param recursive
127      * @return Tinebase_Model_Import|null
128      */
129     protected function _getNextScheduledImport()
130     {
131         $filter = $this->getScheduledImportFilter();
132
133         // Always sort by timestamp to ensure first in first out
134         $pagination = new Tinebase_Model_Pagination(array(
135             'limit'     => 50,
136             'sort'      => 'timestamp',
137             'dir'       => 'ASC'
138         ));
139
140         $records = $this->search($filter, $pagination);
141
142         foreach ($records as $record) {
143             // TODO add failcount to filter in getScheduledImportFilter as
144             //   no more valid imports are run if we have 50+ failing imports
145             if ($record->failcount < self::MAXFAILCOUNT) {
146                 return $record;
147             } else {
148                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
149                     Tinebase_Core::getLogger()->info(__METHOD__ . ' ' . __LINE__ . ' Too many failures, skipping import');
150                 }
151             }
152         }
153         
154         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
155             Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__ . ' No valid ScheduledImport could be found.');
156         }
157
158         return null;
159     }
160     
161     /**
162      * Downloads file to memory
163      * 
164      * @param string $source
165      * @return string|null
166      */
167     protected function _getFileToImport($source)
168     {
169         if (strpos($source, 'http') === 0) {
170             try {
171                 $client = new Zend_Http_Client($source);
172                 $requestBody = $client->request()->getBody();
173             } catch (Exception $e) {
174                 Tinebase_Exception::log($e);
175                 $requestBody = null;
176             }
177             return $requestBody;
178         } else {
179             return file_get_contents($source);
180         }
181     }
182     
183     /**
184      * Execute scheduled import
185      * @param Tinebase_Model_Import $record
186      * @return Tinebase_Model_Import
187      */
188     protected function _doScheduledImport(Tinebase_Model_Import $record)
189     {
190         $currentUser = Tinebase_Core::getUser();
191         // set user who created the import job
192         $importUser = Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('accountId', $record->user_id, 'Tinebase_Model_FullUser');
193         Tinebase_Core::set(Tinebase_Core::USER, $importUser);
194         
195         $importer = $record->getOption('plugin');
196         
197         $options = array(
198             'container_id' => $record->container_id,
199             // legacy
200             'importContainerId' => $record->container_id,
201         );
202         
203         if ($record->getOption('importFileByScheduler') === true) {
204             $toImport = $this->_getFileToImport($record->source);
205         } else {
206             $toImport = array(
207                 'remoteUrl'    => $record->source,
208                 'container_id' => $record->container_id,
209                 'options'      => $record->options
210             );
211         }
212         
213         if ($toImport) {
214             try {
215                 $importer = new $importer($options);
216                 $importer->import($toImport);
217                 $record->failcount = 0;
218             } catch (Exception $e) {
219                 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) {
220                     Tinebase_Core::getLogger()->notice(__METHOD__ . ' ' . __LINE__
221                         . ' Import failed.');
222                 }
223                 Tinebase_Exception::log($e);
224
225                 $record->lastfail = $e->getMessage();
226                 $record->failcount = $record->failcount + 1;
227             }
228
229             if ($record->interval === Tinebase_Model_Import::INTERVAL_ONCE || !$record->timestamp instanceof Tinebase_DateTime) {
230                 $record->timestamp = Tinebase_DateTime::now();
231             }
232
233             switch ($record->interval) {
234                 case Tinebase_Model_Import::INTERVAL_DAILY:
235                     $record->timestamp->addDay(1);
236                     break;
237                 case Tinebase_Model_Import::INTERVAL_WEEKLY:
238                     $record->timestamp->addWeek(1);
239                     break;
240                 case Tinebase_Model_Import::INTERVAL_HOURLY:
241                     $record->timestamp->addHour(1);
242                     break;
243             }
244
245             $record = $this->update($record);
246             
247         } else {
248             if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) {
249                 Tinebase_Core::getLogger()->notice(__METHOD__ . ' ' . __LINE__ . ' The source could not be loaded: "' . $record->source . '"');
250             }
251         }
252         
253         Tinebase_Core::set(Tinebase_Core::USER, $currentUser);
254         
255         return $record;
256     }
257     
258     /**
259      * Creates a remote import for events
260      * 
261      * @param string $remoteUrl
262      * @param string $interval
263      * @param array $importOptions
264      * @throws Calendar_Exception_InvalidUrl
265      * @return Tinebase_Record_Interface
266      */
267     public function createRemoteImportEvent($data)
268     {
269         $possibleIntervals = array(
270             Tinebase_Model_Import::INTERVAL_DAILY,
271             Tinebase_Model_Import::INTERVAL_HOURLY,
272             Tinebase_Model_Import::INTERVAL_ONCE,
273             Tinebase_Model_Import::INTERVAL_WEEKLY
274         );
275         
276         if (! in_array($data['interval'], $possibleIntervals)) {
277             $data['interval'] = Tinebase_Model_Import::INTERVAL_ONCE;
278         }
279         
280         // find container or create a new one
281         $containerId = $data['options']['container_id'];
282         
283         try {
284             $container = Tinebase_Container::getInstance()->getContainerById($containerId);
285         } catch (Tinebase_Exception_InvalidArgument $e) {
286             $container = new Tinebase_Model_Container(array(
287                 'name'              => $data['options']['container_id'],
288                 'type'              => Tinebase_Model_Container::TYPE_PERSONAL,
289                 'backend'           => Tinebase_User::SQL,
290                 'color'             => '#ffffff',
291                 'application_id'    => $data['application_id'],
292                 'owner_id'          => $data['user_id'],
293                 'model'             => $data['model'],
294             ));
295
296             $container = Tinebase_Container::getInstance()->addContainer($container);
297         }
298         
299         $data['options'] = json_encode(array_replace(array(
300             'forceUpdateExisting' => TRUE,
301             'import_defintion' => NULL,
302         ), $data['options']));
303         
304         $record = new Tinebase_Model_Import(array_replace(array(
305             'id'                => Tinebase_Record_Abstract::generateUID(),
306             'user_id'           => Tinebase_Core::getUser()->getId(),
307             'sourcetype'        => Tinebase_Model_Import::SOURCETYPE_REMOTE,
308             'container_id'      => $container->getId(),
309         ), $data));
310         
311         return $this->create($record);
312     }
313 }