7 * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
8 * @author Cornelius Weiss <c.weiss@metaways.de>
9 * @copyright Copyright (c) 2010-2013 Metaways Infosystems GmbH (http://www.metaways.de)
13 * Native tine 2.0 events sql backend
15 * Events consists of the properties of Calendar_Model_Event except Tags and Notes
16 * which are as always handles by their controllers/backends
18 * @TODO rework fetch handling. all fetch operations should be based on search.
19 * remove old grant sql when done
24 class Calendar_Backend_Sql extends Tinebase_Backend_Sql_Abstract
27 * Table name without prefix
31 protected $_tableName = 'cal_events';
38 protected $_modelName = 'Calendar_Model_Event';
41 * if modlog is active, we add 'is_deleted = 0' to select object in _getSelect()
45 protected $_modlogActive = TRUE;
50 * @var Calendar_Backend_Sql_Attendee
52 protected $_attendeeBackend = NULL;
55 * list of record based grants
57 protected $_recordBasedGrants = array(
58 Tinebase_Model_Grants::GRANT_FREEBUSY,
59 Tinebase_Model_Grants::GRANT_READ,
60 Tinebase_Model_Grants::GRANT_SYNC,
61 Tinebase_Model_Grants::GRANT_EXPORT,
62 Tinebase_Model_Grants::GRANT_EDIT,
63 Tinebase_Model_Grants::GRANT_DELETE,
64 Tinebase_Model_Grants::GRANT_PRIVATE,
70 * @param Zend_Db_Adapter_Abstract $_db optional
71 * @param array $_options (optional)
73 public function __construct ($_dbAdapter = NULL, $_options = array())
75 parent::__construct($_dbAdapter, $_options);
77 $this->_attendeeBackend = new Calendar_Backend_Sql_Attendee($_dbAdapter);
83 * @param Tinebase_Record_Interface $_record
84 * @return Tinebase_Record_Interface
85 * @throws Tinebase_Exception_InvalidArgument
86 * @throws Tinebase_Exception_UnexpectedValue
88 public function create(Tinebase_Record_Interface $_record)
91 if ($_record->rrule) {
92 $_record->rrule = (string) $_record->rrule;
94 $_record->rrule = !empty($_record->rrule) ? $_record->rrule : NULL;
95 $_record->recurid = !empty($_record->recurid) ? $_record->recurid : NULL;
97 $event = parent::create($_record);
98 $this->_saveExdates($_record);
99 //$this->_saveAttendee($_record);
101 return $this->get($event->getId());
105 * Gets one entry (by property)
107 * @param mixed $_value
108 * @param string $_property
109 * @param bool $_getDeleted
110 * @return Tinebase_Record_Interface
111 * @throws Tinebase_Exception_NotFound
113 public function getByProperty($_value, $_property = 'name', $_getDeleted = FALSE)
115 //$pagination = new Tinebase_Model_Pagination(array('limit' => 1));
116 $filters = new Calendar_Model_EventFilter();
118 $filter = new Tinebase_Model_Filter_Text($_property, 'equals', $_value);
119 $filters->addFilter($filter);
122 $deletedFilter = new Tinebase_Model_Filter_Bool('is_deleted', 'equals', Tinebase_Model_Filter_Bool::VALUE_NOTSET);
123 $filters->addFilter($deletedFilter);
126 $resultSet = $this->search($filters, NULL, FALSE);
128 switch (count($resultSet)) {
130 throw new Tinebase_Exception_NotFound($this->_modelName . " record with $_property " . $_value . ' not found!');
133 $result = $resultSet->getFirstRecord();
136 throw new Tinebase_Exception_UnexpectedValue(' in total ' . count($resultSet) . ' where found. But only one should!');
143 * Calendar optimized search function
145 * 1. get all events neglecting grants filter
146 * 2. get all related container grants (via resolving)
147 * 3. compute effective grants in PHP and only keep events
148 * user has required grant for
150 * @TODO rethink if an outer container filter could help
152 * @param Tinebase_Model_Filter_FilterGroup $_filter
153 * @param Tinebase_Model_Pagination $_pagination
154 * @param boolean $_onlyIds
155 * @return Tinebase_Record_RecordSet|array
157 public function search(Tinebase_Model_Filter_FilterGroup $_filter = NULL, Tinebase_Model_Pagination $_pagination = NULL, $_onlyIds = FALSE)
159 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' Searching events ...');
161 if ($_pagination === NULL) {
162 $_pagination = new Tinebase_Model_Pagination();
165 $getDeleted = !!$_filter && $_filter->getFilter('is_deleted');
166 $select = parent::_getSelect('*', $getDeleted);
169 /* table */ array('exdate' => $this->_tablePrefix . 'cal_exdate'),
170 /* on */ $this->_db->quoteIdentifier('exdate.cal_event_id') . ' = ' . $this->_db->quoteIdentifier($this->_tableName . '.id'),
171 /* select */ array('exdate' => $this->_dbCommand->getAggregate('exdate.exdate')));
173 // NOTE: we join here as attendee and role filters need it
175 /* table */ array('attendee' => $this->_tablePrefix . 'cal_attendee'),
176 /* on */ $this->_db->quoteIdentifier('attendee.cal_event_id') . ' = ' . $this->_db->quoteIdentifier('cal_events.id'),
177 /* select */ array());
181 /* table */ array('dispcontainer' => $this->_tablePrefix . 'container'),
182 /* on */ $this->_db->quoteIdentifier('dispcontainer.id') . ' = ' . $this->_db->quoteIdentifier('attendee.displaycontainer_id'),
183 /* select */ array());
185 $select->where($this->_db->quoteIdentifier('dispcontainer.is_deleted') . ' = 0 OR ' . $this->_db->quoteIdentifier('dispcontainer.is_deleted') . 'IS NULL');
188 // remove grantsfilter here as we do grants computation in PHP
189 $grantsFilter = $_filter->getFilter('grants');
191 $_filter->removeFilter('grants');
194 // clone the filter, as the filter is also used in the json frontend
195 // and the calendar filter is used in the UI to
196 $clonedFilters = clone $_filter;
198 $calendarFilter = null;
199 foreach ($clonedFilters as $filter) {
200 if ($filter instanceof Calendar_Model_CalendarFilter) {
201 $calendarFilter = $filter;
202 $clonedFilters->removeFilter($filter);
207 $this->_addFilter($select, $clonedFilters);
209 $select->group($this->_tableName . '.' . 'id');
210 Tinebase_Backend_Sql_Abstract::traitGroup($select);
212 if ($calendarFilter) {
213 $select1 = clone $select;
214 $select2 = clone $select;
216 $calendarFilter->appendFilterSql1($select1, $this);
217 $calendarFilter->appendFilterSql2($select2, $this);
219 $select = $this->getAdapter()->select()->union(array(
225 $_pagination->appendPaginationSql($select);
227 $stmt = $this->_db->query($select);
228 $rows = (array)$stmt->fetchAll(Zend_Db::FETCH_ASSOC);
230 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
231 . ' Event base rows fetched: ' . count($rows) . ' select: ' . $select);
233 $result = $this->_rawDataToRecordSet($rows);
235 $this->_checkGrants($result, $grantsFilter);
237 return $_onlyIds ? $result->{is_bool($_onlyIds) ? $this->_getRecordIdentifier() : $_onlyIds} : $result;
241 * calculate event permissions and remove events that don't match
243 * @param Tinebase_Record_RecordSet $events
244 * @param Tinebase_Model_Filter_AclFilter $grantsFilter
246 protected function _checkGrants($events, $grantsFilter)
248 $currentContact = Tinebase_Core::getUser()->contact_id;
249 $containerGrants = Tinebase_Container::getInstance()->getContainerGrantsOfRecords($events, Tinebase_Core::getUser());
250 $resolvedAttendees = Calendar_Model_Attender::getResolvedAttendees($events->attendee, true);
253 $inheritableGrants = array(
254 Tinebase_Model_Grants::GRANT_FREEBUSY,
255 Tinebase_Model_Grants::GRANT_READ,
256 Tinebase_Model_Grants::GRANT_SYNC,
257 Tinebase_Model_Grants::GRANT_EXPORT,
258 Tinebase_Model_Grants::GRANT_PRIVATE,
261 if ($grantsFilter instanceof Calendar_Model_GrantFilter) {
262 $requiredGrants = $grantsFilter->getRequiredGrants();
263 if (is_array($requiredGrants)) {
264 $requiredGrants = array_intersect($requiredGrants, $this->_recordBasedGrants);
266 // TODO throw exception here?
267 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
268 . ' Required grants not set in grants filter: ' . print_r($grantsFilter->toArray(), true));
272 foreach ($events as $event) {
273 $containerId = $event->container_id instanceof Tinebase_Model_Container
274 ? $event->container_id->getId()
275 : $event->container_id;
277 // either current user is organizer or has admin right on container
278 if ( $event->organizer === $currentContact
279 || (isset($containerGrants[$containerId]) && $containerGrants[$containerId]->account_grants[Tinebase_Model_Grants::GRANT_ADMIN])
281 foreach ($this->_recordBasedGrants as $grant) {
282 $event->{$grant} = true;
285 // has all rights => no need to filter
289 // grants to original container
290 if (isset($containerGrants[$containerId])) {
291 foreach ($this->_recordBasedGrants as $grant) {
292 $event->{$grant} = $containerGrants[$containerId]->account_grants[$grant];
296 // check grant inheritance
297 if ($event->attendee instanceof Tinebase_Record_RecordSet) {
298 foreach ($inheritableGrants as $grant) {
299 if (! $event->{$grant}) {
300 foreach ($event->attendee as $attendee) {
301 $attendee = $resolvedAttendees->getById($attendee->getId());
307 if ( $attendee->displaycontainer_id instanceof Tinebase_Model_Container
308 && $attendee->displaycontainer_id->account_grants
309 && ( $attendee->displaycontainer_id->account_grants[$grant]
310 || $attendee->displaycontainer_id->account_grants[Tinebase_Model_Grants::GRANT_ADMIN]
313 $event->{$grant} = true;
321 // check if one of the grants is set ...
322 if (isset($requiredGrants) && is_array($requiredGrants)) {
323 foreach ($requiredGrants as $requiredGrant) {
324 if ($event->{$requiredGrant}) {
329 // ... otherwise mark for removal
330 $toRemove[] = $event;
334 // remove records with non matching grants
335 foreach ($toRemove as $event) {
336 $events->removeRecord($event);
341 * get the basic select object to fetch records from the database
343 * @param array|string|Zend_Db_Expr $_cols columns to get, * per default
344 * @param boolean $_getDeleted get deleted records (if modlog is active)
345 * @return Zend_Db_Select
347 protected function _getSelectSimple($_cols = '*', $_getDeleted = FALSE)
349 $select = $this->_db->select();
351 $select->from(array($this->_tableName => $this->_tablePrefix . $this->_tableName), $_cols);
353 if (!$_getDeleted && $this->_modlogActive) {
354 // don't fetch deleted objects
355 $select->where($this->_db->quoteIdentifier($this->_tableName . '.is_deleted') . ' = 0');
362 * Gets total count of search with $_filter
364 * @param Tinebase_Model_Filter_FilterGroup $_filter
367 public function searchCount(Tinebase_Model_Filter_FilterGroup $_filter)
369 $select = $this->_getSelect(array('count' => 'COUNT(*)'));
370 $this->_addFilter($select, $_filter);
372 $result = $this->_db->fetchOne($select);
378 * Updates existing entry
380 * @param Tinebase_Record_Interface $_record
381 * @throws Tinebase_Exception_Record_Validation|Tinebase_Exception_InvalidArgument
382 * @return Tinebase_Record_Interface Record|NULL
384 public function update(Tinebase_Record_Interface $_record)
386 if ($_record->rrule) {
387 $_record->rrule = (string) $_record->rrule;
390 if ($_record->container_id instanceof Tinebase_Model_Container) {
391 $_record->container_id = $_record->container_id->getId();
394 $_record->rrule = !empty($_record->rrule) ? $_record->rrule : NULL;
395 $_record->recurid = !empty($_record->recurid) ? $_record->recurid : NULL;
397 $event = parent::update($_record);
398 $this->_saveExdates($_record);
400 return $this->get($event->getId(), TRUE);
404 * get the basic select object to fetch records from the database
406 * @param array|string|Zend_Db_Expr $_cols columns to get, * per default
407 * @param boolean $_getDeleted get deleted records (if modlog is active)
408 * @return Zend_Db_Select
410 protected function _getSelect($_cols = '*', $_getDeleted = FALSE)
412 $select = $this->_getSelectSimple();
414 $this->_appendEffectiveGrantCalculationSql($select);
417 /* table */ array('exdate' => $this->_tablePrefix . 'cal_exdate'),
418 /* on */ $this->_db->quoteIdentifier('exdate.cal_event_id') . ' = ' . $this->_db->quoteIdentifier($this->_tableName . '.id'),
419 /* select */ array('exdate' => $this->_dbCommand->getAggregate('exdate.exdate')));
421 $select->group($this->_tableName . '.' . 'id');
427 * appends effective grant calculation to select object
429 * @param Zend_Db_Select $_select
431 protected function _appendEffectiveGrantCalculationSql($_select, $_attendeeFilters = NULL)
433 // groupmemberships of current user, needed to compute phys and inherited grants
435 /* table */ array('groupmemberships' => $this->_tablePrefix . 'group_members'),
436 /* on */ $this->_db->quoteInto($this->_db->quoteIdentifier('groupmemberships.account_id') . ' = ?' , Tinebase_Core::getUser()->getId()),
437 /* select */ array());
439 // attendee joins the attendee we need to compute the curr users effective grants
440 // NOTE: 2010-04 the behaviour changed. Now, only the attendee the client filters for are
441 // taken into account for grants calculation
442 $attendeeWhere = FALSE;
443 if (is_array($_attendeeFilters) && !empty($_attendeeFilters)) {
444 $attendeeSelect = $this->_db->select();
445 foreach ((array) $_attendeeFilters as $attendeeFilter) {
446 if ($attendeeFilter instanceof Calendar_Model_AttenderFilter) {
447 $attendeeFilter->appendFilterSql($attendeeSelect, $this);
451 $whereArray = $attendeeSelect->getPart(Zend_Db_Select::SQL_WHERE);
452 if (! empty($whereArray)) {
453 $attendeeWhere = ' AND ' . array_value(0, $whereArray);
458 /* table */ array('attendee' => $this->_tablePrefix . 'cal_attendee'),
459 /* on */ $this->_db->quoteIdentifier('attendee.cal_event_id') . ' = ' . $this->_db->quoteIdentifier('cal_events.id') .
461 /* select */ array());
466 /* table */ array('attendeeaccounts' => $this->_tablePrefix . 'accounts'),
467 /* on */ $this->_db->quoteIdentifier('attendeeaccounts.contact_id') . ' = ' . $this->_db->quoteIdentifier('attendee.user_id') . ' AND (' .
468 $this->_db->quoteInto($this->_db->quoteIdentifier('attendee.user_type') . '= ?', Calendar_Model_Attender::USERTYPE_USER) . ' OR ' .
469 $this->_db->quoteInto($this->_db->quoteIdentifier('attendee.user_type') . '= ?', Calendar_Model_Attender::USERTYPE_GROUPMEMBER) .
471 /* select */ array());
474 /* table */ array('attendeegroupmemberships' => $this->_tablePrefix . 'group_members'),
475 /* on */ $this->_db->quoteIdentifier('attendeegroupmemberships.account_id') . ' = ' . $this->_db->quoteIdentifier('attendeeaccounts.contact_id'),
476 /* select */ array());
481 /* table */ array('dispgrants' => $this->_tablePrefix . 'container_acl'),
482 /* on */ $this->_db->quoteIdentifier('dispgrants.container_id') . ' = ' . $this->_db->quoteIdentifier('attendee.displaycontainer_id') .
483 ' AND ' . $this->_getContainGrantCondition('dispgrants', 'groupmemberships'),
484 /* select */ array());
487 /* table */ array('physgrants' => $this->_tablePrefix . 'container_acl'),
488 /* on */ $this->_db->quoteIdentifier('physgrants.container_id') . ' = ' . $this->_db->quoteIdentifier('cal_events.container_id'),
489 /* select */ array());
491 $allGrants = Tinebase_Model_Grants::getAllGrants();
493 foreach ($allGrants as $grant) {
494 if (in_array($grant, $this->_recordBasedGrants)) {
495 $_select->columns(array($grant => "\n MAX( CASE WHEN ( \n" .
496 ' /* physgrant */' . $this->_getContainGrantCondition('physgrants', 'groupmemberships', $grant) . " OR \n" .
497 ' /* implicit */' . $this->_getImplicitGrantCondition($grant) . " OR \n" .
498 ' /* inherited */' . $this->_getInheritedGrantCondition($grant) . " \n" .
499 ") THEN 1 ELSE 0 END ) "));
501 $_select->columns(array($grant => "\n MAX( CASE WHEN ( \n" .
502 ' /* physgrant */' . $this->_getContainGrantCondition('physgrants', 'groupmemberships', $grant) . "\n" .
503 ") THEN 1 ELSE 0 END ) "));
509 * returns SQL with container grant condition
511 * @param string $_aclTableName
512 * @param string $_groupMembersTableName
513 * @param string|array $_requiredGrant (defaults none)
514 * @param Zend_Db_Expr|int|Tinebase_Model_User $_user (defaults current user)
517 protected function _getContainGrantCondition($_aclTableName, $_groupMembersTableName, $_requiredGrant=NULL, $_user=NULL )
519 $quoteTypeIdentifier = $this->_db->quoteIdentifier($_aclTableName . '.account_type');
520 $quoteIdIdentifier = $this->_db->quoteIdentifier($_aclTableName . '.account_id');
522 if ($_user instanceof Zend_Db_Expr) {
523 $userExpression = $_user;
525 $accountId = $_user ? Tinebase_Model_User::convertUserIdToInt($_user) : Tinebase_Core::getUser()->getId();
526 $userExpression = new Zend_Db_Expr($this->_db->quote($accountId));
529 $sql = $this->_db->quoteInto( "($quoteTypeIdentifier = ?", Tinebase_Acl_Rights::ACCOUNT_TYPE_USER) . " AND $quoteIdIdentifier = $userExpression)" .
530 $this->_db->quoteInto(" OR ($quoteTypeIdentifier = ?", Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP) . ' AND ' . $this->_db->quoteIdentifier("$_groupMembersTableName.group_id") . " = $quoteIdIdentifier" . ')' .
531 $this->_db->quoteInto(" OR ($quoteTypeIdentifier = ?)", Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE);
533 if ($_requiredGrant) {
534 $sql = "($sql) AND " . $this->_db->quoteInto($this->_db->quoteIdentifier($_aclTableName . '.account_grant') . ' IN (?)', (array)$_requiredGrant);
542 * returns SQL condition for implicit grants
544 * @param string $_requiredGrant
545 * @param Tinebase_Model_User $_user (defaults to current user)
548 protected function _getImplicitGrantCondition($_requiredGrant, $_user=NULL)
550 $accountId = $_user ? $_user->getId() : Tinebase_Core::getUser()->getId();
551 $contactId = $_user ? $_user->contact_id : Tinebase_Core::getUser()->contact_id;
553 // delte grant couldn't be gained implicitly
554 if ($_requiredGrant == Tinebase_Model_Grants::GRANT_DELETE) {
558 // organizer gets all other grants implicitly
559 $sql = $this->_db->quoteIdentifier('cal_events.organizer') . " = " . $this->_db->quote($contactId);
561 // attendee get read, sync, export and private grants implicitly
562 if (in_array($_requiredGrant, array(Tinebase_Model_Grants::GRANT_READ, Tinebase_Model_Grants::GRANT_SYNC, Tinebase_Model_Grants::GRANT_EXPORT, Tinebase_Model_Grants::GRANT_PRIVATE))) {
563 $readCond = $this->_db->quoteIdentifier('attendeeaccounts.id') . ' = ' . $this->_db->quote($accountId) . ' AND (' .
564 $this->_db->quoteInto($this->_db->quoteIdentifier('attendee.user_type') . ' = ?', Calendar_Model_Attender::USERTYPE_USER) . ' OR ' .
565 $this->_db->quoteInto($this->_db->quoteIdentifier('attendee.user_type') . ' = ?', Calendar_Model_Attender::USERTYPE_GROUPMEMBER) .
568 $sql = "($sql) OR ($readCond)";
575 * returns SQL for inherited grants
577 * @param string $_requiredGrant
580 protected function _getInheritedGrantCondition($_requiredGrant)
582 // current user needs to have grant on display calendar
583 $sql = $this->_getContainGrantCondition('dispgrants', 'groupmemberships', $_requiredGrant);
585 // _AND_ attender(admin) of display calendar needs to have grant on phys calendar
586 // @todo include implicit inherited grants
587 if (! in_array($_requiredGrant, array(Tinebase_Model_Grants::GRANT_READ, Tinebase_Model_Grants::GRANT_FREEBUSY))) {
588 $userExpr = new Zend_Db_Expr($this->_db->quoteIdentifier('attendeeaccounts.id'));
590 $attenderPhysGrantCond = $this->_getContainGrantCondition('physgrants', 'attendeegroupmemberships', $_requiredGrant, $userExpr);
591 // NOTE: this condition is weak! Not some attendee must have implicit grant.
592 // -> an attender we have reqired grants for his diplay cal must have implicit grants
593 //$attenderImplicitGrantCond = $this->_getImplicitGrantCondition($_requiredGrant, $userExpr);
595 //$sql = "($sql) AND ($attenderPhysGrantCond) OR ($attenderImplicitGrantCond)";
596 $sql = "($sql) AND ($attenderPhysGrantCond)";
603 * converts raw data from adapter into a single record
605 * @param array $_data
606 * @return Tinebase_Record_Abstract
608 protected function _rawDataToRecord(array $_rawData) {
609 $event = parent::_rawDataToRecord($_rawData);
611 $this->appendForeignRecordSetToRecord($event, 'attendee', 'id', Calendar_Backend_Sql_Attendee::FOREIGNKEY_EVENT, $this->_attendeeBackend);
617 * converts raw data from adapter into a set of records
619 * @param array $_rawData of arrays
620 * @return Tinebase_Record_RecordSet
622 protected function _rawDataToRecordSet(array $_rawData)
624 $events = new Tinebase_Record_RecordSet($this->_modelName);
625 $events->addIndices(array('rrule', 'recurid'));
627 foreach ($_rawData as $rawEvent) {
628 $events->addRecord(new Calendar_Model_Event($rawEvent, true));
631 $this->appendForeignRecordSetToRecordSet($events, 'attendee', 'id', Calendar_Backend_Sql_Attendee::FOREIGNKEY_EVENT, $this->_attendeeBackend);
637 * saves exdates of an event
639 * @param Calendar_Model_Event $_event
641 protected function _saveExdates($_event)
643 $this->_db->delete($this->_tablePrefix . 'cal_exdate', $this->_db->quoteInto($this->_db->quoteIdentifier('cal_event_id') . '= ?', $_event->getId()));
645 // only save exdates if its an recurring event
646 if (! empty($_event->rrule)) {
647 foreach ((array)$_event->exdate as $exdate) {
648 if (is_object($exdate)) {
649 $this->_db->insert($this->_tablePrefix . 'cal_exdate', array(
650 'id' => $_event->generateUID(),
651 'cal_event_id' => $_event->getId(),
652 'exdate' => $exdate->get(Tinebase_Record_Abstract::ISO8601LONG)
655 if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
656 . ' Exdate needs to be an object:' . var_export($exdate, TRUE));
663 * saves attendee of given event
665 * @param Calendar_Model_Evnet $_event
667 protected function _saveAttendee($_event)
669 $attendee = $_event->attendee instanceof Tinebase_Record_RecordSet ?
671 new Tinebase_Record_RecordSet($this->_attendeeBackend->getModelName());
672 $attendee->cal_event_id = $_event->getId();
674 $currentAttendee = $this->_attendeeBackend->getMultipleByProperty($_event->getId(), Calendar_Backend_Sql_Attendee::FOREIGNKEY_EVENT);
676 $diff = $currentAttendee->getMigration($attendee->getArrayOfIds());
677 $this->_attendeeBackend->delete($diff['toDeleteIds']);
679 foreach ($attendee as $attende) {
680 $method = $attende->getId() ? 'update' : 'create';
681 $this->_attendeeBackend->$method($attende);
686 /****************************** attendee functions ************************/
689 * gets attendee of a given event
691 * @param Calendar_Model_Event $_event
692 * @return Tinebase_Record_RecordSet
694 public function getEventAttendee(Calendar_Model_Event $_event)
696 $attendee = $this->_attendeeBackend->getMultipleByProperty($_event->getId(), Calendar_Backend_Sql_Attendee::FOREIGNKEY_EVENT);
702 * creates given attender in database
704 * @param Calendar_Model_Attender $_attendee
705 * @return Calendar_Model_Attender
707 public function createAttendee(Calendar_Model_Attender $_attendee)
709 if ($_attendee->user_id instanceof Addressbook_Model_Contact) {
710 $_attendee->user_id = $_attendee->user_id->getId();
711 } else if ($_attendee->user_id instanceof Addressbook_Model_List) {
712 $_attendee->user_id = $_attendee->user_id->group_id;
715 if ($_attendee->displaycontainer_id instanceof Tinebase_Model_Container) {
716 $_attendee->displaycontainer_id = $_attendee->displaycontainer_id->getId();
719 return $this->_attendeeBackend->create($_attendee);
723 * updates given attender in database
725 * @param Calendar_Model_Attender $_attendee
726 * @return Calendar_Model_Attender
728 public function updateAttendee(Calendar_Model_Attender $_attendee)
730 if ($_attendee->user_id instanceof Addressbook_Model_Contact) {
731 $_attendee->user_id = $_attendee->user_id->getId();
732 } else if ($_attendee->user_id instanceof Addressbook_Model_List) {
733 $_attendee->user_id = $_attendee->user_id->group_id;
736 if ($_attendee->displaycontainer_id instanceof Tinebase_Model_Container) {
737 $_attendee->displaycontainer_id = $_attendee->displaycontainer_id->getId();
740 return $this->_attendeeBackend->update($_attendee);
744 * deletes given attender in database
746 * @param Calendar_Model_Attender $_attendee
749 public function deleteAttendee(array $_ids)
751 return $this->_attendeeBackend->delete($_ids);
755 * delete duplicate events defined by an event filter
757 * @param Calendar_Model_EventFilter $filter
758 * @param boolean $dryrun
759 * @return integer number of deleted events
761 public function deleteDuplicateEvents($filter, $dryrun = TRUE)
763 if ($dryrun && Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
764 . ' - Running in dry run mode - using filter: ' . print_r($filter->toArray(), true));
766 $duplicateFields = array('summary', 'dtstart', 'dtend');
768 $select = $this->_db->select();
769 $select->from(array($this->_tableName => $this->_tablePrefix . $this->_tableName), $duplicateFields);
770 $select->where($this->_db->quoteIdentifier($this->_tableName . '.is_deleted') . ' = 0');
772 $this->_addFilter($select, $filter);
774 $select->group($duplicateFields)
775 ->having('count(*) > 1');
777 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
780 $rows = $this->_fetch($select, self::FETCH_ALL);
782 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
783 . ' ' . print_r($rows, TRUE));
786 foreach ($rows as $row) {
787 $index = $row['summary'] . ' / ' . $row['dtstart'] . ' - ' . $row['dtend'];
789 $filter = new Calendar_Model_EventFilter(array(array(
790 'field' => 'summary',
791 'operator' => 'equals',
792 'value' => $row['summary'],
794 'field' => 'dtstart',
795 'operator' => 'equals',
796 'value' => new Tinebase_DateTime($row['dtstart']),
799 'operator' => 'equals',
800 'value' => new Tinebase_DateTime($row['dtend']),
802 $pagination = new Tinebase_Model_Pagination(array('sort' => array($this->_tableName . '.last_modified_time', $this->_tableName . '.creation_time')));
804 $select = $this->_db->select();
805 $select->from(array($this->_tableName => $this->_tablePrefix . $this->_tableName));
806 $select->where($this->_db->quoteIdentifier($this->_tableName . '.is_deleted') . ' = 0');
808 $this->_addFilter($select, $filter);
809 $pagination->appendPaginationSql($select);
811 $rows = $this->_fetch($select, self::FETCH_ALL);
812 $events = $this->_rawDataToRecordSet($rows);
814 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
815 . ' ' . print_r($events->toArray(), TRUE));
817 $deleteIds = $events->getArrayOfIds();
819 array_shift($deleteIds);
821 if (! empty($deleteIds)) {
822 $deleteContainerIds = ($events->container_id);
823 $origContainer = array_shift($deleteContainerIds);
824 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
825 . ' Deleting ' . count($deleteIds) . ' duplicates of: ' . $index . ' in container_ids ' . implode(',', $deleteContainerIds) . ' (origin container: ' . $origContainer . ')');
826 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
827 . ' ' . print_r($deleteIds, TRUE));
829 $toDelete = array_merge($toDelete, $deleteIds);
831 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
832 . ' No duplicates found for ' . $index);
836 if (empty($toDelete)) {
837 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
838 . ' No duplicates found.');
841 $result = ($dryrun) ? count($toDelete) : $this->delete($toDelete);
848 * repair dangling attendee records (no displaycontainer_id)
850 * @see https://forge.tine20.org/mantisbt/view.php?id=8172
852 public function repairDanglingDisplaycontainerEvents()
854 $filter = new Tinebase_Model_Filter_FilterGroup();
855 $filter->addFilter(new Tinebase_Model_Filter_Text(array(
856 'field' => 'user_type',
859 Calendar_Model_Attender::USERTYPE_USER,
860 Calendar_Model_Attender::USERTYPE_GROUPMEMBER,
861 Calendar_Model_Attender::USERTYPE_RESOURCE
865 $filter->addFilter(new Tinebase_Model_Filter_Text(array(
866 'field' => 'displaycontainer_id',
867 'operator' => 'isnull',
871 $danglingAttendee = $this->_attendeeBackend->search($filter);
872 $danglingContactAttendee = $danglingAttendee->filter('user_type', '/'. Calendar_Model_Attender::USERTYPE_USER . '|'. Calendar_Model_Attender::USERTYPE_GROUPMEMBER .'/', TRUE);
873 $danglingContactIds = array_unique($danglingContactAttendee->user_id);
874 $danglingContacts = Addressbook_Controller_Contact::getInstance()->getMultiple($danglingContactIds, TRUE);
875 $danglingResourceAttendee = $danglingAttendee->filter('user_type', Calendar_Model_Attender::USERTYPE_RESOURCE);
876 $danglingResourceIds = array_unique($danglingResourceAttendee->user_id);
877 Calendar_Controller_Resource::getInstance()->doContainerACLChecks(false);
878 $danglingResources = Calendar_Controller_Resource::getInstance()->getMultiple($danglingResourceIds, TRUE);
880 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
881 . ' Processing ' . count($danglingContactIds) . ' dangling contact ids...');
883 foreach ($danglingContactIds as $danglingContactId) {
884 $danglingContact = $danglingContacts->getById($danglingContactId);
885 if ($danglingContact && $danglingContact->account_id) {
887 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
888 . ' Get default display container for account ' . $danglingContact->account_id);
889 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
890 . ' ' . print_r($danglingContact->toArray(), true));
892 $displayCalId = Calendar_Controller_Event::getDefaultDisplayContainerId($danglingContact->account_id);
894 // finaly repair attendee records
895 $attendeeRecords = $danglingContactAttendee->filter('user_id', $danglingContactId);
896 $this->_attendeeBackend->updateMultiple($attendeeRecords->getId(), array('displaycontainer_id' => $displayCalId));
897 Tinebase_Core::getLogger()->NOTICE(__METHOD__ . '::' . __LINE__ . " repaired the following contact attendee " . print_r($attendeeRecords->toArray(), TRUE));
902 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
903 . ' Processing ' . count($danglingResourceIds) . ' dangling resource ids...');
905 foreach ($danglingResourceIds as $danglingResourceId) {
906 $resource = $danglingResources->getById($danglingResourceId);
907 if ($resource && $resource->container_id) {
908 $displayCalId = $resource->container_id;
909 $attendeeRecords = $danglingResourceAttendee->filter('user_id', $danglingResourceId);
910 $this->_attendeeBackend->updateMultiple($attendeeRecords->getId(), array('displaycontainer_id' => $displayCalId));
911 Tinebase_Core::getLogger()->NOTICE(__METHOD__ . '::' . __LINE__ . " repaired the following resource attendee " . print_r($attendeeRecords->toArray(), TRUE));