8b91d927fa7758eaa946f567259085202b0a9da6
[tine20] / tine20 / Addressbook / Controller / List.php
1 <?php
2 /**
3  * Tine 2.0
4  *
5  * @package     Addressbook
6  * @subpackage  Controller
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @author      Lars Kneschke <l.kneschke@metaways.de>
9  * @copyright   Copyright (c) 2010-2016 Metaways Infosystems GmbH (http://www.metaways.de)
10  * 
11  */
12
13 /**
14  * contact controller for Addressbook
15  *
16  * @package     Addressbook
17  * @subpackage  Controller
18  */
19 class Addressbook_Controller_List extends Tinebase_Controller_Record_Abstract
20 {
21     /**
22      * @var null|Tinebase_Backend_Sql
23      */
24     protected $_memberRolesBackend = null;
25
26     /**
27      * the constructor
28      *
29      * don't use the constructor. use the singleton
30      */
31     private function __construct()
32     {
33         $this->_resolveCustomFields = true;
34         $this->_backend = new Addressbook_Backend_List();
35         if (true === Tinebase_Config::getInstance()->featureEnabled(Tinebase_Config::FEATURE_SEARCH_PATH)) {
36             $this->_useRecordPaths = true;
37         }
38         $this->_modelName = 'Addressbook_Model_List';
39         $this->_applicationName = 'Addressbook';
40     }
41
42     /**
43      * don't clone. Use the singleton.
44      *
45      */
46     private function __clone()
47     {
48     }
49
50     /**
51      * holds the instance of the singleton
52      *
53      * @var Addressbook_Controller_List
54      */
55     private static $_instance = NULL;
56
57     public function getMemberRolesBackend()
58     {
59         if ($this->_memberRolesBackend === null) {
60             $this->_memberRolesBackend = new Tinebase_Backend_Sql(array(
61                 'tableName' => 'adb_list_m_role',
62                 'modelName' => 'Addressbook_Model_ListMemberRole',
63             ));
64         }
65
66         return $this->_memberRolesBackend;
67     }
68
69     /**
70      * the singleton pattern
71      *
72      * @return Addressbook_Controller_List
73      */
74     public static function getInstance()
75     {
76         if (self::$_instance === NULL) {
77             self::$_instance = new Addressbook_Controller_List();
78         }
79
80         return self::$_instance;
81     }
82
83     public static function destroyInstance()
84     {
85         self::$_instance = null;
86     }
87
88     /**
89      * @see Tinebase_Controller_Record_Abstract::get()
90      *
91      * @param string $_id
92      * @param int $_containerId
93      * @return Addressbook_Model_List
94      */
95     public function get($_id, $_containerId = NULL)
96     {
97         $result = new Tinebase_Record_RecordSet('Addressbook_Model_List', array(parent::get($_id, $_containerId)));
98         $this->_removeHiddenListMembers($result);
99         return $result->getFirstRecord();
100     }
101
102     /**
103      * use contact search to remove hidden list members
104      *
105      * @param Tinebase_Record_RecordSet $lists
106      */
107     protected function _removeHiddenListMembers($lists)
108     {
109         if (count($lists) === 0) {
110             return;
111         }
112
113         $allMemberIds = array();
114         foreach ($lists as $list) {
115             $allMemberIds = array_merge($list->members, $allMemberIds);
116         }
117         $allMemberIds = array_unique($allMemberIds);
118
119         if (empty($allMemberIds)) {
120             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
121                 . ' No members found.');
122             return;
123         }
124
125         $allVisibleMemberIds = Addressbook_Controller_Contact::getInstance()->search(new Addressbook_Model_ContactFilter(array(array(
126             'field' => 'id',
127             'operator' => 'in',
128             'value' => $allMemberIds
129         ))), NULL, FALSE, TRUE);
130
131         $hiddenMemberids = array_diff($allMemberIds, $allVisibleMemberIds);
132
133         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
134             . ' Found ' . count($hiddenMemberids) . ' hidden members, removing them');
135         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
136             . print_r($hiddenMemberids, TRUE));
137
138         foreach ($lists as $list) {
139             $list->members = array_diff($list->members, $hiddenMemberids);
140         }
141     }
142
143     /**
144      * @see Tinebase_Controller_Record_Abstract::search()
145      *
146      * @param Tinebase_Model_Filter_FilterGroup $_filter
147      * @param Tinebase_Model_Pagination $_pagination
148      * @param bool $_getRelations
149      * @param bool $_onlyIds
150      * @param string $_action
151      * @return array|Tinebase_Record_RecordSet
152      */
153     public function search(Tinebase_Model_Filter_FilterGroup $_filter = NULL, Tinebase_Model_Pagination $_pagination = NULL, $_getRelations = FALSE, $_onlyIds = FALSE, $_action = 'get')
154     {
155         $result = parent::search($_filter, $_pagination, $_getRelations, $_onlyIds, $_action);
156
157         if ($_onlyIds !== true) {
158             $this->_removeHiddenListMembers($result);
159         }
160
161         return $result;
162     }
163
164     /**
165      * @see Tinebase_Controller_Record_Abstract::getMultiple()
166      *
167      * @param array $_ids
168      * @param bool $_ignoreACL
169      * @return Tinebase_Record_RecordSet
170      */
171     public function getMultiple($_ids, $_ignoreACL = FALSE)
172     {
173         $result = parent::getMultiple($_ids, $_ignoreACL);
174         if (true !== $_ignoreACL) {
175             $this->_removeHiddenListMembers($result);
176         }
177         return $result;
178     }
179
180     /**
181      * add new members to list
182      *
183      * @param  mixed $_listId
184      * @param  mixed $_newMembers
185      * @param  boolean $_addToGroup
186      * @return Addressbook_Model_List
187      */
188     public function addListMember($_listId, $_newMembers, $_addToGroup = true)
189     {
190         try {
191             $list = $this->get($_listId);
192         } catch (Tinebase_Exception_AccessDenied $tead) {
193             $this->_fixEmptyContainerId($_listId);
194             $list = $this->get($_listId);
195         }
196
197         $this->_checkGrant($list, 'update', TRUE, 'No permission to add list member.');
198         $this->_checkGroupGrant($list, TRUE, 'No permission to add list member.');
199
200         $list = $this->_backend->addListMember($_listId, $_newMembers);
201
202         if (true === $_addToGroup && ! empty($list->group_id)) {
203             foreach (Tinebase_Record_RecordSet::getIdsFromMixed($_newMembers) as $userId) {
204                 Admin_Controller_Group::getInstance()->addGroupMember($list->group_id, $userId, false);
205             }
206         }
207
208         return $this->get($list->getId());
209     }
210
211     protected function _checkGroupGrant($_list, $_throw = false, $_msg = '')
212     {
213         if (! empty($_list->group_id)) {
214             if (!Tinebase_Core::getUser()->hasRight('Admin', Admin_Acl_Rights::MANAGE_ACCOUNTS)) {
215                 if ($_throw) {
216                     throw new Tinebase_Exception_AccessDenied($_msg);
217                 } else {
218                     return false;
219                 }
220             }
221         }
222         return true;
223     }
224
225     /**
226      * fixes empty container ids / perhaps this can be removed later as all lists should have a container id!
227      *
228      * @param  mixed $_listId
229      * @return Addressbook_Model_List
230      */
231     protected function _fixEmptyContainerId($_listId)
232     {
233         $list = $this->_backend->get($_listId);
234
235         if (empty($list->container_id)) {
236             $list->container_id = $this->_getDefaultInternalAddressbook();
237             $list = $this->_backend->update($list);
238         }
239
240         return $list;
241     }
242
243     /**
244      * get default internal adb id
245      *
246      * @return string
247      */
248     protected function _getDefaultInternalAddressbook()
249     {
250         $appConfigDefaults = Admin_Controller::getInstance()->getConfigSettings();
251         $result = (isset($appConfigDefaults[Admin_Model_Config::DEFAULTINTERNALADDRESSBOOK])) ? $appConfigDefaults[Admin_Model_Config::DEFAULTINTERNALADDRESSBOOK] : NULL;
252
253         if (empty($result)) {
254             if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
255                 . ' Default internal addressbook not found. Creating new config setting.');
256             $result = Addressbook_Setup_Initialize::setDefaultInternalAddressbook()->getId();
257         }
258         return $result;
259     }
260
261     /**
262      * remove members from list
263      *
264      * @param  mixed $_listId
265      * @param  mixed $_removeMembers
266      * @param  boolean $_removeFromGroup
267      * @return Addressbook_Model_List
268      */
269     public function removeListMember($_listId, $_removeMembers, $_removeFromGroup = true)
270     {
271         $list = $this->get($_listId);
272
273         $this->_checkGrant($list, 'update', TRUE, 'No permission to remove list member.');
274         $this->_checkGroupGrant($list, TRUE, 'No permission to remove list member.');
275
276         $list = $this->_backend->removeListMember($_listId, $_removeMembers);
277
278         if (true === $_removeFromGroup && ! empty($list->group_id)) {
279             foreach (Tinebase_Record_RecordSet::getIdsFromMixed($_removeMembers) as $userId) {
280                 Admin_Controller_Group::getInstance()->removeGroupMember($list->group_id, $userId, false);
281             }
282         }
283
284         return $this->get($list->getId());
285     }
286
287     /**
288      * inspect creation of one record
289      *
290      * @param  Tinebase_Record_Interface $_record
291      * @throws Tinebase_Exception_AccessDenied
292      */
293     protected function _inspectBeforeCreate(Tinebase_Record_Interface $_record)
294     {
295         if (isset($_record->type) && $_record->type == Addressbook_Model_List::LISTTYPE_GROUP) {
296             if (empty($_record->group_id)) {
297                 throw new Tinebase_Exception_UnexpectedValue('group_id is empty, must not happen for list type group');
298             }
299
300             // check rights
301             $this->_checkGroupGrant($_record, TRUE, 'can not add list of type ' . Addressbook_Model_List::LISTTYPE_GROUP);
302
303             // check if group is there, if not => not found exception
304             Admin_Controller_Group::getInstance()->get($_record->group_id);
305         }
306     }
307
308     /**
309      * inspect creation of one record (after create)
310      *
311      * @param   Tinebase_Record_Interface $_createdRecord
312      * @param   Tinebase_Record_Interface $_record
313      * @return  void
314      */
315     protected function _inspectAfterCreate($_createdRecord, Tinebase_Record_Interface $_record)
316     {
317         /** @var Addressbook_Model_List $_createdRecord */
318         $this->_fireChangeListeEvent($_createdRecord);
319     }
320
321     /**
322      * inspect update of one record
323      *
324      * @param   Tinebase_Record_Interface $_record the update record
325      * @param   Tinebase_Record_Interface $_oldRecord the current persistent record
326      * @return  void
327      */
328     protected function _inspectBeforeUpdate($_record, $_oldRecord)
329     {
330         if (! empty($_record->group_id)) {
331
332             // first check if something changed that requires special rights
333             $changeGroup = false;
334             foreach (Addressbook_Model_List::getManageAccountFields() as $field) {
335                 if ($_record->{$field} != $_oldRecord->{$field}) {
336                     $changeGroup = true;
337                     break;
338                 }
339             }
340
341             // then do the update, the group controller will check manage accounts right
342             if ($changeGroup) {
343                 $groupController = Admin_Controller_Group::getInstance();
344                 $group = $groupController->get($_record->group_id);
345
346                 foreach (Addressbook_Model_List::getManageAccountFields() as $field) {
347                     $group->{$field} = $_record->{$field};
348                 }
349
350                 $groupController->update($group, false);
351             }
352         }
353     }
354
355     /**
356      * inspect update of one record (after update)
357      *
358      * @param   Addressbook_Model_List $updatedRecord   the just updated record
359      * @param   Addressbook_Model_List $record          the update record
360      * @param   Addressbook_Model_List $currentRecord   the current record (before update)
361      * @return  void
362      */
363     protected function _inspectAfterUpdate($updatedRecord, $record, $currentRecord)
364     {
365         $this->_fireChangeListeEvent($updatedRecord);
366     }
367
368     /**
369      * fireChangeListeEvent
370      *
371      * @param Addressbook_Model_List $list
372      */
373     protected function _fireChangeListeEvent(Addressbook_Model_List $list)
374     {
375         $event = new Addressbook_Event_ChangeList();
376         $event->list = $list;
377         Tinebase_Event::fireEvent($event);
378     }
379
380     /**
381      * inspects delete action
382      *
383      * @param array $_ids
384      * @return array of ids to actually delete
385      */
386     protected function _inspectDelete(array $_ids)
387     {
388         $lists = $this->getMultiple($_ids);
389         foreach ($lists as $list) {
390             $event = new Addressbook_Event_DeleteList();
391             $event->list = $list;
392             Tinebase_Event::fireEvent($event);
393         }
394
395         return $_ids;
396     }
397
398     /**
399      * create or update list in addressbook sql backend
400      *
401      * @param  Tinebase_Model_Group $group
402      * @return Addressbook_Model_List
403      */
404     public function createOrUpdateByGroup(Tinebase_Model_Group $group)
405     {
406         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($group->toArray(), TRUE));
407
408         try {
409             if (empty($group->list_id)) {
410                 $list = $this->_backend->getByGroupName($group->name);
411                 if (!$list) {
412                     // jump to catch block => no list_id provided and no existing list for group found
413                     throw new Tinebase_Exception_NotFound('list_id is empty');
414                 }
415                 $group->list_id = $list->getId();
416             } else {
417                 $list = $this->_backend->get($group->list_id);
418             }
419
420             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
421                 . ' Update list ' . $group->name);
422
423             $list->name = $group->name;
424             $list->description = $group->description;
425             $list->email = $group->email;
426             $list->type = Addressbook_Model_List::LISTTYPE_GROUP;
427             $list->container_id = (empty($group->container_id)) ? $this->_getDefaultInternalAddressbook() : $group->container_id;
428             $list->members = (isset($group->members)) ? $this->_getContactIds($group->members) : array();
429
430             // add modlog info
431             Tinebase_Timemachine_ModificationLog::setRecordMetaData($list, 'update');
432
433             $list = $this->_backend->update($list);
434             $list = $this->get($list->getId());
435
436         } catch (Tinebase_Exception_NotFound $tenf) {
437             $list = $this->createByGroup($group);
438         }
439
440         return $list;
441     }
442
443     /**
444      * create new list by group
445      *
446      * @param Tinebase_Model_Group $group
447      * @return Addressbook_Model_List
448      */
449     public function createByGroup($group)
450     {
451         $list = new Addressbook_Model_List(array(
452             'name' => $group->name,
453             'description' => $group->description,
454             'email' => $group->email,
455             'type' => Addressbook_Model_List::LISTTYPE_GROUP,
456             'container_id' => (empty($group->container_id)) ? $this->_getDefaultInternalAddressbook() : $group->container_id,
457             'members' => (isset($group->members)) ? $this->_getContactIds($group->members) : array(),
458         ));
459
460         // add modlog info
461         Tinebase_Timemachine_ModificationLog::setRecordMetaData($list, 'create');
462
463         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
464             . ' Add new list ' . $group->name);
465         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
466             . ' ' . print_r($list->toArray(), TRUE));
467
468         $list = $this->_backend->create($list);
469
470         return $list;
471     }
472
473     /**
474      * get contact_ids of users
475      *
476      * @param  array $_userIds
477      * @return array
478      */
479     protected function _getContactIds($_userIds)
480     {
481         $contactIds = array();
482
483         if (empty($_userIds)) {
484             return $contactIds;
485         }
486
487         foreach ($_userIds as $userId) {
488             $user = Tinebase_User::getInstance()->getUserByPropertyFromBackend('accountId', $userId);
489             if (!empty($user->contact_id)) {
490                 $contactIds[] = $user->contact_id;
491             }
492         }
493
494         return $contactIds;
495     }
496
497     /**
498      * you can define default filters here
499      *
500      * @param Tinebase_Model_Filter_FilterGroup $_filter
501      */
502     protected function _addDefaultFilter(Tinebase_Model_Filter_FilterGroup $_filter = NULL)
503     {
504         if (!$_filter->isFilterSet('showHidden')) {
505             $hiddenFilter = $_filter->createFilter('showHidden', 'equals', FALSE);
506             $hiddenFilter->setIsImplicit(TRUE);
507             $_filter->addFilter($hiddenFilter);
508         }
509     }
510
511     /**
512      * set relations / tags / alarms
513      *
514      * @param   Tinebase_Record_Interface $updatedRecord the just updated record
515      * @param   Tinebase_Record_Interface $record the update record
516      * @param   Tinebase_Record_Interface $currentRecord   the original record if one exists
517      * @param   boolean                   $returnUpdatedRelatedData
518      * @return  Tinebase_Record_Interface
519      */
520     protected function _setRelatedData(Tinebase_Record_Interface $updatedRecord, Tinebase_Record_Interface $record, Tinebase_Record_Interface $currentRecord = null, $returnUpdatedRelatedData = FALSE)
521     {
522         /** @var Addressbook_Model_List $record */
523         if (isset($record->memberroles)) {
524             // get migration
525             // TODO add generic helper fn for this?
526             $memberrolesToSet = (!$record->memberroles instanceof Tinebase_Record_RecordSet)
527                 ? new Tinebase_Record_RecordSet(
528                     'Addressbook_Model_ListMemberRole',
529                     $record->memberroles,
530                     /* $_bypassFilters */ true
531                 ) : $record->memberroles;
532
533             foreach ($memberrolesToSet as $memberrole) {
534                 foreach (array('contact_id', 'list_role_id', 'list_id') as $field) {
535                     if (isset($memberrole[$field]['id'])) {
536                         $memberrole[$field] = $memberrole[$field]['id'];
537                     }
538                 }
539             }
540
541             $currentMemberroles = $this->_getMemberRoles($record);
542             $diff = $currentMemberroles->diff($memberrolesToSet);
543             if (count($diff['added']) > 0) {
544                 $diff['added']->list_id = $updatedRecord->getId();
545                 foreach ($diff['added'] as $memberrole) {
546                     $this->getMemberRolesBackend()->create($memberrole);
547                 }
548             }
549             if (count($diff['removed']) > 0) {
550                 $this->getMemberRolesBackend()->delete($diff['removed']->getArrayOfIds());
551             }
552         }
553
554         $result = parent::_setRelatedData($updatedRecord, $record, $currentRecord, $returnUpdatedRelatedData);
555
556         return $result;
557     }
558
559     /**
560      * add related data to record
561      *
562      * @param Addressbook_Model_List $record
563      */
564     protected function _getRelatedData($record)
565     {
566         $memberRoles = $this->_getMemberRoles($record);
567         if (count($memberRoles) > 0) {
568             $record->memberroles = $memberRoles;
569         }
570         parent::_getRelatedData($record);
571     }
572
573     /**
574      * @param Addressbook_Model_List $record
575      * @return Tinebase_Record_RecordSet|Addressbook_Model_ListMemberRole
576      */
577     protected function _getMemberRoles($record)
578     {
579         $result = $this->getMemberRolesBackend()->getMultipleByProperty($record->getId(), 'list_id');
580         return $result;
581     }
582
583     /**
584      * get all lists given contact is member of
585      *
586      * @param $contact
587      * @return array
588      */
589     public function getMemberships($contact)
590     {
591         return $this->_backend->getMemberships($contact);
592     }
593 }