0011996: add fallback app icon
[tine20] / tine20 / Tinebase / Application.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Tinebase
6  * @subpackage  Application
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @copyright   Copyright (c) 2007-2015 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Lars Kneschke <l.kneschke@metaways.de>
10  *
11  * @todo        add 'getTitleTranslation' function?
12  */
13
14 /**
15  * the class provides functions to handle applications
16  * 
17  * @package     Tinebase
18  * @subpackage  Application
19  */
20 class Tinebase_Application
21 {
22     /**
23      * application enabled
24      *
25      */
26     const ENABLED  = 'enabled';
27     
28     /**
29      * application disabled
30      *
31      */
32     const DISABLED = 'disabled';
33     
34     /**
35      * Table name
36      *
37      * @var string
38      */
39     protected $_tableName = 'applications';
40     
41     /**
42      * the db adapter
43      *
44      * @var Zend_Db_Adapter_Abstract
45      */
46     protected $_db;
47     
48     /**
49      * 
50      * @var Tinebase_Backend_Sql
51      */
52     protected $_backend;
53     
54     /**
55      * the constructor
56      *
57      * don't use the constructor. use the singleton 
58      */
59     private function __construct() 
60     {
61     }
62     
63     /**
64      * don't clone. Use the singleton.
65      *
66      */
67     private function __clone() 
68     {
69     }
70
71     /**
72      * holds the instance of the singleton
73      *
74      * @var Tinebase_Application
75      */
76     private static $instance = NULL;
77     
78     /**
79      * Returns instance of Tinebase_Application
80      *
81      * @return Tinebase_Application
82      */
83     public static function getInstance() 
84     {
85         if (self::$instance === NULL) {
86             self::$instance = new Tinebase_Application;
87         }
88         
89         return self::$instance;
90     }
91     
92     /**
93      * returns one application identified by id
94      *
95      * @param Tinebase_Model_Application|string $_applicationId the id of the application
96      * @throws Tinebase_Exception_NotFound
97      * @return Tinebase_Model_Application the information about the application
98      */
99     public function getApplicationById($_applicationId)
100     {
101         $applicationId = Tinebase_Model_Application::convertApplicationIdToInt($_applicationId);
102
103         $application = $this->getApplications()->getById($applicationId);
104         
105         if (!$application) {
106             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Application not found. Id: ' . $applicationId);
107             throw new Tinebase_Exception_NotFound("Application $applicationId not found.");
108         }
109         
110         return $application;
111     }
112
113     /**
114      * returns one application identified by application name
115      * - results are cached
116      *
117      * @param string $_applicationName the name of the application
118      * @return Tinebase_Model_Application the information about the application
119      * @throws Tinebase_Exception_InvalidArgument
120      * @throws Tinebase_Exception_NotFound
121      */
122     public function getApplicationByName($_applicationName)
123     {
124         if(empty($_applicationName) || ! is_string($_applicationName)) {
125             throw new Tinebase_Exception_InvalidArgument('$_applicationName can not be empty / has to be string.');
126         }
127
128         $application = $this->getApplications()->find('name', $_applicationName);
129         
130         if (!$application) {
131             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Application not found. Name: ' . $_applicationName);
132             throw new Tinebase_Exception_NotFound("Application $_applicationName not found.");
133         }
134         
135         return $application;
136     }
137     
138     /**
139      * get list of installed applications
140      *
141      * @param string $_sort optional the column name to sort by
142      * @param string $_dir optional the sort direction can be ASC or DESC only
143      * @param string $_filter optional search parameter
144      * @param int $_limit optional how many applications to return
145      * @param int $_start optional offset for applications
146      * @return Tinebase_Record_RecordSet of Tinebase_Model_Application
147      */
148     public function getApplications($_filter = NULL, $_sort = null, $_dir = 'ASC', $_start = NULL, $_limit = NULL)
149     {
150         $filter = null;
151         if ($_filter) {
152             $filter = new Tinebase_Model_ApplicationFilter(array(
153                 array('field' => 'name', 'operator' => 'contains', 'value' => $_filter),
154             ));
155         }
156         
157         $pagination = null;
158         if ($_sort) {
159             $pagination = new Tinebase_Model_Pagination(array(
160                 'sort'  => $_sort,
161                 'dir'   => $_dir,
162                 'start' => $_start,
163                 'limit' => $_limit
164             ));
165         }
166         
167         if ($filter === null && $pagination === null) {
168             try {
169                 $result = Tinebase_Cache_PerRequest::getInstance()->load(__CLASS__, __METHOD__, 'allApplications', Tinebase_Cache_PerRequest::VISIBILITY_SHARED);
170                 
171                 return $result;
172             } catch (Tinebase_Exception_NotFound $tenf) {
173                 // do nothing
174             }
175         }
176         
177         $result = $this->_getBackend()->search($filter, $pagination);
178         
179         if ($filter === null && $pagination === null) {
180             // cache result in persistent shared cache too
181             // cache will be cleared, when an application will be added or updated
182             Tinebase_Cache_PerRequest::getInstance()->save(__CLASS__, __METHOD__, 'allApplications', $result, Tinebase_Cache_PerRequest::VISIBILITY_SHARED);
183         }
184         
185         return $result;
186     }
187     
188     /**
189      * get enabled or disabled applications
190      *
191      * @param  string  $state  can be Tinebase_Application::ENABLED or Tinebase_Application::DISABLED
192      * @return Tinebase_Record_RecordSet list of applications
193      */
194     public function getApplicationsByState($state)
195     {
196         if (!in_array($state, array(Tinebase_Application::ENABLED, Tinebase_Application::DISABLED))) {
197             throw new Tinebase_Exception_InvalidArgument('$status can be only Tinebase_Application::ENABLED or Tinebase_Application::DISABLED');
198         }
199         
200         $result = $this->getApplications(null, /* sort = */ 'order')->filter('status', $state);
201         
202         return $result;
203     }
204     
205     /**
206      * get hash of installed applications
207      *
208      * @param string $_sort optional the column name to sort by
209      * @param string $_dir optional the sort direction can be ASC or DESC only
210      * @param string $_filter optional search parameter
211      * @param int $_limit optional how many applications to return
212      * @param int $_start optional offset for applications
213      * @return string
214      */
215     public function getApplicationsHash($_filter = NULL, $_sort = null, $_dir = 'ASC', $_start = NULL, $_limit = NULL)
216     {
217         $applications = $this->getApplications($_filter, $_sort, $_dir, $_start, $_limit);
218         
219         // create a hash of installed applications and their versions
220         $applications = array_combine(
221             Tinebase_Application::getInstance()->getApplications()->id,
222             Tinebase_Application::getInstance()->getApplications()->version
223         );
224         
225         ksort($applications);
226         
227         return Tinebase_Helper::arrayHash($applications, true);
228     }
229     
230     /**
231      * return the total number of applications installed
232      *
233      * @param $_filter
234      * 
235      * @return int
236      */
237     public function getTotalApplicationCount($_filter = NULL)
238     {
239         $select = $this->_getDb()->select()
240             ->from(SQL_TABLE_PREFIX . $this->_tableName, array('count' => 'COUNT(*)'));
241         
242         if($_filter !== NULL) {
243             $select->where($this->_getDb()->quoteIdentifier('name') . ' LIKE ?', '%' . $_filter . '%');
244         }
245         
246         $stmt = $this->_getDb()->query($select);
247         $result = $stmt->fetchAll(Zend_Db::FETCH_COLUMN);
248         
249         return $result[0];
250     }
251     
252     /**
253      * return if application is installed (and enabled)
254      *
255      * @param  Tinebase_Model_Application|string  $applicationId  the application name/id/object
256      * @param  boolean $checkEnabled (FALSE by default)
257      * 
258      * @return boolean
259      */
260     public function isInstalled($applicationId, $checkEnabled = FALSE)
261     {
262         try {
263             $app = $this->getApplicationById($applicationId);
264             return ($checkEnabled) ? ($app->status === self::ENABLED) : TRUE;
265         } catch (Tinebase_Exception_NotFound $tenf) {
266             return FALSE;
267         } catch (Zend_Db_Statement_Exception $tenf) {
268             // database tables might be not available yet
269             // @see 0011338: First Configuration fails after Installation
270             return FALSE;
271         }
272     }
273     
274     /**
275      * set application state
276      *
277      * @param   array   $_applicationIds application ids to set new state for
278      * @param   string  $state the new state
279      * @throws  Tinebase_Exception_InvalidArgument
280      */
281     public function setApplicationState($_applicationIds, $state)
282     {
283         if (!in_array($state, array(Tinebase_Application::ENABLED, Tinebase_Application::DISABLED))) {
284             throw new Tinebase_Exception_InvalidArgument('$_state can be only Tinebase_Application::DISABLED  or Tinebase_Application::ENABLED');
285         }
286         
287         if ($_applicationIds instanceof Tinebase_Model_Application ||
288             $_applicationIds instanceof Tinebase_Record_RecordSet
289         ) {
290             $applicationIds = (array)$_applicationIds->getId();
291         } else {
292             $applicationIds = (array)$_applicationIds;
293         }
294         
295         $data = array(
296             'status' => $state
297         );
298         
299         $affectedRows = $this->_getBackend()->updateMultiple($applicationIds, $data);
300         
301         if ($affectedRows === count($applicationIds)) {
302             if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
303                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Disabled/Enabled ' . $affectedRows . ' applications.');
304         } else {
305             if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE))
306                 Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
307                 . ' Could not set state for all requested applications: ' . print_r($applicationIds, TRUE));
308         }
309         
310         $this->resetClassCache();
311     }
312     
313     /**
314      * add new appliaction 
315      *
316      * @param Tinebase_Model_Application $application the new application object
317      * @return Tinebase_Model_Application the new application with the applicationId set
318      */
319     public function addApplication(Tinebase_Model_Application $application)
320     {
321         $application = $this->_getBackend()->create($application);
322         
323         $this->resetClassCache();
324         
325         return $application;
326     }
327     
328     /**
329      * get all possible application rights
330      *
331      * @param   int application id
332      * @return  array   all application rights
333      */
334     public function getAllRights($_applicationId)
335     {
336         $application = $this->getApplicationById($_applicationId);
337         
338         // call getAllApplicationRights for application (if it has specific rights)
339         $appAclClassName = $application->name . '_Acl_Rights';
340         if (@class_exists($appAclClassName)) {
341             $appAclObj = call_user_func(array($appAclClassName, 'getInstance'));
342             $allRights = $appAclObj->getAllApplicationRights();
343         } else {
344             $allRights = Tinebase_Acl_Rights::getInstance()->getAllApplicationRights($application->name);
345         }
346         
347         return $allRights;
348     }
349     
350     /**
351      * get right description
352      *
353      * @param   int     application id
354      * @param   string  right
355      * @return  array   right description
356      */
357     public function getAllRightDescriptions($_applicationId)
358     {
359         $application = $this->getApplicationById($_applicationId);
360         
361         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
362             ' Getting right descriptions for ' . $application->name );
363         
364         // call getAllApplicationRights for application (if it has specific rights)
365         $appAclClassName = $application->name . '_Acl_Rights';
366         if (! @class_exists($appAclClassName)) {
367             $appAclClassName = 'Tinebase_Acl_Rights';
368             $function = 'getTranslatedBasicRightDescriptions';
369         } else {
370             $function = 'getTranslatedRightDescriptions';
371         }
372         
373         $descriptions = call_user_func(array($appAclClassName, $function));
374         
375         return $descriptions;
376     }
377     
378     /**
379      * get tables of application
380      *
381      * @param Tinebase_Model_Application $_applicationId
382      * @return array
383      */
384     public function getApplicationTables($_applicationId)
385     {
386         $applicationId = Tinebase_Model_Application::convertApplicationIdToInt($_applicationId);
387         
388         $select = $this->_getDb()->select()
389             ->from(SQL_TABLE_PREFIX . 'application_tables', array('name'))
390             ->where($this->_getDb()->quoteIdentifier('application_id') . ' = ?', $applicationId);
391             
392         $stmt = $this->_getDb()->query($select);
393         $rows = $stmt->fetchAll(Zend_Db::FETCH_COLUMN);
394         
395         return $rows;
396     }
397
398     /**
399      * remove table from application_tables table
400      *
401      * @param Tinebase_Model_Application|string $_applicationId the applicationId
402      * @param string $_tableName the table name
403      */
404     public function removeApplicationTable($_applicationId, $_tableName)
405     {
406         $applicationId = Tinebase_Model_Application::convertApplicationIdToInt($_applicationId);
407         
408         $where = array(
409             $this->_getDb()->quoteInto($this->_getDb()->quoteIdentifier('application_id') . '= ?', $applicationId),
410             $this->_getDb()->quoteInto($this->_getDb()->quoteIdentifier('name') . '= ?', $_tableName)
411         );
412         
413         $this->_getDb()->delete(SQL_TABLE_PREFIX . 'application_tables', $where);
414     }
415     
416     /**
417      * reset class cache
418      * 
419      * @param string $method
420      * @return Tinebase_Application
421      */
422     public function resetClassCache($method = null)
423     {
424         Tinebase_Cache_PerRequest::getInstance()->reset(__CLASS__, $method);
425         
426         return $this;
427     }
428     
429     /**
430      * remove application from applications table
431      *
432      * @param Tinebase_Model_Application|string $_applicationId the applicationId
433      */
434     public function deleteApplication($_applicationId)
435     {
436         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG))
437             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Removing app ' . $_applicationId . ' from applications table.');
438         
439         $applicationId = Tinebase_Model_Application::convertApplicationIdToInt($_applicationId);
440         
441         $this->resetClassCache();
442         
443         $this->_getBackend()->delete($applicationId);
444     }
445     
446     /**
447      * add table to tine registry
448      *
449      * @param Tinebase_Model_Application
450      * @param string name of table
451      * @param int version of table
452      * @return int
453      */
454     public function addApplicationTable($_applicationId, $_name, $_version)
455     {
456         $applicationId = Tinebase_Model_Application::convertApplicationIdToInt($_applicationId);
457         
458         $applicationData = array(
459             'application_id' => $applicationId,
460             'name'           => $_name,
461             'version'        => $_version
462         );
463         
464         $this->_getDb()->insert(SQL_TABLE_PREFIX . 'application_tables', $applicationData);
465     }
466     
467     /**
468      * update application
469      * 
470      * @param Tinebase_Model_Application $_application
471      * @return Tinebase_Model_Application
472      */
473     public function updateApplication(Tinebase_Model_Application $_application)
474     {
475         $result = $this->_getBackend()->update($_application);
476         
477         $this->resetClassCache();
478         
479         return $result;
480     }
481     
482     /**
483      * delete containers, configs and other data of an application
484      * 
485      * NOTE: if a table with foreign key constraints to applications is added, we need to make sure that the data is deleted here 
486      * 
487      * @param Tinebase_Model_Application $_applicationName
488      * @return void
489      */
490     public function removeApplicationData(Tinebase_Model_Application $_application)
491     {
492         $dataToDelete = array(
493             'container'     => array('tablename' => ''),
494             'config'        => array('tablename' => ''),
495             'customfield'   => array('tablename' => ''),
496             'rights'        => array('tablename' => 'role_rights'),
497             'definitions'   => array('tablename' => 'importexport_definition'),
498             'filter'        => array('tablename' => 'filter'),
499             'modlog'        => array('tablename' => 'timemachine_modlog'),
500             'import'        => array('tablename' => 'import')
501         );
502         $countMessage = ' Deleted';
503         
504         $where = array(
505             $this->_getDb()->quoteInto($this->_getDb()->quoteIdentifier('application_id') . '= ?', $_application->getId())
506         );
507         
508         foreach ($dataToDelete as $dataType => $info) {
509             switch ($dataType) {
510                 case 'container':
511                     $count = Tinebase_Container::getInstance()->deleteContainerByApplicationId($_application->getId());
512                     break;
513                 case 'config':
514                     $count = Tinebase_Config::getInstance()->deleteConfigByApplicationId($_application->getId());
515                     break;
516                   case 'customfield':
517                       $count = Tinebase_CustomField::getInstance()->deleteCustomFieldsForApplication($_application->getId());
518                       break;
519                 default:
520                     if ((isset($info['tablename']) || array_key_exists('tablename', $info)) && ! empty($info['tablename'])) {
521                         try {
522                             $count = $this->_getDb()->delete(SQL_TABLE_PREFIX . $info['tablename'], $where);
523                         } catch (Zend_Db_Statement_Exception $zdse) {
524                             Tinebase_Exception::log($zdse);
525                             $count = 0;
526                         }
527                     } else {
528                         Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' No tablename defined for ' . $dataType);
529                         $count = 0;
530                     }
531             }
532             $countMessage .= ' ' . $count . ' ' . $dataType . '(s) /';
533         }
534         
535         $countMessage .= ' for application ' . $_application->name;
536         Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . $countMessage);
537     }
538     
539     /**
540      * 
541      * @return Tinebase_Backend_Sql
542      */
543     protected function _getBackend()
544     {
545         if (!isset($this->_backend)) {
546             $this->_backend = new Tinebase_Backend_Sql(array(
547                 'modelName' => 'Tinebase_Model_Application', 
548                 'tableName' => 'applications'
549             ), $this->_getDb());
550         }
551         
552         return $this->_backend;
553     }
554     
555     /**
556      * 
557      * @return Zend_Db_Adapter_Abstract
558      */
559     protected function _getDb()
560     {
561         if (!isset($this->_db)) {
562             $this->_db = Tinebase_Core::getDb();
563         }
564         
565         return $this->_db;
566     }
567 }