1<?php 2/** 3 * Defines the AJAX actions used in Kronolith. 4 * 5 * Copyright 2012-2017 Horde LLC (http://www.horde.org/) 6 * 7 * See the enclosed file COPYING for license information (GPL). If you 8 * did not receive this file, see http://www.horde.org/licenses/gpl. 9 * 10 * @author Michael Slusarz <slusarz@horde.org> 11 * @author Jan Schneider <jan@horde.org> 12 * @author Gonçalo Queirós <mail@goncaloqueiros.net> 13 * @category Horde 14 * @license http://www.horde.org/licenses/gpl GPL 15 * @package Kronolith 16 */ 17class Kronolith_Ajax_Application_Handler extends Horde_Core_Ajax_Application_Handler 18{ 19 protected $_external = array('embed'); 20 21 /** 22 * Just polls for alarm messages and keeps session fresh for now. 23 */ 24 public function poll() 25 { 26 return false; 27 } 28 29 /** 30 * Returns a list of all calendars. 31 */ 32 public function listCalendars() 33 { 34 Kronolith::initialize(); 35 $all_external_calendars = $GLOBALS['calendar_manager']->get(Kronolith::ALL_EXTERNAL_CALENDARS); 36 $result = new stdClass; 37 $auth_name = $GLOBALS['registry']->getAuth(); 38 39 // Calendars. Do some twisting to sort own calendar before shared 40 // calendars. 41 foreach (array(true, false) as $my) { 42 foreach ($GLOBALS['calendar_manager']->get(Kronolith::ALL_CALENDARS) as $id => $calendar) { 43 $owner = ($auth_name && ($calendar->owner() == $auth_name)); 44 if (($my && $owner) || (!$my && !$owner)) { 45 $result->calendars['internal'][$id] = $calendar->toHash(); 46 } 47 } 48 49 // Tasklists 50 if (Kronolith::hasApiPermission('tasks')) { 51 foreach ($GLOBALS['registry']->tasks->listTasklists($my, Horde_Perms::SHOW, false) as $id => $tasklist) { 52 if (isset($all_external_calendars['tasks/' . $id])) { 53 $owner = ($auth_name && 54 ($tasklist->get('owner') == $auth_name)); 55 if (($my && $owner) || (!$my && !$owner)) { 56 $result->calendars['tasklists']['tasks/' . $id] = 57 $all_external_calendars['tasks/' . $id]->toHash(); 58 } 59 } 60 } 61 } 62 } 63 64 // Resources 65 if (!empty($GLOBALS['conf']['resource']['driver'])) { 66 foreach (Kronolith::getDriver('Resource')->listResources() as $resource) { 67 if ($resource->get('type') != Kronolith_Resource::TYPE_GROUP) { 68 $rcal = new Kronolith_Calendar_Resource(array( 69 'resource' => $resource 70 )); 71 $result->calendars['resource'][$resource->get('calendar')] = $rcal->toHash(); 72 } else { 73 $rcal = new Kronolith_Calendar_ResourceGroup(array( 74 'resource' => $resource 75 )); 76 $result->calendars['resourcegroup'][$resource->getId()] = $rcal->toHash(); 77 } 78 } 79 } 80 81 // Timeobjects 82 foreach ($all_external_calendars as $id => $calendar) { 83 if ($calendar->api() != 'tasks' && $calendar->display()) { 84 $result->calendars['external'][$id] = $calendar->toHash(); 85 } 86 } 87 88 // Remote calendars 89 foreach ($GLOBALS['calendar_manager']->get(Kronolith::ALL_REMOTE_CALENDARS) as $url => $calendar) { 90 $result->calendars['remote'][$url] = $calendar->toHash(); 91 } 92 93 // Holidays 94 foreach ($GLOBALS['calendar_manager']->get(Kronolith::ALL_HOLIDAYS) as $id => $calendar) { 95 $result->calendars['holiday'][$id] = $calendar->toHash(); 96 } 97 98 return $result; 99 } 100 101 /** 102 * TODO 103 */ 104 public function listEvents() 105 { 106 global $session; 107 108 $start = new Horde_Date($this->vars->start); 109 $end = new Horde_Date($this->vars->end); 110 $result = $this->_signedResponse($this->vars->cal); 111 if (!($kronolith_driver = $this->_getDriver($this->vars->cal))) { 112 return $result; 113 } 114 try { 115 $session->close(); 116 $events = $kronolith_driver->listEvents($start, $end, array( 117 'show_recurrence' => true, 118 'json' => true) 119 ); 120 $session->start(); 121 if (count($events)) { 122 $result->events = $events; 123 } 124 } catch (Exception $e) { 125 $session->start(); 126 $GLOBALS['notification']->push($e, 'horde.error'); 127 } 128 return $result; 129 } 130 131 /** 132 * Returns a JSON object representing the requested event. 133 * 134 * Request variables used: 135 * - cal: The calendar id 136 * - id: The event id 137 * - date: The date of the event we are requesting [OPTIONAL] 138 * - rsd: The event start date of the instance of a recurring event, if 139 * requesting a specific instance. 140 * - red: The event end date of the instance of a recurring event, if 141 * requesting a specific instance. 142 */ 143 public function getEvent() 144 { 145 $result = new stdClass; 146 147 if (!($kronolith_driver = $this->_getDriver($this->vars->cal)) || 148 !isset($this->vars->id)) { 149 return $result; 150 } 151 152 try { 153 $event = $kronolith_driver->getEvent($this->vars->id, $this->vars->date); 154 $event->setTimezone(true); 155 $result->event = $event->toJson(null, true, $GLOBALS['prefs']->getValue('twentyFour') ? 'H:i' : 'h:i A'); 156 // If recurring, we need to format the dates of this instance, since 157 // Kronolith_Driver#getEvent will return the start/end dates of the 158 // original event in the series. 159 if ($event->recurs() && $this->vars->rsd) { 160 $rs = new Horde_Date($this->vars->rsd); 161 $result->event->rsd = $rs->strftime('%x'); 162 $re = new Horde_Date($this->vars->red); 163 $result->event->red = $re->strftime('%x'); 164 } 165 } catch (Horde_Exception_NotFound $e) { 166 $GLOBALS['notification']->push(_("The requested event was not found."), 'horde.error'); 167 } catch (Exception $e) { 168 $GLOBALS['notification']->push($e, 'horde.error'); 169 } 170 171 return $result; 172 } 173 174 /** 175 * Save a new or update an existing event from the AJAX event detail view. 176 * 177 * Request parameters used: 178 * - event: The event id. 179 * - cal: The calendar id. 180 * - targetcalendar: If moving events, the targetcalendar to move to. 181 * - as_new: Save an existing event as a new event. 182 * - recur_edit: If editing an instance of a recurring event series, 183 * how to apply the edit [current|future|all]. 184 * - rstart: If editing an instance of a recurring event series, 185 * the original start datetime of this instance. 186 * - rend: If editing an instance of a recurring event series, 187 * the original ending datetime of this instance. 188 * - sendupdates: Should updates be sent to attendees? 189 * - cstart: Start time of the client cache. 190 * - cend: End time of the client cache. 191 */ 192 public function saveEvent() 193 { 194 global $injector, $notification, $registry; 195 196 $result = $this->_signedResponse($this->vars->targetcalendar); 197 198 if (!($kronolith_driver = $this->_getDriver($this->vars->targetcalendar))) { 199 return $result; 200 } 201 202 if ($this->vars->as_new) { 203 unset($this->vars->event); 204 } 205 if (!$this->vars->event) { 206 $perms = $injector->getInstance('Horde_Core_Perms'); 207 if ($perms->hasAppPermission('max_events') !== true && 208 $perms->hasAppPermission('max_events') <= Kronolith::countEvents()) { 209 Horde::permissionDeniedError( 210 'kronolith', 211 'max_events', 212 sprintf( 213 _("You are not allowed to create more than %d events."), 214 $perms->hasAppPermission('max_events') 215 ) 216 ); 217 return $result; 218 } 219 } 220 221 if ($this->vars->event && 222 $this->vars->cal && 223 $this->vars->cal != $this->vars->targetcalendar) { 224 if (strpos($kronolith_driver->calendar, '\\')) { 225 list($target, $user) = explode( 226 '\\', $kronolith_driver->calendar, 2 227 ); 228 } else { 229 $target = $kronolith_driver->calendar; 230 $user = $registry->getAuth(); 231 } 232 $kronolith_driver = $this->_getDriver($this->vars->cal); 233 // Only delete the event from the source calendar if this user has 234 // permissions to do so. 235 try { 236 $sourceShare = Kronolith::getInternalCalendar( 237 $kronolith_driver->calendar 238 ); 239 $share = Kronolith::getInternalCalendar($target); 240 if ($sourceShare->hasPermission($registry->getAuth(), Horde_Perms::DELETE) && 241 (($user == $registry->getAuth() && 242 $share->hasPermission($registry->getAuth(), Horde_Perms::EDIT)) || 243 ($user != $registry->getAuth() && 244 $share->hasPermission($registry->getAuth(), Kronolith::PERMS_DELEGATE)))) { 245 $kronolith_driver->move($this->vars->event, $target); 246 $kronolith_driver = $this->_getDriver($this->vars->targetcalendar); 247 } 248 } catch (Exception $e) { 249 $notification->push( 250 sprintf( 251 _("There was an error moving the event: %s"), 252 $e->getMessage() 253 ), 254 'horde.error' 255 ); 256 return $result; 257 } 258 } 259 260 if ($this->vars->as_new) { 261 $event = $kronolith_driver->getEvent(); 262 } else { 263 try { 264 $event = $kronolith_driver->getEvent($this->vars->event); 265 } catch (Horde_Exception_NotFound $e) { 266 $notification->push( 267 _("The requested event was not found."), 268 'horde.error' 269 ); 270 return $result; 271 } catch (Exception $e) { 272 $notification->push($e); 273 return $result; 274 } 275 } 276 277 if (!$event->hasPermission(Horde_Perms::EDIT)) { 278 $notification->push( 279 _("You do not have permission to edit this event."), 280 'horde.warning' 281 ); 282 return $result; 283 } 284 285 $removed_attendees = $old_attendees = array(); 286 if ($this->vars->recur_edit && $this->vars->recur_edit != 'all') { 287 switch ($this->vars->recur_edit) { 288 case 'current': 289 $attributes = new stdClass(); 290 $attributes->rstart = $this->vars->rstart; 291 $attributes->rend = $this->vars->rend; 292 $this->_addException($event, $attributes); 293 294 // Create a copy of the original event so we can read in the 295 // new form values for the exception. We also MUST reset the 296 // recurrence property even though we won't be using it, since 297 // clone() does not do a deep copy. Otherwise, the original 298 // event's recurrence will become corrupt. 299 $newEvent = clone($event); 300 $newEvent->recurrence = new Horde_Date_Recurrence($event->start); 301 $newEvent->readForm($event); 302 303 // Create an exception event from the new properties. 304 $exception = $this->_copyEvent($event, $newEvent, $attributes); 305 $exception->start = $newEvent->start; 306 $exception->end = $newEvent->end; 307 308 // Save the new exception. 309 $attributes->cstart = $this->vars->cstart; 310 $attributes->cend = $this->vars->cend; 311 $result = $this->_saveEvent( 312 $exception, 313 $event, 314 $attributes); 315 break; 316 case 'future': 317 $instance = new Horde_Date($this->vars->rstart, $event->timezone); 318 $exception = clone($instance); 319 $exception->mday--; 320 if ($event->end->compareDate($exception) > 0) { 321 // Same as 'all' since this is the first recurrence. 322 $this->vars->recur_edit = 'all'; 323 return $this->saveEvent(); 324 } else { 325 $event->recurrence->setRecurEnd($exception); 326 $newEvent = $kronolith_driver->getEvent(); 327 $newEvent->readForm(); 328 $newEvent->uid = null; 329 $result = $this->_saveEvent( 330 $newEvent, $event, $this->vars, true 331 ); 332 } 333 334 } 335 } else { 336 try { 337 $old_attendees = $event->attendees; 338 $event->readForm(); 339 $removed_attendees = array_diff( 340 array_keys($old_attendees), 341 array_keys($event->attendees) 342 ); 343 $result = $this->_saveEvent($event); 344 } catch (Exception $e) { 345 $notification->push($e); 346 return $result; 347 } 348 } 349 350 if (($result !== true) && $this->vars->sendupdates) { 351 $type = $event->status == Kronolith::STATUS_CANCELLED 352 ? Kronolith::ITIP_CANCEL 353 : Kronolith::ITIP_REQUEST; 354 Kronolith::sendITipNotifications($event, $notification, $type); 355 } 356 357 // Send a CANCEL iTip for attendees that have been removed, but only if 358 // the entire event isn't being marked as cancelled (which would be 359 // caught above). 360 if (!empty($removed_attendees)) { 361 $to_cancel = array(); 362 foreach ($removed_attendees as $email) { 363 $to_cancel[$email] = $old_attendees[$email]; 364 } 365 $cancelEvent = clone $event; 366 Kronolith::sendITipNotifications( 367 $cancelEvent, $notification, Kronolith::ITIP_CANCEL, null, null, $to_cancel 368 ); 369 } 370 Kronolith::notifyOfResourceRejection($event); 371 372 return $result; 373 } 374 375 /** 376 * TODO 377 */ 378 public function quickSaveEvent() 379 { 380 $cal = explode('|', $this->vars->cal, 2); 381 try { 382 $event = Kronolith::quickAdd($this->vars->text, $cal[1]); 383 return $this->_saveEvent($event); 384 } catch (Horde_Exception $e) { 385 $GLOBALS['notification']->push($e); 386 $result = $this->_signedResponse($this->vars->cal); 387 $result->error = true; 388 return $result; 389 } 390 } 391 392 /** 393 * Update event details as a result of a drag/drop operation (which would 394 * only affect the event's start/end times). 395 * 396 * Uses the following request variables: 397 *<pre> 398 * -cal: The calendar id. 399 * -id: The event id. 400 * -att: Attribute hash of changed values. Can contain: 401 * -start: A new start datetime for the event. 402 * -end: A new end datetime for the event. 403 * -offDays: An offset of days to apply to the event. 404 * -offMins: An offset of minutes to apply to the event. 405 * -rstart: The orginal start datetime of a series instance. 406 * -rend: The original end datetime of a series instance. 407 * -rday: A new start value for a series instance (used when 408 * dragging on the month view where only the date can 409 * change, and not the start/end times). 410 * -u: Send update to attendees. 411 *</pre> 412 */ 413 public function updateEvent() 414 { 415 $result = $this->_signedResponse($this->vars->cal); 416 417 if (!($kronolith_driver = $this->_getDriver($this->vars->cal)) || 418 !isset($this->vars->id)) { 419 return $result; 420 } 421 422 try { 423 $oevent = $kronolith_driver->getEvent($this->vars->id); 424 } catch (Exception $e) { 425 $GLOBALS['notification']->push($e, 'horde.error'); 426 return $result; 427 } 428 if (!$oevent) { 429 $GLOBALS['notification']->push(_("The requested event was not found."), 'horde.error'); 430 return $result; 431 } elseif (!$oevent->hasPermission(Horde_Perms::EDIT)) { 432 $GLOBALS['notification']->push(_("You do not have permission to edit this event."), 'horde.warning'); 433 return $result; 434 } 435 436 $attributes = Horde_Serialize::unserialize($this->vars->att, Horde_Serialize::JSON); 437 438 // If this is a recurring event, need to create an exception. 439 if ($oevent->recurs()) { 440 $this->_addException($oevent, $attributes); 441 $event = $this->_copyEvent($oevent, null, $attributes); 442 } else { 443 $event = clone($oevent); 444 } 445 446 foreach ($attributes as $attribute => $value) { 447 switch ($attribute) { 448 case 'start': 449 $newDate = new Horde_Date($value); 450 $newDate->setTimezone($event->start->timezone); 451 $event->start = clone($newDate); 452 break; 453 454 case 'end': 455 $newDate = new Horde_Date($value); 456 $newDate->setTimezone($event->end->timezone); 457 $event->end = clone($newDate); 458 if ($event->end->hour == 23 && 459 $event->end->min == 59 && 460 $event->end->sec == 59) { 461 $event->end->mday++; 462 $event->end->hour = $event->end->min = $event->end->sec = 0; 463 } 464 break; 465 466 case 'offDays': 467 $event->start->mday += $value; 468 $event->end->mday += $value; 469 break; 470 471 case 'offMins': 472 $event->start->min += $value; 473 $event->end->min += $value; 474 break; 475 } 476 } 477 478 $result = $this->_saveEvent($event, ($oevent->recurs() ? $oevent : null), $attributes); 479 if ($this->vars->u) { 480 Kronolith::sendITipNotifications($event, $GLOBALS['notification'], Kronolith::ITIP_REQUEST); 481 } 482 483 return $result; 484 } 485 486 /** 487 * Deletes an event, or an instance of an event series from the backend. 488 * 489 * Uses the following request variables: 490 *<pre> 491 * - cal: The calendar id. 492 * - id: The event id. 493 * - r: If this is an event series, what type of deletion to 494 * perform [future | current | all]. 495 * - rstart: The start time of the event instance being removed, if 496 * this is a series instance. 497 * - cstart: The start date of the client event cache. 498 * - cend: The end date of the client event cache. 499 * - sendupdates: Send cancellation notice to attendees? 500 * </pre> 501 */ 502 public function deleteEvent() 503 { 504 $result = new stdClass; 505 $instance = null; 506 507 if (!($kronolith_driver = $this->_getDriver($this->vars->cal)) || 508 !isset($this->vars->id)) { 509 return $result; 510 } 511 512 try { 513 $event = $kronolith_driver->getEvent($this->vars->id); 514 if (!$event->hasPermission(Horde_Perms::DELETE)) { 515 $GLOBALS['notification']->push(_("You do not have permission to delete this event."), 'horde.warning'); 516 return $result; 517 } 518 $range = null; 519 if ($event->recurs() && $this->vars->r != 'all') { 520 switch ($this->vars->r) { 521 case 'future': 522 // Deleting all future instances. 523 // @TODO: Check if we need to find future exceptions 524 // that are after $recurEnd and remove those as well. 525 $instance = new Horde_Date($this->vars->rstart, $event->timezone); 526 $recurEnd = clone($instance); 527 $recurEnd->hour = 0; 528 $recurEnd->min = 0; 529 $recurEnd->sec = 0; 530 $recurEnd->mday--; 531 if ($event->end->compareDate($recurEnd) > 0) { 532 $kronolith_driver->deleteEvent($event->id); 533 $result = $this->_signedResponse($this->vars->cal); 534 $result->events = array(); 535 } else { 536 $event->recurrence->setRecurEnd($recurEnd); 537 $result = $this->_saveEvent($event, $event, $this->vars); 538 } 539 $range = Kronolith::RANGE_THISANDFUTURE; 540 break; 541 case 'current': 542 // Deleting only the current instance. 543 $instance = new Horde_Date($this->vars->rstart, $event->timezone); 544 $event->recurrence->addException( 545 $instance->year, $instance->month, $instance->mday); 546 $result = $this->_saveEvent($event, $event, $this->vars); 547 } 548 } else { 549 // Deleting an entire series, or this is a single event only. 550 $kronolith_driver->deleteEvent($event->id); 551 $result = $this->_signedResponse($this->vars->cal); 552 $result->events = array(); 553 $result->uid = $event->uid; 554 } 555 556 if ($this->vars->sendupdates) { 557 Kronolith::sendITipNotifications( 558 $event, $GLOBALS['notification'], Kronolith::ITIP_CANCEL, $instance, $range); 559 } 560 $result->deleted = true; 561 } catch (Horde_Exception_NotFound $e) { 562 $GLOBALS['notification']->push(_("The requested event was not found."), 'horde.error'); 563 } catch (Exception $e) { 564 $GLOBALS['notification']->push($e, 'horde.error'); 565 } 566 567 return $result; 568 } 569 570 /** 571 * TODO 572 */ 573 public function searchEvents() 574 { 575 $query = Horde_Serialize::unserialize($this->vars->query, Horde_Serialize::JSON); 576 if (!isset($query->start)) { 577 $query->start = new Horde_Date($_SERVER['REQUEST_TIME']); 578 } 579 if (!isset($query->end)) { 580 $query->end = null; 581 } 582 switch ($this->vars->time) { 583 case 'all': 584 $query->start = null; 585 $query->end = null; 586 break; 587 case 'future': 588 $query->start = new Horde_Date($_SERVER['REQUEST_TIME']); 589 $query->end = null; 590 break; 591 case 'past': 592 $query->start = null; 593 $query->end = new Horde_Date($_SERVER['REQUEST_TIME']); 594 break; 595 } 596 597 $tagger = new Kronolith_Tagger(); 598 $cals = Horde_Serialize::unserialize($this->vars->cals, Horde_Serialize::JSON); 599 $events = array(); 600 foreach ($cals as $cal) { 601 if (!($kronolith_driver = $this->_getDriver($cal))) { 602 continue; 603 } 604 try { 605 $result = $kronolith_driver->search($query, true); 606 if ($result) { 607 $events[$cal] = $result; 608 } 609 } catch (Exception $e) { 610 $GLOBALS['notification']->push($e, 'horde.error'); 611 } 612 $split = explode('|', $cal); 613 if ($split[0] == 'internal') { 614 $result = $tagger->search($query->title, array('type' => 'event', 'calendar' => $split[1])); 615 foreach ($result['events'] as $uid) { 616 Kronolith::addSearchEvents($events[$cal], $kronolith_driver->getByUID($uid), $query, true); 617 } 618 } 619 } 620 621 $result = new stdClass; 622 $result->view = 'search'; 623 $result->query = $this->vars->query; 624 if ($events) { 625 $result->events = $events; 626 } 627 628 return $result; 629 } 630 631 /** 632 * TODO 633 */ 634 public function listTasks() 635 { 636 if (!$GLOBALS['registry']->hasMethod('tasks/listTasks')) { 637 return false; 638 } 639 640 $result = new stdClass; 641 $result->list = $this->vars->list; 642 $result->type = $this->vars->type; 643 try { 644 $tasks = $GLOBALS['registry']->tasks 645 ->listTasks(array( 646 'tasklists' => $this->vars->list, 647 'completed' => $this->vars->type == 'incomplete' ? 'future_incomplete' : $this->vars->type, 648 'include_tags' => true, 649 'external' => false, 650 'json' => true 651 )); 652 if (count($tasks)) { 653 $result->tasks = $tasks; 654 } 655 } catch (Exception $e) { 656 $GLOBALS['notification']->push($e, 'horde.error'); 657 } 658 659 return $result; 660 } 661 662 /** 663 * TODO 664 */ 665 public function getTask() 666 { 667 if (!$GLOBALS['registry']->hasMethod('tasks/getTask') || 668 !isset($this->vars->id) || 669 !isset($this->vars->list)) { 670 return false; 671 } 672 673 $result = new stdClass; 674 try { 675 $task = $GLOBALS['registry']->tasks->getTask($this->vars->list, $this->vars->id); 676 if ($task) { 677 $result->task = $task->toJson(true, $GLOBALS['prefs']->getValue('twentyFour') ? 'H:i' : 'h:i A'); 678 } else { 679 $GLOBALS['notification']->push(_("The requested task was not found."), 'horde.error'); 680 } 681 } catch (Exception $e) { 682 $GLOBALS['notification']->push($e, 'horde.error'); 683 } 684 685 return $result; 686 } 687 688 /** 689 * TODO 690 */ 691 public function saveTask() 692 { 693 if (!$GLOBALS['registry']->hasMethod('tasks/updateTask') || 694 !$GLOBALS['registry']->hasMethod('tasks/addTask')) { 695 return false; 696 } 697 698 $id = $this->vars->task_id; 699 $list = $this->vars->old_tasklist; 700 $task = $this->vars->task; 701 $result = $this->_signedResponse('tasklists|tasks/' . $task['tasklist']); 702 703 $due = trim($task['due_date'] . ' ' . $task['due_time']); 704 if (!empty($due)) { 705 try { 706 $due = Kronolith::parseDate($due); 707 $task['due'] = $due->timestamp(); 708 } catch (Exception $e) { 709 $GLOBALS['notification']->push($e, 'horde.error'); 710 return $result; 711 } 712 } 713 714 if ($task['alarm']['on']) { 715 $value = $task['alarm']['value']; 716 $unit = $task['alarm']['unit']; 717 if ($value == 0) { 718 $value = $unit = 1; 719 } 720 $task['alarm'] = $value * $unit; 721 if (isset($task['alarm_methods']) && isset($task['methods'])) { 722 foreach (array_keys($task['methods']) as $method) { 723 if (!in_array($method, $task['alarm_methods'])) { 724 unset($task['methods'][$method]); 725 } 726 } 727 foreach ($task['alarm_methods'] as $method) { 728 if (!isset($task['methods'][$method])) { 729 $task['methods'][$method] = array(); 730 } 731 } 732 } else { 733 $task['methods'] = array(); 734 } 735 } else { 736 $task['alarm'] = 0; 737 $task['methods'] = array(); 738 } 739 unset($task['alarm_methods']); 740 741 if (!isset($task['completed'])) { 742 $task['completed'] = false; 743 } 744 745 if ($this->vars->recur && !empty($due)) { 746 $task['recurrence'] = Kronolith_Event::readRecurrenceForm($due, 'UTC'); 747 } 748 749 $task['tags'] = Horde_Util::getFormData('tags'); 750 751 try { 752 $ids = ($id && $list) 753 ? $GLOBALS['registry']->tasks->updateTask($list, $id, $task) 754 : $GLOBALS['registry']->tasks->addTask($task); 755 if (!$id) { 756 $id = $ids[0]; 757 } 758 $task = $GLOBALS['registry']->tasks->getTask($task['tasklist'], $id); 759 $result->tasks = array($id => $task->toJson(false, $GLOBALS['prefs']->getValue('twentyFour') ? 'H:i' : 'h:i A')); 760 $result->type = $task->completed ? 'complete' : 'incomplete'; 761 $result->list = $task->tasklist; 762 } catch (Exception $e) { 763 $GLOBALS['notification']->push($e, 'horde.error'); 764 return $result; 765 } 766 767 if ($due && 768 $kronolith_driver = $this->_getDriver('tasklists|tasks/' . $task->tasklist)) { 769 try { 770 $event = $kronolith_driver->getEvent('_tasks' . $id); 771 $end = clone $due; 772 $end->hour = 23; 773 $end->min = $end->sec = 59; 774 $start = clone $due; 775 $start->hour = $start->min = $start->sec = 0; 776 $events = array(); 777 Kronolith::addEvents($events, $event, $start, $end, true, true); 778 if (count($events)) { 779 $result->events = $events; 780 } 781 } catch (Horde_Exception_NotFound $e) { 782 } catch (Exception $e) { 783 $GLOBALS['notification']->push($e, 'horde.error'); 784 } 785 } 786 787 return $result; 788 } 789 790 /** 791 * TODO 792 */ 793 public function quickSaveTask() 794 { 795 if (!$GLOBALS['registry']->hasMethod('tasks/quickAdd')) { 796 return false; 797 } 798 799 $result = $this->_signedResponse( 800 'tasklists|tasks/' . $this->vars->tasklist); 801 802 try { 803 $ids = $GLOBALS['registry']->tasks->quickAdd($this->vars->text); 804 $result->type = 'incomplete'; 805 $result->list = $this->vars->tasklist; 806 $result->tasks = array(); 807 foreach ($ids as $uid) { 808 $task = $GLOBALS['registry']->tasks->export($uid, 'raw'); 809 $result->tasks[$task->id] = $task->toJson( 810 false, 811 $GLOBALS['prefs']->getValue('twentyFour') ? 'H:i' : 'h:i A' 812 ); 813 } 814 } catch (Exception $e) { 815 $GLOBALS['notification']->push($e, 'horde.error'); 816 } 817 818 return $result; 819 } 820 821 /** 822 * TODO 823 */ 824 public function deleteTask() 825 { 826 $result = new stdClass; 827 828 if (!$GLOBALS['registry']->hasMethod('tasks/deleteTask') || 829 !isset($this->vars->id) || 830 !isset($this->vars->list)) { 831 return $result; 832 } 833 834 try { 835 $GLOBALS['registry']->tasks->deleteTask($this->vars->list, $this->vars->id); 836 $result->deleted = true; 837 } catch (Exception $e) { 838 $GLOBALS['notification']->push($e, 'horde.error'); 839 } 840 841 return $result; 842 } 843 844 /** 845 * TODO 846 */ 847 public function toggleCompletion() 848 { 849 $result = new stdClass; 850 851 if (!$GLOBALS['registry']->hasMethod('tasks/toggleCompletion')) { 852 return $result; 853 } 854 855 try { 856 $result->toggled = $GLOBALS['registry']->tasks->toggleCompletion($this->vars->id, $this->vars->list); 857 } catch (Exception $e) { 858 $GLOBALS['notification']->push($e, 'horde.error'); 859 } 860 861 return $result; 862 } 863 864 /** 865 * Generate a list of most frequently used tags for the current user. 866 */ 867 public function listTopTags() 868 { 869 $tagger = new Kronolith_Tagger(); 870 $result = new stdClass; 871 $result->tags = array(); 872 $tags = $tagger->getCloud($GLOBALS['registry']->getAuth(), 10, true); 873 foreach ($tags as $tag) { 874 $result->tags[] = $tag['tag_name']; 875 } 876 return $result; 877 } 878 879 /** 880 * Return fb information for the requested attendee or resource. 881 * 882 * Uses the following request parameters: 883 *<pre> 884 * -email: The attendee's email address. 885 * -resource: The resource id. 886 *</pre> 887 */ 888 public function getFreeBusy() 889 { 890 $result = new stdClass; 891 if ($this->vars->email) { 892 try { 893 $result->fb = Kronolith_FreeBusy::get($this->vars->email, true); 894 } catch (Exception $e) { 895 $GLOBALS['notification']->push($e->getMessage(), 'horde.warning'); 896 } 897 } elseif ($this->vars->resource) { 898 try { 899 $resource = Kronolith::getDriver('Resource') 900 ->getResource($this->vars->resource); 901 try { 902 $result->fb = $resource->getFreeBusy(null, null, true, true); 903 } catch (Horde_Exception $e) { 904 // Resource groups can't provide FB information. 905 $result->fb = null; 906 } 907 } catch (Exception $e) { 908 $GLOBALS['notification']->push($e->getMessage(), 'horde.warning'); 909 } 910 } 911 912 return $result; 913 } 914 915 /** 916 * TODO 917 */ 918 public function searchCalendars() 919 { 920 $result = new stdClass; 921 $result->events = 'Searched for calendars: ' . $this->vars->title; 922 return $result; 923 } 924 925 /** 926 * TODO 927 */ 928 public function saveCalendar() 929 { 930 $calendar_id = $this->vars->calendar; 931 $result = new stdClass; 932 933 switch ($this->vars->type) { 934 case 'internal': 935 $info = array(); 936 foreach (array('name', 'color', 'description', 'tags') as $key) { 937 $info[$key] = $this->vars->$key; 938 } 939 940 // Create a calendar. 941 if (!$calendar_id) { 942 if (!$GLOBALS['registry']->getAuth() || 943 $GLOBALS['prefs']->isLocked('default_share')) { 944 return $result; 945 } 946 try { 947 $calendar = Kronolith::addShare($info); 948 Kronolith::readPermsForm($calendar); 949 if ($calendar->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::SHOW)) { 950 $wrapper = new Kronolith_Calendar_Internal(array('share' => $calendar)); 951 $result->saved = true; 952 $result->id = $calendar->getName(); 953 $result->calendar = $wrapper->toHash(); 954 } 955 } catch (Exception $e) { 956 $GLOBALS['notification']->push($e, 'horde.error'); 957 return $result; 958 } 959 $GLOBALS['notification']->push(sprintf(_("The calendar \"%s\" has been created."), $info['name']), 'horde.success'); 960 break; 961 } 962 963 // Update a calendar. 964 try { 965 $calendar = $GLOBALS['injector']->getInstance('Kronolith_Shares')->getShare($calendar_id); 966 $original_name = $calendar->get('name'); 967 $original_owner = $calendar->get('owner'); 968 Kronolith::updateShare($calendar, $info); 969 Kronolith::readPermsForm($calendar); 970 if ($calendar->get('owner') != $original_owner) { 971 $result->deleted = true; 972 } 973 if ($calendar->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::SHOW)) { 974 $wrapper = new Kronolith_Calendar_Internal(array('share' => $calendar)); 975 $result->saved = true; 976 $result->calendar = $wrapper->toHash(); 977 } 978 } catch (Exception $e) { 979 $GLOBALS['notification']->push($e, 'horde.error'); 980 return $result; 981 982 } 983 if ($calendar->get('name') != $original_name) { 984 $GLOBALS['notification']->push(sprintf(_("The calendar \"%s\" has been renamed to \"%s\"."), $original_name, $calendar->get('name')), 'horde.success'); 985 } else { 986 $GLOBALS['notification']->push(sprintf(_("The calendar \"%s\" has been saved."), $original_name), 'horde.success'); 987 } 988 break; 989 990 case 'tasklists': 991 $calendar = array(); 992 foreach (array('name', 'color', 'description') as $key) { 993 $calendar[$key] = $this->vars->$key; 994 } 995 996 // Create a task list. 997 if (!$calendar_id) { 998 if (!$GLOBALS['registry']->getAuth() || 999 $GLOBALS['prefs']->isLocked('default_share')) { 1000 return $result; 1001 } 1002 try { 1003 $tasklistId = $GLOBALS['registry']->tasks->addTasklist($calendar['name'], $calendar['description'], $calendar['color']); 1004 $tasklists = $GLOBALS['registry']->tasks->listTasklists(true); 1005 if (!isset($tasklists[$tasklistId])) { 1006 $GLOBALS['notification']->push(_("Added task list not found."), 'horde.error'); 1007 return $result; 1008 } 1009 $tasklist = $tasklists[$tasklistId]; 1010 Kronolith::readPermsForm($tasklist); 1011 if ($tasklist->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::SHOW)) { 1012 $wrapper = new Kronolith_Calendar_External_Tasks(array('api' => 'tasks', 'name' => $tasklistId, 'share' => $tasklist)); 1013 1014 // Update external calendars caches. 1015 $all_external = $GLOBALS['session']->get('kronolith', 'all_external_calendars'); 1016 $all_external[] = array('a' => 'tasks', 'n' => $tasklistId, 'd' => $tasklist->get('name')); 1017 $GLOBALS['session']->set('kronolith', 'all_external_calendars', $all_external); 1018 $display_external = $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_EXTERNAL_CALENDARS); 1019 $display_external[] = 'tasks/' . $tasklistId; 1020 $GLOBALS['calendar_manager']->set(Kronolith::DISPLAY_EXTERNAL_CALENDARS, $display_external); 1021 $GLOBALS['prefs']->setValue('display_external_cals', serialize($display_external)); 1022 $all_external = $GLOBALS['calendar_manager']->get(Kronolith::ALL_EXTERNAL_CALENDARS); 1023 $all_external['tasks/' . $tasklistId] = $wrapper; 1024 $GLOBALS['calendar_manager']->set(Kronolith::ALL_EXTERNAL_CALENDARS, $all_external); 1025 1026 $result->saved = true; 1027 $result->id = 'tasks/' . $tasklistId; 1028 $result->calendar = $wrapper->toHash(); 1029 } 1030 } catch (Exception $e) { 1031 $GLOBALS['notification']->push($e, 'horde.error'); 1032 return $result; 1033 } 1034 $GLOBALS['notification']->push(sprintf(_("The task list \"%s\" has been created."), $calendar['name']), 'horde.success'); 1035 break; 1036 } 1037 1038 // Update a task list. 1039 $calendar_id = substr($calendar_id, 6); 1040 try { 1041 $GLOBALS['registry']->tasks->updateTasklist($calendar_id, $calendar); 1042 $tasklists = $GLOBALS['registry']->tasks->listTasklists(true, Horde_Perms::EDIT); 1043 $tasklist = $tasklists[$calendar_id]; 1044 $original_owner = $tasklist->get('owner'); 1045 Kronolith::readPermsForm($tasklist); 1046 if ($tasklist->get('owner') != $original_owner) { 1047 $result->deleted = true; 1048 } 1049 if ($tasklist->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::SHOW)) { 1050 $wrapper = new Kronolith_Calendar_External_Tasks(array('api' => 'tasks', 'name' => $calendar_id, 'share' => $tasklist)); 1051 $result->saved = true; 1052 $result->calendar = $wrapper->toHash(); 1053 } 1054 } catch (Exception $e) { 1055 $GLOBALS['notification']->push($e, 'horde.error'); 1056 return $result; 1057 } 1058 if ($tasklist->get('name') != $calendar['name']) { 1059 $GLOBALS['notification']->push(sprintf(_("The task list \"%s\" has been renamed to \"%s\"."), $tasklist->get('name'), $calendar['name']), 'horde.success'); 1060 } else { 1061 $GLOBALS['notification']->push(sprintf(_("The task list \"%s\" has been saved."), $tasklist->get('name')), 'horde.success'); 1062 } 1063 break; 1064 1065 case 'remote': 1066 $calendar = array(); 1067 foreach (array('name', 'desc', 'url', 'color', 'user', 'password') as $key) { 1068 $calendar[$key] = $this->vars->$key; 1069 } 1070 try { 1071 Kronolith::subscribeRemoteCalendar($calendar, $calendar_id); 1072 } catch (Exception $e) { 1073 $GLOBALS['notification']->push($e, 'horde.error'); 1074 return $result; 1075 } 1076 if ($calendar_id) { 1077 $GLOBALS['notification']->push(sprintf(_("The calendar \"%s\" has been saved."), $calendar['name']), 'horde.success'); 1078 } else { 1079 $GLOBALS['notification']->push(sprintf(_("You have been subscribed to \"%s\" (%s)."), $calendar['name'], $calendar['url']), 'horde.success'); 1080 $result->id = $calendar['url']; 1081 } 1082 $wrapper = new Kronolith_Calendar_Remote($calendar); 1083 $result->saved = true; 1084 $result->calendar = $wrapper->toHash(); 1085 break; 1086 1087 case 'resource': 1088 foreach (array('name', 'description', 'response_type') as $key) { 1089 $info[$key] = $this->vars->$key; 1090 } 1091 1092 if (!$calendar_id) { 1093 // New resource 1094 // @TODO: Groups. 1095 if (!$GLOBALS['registry']->isAdmin() && 1096 !$GLOBALS['injector']->getInstance('Horde_Core_Perms')->hasAppPermission('resource_management')) { 1097 $GLOBALS['notification']->push(_("You are not allowed to create new resources."), 'horde.error'); 1098 return $result; 1099 } 1100 $resource = Kronolith_Resource::addResource(new Kronolith_Resource_Single($info)); 1101 } else { 1102 try { 1103 $rdriver = Kronolith::getDriver('Resource'); 1104 $resource = $rdriver->getResource($rdriver->getResourceIdByCalendar($calendar_id)); 1105 if (!($resource->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::EDIT))) { 1106 $GLOBALS['notification']->push(_("You are not allowed to edit this resource."), 'horde.error'); 1107 return $result; 1108 } 1109 foreach (array('name', 'description', 'response_type', 'email') as $key) { 1110 $resource->set($key, $this->vars->$key); 1111 } 1112 $resource->save(); 1113 } catch (Kronolith_Exception $e) { 1114 $GLOBALS['notification']->push($e->getMessage(), 'horde.error'); 1115 return $result; 1116 } 1117 } 1118 $wrapper = new Kronolith_Calendar_Resource(array('resource' => $resource)); 1119 $result->calendar = $wrapper->toHash(); 1120 $result->saved = true; 1121 $result->id = $resource->get('calendar'); 1122 $GLOBALS['notification']->push(sprintf(_("The resource \"%s\" has been saved."), $resource->get('name'), 'horde.success')); 1123 break; 1124 1125 case 'resourcegroup': 1126 if (empty($calendar_id)) { 1127 // New resource group. 1128 $resource = Kronolith_Resource::addResource( 1129 new Kronolith_Resource_Group(array( 1130 'name' => $this->vars->name, 1131 'description' => $this->vars->description, 1132 'members' => $this->vars->members) 1133 ) 1134 ); 1135 } else { 1136 $driver = Kronolith::getDriver('Resource'); 1137 $resource = $driver->getResource($calendar_id); 1138 $resource->set('name', $this->vars->name); 1139 $resource->set('description', $this->vars->description); 1140 $resource->set('members', $this->vars->members); 1141 $resource->save(); 1142 } 1143 1144 $wrapper = new Kronolith_Calendar_ResourceGroup(array('resource' => $resource)); 1145 $result->calendar = $wrapper->toHash(); 1146 $result->saved = true; 1147 $result->id = $resource->get('calendar'); 1148 $GLOBALS['notification']->push(sprintf(_("The resource group \"%s\" has been saved."), $resource->get('name'), 'horde.success')); 1149 break; 1150 } 1151 1152 return $result; 1153 } 1154 1155 /** 1156 * TODO 1157 */ 1158 public function deleteCalendar() 1159 { 1160 $calendar_id = $this->vars->calendar; 1161 $result = new stdClass; 1162 1163 switch ($this->vars->type) { 1164 case 'internal': 1165 try { 1166 $calendar = $GLOBALS['injector']->getInstance('Kronolith_Shares')->getShare($calendar_id); 1167 } catch (Exception $e) { 1168 $GLOBALS['notification']->push($e, 'horde.error'); 1169 return $result; 1170 } 1171 try { 1172 Kronolith::deleteShare($calendar); 1173 } catch (Exception $e) { 1174 $GLOBALS['notification']->push(sprintf(_("Unable to delete \"%s\": %s"), $calendar->get('name'), $e->getMessage()), 'horde.error'); 1175 return $result; 1176 } 1177 $GLOBALS['notification']->push(sprintf(_("The calendar \"%s\" has been deleted."), $calendar->get('name')), 'horde.success'); 1178 break; 1179 1180 case 'tasklists': 1181 $calendar_id = substr($calendar_id, 6); 1182 $tasklists = $GLOBALS['registry']->tasks->listTasklists(true); 1183 if (!isset($tasklists[$calendar_id])) { 1184 $GLOBALS['notification']->push(_("You are not allowed to delete this task list."), 'horde.error'); 1185 return $result; 1186 } 1187 try { 1188 $GLOBALS['registry']->tasks->deleteTasklist($calendar_id); 1189 } catch (Exception $e) { 1190 $GLOBALS['notification']->push(sprintf(_("Unable to delete \"%s\": %s"), $tasklists[$calendar_id]->get('name'), $e->getMessage()), 'horde.error'); 1191 return $result; 1192 } 1193 $GLOBALS['notification']->push(sprintf(_("The task list \"%s\" has been deleted."), $tasklists[$calendar_id]->get('name')), 'horde.success'); 1194 break; 1195 1196 case 'remote': 1197 try { 1198 $deleted = Kronolith::unsubscribeRemoteCalendar($calendar_id); 1199 } catch (Exception $e) { 1200 $GLOBALS['notification']->push($e, 'horde.error'); 1201 return $result; 1202 } 1203 $GLOBALS['notification']->push(sprintf(_("You have been unsubscribed from \"%s\" (%s)."), $deleted['name'], $deleted['url']), 'horde.success'); 1204 break; 1205 1206 case 'resource': 1207 try { 1208 $rdriver = Kronolith::getDriver('Resource'); 1209 $resource = $rdriver->getResource($rdriver->getResourceIdByCalendar($calendar_id)); 1210 if (!($resource->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::DELETE))) { 1211 $GLOBALS['notification']->push(_("You are not allowed to delete this resource."), 'horde.error'); 1212 return $result; 1213 } 1214 $name = $resource->get('name'); 1215 $rdriver->delete($resource); 1216 } catch (Kronolith_Exception $e) { 1217 $GLOBALS['notification']->push($e->getMessage(), 'horde.error'); 1218 return $result; 1219 } 1220 1221 $GLOBALS['notification']->push(sprintf(_("The resource \"%s\" has been deleted."), $name), 'horde.success'); 1222 break; 1223 1224 case 'resourcegroup': 1225 try { 1226 $rdriver = Kronolith::getDriver('Resource'); 1227 $resource = $rdriver->getResource($calendar_id); 1228 if (!($resource->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::DELETE))) { 1229 $GLOBALS['notification']->push(_("You are not allowed to delete this resource."), 'horde.error'); 1230 return $result; 1231 } 1232 $name = $resource->get('name'); 1233 $rdriver->delete($resource); 1234 } catch (Kronolith_Exception $e) { 1235 $GLOBALS['notification']->push($e->getMessage(), 'horde.error'); 1236 return $result; 1237 } 1238 $GLOBALS['notification']->push(sprintf(_("The resource \"%s\" has been deleted."), $name), 'horde.success'); 1239 1240 } 1241 $result->deleted = true; 1242 1243 return $result; 1244 } 1245 1246 /** 1247 * Returns the information for a shared internal calendar. 1248 */ 1249 public function getCalendar() 1250 { 1251 $result = new stdClass; 1252 $all_calendars = $GLOBALS['calendar_manager']->get(Kronolith::ALL_CALENDARS); 1253 if (!isset($all_calendars[$this->vars->cal]) && !$GLOBALS['conf']['share']['hidden']) { 1254 $GLOBALS['notification']->push(_("You are not allowed to view this calendar."), 'horde.error'); 1255 return $result; 1256 } elseif (!isset($all_calendars[$this->vars->cal])) { 1257 // Subscribing to a "hidden" share, check perms. 1258 $kronolith_shares = $GLOBALS['injector']->getInstance('Kronolith_Shares'); 1259 $share = $kronolith_shares->getShare($this->vars->cal); 1260 if (!$share->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::READ)) { 1261 $GLOBALS['notification']->push(_("You are not allowed to view this calendar."), 'horde.error'); 1262 return $result; 1263 } 1264 $calendar = new Kronolith_Calendar_Internal(array('share' => $share)); 1265 } else { 1266 $calendar = $all_calendars[$this->vars->cal]; 1267 } 1268 1269 $result->calendar = $calendar->toHash(); 1270 return $result; 1271 } 1272 1273 /** 1274 * TODO 1275 */ 1276 public function getRemoteInfo() 1277 { 1278 $params = array('timeout' => 15); 1279 if ($user = $this->vars->user) { 1280 $params['user'] = $user; 1281 $params['password'] = $this->vars->password; 1282 } 1283 if (!empty($GLOBALS['conf']['http']['proxy']['proxy_host'])) { 1284 $params['proxy'] = $GLOBALS['conf']['http']['proxy']; 1285 } 1286 1287 $result = new stdClass; 1288 try { 1289 $driver = $GLOBALS['injector']->getInstance('Kronolith_Factory_Driver')->create('Ical', $params); 1290 $driver->open($this->vars->url); 1291 if ($driver->isCalDAV()) { 1292 $result->success = true; 1293 // TODO: find out how to retrieve calendar information via CalDAV. 1294 } else { 1295 $ical = $driver->getRemoteCalendar(false); 1296 $result->success = true; 1297 try { 1298 $name = $ical->getAttribute('X-WR-CALNAME'); 1299 $result->name = $name; 1300 } catch (Horde_Icalendar_Exception $e) {} 1301 try { 1302 $desc = $ical->getAttribute('X-WR-CALDESC'); 1303 $result->desc = $desc; 1304 } catch (Horde_Icalendar_Exception $e) {} 1305 } 1306 } catch (Exception $e) { 1307 if ($e->getCode() == 401) { 1308 $result->auth = true; 1309 } else { 1310 $GLOBALS['notification']->push($e, 'horde.error'); 1311 } 1312 } 1313 1314 return $result; 1315 } 1316 1317 /** 1318 * TODO 1319 */ 1320 public function saveCalPref() 1321 { 1322 return false; 1323 } 1324 1325 /** 1326 * Return a list of available resources. 1327 * 1328 * @return array A hash of resource_id => resource sorted by resource name. 1329 */ 1330 public function getResourceList() 1331 { 1332 $data = array(); 1333 $resources = Kronolith::getDriver('Resource') 1334 ->listResources(Horde_Perms::READ, array(), 'name'); 1335 foreach ($resources as $resource) { 1336 $data[] = $resource->toJson(); 1337 } 1338 1339 return $data; 1340 } 1341 1342 /** 1343 * Handle output of the embedded widget: allows embedding calendar widgets 1344 * in external websites. 1345 * 1346 * The following arguments are required: 1347 * - calendar: The share_name for the requested calendar. 1348 * - container: The DOM node to populate with the widget. 1349 * - view: The view (block) we want. 1350 * 1351 * The following are optional (and are not used for all views) 1352 * - css 1353 * - days 1354 * - maxevents: The maximum number of events to show. 1355 * - months: The number of months to include. 1356 */ 1357 public function embed() 1358 { 1359 global $page_output, $registry; 1360 1361 /* First, determine the type of view we are asking for */ 1362 $view = $this->vars->view; 1363 1364 /* The DOM container to put the HTML in on the remote site */ 1365 $container = $this->vars->container; 1366 1367 /* The share_name of the calendar to display */ 1368 $calendar = $this->vars->calendar; 1369 1370 /* Deault to showing only 1 month when we have a choice */ 1371 $count_month = $this->vars->get('months', 1); 1372 1373 /* Default to no limit for the number of events */ 1374 $max_events = $this->vars->get('maxevents', 0); 1375 1376 /* Default to one week */ 1377 $count_days = $this->vars->get('days', 7); 1378 1379 if ($this->vars->css == 'none') { 1380 $nocss = true; 1381 } 1382 1383 /* Build the block parameters */ 1384 $params = array( 1385 'calendar' => $calendar, 1386 'maxevents' => $max_events, 1387 'months' => $count_month, 1388 'days' => $count_days 1389 ); 1390 1391 /* Call the Horde_Block api to get the calendar HTML */ 1392 $title = $registry->call('horde/blockTitle', array('kronolith', $view, $params)); 1393 $results = $registry->call('horde/blockContent', array('kronolith', $view, $params)); 1394 1395 /* Some needed paths */ 1396 $js_path = $registry->get('jsuri', 'kronolith'); 1397 1398 /* Local js */ 1399 $jsurl = Horde::url($js_path . '/embed.js', true); 1400 1401 /* Horde's js */ 1402 $hjs_path = $registry->get('jsuri', 'horde'); 1403 $hjsurl = Horde::url($hjs_path . '/tooltips.js', true); 1404 $pturl = Horde::url($hjs_path . '/prototype.js', true); 1405 1406 /* CSS */ 1407 if (empty($nocss)) { 1408 $page_output->addThemeStylesheet('embed.css'); 1409 1410 Horde::startBuffer(); 1411 $page_output->includeStylesheetFiles(array('nobase' => true), true); 1412 $css = Horde::endBuffer(); 1413 } else { 1414 $css = ''; 1415 } 1416 1417 /* Escape the text and put together the javascript to send back */ 1418 $container = Horde_Serialize::serialize($container, Horde_Serialize::JSON); 1419 $results = Horde_Serialize::serialize('<div class="kronolith_embedded"><div class="title">' . $title . '</div>' . $results . '</div>', Horde_Serialize::JSON); 1420 1421 $js = <<<EOT 1422if (typeof kronolith == 'undefined') { 1423 if (typeof Prototype == 'undefined') { 1424 document.write('<script type="text/javascript" src="$pturl"></script>'); 1425 } 1426 if (typeof Horde_ToolTips == 'undefined') { 1427 Horde_ToolTips_Autoload = false; 1428 document.write('<script type="text/javascript" src="$hjsurl"></script>'); 1429 } 1430 kronolith = new Object(); 1431 kronolithNodes = new Array(); 1432 document.write('<script type="text/javascript" src="$jsurl"></script>'); 1433 document.write('$css'); 1434} 1435kronolithNodes[kronolithNodes.length] = $container; 1436kronolith[$container] = $results; 1437EOT; 1438 1439 return new Horde_Core_Ajax_Response_Raw($js, 'text/javascript'); 1440 } 1441 1442 public function toTimeslice() 1443 { 1444 $driver = $this->_getDriver($this->vars->cal); 1445 $event = $driver->getEvent($this->vars->e); 1446 1447 try { 1448 Kronolith::toTimeslice($event, $this->vars->t, $this->vars->c); 1449 } catch (Kronolith_Exception $e) { 1450 $GLOBALS['notification']->push(sprintf(_("Error saving timeslice: %s"), $e->getMessage()), 'horde.error'); 1451 return false; 1452 } 1453 $GLOBALS['notification']->push(_("Successfully saved timeslice."), 'horde.success'); 1454 1455 return true; 1456 } 1457 1458 /** 1459 * Check reply status of any resources and report back. Used as a check 1460 * before saving an event to give the user feedback. 1461 * 1462 * The following arguments are expected: 1463 * - r: A comma separated string of resource identifiers. 1464 * - s: The event start time to check. 1465 * - e: The event end time to check. 1466 * - u: The event uid, if not a new event. 1467 * - c: The event's calendar. 1468 */ 1469 public function checkResources() 1470 { 1471 if (empty($GLOBALS['conf']['resource']['driver'])) { 1472 return array(); 1473 } 1474 1475 if ($this->vars->i) { 1476 $event = $this->_getDriver($this->vars->c)->getEvent($this->vars->i); 1477 } else { 1478 $event = Kronolith::getDriver()->getEvent(); 1479 } 1480 // Overrite start/end times since we may be checking before we edit 1481 // an existing event with new times. 1482 $event->start = new Horde_Date($this->vars->s); 1483 $event->end = new Horde_Date($this->vars->e); 1484 $event->start->setTimezone(date_default_timezone_get()); 1485 $event->end->setTimezone(date_default_timezone_get()); 1486 $results = array(); 1487 foreach (explode(',', $this->vars->r) as $id) { 1488 $resource = Kronolith::getDriver('Resource')->getResource($id); 1489 $results[$id] = $resource->getResponse($event); 1490 } 1491 1492 return $results; 1493 } 1494 1495 /** 1496 * Returns the driver object for a calendar. 1497 * 1498 * @param string $cal A calendar string in the format "type|name". 1499 * 1500 * @return Kronolith_Driver|boolean A driver instance or false on failure. 1501 */ 1502 protected function _getDriver($cal) 1503 { 1504 list($driver, $calendar) = explode('|', $cal); 1505 if ($driver == 'internal' && 1506 !Kronolith::hasPermission($calendar, Horde_Perms::SHOW)) { 1507 $GLOBALS['notification']->push(_("Permission Denied"), 'horde.error'); 1508 return false; 1509 } 1510 try { 1511 $kronolith_driver = Kronolith::getDriver($driver, $calendar); 1512 } catch (Exception $e) { 1513 $GLOBALS['notification']->push($e, 'horde.error'); 1514 return false; 1515 } 1516 if ($driver == 'remote') { 1517 $kronolith_driver->setParam('timeout', 15); 1518 } 1519 return $kronolith_driver; 1520 } 1521 1522 /** 1523 * Saves an event and returns a signed result object including the saved 1524 * event. 1525 * 1526 * @param Kronolith_Event $event An event object. 1527 * @param Kronolith_Event $original If $event is an exception, this should 1528 * be set to the original event. 1529 * @param object $attributes The attributes sent by the client. 1530 * Expected to contain cstart and cend. 1531 * @param boolean $saveOriginal Commit any changes in $original to 1532 * storage also. 1533 * 1534 * @return object The result object. 1535 */ 1536 protected function _saveEvent(Kronolith_Event $event, 1537 Kronolith_Event $original = null, 1538 $attributes = null, 1539 $saveOriginal = false) 1540 { 1541 if ($this->vars->targetcalendar) { 1542 $cal = $this->vars->targetcalendar; 1543 } elseif ($this->vars->cal) { 1544 $cal = $this->vars->cal; 1545 } else { 1546 $cal = $event->calendarType . '|' . $event->calendar; 1547 } 1548 $result = $this->_signedResponse($cal); 1549 $events = array(); 1550 try { 1551 $event->save(); 1552 if (!$this->vars->view_start || !$this->vars->view_end) { 1553 $result->events = array(); 1554 return $result; 1555 } 1556 $end = new Horde_Date($this->vars->view_end); 1557 $end->hour = 23; 1558 $end->min = $end->sec = 59; 1559 Kronolith::addEvents( 1560 $events, $event, 1561 new Horde_Date($this->vars->view_start), 1562 $end, true, true); 1563 // If this is an exception, we re-add the original event also; 1564 // cstart and cend are the cacheStart and cacheEnd dates from the 1565 // client. 1566 if (!empty($original)) { 1567 Kronolith::addEvents( 1568 $events, $original, 1569 new Horde_Date($attributes->cstart), 1570 new Horde_Date($attributes->cend), 1571 true, true); 1572 if ($saveOriginal) { 1573 $original->save(); 1574 } 1575 } 1576 1577 // If this event recurs, we must add any bound exceptions to the 1578 // results 1579 if ($event->recurs()) { 1580 $bound = $event->boundExceptions(false); 1581 foreach ($bound as $day => &$exceptions) { 1582 foreach ($exceptions as &$exception) { 1583 $exception = $exception->toJson(); 1584 } 1585 } 1586 Kronolith::mergeEvents($events, $bound); 1587 } 1588 $result->events = count($events) ? $events : array(); 1589 } catch (Exception $e) { 1590 $GLOBALS['notification']->push($e, 'horde.error'); 1591 } 1592 return $result; 1593 } 1594 1595 /** 1596 * Creates a result object with the signature of the current request. 1597 * 1598 * @param string $calendar A calendar id. 1599 * 1600 * @return object The result object. 1601 */ 1602 protected function _signedResponse($calendar) 1603 { 1604 $result = new stdClass; 1605 $result->cal = $calendar; 1606 $result->view = $this->vars->view; 1607 $result->sig = $this->vars->sig; 1608 return $result; 1609 } 1610 1611 /** 1612 * Add an exception to the original event. 1613 * 1614 * @param Kronolith_Event $event The recurring event. 1615 * @param object $attributes The attributes passed from the client. 1616 * Expected to contain either rstart or rday. 1617 * 1618 * @return Kronolith_Event The event representing the exception, with 1619 * the start/end times set the same as the original 1620 * occurence. 1621 */ 1622 protected function _addException(Kronolith_Event $event, $attributes) 1623 { 1624 if ($attributes->rstart) { 1625 $rstart = new Horde_Date($attributes->rstart); 1626 $rstart->setTimezone($event->start->timezone); 1627 } else { 1628 $rstart = new Horde_Date($attributes->rday); 1629 $rstart->setTimezone($event->start->timezone); 1630 $rstart->hour = $event->start->hour; 1631 $rstart->min = $event->start->min; 1632 } 1633 $event->recurrence->addException($rstart->year, $rstart->month, $rstart->mday); 1634 $event->save(); 1635 } 1636 1637 /** 1638 * Creates a new event that represents an exception to a recurring event. 1639 * 1640 * @param Kronolith_Event $event The original recurring event. 1641 * @param Kronolith_Event $copy If present, contains a copy of $event, but 1642 * with changes from edited event form. 1643 * @param stdClass $attributes The attributes passed from the client. 1644 * Expected to contain rstart and rend or 1645 * rday that represents the original 1646 * starting/ending date of the instance. 1647 * 1648 * @return Kronolith_Event The event representing the exception 1649 */ 1650 protected function _copyEvent(Kronolith_Event $event, Kronolith_Event $copy = null, $attributes = null) 1651 { 1652 if (empty($copy)) { 1653 $copy = clone($event); 1654 } 1655 1656 if ($attributes->rstart) { 1657 $rstart = new Horde_Date($attributes->rstart); 1658 $rstart->setTimezone($event->start->timezone); 1659 $rend = new Horde_Date($attributes->rend); 1660 $rend->setTimezone($event->end->timezone); 1661 } else { 1662 $rstart = new Horde_Date($attributes->rday); 1663 $rstart->setTimezone($event->start->timezone); 1664 $rstart->hour = $event->start->hour; 1665 $rstart->min = $event->start->min; 1666 $rend = $rstart->add($event->getDuration); 1667 $rend->setTimezone($event->end->timezone); 1668 $rend->hour = $event->end->hour; 1669 $rend->min = $event->end->min; 1670 } 1671 $uid = $event->uid; 1672 $otime = $event->start->strftime('%T'); 1673 1674 // Create new event for the exception 1675 $nevent = $event->getDriver()->getEvent(); 1676 $nevent->baseid = $uid; 1677 $nevent->exceptionoriginaldate = new Horde_Date($rstart->strftime('%Y-%m-%d') . 'T' . $otime); 1678 $nevent->exceptionoriginaldate->setTimezone($event->start->timezone); 1679 $nevent->creator = $event->creator; 1680 $nevent->title = $copy->title; 1681 $nevent->description = $copy->description; 1682 $nevent->location = $copy->location; 1683 $nevent->private = $copy->private; 1684 $nevent->url = $copy->url; 1685 $nevent->status = $copy->status; 1686 $nevent->attendees = $copy->attendees; 1687 $nevent->setResources($copy->getResources()); 1688 $nevent->start = $rstart; 1689 $nevent->end = $rend; 1690 $nevent->initialized = true; 1691 1692 return $nevent; 1693 } 1694 1695} 1696