Ctrl K
ComunidadGuías…

tine20/Calendar/Controller.php

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
<?php
/**
 * Sql Calendar 
 * 
 * @package     Calendar
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2009-2023 Metaways Infosystems GmbH (http://www.metaways.de)
 */

/**
 * main controller for Calendar
 *
 * @package     Calendar
 */
class Calendar_Controller extends Tinebase_Controller_Event implements
    Tinebase_Application_Container_Interface,
    Felamimail_Controller_MassMailingPluginInterface
{
    /**
     * holds the instance of the singleton
     *
     * @var Calendar_Controller
     */
    private static $_instance = NULL;

    /**
     * holds the default Model of this application
     * @var string
     */
    protected static $_defaultModel = 'Calendar_Model_Event';

    /**
     * application name (is needed in checkRight())
     *
     * @var string
     */
    protected $_applicationName = 'Calendar';

    const XPROP_EXTERNAL_INVITATION_CALENDAR = 'externalInvitationCalendar';

    /**
     * don't clone. Use the singleton.
     *
     */
    private function __clone() 
    {
    }
    
    /**
     * the singleton pattern
     *
     * @return Calendar_Controller
     */
    public static function getInstance() 
    {
        if (self::$_instance === NULL) {
            self::$_instance = new Calendar_Controller();
        }
        
        return self::$_instance;
    }

    /**
     * event handler function
     * 
     * all events get routed through this function
     *
     * @param Tinebase_Event_Abstract $_eventObject the eventObject
     */
    protected function _handleEvent(Tinebase_Event_Abstract $_eventObject)
    {
        switch (get_class($_eventObject)) {
            case 'Admin_Event_AddAccount':
                Tinebase_Core::getPreference('Calendar')->getValueForUser(
                    Calendar_Preference::DEFAULTCALENDAR, $_eventObject->account->getId());
                break;

            case 'Calendar_Event_DeleteResource':
                /**
                 * @var Calendar_Event_DeleteResource $_eventObject
                 */
                $this->_deleteEventAttenders($_eventObject);

                break;

            case 'Tinebase_Event_User_DeleteAccount':
                /**
                 * @var Tinebase_Event_User_DeleteAccount $_eventObject
                 */
                $this->_handleDeleteUserEvent($_eventObject);

                // let event bubble
                parent::_handleEvent($_eventObject);
                break;
                
            case 'Admin_Event_UpdateGroup':
                if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                    Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__ . ' updated group '
                        . $_eventObject->group->name);
                }
                Tinebase_ActionQueue::getInstance()->queueAction('Calendar.onUpdateGroup', $_eventObject->group->getId());
                break;
            case 'Addressbook_Event_DeleteList':
                $this->_deleteEventAttenders($_eventObject);
                break;
            case Tinebase_Event_AddGroupMember::class:
                if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                    Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__ . ' Add group member '
                        . $_eventObject->userId . ' to group ' . $_eventObject->groupId);
                }
                Tinebase_ActionQueue::getInstance()->queueAction('Calendar.onUpdateGroup', $_eventObject->groupId);
                break;
                
            case Tinebase_Event_RemoveGroupMember::class:
                if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                    Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__ . ' Remove group member '
                        . $_eventObject->userId . ' from group ' . $_eventObject->groupId);
                }
                Tinebase_ActionQueue::getInstance()->queueAction('Calendar.onUpdateGroup', $_eventObject->groupId);
                break;
                
            case Tinebase_Event_Container_BeforeCreate::class:
                $this->_handleContainerBeforeCreateEvent($_eventObject);
                break;

            case Tinebase_Event_Container_AfterCreate::class:
                $this->_handleContainerAfterCreateEvent($_eventObject);
                break;
        }
    }

    protected function _handleDeleteUserEvent($_eventObject)
    {
        // this needs to happen before deletePersonalFolder. Otherwise the attender display container (the personal folder) is gone and the attendee filter doesnt work anymore!
        if (!$_eventObject->keepAsContact()) {
            // remove all event attenders for this contact
            $this->_deleteEventAttenders($_eventObject);
        }

        if ($_eventObject->keepOrganizerEvents()) {
            $this->_keepOrganizerEvents($_eventObject);
        }

        if ($_eventObject->deletePersonalContainers()) {
            $this->deletePersonalFolder($_eventObject->account, Calendar_Model_Event::class);
        }
    }

    /**
     * @param Tinebase_Event_Abstract $_eventObject
     */
    protected function _deleteEventAttenders(Tinebase_Event_Abstract $_eventObject)
    {
        if ($_eventObject instanceof Tinebase_Event_User_DeleteAccount) {
            $attenderId = $_eventObject->account->contact_id;
            $type = Calendar_Model_Attender::USERTYPE_USER;
        } elseif ($_eventObject instanceof Calendar_Event_DeleteResource) {
            $attenderId = $_eventObject->resource->getId();
            $type = Calendar_Model_Attender::USERTYPE_RESOURCE;
        }  else if ($_eventObject instanceof Addressbook_Event_DeleteList) {
            $attenderId = $_eventObject->list->getId();
            $type = Calendar_Model_Attender::USERTYPE_GROUP;
        } else {
            Tinebase_Core::getLogger()->err(__METHOD__ . ' ' . __LINE__ . ' unknown event type: ' .
                get_class($_eventObject));
            return;
        }

        $filter = new Calendar_Model_EventFilter(array(array(
            'field' => 'attender', 'operator' => 'in', 'value' => array(array(
                'user_type' => $type,
                'user_id'   => $attenderId,
            ))
        )));

        if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . ' '
            . __LINE__ . ' Deleting Event Attender ' . $attenderId . ' (type: ' . $type . ')');

        $eventController = Calendar_Controller_Event::getInstance();
        $oldAcl = $eventController->doContainerACLChecks(false);
        $oldSendNotification = $eventController->sendNotifications(false);

        $events = $eventController->search($filter);
        /** @var Calendar_Model_Event $event */
        foreach ($events as $event) {
            $toRemove = array();
            foreach ($event->attendee as $key => $attendee) {
                $attendeeUserId = $attendee->user_id instanceof Tinebase_Record_Interface
                    ? $attendee->user_id->getId()
                    : $attendee->user_id;

                if ($attendeeUserId === $attenderId &&
                    ($attendee->user_type === Calendar_Model_Attender::USERTYPE_USER ||
                        $attendee->user_type === Calendar_Model_Attender::USERTYPE_GROUPMEMBER ||
                        $attendee->user_type === Calendar_Model_Attender::USERTYPE_RESOURCE ||
                        $attendee->user_type === Calendar_Model_Attender::USERTYPE_GROUP
                    )) {
                    $toRemove[] = $key;
                }
            }
            if (count($toRemove) > 0) {
                foreach ($toRemove as $index) {
                    $event->attendee->offsetUnset($index);
                }

                $eventController->update($event);
            }
        }

        $eventController->doContainerACLChecks($oldAcl);
        $eventController->sendNotifications($oldSendNotification);
    }

    protected function _keepOrganizerEvents($_eventObject)
    {
        $accountId = $_eventObject->account->getId();
        $contactId = $_eventObject->account->contact_id;

        $contact = null;
        $newContact = null;
        $contactEmail = null;
        if ($_eventObject->keepAsContact()) {
            try {
                $contact = Addressbook_Controller_Contact::getInstance()->get($contactId);
                $contactEmail = $contact->getPreferredEmailAddress();
            } catch (Tinebase_Exception_NotFound $tenf) {
                // ignore
                $contactEmail = $_eventObject->account->accountEmailAddress;
            }
        } else {
            $contactEmail = $_eventObject->account->accountEmailAddress;
        }

        if (null === $contact) {
            $newContact = Calendar_Model_Attender::resolveEmailToContact(array(
                'email' => $contactEmail,
            ));
        }

        $eventController = Calendar_Controller_Event::getInstance();
        $oldState = $eventController->doContainerACLChecks(false);
        // delete all events where our deletee is organizer and that are private (no matter if they have attendees or not)
        /*$filter = new Calendar_Model_EventFilter(array(
            array('field' => 'class', 'operator' => 'equals', 'value' => Calendar_Model_Event::CLASS_PRIVATE),
            array('field' => 'organizer', 'operator' => 'equals', 'value' => $contactId),
        ));
        $eventController->deleteByFilter($filter);*/

        // delete all events where our deletee is organizer and that dont have any additional attenders except the organizer / deletee himself
        $filter = new Calendar_Model_EventFilter(array(
            array('field' => 'organizer', 'operator' => 'equals', 'value' => $contactId),
            array('field' => 'attender', 'operator' => 'notHasSomeExcept', 'value' => array(
                'user_type' => Calendar_Model_Attender::USERTYPE_USER,
                'user_id' => $contactId,
            )),
        ));
        $eventController->deleteByFilter($filter);

        $eventController->doContainerACLChecks($oldState);

        // get all personal containers
        $containers = Tinebase_Container::getInstance()->getPersonalContainer($accountId, Calendar_Model_Event::class, $accountId, '*', true);
        if ($containers->count() > 0) {
            // take the first one and make it an invitation container
            $container = $containers->getByIndex(0);
            $this->convertToInvitationContainer($container, $contactEmail);

            // if there are more than 1 container, move contents to invitation container, then delete them
            $i = 1;
            while ($containers->count() > 1) {
                $moveContainer = $containers->getByIndex($i);
                $containers->offsetUnset($i++);

                //move $moveContainer content to $container
                $eventController->getBackend()->moveEventsToContainer($moveContainer, $container);
                //delete $moveContainer
                Tinebase_Container::getInstance()->deleteContainer($moveContainer, true);
            }
        }

        // replace old contactId with newContact->getId()
        if (null !== $newContact) {
            $eventController->getBackend()->replaceContactId($contactId, $newContact->getId());
        }
    }

    /**
     * Converts the calendar to be a calendar for external organizer
     *
     * @param Tinebase_Model_Container $container
     */
    public function convertToInvitationContainer(Tinebase_Model_Container $container, $emailAddress)
    {
        if ($container->model !== 'Calendar_Model_Event') {
            Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__ . ' container provided needs to have the model Calendar_Model_Event instead of ' . $container->model);
            throw Tinebase_Exception_UnexpectedValue('container provided needs to have the model Calendar_Model_Event instead of ' . $container->model);
        }

        $tbc = Tinebase_Container::getInstance();
        try {
            $oldContainer = $tbc->getContainerByName(Calendar_Model_Event::class, $emailAddress, Tinebase_Model_Container::TYPE_SHARED);

            // TODO fix me!
            // bad, we should move the events from $oldContainer to $container

            $tbc->deleteContainer($oldContainer, true);
        } catch (Tinebase_Exception_NotFound $tenf) {
            //good, ignore
        }

        $container->name = $emailAddress;
        $container->color = '#333399';
        $container->type = Tinebase_Model_Container::TYPE_SHARED;
        $tbc->update($container);

        $grants = new Tinebase_Record_RecordSet($container->getGrantClass(), array(
            array(
                'account_id'      => '0',
                'account_type'    => Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE,
                Tinebase_Model_Grants::GRANT_ADD         => true,
                Tinebase_Model_Grants::GRANT_EDIT        => true,
//                Tinebase_Model_Grants::GRANT_DELETE      => true,
            )
        ));
        $tbc->setGrants($container->getId(), $grants, true, false);
    }

    /**
     * Get/Create Calendar for external organizer
     * 
     * @param  Addressbook_Model_Contact|null $organizer organizer id
     * @param  string $emailAddress
     * @return Tinebase_Model_Container  container id
     */
    public function getInvitationContainer($organizer, $emailAddress = null)
    {
        if (null !== $organizer) {
            $containerName = $organizer->getPreferredEmailAddress();
        } else {
            $containerName = $emailAddress;
        }

        if (empty($containerName)) {
            throw new Tinebase_Exception_UnexpectedValue('Event organizer does not have an email address');
        }
        
        try {
            $container = Tinebase_Container::getInstance()->getContainerByName(Calendar_Model_Event::class, $containerName, Tinebase_Model_Container::TYPE_SHARED);
        } catch (Tinebase_Exception_NotFound $tenf) {
            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
                    . ' No invitation container found. Creating a new one for organizer ' . $containerName);
            }
            
            $container = Tinebase_Container::getInstance()->addContainer(new Tinebase_Model_Container(array(
                'name'              => $containerName,
                'color'             => '#333399',
                'type'              => Tinebase_Model_Container::TYPE_SHARED,
                'backend'           => Tinebase_User::SQL,
                'application_id'    => Tinebase_Application::getInstance()->getApplicationByName('Calendar')->getId(),
                'model'             => 'Calendar_Model_Event',
                'xprops'            => [self::XPROP_EXTERNAL_INVITATION_CALENDAR => true],
            )), NULL, TRUE);
            
            $grants = new Tinebase_Record_RecordSet($container->getGrantClass(), array(
                array(
                    'account_id'      => '0',
                    'account_type'    => Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE,
                    Tinebase_Model_Grants::GRANT_ADD         => true,
                    Tinebase_Model_Grants::GRANT_EDIT        => true,
//                    Tinebase_Model_Grants::GRANT_DELETE      => true,
                )
            ));
            Tinebase_Container::getInstance()->setGrants($container, $grants, true, false);
        }
         
        return $container;
    }
    
    /**
     * creates the initial folder for new accounts
     *
     * @param mixed[int|Tinebase_Model_User] $_account   the account object
     * @return Tinebase_Record_RecordSet of subtype Tinebase_Model_Container
     */
    public function createPersonalFolder($_account)
    {
        $result = new Tinebase_Record_RecordSet(Tinebase_Model_Container::class);
        $translation = Tinebase_Translation::getTranslation($this->_applicationName);
        $account = Tinebase_User::getInstance()->getUserById($_account);
        $name = sprintf($translation->_("%s's personal calendar"), $account->accountFullName);
        $container = Tinebase_Container::getInstance()->createDefaultContainer(
            static::$_defaultModel,
            $this->_applicationName,
            $account,
            $name
        );
        $result->addRecord($container);
        return $result;
    }

    protected function _handleContainerAfterCreateEvent(Tinebase_Event_Container_AfterCreate $event): void
    {
        if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->INFO(__METHOD__ . ' ' . __LINE__
            . ' about to handle Tinebase_Event_Container_AfterCreate' );

        if ($syncConfig = $event->container->getRecordFromXProps([Calendar_Controller_Event::SYNC_CONTAINER], Calendar_Model_SyncContainerConfig::class)) {
            $syncConfig->sync($event->container, skipReadCollectionMetaData: true);
            $event->container->xprops()[Calendar_Controller_Event::SYNC_CONTAINER] = $syncConfig->dehydrate();
        }
    }

    /**
     * handler for Tinebase_Event_Container_BeforeCreate
     * - give owner of personal container all grants
     * - give freebusy grants to anyone for personal container
     * 
     * @param Tinebase_Event_Container_BeforeCreate $_eventObject
     */
    protected function _handleContainerBeforeCreateEvent(Tinebase_Event_Container_BeforeCreate $_eventObject)
    {
        if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . ' ' . __LINE__
            . ' about to handle Tinebase_Event_Container_BeforeCreate' );

        if ($syncConfig = $_eventObject->container->getRecordFromXProps([Calendar_Controller_Event::SYNC_CONTAINER], Calendar_Model_SyncContainerConfig::class)) {
            $syncConfig->sync();
            $_eventObject->container->xprops()[Calendar_Controller_Event::SYNC_CONTAINER] = $syncConfig->dehydrate();
        }

        $this->_addDefaultPersonalGrantsToContainer(
            $_eventObject->container,
            'Calendar',
            $_eventObject->grants,
            $_eventObject->accountId,
            [
                Calendar_Model_Event::class,
            ],
            Calendar_Model_EventPersonalGrants::class,
            ! Tinebase_Config::getInstance()->get(Tinebase_Config::ANYONE_ACCOUNT_DISABLED) ?
                [
                    new Calendar_Model_EventPersonalGrants(array(
                        'account_id' => '0',
                        'account_type' => Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE,
                        Calendar_Model_EventPersonalGrants::GRANT_FREEBUSY => true
                    ), true)
                ] : []
        );
    }
    
    /**
     * send notifications 
     * 
     * @param Calendar_Model_Event       $_event
     * @param Tinebase_Model_FullUser    $_updater
     * @param String                     $_action
     * @param Calendar_Model_Event       $_oldEvent
     * @return void
     */
    public function sendEventNotifications($_event, $_updater, $_action, $_oldEvent = NULL)
    {
        Calendar_Controller_EventNotifications::getInstance()->doSendNotifications($_event, $_updater, $_action, $_oldEvent);
    }
    
    /**
     * update group events
     * 
     * @param string $_groupId
     * @return void
     */
    public function onUpdateGroup($_groupId)
    {
        Calendar_Controller_Event::getInstance()->onUpdateGroup($_groupId);
    }

    /**
     * get core data for this application
     *
     * @return Tinebase_Record_RecordSet
     */
    public function getCoreDataForApplication()
    {
        $result = parent::getCoreDataForApplication();

        $application = Tinebase_Application::getInstance()->getApplicationByName($this->_applicationName);

        if (Tinebase_Core::getUser()->hasRight($application, Calendar_Acl_Rights::MANAGE_RESOURCES)) {
            $result->addRecord(new CoreData_Model_CoreData(array(
                'id' => 'cal_resources',
                'application_id' => $application,
                'model' => 'Calendar_Model_Resource',
                'label' => 'Resources' // _('Resources')
            )));
        }

        if (Tinebase_Core::getUser()->hasRight($application, Calendar_Acl_Rights::MANAGE_EVENT_TYPES) &&
            Calendar_Config::getInstance()->featureEnabled(Calendar_Config::FEATURE_EVENT_TYPE)
        ) {
            $result->addRecord(new CoreData_Model_CoreData(array(
                'id' => 'cal_event_types',
                'application_id' => $application,
                'model' => 'Calendar_Model_EventType',
                'label' => 'Event Types' // _('Event Types')
            )));
        }

        return $result;
    }

    /**
     * @param Felamimail_Model_Message $_message
     * @return null
     */
    public function prepareMassMailingMessage(Felamimail_Model_Message $_message, Tinebase_Twig $_twig, &$context)
    {
        if (Calendar_Config::getInstance()->featureEnabled(Calendar_Config::FEATURE_POLLS)) {
            Calendar_Controller_Poll::getInstance()->prepareMassMailingMessage($_message, $_twig, $context);
        }
        return;
    }

    public static function addFastRoutes(\FastRoute\RouteCollector $routeCollector): void
    {
        $routeCollector->addRoute('PROPFIND', '/.well-known/caldav', (new Tinebase_Expressive_RouteHandler(
            self::class, 'publicWellKnownCalDav', [
            Tinebase_Expressive_RouteHandler::IS_PUBLIC => true
        ]))->toArray());

        $routeCollector->addGroup('/Calendar', function (\FastRoute\RouteCollector $routeCollector) {
            if (Calendar_Config::getInstance()->featureEnabled(Calendar_Config::FEATURE_POLLS)) {
                $routeCollector->get('/view/pollagb', (new Tinebase_Expressive_RouteHandler(
                    Calendar_Controller_Poll::class, 'publicApiGetAGB', [
                    Tinebase_Expressive_RouteHandler::IS_PUBLIC => true
                ]))->toArray());
                $routeCollector->get('/view/poll/{pollId}[/{userKey}[/{authKey}]]', (new Tinebase_Expressive_RouteHandler(
                    Calendar_Controller_Poll::class, 'publicApiMainScreen', [
                    Tinebase_Expressive_RouteHandler::IS_PUBLIC => true,
                    Tinebase_Expressive_RouteHandler::UNAUTHORIZED_REDIRECT_LOGIN => true,
                ]))->toArray());
                $routeCollector->get('/poll/{pollId}[/{userKey}[/{authKey}]]', (new Tinebase_Expressive_RouteHandler(
                    Calendar_Controller_Poll::class, 'publicApiGetPoll', [
                    Tinebase_Expressive_RouteHandler::IS_PUBLIC => true
                ]))->toArray());
                $routeCollector->post('/poll/{pollId}', (new Tinebase_Expressive_RouteHandler(
                    Calendar_Controller_Poll::class, 'publicApiUpdateAttendeeStatus', [
                    Tinebase_Expressive_RouteHandler::IS_PUBLIC => true
                ]))->toArray());
                $routeCollector->post('/poll/join/{pollId}', (new Tinebase_Expressive_RouteHandler(
                    Calendar_Controller_Poll::class, 'publicApiAddAttendee', [
                    Tinebase_Expressive_RouteHandler::IS_PUBLIC => true
                ]))->toArray());
            }
            $routeCollector->get('/view/floorplan[/{floorplan}]', (new Tinebase_Expressive_RouteHandler(
                self::class, 'floorplanMainScreen', [
                Tinebase_Expressive_RouteHandler::IS_PUBLIC => false,
                Tinebase_Expressive_RouteHandler::UNAUTHORIZED_REDIRECT_LOGIN => true,
            ]))->toArray());
            $routeCollector->get('/freebusy/{urlId}', (new Tinebase_Expressive_RouteHandler(
                self::class, 'publicFreeBusy', [
                Tinebase_Expressive_RouteHandler::IS_PUBLIC => true,
            ]))->toArray());
        });
    }

    public function publicFreeBusy(string $urlId): \Psr\Http\Message\ResponseInterface
    {
        $freeBusyUrlCtrlRaii = new Tinebase_RAII(Calendar_Controller_FreeBusyUrl::getInstance()->assertPublicUsage());
        $resourceCtrlRaii = new Tinebase_RAII(Calendar_Controller_Resource::getInstance()->assertPublicUsage());

        $fbUrl = Calendar_Controller_FreeBusyUrl::getInstance()->get($urlId);

        switch ($fbUrl->{Calendar_Model_FreeBusyUrl::FLD_OWNER_CLASS}) {
            case Tinebase_Model_User::class:
                $oldUser = Tinebase_Core::getUser();
                $resetUser = new Tinebase_RAII(fn() => $oldUser ? Tinebase_Core::setUser($oldUser) : Tinebase_Core::unsetUser());
                Tinebase_Core::setUser($user = Tinebase_User::getInstance()->getUserById(
                    $fbUrl->getIdFromProperty(Calendar_Model_FreeBusyUrl::FLD_OWNER_ID)
                ));

                $response = $this->_getFBResponse(new Calendar_Model_Attender([
                    Calendar_Model_Attender::FLD_USER_TYPE => Calendar_Model_Attender::USERTYPE_USER,
                    Calendar_Model_Attender::FLD_USER_ID => $user->contact_id,
                ], true), $user->accountEmailAddress);

                unset($resetUser);
                break;
            case Calendar_Model_Resource::class:
                $eventCtrlRaii = new Tinebase_RAII(Calendar_Controller_Event::getInstance()->assertPublicUsage());

                $response = $this->_getFBResponse(new Calendar_Model_Attender([
                    Calendar_Model_Attender::FLD_USER_TYPE => Calendar_Model_Attender::USERTYPE_RESOURCE,
                    Calendar_Model_Attender::FLD_USER_ID => $fbUrl->{Calendar_Model_FreeBusyUrl::FLD_OWNER_ID}->getId(),
                ], true), $fbUrl->{Calendar_Model_FreeBusyUrl::FLD_OWNER_ID}->email); // TODO FIXME email might be empty?

                unset($eventCtrlRaii);
                break;
            default:
                throw new Tinebase_Exception_NotFound('free busy url owner class not supported');
        }

        unset($resourceCtrlRaii);
        unset($freeBusyUrlCtrlRaii);

        return $response;
    }


    protected function _getFBResponse(Calendar_Model_Attender $attendee, string $organizer): \Psr\Http\Message\ResponseInterface
    {
        $calendarController = Calendar_Controller_Event::getInstance();
        $allFb = $calendarController->getFreeBusyInfo(new Calendar_Model_EventFilter([[
            'field' => 'period',
            'operator' => 'within',
            'value' => ['from' => $from = Tinebase_DateTime::now(), 'until' => $until = Tinebase_DateTime::now()->addYear(1)],
        ]]), new Tinebase_Record_RecordSet(Calendar_Model_Attender::class, [$attendee]));

        $vcalendar = new \Sabre\VObject\Component\VCalendar();
        $vcalendar->METHOD = 'PUBLISH'; // @phpstan-ignore-line
        $vFree = $vcalendar->createComponent('VFREEBUSY', [
            'DTSTAMP' => Tinebase_DateTime::now(),
            'DTSTART' => $from->getClone(),
            'DTEND' => $until->getClone(),
            'ORGANIZER' => $organizer,
        ]);
        $vcalendar->add($vFree);

        /** @var Calendar_Model_FreeBusy $freeBusy */
        foreach ($allFb as $freeBusy) {
            if (Calendar_Model_FreeBusy::FREEBUSY_FREE === $freeBusy->type) {
                continue;
            }
            $type = 'BUSY';
            if (Calendar_Model_FreeBusy::FREEBUSY_BUSY_TENTATIVE === $freeBusy->type) {
                $type = 'BUSY-TENTATIVE';
            }
            $vFree->add('FREEBUSY', $freeBusy->dtstart->format('Ymd\THis\Z') . '/' . $freeBusy->dtend->format('Ymd\THis\Z'), ['FBTYPE' => $type]);
        }

        $response = new \Laminas\Diactoros\Response(headers: [
            'Content-Type' => 'text/calendar; charset=utf-8',
            'Cache-Control' => 'no-store',
        ]);
        $response->getBody()->write($vcalendar->serialize());
        return $response;
    }

    public function publicWellKnownCalDav(): \Psr\Http\Message\ResponseInterface
    {
        return new \Laminas\Diactoros\Response(status: 301, headers: ['Location' => '/calendars']);
    }

    public function floorplanMainScreen($floorplan=0)
    {
        $locale = Tinebase_Core::getLocale();

        $jsFiles[] = "index.php?method=Tinebase.getJsTranslations&locale={$locale}&app=Calendar";
        $jsFiles[] = 'Calendar/js/floorplan/src/index.es6.js';

        $user = Tinebase_Core::getUser();
        $userContactArray = array();
        if (Tinebase_Application::getInstance()->isInstalled('Addressbook') === true) {
            try {
                $userContactArray = Addressbook_Controller_Contact::getInstance()->getContactByUserId($user->getId(), TRUE)->toArray();
            } catch (Addressbook_Exception_NotFound $aenf) {
                if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) /** @noinspection PhpUndefinedMethodInspection */
                    Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
                        . ' User not found in Addressbook: ' . $user->accountDisplayName);
            }
        }

        return Tinebase_Frontend_Http_SinglePageApplication::getClientHTML($jsFiles, Calendar_Config::APP_NAME, context: [
            'base' => Tinebase_Core::getUrl(Tinebase_Core::GET_URL_PATH),
            'lang' => $locale,
            'initialData' => [
                'floorplans' => Calendar_Config::getInstance()->{Calendar_Config::FLOORPLANS},
                'resources' => Calendar_Controller_Resource::getInstance()->getAll()->toArray(),
                'currentContact' => $userContactArray,
                'jsonKey' => Tinebase_Core::get('jsonKey'),
                'locale' => $locale->toString(),
                'broadcasthubConfig' => Tinebase_Config::getInstance()->getClientRegistryConfig()['Tinebase']['broadcasthub']['value']
            ]
        ]);
    }
}