1<?php 2 3class goCalendar extends GoBaseBackendDiff { 4 5 6 public function DeleteMessage($folderid, $id, $contentparameters) { 7 ZLog::Write(LOGLEVEL_DEBUG, 'goCalendar->DeleteMessage('.$folderid.','.$id.')'); 8 9 $event = \GO\Calendar\Model\Event::model()->findByPk($id); 10 11// if(!$event->is_organizer) { 12// 13// //iphone uses delete to decline! 14// if($this->MeetingResponse($id, "a/GroupOfficeCalendar", 4)) { 15// return true; 16// } 17// } 18 19 // Only delete from GO when you have the right permissions for it. 20 if ($event && $event->checkPermissionLevel(\GO\Base\Model\Acl::DELETE_PERMISSION)) { 21 //HTC deletes old appointments. We don't like that so we refuse to delete appointments older then 7 days. 22 if($event->start_time<\GO\Base\Util\Date::date_add(time(), -7)){ 23 return true; 24 } else { 25 return $event->delete(); 26 } 27 } else { 28 return true; 29 } 30 } 31 32 /** 33 * Get the item object that needs to be synced to the phone. 34 * This information will be send to the phone. 35 * 36 * Direction: SERVER -> PHONE 37 * 38 * @param StringHelper $folderid 39 * @param int $id 40 * @param array $contentparameters 41 * @return \SyncAppointment 42 */ 43 public function GetMessage($folderid, $id, $contentparameters) { 44 $event = \GO\Calendar\Model\Event::model()->findByPk($id); 45 if($event) 46 return $this->_handleEvent($event,$contentparameters); 47 else 48 return false; 49 } 50 51 /** 52 * Handle the event request 53 * 54 * Direction: SERVER -> PHONE 55 * 56 * @param \GO\Calendar\Model\Event $event 57 * @return \SyncAppointment 58 */ 59 private function _handleEvent($event,$contentparameters, $exception=false) { 60 $message = $exception ? new SyncAppointmentException() : new SyncAppointment(); 61 62 $message->timezone = GoSyncUtils::getTimeZoneForClient(); //xP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAFAAEAAAAAAAAAxP///w=="; 63 $message->starttime = $event->start_time; 64 $message->subject = $event->name; 65 $message->uid = $event->uuid; 66 $message->location = $event->location; 67 $message->endtime = $event->end_time; 68 69 if($event->all_day_event) { 70 //correction because GO saves till 23:59 71 $message->endtime += 60; 72 } 73 74 $message->busystatus = $event->busy == 1 ? "2" : "0"; 75 76 if(!$event->is_organizer) 77 { 78 $participant = GO\Calendar\Model\Participant::model()->findSingleByAttributes(array('event_id' => $event->id, 'user_id' => GO::user()->id)); 79 80 if($participant) { 81 //iphone uses busy status for events. 82 switch($participant->status) { 83 case GO\Calendar\Model\Participant::STATUS_ACCEPTED: 84 $message->busystatus = 2; 85 break; 86 case GO\Calendar\Model\Participant::STATUS_TENTATIVE: 87 $message->busystatus = 1; 88 break; 89 case GO\Calendar\Model\Participant::STATUS_DECLINED: 90 $message->busystatus = 3; 91 break; 92 } 93 94 } 95 } 96 97 $bpReturnType = GoSyncUtils::getBodyPreferenceMatch($contentparameters->GetBodyPreference()); 98 99 if (Request::GetProtocolVersion() >= 12.0) { 100 $message->asbody = GoSyncUtils::createASBodyForMessage($event,'description',$bpReturnType); 101 } else { 102 $message->body = \GO\Base\Util\StringHelper::normalizeCrlf($event->description); 103 $message->bodysize = strlen($message->body); 104 $message->bodytruncated = 0; 105 } 106 107// $message->sensitivity; 108// $message->deleted; 109// $message->categories; 110 $message->dtstamp = $event->ctime; 111// $message->rtf; 112 // AS 12.0 props 113// $message->nativebodytype; 114 // AS 14.0 props 115// $message->disallownewtimeprop; 116// $message->responsetype; 117// $message->responserequested; 118 119 $message->alldayevent = $event->all_day_event; 120 121 if (!empty($event->rrule)) 122 $message->recurrence = GoSyncUtils::exportRecurrence($event); 123 124 if ($event->reminder !== null){ 125 126 if(empty($event->reminder)){ // 0 127 $message->reminder = 0; 128 } else { 129 $message->reminder = $event->reminder / 60; 130 } 131 } 132 133 $exceptions = $this->_handleExceptions($event,$contentparameters); 134 if ($exceptions !== false) 135 $message->exceptions = $exceptions; 136 137 $this->_handleParticipants($event, $message); 138 139 //ZLog::Write(LOGLEVEL_DEBUG, 'MESSAGE '.var_export($message,true)); 140 return $message; 141 } 142 143 private function _handleExceptions($event,$contentparameters) { 144 $stmt = $event->exceptions(); 145 $exceptions = false; 146 147 if ($stmt->rowCount() > 0) { 148 149 Zlog::Write(LOGLEVEL_DEBUG, "Found exceptions"); 150 $exceptions = array(); 151 while ($exception = $stmt->fetch()) { 152 if ($exception->event) { 153 $exceptionEvent = $exception->event; 154 155 $xcp = $this->_handleEvent($exceptionEvent,$contentparameters, true); 156 $xcp->exceptionstarttime = $exception->getStartTime(); 157 $exceptions[] = $xcp; 158 } else { 159 160 $xcp = new SyncAppointmentException(); 161 $xcp->exceptionstarttime = $exception->getStartTime(); 162 $xcp->deleted = 1; 163 $exceptions[] = $xcp; 164 } 165 } 166 } 167 168 return $exceptions; 169 } 170 171 /** 172 * 173 * Direction: SERVER -> PHONE 174 * 175 * @param \GO\Calendar\Model\Event $event 176 * @param type $message 177 */ 178 private function _handleParticipants($event, &$message) { 179 // Status 0 = no meeting, status 1 = organizer, status 2/3/4/5 = tentative/accepted/declined/notresponded 180 $message->meetingstatus = 0; 181 182 $organizer = $event->getOrganizer(); 183// ZLog::Write(LOGLEVEL_DEBUG, '[SERVER -> PHONE]'.var_export($organizer,true)); 184 if(!empty($organizer)) { 185 $message->organizername = $organizer->name; 186 $message->organizeremail = $organizer->email; 187 ZLog::Write(LOGLEVEL_DEBUG, '[SERVER -> PHONE] ORGANIZER '.$organizer->name.' : '.$organizer->email); 188 189 $stmt = $event->participants(); 190 ZLog::Write(LOGLEVEL_DEBUG, '[SERVER -> PHONE] _handleParticipants rows:'.$stmt->rowCount()); 191 if ($stmt->rowCount() > 0) { 192 193 194 $message->responsetype = $event->is_organizer ? 1 : 0; 195 196 if($event->status == \GO\Calendar\Model\Event::STATUS_CANCELLED) { 197 $message->meetingstatus = $event->is_organizer ? 5 : 7; 198 }else 199 { 200 $message->meetingstatus = $event->is_organizer ? 1 : 3; 201 } 202 203 while ($participant = $stmt->fetch()) { 204 ZLog::Write(LOGLEVEL_DEBUG, '[SERVER -> PHONE] PARTICIPANT'.$participant->is_organizer?'(organizer)':''.' '.$participant->name.' : '.$participant->email); 205 if (!$participant->is_organizer) { 206 207 208 209 $att = new SyncAttendee(); 210 $att->name = $participant->name; 211 $att->email = $participant->email; 212 $att->attendeetype = 1; //not supported by z-push 1 213 switch ($participant->status) { //not supported by z-push 1 214 case \GO\Calendar\Model\Participant::STATUS_ACCEPTED: 215 $att->attendeestatus = 3; 216 break; 217 case \GO\Calendar\Model\Participant::STATUS_DECLINED: 218 $att->attendeestatus = 4; 219 break; 220 case \GO\Calendar\Model\Participant::STATUS_TENTATIVE: 221 $att->attendeestatus = 2; 222 break; 223 default: 224 $att->attendeestatus = 0; 225 break; 226 } 227 $message->attendees[] = $att; 228 229 if($participant->user_id == GO::user()->id) { 230 $message->responsetype = $att->attendeestatus; 231 } 232 } 233 } 234 } 235 } 236 } 237 238 /** 239 * Handle the participants of the incoming appointment 240 * 241 * Direction: PHONE -> SERVER 242 * 243 * @param \SyncAppointment $message 244 * @param \GO\Calendar\Model\Event $event 245 */ 246 private function _handleAppointmentParticipants(SyncAppointment $message, $event) { 247 248 // Remove existing participants 249 // this function is not called on updates because it's unreliable 250 if (isset($message->attendees)) { 251 $stmt = $event->participants(); 252 253 $existingParticipants = array(); 254 $hasOrganizer=false; 255 foreach($stmt as $participant){ 256 if($participant->is_organizer){ 257 $hasOrganizer = true; 258 }else 259 { 260 $existingParticipants[$participant->email]=$participant; 261 } 262 } 263 264 ///ZLog::Write(LOGLEVEL_DEBUG, '[PHONE -> SERVER] goCalendar->handleAppointmentParticipants('. var_export($message,true).','. var_export($event,true).')'); 265 266 267 268 if(isset($message->organizeremail)){ 269 270 if(!$hasOrganizer){ 271 $organizer = $event->getOrganizer(); 272 273 if($organizer){ 274 275 $organizer->email = $message->organizeremail; 276 277 if(isset($message->organizername)) 278 $organizer->name = $message->organizername; 279 280 } else { 281 282 $organizer = new \GO\Calendar\Model\Participant(); 283 284 $organizer->email = $message->organizeremail; 285 $organizer->is_organizer = true; 286 287 if(isset($message->organizername)) 288 $organizer->name = $message->organizername; 289 290 if(!$event->addParticipant($organizer)) 291 ZLog::Write(LOGLEVEL_ERROR, '[PHONE -> SERVER] Could not add the organizer('.$message->organizeremail.') to the event('.$event->name.')!'); 292 } 293 } 294 }elseif(!$hasOrganizer){ 295 $organizer = $event->getDefaultOrganizerParticipant(); 296 $organizer->save(); 297 } 298 299 foreach ($message->attendees as $attendee) { 300 301 302 if(isset($existingParticipants[$attendee->email])){ 303 $participant = $existingParticipants[$attendee->email]; 304 unset($existingParticipants[$attendee->email]); 305 } else { 306 $participant = new \GO\Calendar\Model\Participant(); 307 } 308 309 $participant->event_id = $event->id; 310 $participant->email = $attendee->email; 311 $participant->name = $attendee->name; 312 $participant->status = \GO\Calendar\Model\Participant::STATUS_PENDING; 313 314 if(isset($attendee->attendeestatus)) { 315 switch($attendee->attendeestatus) { 316 case 2: 317 $participant->status = \GO\Calendar\Model\Participant::STATUS_TENTATIVE; 318 break; 319 case 3: 320 $participant->status = \GO\Calendar\Model\Participant::STATUS_ACCEPTED; 321 break; 322 case 4: 323 $participant->status = \GO\Calendar\Model\Participant::STATUS_DECLINED; 324 break; 325 } 326 } 327 ZLog::Write(LOGLEVEL_DEBUG, '[PHONE -> SERVER] PARTICIPANT '.$participant->name.' : '.$participant->email); 328 $success = $participant->save(); 329 ZLog::Write(LOGLEVEL_DEBUG, '[PHONE -> SERVER] PARTICIPANT SAVE ~~ '.$success?'OK':'ERROR'); 330 } 331 332 333 foreach($existingParticipants as $notIncludedParticipant){ 334 335 ZLog::Write(LOGLEVEL_DEBUG, "DELETE participant: ".$notIncludedParticipant->email); 336 $notIncludedParticipant->delete(); 337 } 338 } 339 } 340 341 private $timezone; 342 private function getDefaultTimeZone() { 343 if(!isset($this->timezone)) { 344 $this->timezone = go()->getAuthState()->getUser(['timezone'])->timezone; 345 } 346 return $this->timezone; 347 } 348 349 private function importAllDayTime($time) { 350 $dt = new \DateTime('@'.$time, new \DateTimeZone("UTC")); 351 $dt->setTimezone(new \DateTimeZone($this->getDefaultTimeZone())); 352 $dt->setTime(0, 0); 353 $newTime = $dt->format("U"); 354 355 return $newTime; 356 } 357 358 /** 359 * 360 * Direction: PHONE -> SERVER 361 * 362 * @param \SyncAppointment $message 363 * @param \GO\Calendar\Model\Event $event 364 * @return type 365 */ 366 private function _handleAppointment($message, $event) { 367 ZLog::Write(LOGLEVEL_DEBUG, 'goCalendar->_handleAppointment() MESSAGE ~~~ '); 368 369 370// $message->timezone; 371// $message->dtstamp; 372// $message->sensitivity; 373// $message->rtf; 374// $message->meetingstatus; 375// $message->attendees; 376// $message->bodytruncated; 377// $message->exception; 378// $message->deleted; 379// $message->exceptionstarttime; 380// $message->categories; 381// 382// // AS 12.0 props 383// $message->nativebodytype; 384// 385// // AS 14.0 props 386// $message->disallownewtimeprop; 387// $message->responsetype; 388// $message->responserequested; 389 390 if (isset($message->uid)) 391 $event->uuid = $message->uid; 392 if (isset($message->starttime)) { 393 $event->start_time = $message->starttime; 394 if($message->alldayevent) { 395 $event->start_time = $this->importAllDayTime($event->start_time); 396 } 397 } 398 if (isset($message->endtime)){ 399 $event->end_time = $message->endtime; 400 if($message->alldayevent) { 401 $event->end_time = $this->importAllDayTime($event->end_time); 402 } 403 } 404 if (isset($message->location)) 405 $event->location = $message->location; 406 if (isset($message->reminder)) 407 $event->reminder = $message->reminder * 60; 408 if (isset($message->busystatus)) 409 $event->busy = !empty($message->busystatus) ? 2 : 0; 410 if (isset($message->sensitivity)) 411 $event->private = empty($message->sensitivity) ? 0 : 1; 412 if (!empty($message->alldayevent)) { 413 $event->end_time -= 60; 414 } 415 $event->all_day_event = $message->alldayevent; 416 $event->name = !empty($message->subject) ? $message->subject : "No subject"; 417 418 $event->description = GoSyncUtils::getBodyFromMessage($message); 419 420 if (isset($message->recurrence)) { 421 if (!empty($message->recurrence->until)) 422 $event->repeat_end_time = $message->recurrence->until; 423 $event->rrule = GoSyncUtils::importRecurrence($message->recurrence, $event->start_time); 424 } 425 426 $new = $event->isNew; 427 428 $event->cutAttributeLengths(); 429 if(!$event->save()){ 430 ZLog::Write(LOGLEVEL_WARN, 'ZPUSH2EVENT::Could not save ' . $event->id); 431 ZLog::Write(LOGLEVEL_WARN, var_export($event->getValidationErrors(), true)); 432 return false; 433 } 434 435 $event->exceptions()->callOnEach('delete'); 436 $event->exceptionEvents()->callOnEach('delete'); 437 438 439 440 //don't update existing participants because it is unreliable data from the phone 441 if($event->is_organizer) 442 $this->_handleAppointmentParticipants($message, $event); 443 else 444 { 445 //iphone sends busy status for tentative 446 if($message->busystatus == 1 || $message->busystatus == 2) //tentative 447 { 448 $this->MeetingResponse($event->id, 'a/GroupOfficeCalendar', $message->busystatus == 1 ? 2 : 3); 449 } 450 } 451 452 if (isset($message->exceptions)) { 453 foreach ($message->exceptions as $k => $v) { 454 if (!$v->deleted) { 455 $e = $event->createExceptionEvent($v->exceptionstarttime, array(), true); 456 $e->calendar_id = $event->calendar_id; 457 $e->exception_for_event_id = $event->id; 458 $e->uuid = $event->uuid; 459 // Recursive add the appointment exceptions 460 ZLog::Write(LOGLEVEL_DEBUG, "Creating exception"); 461 $e = $this->_handleAppointment($v, $e); 462 } else { 463 $event->createException($v->exceptionstarttime); 464 } 465 } 466 } 467 return $event; 468 } 469 470 /** 471 * Save the information from the phone to Group-Office. 472 * 473 * Direction: PHONE -> SERVER 474 * 475 * @param StringHelper $folderid 476 * @param int $id 477 * @param \SyncAppointment $message 478 * @return array 479 */ 480 public function ChangeMessage($folderid, $id, $message,$contentParameters) { 481 ZLog::Write(LOGLEVEL_DEBUG, 'goCalendar->ChangeMessage('.$folderid.','.$id.',)'); 482 try { 483 484 $event = \GO\Calendar\Model\Event::model()->findByPk($id); 485 486 if ($event) { 487 if ($event->permissionLevel < \GO\Base\Model\Acl::WRITE_PERMISSION) { 488 ZLog::Write(LOGLEVEL_DEBUG, "Skipping update of read-only event " . $event->name); 489 return $this->StatMessage($folderid, $id); 490 } 491 } else { 492 $calendar = GoSyncUtils::getUserSettings()->getDefaultCalendar(); 493 494 if (!$calendar) 495 throw new \Exception("FATAL: No default calendar configured"); 496 497 $event = new \GO\Calendar\Model\Event(); 498 $event->calendar_id = $calendar->id; 499 } 500 501 $event = $this->_handleAppointment($message, $event); 502 if(!$event) 503 return false; 504 505 $id = $event->id; 506 } catch (\Exception $e) { 507 ZLog::Write(LOGLEVEL_FATAL, 'ZPUSH2CALENDAR::EXCEPTION ~~ ' . (string) $e); 508 } 509 510 return $this->StatMessage($folderid, $id); 511 } 512 513 /** 514 * Get the status of an item 515 * 516 * @param StringHelper $folderid 517 * @param int $id 518 * @return array 519 */ 520 public function StatMessage($folderid, $id) { 521 522 $event = \GO\Calendar\Model\Event::model()->findByPk($id); 523 $stat = false; 524 if ($event) { 525 $stat = array(); 526 $stat["id"] = $event->id; 527 $stat["flags"] = 1; 528 $stat["mod"] = $event->mtime; 529 } 530 531 return $stat; 532 } 533 534 535 /** 536 * Processes a response to a meeting request. 537 * CalendarID is a reference and has to be set if a new calendar item is created 538 * 539 * @param string $requestid id of the object containing the request 540 * @param string $folderid id of the parent folder of $requestid 541 * @param string $response 542 * 543 * @access public 544 * @return string id of the created/updated calendar obj 545 * @throws StatusException 546 */ 547 public function MeetingResponse($requestid, $folderid, $response) { 548 549 ZLog::Write(LOGLEVEL_DEBUG, 'goCalendar->MeetingResponse('.$requestid.', '.$folderid.', '.$response.')'); 550 551 $event = \GO\Calendar\Model\Event::model()->findByPk($requestid); 552 553 $participant = GO\Calendar\Model\Participant::model()->findSingleByAttributes(array('user_id' => GO::user()->id, 'event_id' => $requestid)); 554 if(!$participant) { 555 throw new StatusException("Participant not found!"); 556 } 557 558 switch($response) { 559 case 2: 560 $participant->status = \GO\Calendar\Model\Participant::STATUS_TENTATIVE; 561 break; 562 case 1: //??? 563 case 3: 564 $participant->status = \GO\Calendar\Model\Participant::STATUS_ACCEPTED; 565 break; 566 case 4: 567 $participant->status = \GO\Calendar\Model\Participant::STATUS_DECLINED; 568 break; 569 } 570 571 if(!$participant->save(false)) { 572 throw new StatusException("Failed to save participant"); 573 } 574 575 ZLog::Write(LOGLEVEL_DEBUG, 'Participant '.$participant->id.' set to status '.$participant->status); 576 577 return $requestid; 578 } 579 580 /** 581 * Get the list of the items that need to be synced 582 * 583 * @param StringHelper $folderid 584 * @param type $cutoffdate 585 * @return array 586 */ 587 public function GetMessageList($folderid, $cutoffdate) { 588 589 $messages = array(); 590 if (!\GO::modules()->calendar) { 591 return $messages; 592 } 593 594 $params = \GO\Base\Db\FindParams::newInstance() 595 ->ignoreAcl() 596 ->select('t.id,t.mtime,t.private,t.calendar_id') 597 ->joinModel(array( 598 'model' => 'GO\Sync\Model\UserCalendar', 599 'tableAlias' => 'ua', 600 'localTableAlias' => 't', 601 'localField' => 'calendar_id', 602 'foreignField' => 'calendar_id' 603 )) 604 ->criteria( 605 \GO\Base\Db\FindCriteria::newInstance() 606 ->addCondition('user_id', \GO::user()->id, '=', 'ua') 607 ->addCondition('exception_for_event_id', 0) 608 ); 609 610 if (!empty($cutoffdate)) { 611 ZLog::Write(LOGLEVEL_DEBUG, 'Client sent cutoff date for calendar: ' . \GO\Base\Util\Date::get_timestamp($cutoffdate)); 612 613 $params->getCriteria()->mergeWith(\GO\Base\Db\FindCriteria::newInstance() 614 ->addCondition('end_time', $cutoffdate, '>=') 615 ->mergeWith( 616 \GO\Base\Db\FindCriteria::newInstance() 617 ->addCondition('rrule', '', '!=') 618 ->mergeWith( 619 \GO\Base\Db\FindCriteria::newInstance() 620 ->addCondition('repeat_end_time', 0) 621 ->addCondition('repeat_end_time', $cutoffdate, '>=', 't', false)) 622 , false) 623 ); 624 } 625 626 $stmt = \GO\Calendar\Model\Event::model()->find($params); 627 628 while ($event = $stmt->fetch()) { 629 630 if(!$event->private || $event->calendar->user_id == \GO::user()->id){ 631 $message = array(); 632 $message['id'] = $event->id; 633 $message['mod'] = $event->mtime; 634 $message['flags'] = 1; 635 $messages[] = $message; 636 } 637 } 638 639 return $messages; 640 } 641 642 /** 643 * Get the syncFolder that is attached to the given id 644 * 645 * @param StringHelper $id 646 * @return \SyncFolder 647 */ 648 public function GetFolder($id) { 649 650 if ($id != BackendGoConfig::CALENDARBACKENDFOLDER) { 651 return false; 652 } 653 654 $folder = new SyncFolder(); 655 $folder->serverid = $id; 656 $folder->parentid = "0"; 657 $folder->displayname = 'Calendar'; 658 $folder->type = SYNC_FOLDER_TYPE_APPOINTMENT; 659 660 return $folder; 661 } 662 663 /** 664 * Get a list of folders that are located in the current folder 665 * 666 * @return array 667 */ 668 public function GetFolderList() { 669 $folders = array(); 670 $folder = $this->StatFolder(BackendGoConfig::CALENDARBACKENDFOLDER); 671 $folders[] = $folder; 672 673 return $folders; 674 } 675 676 677 public function getNotification($folder=null) { 678 679 680 $params = \GO\Base\Db\FindParams::newInstance() 681 ->ignoreAcl()->debugSql() 682 ->single(true, true) 683 ->select('count(*) AS count, max(mtime) AS lastmtime') 684 ->join(\GO\Sync\Model\UserCalendar::model()->tableName(), \GO\Base\Db\FindCriteria::newInstance() 685 ->addCondition('calendar_id', 's.calendar_id', '=', 't', true, true) 686 ->addCondition('user_id', \GO::user()->id, '=', 's') 687 , 's'); 688 689 690 $record = \GO\Calendar\Model\Event::model()->find($params); 691 692 $lastmtime = isset($record->lastmtime) ? $record->lastmtime : 0; 693 $newstate = 'M'.$lastmtime.':C'.$record->count; 694 695 ZLog::Write(LOGLEVEL_DEBUG,'goCalendar->getNotification() State: '.$newstate); 696 697 return $newstate; 698 } 699 700} 701