0009768: Use ModelConfig for Timetracker models
[tine20] / tine20 / Timetracker / Controller / Timesheet.php
1 <?php
2 /**
3  * Timesheet controller for Timetracker application
4  * 
5  * @package     Timetracker
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) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
10  */
11
12 /**
13  * Timesheet controller class for Timetracker application
14  * 
15  * @package     Timetracker
16  * @subpackage  Controller
17  */
18 class Timetracker_Controller_Timesheet extends Tinebase_Controller_Record_Abstract
19 {
20     /**
21      * should deadline be checked
22      * 
23      * @var boolean
24      */
25     protected $_doCheckDeadline = TRUE;
26     
27     /**
28      * check deadline or not
29      * 
30      * @return boolean
31      */
32     public function doCheckDeadLine()
33     {
34         $value = (func_num_args() === 1) ? (bool) func_get_arg(0) : NULL;
35         return $this->_setBooleanMemberVar('_doCheckDeadline', $value);
36     }
37     /**
38      * the constructor
39      *
40      * don't use the constructor. use the singleton 
41      */
42     private function __construct() {
43         
44         // config
45         $this->_applicationName = 'Timetracker';
46         $this->_backend = new Timetracker_Backend_Timesheet();
47         $this->_modelName = 'Timetracker_Model_Timesheet';
48         $this->_resolveCustomFields = TRUE;
49         
50         // disable container ACL checks as we don't init the 'Shared Timesheets' grants in the setup
51         $this->_doContainerACLChecks = FALSE;
52         
53         // use modlog and don't completely delete records
54         $this->_purgeRecords = FALSE;
55     }
56     
57     /**
58      * don't clone. Use the singleton.
59      *
60      */
61     private function __clone()
62     {
63     }
64     
65     /**
66      * field grants for specific timesheet fields
67      *
68      * @var array
69      */
70     protected $_fieldGrants = array(
71         'is_billable' => array('default' => 1,  'requiredGrant' => Timetracker_Model_TimeaccountGrants::MANAGE_BILLABLE),
72         'billed_in'   => array('default' => '', 'requiredGrant' => Tinebase_Model_Grants::GRANT_ADMIN),
73         'is_cleared'  => array('default' => 0,  'requiredGrant' => Tinebase_Model_Grants::GRANT_ADMIN),
74     );
75     
76     /**
77      * holds the instance of the singleton
78      *
79      * @var Timetracker_Controller_Timesheet
80      */
81     private static $_instance = NULL;
82     
83     /**
84      * the singleton pattern
85      *
86      * @return Timetracker_Controller_Timesheet
87      */
88     public static function getInstance() 
89     {
90         if (self::$_instance === NULL) {
91             self::$_instance = new self();
92         }
93         
94         return self::$_instance;
95     }        
96
97     /****************************** functions ************************/
98
99     /**
100      * get all timesheets for a timeaccount
101      *
102      * @param string $_timeaccountId
103      * @return Tinebase_Record_RecordSet of Timetracker_Model_Timesheet records
104      */
105     public function getTimesheetsByTimeaccountId($_timeaccountId)
106     {
107         $filter = new Timetracker_Model_TimesheetFilter(array(
108             array('field' => 'timeaccount_id', 'operator' => 'AND', 'value' => array(
109                 array('field' => 'id', 'operator' => 'equals', 'value' => $_timeaccountId),
110             ))
111         ));
112         
113         $records = $this->search($filter);
114         
115         return $records;
116     }
117     
118     /**
119      * find timesheets by the given arguments. the result will be returned in an array
120      *
121      * @param string $timeaccountId
122      * @param Tinebase_DateTime $startDate
123      * @param Tinebase_DateTime $endDate
124      * @param string $destination
125      * @param string $taCostCenter
126      * @param string $cacheId
127      * @return array
128      */
129     public function findTimesheetsByTimeaccountAndPeriod($timeaccountId, $startDate, $endDate, $destination = NULL, $taCostCenter = NULL)
130     {
131         $filter = new Timetracker_Model_TimesheetFilter(array());
132         $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'timeaccount_id', 'operator' => 'equals', 'value' => $timeaccountId)));
133         $filter->addFilter(new Tinebase_Model_Filter_Date(array('field' => 'start_date', 'operator' => 'before', 'value' => $endDate)));
134         $filter->addFilter(new Tinebase_Model_Filter_Date(array('field' => 'start_date', 'operator' => 'after', 'value' => $startDate)));
135         $filter->addFilter(new Tinebase_Model_Filter_Bool(array('field' => 'is_cleared', 'operator' => 'equals', 'value' => true)));
136         
137         $timesheets = $this->search($filter);
138         
139         $matrix = array();
140         foreach($timesheets as $ts) {
141             $matrix[] = array(
142                 'userAccountId' => $ts->account_id,
143                 'amount' => ($ts->duration / 60),
144                 'destination' => $destination,
145                 'taCostCenter' => $taCostCenter
146             );
147         }
148         
149         return $matrix;
150     }
151     
152     /**
153      * checks deadline of record
154      * 
155      * @param Timetracker_Model_Timesheet $_record
156      * @param boolean $_throwException
157      * @return void
158      * @throws Timetracker_Exception_Deadline
159      */
160     protected function _checkDeadline(Timetracker_Model_Timesheet $_record, $_throwException = TRUE)
161     {
162         if (! $this->_doCheckDeadline) {
163             return;
164         }
165         
166         // get timeaccount
167         $timeaccount = Timetracker_Controller_Timeaccount::getInstance()->get($_record->timeaccount_id);
168         
169         if (isset($timeaccount->deadline) && $timeaccount->deadline == Timetracker_Model_Timeaccount::DEADLINE_LASTWEEK) {
170             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Check if deadline is exceeded for timeaccount ' . $timeaccount->title);
171             
172             // it is only on monday allowed to add timesheets for last week
173             $date = new Tinebase_DateTime();
174             
175             $date->setTime(0,0,0);
176             $dayOfWeek = $date->get('w');
177             
178             if ($dayOfWeek >= 2) {
179                 // only allow to add ts for this week
180                 $date->sub($dayOfWeek, Tinebase_DateTime::MODIFIER_DAY);
181             } else {
182                 // only allow to add ts for last week
183                 $date->sub($dayOfWeek+7, Tinebase_DateTime::MODIFIER_DAY);
184             }
185             
186             // convert start date to Tinebase_DateTime
187             $startDate = new Tinebase_DateTime($_record->start_date);
188             if ($date->compare($startDate) >= 0) {
189                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Deadline exceeded: ' . $startDate . ' < ' . $date);
190                 
191                 if ($this->checkRight(Timetracker_Acl_Rights::MANAGE_TIMEACCOUNTS, FALSE)
192                      || Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Tinebase_Model_Grants::GRANT_ADMIN)) {
193                     if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ 
194                         . ' User with admin / manage all rights is allowed to save Timesheet even if it exceeds the deadline.'
195                     );
196                 } else if ($_throwException) {
197                     throw new Timetracker_Exception_Deadline();
198                 }
199             } else {
200                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Valid date: ' . $startDate . ' >= ' . $date);
201             }
202         }
203     }
204     
205     /****************************** overwritten functions ************************/    
206     
207     /**
208      * inspect creation of one record
209      * 
210      * @param   Tinebase_Record_Interface $_record
211      * @return  void
212      */
213     protected function _inspectBeforeCreate(Tinebase_Record_Interface $_record)
214     {
215         $this->_checkDeadline($_record);
216     }
217     
218     /**
219      * inspect update of one record
220      * 
221      * @param   Tinebase_Record_Interface $_record      the update record
222      * @param   Tinebase_Record_Interface $_oldRecord   the current persistent record
223      * @return  void
224      */
225     protected function _inspectBeforeUpdate($_record, $_oldRecord)
226     {
227         $this->_checkDeadline($_record);
228     }    
229     
230     /**
231      * check grant for action
232      *
233      * @param Timetracker_Model_Timeaccount $_record
234      * @param string $_action
235      * @param boolean $_throw
236      * @param string $_errorMessage
237      * @param Timetracker_Model_Timesheet $_oldRecord
238      * @return boolean
239      * @throws Tinebase_Exception_AccessDenied
240      * 
241      * @todo think about just setting the default values when user 
242      *       hasn't the required grant to change the field (instead of throwing exception) 
243      */
244     protected function _checkGrant($_record, $_action, $_throw = TRUE, $_errorMessage = 'No Permission.', $_oldRecord = NULL)
245     {
246         if (!$this->_doContainerACLChecks) {
247             return true;
248         }
249
250         $isAdmin = false;
251         // users with MANAGE_TIMEACCOUNTS have all grants here
252         if ( $this->checkRight(Timetracker_Acl_Rights::MANAGE_TIMEACCOUNTS, FALSE)
253             || Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Tinebase_Model_Grants::GRANT_ADMIN)) {
254             $isAdmin = true;
255         }
256
257         // only TA managers are allowed to alter TS of closed TAs, but they have to confirm first that they really want to do it
258         if ($_action != 'get') {
259             $timeaccount = Timetracker_Controller_Timeaccount::getInstance()->get($_record->timeaccount_id);
260             if (! $timeaccount->is_open) {
261                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
262                         . ' This Timeaccount is already closed!');
263
264                 if ($isAdmin === true) {
265                     if (is_array($this->_requestContext) && isset($this->_requestContext['skipClosedCheck']) && $this->_requestContext['skipClosedCheck']) {
266                         return true;
267                     }
268                 }
269
270                 if ($_throw) {
271                     throw new Timetracker_Exception_ClosedTimeaccount();
272                 }
273                 return FALSE;
274             }
275             
276             // check if timeaccount->is_billable is false => set default in fieldGrants to 0 and allow only managers to change it
277             if (!$timeaccount->is_billable) {
278                 $this->_fieldGrants['is_billable']['default'] = 0;
279                 $this->_fieldGrants['is_billable']['requiredGrant'] = Tinebase_Model_Grants::GRANT_ADMIN;
280             }
281         }
282
283         if ($isAdmin === true) {
284             return true;
285         }
286
287         
288         $hasGrant = FALSE;
289         
290         switch ($_action) {
291             case 'get':
292                 $hasGrant = (
293                     Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, array(
294                         Timetracker_Model_TimeaccountGrants::VIEW_ALL,
295                         Timetracker_Model_TimeaccountGrants::BOOK_ALL
296                     ))
297                     || ($_record->account_id == Tinebase_Core::getUser()->getId() && Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Timetracker_Model_TimeaccountGrants::BOOK_OWN))
298                 );
299                 break;
300             case 'create':
301                 $hasGrant = (
302                     ($_record->account_id == Tinebase_Core::getUser()->getId() && Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Timetracker_Model_TimeaccountGrants::BOOK_OWN))
303                     || Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Timetracker_Model_TimeaccountGrants::BOOK_ALL) 
304                 );
305                 
306                 if ($hasGrant) {
307                     foreach ($this->_fieldGrants as $field => $config) {
308                         if (isset($_record->$field) && $_record->$field != $config['default']) {
309                             $hasGrant &= Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, $config['requiredGrant']);
310                         }
311                     }
312                 }
313
314                 break;
315             case 'update':
316                 $hasGrant = (
317                     ($_record->account_id == Tinebase_Core::getUser()->getId() && Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Timetracker_Model_TimeaccountGrants::BOOK_OWN))
318                     || Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Timetracker_Model_TimeaccountGrants::BOOK_ALL) 
319                 );
320                 
321                 if ($hasGrant) {
322                     foreach ($this->_fieldGrants as $field => $config) {
323                         if (isset($_record->$field) && $_record->$field != $_oldRecord->$field) {
324                             $hasGrant &= Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, $config['requiredGrant']);
325                         }
326                     }
327                 }
328
329                 break;
330             case 'delete':
331                 $hasGrant = (
332                     ($_record->account_id == Tinebase_Core::getUser()->getId() && Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Timetracker_Model_TimeaccountGrants::BOOK_OWN))
333                     || Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Timetracker_Model_TimeaccountGrants::BOOK_ALL) 
334                 );
335                 break;
336         }
337         
338         if ($_throw && !$hasGrant) {
339             throw new Tinebase_Exception_AccessDenied($_errorMessage);
340         }
341         
342         return $hasGrant;
343     }
344     
345     /**
346      * Removes timeaccounts where current user has no access to
347      * 
348      * @param Tinebase_Model_Filter_FilterGroup $_filter
349      * @param string $_action get|update
350      */
351     public function checkFilterACL(Tinebase_Model_Filter_FilterGroup $_filter, $_action = 'get')
352     {
353         switch ($_action) {
354             case 'get':
355                 $_filter->setRequiredGrants(array(
356                     Timetracker_Model_TimeaccountGrants::BOOK_OWN,
357                     Timetracker_Model_TimeaccountGrants::BOOK_ALL,
358                     Timetracker_Model_TimeaccountGrants::VIEW_ALL,
359                     Tinebase_Model_Grants::GRANT_ADMIN,
360                 ));
361                 break;
362             case 'update':
363                 $_filter->setRequiredGrants(array(
364                     Timetracker_Model_TimeaccountGrants::BOOK_OWN,
365                     Timetracker_Model_TimeaccountGrants::BOOK_ALL,
366                     Tinebase_Model_Grants::GRANT_ADMIN,
367                 ));
368                 break;
369             case 'export':
370                 $_filter->setRequiredGrants(array(
371                     Tinebase_Model_Grants::GRANT_EXPORT,
372                     Tinebase_Model_Grants::GRANT_ADMIN,
373                 ));
374                 break;
375             default:
376                 throw new Timetracker_Exception_UnexpectedValue('Unknown action: ' . $_action);
377         }
378     }
379 }