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.
This commit is contained in:
Tim Schumacher 2016-06-02 16:01:44 +02:00
parent 7c30f8956e
commit b3a4836a10
10 changed files with 204 additions and 780 deletions

View file

@ -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 * @return string The help text
*/ */
@ -119,10 +119,10 @@ class PhpIniRequirement extends Requirement
* *
* @param string $cfgName The configuration name used for ini_get() * @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, * @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. * @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. * 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. * 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 $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 $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) * @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 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, * @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. * @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. * 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. * 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 $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 $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) * @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 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, * @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. * @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. * 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. * 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 $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 $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) * @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 <strong>iconv</strong> extension.'
);
$this->addRequirement( $this->addRequirement(
function_exists('json_encode'), function_exists('json_encode'),
'json_encode() must be available', 'json_encode() must be available',
@ -542,11 +548,22 @@ class SymfonyRequirements extends RequirementCollection
/* optional recommendations follow */ /* optional recommendations follow */
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( $this->addRecommendation(
file_get_contents(__FILE__) === file_get_contents(__DIR__.'/../vendor/sensio/distribution-bundle/Sensio/Bundle/DistributionBundle/Resources/skeleton/app/SymfonyRequirements.php'), file_get_contents(__FILE__) === $contents,
'Requirements file should be up-to-date', 'Requirements file should be up-to-date',
'Your requirements file is outdated. Run composer install and re-check your configuration.' 'Your requirements file is outdated. Run composer install and re-check your configuration.'
); );
}
$this->addRecommendation( $this->addRecommendation(
version_compare($installedPhpVersion, '5.3.4', '>='), version_compare($installedPhpVersion, '5.3.4', '>='),
@ -627,20 +644,20 @@ class SymfonyRequirements extends RequirementCollection
} }
$this->addRecommendation( $this->addRecommendation(
class_exists('Locale'), extension_loaded('intl'),
'intl extension should be available', 'intl extension should be available',
'Install and enable the <strong>intl</strong> extension (used for validators).' 'Install and enable the <strong>intl</strong> extension (used for validators).'
); );
if (class_exists('Collator')) { if (extension_loaded('intl')) {
// in some WAMP server installations, new Collator() returns null
$this->addRecommendation( $this->addRecommendation(
null !== new Collator('fr_FR'), null !== new Collator('fr_FR'),
'intl extension should be correctly configured', '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.' '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')) { if (defined('INTL_ICU_VERSION')) {
$version = INTL_ICU_VERSION; $version = INTL_ICU_VERSION;
} else { } else {
@ -659,6 +676,14 @@ class SymfonyRequirements extends RequirementCollection
'intl ICU version should be at least 4+', 'intl ICU version should be at least 4+',
'Upgrade your <strong>intl</strong> extension with a newer ICU version (4+).' 'Upgrade your <strong>intl</strong> 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 "<strong>intl.error_level</strong>" to "<strong>0</strong>" in php.ini<a href="#phpini">*</a> to inhibit the messages when an error occurs in ICU functions.'
);
} }
$accelerator = $accelerator =

View file

@ -6,7 +6,7 @@ $lineSize = 70;
$symfonyRequirements = new SymfonyRequirements(); $symfonyRequirements = new SymfonyRequirements();
$iniPath = $symfonyRequirements->getPhpIniConfigPath(); $iniPath = $symfonyRequirements->getPhpIniConfigPath();
echo_title('Symfony2 Requirements Checker'); echo_title('Symfony Requirements Checker');
echo '> PHP is using the following php.ini file:'.PHP_EOL; echo '> PHP is using the following php.ini file:'.PHP_EOL;
if ($iniPath) { if ($iniPath) {
@ -42,9 +42,9 @@ foreach ($symfonyRequirements->getRecommendations() as $req) {
} }
if ($checkPassed) { 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 { } 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'); echo_title('Fix the following mandatory requirements', 'red');

View file

@ -38,7 +38,7 @@
"doctrine/doctrine-migrations-bundle": "dev-master", "doctrine/doctrine-migrations-bundle": "dev-master",
"jbroadway/urlify" : "~1.0", "jbroadway/urlify" : "~1.0",
"enko/relativedateparser" : "0.5", "enko/relativedateparser" : "0.5",
"sabre/dav" : "~2.0" "sabre/vobject": "^4.1"
}, },
"require-dev": { "require-dev": {
"sensio/generator-bundle": "~2.3" "sensio/generator-bundle": "~2.3"

275
composer.lock generated
View file

@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"hash": "929ec448ee4bd9047bda7086b30492ef", "hash": "b653fb6b6230d43a6048e92f7b208ff8",
"content-hash": "855e0330604c2434a4fbbc453c11e2b0", "content-hash": "3a7c343089ce6886d5fefe973df0b6cc",
"packages": [ "packages": [
{ {
"name": "doctrine/annotations", "name": "doctrine/annotations",
@ -1583,95 +1583,21 @@
"time": "2012-12-21 11:40:51" "time": "2012-12-21 11:40:51"
}, },
{ {
"name": "sabre/dav", "name": "sabre/uri",
"version": "2.1.10", "version": "1.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/fruux/sabre-dav.git", "url": "https://github.com/fruux/sabre-uri.git",
"reference": "9f8c1939a3f66eb7170489fc48579ffd1461af62" "reference": "9012116434d84ef6e5e37a89dfdbfbe2204a8704"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/fruux/sabre-dav/zipball/9f8c1939a3f66eb7170489fc48579ffd1461af62", "url": "https://api.github.com/repos/fruux/sabre-uri/zipball/9012116434d84ef6e5e37a89dfdbfbe2204a8704",
"reference": "9f8c1939a3f66eb7170489fc48579ffd1461af62", "reference": "9012116434d84ef6e5e37a89dfdbfbe2204a8704",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-ctype": "*", "php": ">=5.4.7"
"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"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "*", "phpunit/phpunit": "*",
@ -1679,8 +1605,11 @@
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
"files": [
"lib/functions.php"
],
"psr-4": { "psr-4": {
"Sabre\\Event\\": "lib/" "Sabre\\Uri\\": "lib/"
} }
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
@ -1695,90 +1624,40 @@
"role": "Developer" "role": "Developer"
} }
], ],
"description": "sabre/event is a library for lightweight event-based programming", "description": "Functions for making sense out of URIs.",
"homepage": "http://sabre.io/event/", "homepage": "http://sabre.io/uri/",
"keywords": [ "keywords": [
"EventEmitter", "rfc3986",
"events", "uri",
"hooks", "url"
"plugin",
"promise",
"signal"
], ],
"time": "2015-05-19 10:24:22" "time": "2016-03-08 02:29:27"
},
{
"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"
}, },
{ {
"name": "sabre/vobject", "name": "sabre/vobject",
"version": "3.5.2", "version": "4.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/fruux/sabre-vobject.git", "url": "https://github.com/fruux/sabre-vobject.git",
"reference": "b7d6005b9f8e18bfe2b953d9847df0b3e4098441" "reference": "8899c0e856b3178b17f4e9a4e85010209f32a2fa"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/fruux/sabre-vobject/zipball/b7d6005b9f8e18bfe2b953d9847df0b3e4098441", "url": "https://api.github.com/repos/fruux/sabre-vobject/zipball/8899c0e856b3178b17f4e9a4e85010209f32a2fa",
"reference": "b7d6005b9f8e18bfe2b953d9847df0b3e4098441", "reference": "8899c0e856b3178b17f4e9a4e85010209f32a2fa",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-mbstring": "*", "ext-mbstring": "*",
"php": ">=5.3.1" "php": ">=5.5",
"sabre/xml": "~1.1"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "*", "phpunit/phpunit": "*",
"squizlabs/php_codesniffer": "*" "sabre/cs": "~0.0.3"
},
"suggest": {
"hoa/bench": "If you would like to run the benchmark scripts"
}, },
"bin": [ "bin": [
"bin/vobject", "bin/vobject",
@ -1787,7 +1666,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "3.2.x-dev" "dev-master": "4.0.x-dev"
} }
}, },
"autoload": { "autoload": {
@ -1811,18 +1690,106 @@
"email": "dominik@fruux.com", "email": "dominik@fruux.com",
"homepage": "http://tobschall.de/", "homepage": "http://tobschall.de/",
"role": "Developer" "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", "description": "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects",
"homepage": "http://sabre.io/vobject/", "homepage": "http://sabre.io/vobject/",
"keywords": [ "keywords": [
"VObject", "availability",
"freebusy",
"iCalendar", "iCalendar",
"ics",
"jCal", "jCal",
"jCard", "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", "name": "sensio/distribution-bundle",

View file

@ -15,19 +15,8 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Hackspace\Bundle\CalciferBundle\Entity\Event; use Hackspace\Bundle\CalciferBundle\Entity\Event;
use Hackspace\Bundle\CalciferBundle\Form\EventType; use Hackspace\Bundle\CalciferBundle\Form\EventType;
use Symfony\Component\HttpFoundation\Response; 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. * Event controller.
* *
@ -35,45 +24,6 @@ use
*/ */
class EventController extends Controller 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. * Lists all Event entities as ICS.
* *
@ -96,22 +46,14 @@ class EventController extends Controller
->setParameter('startdate', $now); ->setParameter('startdate', $now);
$entities = $qb->getQuery()->execute(); $entities = $qb->getQuery()->execute();
$calendar = new Calendar(); $vcalendar = new VObject\Component\VCalendar();
$calendar->setProdId('-//My Company//Cool Calendar App//EN');
foreach ($entities as $entity) { foreach ($entities as $entity) {
/** @var Event $entity */ /** @var Event $entity */
$event = $entity->ConvertToCalendarEvent(); $vcalendar->add('VEVENT',$entity->ConvertToCalendarEvent());
$calendar->addEvent($event);
} }
$calendarExport = new CalendarExport(new CalendarStream, new Formatter()); $response = new Response($vcalendar->serialize());
$calendarExport->addCalendar($calendar);
//output .ics formatted text
$result = $calendarExport->getStream();
$response = new Response($result);
$response->headers->set('Content-Type', 'text/calendar'); $response->headers->set('Content-Type', 'text/calendar');
return $response; return $response;

View file

@ -16,12 +16,9 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Hackspace\Bundle\CalciferBundle\Entity\Event; use Hackspace\Bundle\CalciferBundle\Entity\Event;
use Hackspace\Bundle\CalciferBundle\Form\EventType; use Hackspace\Bundle\CalciferBundle\Form\EventType;
use Symfony\Component\HttpFoundation\Response; 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 Symfony\Component\HttpFoundation\AcceptHeader;
use Sabre\VObject;
/** /**
* Location controller. * Location controller.
* *
@ -68,24 +65,15 @@ class LocationController extends Controller
$entities = $qb->getQuery()->execute(); $entities = $qb->getQuery()->execute();
if ($format == 'ics') { if ($format == 'ics') {
$calendar = new Calendar(); $vcalendar = new VObject\Component\VCalendar();
$calendar->setProdId('-//My Company//Cool Calendar App//EN');
foreach ($entities as $entity) { foreach ($entities as $entity) {
/** @var Event $entity */ /** @var Event $entity */
$event = $entity->ConvertToCalendarEvent(); $vcalendar->add('VEVENT',$entity->ConvertToCalendarEvent());
$calendar->addEvent($event);
} }
$calendarExport = new CalendarExport(new CalendarStream, new Formatter()); $response = new Response($vcalendar->serialize());
$calendarExport->addCalendar($calendar);
//output .ics formatted text
$result = $calendarExport->getStream();
$response = new Response($result);
$response->headers->set('Content-Type', 'text/calendar'); $response->headers->set('Content-Type', 'text/calendar');
return $response; return $response;
} else { } else {
return array( return array(

View file

@ -7,7 +7,6 @@ use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Hackspace\Bundle\CalciferBundle\Entity\Location; use Hackspace\Bundle\CalciferBundle\Entity\Location;
use Hackspace\Bundle\CalciferBundle\Entity\Tag; use Hackspace\Bundle\CalciferBundle\Entity\Tag;
use Jsvrcek\ICS\Model\Description\Geo;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; 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\Entity\Event;
use Hackspace\Bundle\CalciferBundle\Form\EventType; use Hackspace\Bundle\CalciferBundle\Form\EventType;
use Symfony\Component\HttpFoundation\Response; 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 Symfony\Component\Validator\Constraints\DateTime;
use Doctrine\ORM\Query\ResultSetMappingBuilder; use Doctrine\ORM\Query\ResultSetMappingBuilder;
use Symfony\Component\HttpFoundation\AcceptHeader; use Symfony\Component\HttpFoundation\AcceptHeader;
use Sabre\VObject;
/** /**
* Tag controller. * Tag controller.
* *
@ -143,23 +136,14 @@ EOF;
} }
if ($format == 'ics') { if ($format == 'ics') {
$calendar = new Calendar(); $vcalendar = new VObject\Component\VCalendar();
$calendar->setProdId('-//My Company//Cool Calendar App//EN');
$calendar->setTimeZone(new \DateTimeZone('Europe/Berlin'));
foreach ($entities as $entity) { foreach ($entities as $entity) {
/** @var Event $entity */ /** @var Event $entity */
$event = $entity->ConvertToCalendarEvent(); $vcalendar->add('VEVENT',$entity->ConvertToCalendarEvent());
$calendar->addEvent($event);
} }
$calendarExport = new CalendarExport(new CalendarStream, new Formatter()); $response = new Response($vcalendar->serialize());
$calendarExport->addCalendar($calendar);
//output .ics formatted text
$result = $calendarExport->getStream();
$response = new Response($result);
$response->headers->set('Content-Type', 'text/calendar'); $response->headers->set('Content-Type', 'text/calendar');
return $response; return $response;

View file

@ -5,17 +5,7 @@ namespace Hackspace\Bundle\CalciferBundle\Entity;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Jsvrcek\ICS\Model\Description\Location As EventLocation;
use Symfony\Component\Validator\Constraints\DateTime; 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 * Event
@ -144,32 +134,30 @@ class Event extends BaseEntity
public function ConvertToCalendarEvent() { 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 = []; $categories = [];
foreach($this->tags as $tag) { foreach($this->tags as $tag) {
$event->addCategory($tag->name); $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) { if ($this->location instanceof Location) {
$location = new EventLocation(); $event["VEVENT"]["LOCATION"] = $this->location->name;
$location->setName($this->location->name);
$event->setLocations([$location]);
if (\is_float($this->location->lon) && \is_float($this->location->lat)) { if (\is_float($this->location->lon) && \is_float($this->location->lat)) {
$geo = new Geo(); $event["VEVENT"]["GEO"] = [$this->location->lat, $this->location->lon];
$geo->setLatitude($this->location->lat);
$geo->setLongitude($this->location->lon);
$event->setGeo($geo);
} }
} }
$event->setDescription($this->description);
return $event; return $event;
} }
} }

View file

@ -1,312 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: tim
* Date: 15.11.14
* Time: 17:13
*/
namespace Hackspace\Bundle\CalciferBundle\libs;
use Jsvrcek\ICS\Model\Calendar;
use Jsvrcek\ICS\CalendarExport;
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 = 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');
}
}

View file

@ -1,158 +0,0 @@
<?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, $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');
}
}