improve getModels() in app controller
[tine20] / tine20 / Tinebase / Controller / Abstract.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 Schuele <p.schuele@metaways.de>
9  * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
10  * 
11  */
12
13 /**
14  * controller abstract for applications
15  *
16  * @package     Tinebase
17  * @subpackage  Controller
18  */
19 abstract class Tinebase_Controller_Abstract extends Tinebase_Pluggable_Abstract implements Tinebase_Controller_Interface
20 {
21     /**
22      * default settings
23      * 
24      * @var array
25      */
26     protected $_defaultsSettings = array();
27
28     /**
29      * application models if given
30      *
31      * @var null
32      */
33     protected $_models = null;
34
35     /**
36      * holds the default Model of this application
37      * @var string
38      */
39     protected static $_defaultModel = NULL;
40     
41     /**
42      * application name (is needed in checkRight())
43      *
44      * @var string
45      */
46     protected $_applicationName = '';
47     
48     /**
49      * disable events on demand
50      * 
51      * @var mixed   false => no events filtered, true => all events filtered, array => disable only specific events
52      */
53     protected $_disabledEvents = false;
54
55     /**
56      * Models of this application that make use of Tinebase_Record_Path
57      *
58      * @var array|null
59      */
60     protected $_modelsUsingPath = null;
61     
62     /**
63      * generic check admin rights function
64      * rules: 
65      * - ADMIN right includes all other rights
66      * - MANAGE_* right includes VIEW_* right 
67      * - results are cached if caching is active (with cache tag 'rights')
68      * 
69      * @param   string  $_right to check
70      * @param   boolean $_throwException [optional]
71      * @param   boolean $_includeTinebaseAdmin [optional]
72      * @return  boolean
73      * @throws  Tinebase_Exception_UnexpectedValue
74      * @throws  Tinebase_Exception_AccessDenied
75      * @throws  Tinebase_Exception
76      * 
77      * @todo move that to *_Acl_Rights
78      * @todo include Tinebase admin? atm only the application admin right is checked
79      * @todo think about moving the caching to Tinebase_Acl_Roles and use only a class cache as it is difficult (and slow?) to invalidate
80      */
81     public function checkRight($_right, $_throwException = TRUE, $_includeTinebaseAdmin = TRUE) 
82     {
83         if (empty($this->_applicationName)) {
84             throw new Tinebase_Exception_UnexpectedValue('No application name defined!');
85         }
86         if (! is_object(Tinebase_Core::getUser())) {
87             throw new Tinebase_Exception('No user found for right check!');
88         }
89         
90         $right = strtoupper($_right);
91         
92         $cache = Tinebase_Core::getCache();
93         $cacheId = Tinebase_Helper::convertCacheId('checkRight' . Tinebase_Core::getUser()->getId() . $right . $this->_applicationName);
94         $result = $cache->load($cacheId);
95         
96         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $cacheId);
97         
98         if (!$result) {
99             $applicationRightsClass = $this->_applicationName . '_Acl_Rights';
100             
101             // array with the rights that should be checked, ADMIN is in it per default
102             $rightsToCheck = ($_includeTinebaseAdmin) ? array(Tinebase_Acl_Rights::ADMIN) : array();
103             
104             if (preg_match("/VIEW_([A-Z_]*)/", $right, $matches)) {
105                 // manage right includes view right
106                 $rightsToCheck[] = constant($applicationRightsClass. '::MANAGE_' . $matches[1]);
107             } 
108             
109             $rightsToCheck[] = constant($applicationRightsClass. '::' . $right);
110             
111             $result = FALSE;
112             
113             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
114                 . ' Checking rights: ' . print_r($rightsToCheck, TRUE));
115             
116             foreach ($rightsToCheck as $rightToCheck) {
117                 if (Tinebase_Acl_Roles::getInstance()->hasRight($this->_applicationName, Tinebase_Core::getUser()->getId(), $rightToCheck)) {
118                     $result = TRUE;
119                     break;
120                 }
121             }
122             
123             $cache->save($result, $cacheId, array('rights'), 120);
124         }
125         
126         if (!$result && $_throwException) {
127             throw new Tinebase_Exception_AccessDenied("You are not allowed to $right in application $this->_applicationName !");
128         }
129         
130         return $result;
131     }
132     
133     /**
134      * Returns default settings for app
135      *
136      * @param boolean $_resolve if some values should be resolved
137      * @return  array settings data
138      */
139     public function getConfigSettings($_resolve = FALSE)
140     {
141         $appConfig = Tinebase_Config::getAppConfig($this->_applicationName);
142         if ($appConfig != NULL) {
143             $settings = $appConfig->get(
144                 Tinebase_Config::APPDEFAULTS, 
145                 new Tinebase_Config_Struct($this->_defaultsSettings)
146             )->toArray();
147         } else { 
148             $settings = $this->_defaultsSettings;
149         }
150         return ($_resolve) ? $this->_resolveConfigSettings($settings) : $settings;
151     }
152     
153     /**
154      * resolve some settings
155      * 
156      * @param array $_settings
157      */
158     protected function _resolveConfigSettings($_settings)
159     {
160         return $_settings;
161     }
162     
163     /**
164      * save settings
165      * 
166      * @param array $_settings
167      * @return void
168      */
169     public function saveConfigSettings($_settings)
170     {
171         // only admins are allowed to do this
172         $this->checkRight(Tinebase_Acl_Rights::ADMIN);
173         
174         $appConfig = Tinebase_Config::getAppConfig($this->_applicationName);
175         
176         if ($appConfig !== NULL) {
177             $appConfig->set(Tinebase_Config::APPDEFAULTS, $_settings);
178         }
179     }
180     
181     /**
182      * returns the default model of this application
183      *
184      * @return string
185      */
186     public function getDefaultModel()
187     {
188         if (static::$_defaultModel !== null) {
189             return static::$_defaultModel;
190         }
191
192         // no default model defined, using first model of app...
193         $models = $this->getModels();
194         return (count($models) > 0) ? $models[0] : null;
195     }
196     
197     /**
198      * returns controller instance for given $_controllerName
199      * 
200      * @param string $_controllerName
201      * @return Tinebase_Controller
202      */
203     public static function getController($_controllerName)
204     {
205         if (! class_exists($_controllerName)) {
206             throw new Exception("Controller" . $_controllerName . "not found.");
207         }
208         
209         if (!in_array('Tinebase_Controller_Interface', class_implements($_controllerName))) {
210             throw new Exception("Controller $_controllerName does not implement Tinebase_Controller_Interface.");
211         }
212         
213         return call_user_func(array($_controllerName, 'getInstance'));
214     }
215
216     /**
217      * delete all personal user folders and the content associated with these folders
218      *
219      * @param Tinebase_Model_User|string $_accountId the account object
220      */
221     public function deletePersonalFolder($_accountId, $model = '')
222     {
223         if ($_accountId instanceof Tinebase_Record_Abstract) {
224             $_accountId = $_accountId->getId();
225         }
226
227         if ('' === $model) {
228             $model = static::$_defaultModel;
229         }
230         // attention, currently everybody who has admin rights on a personal container is the owner of it
231         // even if multiple users have admin rights on that personal container! (=> multiple owners)
232         $containers = Tinebase_Container::getInstance()->getPersonalContainer($_accountId, $model, $_accountId, '*', true);
233
234         foreach ($containers as $container) {
235             //Tinebase_Container::getInstance()->deleteContainerContents($container, true);
236             Tinebase_Container::getInstance()->deleteContainer($container, true);
237         }
238     }
239
240     /**
241      * get core data for this application
242      *
243      * @return Tinebase_Record_RecordSet
244      *
245      * TODO add generic approach for fetching core data from config
246      */
247     public function getCoreDataForApplication()
248     {
249         $result = new Tinebase_Record_RecordSet('CoreData_Model_CoreData');
250
251         // TODO get configured core data
252
253         return $result;
254     }
255
256     /**
257      * get all models of this application that use tinebase_record_path
258      *
259      * @return array|null
260      */
261     public function getModelsUsingPaths()
262     {
263         return $this->_modelsUsingPath;
264     }
265
266     /**
267      * @return array
268      *
269      * @param bool $MCV2only filter for new modelconfig with doctrine schema tool
270      */
271     public function getModels($MCV2only = false)
272     {
273         if ($this->_models === null && ! empty($this->_applicationName)) {
274
275             $cache = Tinebase_Core::getCache();
276             $cacheId = Tinebase_Helper::convertCacheId('getModels' . $this->_applicationName);
277             $models = $cache->load($cacheId);
278
279             if (! $models) {
280                 $models = $this->_getModelsFromAppDir();
281                 // cache for a long time only on prod
282                 $cache->save($models, $cacheId, array(), TINE20_BUILDTYPE === 'DEVELOPMENT' ? 1 : 3600);
283             }
284
285             $this->_models = $models;
286         }
287
288         if ($MCV2only) {
289             $md = new Tinebase_Record_DoctrineMappingDriver();
290             $MCv2Models = array();
291             foreach ((array)$this->_models as $model) {
292                 if ($md->isTransient($model)) {
293                     $MCv2Models[] = $model;
294                 }
295             }
296
297             return $MCv2Models;
298         }
299
300         return $this->_models;
301     }
302
303     /**
304      * get models from application directory
305      *
306      * @return array|null
307      */
308     protected function _getModelsFromAppDir()
309     {
310         try {
311             $dir = new DirectoryIterator(dirname(dirname(dirname(__FILE__))) . '/' . $this->_applicationName . '/Model/');
312         } catch (Exception $e) {
313             Tinebase_Exception::log($e);
314             return null;
315         }
316
317         $models = array();
318         foreach ($dir as $fileinfo) {
319             if (!$fileinfo->isDot() && !$fileinfo->isLink()) {
320                 if ($this->_isModelFile($fileinfo)) {
321                     $models[] = $this->_applicationName . '_Model_' . str_replace('.php', '', $fileinfo->getBasename());
322                 } else if ($fileinfo->isDir()) {
323                     // go (only) one level deeper
324                     $subdir = new DirectoryIterator($fileinfo->getPath() . '/' . $fileinfo->getFilename());
325                     foreach ($subdir as $subfileinfo) {
326                         if ($this->_isModelFile($subfileinfo)) {
327                             $models[] = $this->_applicationName . '_Model_' . $fileinfo->getBasename() . '_'
328                                 . str_replace('.php', '', $subfileinfo->getBasename());
329                         }
330                     }
331
332                 }
333             }
334         }
335
336         foreach ($models as $key => $model) {
337             if (class_exists($model)) {
338                 $reflection = new ReflectionClass($model);
339                 $interfaces = $reflection->getInterfaceNames();
340                 if (! in_array('Tinebase_Record_Interface', $interfaces)) {
341                     unset($models[$key]);
342                 }
343             } else {
344                 // interface, no php class, ...
345                 unset($models[$key]);
346             }
347         }
348
349         return $models;
350     }
351
352     /**
353      * returns true if $fileinfo describes a model file
354      *
355      * @param $fileinfo
356      * @return bool
357      */
358     protected function _isModelFile($fileinfo)
359     {
360         $isModel = (
361             ! $fileinfo->isDot() &&
362             ! $fileinfo->isLink() &&
363             $fileinfo->isFile() &&
364             ! preg_match('/filter\.php/i', $fileinfo->getBasename()) &&
365             ! preg_match('/abstract\.php/i', $fileinfo->getBasename())
366         );
367
368         return $isModel;
369     }
370 }