Add a principal and a caldav backend for sabre to work with.
Ticket #31
This commit is contained in:
parent
2f1d60f3cc
commit
539616f7b7
2 changed files with 463 additions and 0 deletions
|
@ -0,0 +1,305 @@
|
|||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: tim
|
||||
* Date: 15.11.14
|
||||
* Time: 17:13
|
||||
*/
|
||||
|
||||
namespace Hackspace\Bundle\CalciferBundle\libs;
|
||||
|
||||
|
||||
use Jsvrcek\ICS\CalendarStream;
|
||||
use Jsvrcek\ICS\Model\CalendarEvent;
|
||||
use Jsvrcek\ICS\Utility\Formatter;
|
||||
use Sabre\CalDAV\Backend\AbstractBackend;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Hackspace\Bundle\CalciferBundle\Entity\Event;
|
||||
|
||||
class CalciferCaldavBackend extends AbstractBackend
|
||||
{
|
||||
/** @var Controller */
|
||||
private $controller = null;
|
||||
|
||||
function __construct(Controller $controller)
|
||||
{
|
||||
$this->controller = $controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of calendars for a principal.
|
||||
*
|
||||
* Every project is an array with the following keys:
|
||||
* * id, a unique id that will be used by other functions to modify the
|
||||
* calendar. This can be the same as the uri or a database key.
|
||||
* * uri, which the basename of the uri with which the calendar is
|
||||
* accessed.
|
||||
* * principaluri. The owner of the calendar. Almost always the same as
|
||||
* principalUri passed to this method.
|
||||
*
|
||||
* Furthermore it can contain webdav properties in clark notation. A very
|
||||
* common one is '{DAV:}displayname'.
|
||||
*
|
||||
* Many clients also require:
|
||||
* {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
|
||||
* For this property, you can just return an instance of
|
||||
* Sabre\CalDAV\Property\SupportedCalendarComponentSet.
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @return array
|
||||
*/
|
||||
public function getCalendarsForUser($principalUri)
|
||||
{
|
||||
return [[
|
||||
'id' => 1,
|
||||
'uri' => 'calendar',
|
||||
'principaluri' => '/caldav/calcifer',
|
||||
]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new calendar for a principal.
|
||||
*
|
||||
* If the creation was a success, an id must be returned that can be used to reference
|
||||
* this calendar in other methods, such as updateCalendar.
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @param string $calendarUri
|
||||
* @param array $properties
|
||||
* @return void
|
||||
*/
|
||||
public function createCalendar($principalUri, $calendarUri, array $properties)
|
||||
{
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a calendar and all it's objects
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
* @return void
|
||||
*/
|
||||
public function deleteCalendar($calendarId)
|
||||
{
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
private function FormatCalendarEvent(CalendarEvent $event)
|
||||
{
|
||||
$stream = new CalendarStream();
|
||||
$formatter = new Formatter();
|
||||
$stream->addItem('BEGIN:VEVENT')
|
||||
->addItem('UID:' . $event->getUid())
|
||||
->addItem('DTSTART:' . $formatter->getFormattedUTCDateTime($event->getStart()))
|
||||
->addItem('DTEND:' . $formatter->getFormattedUTCDateTime($event->getEnd()))
|
||||
->addItem('SUMMARY:' . $event->getSummary())
|
||||
->addItem('DESCRIPTION:' . $event->getDescription());
|
||||
|
||||
if ($event->getClass())
|
||||
$stream->addItem('CLASS:' . $event->getClass());
|
||||
|
||||
/* @var $location Location */
|
||||
foreach ($event->getLocations() as $location) {
|
||||
$stream
|
||||
->addItem('LOCATION' . $location->getUri() . $location->getLanguage() . ':' . $location->getName());
|
||||
}
|
||||
|
||||
if ($event->getGeo())
|
||||
$stream->addItem('GEO:' . $event->getGeo()->getLatitude() . ';' . $event->getGeo()->getLongitude());
|
||||
|
||||
if ($event->getUrl())
|
||||
$stream->addItem('URL:' . $event->getUrl());
|
||||
|
||||
if ($event->getCreated())
|
||||
$stream->addItem('CREATED:' . $formatter->getFormattedUTCDateTime($event->getCreated()));
|
||||
|
||||
if ($event->getLastModified())
|
||||
$stream->addItem('LAST-MODIFIED:' . $formatter->getFormattedUTCDateTime($event->getLastModified()));
|
||||
|
||||
foreach ($event->getAttendees() as $attendee) {
|
||||
$stream->addItem($attendee->__toString());
|
||||
}
|
||||
|
||||
if ($event->getOrganizer())
|
||||
$stream->addItem($event->getOrganizer()->__toString());
|
||||
|
||||
$stream->addItem('END:VEVENT');
|
||||
|
||||
return $stream->getStream();
|
||||
}
|
||||
|
||||
private function formatEvent(Event $event)
|
||||
{
|
||||
/** @var CalendarEvent $calendar_event */
|
||||
$calendar_event = $event->ConvertToCalendarEvent();
|
||||
$calendar_data = $this->FormatCalendarEvent($calendar_event);
|
||||
|
||||
$event_data = [
|
||||
'id' => $event->id,
|
||||
'uri' => $event->slug,
|
||||
'lastmodified' => $event->startdate,
|
||||
'etag' => '"' . sha1($calendar_data) . '"',
|
||||
'calendarid' => 1,
|
||||
'calendardata' => $calendar_data,
|
||||
'size' => strlen($calendar_data),
|
||||
'component' => 'VEVENT',
|
||||
];
|
||||
return $event_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all calendar objects within a calendar.
|
||||
*
|
||||
* Every item contains an array with the following keys:
|
||||
* * id - unique identifier which will be used for subsequent updates
|
||||
* * calendardata - The iCalendar-compatible calendar data
|
||||
* * uri - a unique key which will be used to construct the uri. This can
|
||||
* be any arbitrary string, but making sure it ends with '.ics' is a
|
||||
* good idea. This is only the basename, or filename, not the full
|
||||
* path.
|
||||
* * lastmodified - a timestamp of the last modification time
|
||||
* * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
|
||||
* '"abcdef"')
|
||||
* * calendarid - The calendarid as it was passed to this function.
|
||||
* * size - The size of the calendar objects, in bytes.
|
||||
* * component - optional, a string containing the type of object, such
|
||||
* as 'vevent' or 'vtodo'. If specified, this will be used to populate
|
||||
* the Content-Type header.
|
||||
*
|
||||
* Note that the etag is optional, but it's highly encouraged to return for
|
||||
* speed reasons.
|
||||
*
|
||||
* The calendardata is also optional. If it's not returned
|
||||
* 'getCalendarObject' will be called later, which *is* expected to return
|
||||
* calendardata.
|
||||
*
|
||||
* If neither etag or size are specified, the calendardata will be
|
||||
* used/fetched to determine these numbers. If both are specified the
|
||||
* amount of times this is needed is reduced by a great degree.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
* @return array
|
||||
*/
|
||||
public function getCalendarObjects($calendarId)
|
||||
{
|
||||
/** @var EntityManager $em */
|
||||
$em = $this->controller->getDoctrine()->getManager();
|
||||
|
||||
$now = new \DateTime();
|
||||
$now->setTime(0, 0, 0);
|
||||
/** @var QueryBuilder $qb */
|
||||
$qb = $em->createQueryBuilder();
|
||||
$qb->select(array('e'))
|
||||
->from('CalciferBundle:Event', 'e')
|
||||
->where('e.startdate >= :startdate')
|
||||
->orderBy('e.startdate')
|
||||
->setParameter('startdate', $now);
|
||||
$entities = $qb->getQuery()->execute();
|
||||
|
||||
if (count($entities) > 0) {
|
||||
$events = [];
|
||||
foreach ($entities as $event) {
|
||||
/** @var Event $event */
|
||||
$events[] = $this->formatEvent($event);
|
||||
}
|
||||
return $events;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information from a single calendar object, based on it's object
|
||||
* uri.
|
||||
*
|
||||
* The object uri is only the basename, or filename and not a full path.
|
||||
*
|
||||
* The returned array must have the same keys as getCalendarObjects. The
|
||||
* 'calendardata' object is required here though, while it's not required
|
||||
* for getCalendarObjects.
|
||||
*
|
||||
* This method must return null if the object did not exist.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
* @param string $objectUri
|
||||
* @return array|null
|
||||
*/
|
||||
public function getCalendarObject($calendarId, $objectUri)
|
||||
{
|
||||
/** @var EntityManager $em */
|
||||
$em = $this->controller->getDoctrine()->getManager();
|
||||
|
||||
/** @var EntityRepository $repo */
|
||||
$repo = $em->getRepository('CalciferBundle:Event');
|
||||
|
||||
/** @var Event $entity */
|
||||
$event = $repo->findOneBy(['slug' => $objectUri]);
|
||||
|
||||
if (!($event instanceof Event)) {
|
||||
throw $this->controller->createNotFoundException('Unable to find Event entity.');
|
||||
}
|
||||
|
||||
return $this->formatEvent($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new calendar object.
|
||||
*
|
||||
* The object uri is only the basename, or filename and not a full path.
|
||||
*
|
||||
* It is possible return an etag from this function, which will be used in
|
||||
* the response to this PUT request. Note that the ETag must be surrounded
|
||||
* by double-quotes.
|
||||
*
|
||||
* However, you should only really return this ETag if you don't mangle the
|
||||
* calendar-data. If the result of a subsequent GET to this object is not
|
||||
* the exact same as this request body, you should omit the ETag.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
* @param string $objectUri
|
||||
* @param string $calendarData
|
||||
* @return string|null
|
||||
*/
|
||||
public function createCalendarObject($calendarId, $objectUri, $calendarData)
|
||||
{
|
||||
// TODO: Implement createCalendarObject() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing calendarobject, based on it's uri.
|
||||
*
|
||||
* The object uri is only the basename, or filename and not a full path.
|
||||
*
|
||||
* It is possible return an etag from this function, which will be used in
|
||||
* the response to this PUT request. Note that the ETag must be surrounded
|
||||
* by double-quotes.
|
||||
*
|
||||
* However, you should only really return this ETag if you don't mangle the
|
||||
* calendar-data. If the result of a subsequent GET to this object is not
|
||||
* the exact same as this request body, you should omit the ETag.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
* @param string $objectUri
|
||||
* @param string $calendarData
|
||||
* @return string|null
|
||||
*/
|
||||
public function updateCalendarObject($calendarId, $objectUri, $calendarData)
|
||||
{
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an existing calendar object.
|
||||
*
|
||||
* The object uri is only the basename, or filename and not a full path.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
* @param string $objectUri
|
||||
* @return void
|
||||
*/
|
||||
public function deleteCalendarObject($calendarId, $objectUri)
|
||||
{
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: tim
|
||||
* Date: 15.11.14
|
||||
* Time: 19:45
|
||||
*/
|
||||
|
||||
namespace Hackspace\Bundle\CalciferBundle\libs;
|
||||
|
||||
use
|
||||
Sabre\DAV,
|
||||
Sabre\DAVACL,
|
||||
Sabre\HTTP\URLUtil;
|
||||
|
||||
class CalciferPrincipalBackend extends DAVACL\PrincipalBackend\AbstractBackend
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns a list of principals based on a prefix.
|
||||
*
|
||||
* This prefix will often contain something like 'principals'. You are only
|
||||
* expected to return principals that are in this base path.
|
||||
*
|
||||
* You are expected to return at least a 'uri' for every user, you can
|
||||
* return any additional properties if you wish so. Common properties are:
|
||||
* {DAV:}displayname
|
||||
* {http://sabredav.org/ns}email-address - This is a custom SabreDAV
|
||||
* field that's actually injected in a number of other properties. If
|
||||
* you have an email address, use this property.
|
||||
*
|
||||
* @param string $prefixPath
|
||||
* @return array
|
||||
*/
|
||||
function getPrincipalsByPrefix($prefixPath)
|
||||
{
|
||||
return [
|
||||
[
|
||||
'{DAV:}displayname' => 'calcifer',
|
||||
'{http://sabredav.org/ns}email-address' => 'calcifer@example.com',
|
||||
'uri' => '/caldav/calcifer',
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specific principal, specified by it's path.
|
||||
* The returned structure should be the exact same as from
|
||||
* getPrincipalsByPrefix.
|
||||
*
|
||||
* @param string $path
|
||||
* @return array
|
||||
*/
|
||||
function getPrincipalByPath($path)
|
||||
{
|
||||
return [
|
||||
'{DAV:}displayname' => 'calcifer',
|
||||
'{http://sabredav.org/ns}email-address' => 'calcifer@example.com',
|
||||
'uri' => '/caldav/calcifer',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates one ore more webdav properties on a principal.
|
||||
*
|
||||
* The list of mutations is stored in a Sabre\DAV\PropPatch object.
|
||||
* To do the actual updates, you must tell this object which properties
|
||||
* you're going to process with the handle() method.
|
||||
*
|
||||
* Calling the handle method is like telling the PropPatch object "I
|
||||
* promise I can handle updating this property".
|
||||
*
|
||||
* Read the PropPatch documenation for more info and examples.
|
||||
*
|
||||
* @param string $path
|
||||
* @param \Sabre\DAV\PropPatch $propPatch
|
||||
* @return void
|
||||
*/
|
||||
function updatePrincipal($path, \Sabre\DAV\PropPatch $propPatch)
|
||||
{
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to search for principals matching a set of
|
||||
* properties.
|
||||
*
|
||||
* This search is specifically used by RFC3744's principal-property-search
|
||||
* REPORT. You should at least allow searching on
|
||||
* http://sabredav.org/ns}email-address.
|
||||
*
|
||||
* The actual search should be a unicode-non-case-sensitive search. The
|
||||
* keys in searchProperties are the WebDAV property names, while the values
|
||||
* are the property values to search on.
|
||||
*
|
||||
* If multiple properties are being searched on, the search should be
|
||||
* AND'ed.
|
||||
*
|
||||
* This method should simply return an array with full principal uri's.
|
||||
*
|
||||
* If somebody attempted to search on a property the backend does not
|
||||
* support, you should simply return 0 results.
|
||||
*
|
||||
* You can also just return 0 results if you choose to not support
|
||||
* searching at all, but keep in mind that this may stop certain features
|
||||
* from working.
|
||||
*
|
||||
* @param string $prefixPath
|
||||
* @param array $searchProperties
|
||||
* @return array
|
||||
*/
|
||||
function searchPrincipals($prefixPath, array $searchProperties)
|
||||
{
|
||||
return [
|
||||
[
|
||||
'{DAV:}displayname' => 'calcifer',
|
||||
'{http://sabredav.org/ns}email-address' => 'calcifer@example.com',
|
||||
'uri' => '/caldav/calcifer',
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of members for a group-principal
|
||||
*
|
||||
* @param string $principal
|
||||
* @return array
|
||||
*/
|
||||
function getGroupMemberSet($principal)
|
||||
{
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of groups a principal is a member of
|
||||
*
|
||||
* @param string $principal
|
||||
* @return array
|
||||
*/
|
||||
function getGroupMembership($principal)
|
||||
{
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the list of group members for a group principal.
|
||||
*
|
||||
* The principals should be passed as a list of uri's.
|
||||
*
|
||||
* @param string $principal
|
||||
* @param array $members
|
||||
* @return void
|
||||
*/
|
||||
function setGroupMemberSet($principal, array $members)
|
||||
{
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
}
|
Reference in a new issue