From b3a4836a10d4eef4e192617810d7d57fadbff971 Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Thu, 2 Jun 2016 16:01:44 +0200 Subject: [PATCH] Reworked ics handling by using sabre vobject This results in more clean ics files, which for example respect spaces which occur directly after a internal line break. --- app/SymfonyRequirements.php | 63 ++-- app/check.php | 6 +- composer.json | 2 +- composer.lock | 275 +++++++-------- .../Controller/EventController.php | 68 +--- .../Controller/LocationController.php | 22 +- .../Controller/TagController.php | 28 +- .../Bundle/CalciferBundle/Entity/Event.php | 50 ++- .../libs/CalciferCaldavBackend.php | 312 ------------------ .../libs/CalciferPrincipalBackend.php | 158 --------- 10 files changed, 204 insertions(+), 780 deletions(-) delete mode 100644 src/Hackspace/Bundle/CalciferBundle/libs/CalciferCaldavBackend.php delete mode 100644 src/Hackspace/Bundle/CalciferBundle/libs/CalciferPrincipalBackend.php diff --git a/app/SymfonyRequirements.php b/app/SymfonyRequirements.php index cbcf1c8..28b0dcd 100644 --- a/app/SymfonyRequirements.php +++ b/app/SymfonyRequirements.php @@ -77,7 +77,7 @@ class Requirement } /** - * Returns the help text for resolving the problem + * Returns the help text for resolving the problem. * * @return string The help text */ @@ -119,10 +119,10 @@ class PhpIniRequirement extends Requirement * * @param string $cfgName The configuration name used for ini_get() * @param bool|callback $evaluation Either a boolean indicating whether the configuration should evaluate to true or false, - or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement + * or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement * @param bool $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false. - This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin. - Example: You require a config to be true but PHP later removes this config and defaults it to true internally. + * This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin. + * Example: You require a config to be true but PHP later removes this config and defaults it to true internally. * @param string|null $testMessage The message for testing the requirement (when null and $evaluation is a boolean a default message is derived) * @param string|null $helpHtml The help text formatted in HTML for resolving the problem (when null and $evaluation is a boolean a default help is derived) * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) @@ -221,10 +221,10 @@ class RequirementCollection implements IteratorAggregate * * @param string $cfgName The configuration name used for ini_get() * @param bool|callback $evaluation Either a boolean indicating whether the configuration should evaluate to true or false, - or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement + * or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement * @param bool $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false. - This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin. - Example: You require a config to be true but PHP later removes this config and defaults it to true internally. + * This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin. + * Example: You require a config to be true but PHP later removes this config and defaults it to true internally. * @param string $testMessage The message for testing the requirement (when null and $evaluation is a boolean a default message is derived) * @param string $helpHtml The help text formatted in HTML for resolving the problem (when null and $evaluation is a boolean a default help is derived) * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) @@ -239,10 +239,10 @@ class RequirementCollection implements IteratorAggregate * * @param string $cfgName The configuration name used for ini_get() * @param bool|callback $evaluation Either a boolean indicating whether the configuration should evaluate to true or false, - or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement + * or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement * @param bool $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false. - This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin. - Example: You require a config to be true but PHP later removes this config and defaults it to true internally. + * This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin. + * Example: You require a config to be true but PHP later removes this config and defaults it to true internally. * @param string $testMessage The message for testing the requirement (when null and $evaluation is a boolean a default message is derived) * @param string $helpHtml The help text formatted in HTML for resolving the problem (when null and $evaluation is a boolean a default help is derived) * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) @@ -446,6 +446,12 @@ class SymfonyRequirements extends RequirementCollection ); } + $this->addRequirement( + function_exists('iconv'), + 'iconv() must be available', + 'Install and enable the iconv extension.' + ); + $this->addRequirement( function_exists('json_encode'), 'json_encode() must be available', @@ -542,11 +548,22 @@ class SymfonyRequirements extends RequirementCollection /* optional recommendations follow */ - $this->addRecommendation( - file_get_contents(__FILE__) === file_get_contents(__DIR__.'/../vendor/sensio/distribution-bundle/Sensio/Bundle/DistributionBundle/Resources/skeleton/app/SymfonyRequirements.php'), - 'Requirements file should be up-to-date', - 'Your requirements file is outdated. Run composer install and re-check your configuration.' - ); + if (file_exists(__DIR__.'/../vendor/composer')) { + require_once __DIR__.'/../vendor/autoload.php'; + + try { + $r = new ReflectionClass('Sensio\Bundle\DistributionBundle\SensioDistributionBundle'); + + $contents = file_get_contents(dirname($r->getFileName()).'/Resources/skeleton/app/SymfonyRequirements.php'); + } catch (ReflectionException $e) { + $contents = ''; + } + $this->addRecommendation( + file_get_contents(__FILE__) === $contents, + 'Requirements file should be up-to-date', + 'Your requirements file is outdated. Run composer install and re-check your configuration.' + ); + } $this->addRecommendation( version_compare($installedPhpVersion, '5.3.4', '>='), @@ -627,20 +644,20 @@ class SymfonyRequirements extends RequirementCollection } $this->addRecommendation( - class_exists('Locale'), + extension_loaded('intl'), 'intl extension should be available', 'Install and enable the intl extension (used for validators).' ); - if (class_exists('Collator')) { + if (extension_loaded('intl')) { + // in some WAMP server installations, new Collator() returns null $this->addRecommendation( null !== new Collator('fr_FR'), 'intl extension should be correctly configured', 'The intl extension does not behave properly. This problem is typical on PHP 5.3.X x64 WIN builds.' ); - } - if (class_exists('Locale')) { + // check for compatible ICU versions (only done when you have the intl extension) if (defined('INTL_ICU_VERSION')) { $version = INTL_ICU_VERSION; } else { @@ -659,6 +676,14 @@ class SymfonyRequirements extends RequirementCollection 'intl ICU version should be at least 4+', 'Upgrade your intl extension with a newer ICU version (4+).' ); + + $this->addPhpIniRecommendation( + 'intl.error_level', + create_function('$cfgValue', 'return (int) $cfgValue === 0;'), + true, + 'intl.error_level should be 0 in php.ini', + 'Set "intl.error_level" to "0" in php.ini* to inhibit the messages when an error occurs in ICU functions.' + ); } $accelerator = diff --git a/app/check.php b/app/check.php index 90bad4a..4283cde 100644 --- a/app/check.php +++ b/app/check.php @@ -6,7 +6,7 @@ $lineSize = 70; $symfonyRequirements = new SymfonyRequirements(); $iniPath = $symfonyRequirements->getPhpIniConfigPath(); -echo_title('Symfony2 Requirements Checker'); +echo_title('Symfony Requirements Checker'); echo '> PHP is using the following php.ini file:'.PHP_EOL; if ($iniPath) { @@ -42,9 +42,9 @@ foreach ($symfonyRequirements->getRecommendations() as $req) { } if ($checkPassed) { - echo_block('success', 'OK', 'Your system is ready to run Symfony2 projects', true); + echo_block('success', 'OK', 'Your system is ready to run Symfony projects'); } else { - echo_block('error', 'ERROR', 'Your system is not ready to run Symfony2 projects', true); + echo_block('error', 'ERROR', 'Your system is not ready to run Symfony projects'); echo_title('Fix the following mandatory requirements', 'red'); diff --git a/composer.json b/composer.json index f8723ff..64b368a 100755 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "doctrine/doctrine-migrations-bundle": "dev-master", "jbroadway/urlify" : "~1.0", "enko/relativedateparser" : "0.5", - "sabre/dav" : "~2.0" + "sabre/vobject": "^4.1" }, "require-dev": { "sensio/generator-bundle": "~2.3" diff --git a/composer.lock b/composer.lock index 791ed4b..50245d6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "929ec448ee4bd9047bda7086b30492ef", - "content-hash": "855e0330604c2434a4fbbc453c11e2b0", + "hash": "b653fb6b6230d43a6048e92f7b208ff8", + "content-hash": "3a7c343089ce6886d5fefe973df0b6cc", "packages": [ { "name": "doctrine/annotations", @@ -1583,95 +1583,21 @@ "time": "2012-12-21 11:40:51" }, { - "name": "sabre/dav", - "version": "2.1.10", + "name": "sabre/uri", + "version": "1.1.0", "source": { "type": "git", - "url": "https://github.com/fruux/sabre-dav.git", - "reference": "9f8c1939a3f66eb7170489fc48579ffd1461af62" + "url": "https://github.com/fruux/sabre-uri.git", + "reference": "9012116434d84ef6e5e37a89dfdbfbe2204a8704" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fruux/sabre-dav/zipball/9f8c1939a3f66eb7170489fc48579ffd1461af62", - "reference": "9f8c1939a3f66eb7170489fc48579ffd1461af62", + "url": "https://api.github.com/repos/fruux/sabre-uri/zipball/9012116434d84ef6e5e37a89dfdbfbe2204a8704", + "reference": "9012116434d84ef6e5e37a89dfdbfbe2204a8704", "shasum": "" }, "require": { - "ext-ctype": "*", - "ext-date": "*", - "ext-dom": "*", - "ext-iconv": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-pcre": "*", - "ext-simplexml": "*", - "ext-spl": "*", - "php": ">=5.4.1", - "sabre/event": "^2.0.0", - "sabre/http": "^3.0.0", - "sabre/vobject": "^3.3.4" - }, - "require-dev": { - "evert/phpdoc-md": "~0.1.0", - "phpunit/phpunit": "~4.2", - "squizlabs/php_codesniffer": "~1.5.3" - }, - "suggest": { - "ext-curl": "*", - "ext-pdo": "*" - }, - "bin": [ - "bin/sabredav", - "bin/naturalselection" - ], - "type": "library", - "autoload": { - "psr-4": { - "Sabre\\DAV\\": "lib/DAV/", - "Sabre\\DAVACL\\": "lib/DAVACL/", - "Sabre\\CalDAV\\": "lib/CalDAV/", - "Sabre\\CardDAV\\": "lib/CardDAV/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Evert Pot", - "email": "me@evertpot.com", - "homepage": "http://evertpot.com/", - "role": "Developer" - } - ], - "description": "WebDAV Framework for PHP", - "homepage": "http://sabre.io/", - "keywords": [ - "CalDAV", - "CardDAV", - "WebDAV", - "framework", - "iCalendar" - ], - "time": "2016-03-10 20:49:48" - }, - { - "name": "sabre/event", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/fruux/sabre-event.git", - "reference": "337b6f5e10ea6e0b21e22c7e5788dd3883ae73ff" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fruux/sabre-event/zipball/337b6f5e10ea6e0b21e22c7e5788dd3883ae73ff", - "reference": "337b6f5e10ea6e0b21e22c7e5788dd3883ae73ff", - "shasum": "" - }, - "require": { - "php": ">=5.4.1" + "php": ">=5.4.7" }, "require-dev": { "phpunit/phpunit": "*", @@ -1679,8 +1605,11 @@ }, "type": "library", "autoload": { + "files": [ + "lib/functions.php" + ], "psr-4": { - "Sabre\\Event\\": "lib/" + "Sabre\\Uri\\": "lib/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1695,90 +1624,40 @@ "role": "Developer" } ], - "description": "sabre/event is a library for lightweight event-based programming", - "homepage": "http://sabre.io/event/", + "description": "Functions for making sense out of URIs.", + "homepage": "http://sabre.io/uri/", "keywords": [ - "EventEmitter", - "events", - "hooks", - "plugin", - "promise", - "signal" + "rfc3986", + "uri", + "url" ], - "time": "2015-05-19 10:24:22" - }, - { - "name": "sabre/http", - "version": "3.0.5", - "source": { - "type": "git", - "url": "https://github.com/fruux/sabre-http.git", - "reference": "6b06c03376219b3d608e1f878514ec105ed1b577" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fruux/sabre-http/zipball/6b06c03376219b3d608e1f878514ec105ed1b577", - "reference": "6b06c03376219b3d608e1f878514ec105ed1b577", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=5.4", - "sabre/event": ">=1.0.0,<3.0.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.3", - "squizlabs/php_codesniffer": "~1.5.3" - }, - "suggest": { - "ext-curl": " to make http requests with the Client class" - }, - "type": "library", - "autoload": { - "psr-4": { - "Sabre\\HTTP\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Evert Pot", - "email": "me@evertpot.com", - "homepage": "http://evertpot.com/", - "role": "Developer" - } - ], - "description": "The sabre/http library provides utilities for dealing with http requests and responses. ", - "homepage": "https://github.com/fruux/sabre-http", - "keywords": [ - "http" - ], - "time": "2015-05-11 15:25:57" + "time": "2016-03-08 02:29:27" }, { "name": "sabre/vobject", - "version": "3.5.2", + "version": "4.1.0", "source": { "type": "git", "url": "https://github.com/fruux/sabre-vobject.git", - "reference": "b7d6005b9f8e18bfe2b953d9847df0b3e4098441" + "reference": "8899c0e856b3178b17f4e9a4e85010209f32a2fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fruux/sabre-vobject/zipball/b7d6005b9f8e18bfe2b953d9847df0b3e4098441", - "reference": "b7d6005b9f8e18bfe2b953d9847df0b3e4098441", + "url": "https://api.github.com/repos/fruux/sabre-vobject/zipball/8899c0e856b3178b17f4e9a4e85010209f32a2fa", + "reference": "8899c0e856b3178b17f4e9a4e85010209f32a2fa", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": ">=5.3.1" + "php": ">=5.5", + "sabre/xml": "~1.1" }, "require-dev": { "phpunit/phpunit": "*", - "squizlabs/php_codesniffer": "*" + "sabre/cs": "~0.0.3" + }, + "suggest": { + "hoa/bench": "If you would like to run the benchmark scripts" }, "bin": [ "bin/vobject", @@ -1787,7 +1666,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2.x-dev" + "dev-master": "4.0.x-dev" } }, "autoload": { @@ -1811,18 +1690,106 @@ "email": "dominik@fruux.com", "homepage": "http://tobschall.de/", "role": "Developer" + }, + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net", + "homepage": "http://mnt.io/", + "role": "Developer" } ], "description": "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects", "homepage": "http://sabre.io/vobject/", "keywords": [ - "VObject", + "availability", + "freebusy", "iCalendar", + "ics", "jCal", "jCard", - "vCard" + "recurrence", + "rfc2425", + "rfc2426", + "rfc2739", + "rfc4770", + "rfc5545", + "rfc5546", + "rfc6321", + "rfc6350", + "rfc6351", + "rfc6474", + "rfc6638", + "rfc6715", + "rfc6868", + "vCard", + "vcf", + "xCal", + "xCard" ], - "time": "2016-04-24 07:05:24" + "time": "2016-04-07 00:48:27" + }, + { + "name": "sabre/xml", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/fruux/sabre-xml.git", + "reference": "f48d98c22a4a4bef76cabb5968ffaddbb2bb593e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruux/sabre-xml/zipball/f48d98c22a4a4bef76cabb5968ffaddbb2bb593e", + "reference": "f48d98c22a4a4bef76cabb5968ffaddbb2bb593e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "lib-libxml": ">=2.6.20", + "php": ">=5.4.1", + "sabre/uri": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "*", + "sabre/cs": "~0.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Sabre\\Xml\\": "lib/" + }, + "files": [ + "lib/Deserializer/functions.php", + "lib/Serializer/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + }, + { + "name": "Markus Staab", + "email": "markus.staab@redaxo.de", + "role": "Developer" + } + ], + "description": "sabre/xml is an XML library that you may not hate.", + "homepage": "https://sabre.io/xml/", + "keywords": [ + "XMLReader", + "XMLWriter", + "dom", + "xml" + ], + "time": "2016-05-19 21:56:49" }, { "name": "sensio/distribution-bundle", diff --git a/src/Hackspace/Bundle/CalciferBundle/Controller/EventController.php b/src/Hackspace/Bundle/CalciferBundle/Controller/EventController.php index 79ccc0d..9e2af22 100755 --- a/src/Hackspace/Bundle/CalciferBundle/Controller/EventController.php +++ b/src/Hackspace/Bundle/CalciferBundle/Controller/EventController.php @@ -15,19 +15,8 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use Hackspace\Bundle\CalciferBundle\Entity\Event; use Hackspace\Bundle\CalciferBundle\Form\EventType; use Symfony\Component\HttpFoundation\Response; -use Jsvrcek\ICS\Model\Calendar; -use Jsvrcek\ICS\Utility\Formatter; -use Jsvrcek\ICS\CalendarStream; -use Jsvrcek\ICS\CalendarExport; -use - Sabre\VObject, - Sabre\CalDAV, - Sabre\DAV, - Sabre\DAVACL, - Sabre\DAV\Exception\Forbidden, - Hackspace\Bundle\CalciferBundle\libs\CalciferCaldavBackend, - Hackspace\Bundle\CalciferBundle\libs\CalciferPrincipalBackend; +use Sabre\VObject; /** * Event controller. * @@ -35,45 +24,6 @@ use */ class EventController extends Controller { - /** - * Finds and displays a Event entity. - * - * @Route("/{url}", name="events_caldav", requirements={"url" : "caldav(.+)"}) - */ - public function caldavEntry() - { - // Backends - $calendarBackend = new CalciferCaldavBackend($this); - $principalBackend = new CalciferPrincipalBackend(); - // Directory structure - $tree = [ - new CalDAV\CalendarRootNode($principalBackend, $calendarBackend), - ]; - - $server = new DAV\Server($tree); - - $server->setBaseUri('/caldav'); - - /*$aclPlugin = new DAVACL\Plugin(); - $aclPlugin->allowAccessToNodesWithoutACL = false; - $server->addPlugin($aclPlugin);*/ - - /* CalDAV support */ - $caldavPlugin = new CalDAV\Plugin(); - $server->addPlugin($caldavPlugin); - - /* WebDAV-Sync plugin */ - $server->addPlugin(new DAV\Sync\Plugin()); - -// Support for html frontend - $browser = new DAV\Browser\Plugin(); - $server->addPlugin($browser); - -// And off we go! - $server->exec(); - return new Response(); - } - /** * Lists all Event entities as ICS. * @@ -96,22 +46,14 @@ class EventController extends Controller ->setParameter('startdate', $now); $entities = $qb->getQuery()->execute(); - $calendar = new Calendar(); - $calendar->setProdId('-//My Company//Cool Calendar App//EN'); + $vcalendar = new VObject\Component\VCalendar(); foreach ($entities as $entity) { - /** @var Event $entity */ - $event = $entity->ConvertToCalendarEvent(); - $calendar->addEvent($event); + /** @var Event $entity */ + $vcalendar->add('VEVENT',$entity->ConvertToCalendarEvent()); } - $calendarExport = new CalendarExport(new CalendarStream, new Formatter()); - $calendarExport->addCalendar($calendar); - - //output .ics formatted text - $result = $calendarExport->getStream(); - - $response = new Response($result); + $response = new Response($vcalendar->serialize()); $response->headers->set('Content-Type', 'text/calendar'); return $response; diff --git a/src/Hackspace/Bundle/CalciferBundle/Controller/LocationController.php b/src/Hackspace/Bundle/CalciferBundle/Controller/LocationController.php index bbdb83b..00d096e 100755 --- a/src/Hackspace/Bundle/CalciferBundle/Controller/LocationController.php +++ b/src/Hackspace/Bundle/CalciferBundle/Controller/LocationController.php @@ -16,12 +16,9 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use Hackspace\Bundle\CalciferBundle\Entity\Event; use Hackspace\Bundle\CalciferBundle\Form\EventType; use Symfony\Component\HttpFoundation\Response; -use Jsvrcek\ICS\Model\Calendar; -use Jsvrcek\ICS\Utility\Formatter; -use Jsvrcek\ICS\CalendarStream; -use Jsvrcek\ICS\CalendarExport; use Symfony\Component\HttpFoundation\AcceptHeader; +use Sabre\VObject; /** * Location controller. * @@ -68,24 +65,15 @@ class LocationController extends Controller $entities = $qb->getQuery()->execute(); if ($format == 'ics') { - $calendar = new Calendar(); - $calendar->setProdId('-//My Company//Cool Calendar App//EN'); + $vcalendar = new VObject\Component\VCalendar(); foreach ($entities as $entity) { - /** @var Event $entity */ - $event = $entity->ConvertToCalendarEvent(); - $calendar->addEvent($event); + /** @var Event $entity */ + $vcalendar->add('VEVENT',$entity->ConvertToCalendarEvent()); } - $calendarExport = new CalendarExport(new CalendarStream, new Formatter()); - $calendarExport->addCalendar($calendar); - - //output .ics formatted text - $result = $calendarExport->getStream(); - - $response = new Response($result); + $response = new Response($vcalendar->serialize()); $response->headers->set('Content-Type', 'text/calendar'); - return $response; } else { return array( diff --git a/src/Hackspace/Bundle/CalciferBundle/Controller/TagController.php b/src/Hackspace/Bundle/CalciferBundle/Controller/TagController.php index 230b828..09b76f3 100755 --- a/src/Hackspace/Bundle/CalciferBundle/Controller/TagController.php +++ b/src/Hackspace/Bundle/CalciferBundle/Controller/TagController.php @@ -7,7 +7,6 @@ use Doctrine\ORM\EntityRepository; use Doctrine\ORM\QueryBuilder; use Hackspace\Bundle\CalciferBundle\Entity\Location; use Hackspace\Bundle\CalciferBundle\Entity\Tag; -use Jsvrcek\ICS\Model\Description\Geo; use Symfony\Component\HttpFoundation\Request; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; @@ -16,18 +15,12 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use Hackspace\Bundle\CalciferBundle\Entity\Event; use Hackspace\Bundle\CalciferBundle\Form\EventType; use Symfony\Component\HttpFoundation\Response; -use Jsvrcek\ICS\Model\Calendar; -use Jsvrcek\ICS\Model\CalendarEvent; -use Jsvrcek\ICS\Model\Relationship\Attendee; -use Jsvrcek\ICS\Model\Relationship\Organizer; - -use Jsvrcek\ICS\Utility\Formatter; -use Jsvrcek\ICS\CalendarStream; -use Jsvrcek\ICS\CalendarExport; use Symfony\Component\Validator\Constraints\DateTime; use Doctrine\ORM\Query\ResultSetMappingBuilder; use Symfony\Component\HttpFoundation\AcceptHeader; +use Sabre\VObject; + /** * Tag controller. * @@ -143,23 +136,14 @@ EOF; } if ($format == 'ics') { - $calendar = new Calendar(); - $calendar->setProdId('-//My Company//Cool Calendar App//EN'); - $calendar->setTimeZone(new \DateTimeZone('Europe/Berlin')); + $vcalendar = new VObject\Component\VCalendar(); foreach ($entities as $entity) { - /** @var Event $entity */ - $event = $entity->ConvertToCalendarEvent(); - $calendar->addEvent($event); + /** @var Event $entity */ + $vcalendar->add('VEVENT',$entity->ConvertToCalendarEvent()); } - $calendarExport = new CalendarExport(new CalendarStream, new Formatter()); - $calendarExport->addCalendar($calendar); - - //output .ics formatted text - $result = $calendarExport->getStream(); - - $response = new Response($result); + $response = new Response($vcalendar->serialize()); $response->headers->set('Content-Type', 'text/calendar'); return $response; diff --git a/src/Hackspace/Bundle/CalciferBundle/Entity/Event.php b/src/Hackspace/Bundle/CalciferBundle/Entity/Event.php index 93506ea..2dfb776 100755 --- a/src/Hackspace/Bundle/CalciferBundle/Entity/Event.php +++ b/src/Hackspace/Bundle/CalciferBundle/Entity/Event.php @@ -5,17 +5,7 @@ namespace Hackspace\Bundle\CalciferBundle\Entity; use Doctrine\ORM\Mapping as ORM; -use Jsvrcek\ICS\Model\Description\Location As EventLocation; use Symfony\Component\Validator\Constraints\DateTime; -use Jsvrcek\ICS\Model\Calendar; -use Jsvrcek\ICS\Model\CalendarEvent; -use Jsvrcek\ICS\Model\Relationship\Attendee; -use Jsvrcek\ICS\Model\Relationship\Organizer; - -use Jsvrcek\ICS\Utility\Formatter; -use Jsvrcek\ICS\CalendarStream; -use Jsvrcek\ICS\CalendarExport; -use Jsvrcek\ICS\Model\Description\Geo; /** * Event @@ -144,32 +134,30 @@ class Event extends BaseEntity public function ConvertToCalendarEvent() { - $event = new CalendarEvent(); - $event->setStart($this->startdate); - if ($this->enddate instanceof \DateTime) - $event->setEnd($this->enddate); - $event->setSummary($this->summary); - $event->setUrl($this->url); - $uid = sprintf("https://%s/termine/%s",$_SERVER['HTTP_HOST'],$this->slug); - $event->setUid($uid); - if (count($this->tags) > 0) { - $categories = []; - foreach($this->tags as $tag) { - $event->addCategory($tag->name); - } + $categories = []; + foreach($this->tags as $tag) { + $categories[] = $tag->name; } + + $event = [ + "VEVENT" => [ + 'SUMMARY' => $this->summary, + 'DTSTART' => $this->startdate, + 'DESCRIPTION' => $this->description, + 'URL' => $this->url, + 'CATEGORIES' => $categories, + ] + ]; + if (!is_null($this->enddate)) + $event["VEVENT"]["DTEND"] = $this->enddate; + if ($this->location instanceof Location) { - $location = new EventLocation(); - $location->setName($this->location->name); - $event->setLocations([$location]); + $event["VEVENT"]["LOCATION"] = $this->location->name; if (\is_float($this->location->lon) && \is_float($this->location->lat)) { - $geo = new Geo(); - $geo->setLatitude($this->location->lat); - $geo->setLongitude($this->location->lon); - $event->setGeo($geo); + $event["VEVENT"]["GEO"] = [$this->location->lat, $this->location->lon]; } } - $event->setDescription($this->description); + return $event; } } diff --git a/src/Hackspace/Bundle/CalciferBundle/libs/CalciferCaldavBackend.php b/src/Hackspace/Bundle/CalciferBundle/libs/CalciferCaldavBackend.php deleted file mode 100644 index f2003f8..0000000 --- a/src/Hackspace/Bundle/CalciferBundle/libs/CalciferCaldavBackend.php +++ /dev/null @@ -1,312 +0,0 @@ -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 = new Calendar(); - $calendar->setProdId('-//My Company//Cool Calendar App//EN'); - $calendar->addEvent($calendar_event); - $calendarExport = new CalendarExport(new CalendarStream, new Formatter()); - $calendarExport->addCalendar($calendar); - - //output .ics formatted text - $calendar_data = $calendarExport->getStream(); - - - $event_data = [ - 'id' => $event->id, - 'uri' => $event->slug . '.ics', - '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') - ->orderBy('e.startdate'); - $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' => substr($objectUri,0,strlen($objectUri) - 4)]); - - 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'); - } -} \ No newline at end of file diff --git a/src/Hackspace/Bundle/CalciferBundle/libs/CalciferPrincipalBackend.php b/src/Hackspace/Bundle/CalciferBundle/libs/CalciferPrincipalBackend.php deleted file mode 100644 index aecf15b..0000000 --- a/src/Hackspace/Bundle/CalciferBundle/libs/CalciferPrincipalBackend.php +++ /dev/null @@ -1,158 +0,0 @@ - '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, $test = 'allof') - { - 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'); - } -} \ No newline at end of file