Merge remote-tracking branch 'upstream/dev' into plugin-repo

This commit is contained in:
Andrew Manning 2016-05-11 05:54:20 -04:00
commit d968fc51ea
741 changed files with 64805 additions and 42453 deletions

View file

@ -230,6 +230,12 @@ class Admin extends \Zotlabs\Web\Controller {
. ' A list about current PHP versions can be found <a href="http://php.net/supported-versions.php" class="alert-link">here</a>.';
}
$vmaster = get_repository_version('master');
$vdev = get_repository_version('dev');
$upgrade = ((version_compare(STD_VERSION,$vmaster) < 0) ? t('Your software should be updated') : '');
$t = get_markup_template('admin_summary.tpl');
return replace_macros($t, array(
'$title' => t('Administration'),
@ -241,6 +247,9 @@ class Admin extends \Zotlabs\Web\Controller {
'$channels' => array( t('Registered channels'), $channels),
'$plugins' => array( t('Active plugins'), $plugins ),
'$version' => array( t('Version'), STD_VERSION),
'$vmaster' => array( t('Repository version (master)'), $vmaster),
'$vdev' => array( t('Repository version (dev)'), $vdev),
'$upgrade' => $upgrade,
'$build' => get_config('system', 'db_version')
));
}

View file

@ -90,7 +90,6 @@ class Cloud extends \Zotlabs\Web\Controller {
}
}
// require_once('\Zotlabs/Storage/Browser.php');
// provide a directory view for the cloud in Hubzilla
$browser = new \Zotlabs\Storage\Browser($auth);
$auth->setBrowserPlugin($browser);

View file

@ -60,38 +60,53 @@ class Dav extends \Zotlabs\Web\Controller {
if ($which)
profile_load($a, $which, $profile);
$auth = new \Zotlabs\Storage\BasicAuth();
$ob_hash = get_observer_hash();
// $authBackend = new \Sabre\DAV\Auth\Backend\BasicCallBack(function($userName,$password) {
// if(account_verify_password($userName,$password))
// return true;
// return false;
// });
if ($ob_hash) {
if (local_channel()) {
$channel = \App::get_channel();
$auth->setCurrentUser($channel['channel_address']);
$auth->channel_id = $channel['channel_id'];
$auth->channel_hash = $channel['channel_hash'];
$auth->channel_account_id = $channel['channel_account_id'];
if($channel['channel_timezone'])
$auth->setTimezone($channel['channel_timezone']);
}
$auth->observer = $ob_hash;
}
// $ob_hash = get_observer_hash();
if ($_GET['davguest'])
$_SESSION['davguest'] = true;
// if ($ob_hash) {
// if (local_channel()) {
// $channel = \App::get_channel();
// $auth->setCurrentUser($channel['channel_address']);
// $auth->channel_id = $channel['channel_id'];
// $auth->channel_hash = $channel['channel_hash'];
// $auth->channel_account_id = $channel['channel_account_id'];
// if($channel['channel_timezone'])
// $auth->setTimezone($channel['channel_timezone']);
// }
// $auth->observer = $ob_hash;
// }
$_SERVER['QUERY_STRING'] = str_replace(array('?f=', '&f='), array('', ''), $_SERVER['QUERY_STRING']);
$_SERVER['QUERY_STRING'] = strip_zids($_SERVER['QUERY_STRING']);
$_SERVER['QUERY_STRING'] = preg_replace('/[\?&]davguest=(.*?)([\?&]|$)/ism', '', $_SERVER['QUERY_STRING']);
// if ($_GET['davguest'])
// $_SESSION['davguest'] = true;
$_SERVER['REQUEST_URI'] = str_replace(array('?f=', '&f='), array('', ''), $_SERVER['REQUEST_URI']);
$_SERVER['REQUEST_URI'] = strip_zids($_SERVER['REQUEST_URI']);
$_SERVER['REQUEST_URI'] = preg_replace('/[\?&]davguest=(.*?)([\?&]|$)/ism', '', $_SERVER['REQUEST_URI']);
// $_SERVER['QUERY_STRING'] = str_replace(array('?f=', '&f='), array('', ''), $_SERVER['QUERY_STRING']);
// $_SERVER['QUERY_STRING'] = strip_zids($_SERVER['QUERY_STRING']);
// $_SERVER['QUERY_STRING'] = preg_replace('/[\?&]davguest=(.*?)([\?&]|$)/ism', '', $_SERVER['QUERY_STRING']);
//
// $_SERVER['REQUEST_URI'] = str_replace(array('?f=', '&f='), array('', ''), $_SERVER['REQUEST_URI']);
// $_SERVER['REQUEST_URI'] = strip_zids($_SERVER['REQUEST_URI']);
// $_SERVER['REQUEST_URI'] = preg_replace('/[\?&]davguest=(.*?)([\?&]|$)/ism', '', $_SERVER['REQUEST_URI']);
$rootDirectory = new \Zotlabs\Storage\Directory('/', $auth);
// A SabreDAV server-object
$server = new SDAV\Server($rootDirectory);
$authPlugin = new \Sabre\DAV\Auth\Plugin($auth);
$server->addPlugin($authPlugin);
// prevent overwriting changes each other with a lock backend
$lockBackend = new SDAV\Locks\Backend\File('store/[data]/locks');
$lockPlugin = new SDAV\Locks\Plugin($lockBackend);
@ -108,29 +123,29 @@ class Dav extends \Zotlabs\Web\Controller {
// In order to avoid prompting for passwords for viewing a DIRECTORY, add
// the URL query parameter 'davguest=1'.
$isapublic_file = false;
$davguest = ((x($_SESSION, 'davguest')) ? true : false);
// $isapublic_file = false;
// $davguest = ((x($_SESSION, 'davguest')) ? true : false);
if ((! $auth->observer) && ($_SERVER['REQUEST_METHOD'] === 'GET')) {
try {
$x = RedFileData('/' . \App::$cmd, $auth);
if($x instanceof \Zotlabs\Storage\File)
$isapublic_file = true;
}
catch (Exception $e) {
$isapublic_file = false;
}
}
// if ((! $auth->observer) && ($_SERVER['REQUEST_METHOD'] === 'GET')) {
// try {
// $x = RedFileData('/' . \App::$cmd, $auth);
// if($x instanceof \Zotlabs\Storage\File)
// $isapublic_file = true;
// }
// catch (Exception $e) {
// $isapublic_file = false;
// }
// }
if ((! $auth->observer) && (! $isapublic_file) && (! $davguest)) {
try {
$auth->Authenticate($server, t('$Projectname channel'));
}
catch (Exception $e) {
logger('mod_cloud: auth exception' . $e->getMessage());
http_status_exit($e->getHTTPCode(), $e->getMessage());
}
}
// if ((! $auth->observer) && (! $isapublic_file) && (! $davguest)) {
// try {
// $auth->Authenticate($server, t('$Projectname channel'));
// }
// catch (Exception $e) {
// logger('mod_cloud: auth exception' . $e->getMessage());
// http_status_exit($e->getHTTPCode(), $e->getMessage());
// }
// }
// require_once('Zotlabs/Storage/Browser.php');
// provide a directory view for the cloud in Hubzilla

View file

@ -84,10 +84,6 @@ class BasicAuth extends DAV\Auth\Backend\AbstractBasic {
* @return bool
*/
protected function validateUserPass($username, $password) {
if (trim($password) === '+++') {
logger('guest: ' . $username);
return true;
}
require_once('include/auth.php');
$record = account_verify_password($username, $password);

View file

@ -101,8 +101,8 @@ class Browser extends DAV\Browser\Plugin {
$parentpath = array();
// only show parent if not leaving /cloud/; TODO how to improve this?
if ($path && $path != "cloud") {
list($parentUri) = DAV\URLUtil::splitPath($path);
$fullPath = DAV\URLUtil::encodePath($this->server->getBaseUri() . $parentUri);
list($parentUri) = \Sabre\HTTP\URLUtil::splitPath($path);
$fullPath = \Sabre\HTTP\URLUtil::encodePath($this->server->getBaseUri() . $parentUri);
$parentpath['icon'] = $this->enableAssets ? '<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl('icons/parent' . $this->iconExtension) . '" width="24" alt="' . t('parent') . '"></a>' : '';
$parentpath['path'] = $fullPath;
@ -116,7 +116,7 @@ class Browser extends DAV\Browser\Plugin {
// This is the current directory, we can skip it
if (rtrim($file['href'],'/') == $path) continue;
list(, $name) = DAV\URLUtil::splitPath($file['href']);
list(, $name) = \Sabre\HTTP\URLUtil::splitPath($file['href']);
if (isset($file[200]['{DAV:}resourcetype'])) {
$type = $file[200]['{DAV:}resourcetype']->getValue();
@ -166,7 +166,7 @@ class Browser extends DAV\Browser\Plugin {
$size = isset($file[200]['{DAV:}getcontentlength']) ? (int)$file[200]['{DAV:}getcontentlength'] : '';
$lastmodified = ((isset($file[200]['{DAV:}getlastmodified'])) ? $file[200]['{DAV:}getlastmodified']->getTime()->format('Y-m-d H:i:s') : '');
$fullPath = DAV\URLUtil::encodePath('/' . trim($this->server->getBaseUri() . ($path ? $path . '/' : '') . $name, '/'));
$fullPath = \Sabre\HTTP\URLUtil::encodePath('/' . trim($this->server->getBaseUri() . ($path ? $path . '/' : '') . $name, '/'));
$displayName = isset($file[200]['{DAV:}displayname']) ? $file[200]['{DAV:}displayname'] : $name;
@ -219,7 +219,7 @@ class Browser extends DAV\Browser\Plugin {
$output = '';
if ($this->enablePost) {
$this->server->broadcastEvent('onHTMLActionsPanel', array($parent, &$output));
$this->server->emit('onHTMLActionsPanel', array($parent, &$output));
}
$html .= replace_macros(get_markup_template('cloud.tpl'), array(

View file

@ -536,20 +536,10 @@ function event_import_ical($ical, $uid) {
}
$dtstart = $ical->DTSTART->getDateTime();
$ev['adjust'] = (($ical->DTSTART->isFloating()) ? 1 : 0);
// logger('dtstart: ' . var_export($dtstart,true));
switch($dtstart->timezone_type) {
case VObject\Property\DateTime::UTC :
$ev['adjust'] = 0;
break;
case VObject\Property\DateTime::LOCALTZ :
default:
$ev['adjust'] = 1;
break;
}
$ev['start'] = datetime_convert((($ev['adjust']) ? 'UTC' : date_default_timezone_get()),'UTC',
$dtstart->format(\DateTime::W3C));

View file

@ -2128,3 +2128,18 @@ function deliverable_singleton($channel_id,$xchan) {
return false;
}
function get_repository_version($branch = 'master') {
$path = "https://raw.githubusercontent.com/redmatrix/hubzilla/$branch/boot.php";
$x = z_fetch_url($path);
if($x['success']) {
$y = preg_match('/define(.*?)STD_VERSION(.*?)([0-9.].*)\'/',$x['body'],$matches);
if($y)
return $matches[3];
}
return '?.?';
}

2
vendor/autoload.php vendored
View file

@ -4,4 +4,4 @@
require_once __DIR__ . '/composer' . '/autoload_real.php';
return ComposerAutoloaderInita478c0bdc9041edcc4f485e8fb39b90d::getLoader();
return ComposerAutoloaderInit85a1cefa95be2f464cf7f947cbc4c785::getLoader();

1
vendor/bin/generate_vcards vendored Symbolic link
View file

@ -0,0 +1 @@
../sabre/vobject/bin/generate_vcards

1
vendor/bin/naturalselection vendored Symbolic link
View file

@ -0,0 +1 @@
../sabre/dav/bin/naturalselection

1
vendor/bin/sabredav vendored Symbolic link
View file

@ -0,0 +1 @@
../sabre/dav/bin/sabredav

1
vendor/bin/vobject vendored Symbolic link
View file

@ -0,0 +1 @@
../sabre/vobject/bin/vobject

View file

@ -13,9 +13,7 @@
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0 class loader
*
* See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
@ -39,6 +37,8 @@ namespace Composer\Autoload;
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see http://www.php-fig.org/psr/psr-0/
* @see http://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
@ -54,11 +54,17 @@ class ClassLoader
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
}
return array();
}
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
@ -141,8 +147,10 @@ class ClassLoader
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-0 base directories
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
@ -204,6 +212,8 @@ class ClassLoader
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
@ -240,6 +250,27 @@ class ClassLoader
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* Registers this instance as an autoloader.
*
@ -291,6 +322,9 @@ class ClassLoader
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative) {
return false;
}
$file = $this->findFileWithExtension($class, '.php');

21
vendor/composer/LICENSE vendored Normal file
View file

@ -0,0 +1,21 @@
Copyright (c) 2016 Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

16
vendor/composer/autoload_files.php vendored Normal file
View file

@ -0,0 +1,16 @@
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'383eaff206634a77a1be54e64e6459c7' => $vendorDir . '/sabre/uri/lib/functions.php',
'2b9d0f43f9552984cfa82fee95491826' => $vendorDir . '/sabre/event/lib/coroutine.php',
'd81bab31d3feb45bfe2f283ea3c8fdf7' => $vendorDir . '/sabre/event/lib/Loop/functions.php',
'a1cce3d26cc15c00fcd0b3354bd72c88' => $vendorDir . '/sabre/event/lib/Promise/functions.php',
'3569eecfeed3bcf0bad3c998a494ecb8' => $vendorDir . '/sabre/xml/lib/Deserializer/functions.php',
'93aa591bc4ca510c520999e34229ee79' => $vendorDir . '/sabre/xml/lib/Serializer/functions.php',
'ebdb698ed4152ae445614b69b5e4bb6a' => $vendorDir . '/sabre/http/lib/functions.php',
);

View file

@ -6,10 +6,4 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Sabre\\VObject' => array($vendorDir . '/sabre/vobject/lib'),
'Sabre\\HTTP' => array($vendorDir . '/sabre/dav/lib'),
'Sabre\\DAVACL' => array($vendorDir . '/sabre/dav/lib'),
'Sabre\\DAV' => array($vendorDir . '/sabre/dav/lib'),
'Sabre\\CardDAV' => array($vendorDir . '/sabre/dav/lib'),
'Sabre\\CalDAV' => array($vendorDir . '/sabre/dav/lib'),
);

View file

@ -6,4 +6,13 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Sabre\\Xml\\' => array($vendorDir . '/sabre/xml/lib'),
'Sabre\\VObject\\' => array($vendorDir . '/sabre/vobject/lib'),
'Sabre\\Uri\\' => array($vendorDir . '/sabre/uri/lib'),
'Sabre\\HTTP\\' => array($vendorDir . '/sabre/http/lib'),
'Sabre\\Event\\' => array($vendorDir . '/sabre/event/lib'),
'Sabre\\DAV\\' => array($vendorDir . '/sabre/dav/lib/DAV'),
'Sabre\\DAVACL\\' => array($vendorDir . '/sabre/dav/lib/DAVACL'),
'Sabre\\CardDAV\\' => array($vendorDir . '/sabre/dav/lib/CardDAV'),
'Sabre\\CalDAV\\' => array($vendorDir . '/sabre/dav/lib/CalDAV'),
);

View file

@ -2,7 +2,7 @@
// autoload_real.php @generated by Composer
class ComposerAutoloaderInita478c0bdc9041edcc4f485e8fb39b90d
class ComposerAutoloaderInit85a1cefa95be2f464cf7f947cbc4c785
{
private static $loader;
@ -19,10 +19,16 @@ class ComposerAutoloaderInita478c0bdc9041edcc4f485e8fb39b90d
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInita478c0bdc9041edcc4f485e8fb39b90d', 'loadClassLoader'), true, true);
spl_autoload_register(array('ComposerAutoloaderInit85a1cefa95be2f464cf7f947cbc4c785', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInita478c0bdc9041edcc4f485e8fb39b90d', 'loadClassLoader'));
spl_autoload_unregister(array('ComposerAutoloaderInit85a1cefa95be2f464cf7f947cbc4c785', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION');
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit85a1cefa95be2f464cf7f947cbc4c785::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
@ -37,14 +43,28 @@ class ComposerAutoloaderInita478c0bdc9041edcc4f485e8fb39b90d
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(true);
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInit85a1cefa95be2f464cf7f947cbc4c785::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire85a1cefa95be2f464cf7f947cbc4c785($fileIdentifier, $file);
}
return $loader;
}
}
function composerRequirea478c0bdc9041edcc4f485e8fb39b90d($file)
function composerRequire85a1cefa95be2f464cf7f947cbc4c785($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
require $file;
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
}
}

81
vendor/composer/autoload_static.php vendored Normal file
View file

@ -0,0 +1,81 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit85a1cefa95be2f464cf7f947cbc4c785
{
public static $files = array (
'383eaff206634a77a1be54e64e6459c7' => __DIR__ . '/..' . '/sabre/uri/lib/functions.php',
'2b9d0f43f9552984cfa82fee95491826' => __DIR__ . '/..' . '/sabre/event/lib/coroutine.php',
'd81bab31d3feb45bfe2f283ea3c8fdf7' => __DIR__ . '/..' . '/sabre/event/lib/Loop/functions.php',
'a1cce3d26cc15c00fcd0b3354bd72c88' => __DIR__ . '/..' . '/sabre/event/lib/Promise/functions.php',
'3569eecfeed3bcf0bad3c998a494ecb8' => __DIR__ . '/..' . '/sabre/xml/lib/Deserializer/functions.php',
'93aa591bc4ca510c520999e34229ee79' => __DIR__ . '/..' . '/sabre/xml/lib/Serializer/functions.php',
'ebdb698ed4152ae445614b69b5e4bb6a' => __DIR__ . '/..' . '/sabre/http/lib/functions.php',
);
public static $prefixLengthsPsr4 = array (
'S' =>
array (
'Sabre\\Xml\\' => 10,
'Sabre\\VObject\\' => 14,
'Sabre\\Uri\\' => 10,
'Sabre\\HTTP\\' => 11,
'Sabre\\Event\\' => 12,
'Sabre\\DAV\\' => 10,
'Sabre\\DAVACL\\' => 13,
'Sabre\\CardDAV\\' => 14,
'Sabre\\CalDAV\\' => 13,
),
);
public static $prefixDirsPsr4 = array (
'Sabre\\Xml\\' =>
array (
0 => __DIR__ . '/..' . '/sabre/xml/lib',
),
'Sabre\\VObject\\' =>
array (
0 => __DIR__ . '/..' . '/sabre/vobject/lib',
),
'Sabre\\Uri\\' =>
array (
0 => __DIR__ . '/..' . '/sabre/uri/lib',
),
'Sabre\\HTTP\\' =>
array (
0 => __DIR__ . '/..' . '/sabre/http/lib',
),
'Sabre\\Event\\' =>
array (
0 => __DIR__ . '/..' . '/sabre/event/lib',
),
'Sabre\\DAV\\' =>
array (
0 => __DIR__ . '/..' . '/sabre/dav/lib/DAV',
),
'Sabre\\DAVACL\\' =>
array (
0 => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL',
),
'Sabre\\CardDAV\\' =>
array (
0 => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV',
),
'Sabre\\CalDAV\\' =>
array (
0 => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV',
),
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit85a1cefa95be2f464cf7f947cbc4c785::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit85a1cefa95be2f464cf7f947cbc4c785::$prefixDirsPsr4;
}, null, ClassLoader::class);
}
}

View file

@ -1,32 +1,35 @@
[
{
"name": "sabre/vobject",
"version": "2.1.4",
"version_normalized": "2.1.4.0",
"name": "sabre/uri",
"version": "1.1.0",
"version_normalized": "1.1.0.0",
"source": {
"type": "git",
"url": "https://github.com/fruux/sabre-vobject.git",
"reference": "199b6ec87104b05e3013dfd5b90eafbbe4cf97dc"
"url": "https://github.com/fruux/sabre-uri.git",
"reference": "9012116434d84ef6e5e37a89dfdbfbe2204a8704"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fruux/sabre-vobject/zipball/199b6ec87104b05e3013dfd5b90eafbbe4cf97dc",
"reference": "199b6ec87104b05e3013dfd5b90eafbbe4cf97dc",
"url": "https://api.github.com/repos/fruux/sabre-uri/zipball/9012116434d84ef6e5e37a89dfdbfbe2204a8704",
"reference": "9012116434d84ef6e5e37a89dfdbfbe2204a8704",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": ">=5.3.1"
"php": ">=5.4.7"
},
"time": "2014-03-30 23:01:06",
"bin": [
"bin/vobjectvalidate.php"
],
"require-dev": {
"phpunit/phpunit": "*",
"sabre/cs": "~0.0.1"
},
"time": "2016-03-08 02:29:27",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-0": {
"Sabre\\VObject": "lib/"
"files": [
"lib/functions.php"
],
"psr-4": {
"Sabre\\Uri\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
@ -36,32 +39,310 @@
"authors": [
{
"name": "Evert Pot",
"email": "evert@rooftopsolutions.nl",
"email": "me@evertpot.com",
"homepage": "http://evertpot.com/",
"role": "Developer"
}
],
"description": "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects",
"homepage": "https://github.com/fruux/sabre-vobject",
"description": "Functions for making sense out of URIs.",
"homepage": "http://sabre.io/uri/",
"keywords": [
"VObject",
"rfc3986",
"uri",
"url"
]
},
{
"name": "sabre/event",
"version": "3.0.0",
"version_normalized": "3.0.0.0",
"source": {
"type": "git",
"url": "https://github.com/fruux/sabre-event.git",
"reference": "831d586f5a442dceacdcf5e9c4c36a4db99a3534"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fruux/sabre-event/zipball/831d586f5a442dceacdcf5e9c4c36a4db99a3534",
"reference": "831d586f5a442dceacdcf5e9c4c36a4db99a3534",
"shasum": ""
},
"require": {
"php": ">=5.5"
},
"require-dev": {
"phpunit/phpunit": "*",
"sabre/cs": "~0.0.4"
},
"time": "2015-11-05 20:14:39",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Sabre\\Event\\": "lib/"
},
"files": [
"lib/coroutine.php",
"lib/Loop/functions.php",
"lib/Promise/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"
}
],
"description": "sabre/event is a library for lightweight event-based programming",
"homepage": "http://sabre.io/event/",
"keywords": [
"EventEmitter",
"async",
"events",
"hooks",
"plugin",
"promise",
"signal"
]
},
{
"name": "sabre/http",
"version": "4.2.1",
"version_normalized": "4.2.1.0",
"source": {
"type": "git",
"url": "https://github.com/fruux/sabre-http.git",
"reference": "2e93bc8321524c67be4ca5b8415daebd4c8bf85e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fruux/sabre-http/zipball/2e93bc8321524c67be4ca5b8415daebd4c8bf85e",
"reference": "2e93bc8321524c67be4ca5b8415daebd4c8bf85e",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": ">=5.4",
"sabre/event": ">=1.0.0,<4.0.0",
"sabre/uri": "~1.0"
},
"require-dev": {
"phpunit/phpunit": "~4.3",
"sabre/cs": "~0.0.1"
},
"suggest": {
"ext-curl": " to make http requests with the Client class"
},
"time": "2016-01-06 23:00:08",
"type": "library",
"installation-source": "dist",
"autoload": {
"files": [
"lib/functions.php"
],
"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"
]
},
{
"name": "sabre/xml",
"version": "1.4.1",
"version_normalized": "1.4.1.0",
"source": {
"type": "git",
"url": "https://github.com/fruux/sabre-xml.git",
"reference": "59998046db252634259a878baf1af18159f508f3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fruux/sabre-xml/zipball/59998046db252634259a878baf1af18159f508f3",
"reference": "59998046db252634259a878baf1af18159f508f3",
"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"
},
"time": "2016-03-12 22:23:16",
"type": "library",
"installation-source": "dist",
"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"
]
},
{
"name": "sabre/vobject",
"version": "4.1.0",
"version_normalized": "4.1.0.0",
"source": {
"type": "git",
"url": "https://github.com/fruux/sabre-vobject.git",
"reference": "8899c0e856b3178b17f4e9a4e85010209f32a2fa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fruux/sabre-vobject/zipball/8899c0e856b3178b17f4e9a4e85010209f32a2fa",
"reference": "8899c0e856b3178b17f4e9a4e85010209f32a2fa",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": ">=5.5",
"sabre/xml": "~1.1"
},
"require-dev": {
"phpunit/phpunit": "*",
"sabre/cs": "~0.0.3"
},
"suggest": {
"hoa/bench": "If you would like to run the benchmark scripts"
},
"time": "2016-04-07 00:48:27",
"bin": [
"bin/vobject",
"bin/generate_vcards"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Sabre\\VObject\\": "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"
},
{
"name": "Dominik Tobschall",
"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": [
"availability",
"freebusy",
"iCalendar",
"vCard"
"ics",
"jCal",
"jCard",
"recurrence",
"rfc2425",
"rfc2426",
"rfc2739",
"rfc4770",
"rfc5545",
"rfc5546",
"rfc6321",
"rfc6350",
"rfc6351",
"rfc6474",
"rfc6638",
"rfc6715",
"rfc6868",
"vCard",
"vcf",
"xCal",
"xCard"
]
},
{
"name": "sabre/dav",
"version": "1.8.10",
"version_normalized": "1.8.10.0",
"version": "3.1.3",
"version_normalized": "3.1.3.0",
"source": {
"type": "git",
"url": "https://github.com/fruux/sabre-dav.git",
"reference": "0d064536ed3c7974e486b6ebb5b17ad7a974fe18"
"reference": "8a266c7b5e140da79529414b9cde2a2d058b536b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fruux/sabre-dav/zipball/0d064536ed3c7974e486b6ebb5b17ad7a974fe18",
"reference": "0d064536ed3c7974e486b6ebb5b17ad7a974fe18",
"url": "https://api.github.com/repos/fruux/sabre-dav/zipball/8a266c7b5e140da79529414b9cde2a2d058b536b",
"reference": "8a266c7b5e140da79529414b9cde2a2d058b536b",
"shasum": ""
},
"require": {
@ -69,39 +350,45 @@
"ext-date": "*",
"ext-dom": "*",
"ext-iconv": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-pcre": "*",
"ext-simplexml": "*",
"ext-spl": "*",
"php": ">=5.3.1",
"sabre/vobject": "~2.1.0"
},
"provide": {
"evert/sabredav": "1.7.*"
"lib-libxml": ">=2.7.0",
"php": ">=5.5.0",
"sabre/event": ">=2.0.0, <4.0.0",
"sabre/http": "^4.2.1",
"sabre/uri": "~1.0",
"sabre/vobject": "~4.0",
"sabre/xml": "~1.0"
},
"require-dev": {
"evert/phpdoc-md": "~0.0.7",
"phpunit/phpunit": "~4.0.0"
"evert/phpdoc-md": "~0.1.0",
"phpunit/phpunit": "> 4.8, <=6.0.0",
"sabre/cs": "~0.0.5"
},
"suggest": {
"ext-apc": "*",
"ext-curl": "*",
"ext-pdo": "*"
},
"time": "2014-05-16 00:14:02",
"time": "2016-04-07 01:02:57",
"bin": [
"bin/sabredav"
"bin/sabredav",
"bin/naturalselection"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.1.0-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-0": {
"Sabre\\DAV": "lib/",
"Sabre\\HTTP": "lib/",
"Sabre\\DAVACL": "lib/",
"Sabre\\CalDAV": "lib/",
"Sabre\\CardDAV": "lib/"
"psr-4": {
"Sabre\\DAV\\": "lib/DAV/",
"Sabre\\DAVACL\\": "lib/DAVACL/",
"Sabre\\CalDAV\\": "lib/CalDAV/",
"Sabre\\CardDAV\\": "lib/CardDAV/"
}
},
"notification-url": "https://packagist.org/downloads/",
@ -117,7 +404,7 @@
}
],
"description": "WebDAV Framework for PHP",
"homepage": "http://code.google.com/p/sabredav/",
"homepage": "http://sabre.io/",
"keywords": [
"CalDAV",
"CardDAV",

View file

@ -1,22 +1,43 @@
docs/api
docs/wikidocs
build.properties
build
public
data
fileserver.php
fileserver2.php
calendarserver.php
groupwareserver.php
package.xml
tmpdata
# Unit tests
tests/temp
tests/.sabredav
tests/cov
# Custom settings for tests
tests/config.user.php
# ViM
*.swp
# Composer
composer.lock
vendor
# Composer binaries
bin/phing
bin/phpunit
bin/vobjectvalidate.php
bin/vobject
bin/generate_vcards
bin/phpdocmd
bin/phpunit
bin/php-cs-fixer
bin/sabre-cs-fixer
# Assuming every .php file in the root is for testing
/*.php
# Other testing stuff
/tmpdata
/data
/public
# Build
build
build.properties
# Docs
docs/api
docs/wikidocs
# Mac
.DS_Store

View file

@ -1,28 +1,33 @@
language: php
php:
- 5.3.3
- 5.3
- 5.4
- 5.5
- 5.6
- 7
- hhvm
matrix:
fast_finish: true
allow_failures:
- php: 5.6
- php: hhvm
env:
matrix:
- LOWEST_DEPS="" TEST_DEPS=""
- LOWEST_DEPS="--prefer-lowest" TEST_DEPS="tests/Sabre/"
services:
- mysql
sudo: false
cache: vendor
before_script:
- mysql -e 'create database sabredav'
- composer self-update
- composer install --prefer-source
# - echo "zend.enable_gc=0" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`
# - composer self-update
- composer update --prefer-source $LOWEST_DEPS
script:
- phpunit --configuration tests/phpunit.xml
- cp tests/composer.vobject3.json composer.json
- composer update --no-dev
- phpunit --configuration tests/phpunit.xml
- ./bin/phpunit --configuration tests/phpunit.xml $TEST_DEPS
- ./bin/sabre-cs-fixer fix lib/ --dry-run --diff

2242
vendor/sabre/dav/CHANGELOG.md vendored Normal file

File diff suppressed because it is too large Load diff

87
vendor/sabre/dav/CONTRIBUTING.md vendored Normal file
View file

@ -0,0 +1,87 @@
Contributing to sabre projects
==============================
Want to contribute to sabre/dav? Here are some guidelines to ensure your patch
gets accepted.
Building a new feature? Contact us first
----------------------------------------
We may not want to accept every feature that comes our way. Sometimes
features are out of scope for our projects.
We don't want to waste your time, so by having a quick chat with us first,
you may find out quickly if the feature makes sense to us, and we can give
some tips on how to best build the feature.
If we don't accept the feature, it could be for a number of reasons. For
instance, we've rejected features in the past because we felt uncomfortable
assuming responsibility for maintaining the feature.
In those cases, it's often possible to keep the feature separate from the
sabre projects. sabre/dav for instance has a plugin system, and there's no
reason the feature can't live in a project you own.
In that case, definitely let us know about your plugin as well, so we can
feature it on [sabre.io][4].
We are often on [IRC][5], in the #sabredav channel on freenode. If there's
no one there, post a message on the [mailing list][6].
Coding standards
----------------
sabre projects follow:
1. [PSR-1][1]
2. [PSR-4][2]
sabre projects don't follow [PSR-2][3].
In addition to that, here's a list of basic rules:
1. PHP 5.4 array syntax must be used every where. This means you use `[` and
`]` instead of `array(` and `)`.
2. Use PHP namespaces everywhere.
3. Use 4 spaces for indentation.
4. Try to keep your lines under 80 characters. This is not a hard rule, as
there are many places in the source where it felt more sensibile to not
do so. In particular, function declarations are never split over multiple
lines.
5. Opening braces (`{`) are _always_ on the same line as the `class`, `if`,
`function`, etc. they belong to.
6. `public` must be omitted from method declarations. It must also be omitted
for static properties.
7. All files should use unix-line endings (`\n`).
8. Files must omit the closing php tag (`?>`).
9. `true`, `false` and `null` are always lower-case.
10. Constants are always upper-case.
11. Any of the rules stated before may be broken where this is the pragmatic
thing to do.
Unit test requirements
----------------------
Any new feature or change requires unittests. We use [PHPUnit][7] for all our
tests.
Adding unittests will greatly increase the likelyhood of us quickly accepting
your pull request. If unittests are not included though for whatever reason,
we'd still _love_ your pull request.
We may have to write the tests ourselves, which can increase the time it takes
to accept the patch, but we'd still really like your contribution!
To run the testsuite jump into the directory `cd tests` and trigger `phpunit`.
Make sure you did a `composer install` beforehand.
[1]: http://www.php-fig.org/psr/psr-1/
[2]: http://www.php-fig.org/psr/psr-4/
[3]: http://www.php-fig.org/psr/psr-2/
[4]: http://sabre.io/
[5]: irc://freenode.net/#sabredav
[6]: http://groups.google.com/group/sabredav-discuss
[7]: http://phpunit.de/

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
Copyright (C) 2007-2016 fruux GmbH (https://fruux.com/).
All rights reserved.

View file

@ -1,30 +1,29 @@
# What is SabreDAV
![sabre's logo](http://sabre.io/img/logo.png) SabreDAV
======================================================
SabreDAV allows you to easily add WebDAV support to a PHP application. SabreDAV is meant to cover the entire standard, and attempts to allow integration using an easy to understand API.
Introduction
------------
### Feature list:
SabreDAV is the most popular WebDAV framework for PHP. Use it to create WebDAV, CalDAV and CardDAV servers.
* Fully WebDAV compliant
* Supports Windows XP, Windows Vista, Mac OS/X, DavFSv2, Cadaver, Netdrive, Open Office, and probably more.
* Passing all Litmus tests.
* Supporting class 1, 2 and 3 Webdav servers.
* Locking support.
* Custom property support.
* CalDAV (tested with [Evolution](http://code.google.com/p/sabredav/wiki/Evolution), [iCal](http://code.google.com/p/sabredav/wiki/ICal), [iPhone](http://code.google.com/p/sabredav/wiki/IPhone) and [Lightning](http://code.google.com/p/sabredav/wiki/Lightning)).
* CardDAV (tested with [OS/X addressbook](http://code.google.com/p/sabredav/wiki/OSXAddressbook), the [iOS addressbook](http://code.google.com/p/sabredav/wiki/iOSCardDAV) and [Evolution](http://code.google.com/p/sabredav/wiki/Evolution)).
* Over 97% unittest code coverage.
Full documentation can be found on the website:
### Supported RFC's:
http://sabre.io/
* [RFC2617](http://www.ietf.org/rfc/rfc2617.txt): Basic/Digest auth.
* [RFC2518](http://www.ietf.org/rfc/rfc2518.txt): First WebDAV spec.
* [RFC3744](http://www.ietf.org/rfc/rfc3744.txt): ACL (some features missing).
* [RFC4709](http://www.ietf.org/rfc/rfc4709.txt): [DavMount](http://code.google.com/p/sabredav/wiki/DavMount).
* [RFC4791](http://www.ietf.org/rfc/rfc4791.txt): CalDAV.
* [RFC4918](http://www.ietf.org/rfc/rfc4918.txt): WebDAV revision.
* [RFC5397](http://www.ietf.org/rfc/rfc5689.txt): current-user-principal.
* [RFC5689](http://www.ietf.org/rfc/rfc5689.txt): Extended MKCOL.
* [RFC5789](http://tools.ietf.org/html/rfc5789): PATCH method for HTTP.
* [RFC6352](http://www.ietf.org/rfc/rfc6352.txt): CardDAV
* [draft-daboo-carddav-directory-gateway](http://tools.ietf.org/html/draft-daboo-carddav-directory-gateway): CardDAV directory gateway
* CalDAV ctag, CalDAV-proxy.
Build status
------------
| branch | status | minimum PHP version |
| ------------ | ------ | ------------------- |
| master | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=master)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.5 |
| 3.0 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=3.0)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.4 |
| 2.1 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=2.1)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.4 |
| 2.0 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=2.0)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.4 |
| 1.8 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=1.8)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.3 |
| 1.7 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=1.7)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.3 |
| 1.6 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=1.6)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.3 |
Made at fruux
-------------
SabreDAV is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support.

46
vendor/sabre/dav/bin/build.php vendored Normal file → Executable file
View file

@ -1,3 +1,4 @@
#!/usr/bin/env php
<?php
$tasks = [
@ -100,7 +101,7 @@ function composerupdate() {
global $baseDir;
echo " Updating composer packages to latest version\n\n";
system('cd ' . $baseDir . '; composer update --dev');
system('cd ' . $baseDir . '; composer update');
}
function test() {
@ -120,12 +121,51 @@ function test() {
function buildzip() {
global $baseDir, $version;
echo " Asking composer to download sabre/dav $version\n\n";
system("composer create-project --no-dev sabre/dav build/SabreDAV $version", $code);
echo " Generating composer.json\n";
$input = json_decode(file_get_contents(__DIR__ . '/../composer.json'), true);
$newComposer = [
"require" => $input['require'],
"config" => [
"bin-dir" => "./bin",
],
"prefer-stable" => true,
"minimum-stability" => "alpha",
];
unset(
$newComposer['require']['sabre/vobject'],
$newComposer['require']['sabre/http'],
$newComposer['require']['sabre/uri'],
$newComposer['require']['sabre/event']
);
$newComposer['require']['sabre/dav'] = $version;
mkdir('build/SabreDAV');
file_put_contents('build/SabreDAV/composer.json', json_encode($newComposer, JSON_PRETTY_PRINT));
echo " Downloading dependencies\n";
system("cd build/SabreDAV; composer install -n", $code);
if ($code !== 0) {
echo "Composer reported error code $code\n";
die(1);
}
echo " Removing pointless files\n";
unlink('build/SabreDAV/composer.json');
unlink('build/SabreDAV/composer.lock');
echo " Moving important files to the root of the project\n";
$fileNames = [
'CHANGELOG.md',
'LICENSE',
'README.md',
'examples',
];
foreach ($fileNames as $fileName) {
echo " $fileName\n";
rename('build/SabreDAV/vendor/sabre/dav/' . $fileName, 'build/SabreDAV/' . $fileName);
}
// <zip destfile="build/SabreDAV-${sabredav.version}.zip" basedir="build/SabreDAV" prefix="SabreDAV/" />
echo "\n";

View file

@ -35,10 +35,10 @@ HELLO;
// There's a bunch of places where the autoloader could be, so we'll try all of
// them.
$paths = array(
$paths = [
__DIR__ . '/../vendor/autoload.php',
__DIR__ . '/../../../autoload.php',
);
];
foreach ($paths as $path) {
if (file_exists($path)) {
@ -67,13 +67,13 @@ if (!$row) {
exit(-1);
}
$requiredFields = array(
$requiredFields = [
'id',
'calendardata',
'uri',
'calendarid',
'lastmodified',
);
];
foreach ($requiredFields as $requiredField) {
if (!array_key_exists($requiredField, $row)) {
@ -83,13 +83,13 @@ foreach($requiredFields as $requiredField) {
}
}
$fields17 = array(
$fields17 = [
'etag',
'size',
'componenttype',
'firstoccurence',
'lastoccurence',
);
];
$found = 0;
foreach ($fields17 as $field) {
@ -161,14 +161,14 @@ while($row = $result->fetch()) {
echo "This record is ignored, you should inspect it to see if there's anything wrong.\n===\n";
continue;
}
$stmt->execute(array(
$stmt->execute([
$newData['etag'],
$newData['size'],
$newData['componentType'],
$newData['firstOccurence'],
$newData['lastOccurence'],
$row['id'],
));
]);
$done++;
if ($done % 500 === 0) {
@ -256,7 +256,7 @@ function getDenormalizedData($calendarData) {
$lastOccurence = $firstOccurence;
}
} else {
$it = new \Sabre\VObject\RecurrenceIterator($vObject, (string)$component->UID);
$it = new \Sabre\VObject\Recur\EventIterator($vObject, (string)$component->UID);
$maxDate = new DateTime(\Sabre\CalDAV\Backend\PDO::MAX_DATE);
if ($it->isInfinite()) {
$lastOccurence = $maxDate->getTimeStamp();
@ -273,12 +273,12 @@ function getDenormalizedData($calendarData) {
}
}
return array(
return [
'etag' => md5($calendarData),
'size' => strlen($calendarData),
'componentType' => $componentType,
'firstOccurence' => $firstOccurence,
'lastOccurence' => $lastOccurence,
);
];
}

453
vendor/sabre/dav/bin/migrateto20.php vendored Executable file
View file

@ -0,0 +1,453 @@
#!/usr/bin/env php
<?php
echo "SabreDAV migrate script for version 2.0\n";
if ($argc < 2) {
echo <<<HELLO
This script help you migrate from a pre-2.0 database to 2.0 and later
The 'calendars', 'addressbooks' and 'cards' tables will be upgraded, and new
tables (calendarchanges, addressbookchanges, propertystorage) will be added.
If you don't use the default PDO CalDAV or CardDAV backend, it's pointless to
run this script.
Keep in mind that ALTER TABLE commands will be executed. If you have a large
dataset this may mean that this process takes a while.
Lastly: Make a back-up first. This script has been tested, but the amount of
potential variants are extremely high, so it's impossible to deal with every
possible situation.
In the worst case, you will lose all your data. This is not an overstatement.
Usage:
php {$argv[0]} [pdo-dsn] [username] [password]
For example:
php {$argv[0]} "mysql:host=localhost;dbname=sabredav" root password
php {$argv[0]} sqlite:data/sabredav.db
HELLO;
exit();
}
// There's a bunch of places where the autoloader could be, so we'll try all of
// them.
$paths = [
__DIR__ . '/../vendor/autoload.php',
__DIR__ . '/../../../autoload.php',
];
foreach ($paths as $path) {
if (file_exists($path)) {
include $path;
break;
}
}
$dsn = $argv[1];
$user = isset($argv[2]) ? $argv[2] : null;
$pass = isset($argv[3]) ? $argv[3] : null;
echo "Connecting to database: " . $dsn . "\n";
$pdo = new PDO($dsn, $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
switch ($driver) {
case 'mysql' :
echo "Detected MySQL.\n";
break;
case 'sqlite' :
echo "Detected SQLite.\n";
break;
default :
echo "Error: unsupported driver: " . $driver . "\n";
die(-1);
}
foreach (['calendar', 'addressbook'] as $itemType) {
$tableName = $itemType . 's';
$tableNameOld = $tableName . '_old';
$changesTable = $itemType . 'changes';
echo "Upgrading '$tableName'\n";
// The only cross-db way to do this, is to just fetch a single record.
$row = $pdo->query("SELECT * FROM $tableName LIMIT 1")->fetch();
if (!$row) {
echo "No records were found in the '$tableName' table.\n";
echo "\n";
echo "We're going to rename the old table to $tableNameOld (just in case).\n";
echo "and re-create the new table.\n";
switch ($driver) {
case 'mysql' :
$pdo->exec("RENAME TABLE $tableName TO $tableNameOld");
switch ($itemType) {
case 'calendar' :
$pdo->exec("
CREATE TABLE calendars (
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
principaluri VARCHAR(100),
displayname VARCHAR(100),
uri VARCHAR(200),
synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1',
description TEXT,
calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0',
calendarcolor VARCHAR(10),
timezone TEXT,
components VARCHAR(20),
transparent TINYINT(1) NOT NULL DEFAULT '0',
UNIQUE(principaluri, uri)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
");
break;
case 'addressbook' :
$pdo->exec("
CREATE TABLE addressbooks (
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
principaluri VARCHAR(255),
displayname VARCHAR(255),
uri VARCHAR(200),
description TEXT,
synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1',
UNIQUE(principaluri, uri)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
");
break;
}
break;
case 'sqlite' :
$pdo->exec("ALTER TABLE $tableName RENAME TO $tableNameOld");
switch ($itemType) {
case 'calendar' :
$pdo->exec("
CREATE TABLE calendars (
id integer primary key asc,
principaluri text,
displayname text,
uri text,
synctoken integer,
description text,
calendarorder integer,
calendarcolor text,
timezone text,
components text,
transparent bool
);
");
break;
case 'addressbook' :
$pdo->exec("
CREATE TABLE addressbooks (
id integer primary key asc,
principaluri text,
displayname text,
uri text,
description text,
synctoken integer
);
");
break;
}
break;
}
echo "Creation of 2.0 $tableName table is complete\n";
} else {
// Checking if there's a synctoken field already.
if (array_key_exists('synctoken', $row)) {
echo "The 'synctoken' field already exists in the $tableName table.\n";
echo "It's likely you already upgraded, so we're simply leaving\n";
echo "the $tableName table alone\n";
} else {
echo "1.8 table schema detected\n";
switch ($driver) {
case 'mysql' :
$pdo->exec("ALTER TABLE $tableName ADD synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1'");
$pdo->exec("ALTER TABLE $tableName DROP ctag");
$pdo->exec("UPDATE $tableName SET synctoken = '1'");
break;
case 'sqlite' :
$pdo->exec("ALTER TABLE $tableName ADD synctoken integer");
$pdo->exec("UPDATE $tableName SET synctoken = '1'");
echo "Note: there's no easy way to remove fields in sqlite.\n";
echo "The ctag field is no longer used, but it's kept in place\n";
break;
}
echo "Upgraded '$tableName' to 2.0 schema.\n";
}
}
try {
$pdo->query("SELECT * FROM $changesTable LIMIT 1");
echo "'$changesTable' already exists. Assuming that this part of the\n";
echo "upgrade was already completed.\n";
} catch (Exception $e) {
echo "Creating '$changesTable' table.\n";
switch ($driver) {
case 'mysql' :
$pdo->exec("
CREATE TABLE $changesTable (
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
uri VARCHAR(200) NOT NULL,
synctoken INT(11) UNSIGNED NOT NULL,
{$itemType}id INT(11) UNSIGNED NOT NULL,
operation TINYINT(1) NOT NULL,
INDEX {$itemType}id_synctoken ({$itemType}id, synctoken)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
");
break;
case 'sqlite' :
$pdo->exec("
CREATE TABLE $changesTable (
id integer primary key asc,
uri text,
synctoken integer,
{$itemType}id integer,
operation bool
);
");
$pdo->exec("CREATE INDEX {$itemType}id_synctoken ON $changesTable ({$itemType}id, synctoken);");
break;
}
}
}
try {
$pdo->query("SELECT * FROM calendarsubscriptions LIMIT 1");
echo "'calendarsubscriptions' already exists. Assuming that this part of the\n";
echo "upgrade was already completed.\n";
} catch (Exception $e) {
echo "Creating calendarsubscriptions table.\n";
switch ($driver) {
case 'mysql' :
$pdo->exec("
CREATE TABLE calendarsubscriptions (
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
uri VARCHAR(200) NOT NULL,
principaluri VARCHAR(100) NOT NULL,
source TEXT,
displayname VARCHAR(100),
refreshrate VARCHAR(10),
calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0',
calendarcolor VARCHAR(10),
striptodos TINYINT(1) NULL,
stripalarms TINYINT(1) NULL,
stripattachments TINYINT(1) NULL,
lastmodified INT(11) UNSIGNED,
UNIQUE(principaluri, uri)
);
");
break;
case 'sqlite' :
$pdo->exec("
CREATE TABLE calendarsubscriptions (
id integer primary key asc,
uri text,
principaluri text,
source text,
displayname text,
refreshrate text,
calendarorder integer,
calendarcolor text,
striptodos bool,
stripalarms bool,
stripattachments bool,
lastmodified int
);
");
$pdo->exec("CREATE INDEX principaluri_uri ON calendarsubscriptions (principaluri, uri);");
break;
}
}
try {
$pdo->query("SELECT * FROM propertystorage LIMIT 1");
echo "'propertystorage' already exists. Assuming that this part of the\n";
echo "upgrade was already completed.\n";
} catch (Exception $e) {
echo "Creating propertystorage table.\n";
switch ($driver) {
case 'mysql' :
$pdo->exec("
CREATE TABLE propertystorage (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
path VARBINARY(1024) NOT NULL,
name VARBINARY(100) NOT NULL,
value MEDIUMBLOB
);
");
$pdo->exec("
CREATE UNIQUE INDEX path_property ON propertystorage (path(600), name(100));
");
break;
case 'sqlite' :
$pdo->exec("
CREATE TABLE propertystorage (
id integer primary key asc,
path TEXT,
name TEXT,
value TEXT
);
");
$pdo->exec("
CREATE UNIQUE INDEX path_property ON propertystorage (path, name);
");
break;
}
}
echo "Upgrading cards table to 2.0 schema\n";
try {
$create = false;
$row = $pdo->query("SELECT * FROM cards LIMIT 1")->fetch();
if (!$row) {
$random = mt_rand(1000, 9999);
echo "There was no data in the cards table, so we're re-creating it\n";
echo "The old table will be renamed to cards_old$random, just in case.\n";
$create = true;
switch ($driver) {
case 'mysql' :
$pdo->exec("RENAME TABLE cards TO cards_old$random");
break;
case 'sqlite' :
$pdo->exec("ALTER TABLE cards RENAME TO cards_old$random");
break;
}
}
} catch (Exception $e) {
echo "Exception while checking cards table. Assuming that the table does not yet exist.\n";
echo "Debug: ", $e->getMessage(), "\n";
$create = true;
}
if ($create) {
switch ($driver) {
case 'mysql' :
$pdo->exec("
CREATE TABLE cards (
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
addressbookid INT(11) UNSIGNED NOT NULL,
carddata MEDIUMBLOB,
uri VARCHAR(200),
lastmodified INT(11) UNSIGNED,
etag VARBINARY(32),
size INT(11) UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
");
break;
case 'sqlite' :
$pdo->exec("
CREATE TABLE cards (
id integer primary key asc,
addressbookid integer,
carddata blob,
uri text,
lastmodified integer,
etag text,
size integer
);
");
break;
}
} else {
switch ($driver) {
case 'mysql' :
$pdo->exec("
ALTER TABLE cards
ADD etag VARBINARY(32),
ADD size INT(11) UNSIGNED NOT NULL;
");
break;
case 'sqlite' :
$pdo->exec("
ALTER TABLE cards ADD etag text;
ALTER TABLE cards ADD size integer;
");
break;
}
echo "Reading all old vcards and populating etag and size fields.\n";
$result = $pdo->query('SELECT id, carddata FROM cards');
$stmt = $pdo->prepare('UPDATE cards SET etag = ?, size = ? WHERE id = ?');
while ($row = $result->fetch(\PDO::FETCH_ASSOC)) {
$stmt->execute([
md5($row['carddata']),
strlen($row['carddata']),
$row['id']
]);
}
}
echo "Upgrade to 2.0 schema completed.\n";

180
vendor/sabre/dav/bin/migrateto21.php vendored Executable file
View file

@ -0,0 +1,180 @@
#!/usr/bin/env php
<?php
echo "SabreDAV migrate script for version 2.1\n";
if ($argc < 2) {
echo <<<HELLO
This script help you migrate from a pre-2.1 database to 2.1.
Changes:
The 'calendarobjects' table will be upgraded.
'schedulingobjects' will be created.
If you don't use the default PDO CalDAV or CardDAV backend, it's pointless to
run this script.
Keep in mind that ALTER TABLE commands will be executed. If you have a large
dataset this may mean that this process takes a while.
Lastly: Make a back-up first. This script has been tested, but the amount of
potential variants are extremely high, so it's impossible to deal with every
possible situation.
In the worst case, you will lose all your data. This is not an overstatement.
Usage:
php {$argv[0]} [pdo-dsn] [username] [password]
For example:
php {$argv[0]} "mysql:host=localhost;dbname=sabredav" root password
php {$argv[0]} sqlite:data/sabredav.db
HELLO;
exit();
}
// There's a bunch of places where the autoloader could be, so we'll try all of
// them.
$paths = [
__DIR__ . '/../vendor/autoload.php',
__DIR__ . '/../../../autoload.php',
];
foreach ($paths as $path) {
if (file_exists($path)) {
include $path;
break;
}
}
$dsn = $argv[1];
$user = isset($argv[2]) ? $argv[2] : null;
$pass = isset($argv[3]) ? $argv[3] : null;
echo "Connecting to database: " . $dsn . "\n";
$pdo = new PDO($dsn, $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
switch ($driver) {
case 'mysql' :
echo "Detected MySQL.\n";
break;
case 'sqlite' :
echo "Detected SQLite.\n";
break;
default :
echo "Error: unsupported driver: " . $driver . "\n";
die(-1);
}
echo "Upgrading 'calendarobjects'\n";
$addUid = false;
try {
$result = $pdo->query('SELECT * FROM calendarobjects LIMIT 1');
$row = $result->fetch(\PDO::FETCH_ASSOC);
if (!$row) {
echo "No data in table. Going to try to add the uid field anyway.\n";
$addUid = true;
} elseif (array_key_exists('uid', $row)) {
echo "uid field exists. Assuming that this part of the migration has\n";
echo "Already been completed.\n";
} else {
echo "2.0 schema detected.\n";
$addUid = true;
}
} catch (Exception $e) {
echo "Could not find a calendarobjects table. Skipping this part of the\n";
echo "upgrade.\n";
}
if ($addUid) {
switch ($driver) {
case 'mysql' :
$pdo->exec('ALTER TABLE calendarobjects ADD uid VARCHAR(200)');
break;
case 'sqlite' :
$pdo->exec('ALTER TABLE calendarobjects ADD uid TEXT');
break;
}
$result = $pdo->query('SELECT id, calendardata FROM calendarobjects');
$stmt = $pdo->prepare('UPDATE calendarobjects SET uid = ? WHERE id = ?');
$counter = 0;
while ($row = $result->fetch(\PDO::FETCH_ASSOC)) {
try {
$vobj = \Sabre\VObject\Reader::read($row['calendardata']);
} catch (\Exception $e) {
echo "Warning! Item with id $row[id] could not be parsed!\n";
continue;
}
$uid = null;
$item = $vobj->getBaseComponent();
if (!isset($item->UID)) {
echo "Warning! Item with id $item[id] does NOT have a UID property and this is required.\n";
continue;
}
$uid = (string)$item->UID;
$stmt->execute([$uid, $row['id']]);
$counter++;
}
}
echo "Creating 'schedulingobjects'\n";
switch ($driver) {
case 'mysql' :
$pdo->exec('CREATE TABLE IF NOT EXISTS schedulingobjects
(
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
principaluri VARCHAR(255),
calendardata MEDIUMBLOB,
uri VARCHAR(200),
lastmodified INT(11) UNSIGNED,
etag VARCHAR(32),
size INT(11) UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
');
break;
case 'sqlite' :
$pdo->exec('CREATE TABLE IF NOT EXISTS schedulingobjects (
id integer primary key asc,
principaluri text,
calendardata blob,
uri text,
lastmodified integer,
etag text,
size integer
)
');
break;
$pdo->exec('
CREATE INDEX principaluri_uri ON calendarsubscriptions (principaluri, uri);
');
break;
}
echo "Done.\n";
echo "Upgrade to 2.1 schema completed.\n";

171
vendor/sabre/dav/bin/migrateto30.php vendored Executable file
View file

@ -0,0 +1,171 @@
#!/usr/bin/env php
<?php
echo "SabreDAV migrate script for version 3.0\n";
if ($argc < 2) {
echo <<<HELLO
This script help you migrate from a pre-3.0 database to 3.0 and later
Changes:
* The propertystorage table has changed to allow storage of complex
properties.
* the vcardurl field in the principals table is no more. This was moved to
the propertystorage table.
Keep in mind that ALTER TABLE commands will be executed. If you have a large
dataset this may mean that this process takes a while.
Lastly: Make a back-up first. This script has been tested, but the amount of
potential variants are extremely high, so it's impossible to deal with every
possible situation.
In the worst case, you will lose all your data. This is not an overstatement.
Usage:
php {$argv[0]} [pdo-dsn] [username] [password]
For example:
php {$argv[0]} "mysql:host=localhost;dbname=sabredav" root password
php {$argv[0]} sqlite:data/sabredav.db
HELLO;
exit();
}
// There's a bunch of places where the autoloader could be, so we'll try all of
// them.
$paths = [
__DIR__ . '/../vendor/autoload.php',
__DIR__ . '/../../../autoload.php',
];
foreach ($paths as $path) {
if (file_exists($path)) {
include $path;
break;
}
}
$dsn = $argv[1];
$user = isset($argv[2]) ? $argv[2] : null;
$pass = isset($argv[3]) ? $argv[3] : null;
echo "Connecting to database: " . $dsn . "\n";
$pdo = new PDO($dsn, $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
switch ($driver) {
case 'mysql' :
echo "Detected MySQL.\n";
break;
case 'sqlite' :
echo "Detected SQLite.\n";
break;
default :
echo "Error: unsupported driver: " . $driver . "\n";
die(-1);
}
echo "Upgrading 'propertystorage'\n";
$addValueType = false;
try {
$result = $pdo->query('SELECT * FROM propertystorage LIMIT 1');
$row = $result->fetch(\PDO::FETCH_ASSOC);
if (!$row) {
echo "No data in table. Going to re-create the table.\n";
$random = mt_rand(1000, 9999);
echo "Renaming propertystorage -> propertystorage_old$random and creating new table.\n";
switch ($driver) {
case 'mysql' :
$pdo->exec('RENAME TABLE propertystorage TO propertystorage_old' . $random);
$pdo->exec('
CREATE TABLE propertystorage (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
path VARBINARY(1024) NOT NULL,
name VARBINARY(100) NOT NULL,
valuetype INT UNSIGNED,
value MEDIUMBLOB
);
');
$pdo->exec('CREATE UNIQUE INDEX path_property_' . $random . ' ON propertystorage (path(600), name(100));');
break;
case 'sqlite' :
$pdo->exec('ALTER TABLE propertystorage RENAME TO propertystorage_old' . $random);
$pdo->exec('
CREATE TABLE propertystorage (
id integer primary key asc,
path text,
name text,
valuetype integer,
value blob
);');
$pdo->exec('CREATE UNIQUE INDEX path_property_' . $random . ' ON propertystorage (path, name);');
break;
}
} elseif (array_key_exists('valuetype', $row)) {
echo "valuetype field exists. Assuming that this part of the migration has\n";
echo "Already been completed.\n";
} else {
echo "2.1 schema detected. Going to perform upgrade.\n";
$addValueType = true;
}
} catch (Exception $e) {
echo "Could not find a propertystorage table. Skipping this part of the\n";
echo "upgrade.\n";
echo $e->getMessage(), "\n";
}
if ($addValueType) {
switch ($driver) {
case 'mysql' :
$pdo->exec('ALTER TABLE propertystorage ADD valuetype INT UNSIGNED');
break;
case 'sqlite' :
$pdo->exec('ALTER TABLE propertystorage ADD valuetype INT');
break;
}
$pdo->exec('UPDATE propertystorage SET valuetype = 1 WHERE valuetype IS NULL ');
}
echo "Migrating vcardurl\n";
$result = $pdo->query('SELECT id, uri, vcardurl FROM principals WHERE vcardurl IS NOT NULL');
$stmt1 = $pdo->prepare('INSERT INTO propertystorage (path, name, valuetype, value) VALUES (?, ?, 3, ?)');
while ($row = $result->fetch(\PDO::FETCH_ASSOC)) {
// Inserting the new record
$stmt1->execute([
'addressbooks/' . basename($row['uri']),
'{http://calendarserver.org/ns/}me-card',
serialize(new Sabre\DAV\Xml\Property\Href($row['vcardurl']))
]);
echo serialize(new Sabre\DAV\Xml\Property\Href($row['vcardurl']));
}
echo "Done.\n";
echo "Upgrade to 3.0 schema completed.\n";

View file

@ -6,7 +6,7 @@
# http://www.rooftopsolutions.nl/
#
# This utility is distributed along with SabreDAV
# license: http://code.google.com/p/sabredav/wiki/License Modified BSD License
# license: http://sabre.io/license/ Modified BSD License
import os
from optparse import OptionParser
@ -16,16 +16,16 @@ def getfreespace(path):
stat = os.statvfs(path)
return stat.f_frsize * stat.f_bavail
def getbytesleft(path,treshold):
return getfreespace(path)-treshold
def getbytesleft(path,threshold):
return getfreespace(path)-threshold
def run(cacheDir, treshold, sleep=5, simulate=False, min_erase = 0):
def run(cacheDir, threshold, sleep=5, simulate=False, min_erase = 0):
bytes = getbytesleft(cacheDir,treshold)
bytes = getbytesleft(cacheDir,threshold)
if (bytes>0):
print "Bytes to go before we hit treshhold:", bytes
print "Bytes to go before we hit threshold:", bytes
else:
print "Treshold exceeded with:", -bytes, "bytes"
print "Threshold exceeded with:", -bytes, "bytes"
dir = os.listdir(cacheDir)
dir2 = []
for file in dir:
@ -46,7 +46,7 @@ def run(cacheDir, treshold, sleep=5, simulate=False, min_erase = 0):
left = min_erase
# If the min_erase setting is lower than the amount of bytes over
# the treshold, we use that number instead.
# the threshold, we use that number instead.
if left < -bytes :
left = -bytes
@ -72,8 +72,8 @@ def run(cacheDir, treshold, sleep=5, simulate=False, min_erase = 0):
def main():
parser = OptionParser(
version="naturalselecton v0.3",
description="Cache directory manager. Deletes cache entries based on accesstime and free space tresholds.\n" +
version="naturalselection v0.3",
description="Cache directory manager. Deletes cache entries based on accesstime and free space thresholds.\n" +
"This utility is distributed alongside SabreDAV.",
usage="usage: %prog [options] cacheDirectory",
)
@ -98,15 +98,15 @@ def main():
default=5
)
parser.add_option(
'-l','--treshold',
help="Treshhold in bytes (default = 10737418240, which is 10GB)",
'-l','--threshold',
help="Threshold in bytes (default = 10737418240, which is 10GB)",
type="int",
dest="treshold",
dest="threshold",
default=10737418240
)
parser.add_option(
'-m', '--min-erase',
help="Minimum number of bytes to erase when the treshold is reached. " +
help="Minimum number of bytes to erase when the threshold is reached. " +
"Setting this option higher will reduce the amount of times the cache directory will need to be scanned. " +
"(the default is 1073741824, which is 1GB.)",
type="int",
@ -130,7 +130,7 @@ def main():
cacheDir,
sleep=options.sleep,
simulate=options.simulate,
treshold=options.treshold,
threshold=options.threshold,
min_erase=options.min_erase
)
if runs>0:

View file

@ -27,10 +27,10 @@ if (php_sapi_name()!=='cli-server') {
// Finding composer
$paths = array(
$paths = [
__DIR__ . '/../vendor/autoload.php',
__DIR__ . '/../../../autoload.php',
);
];
foreach ($paths as $path) {
if (file_exists($path)) {

View file

@ -35,22 +35,23 @@ $carddavBackend = new Sabre\CardDAV\Backend\PDO($pdo);
//$caldavBackend = new Sabre\CalDAV\Backend\PDO($pdo);
// Setting up the directory tree //
$nodes = array(
$nodes = [
new Sabre\DAVACL\PrincipalCollection($principalBackend),
// new Sabre\CalDAV\CalendarRootNode($authBackend, $caldavBackend),
// new Sabre\CalDAV\CalendarRoot($authBackend, $caldavBackend),
new Sabre\CardDAV\AddressBookRoot($principalBackend, $carddavBackend),
);
];
// The object tree needs in turn to be passed to the server class
$server = new Sabre\DAV\Server($nodes);
$server->setBaseUri($baseUri);
// Plugins
$server->addPlugin(new Sabre\DAV\Auth\Plugin($authBackend,'SabreDAV'));
$server->addPlugin(new Sabre\DAV\Auth\Plugin($authBackend));
$server->addPlugin(new Sabre\DAV\Browser\Plugin());
//$server->addPlugin(new Sabre\CalDAV\Plugin());
$server->addPlugin(new Sabre\CardDAV\Plugin());
$server->addPlugin(new Sabre\DAVACL\Plugin());
$server->addPlugin(new Sabre\DAV\Sync\Plugin());
// And off we go!
$server->exec();

View file

@ -1,26 +0,0 @@
<?php
// !!!! Make sure the Sabre directory is in the include_path !!!
// example:
// set_include_path('lib/' . PATH_SEPARATOR . get_include_path());
// settings
date_default_timezone_set('Canada/Eastern');
// Files we need
require_once 'vendor/autoload.php';
$u = 'admin';
$p = '1234';
$auth = new \Sabre\HTTP\BasicAuth();
$result = $auth->getUserPass();
if (!$result || $result[0]!=$u || $result[1]!=$p) {
$auth->requireLogin();
echo "Authentication required\n";
die();
}

View file

@ -1,25 +0,0 @@
<?php
// !!!! Make sure the Sabre directory is in the include_path !!!
// example:
// set_include_path('lib/' . PATH_SEPARATOR . get_include_path());
// settings
date_default_timezone_set('Canada/Eastern');
// Files we need
require_once 'vendor/autoload.php';
$u = 'admin';
$p = '1234';
$auth = new \Sabre\HTTP\DigestAuth();
$auth->init();
if ($auth->getUsername() != $u || !$auth->validatePassword($p)) {
$auth->requireLogin();
echo "Authentication required\n";
die();
}

View file

@ -1,123 +0,0 @@
<?php
// !!!! Make sure the Sabre directory is in the include_path !!!
// example:
// set_include_path('lib/' . PATH_SEPARATOR . get_include_path());
/*
This example demonstrates a simple way to create your own virtual filesystems.
By extending the _File and Directory classes, you can easily create a tree
based on various datasources.
The most obvious example is the filesystem itself. A more complete and documented
example can be found in:
lib/Sabre/DAV/FS/Node.php
lib/Sabre/DAV/FS/Directory.php
lib/Sabre/DAV/FS/File.php
*/
// settings
date_default_timezone_set('Canada/Eastern');
$publicDir = 'public';
// Files we need
require_once 'vendor/autoload.php';
class MyCollection extends Sabre\DAV\Collection {
private $myPath;
function __construct($myPath) {
$this->myPath = $myPath;
}
function getChildren() {
$children = array();
// Loop through the directory, and create objects for each node
foreach(scandir($this->myPath) as $node) {
// Ignoring files staring with .
if ($node[0]==='.') continue;
$children[] = $this->getChild($node);
}
return $children;
}
function getChild($name) {
$path = $this->myPath . '/' . $name;
// We have to throw a NotFound exception if the file didn't exist
if (!file\exists($this->myPath)) throw new \Sabre\DAV\Exception\NotFound('The file with name: ' . $name . ' could not be found');
// Some added security
if ($name[0]=='.') throw new \Sabre\DAV\Exception\Forbidden('Access denied');
if (is_dir($path)) {
return new \MyCollection($name);
} else {
return new \MyFile($path);
}
}
function getName() {
return basename($this->myPath);
}
}
class MyFile extends \Sabre\DAV\File {
private $myPath;
function __construct($myPath) {
$this->myPath = $myPath;
}
function getName() {
return basename($this->myPath);
}
function get() {
return fopen($this->myPath,'r');
}
function getSize() {
return filesize($this->myPath);
}
}
// Make sure there is a directory in your current directory named 'public'. We will be exposing that directory to WebDAV
$rootNode = new \MyCollection($publicDir);
// The rootNode needs to be passed to the server object.
$server = new \Sabre\DAV\Server($rootNode);
// And off we go!
$server->exec();

View file

@ -1,18 +1,28 @@
CREATE TABLE addressbooks (
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
principaluri VARCHAR(255),
principaluri VARBINARY(255),
displayname VARCHAR(255),
uri VARCHAR(200),
uri VARBINARY(200),
description TEXT,
ctag INT(11) UNSIGNED NOT NULL DEFAULT '1',
UNIQUE(principaluri, uri)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1',
UNIQUE(principaluri(100), uri(100))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE cards (
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
addressbookid INT(11) UNSIGNED NOT NULL,
carddata MEDIUMBLOB,
uri VARCHAR(200),
lastmodified INT(11) UNSIGNED
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
uri VARBINARY(200),
lastmodified INT(11) UNSIGNED,
etag VARBINARY(32),
size INT(11) UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE addressbookchanges (
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
uri VARBINARY(200) NOT NULL,
synctoken INT(11) UNSIGNED NOT NULL,
addressbookid INT(11) UNSIGNED NOT NULL,
operation TINYINT(1) NOT NULL,
INDEX addressbookid_synctoken (addressbookid, synctoken)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View file

@ -1,28 +1,64 @@
CREATE TABLE calendarobjects (
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
calendardata MEDIUMBLOB,
uri VARCHAR(200),
uri VARBINARY(200),
calendarid INTEGER UNSIGNED NOT NULL,
lastmodified INT(11) UNSIGNED,
etag VARCHAR(32),
etag VARBINARY(32),
size INT(11) UNSIGNED NOT NULL,
componenttype VARCHAR(8),
componenttype VARBINARY(8),
firstoccurence INT(11) UNSIGNED,
lastoccurence INT(11) UNSIGNED,
uid VARBINARY(200),
UNIQUE(calendarid, uri)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE calendars (
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
principaluri VARCHAR(100),
principaluri VARBINARY(100),
displayname VARCHAR(100),
uri VARCHAR(200),
ctag INTEGER UNSIGNED NOT NULL DEFAULT '0',
uri VARBINARY(200),
synctoken INTEGER UNSIGNED NOT NULL DEFAULT '1',
description TEXT,
calendarorder INTEGER UNSIGNED NOT NULL DEFAULT '0',
calendarcolor VARCHAR(10),
calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0',
calendarcolor VARBINARY(10),
timezone TEXT,
components VARCHAR(20),
components VARBINARY(21),
transparent TINYINT(1) NOT NULL DEFAULT '0',
UNIQUE(principaluri, uri)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE calendarchanges (
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
uri VARBINARY(200) NOT NULL,
synctoken INT(11) UNSIGNED NOT NULL,
calendarid INT(11) UNSIGNED NOT NULL,
operation TINYINT(1) NOT NULL,
INDEX calendarid_synctoken (calendarid, synctoken)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE calendarsubscriptions (
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
uri VARBINARY(200) NOT NULL,
principaluri VARBINARY(100) NOT NULL,
source TEXT,
displayname VARCHAR(100),
refreshrate VARCHAR(10),
calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0',
calendarcolor VARBINARY(10),
striptodos TINYINT(1) NULL,
stripalarms TINYINT(1) NULL,
stripattachments TINYINT(1) NULL,
lastmodified INT(11) UNSIGNED,
UNIQUE(principaluri, uri)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE schedulingobjects (
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
principaluri VARBINARY(255),
calendardata MEDIUMBLOB,
uri VARBINARY(200),
lastmodified INT(11) UNSIGNED,
etag VARBINARY(32),
size INT(11) UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View file

@ -3,11 +3,10 @@ CREATE TABLE locks (
owner VARCHAR(100),
timeout INTEGER UNSIGNED,
created INTEGER,
token VARCHAR(100),
token VARBINARY(100),
scope TINYINT,
depth TINYINT,
uri VARCHAR(1000),
uri VARBINARY(1000),
INDEX(token),
INDEX(uri)
);
INDEX(uri(100))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View file

@ -1,19 +1,17 @@
CREATE TABLE principals (
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
uri VARCHAR(200) NOT NULL,
email VARCHAR(80),
uri VARBINARY(200) NOT NULL,
email VARBINARY(80),
displayname VARCHAR(80),
vcardurl VARCHAR(255),
UNIQUE(uri)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE groupmembers (
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
principal_id INTEGER UNSIGNED NOT NULL,
member_id INTEGER UNSIGNED NOT NULL,
UNIQUE(principal_id, member_id)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO principals (uri,email,displayname) VALUES
('principals/admin', 'admin@example.org','Administrator'),

View file

@ -1,9 +1,9 @@
CREATE TABLE users (
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50),
digesta1 VARCHAR(32),
username VARBINARY(50),
digesta1 VARBINARY(32),
UNIQUE(username)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO users (username,digesta1) VALUES
('admin', '87fd274b7b6c01e48d7c2f965da8ddf7');

View file

@ -4,7 +4,7 @@ CREATE TABLE addressbooks (
displayname VARCHAR(255),
uri VARCHAR(200),
description TEXT,
ctag INTEGER NOT NULL DEFAULT 1
synctoken INTEGER NOT NULL DEFAULT 1
);
ALTER TABLE ONLY addressbooks
@ -18,7 +18,9 @@ CREATE TABLE cards (
addressbookid INTEGER NOT NULL,
carddata TEXT,
uri VARCHAR(200),
lastmodified INTEGER
lastmodified INTEGER,
etag VARCHAR(32),
size INTEGER NOT NULL
);
ALTER TABLE ONLY cards
@ -31,3 +33,20 @@ ALTER TABLE ONLY cards
ADD CONSTRAINT cards_addressbookid_fkey FOREIGN KEY (addressbookid) REFERENCES addressbooks(id)
ON DELETE CASCADE;
CREATE TABLE addressbookchanges (
id SERIAL NOT NULL,
uri VARCHAR(200) NOT NULL,
synctoken INTEGER NOT NULL,
addressbookid INTEGER NOT NULL,
operation SMALLINT NOT NULL
);
ALTER TABLE ONLY addressbookchanges
ADD CONSTRAINT addressbookchanges_pkey PRIMARY KEY (id);
CREATE INDEX addressbookchanges_addressbookid_synctoken_ix
ON addressbookchanges USING btree (addressbookid, synctoken);
ALTER TABLE ONLY addressbookchanges
ADD CONSTRAINT addressbookchanges_addressbookid_fkey FOREIGN KEY (addressbookid) REFERENCES addressbooks(id)
ON DELETE CASCADE;

View file

@ -3,12 +3,13 @@ CREATE TABLE calendars (
principaluri VARCHAR(100),
displayname VARCHAR(100),
uri VARCHAR(200),
ctag INTEGER NOT NULL DEFAULT 0,
synctoken INTEGER NOT NULL DEFAULT 1,
description TEXT,
calendarorder INTEGER NOT NULL DEFAULT 0,
calendarcolor VARCHAR(10),
timezone TEXT,
components VARCHAR(20),
uid VARCHAR(200),
transparent SMALLINT NOT NULL DEFAULT '0'
);
@ -20,15 +21,16 @@ CREATE UNIQUE INDEX calendars_ukey
CREATE TABLE calendarobjects (
id SERIAL NOT NULL,
calendarid INTEGER NOT NULL,
calendardata TEXT,
uri VARCHAR(200),
calendarid INTEGER NOT NULL,
lastmodified INTEGER,
etag VARCHAR(32),
size INTEGER NOT NULL,
componenttype VARCHAR(8),
lastmodified INTEGER,
firstoccurence INTEGER,
lastoccurence INTEGER
lastoccurence INTEGER,
uid VARCHAR(200)
);
ALTER TABLE ONLY calendarobjects
@ -40,3 +42,52 @@ CREATE UNIQUE INDEX calendarobjects_ukey
ALTER TABLE ONLY calendarobjects
ADD CONSTRAINT calendarobjects_calendarid_fkey FOREIGN KEY (calendarid) REFERENCES calendars(id)
ON DELETE CASCADE;
CREATE TABLE calendarsubscriptions (
id SERIAL NOT NULL,
uri VARCHAR(200) NOT NULL,
principaluri VARCHAR(100) NOT NULL,
source TEXT,
displayname VARCHAR(100),
refreshrate VARCHAR(10),
calendarorder INTEGER NOT NULL DEFAULT 0,
calendarcolor VARCHAR(10),
striptodos SMALLINT NULL,
stripalarms SMALLINT NULL,
stripattachments SMALLINT NULL,
lastmodified INTEGER
);
ALTER TABLE ONLY calendarsubscriptions
ADD CONSTRAINT calendarsubscriptions_pkey PRIMARY KEY (id);
CREATE UNIQUE INDEX calendarsubscriptions_ukey
ON calendarsubscriptions USING btree (principaluri, uri);
CREATE TABLE calendarchanges (
id SERIAL NOT NULL,
uri VARCHAR(200) NOT NULL,
synctoken INTEGER NOT NULL,
calendarid INTEGER NOT NULL,
operation SMALLINT NOT NULL DEFAULT 0
);
ALTER TABLE ONLY calendarchanges
ADD CONSTRAINT calendarchanges_pkey PRIMARY KEY (id);
CREATE INDEX calendarchanges_calendarid_synctoken_ix
ON calendarchanges USING btree (calendarid, synctoken);
ALTER TABLE ONLY calendarchanges
ADD CONSTRAINT calendarchanges_calendar_fk FOREIGN KEY (calendarid) REFERENCES calendars(id)
ON DELETE CASCADE;
CREATE TABLE schedulingobjects (
id SERIAL NOT NULL,
principaluri VARCHAR(255),
calendardata BYTEA,
uri VARCHAR(200),
lastmodified INTEGER,
etag VARCHAR(32),
size INTEGER NOT NULL
);

View file

@ -4,10 +4,16 @@ CREATE TABLE locks (
timeout INTEGER,
created INTEGER,
token VARCHAR(100),
scope smallint,
depth smallint,
uri text
scope SMALLINT,
depth SMALLINT,
uri TEXT
);
ALTER TABLE ONLY locks
ADD CONSTRAINT locks_pkey PRIMARY KEY (id);
CREATE INDEX locks_token_ix
ON locks USING btree (token);
CREATE INDEX locks_uri_ix
ON locks USING btree (uri);

View file

@ -1,9 +1,8 @@
CREATE TABLE principals (
id SERIAL NOT NULL,
uri VARCHAR(100) NOT NULL,
uri VARCHAR(200) NOT NULL,
email VARCHAR(80),
displayname VARCHAR(80),
vcardurl VARCHAR(255)
displayname VARCHAR(80)
);
ALTER TABLE ONLY principals
@ -28,10 +27,9 @@ ALTER TABLE ONLY groupmembers
ADD CONSTRAINT groupmembers_principal_id_fkey FOREIGN KEY (principal_id) REFERENCES principals(id)
ON DELETE CASCADE;
-- Is this correct correct link ... or not?
-- ALTER TABLE ONLY groupmembers
-- ADD CONSTRAINT groupmembers_member_id_id_fkey FOREIGN KEY (member_id) REFERENCES users(id)
-- ON DELETE CASCADE;
ALTER TABLE ONLY groupmembers
ADD CONSTRAINT groupmembers_member_id_id_fkey FOREIGN KEY (member_id) REFERENCES principals(id)
ON DELETE CASCADE;
INSERT INTO principals (uri,email,displayname) VALUES
('principals/admin', 'admin@example.org','Administrator'),

View file

@ -1,8 +1,7 @@
CREATE TABLE users (
id SERIAL NOT NULL,
username VARCHAR(50),
digesta1 VARCHAR(32),
UNIQUE(username)
digesta1 VARCHAR(32)
);
ALTER TABLE ONLY users

View file

@ -1,17 +1,28 @@
CREATE TABLE addressbooks (
id integer primary key asc,
principaluri text,
id integer primary key asc NOT NULL,
principaluri text NOT NULL,
displayname text,
uri text,
uri text NOT NULL,
description text,
ctag integer
synctoken integer DEFAULT 1 NOT NULL
);
CREATE TABLE cards (
id integer primary key asc,
addressbookid integer,
id integer primary key asc NOT NULL,
addressbookid integer NOT NULL,
carddata blob,
uri text,
lastmodified integer
uri text NOT NULL,
lastmodified integer,
etag text,
size integer
);
CREATE TABLE addressbookchanges (
id integer primary key asc NOT NULL,
uri text,
synctoken integer NOT NULL,
addressbookid integer NOT NULL,
operation integer NOT NULL
);
CREATE INDEX addressbookid_synctoken ON addressbookchanges (addressbookid, synctoken);

View file

@ -1,26 +1,64 @@
CREATE TABLE calendarobjects (
id integer primary key asc,
calendardata blob,
uri text,
calendarid integer,
lastmodified integer,
etag text,
size integer,
id integer primary key asc NOT NULL,
calendardata blob NOT NULL,
uri text NOT NULL,
calendarid integer NOT NULL,
lastmodified integer NOT NULL,
etag text NOT NULL,
size integer NOT NULL,
componenttype text,
firstoccurence integer,
lastoccurence integer
lastoccurence integer,
uid text
);
CREATE TABLE calendars (
id integer primary key asc,
principaluri text,
id integer primary key asc NOT NULL,
principaluri text NOT NULL,
displayname text,
uri text,
ctag integer,
uri text NOT NULL,
synctoken integer DEFAULT 1 NOT NULL,
description text,
calendarorder integer,
calendarcolor text,
timezone text,
components text,
components text NOT NULL,
transparent bool
);
CREATE TABLE calendarchanges (
id integer primary key asc NOT NULL,
uri text,
synctoken integer NOT NULL,
calendarid integer NOT NULL,
operation integer NOT NULL
);
CREATE INDEX calendarid_synctoken ON calendarchanges (calendarid, synctoken);
CREATE TABLE calendarsubscriptions (
id integer primary key asc NOT NULL,
uri text NOT NULL,
principaluri text NOT NULL,
source text NOT NULL,
displayname text,
refreshrate text,
calendarorder integer,
calendarcolor text,
striptodos bool,
stripalarms bool,
stripattachments bool,
lastmodified int
);
CREATE TABLE schedulingobjects (
id integer primary key asc NOT NULL,
principaluri text NOT NULL,
calendardata blob,
uri text NOT NULL,
lastmodified integer,
etag text NOT NULL,
size integer NOT NULL
);
CREATE INDEX principaluri_uri ON calendarsubscriptions (principaluri, uri);

View file

@ -1,6 +1,6 @@
BEGIN TRANSACTION;
CREATE TABLE locks (
id integer primary key asc,
id integer primary key asc NOT NULL,
owner text,
timeout integer,
created integer,

View file

@ -1,16 +1,15 @@
CREATE TABLE principals (
id INTEGER PRIMARY KEY ASC,
uri TEXT,
id INTEGER PRIMARY KEY ASC NOT NULL,
uri TEXT NOT NULL,
email TEXT,
displayname TEXT,
vcardurl TEXT,
UNIQUE(uri)
);
CREATE TABLE groupmembers (
id INTEGER PRIMARY KEY ASC,
principal_id INTEGER,
member_id INTEGER,
id INTEGER PRIMARY KEY ASC NOT NULL,
principal_id INTEGER NOT NULL,
member_id INTEGER NOT NULL,
UNIQUE(principal_id, member_id)
);

View file

@ -1,7 +1,7 @@
CREATE TABLE users (
id integer primary key asc,
username TEXT,
digesta1 TEXT,
id integer primary key asc NOT NULL,
username TEXT NOT NULL,
digesta1 TEXT NOT NULL,
UNIQUE(username)
);

View file

@ -23,10 +23,6 @@
# This is also to prevent high memory usage
php_flag always_populate_raw_post_data off
# This is almost a given, but magic quotes is *still* on on some
# linux distributions
php_flag magic_quotes_gpc off
# SabreDAV is not compatible with mbstring function overloading
php_flag mbstring.func_overload off

View file

@ -0,0 +1,226 @@
<?php
namespace Sabre\CalDAV\Backend;
use Sabre\VObject;
use Sabre\CalDAV;
/**
* Abstract Calendaring backend. Extend this class to create your own backends.
*
* Checkout the BackendInterface for all the methods that must be implemented.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
abstract class AbstractBackend implements BackendInterface {
/**
* Updates properties for a calendar.
*
* 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 updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch) {
}
/**
* Returns a list of calendar objects.
*
* This method should work identical to getCalendarObject, but instead
* return all the calendar objects in the list as an array.
*
* If the backend supports this, it may allow for some speed-ups.
*
* @param mixed $calendarId
* @param array $uris
* @return array
*/
function getMultipleCalendarObjects($calendarId, array $uris) {
return array_map(function($uri) use ($calendarId) {
return $this->getCalendarObject($calendarId, $uri);
}, $uris);
}
/**
* Performs a calendar-query on the contents of this calendar.
*
* The calendar-query is defined in RFC4791 : CalDAV. Using the
* calendar-query it is possible for a client to request a specific set of
* object, based on contents of iCalendar properties, date-ranges and
* iCalendar component types (VTODO, VEVENT).
*
* This method should just return a list of (relative) urls that match this
* query.
*
* The list of filters are specified as an array. The exact array is
* documented by \Sabre\CalDAV\CalendarQueryParser.
*
* Note that it is extremely likely that getCalendarObject for every path
* returned from this method will be called almost immediately after. You
* may want to anticipate this to speed up these requests.
*
* This method provides a default implementation, which parses *all* the
* iCalendar objects in the specified calendar.
*
* This default may well be good enough for personal use, and calendars
* that aren't very large. But if you anticipate high usage, big calendars
* or high loads, you are strongly adviced to optimize certain paths.
*
* The best way to do so is override this method and to optimize
* specifically for 'common filters'.
*
* Requests that are extremely common are:
* * requests for just VEVENTS
* * requests for just VTODO
* * requests with a time-range-filter on either VEVENT or VTODO.
*
* ..and combinations of these requests. It may not be worth it to try to
* handle every possible situation and just rely on the (relatively
* easy to use) CalendarQueryValidator to handle the rest.
*
* Note that especially time-range-filters may be difficult to parse. A
* time-range filter specified on a VEVENT must for instance also handle
* recurrence rules correctly.
* A good example of how to interprete all these filters can also simply
* be found in \Sabre\CalDAV\CalendarQueryFilter. This class is as correct
* as possible, so it gives you a good idea on what type of stuff you need
* to think of.
*
* @param mixed $calendarId
* @param array $filters
* @return array
*/
function calendarQuery($calendarId, array $filters) {
$result = [];
$objects = $this->getCalendarObjects($calendarId);
foreach ($objects as $object) {
if ($this->validateFilterForObject($object, $filters)) {
$result[] = $object['uri'];
}
}
return $result;
}
/**
* This method validates if a filter (as passed to calendarQuery) matches
* the given object.
*
* @param array $object
* @param array $filters
* @return bool
*/
protected function validateFilterForObject(array $object, array $filters) {
// Unfortunately, setting the 'calendardata' here is optional. If
// it was excluded, we actually need another call to get this as
// well.
if (!isset($object['calendardata'])) {
$object = $this->getCalendarObject($object['calendarid'], $object['uri']);
}
$vObject = VObject\Reader::read($object['calendardata']);
$validator = new CalDAV\CalendarQueryValidator();
$result = $validator->validate($vObject, $filters);
// Destroy circular references so PHP will GC the object.
$vObject->destroy();
return $result;
}
/**
* Searches through all of a users calendars and calendar objects to find
* an object with a specific UID.
*
* This method should return the path to this object, relative to the
* calendar home, so this path usually only contains two parts:
*
* calendarpath/objectpath.ics
*
* If the uid is not found, return null.
*
* This method should only consider * objects that the principal owns, so
* any calendars owned by other principals that also appear in this
* collection should be ignored.
*
* @param string $principalUri
* @param string $uid
* @return string|null
*/
function getCalendarObjectByUID($principalUri, $uid) {
// Note: this is a super slow naive implementation of this method. You
// are highly recommended to optimize it, if your backend allows it.
foreach ($this->getCalendarsForUser($principalUri) as $calendar) {
// We must ignore calendars owned by other principals.
if ($calendar['principaluri'] !== $principalUri) {
continue;
}
// Ignore calendars that are shared.
if (isset($calendar['{http://sabredav.org/ns}owner-principal']) && $calendar['{http://sabredav.org/ns}owner-principal'] !== $principalUri) {
continue;
}
$results = $this->calendarQuery(
$calendar['id'],
[
'name' => 'VCALENDAR',
'prop-filters' => [],
'comp-filters' => [
[
'name' => 'VEVENT',
'is-not-defined' => false,
'time-range' => null,
'comp-filters' => [],
'prop-filters' => [
[
'name' => 'UID',
'is-not-defined' => false,
'time-range' => null,
'text-match' => [
'value' => $uid,
'negate-condition' => false,
'collation' => 'i;octet',
],
'param-filters' => [],
],
]
]
],
]
);
if ($results) {
// We have a match
return $calendar['uri'] . '/' . $results[0];
}
}
}
}

View file

@ -5,7 +5,7 @@ namespace Sabre\CalDAV\Backend;
/**
* Every CalDAV backend must at least implement this interface.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
@ -17,7 +17,7 @@ interface BackendInterface {
* 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
* * uri, which is 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.
@ -25,82 +25,74 @@ interface BackendInterface {
* 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.
*
* If you return {http://sabredav.org/ns}read-only and set the value to 1,
* ACL will automatically be put in read-only mode.
*
* @param string $principalUri
* @return array
*/
public function getCalendarsForUser($principalUri);
function getCalendarsForUser($principalUri);
/**
* 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.
* 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);
function createCalendar($principalUri, $calendarUri, array $properties);
/**
* Updates properties for a calendar.
*
* The mutations array uses the propertyName in clark-notation as key,
* and the array value for the property value. In the case a property
* should be deleted, the property value will be null.
* 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.
*
* This method must be atomic. If one property cannot be changed, the
* entire operation must fail.
* Calling the handle method is like telling the PropPatch object "I
* promise I can handle updating this property".
*
* If the operation was successful, true can be returned.
* If the operation failed, false can be returned.
* Read the PropPatch documentation for more info and examples.
*
* Deletion of a non-existent property is always successful.
*
* Lastly, it is optional to return detailed information about any
* failures. In this case an array should be returned with the following
* structure:
*
* array(
* 403 => array(
* '{DAV:}displayname' => null,
* ),
* 424 => array(
* '{DAV:}owner' => null,
* )
* )
*
* In this example it was forbidden to update {DAV:}displayname.
* (403 Forbidden), which in turn also caused {DAV:}owner to fail
* (424 Failed Dependency) because the request needs to be atomic.
*
* @param mixed $calendarId
* @param array $mutations
* @return bool|array
* @param string $path
* @param \Sabre\DAV\PropPatch $propPatch
* @return void
*/
public function updateCalendar($calendarId, array $mutations);
function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch);
/**
* Delete a calendar and all it's objects
* Delete a calendar and all its objects
*
* @param mixed $calendarId
* @return void
*/
public function deleteCalendar($calendarId);
function deleteCalendar($calendarId);
/**
* 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.
* * 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.
@ -116,12 +108,14 @@ interface BackendInterface {
* @param mixed $calendarId
* @return array
*/
public function getCalendarObjects($calendarId);
function getCalendarObjects($calendarId);
/**
* 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.
@ -132,14 +126,30 @@ interface BackendInterface {
* @param string $objectUri
* @return array|null
*/
public function getCalendarObject($calendarId,$objectUri);
function getCalendarObject($calendarId, $objectUri);
/**
* Returns a list of calendar objects.
*
* This method should work identical to getCalendarObject, but instead
* return all the calendar objects in the list as an array.
*
* If the backend supports this, it may allow for some speed-ups.
*
* @param mixed $calendarId
* @param array $uris
* @return array
*/
function getMultipleCalendarObjects($calendarId, array $uris);
/**
* Creates a new calendar object.
*
* 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.
* The object uri is only the basename, or filename and not a full path.
*
* It is possible to 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
@ -150,11 +160,13 @@ interface BackendInterface {
* @param string $calendarData
* @return string|null
*/
public function createCalendarObject($calendarId,$objectUri,$calendarData);
function createCalendarObject($calendarId, $objectUri, $calendarData);
/**
* 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.
@ -168,16 +180,18 @@ interface BackendInterface {
* @param string $calendarData
* @return string|null
*/
public function updateCalendarObject($calendarId,$objectUri,$calendarData);
function updateCalendarObject($calendarId, $objectUri, $calendarData);
/**
* 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);
function deleteCalendarObject($calendarId, $objectUri);
/**
* Performs a calendar-query on the contents of this calendar.
@ -228,6 +242,27 @@ interface BackendInterface {
* @param array $filters
* @return array
*/
public function calendarQuery($calendarId, array $filters);
function calendarQuery($calendarId, array $filters);
/**
* Searches through all of a users calendars and calendar objects to find
* an object with a specific UID.
*
* This method should return the path to this object, relative to the
* calendar home, so this path usually only contains two parts:
*
* calendarpath/objectpath.ics
*
* If the uid is not found, return null.
*
* This method should only consider * objects that the principal owns, so
* any calendars owned by other principals that also appear in this
* collection should be ignored.
*
* @param string $principalUri
* @param string $uid
* @return string|null
*/
function getCalendarObjectByUID($principalUri, $uid);
}

View file

@ -2,6 +2,8 @@
namespace Sabre\CalDAV\Backend;
use Sabre\CalDAV\Xml\Notification\NotificationInterface;
/**
* Adds caldav notification support to a backend.
*
@ -16,7 +18,7 @@ namespace Sabre\CalDAV\Backend;
*
* The primary usecase is to allow for calendar-sharing.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
@ -25,13 +27,10 @@ interface NotificationSupport extends BackendInterface {
/**
* Returns a list of notifications for a given principal url.
*
* The returned array should only consist of implementations of
* \Sabre\CalDAV\Notifications\INotificationType.
*
* @param string $principalUri
* @return array
* @return NotificationInterface[]
*/
public function getNotificationsForPrincipal($principalUri);
function getNotificationsForPrincipal($principalUri);
/**
* This deletes a specific notifcation.
@ -39,9 +38,9 @@ interface NotificationSupport extends BackendInterface {
* This may be called by a client once it deems a notification handled.
*
* @param string $principalUri
* @param \Sabre\CalDAV\Notifications\INotificationType $notification
* @param NotificationInterface $notification
* @return void
*/
public function deleteNotification($principalUri, \Sabre\CalDAV\Notifications\INotificationType $notification);
function deleteNotification($principalUri, NotificationInterface $notification);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,65 @@
<?php
namespace Sabre\CalDAV\Backend;
/**
* Implementing this interface adds CalDAV Scheduling support to your caldav
* server, as defined in rfc6638.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface SchedulingSupport extends BackendInterface {
/**
* Returns a single scheduling object for the inbox collection.
*
* The returned array should contain the following elements:
* * uri - A unique basename for the object. This will be used to
* construct a full uri.
* * calendardata - The iCalendar object
* * lastmodified - The last modification date. Can be an int for a unix
* timestamp, or a PHP DateTime object.
* * etag - A unique token that must change if the object changed.
* * size - The size of the object, in bytes.
*
* @param string $principalUri
* @param string $objectUri
* @return array
*/
function getSchedulingObject($principalUri, $objectUri);
/**
* Returns all scheduling objects for the inbox collection.
*
* These objects should be returned as an array. Every item in the array
* should follow the same structure as returned from getSchedulingObject.
*
* The main difference is that 'calendardata' is optional.
*
* @param string $principalUri
* @return array
*/
function getSchedulingObjects($principalUri);
/**
* Deletes a scheduling object from the inbox collection.
*
* @param string $principalUri
* @param string $objectUri
* @return void
*/
function deleteSchedulingObject($principalUri, $objectUri);
/**
* Creates a new scheduling object. This should land in a users' inbox.
*
* @param string $principalUri
* @param string $objectUri
* @param string $objectData
* @return void
*/
function createSchedulingObject($principalUri, $objectUri, $objectData);
}

View file

@ -70,7 +70,7 @@ namespace Sabre\CalDAV\Backend;
* In the case of an invite, the sharee may reply with an 'accept' or
* 'decline'. These are always represented by:
*
* Sabre\CalDAV\Notifications\Notification\Invite
* Sabre\CalDAV\Notifications\Notification\InviteReply
*
*
* Calendar access by sharees
@ -136,7 +136,7 @@ namespace Sabre\CalDAV\Backend;
* * unpublished
*
* If a calendar is published, the following property should be returned
* for each calendar in getCalendarsForPrincipal.
* for each calendar in getCalendarsForUser.
*
* {http://calendarserver.org/ns/}publish-url
*
@ -157,7 +157,7 @@ namespace Sabre\CalDAV\Backend;
* ==============================================
*
* If Sabre\CalDAV\Property\AllowedSharingModes is returned from
* getCalendarsByUser, this allows the server to specify whether either sharing,
* getCalendarsForUser, this allows the server to specify whether either sharing,
* or publishing is supported.
*
* This allows a client to determine in advance which features are available,
@ -165,7 +165,7 @@ namespace Sabre\CalDAV\Backend;
* the backend, the SharingPlugin automatically injects it and assumes both
* features are available.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/

View file

@ -0,0 +1,89 @@
<?php
namespace Sabre\CalDAV\Backend;
use Sabre\DAV;
/**
* Every CalDAV backend must at least implement this interface.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface SubscriptionSupport extends BackendInterface {
/**
* Returns a list of subscriptions for a principal.
*
* Every subscription is an array with the following keys:
* * id, a unique id that will be used by other functions to modify the
* subscription. This can be the same as the uri or a database key.
* * uri. This is just the 'base uri' or 'filename' of the subscription.
* * principaluri. The owner of the subscription. Almost always the same as
* principalUri passed to this method.
*
* Furthermore, all the subscription info must be returned too:
*
* 1. {DAV:}displayname
* 2. {http://apple.com/ns/ical/}refreshrate
* 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
* should not be stripped).
* 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
* should not be stripped).
* 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
* attachments should not be stripped).
* 6. {http://calendarserver.org/ns/}source (Must be a
* Sabre\DAV\Property\Href).
* 7. {http://apple.com/ns/ical/}calendar-color
* 8. {http://apple.com/ns/ical/}calendar-order
* 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
* (should just be an instance of
* Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
* default components).
*
* @param string $principalUri
* @return array
*/
function getSubscriptionsForUser($principalUri);
/**
* Creates a new subscription for a principal.
*
* If the creation was a success, an id must be returned that can be used to reference
* this subscription in other methods, such as updateSubscription.
*
* @param string $principalUri
* @param string $uri
* @param array $properties
* @return mixed
*/
function createSubscription($principalUri, $uri, array $properties);
/**
* Updates a subscription
*
* 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 mixed $subscriptionId
* @param \Sabre\DAV\PropPatch $propPatch
* @return void
*/
function updateSubscription($subscriptionId, DAV\PropPatch $propPatch);
/**
* Deletes a subscription.
*
* @param mixed $subscriptionId
* @return void
*/
function deleteSubscription($subscriptionId);
}

View file

@ -0,0 +1,81 @@
<?php
namespace Sabre\CalDAV\Backend;
/**
* WebDAV-sync support for CalDAV backends.
*
* In order for backends to advertise support for WebDAV-sync, this interface
* must be implemented.
*
* Implementing this can result in a significant reduction of bandwidth and CPU
* time.
*
* For this to work, you _must_ return a {http://sabredav.org/ns}sync-token
* property from getCalendarsFromUser.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface SyncSupport extends BackendInterface {
/**
* The getChanges method returns all the changes that have happened, since
* the specified syncToken in the specified calendar.
*
* This function should return an array, such as the following:
*
* [
* 'syncToken' => 'The current synctoken',
* 'added' => [
* 'new.txt',
* ],
* 'modified' => [
* 'modified.txt',
* ],
* 'deleted' => [
* 'foo.php.bak',
* 'old.txt'
* ]
* );
*
* The returned syncToken property should reflect the *current* syncToken
* of the calendar, as reported in the {http://sabredav.org/ns}sync-token
* property This is * needed here too, to ensure the operation is atomic.
*
* If the $syncToken argument is specified as null, this is an initial
* sync, and all members should be reported.
*
* The modified property is an array of nodenames that have changed since
* the last token.
*
* The deleted property is an array with nodenames, that have been deleted
* from collection.
*
* The $syncLevel argument is basically the 'depth' of the report. If it's
* 1, you only have to report changes that happened only directly in
* immediate descendants. If it's 2, it should also include changes from
* the nodes below the child collections. (grandchildren)
*
* The $limit argument allows a client to specify how many results should
* be returned at most. If the limit is not specified, it should be treated
* as infinite.
*
* If the limit (infinite or not) is higher than you're willing to return,
* you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
*
* If the syncToken is expired (due to data cleanup) or unknown, you must
* return null.
*
* The limit is 'suggestive'. You are free to ignore it.
*
* @param string $calendarId
* @param string $syncToken
* @param int $syncLevel
* @param int $limit
* @return array
*/
function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null);
}

527
vendor/sabre/dav/lib/CalDAV/Calendar.php vendored Normal file
View file

@ -0,0 +1,527 @@
<?php
namespace Sabre\CalDAV;
use Sabre\DAV;
use Sabre\DAVACL;
use Sabre\DAV\PropPatch;
/**
* This object represents a CalDAV calendar.
*
* A calendar can contain multiple TODO and or Events. These are represented
* as \Sabre\CalDAV\CalendarObject objects.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Calendar implements ICalendar, DAV\IProperties, DAV\Sync\ISyncCollection, DAV\IMultiGet {
/**
* This is an array with calendar information
*
* @var array
*/
protected $calendarInfo;
/**
* CalDAV backend
*
* @var Backend\BackendInterface
*/
protected $caldavBackend;
/**
* Constructor
*
* @param Backend\BackendInterface $caldavBackend
* @param array $calendarInfo
*/
function __construct(Backend\BackendInterface $caldavBackend, $calendarInfo) {
$this->caldavBackend = $caldavBackend;
$this->calendarInfo = $calendarInfo;
}
/**
* Returns the name of the calendar
*
* @return string
*/
function getName() {
return $this->calendarInfo['uri'];
}
/**
* Updates properties on this node.
*
* This method received a PropPatch object, which contains all the
* information about the update.
*
* To update specific properties, call the 'handle' method on this object.
* Read the PropPatch documentation for more information.
*
* @param PropPatch $propPatch
* @return void
*/
function propPatch(PropPatch $propPatch) {
return $this->caldavBackend->updateCalendar($this->calendarInfo['id'], $propPatch);
}
/**
* Returns the list of properties
*
* @param array $requestedProperties
* @return array
*/
function getProperties($requestedProperties) {
$response = [];
foreach ($this->calendarInfo as $propName => $propValue) {
if ($propName[0] === '{')
$response[$propName] = $this->calendarInfo[$propName];
}
return $response;
}
/**
* Returns a calendar object
*
* The contained calendar objects are for example Events or Todo's.
*
* @param string $name
* @return \Sabre\CalDAV\ICalendarObject
*/
function getChild($name) {
$obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name);
if (!$obj) throw new DAV\Exception\NotFound('Calendar object not found');
$obj['acl'] = $this->getChildACL();
return new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj);
}
/**
* Returns the full list of calendar objects
*
* @return array
*/
function getChildren() {
$objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id']);
$children = [];
foreach ($objs as $obj) {
$obj['acl'] = $this->getChildACL();
$children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj);
}
return $children;
}
/**
* This method receives a list of paths in it's first argument.
* It must return an array with Node objects.
*
* If any children are not found, you do not have to return them.
*
* @param string[] $paths
* @return array
*/
function getMultipleChildren(array $paths) {
$objs = $this->caldavBackend->getMultipleCalendarObjects($this->calendarInfo['id'], $paths);
$children = [];
foreach ($objs as $obj) {
$obj['acl'] = $this->getChildACL();
$children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj);
}
return $children;
}
/**
* Checks if a child-node exists.
*
* @param string $name
* @return bool
*/
function childExists($name) {
$obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name);
if (!$obj)
return false;
else
return true;
}
/**
* Creates a new directory
*
* We actually block this, as subdirectories are not allowed in calendars.
*
* @param string $name
* @return void
*/
function createDirectory($name) {
throw new DAV\Exception\MethodNotAllowed('Creating collections in calendar objects is not allowed');
}
/**
* Creates a new file
*
* The contents of the new file must be a valid ICalendar string.
*
* @param string $name
* @param resource $calendarData
* @return string|null
*/
function createFile($name, $calendarData = null) {
if (is_resource($calendarData)) {
$calendarData = stream_get_contents($calendarData);
}
return $this->caldavBackend->createCalendarObject($this->calendarInfo['id'], $name, $calendarData);
}
/**
* Deletes the calendar.
*
* @return void
*/
function delete() {
$this->caldavBackend->deleteCalendar($this->calendarInfo['id']);
}
/**
* Renames the calendar. Note that most calendars use the
* {DAV:}displayname to display a name to display a name.
*
* @param string $newName
* @return void
*/
function setName($newName) {
throw new DAV\Exception\MethodNotAllowed('Renaming calendars is not yet supported');
}
/**
* Returns the last modification date as a unix timestamp.
*
* @return void
*/
function getLastModified() {
return null;
}
/**
* Returns the owner principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
function getOwner() {
return $this->calendarInfo['principaluri'];
}
/**
* Returns a group principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
function getGroup() {
return null;
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
function getACL() {
$acl = [
[
'privilege' => '{DAV:}read',
'principal' => $this->getOwner(),
'protected' => true,
],
[
'privilege' => '{DAV:}read',
'principal' => $this->getOwner() . '/calendar-proxy-write',
'protected' => true,
],
[
'privilege' => '{DAV:}read',
'principal' => $this->getOwner() . '/calendar-proxy-read',
'protected' => true,
],
[
'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy',
'principal' => '{DAV:}authenticated',
'protected' => true,
],
];
if (empty($this->calendarInfo['{http://sabredav.org/ns}read-only'])) {
$acl[] = [
'privilege' => '{DAV:}write',
'principal' => $this->getOwner(),
'protected' => true,
];
$acl[] = [
'privilege' => '{DAV:}write',
'principal' => $this->getOwner() . '/calendar-proxy-write',
'protected' => true,
];
}
return $acl;
}
/**
* This method returns the ACL's for calendar objects in this calendar.
* The result of this method automatically gets passed to the
* calendar-object nodes in the calendar.
*
* @return array
*/
function getChildACL() {
$acl = [
[
'privilege' => '{DAV:}read',
'principal' => $this->getOwner(),
'protected' => true,
],
[
'privilege' => '{DAV:}read',
'principal' => $this->getOwner() . '/calendar-proxy-write',
'protected' => true,
],
[
'privilege' => '{DAV:}read',
'principal' => $this->getOwner() . '/calendar-proxy-read',
'protected' => true,
],
];
if (empty($this->calendarInfo['{http://sabredav.org/ns}read-only'])) {
$acl[] = [
'privilege' => '{DAV:}write',
'principal' => $this->getOwner(),
'protected' => true,
];
$acl[] = [
'privilege' => '{DAV:}write',
'principal' => $this->getOwner() . '/calendar-proxy-write',
'protected' => true,
];
}
return $acl;
}
/**
* Updates the ACL
*
* This method will receive a list of new ACE's.
*
* @param array $acl
* @return void
*/
function setACL(array $acl) {
throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
}
/**
* Returns the list of supported privileges for this node.
*
* The returned data structure is a list of nested privileges.
* See \Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
* standard structure.
*
* If null is returned from this method, the default privilege set is used,
* which is fine for most common usecases.
*
* @return array|null
*/
function getSupportedPrivilegeSet() {
$default = DAVACL\Plugin::getDefaultSupportedPrivilegeSet();
// We need to inject 'read-free-busy' in the tree, aggregated under
// {DAV:}read.
foreach ($default['aggregates'] as &$agg) {
if ($agg['privilege'] !== '{DAV:}read') continue;
$agg['aggregates'][] = [
'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy',
];
}
return $default;
}
/**
* Performs a calendar-query on the contents of this calendar.
*
* The calendar-query is defined in RFC4791 : CalDAV. Using the
* calendar-query it is possible for a client to request a specific set of
* object, based on contents of iCalendar properties, date-ranges and
* iCalendar component types (VTODO, VEVENT).
*
* This method should just return a list of (relative) urls that match this
* query.
*
* The list of filters are specified as an array. The exact array is
* documented by Sabre\CalDAV\CalendarQueryParser.
*
* @param array $filters
* @return array
*/
function calendarQuery(array $filters) {
return $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters);
}
/**
* This method returns the current sync-token for this collection.
* This can be any string.
*
* If null is returned from this function, the plugin assumes there's no
* sync information available.
*
* @return string|null
*/
function getSyncToken() {
if (
$this->caldavBackend instanceof Backend\SyncSupport &&
isset($this->calendarInfo['{DAV:}sync-token'])
) {
return $this->calendarInfo['{DAV:}sync-token'];
}
if (
$this->caldavBackend instanceof Backend\SyncSupport &&
isset($this->calendarInfo['{http://sabredav.org/ns}sync-token'])
) {
return $this->calendarInfo['{http://sabredav.org/ns}sync-token'];
}
}
/**
* The getChanges method returns all the changes that have happened, since
* the specified syncToken and the current collection.
*
* This function should return an array, such as the following:
*
* [
* 'syncToken' => 'The current synctoken',
* 'added' => [
* 'new.txt',
* ],
* 'modified' => [
* 'modified.txt',
* ],
* 'deleted' => [
* 'foo.php.bak',
* 'old.txt'
* ]
* ];
*
* The syncToken property should reflect the *current* syncToken of the
* collection, as reported getSyncToken(). This is needed here too, to
* ensure the operation is atomic.
*
* If the syncToken is specified as null, this is an initial sync, and all
* members should be reported.
*
* The modified property is an array of nodenames that have changed since
* the last token.
*
* The deleted property is an array with nodenames, that have been deleted
* from collection.
*
* The second argument is basically the 'depth' of the report. If it's 1,
* you only have to report changes that happened only directly in immediate
* descendants. If it's 2, it should also include changes from the nodes
* below the child collections. (grandchildren)
*
* The third (optional) argument allows a client to specify how many
* results should be returned at most. If the limit is not specified, it
* should be treated as infinite.
*
* If the limit (infinite or not) is higher than you're willing to return,
* you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
*
* If the syncToken is expired (due to data cleanup) or unknown, you must
* return null.
*
* The limit is 'suggestive'. You are free to ignore it.
*
* @param string $syncToken
* @param int $syncLevel
* @param int $limit
* @return array
*/
function getChanges($syncToken, $syncLevel, $limit = null) {
if (!$this->caldavBackend instanceof Backend\SyncSupport) {
return null;
}
return $this->caldavBackend->getChangesForCalendar(
$this->calendarInfo['id'],
$syncToken,
$syncLevel,
$limit
);
}
}

View file

@ -3,16 +3,24 @@
namespace Sabre\CalDAV;
use Sabre\DAV;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\MkCol;
use Sabre\DAVACL;
use Sabre\HTTP\URLUtil;
/**
* The UserCalenders class contains all calendars associated to one user
* The CalendarHome represents a node that is usually in a users'
* calendar-homeset.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* It contains all the users' calendars, and can optionally contain a
* notifications collection, calendar subscriptions, a users' inbox, and a
* users' outbox.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class UserCalendars implements DAV\IExtendedCollection, DAVACL\IACL {
class CalendarHome implements DAV\IExtendedCollection, DAVACL\IACL {
/**
* CalDAV backend
@ -34,7 +42,7 @@ class UserCalendars implements DAV\IExtendedCollection, DAVACL\IACL {
* @param Backend\BackendInterface $caldavBackend
* @param mixed $userUri
*/
public function __construct(Backend\BackendInterface $caldavBackend, $principalInfo) {
function __construct(Backend\BackendInterface $caldavBackend, $principalInfo) {
$this->caldavBackend = $caldavBackend;
$this->principalInfo = $principalInfo;
@ -46,9 +54,9 @@ class UserCalendars implements DAV\IExtendedCollection, DAVACL\IACL {
*
* @return string
*/
public function getName() {
function getName() {
list(,$name) = DAV\URLUtil::splitPath($this->principalInfo['uri']);
list(, $name) = URLUtil::splitPath($this->principalInfo['uri']);
return $name;
}
@ -59,7 +67,7 @@ class UserCalendars implements DAV\IExtendedCollection, DAVACL\IACL {
* @param string $name
* @return void
*/
public function setName($name) {
function setName($name) {
throw new DAV\Exception\Forbidden();
@ -70,7 +78,7 @@ class UserCalendars implements DAV\IExtendedCollection, DAVACL\IACL {
*
* @return void
*/
public function delete() {
function delete() {
throw new DAV\Exception\Forbidden();
@ -81,7 +89,7 @@ class UserCalendars implements DAV\IExtendedCollection, DAVACL\IACL {
*
* @return int
*/
public function getLastModified() {
function getLastModified() {
return null;
@ -96,7 +104,7 @@ class UserCalendars implements DAV\IExtendedCollection, DAVACL\IACL {
* @param resource $data
* @return void
*/
public function createFile($filename, $data=null) {
function createFile($filename, $data = null) {
throw new DAV\Exception\MethodNotAllowed('Creating new files in this collection is not supported');
@ -110,7 +118,7 @@ class UserCalendars implements DAV\IExtendedCollection, DAVACL\IACL {
* @param string $filename
* @return void
*/
public function createDirectory($filename) {
function createDirectory($filename) {
throw new DAV\Exception\MethodNotAllowed('Creating new collections in this collection is not supported');
@ -120,17 +128,46 @@ class UserCalendars implements DAV\IExtendedCollection, DAVACL\IACL {
* Returns a single calendar, by name
*
* @param string $name
* @todo needs optimizing
* @return Calendar
*/
public function getChild($name) {
function getChild($name) {
foreach($this->getChildren() as $child) {
if ($name==$child->getName())
return $child;
// Special nodes
if ($name === 'inbox' && $this->caldavBackend instanceof Backend\SchedulingSupport) {
return new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']);
}
if ($name === 'outbox' && $this->caldavBackend instanceof Backend\SchedulingSupport) {
return new Schedule\Outbox($this->principalInfo['uri']);
}
if ($name === 'notifications' && $this->caldavBackend instanceof Backend\NotificationSupport) {
return new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
}
// Calendars
foreach ($this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']) as $calendar) {
if ($calendar['uri'] === $name) {
if ($this->caldavBackend instanceof Backend\SharingSupport) {
if (isset($calendar['{http://calendarserver.org/ns/}shared-url'])) {
return new SharedCalendar($this->caldavBackend, $calendar);
} else {
return new ShareableCalendar($this->caldavBackend, $calendar);
}
} else {
return new Calendar($this->caldavBackend, $calendar);
}
}
}
if ($this->caldavBackend instanceof Backend\SubscriptionSupport) {
foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) {
if ($subscription['uri'] === $name) {
return new Subscriptions\Subscription($this->caldavBackend, $subscription);
}
}
}
throw new DAV\Exception\NotFound('Calendar with name \'' . $name . '\' could not be found');
throw new NotFound('Node with name \'' . $name . '\' could not be found');
}
@ -138,17 +175,15 @@ class UserCalendars implements DAV\IExtendedCollection, DAVACL\IACL {
* Checks if a calendar exists.
*
* @param string $name
* @todo needs optimizing
* @return bool
*/
public function childExists($name) {
function childExists($name) {
foreach($this->getChildren() as $child) {
if ($name==$child->getName())
return true;
}
try {
return !!$this->getChild($name);
} catch (NotFound $e) {
return false;
}
}
@ -157,10 +192,10 @@ class UserCalendars implements DAV\IExtendedCollection, DAVACL\IACL {
*
* @return array
*/
public function getChildren() {
function getChildren() {
$calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']);
$objs = array();
$objs = [];
foreach ($calendars as $calendar) {
if ($this->caldavBackend instanceof Backend\SharingSupport) {
if (isset($calendar['{http://calendarserver.org/ns/}shared-url'])) {
@ -172,28 +207,41 @@ class UserCalendars implements DAV\IExtendedCollection, DAVACL\IACL {
$objs[] = new Calendar($this->caldavBackend, $calendar);
}
}
if ($this->caldavBackend instanceof Backend\SchedulingSupport) {
$objs[] = new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']);
$objs[] = new Schedule\Outbox($this->principalInfo['uri']);
}
// We're adding a notifications node, if it's supported by the backend.
if ($this->caldavBackend instanceof Backend\NotificationSupport) {
$objs[] = new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
}
// If the backend supports subscriptions, we'll add those as well,
if ($this->caldavBackend instanceof Backend\SubscriptionSupport) {
foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) {
$objs[] = new Subscriptions\Subscription($this->caldavBackend, $subscription);
}
}
return $objs;
}
/**
* Creates a new calendar
* Creates a new calendar or subscription.
*
* @param string $name
* @param array $resourceType
* @param array $properties
* @param MkCol $mkCol
* @throws DAV\Exception\InvalidResourceType
* @return void
*/
public function createExtendedCollection($name, array $resourceType, array $properties) {
function createExtendedCollection($name, MkCol $mkCol) {
$isCalendar = false;
foreach($resourceType as $rt) {
$isSubscription = false;
foreach ($mkCol->getResourceType() as $rt) {
switch ($rt) {
case '{DAV:}collection' :
case '{http://calendarserver.org/ns/}shared-owner' :
@ -202,15 +250,31 @@ class UserCalendars implements DAV\IExtendedCollection, DAVACL\IACL {
case '{urn:ietf:params:xml:ns:caldav}calendar' :
$isCalendar = true;
break;
case '{http://calendarserver.org/ns/}subscribed' :
$isSubscription = true;
break;
default :
throw new DAV\Exception\InvalidResourceType('Unknown resourceType: ' . $rt);
}
}
if (!$isCalendar) {
throw new DAV\Exception\InvalidResourceType('You can only create calendars in this collection');
$properties = $mkCol->getRemainingValues();
$mkCol->setRemainingResultCode(201);
if ($isSubscription) {
if (!$this->caldavBackend instanceof Backend\SubscriptionSupport) {
throw new DAV\Exception\InvalidResourceType('This backend does not support subscriptions');
}
$this->caldavBackend->createSubscription($this->principalInfo['uri'], $name, $properties);
} elseif ($isCalendar) {
$this->caldavBackend->createCalendar($this->principalInfo['uri'], $name, $properties);
} else {
throw new DAV\Exception\InvalidResourceType('You can only create calendars and subscriptions in this collection');
}
}
/**
@ -220,7 +284,7 @@ class UserCalendars implements DAV\IExtendedCollection, DAVACL\IACL {
*
* @return string|null
*/
public function getOwner() {
function getOwner() {
return $this->principalInfo['uri'];
@ -233,7 +297,7 @@ class UserCalendars implements DAV\IExtendedCollection, DAVACL\IACL {
*
* @return string|null
*/
public function getGroup() {
function getGroup() {
return null;
@ -251,36 +315,36 @@ class UserCalendars implements DAV\IExtendedCollection, DAVACL\IACL {
*
* @return array
*/
public function getACL() {
function getACL() {
return array(
array(
return [
[
'privilege' => '{DAV:}read',
'principal' => $this->principalInfo['uri'],
'protected' => true,
),
array(
],
[
'privilege' => '{DAV:}write',
'principal' => $this->principalInfo['uri'],
'protected' => true,
),
array(
],
[
'privilege' => '{DAV:}read',
'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write',
'protected' => true,
),
array(
],
[
'privilege' => '{DAV:}write',
'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write',
'protected' => true,
),
array(
],
[
'privilege' => '{DAV:}read',
'principal' => $this->principalInfo['uri'] . '/calendar-proxy-read',
'protected' => true,
),
],
);
];
}
@ -292,7 +356,7 @@ class UserCalendars implements DAV\IExtendedCollection, DAVACL\IACL {
* @param array $acl
* @return void
*/
public function setACL(array $acl) {
function setACL(array $acl) {
throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
@ -310,7 +374,7 @@ class UserCalendars implements DAV\IExtendedCollection, DAVACL\IACL {
*
* @return array|null
*/
public function getSupportedPrivilegeSet() {
function getSupportedPrivilegeSet() {
return null;
@ -329,7 +393,7 @@ class UserCalendars implements DAV\IExtendedCollection, DAVACL\IACL {
* @param string $summary A description of the reply
* @return null|string
*/
public function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null) {
function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null) {
if (!$this->caldavBackend instanceof Backend\SharingSupport) {
throw new DAV\Exception\NotImplemented('Sharing support is not implemented by this backend.');
@ -339,4 +403,28 @@ class UserCalendars implements DAV\IExtendedCollection, DAVACL\IACL {
}
/**
* Searches through all of a users calendars and calendar objects to find
* an object with a specific UID.
*
* This method should return the path to this object, relative to the
* calendar home, so this path usually only contains two parts:
*
* calendarpath/objectpath.ics
*
* If the uid is not found, return null.
*
* This method should only consider * objects that the principal owns, so
* any calendars owned by other principals that also appear in this
* collection should be ignored.
*
* @param string $uid
* @return string|null
*/
function getCalendarObjectByUID($uid) {
return $this->caldavBackend->getCalendarObjectByUID($this->principalInfo['uri'], $uid);
}
}

View file

@ -5,7 +5,7 @@ namespace Sabre\CalDAV;
/**
* The CalendarObject represents a single VEVENT or VTODO within a Calendar.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
@ -35,17 +35,25 @@ class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\
/**
* Constructor
*
* The following properties may be passed within $objectData:
*
* * calendarid - This must refer to a calendarid from a caldavBackend
* * uri - A unique uri. Only the 'basename' must be passed.
* * calendardata (optional) - The iCalendar data
* * etag - (optional) The etag for this object, MUST be encloded with
* double-quotes.
* * size - (optional) The size of the data in bytes.
* * lastmodified - (optional) format as a unix timestamp.
* * acl - (optional) Use this to override the default ACL for the node.
*
* @param Backend\BackendInterface $caldavBackend
* @param array $calendarInfo
* @param array $objectData
*/
public function __construct(Backend\BackendInterface $caldavBackend,array $calendarInfo,array $objectData) {
function __construct(Backend\BackendInterface $caldavBackend, array $calendarInfo, array $objectData) {
$this->caldavBackend = $caldavBackend;
if (!isset($objectData['calendarid'])) {
throw new \InvalidArgumentException('The objectData argument must contain a \'calendarid\' property');
}
if (!isset($objectData['uri'])) {
throw new \InvalidArgumentException('The objectData argument must contain an \'uri\' property');
}
@ -60,7 +68,7 @@ class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\
*
* @return string
*/
public function getName() {
function getName() {
return $this->objectData['uri'];
@ -71,12 +79,12 @@ class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\
*
* @return string
*/
public function get() {
function get() {
// Pre-populating the 'calendardata' is optional, if we don't have it
// already we fetch it from the backend.
if (!isset($this->objectData['calendardata'])) {
$this->objectData = $this->caldavBackend->getCalendarObject($this->objectData['calendarid'], $this->objectData['uri']);
$this->objectData = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $this->objectData['uri']);
}
return $this->objectData['calendardata'];
@ -88,7 +96,7 @@ class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\
* @param string|resource $calendarData
* @return string
*/
public function put($calendarData) {
function put($calendarData) {
if (is_resource($calendarData)) {
$calendarData = stream_get_contents($calendarData);
@ -106,7 +114,7 @@ class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\
*
* @return void
*/
public function delete() {
function delete() {
$this->caldavBackend->deleteCalendarObject($this->calendarInfo['id'], $this->objectData['uri']);
@ -117,9 +125,13 @@ class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\
*
* @return string
*/
public function getContentType() {
function getContentType() {
return 'text/calendar; charset=utf-8';
$mime = 'text/calendar; charset=utf-8';
if (isset($this->objectData['component']) && $this->objectData['component']) {
$mime .= '; component=' . $this->objectData['component'];
}
return $mime;
}
@ -130,7 +142,7 @@ class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\
*
* @return string
*/
public function getETag() {
function getETag() {
if (isset($this->objectData['etag'])) {
return $this->objectData['etag'];
@ -145,7 +157,7 @@ class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\
*
* @return int
*/
public function getLastModified() {
function getLastModified() {
return $this->objectData['lastmodified'];
@ -156,7 +168,7 @@ class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\
*
* @return int
*/
public function getSize() {
function getSize() {
if (array_key_exists('size', $this->objectData)) {
return $this->objectData['size'];
@ -173,7 +185,7 @@ class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\
*
* @return string|null
*/
public function getOwner() {
function getOwner() {
return $this->calendarInfo['principaluri'];
@ -186,7 +198,7 @@ class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\
*
* @return string|null
*/
public function getGroup() {
function getGroup() {
return null;
@ -204,7 +216,7 @@ class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\
*
* @return array
*/
public function getACL() {
function getACL() {
// An alternative acl may be specified in the object data.
if (isset($this->objectData['acl'])) {
@ -212,34 +224,34 @@ class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\
}
// The default ACL
return array(
array(
return [
[
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
),
array(
],
[
'privilege' => '{DAV:}write',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
),
array(
],
[
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
'protected' => true,
),
array(
],
[
'privilege' => '{DAV:}write',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
'protected' => true,
),
array(
],
[
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read',
'protected' => true,
),
],
);
];
}
@ -251,7 +263,7 @@ class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\
* @param array $acl
* @return void
*/
public function setACL(array $acl) {
function setACL(array $acl) {
throw new \Sabre\DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
@ -269,11 +281,10 @@ class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\
*
* @return array|null
*/
public function getSupportedPrivilegeSet() {
function getSupportedPrivilegeSet() {
return null;
}
}

View file

@ -14,7 +14,7 @@ use DateTime;
* This is used to determine which icalendar objects should be returned for a
* calendar-query REPORT request.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
@ -29,7 +29,7 @@ class CalendarQueryValidator {
* @param array $filters
* @return bool
*/
public function validate(VObject\Component $vObject,array $filters) {
function validate(VObject\Component\VCalendar $vObject, array $filters) {
// The top level object is always a component filter.
// We'll parse it manually, as it's pretty simple.
@ -59,7 +59,7 @@ class CalendarQueryValidator {
foreach ($filters as $filter) {
$isDefined = isset($parent->$filter['name']);
$isDefined = isset($parent->{$filter['name']});
if ($filter['is-not-defined']) {
@ -75,7 +75,7 @@ class CalendarQueryValidator {
}
if ($filter['time-range']) {
foreach($parent->$filter['name'] as $subComponent) {
foreach ($parent->{$filter['name']} as $subComponent) {
if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
continue 2;
}
@ -89,7 +89,7 @@ class CalendarQueryValidator {
// If there are sub-filters, we need to find at least one component
// for which the subfilters hold true.
foreach($parent->$filter['name'] as $subComponent) {
foreach ($parent->{$filter['name']} as $subComponent) {
if (
$this->validateCompFilters($subComponent, $filter['comp-filters']) &&
@ -128,7 +128,7 @@ class CalendarQueryValidator {
foreach ($filters as $filter) {
$isDefined = isset($parent->$filter['name']);
$isDefined = isset($parent->{$filter['name']});
if ($filter['is-not-defined']) {
@ -144,7 +144,7 @@ class CalendarQueryValidator {
}
if ($filter['time-range']) {
foreach($parent->$filter['name'] as $subComponent) {
foreach ($parent->{$filter['name']} as $subComponent) {
if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
continue 2;
}
@ -158,7 +158,7 @@ class CalendarQueryValidator {
// If there are sub-filters, we need to find at least one property
// for which the subfilters hold true.
foreach($parent->$filter['name'] as $subComponent) {
foreach ($parent->{$filter['name']} as $subComponent) {
if (
$this->validateParamFilters($subComponent, $filter['param-filters']) &&
@ -217,34 +217,17 @@ class CalendarQueryValidator {
continue;
}
if (version_compare(VObject\Version::VERSION, '3.0.0beta1', '>=')) {
// If there are sub-filters, we need to find at least one parameter
// for which the subfilters hold true.
foreach($parent[$filter['name']]->getParts() as $subParam) {
foreach ($parent[$filter['name']]->getParts() as $paramPart) {
if($this->validateTextMatch($subParam,$filter['text-match'])) {
if ($this->validateTextMatch($paramPart, $filter['text-match'])) {
// We had a match, so this param-filter succeeds
continue 2;
}
}
} else {
// If there are sub-filters, we need to find at least one parameter
// for which the subfilters hold true.
foreach($parent[$filter['name']] as $subParam) {
if($this->validateTextMatch($subParam,$filter['text-match'])) {
// We had a match, so this param-filter succeeds
continue 2;
}
}
}
// If we got here it means there was a text-match filter and there
// were no matches. This means the filter needs to return false.
return false;
@ -270,7 +253,7 @@ class CalendarQueryValidator {
protected function validateTextMatch($check, array $textMatch) {
if ($check instanceof VObject\Node) {
$check = (string)$check;
$check = $check->getValue();
}
$isMatching = \Sabre\DAV\StringUtil::textMatch($check, $textMatch['value'], $textMatch['collation']);
@ -318,7 +301,7 @@ class CalendarQueryValidator {
if ($component->parent->name === 'VEVENT' && $component->parent->RRULE) {
// Fire up the iterator!
$it = new VObject\RecurrenceIterator($component->parent->parent, (string)$component->parent->UID);
$it = new VObject\Recur\EventIterator($component->parent->parent, (string)$component->parent->UID);
while ($it->valid()) {
$expandedEvent = $it->getEventObject();

View file

@ -10,11 +10,14 @@ use Sabre\DAVACL\PrincipalBackend;
* This object is responsible for generating a list of calendar-homes for each
* user.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* This is the top-most node for the calendars tree. In most servers this class
* represents the "/calendars" path.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class CalendarRootNode extends \Sabre\DAVACL\AbstractPrincipalCollection {
class CalendarRoot extends \Sabre\DAVACL\AbstractPrincipalCollection {
/**
* CalDAV backend
@ -37,7 +40,7 @@ class CalendarRootNode extends \Sabre\DAVACL\AbstractPrincipalCollection {
* @param Backend\BackendInterface $caldavBackend
* @param string $principalPrefix
*/
public function __construct(PrincipalBackend\BackendInterface $principalBackend,Backend\BackendInterface $caldavBackend, $principalPrefix = 'principals') {
function __construct(PrincipalBackend\BackendInterface $principalBackend, Backend\BackendInterface $caldavBackend, $principalPrefix = 'principals') {
parent::__construct($principalBackend, $principalPrefix);
$this->caldavBackend = $caldavBackend;
@ -52,7 +55,7 @@ class CalendarRootNode extends \Sabre\DAVACL\AbstractPrincipalCollection {
*
* @return string
*/
public function getName() {
function getName() {
return Plugin::CALENDAR_ROOT;
@ -68,9 +71,9 @@ class CalendarRootNode extends \Sabre\DAVACL\AbstractPrincipalCollection {
* @param array $principal
* @return \Sabre\DAV\INode
*/
public function getChildForPrincipal(array $principal) {
function getChildForPrincipal(array $principal) {
return new UserCalendars($this->caldavBackend, $principal);
return new CalendarHome($this->caldavBackend, $principal);
}

View file

@ -8,7 +8,7 @@ use Sabre\CalDAV;
/**
* InvalidComponentType
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
@ -23,7 +23,7 @@ class InvalidComponentType extends DAV\Exception\Forbidden {
* @param \DOMElement $errorNode
* @return void
*/
public function serialize(DAV\Server $server, \DOMElement $errorNode) {
function serialize(DAV\Server $server, \DOMElement $errorNode) {
$doc = $errorNode->ownerDocument;

View file

@ -0,0 +1,366 @@
<?php
namespace Sabre\CalDAV;
use DateTimeZone;
use Sabre\DAV;
use Sabre\VObject;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\DAV\Exception\BadRequest;
use DateTime;
/**
* ICS Exporter
*
* This plugin adds the ability to export entire calendars as .ics files.
* This is useful for clients that don't support CalDAV yet. They often do
* support ics files.
*
* To use this, point a http client to a caldav calendar, and add ?expand to
* the url.
*
* Further options that can be added to the url:
* start=123456789 - Only return events after the given unix timestamp
* end=123245679 - Only return events from before the given unix timestamp
* expand=1 - Strip timezone information and expand recurring events.
* If you'd like to expand, you _must_ also specify start
* and end.
*
* By default this plugin returns data in the text/calendar format (iCalendar
* 2.0). If you'd like to receive jCal data instead, you can use an Accept
* header:
*
* Accept: application/calendar+json
*
* Alternatively, you can also specify this in the url using
* accept=application/calendar+json, or accept=jcal for short. If the url
* parameter and Accept header is specified, the url parameter wins.
*
* Note that specifying a start or end data implies that only events will be
* returned. VTODO and VJOURNAL will be stripped.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class ICSExportPlugin extends DAV\ServerPlugin {
/**
* Reference to Server class
*
* @var \Sabre\DAV\Server
*/
protected $server;
/**
* Initializes the plugin and registers event handlers
*
* @param \Sabre\DAV\Server $server
* @return void
*/
function initialize(DAV\Server $server) {
$this->server = $server;
$server->on('method:GET', [$this, 'httpGet'], 90);
$server->on('browserButtonActions', function($path, $node, &$actions) {
if ($node instanceof ICalendar) {
$actions .= '<a href="' . htmlspecialchars($path, ENT_QUOTES, 'UTF-8') . '?export"><span class="oi" data-glyph="calendar"></span></a>';
}
});
}
/**
* Intercepts GET requests on calendar urls ending with ?export.
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @return bool
*/
function httpGet(RequestInterface $request, ResponseInterface $response) {
$queryParams = $request->getQueryParameters();
if (!array_key_exists('export', $queryParams)) return;
$path = $request->getPath();
$node = $this->server->getProperties($path, [
'{DAV:}resourcetype',
'{DAV:}displayname',
'{http://sabredav.org/ns}sync-token',
'{DAV:}sync-token',
'{http://apple.com/ns/ical/}calendar-color',
]);
if (!isset($node['{DAV:}resourcetype']) || !$node['{DAV:}resourcetype']->is('{' . Plugin::NS_CALDAV . '}calendar')) {
return;
}
// Marking the transactionType, for logging purposes.
$this->server->transactionType = 'get-calendar-export';
$properties = $node;
$start = null;
$end = null;
$expand = false;
$componentType = false;
if (isset($queryParams['start'])) {
if (!ctype_digit($queryParams['start'])) {
throw new BadRequest('The start= parameter must contain a unix timestamp');
}
$start = DateTime::createFromFormat('U', $queryParams['start']);
}
if (isset($queryParams['end'])) {
if (!ctype_digit($queryParams['end'])) {
throw new BadRequest('The end= parameter must contain a unix timestamp');
}
$end = DateTime::createFromFormat('U', $queryParams['end']);
}
if (isset($queryParams['expand']) && !!$queryParams['expand']) {
if (!$start || !$end) {
throw new BadRequest('If you\'d like to expand recurrences, you must specify both a start= and end= parameter.');
}
$expand = true;
$componentType = 'VEVENT';
}
if (isset($queryParams['componentType'])) {
if (!in_array($queryParams['componentType'], ['VEVENT', 'VTODO', 'VJOURNAL'])) {
throw new BadRequest('You are not allowed to search for components of type: ' . $queryParams['componentType'] . ' here');
}
$componentType = $queryParams['componentType'];
}
$format = \Sabre\HTTP\Util::Negotiate(
$request->getHeader('Accept'),
[
'text/calendar',
'application/calendar+json',
]
);
if (isset($queryParams['accept'])) {
if ($queryParams['accept'] === 'application/calendar+json' || $queryParams['accept'] === 'jcal') {
$format = 'application/calendar+json';
}
}
if (!$format) {
$format = 'text/calendar';
}
$this->generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, $response);
// Returning false to break the event chain
return false;
}
/**
* This method is responsible for generating the actual, full response.
*
* @param string $path
* @param DateTime|null $start
* @param DateTime|null $end
* @param bool $expand
* @param string $componentType
* @param string $format
* @param array $properties
* @param ResponseInterface $response
*/
protected function generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, ResponseInterface $response) {
$calDataProp = '{' . Plugin::NS_CALDAV . '}calendar-data';
$blobs = [];
if ($start || $end || $componentType) {
// If there was a start or end filter, we need to enlist
// calendarQuery for speed.
$calendarNode = $this->server->tree->getNodeForPath($path);
$queryResult = $calendarNode->calendarQuery([
'name' => 'VCALENDAR',
'comp-filters' => [
[
'name' => $componentType,
'comp-filters' => [],
'prop-filters' => [],
'is-not-defined' => false,
'time-range' => [
'start' => $start,
'end' => $end,
],
],
],
'prop-filters' => [],
'is-not-defined' => false,
'time-range' => null,
]);
// queryResult is just a list of base urls. We need to prefix the
// calendar path.
$queryResult = array_map(
function($item) use ($path) {
return $path . '/' . $item;
},
$queryResult
);
$nodes = $this->server->getPropertiesForMultiplePaths($queryResult, [$calDataProp]);
unset($queryResult);
} else {
$nodes = $this->server->getPropertiesForPath($path, [$calDataProp], 1);
}
// Flattening the arrays
foreach ($nodes as $node) {
if (isset($node[200][$calDataProp])) {
$blobs[$node['href']] = $node[200][$calDataProp];
}
}
unset($nodes);
$mergedCalendar = $this->mergeObjects(
$properties,
$blobs
);
if ($expand) {
$calendarTimeZone = null;
// We're expanding, and for that we need to figure out the
// calendar's timezone.
$tzProp = '{' . Plugin::NS_CALDAV . '}calendar-timezone';
$tzResult = $this->server->getProperties($path, [$tzProp]);
if (isset($tzResult[$tzProp])) {
// This property contains a VCALENDAR with a single
// VTIMEZONE.
$vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
$calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
// Destroy circular references to PHP will GC the object.
$vtimezoneObj->destroy();
unset($vtimezoneObj);
} else {
// Defaulting to UTC.
$calendarTimeZone = new DateTimeZone('UTC');
}
$mergedCalendar = $mergedCalendar->expand($start, $end, $calendarTimeZone);
}
$response->setHeader('Content-Type', $format);
switch ($format) {
case 'text/calendar' :
$mergedCalendar = $mergedCalendar->serialize();
break;
case 'application/calendar+json' :
$mergedCalendar = json_encode($mergedCalendar->jsonSerialize());
break;
}
$response->setStatus(200);
$response->setBody($mergedCalendar);
}
/**
* Merges all calendar objects, and builds one big iCalendar blob.
*
* @param array $properties Some CalDAV properties
* @param array $inputObjects
* @return VObject\Component\VCalendar
*/
function mergeObjects(array $properties, array $inputObjects) {
$calendar = new VObject\Component\VCalendar();
$calendar->version = '2.0';
if (DAV\Server::$exposeVersion) {
$calendar->prodid = '-//SabreDAV//SabreDAV ' . DAV\Version::VERSION . '//EN';
} else {
$calendar->prodid = '-//SabreDAV//SabreDAV//EN';
}
if (isset($properties['{DAV:}displayname'])) {
$calendar->{'X-WR-CALNAME'} = $properties['{DAV:}displayname'];
}
if (isset($properties['{http://apple.com/ns/ical/}calendar-color'])) {
$calendar->{'X-APPLE-CALENDAR-COLOR'} = $properties['{http://apple.com/ns/ical/}calendar-color'];
}
$collectedTimezones = [];
$timezones = [];
$objects = [];
foreach ($inputObjects as $href => $inputObject) {
$nodeComp = VObject\Reader::read($inputObject);
foreach ($nodeComp->children() as $child) {
switch ($child->name) {
case 'VEVENT' :
case 'VTODO' :
case 'VJOURNAL' :
$objects[] = clone $child;
break;
// VTIMEZONE is special, because we need to filter out the duplicates
case 'VTIMEZONE' :
// Naively just checking tzid.
if (in_array((string)$child->TZID, $collectedTimezones)) continue;
$timezones[] = clone $child;
$collectedTimezones[] = $child->TZID;
break;
}
}
// Destroy circular references to PHP will GC the object.
$nodeComp->destroy();
unset($nodeComp);
}
foreach ($timezones as $tz) $calendar->add($tz);
foreach ($objects as $obj) $calendar->add($obj);
return $calendar;
}
/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins
* using \Sabre\DAV\Server::getPlugin
*
* @return string
*/
function getPluginName() {
return 'ics-export';
}
/**
* Returns a bunch of meta-data about the plugin.
*
* Providing this information is optional, and is mainly displayed by the
* Browser plugin.
*
* The description key in the returned array may contain html and will not
* be sanitized.
*
* @return array
*/
function getPluginInfo() {
return [
'name' => $this->getPluginName(),
'description' => 'Adds the ability to export CalDAV calendars as a single iCalendar file.',
'link' => 'http://sabre.io/dav/ics-export-plugin/',
];
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace Sabre\CalDAV;
use Sabre\DAVACL;
/**
* Calendar interface
*
* Implement this interface to allow a node to be recognized as an calendar.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface ICalendar extends ICalendarObjectContainer, DAVACL\IACL {
}

View file

@ -1,6 +1,7 @@
<?php
namespace Sabre\CalDAV;
use Sabre\DAV;
/**
@ -11,11 +12,10 @@ use Sabre\DAV;
*
* Calendar objects are resources such as Events, Todo's or Journals.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface ICalendarObject extends DAV\IFile {
}

View file

@ -1,18 +1,21 @@
<?php
namespace Sabre\CalDAV;
use Sabre\DAV;
/**
* Calendar interface
* This interface represents a node that may contain calendar objects.
*
* Implement this interface to allow a node to be recognized as an calendar.
* This is the shared parent for both the Inbox collection and calendars
* resources.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* In most cases you will likely want to look at ICalendar instead of this
* interface.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface ICalendar extends DAV\ICollection {
interface ICalendarObjectContainer extends \Sabre\DAV\ICollection {
/**
* Performs a calendar-query on the contents of this calendar.
@ -31,6 +34,6 @@ interface ICalendar extends DAV\ICollection {
* @param array $filters
* @return array
*/
public function calendarQuery(array $filters);
function calendarQuery(array $filters);
}

View file

@ -5,7 +5,7 @@ namespace Sabre\CalDAV;
/**
* This interface represents a Calendar that can be shared with other users.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/

View file

@ -5,7 +5,7 @@ namespace Sabre\CalDAV;
/**
* This interface represents a Calendar that is shared by a different user.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/

View file

@ -16,7 +16,7 @@ use Sabre\DAVACL;
* This collection should only return Sabre\CalDAV\Notifications\INode nodes as
* its children.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
@ -42,7 +42,7 @@ class Collection extends DAV\Collection implements ICollection, DAVACL\IACL {
* @param CalDAV\Backend\NotificationSupport $caldavBackend
* @param string $principalUri
*/
public function __construct(CalDAV\Backend\NotificationSupport $caldavBackend, $principalUri) {
function __construct(CalDAV\Backend\NotificationSupport $caldavBackend, $principalUri) {
$this->caldavBackend = $caldavBackend;
$this->principalUri = $principalUri;
@ -54,9 +54,9 @@ class Collection extends DAV\Collection implements ICollection, DAVACL\IACL {
*
* @return array
*/
public function getChildren() {
function getChildren() {
$children = array();
$children = [];
$notifications = $this->caldavBackend->getNotificationsForPrincipal($this->principalUri);
foreach ($notifications as $notification) {
@ -77,7 +77,7 @@ class Collection extends DAV\Collection implements ICollection, DAVACL\IACL {
*
* @return string
*/
public function getName() {
function getName() {
return 'notifications';
@ -90,7 +90,7 @@ class Collection extends DAV\Collection implements ICollection, DAVACL\IACL {
*
* @return string|null
*/
public function getOwner() {
function getOwner() {
return $this->principalUri;
@ -103,7 +103,7 @@ class Collection extends DAV\Collection implements ICollection, DAVACL\IACL {
*
* @return string|null
*/
public function getGroup() {
function getGroup() {
return null;
@ -121,20 +121,20 @@ class Collection extends DAV\Collection implements ICollection, DAVACL\IACL {
*
* @return array
*/
public function getACL() {
function getACL() {
return array(
array(
return [
[
'principal' => $this->getOwner(),
'privilege' => '{DAV:}read',
'protected' => true,
),
array(
],
[
'principal' => $this->getOwner(),
'privilege' => '{DAV:}write',
'protected' => true,
)
);
]
];
}
@ -146,7 +146,7 @@ class Collection extends DAV\Collection implements ICollection, DAVACL\IACL {
* @param array $acl
* @return void
*/
public function setACL(array $acl) {
function setACL(array $acl) {
throw new DAV\Exception\NotImplemented('Updating ACLs is not implemented here');
@ -164,7 +164,7 @@ class Collection extends DAV\Collection implements ICollection, DAVACL\IACL {
*
* @return array|null
*/
public function getSupportedPrivilegeSet() {
function getSupportedPrivilegeSet() {
return null;

View file

@ -14,11 +14,10 @@ use Sabre\DAV;
* This collection should only return Sabre\CalDAV\Notifications\INode nodes as
* its children.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface ICollection extends DAV\ICollection {
}

View file

@ -12,7 +12,7 @@ namespace Sabre\CalDAV\Notifications;
* For a complete example, check out the Notification class, which contains
* some helper functions.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/

View file

@ -4,6 +4,7 @@ namespace Sabre\CalDAV\Notifications;
use Sabre\DAV;
use Sabre\CalDAV;
use Sabre\CalDAV\Xml\Notification\NotificationInterface;
use Sabre\DAVACL;
/**
@ -12,8 +13,8 @@ use Sabre\DAVACL;
* The signature is mostly identical to that of Sabre\DAV\IFile, but the get() method
* MUST return an xml document that matches the requirements of the
* 'caldav-notifications.txt' spec.
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
@ -45,9 +46,9 @@ class Node extends DAV\File implements INode, DAVACL\IACL {
*
* @param CalDAV\Backend\NotificationSupport $caldavBackend
* @param string $principalUri
* @param CalDAV\Notifications\INotificationType $notification
* @param NotificationInterface $notification
*/
public function __construct(CalDAV\Backend\NotificationSupport $caldavBackend, $principalUri, INotificationType $notification) {
function __construct(CalDAV\Backend\NotificationSupport $caldavBackend, $principalUri, NotificationInterface $notification) {
$this->caldavBackend = $caldavBackend;
$this->principalUri = $principalUri;
@ -60,7 +61,7 @@ class Node extends DAV\File implements INode, DAVACL\IACL {
*
* @return id
*/
public function getName() {
function getName() {
return $this->notification->getId() . '.xml';
@ -73,7 +74,7 @@ class Node extends DAV\File implements INode, DAVACL\IACL {
*
* @return string
*/
public function getETag() {
function getETag() {
return $this->notification->getETag();
@ -85,7 +86,7 @@ class Node extends DAV\File implements INode, DAVACL\IACL {
*
* @return INotificationType
*/
public function getNotificationType() {
function getNotificationType() {
return $this->notification;
@ -96,7 +97,7 @@ class Node extends DAV\File implements INode, DAVACL\IACL {
*
* @return void
*/
public function delete() {
function delete() {
$this->caldavBackend->deleteNotification($this->getOwner(), $this->notification);
@ -109,7 +110,7 @@ class Node extends DAV\File implements INode, DAVACL\IACL {
*
* @return string|null
*/
public function getOwner() {
function getOwner() {
return $this->principalUri;
@ -122,7 +123,7 @@ class Node extends DAV\File implements INode, DAVACL\IACL {
*
* @return string|null
*/
public function getGroup() {
function getGroup() {
return null;
@ -140,20 +141,20 @@ class Node extends DAV\File implements INode, DAVACL\IACL {
*
* @return array
*/
public function getACL() {
function getACL() {
return array(
array(
return [
[
'principal' => $this->getOwner(),
'privilege' => '{DAV:}read',
'protected' => true,
),
array(
],
[
'principal' => $this->getOwner(),
'privilege' => '{DAV:}write',
'protected' => true,
)
);
]
];
}
@ -165,7 +166,7 @@ class Node extends DAV\File implements INode, DAVACL\IACL {
* @param array $acl
* @return void
*/
public function setACL(array $acl) {
function setACL(array $acl) {
throw new DAV\Exception\NotImplemented('Updating ACLs is not implemented here');
@ -183,7 +184,7 @@ class Node extends DAV\File implements INode, DAVACL\IACL {
*
* @return array|null
*/
public function getSupportedPrivilegeSet() {
function getSupportedPrivilegeSet() {
return null;

View file

@ -0,0 +1,180 @@
<?php
namespace Sabre\CalDAV\Notifications;
use Sabre\DAV;
use Sabre\DAV\PropFind;
use Sabre\DAV\INode as BaseINode;
use Sabre\DAV\ServerPlugin;
use Sabre\DAV\Server;
use Sabre\DAVACL;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
/**
* Notifications plugin
*
* This plugin implements several features required by the caldav-notification
* draft specification.
*
* Before version 2.1.0 this functionality was part of Sabre\CalDAV\Plugin but
* this has since been split up.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Plugin extends ServerPlugin {
/**
* This is the namespace for the proprietary calendarserver extensions
*/
const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
/**
* Reference to the main server object.
*
* @var Server
*/
protected $server;
/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins
* using \Sabre\DAV\Server::getPlugin
*
* @return string
*/
function getPluginName() {
return 'notifications';
}
/**
* This initializes the plugin.
*
* This function is called by Sabre\DAV\Server, after
* addPlugin is called.
*
* This method should set up the required event subscriptions.
*
* @param Server $server
* @return void
*/
function initialize(Server $server) {
$this->server = $server;
$server->on('method:GET', [$this, 'httpGet'], 90);
$server->on('propFind', [$this, 'propFind']);
$server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs';
$server->resourceTypeMapping['\\Sabre\\CalDAV\\Notifications\\ICollection'] = '{' . self::NS_CALENDARSERVER . '}notification';
array_push($server->protectedProperties,
'{' . self::NS_CALENDARSERVER . '}notification-URL',
'{' . self::NS_CALENDARSERVER . '}notificationtype'
);
}
/**
* PropFind
*
* @param PropFind $propFind
* @param BaseINode $node
* @return void
*/
function propFind(PropFind $propFind, BaseINode $node) {
$caldavPlugin = $this->server->getPlugin('caldav');
if ($node instanceof DAVACL\IPrincipal) {
$principalUrl = $node->getPrincipalUrl();
// notification-URL property
$propFind->handle('{' . self::NS_CALENDARSERVER . '}notification-URL', function() use ($principalUrl, $caldavPlugin) {
$notificationPath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl) . '/notifications/';
return new DAV\Xml\Property\Href($notificationPath);
});
}
if ($node instanceof INode) {
$propFind->handle(
'{' . self::NS_CALENDARSERVER . '}notificationtype',
[$node, 'getNotificationType']
);
}
}
/**
* This event is triggered before the usual GET request handler.
*
* We use this to intercept GET calls to notification nodes, and return the
* proper response.
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @return void
*/
function httpGet(RequestInterface $request, ResponseInterface $response) {
$path = $request->getPath();
try {
$node = $this->server->tree->getNodeForPath($path);
} catch (DAV\Exception\NotFound $e) {
return;
}
if (!$node instanceof INode)
return;
$writer = $this->server->xml->getWriter();
$writer->contextUri = $this->server->getBaseUri();
$writer->openMemory();
$writer->startDocument('1.0', 'UTF-8');
$writer->startElement('{http://calendarserver.org/ns/}notification');
$node->getNotificationType()->xmlSerializeFull($writer);
$writer->endElement();
$response->setHeader('Content-Type', 'application/xml');
$response->setHeader('ETag', $node->getETag());
$response->setStatus(200);
$response->setBody($writer->outputMemory());
// Return false to break the event chain.
return false;
}
/**
* Returns a bunch of meta-data about the plugin.
*
* Providing this information is optional, and is mainly displayed by the
* Browser plugin.
*
* The description key in the returned array may contain html and will not
* be sanitized.
*
* @return array
*/
function getPluginInfo() {
return [
'name' => $this->getPluginName(),
'description' => 'Adds support for caldav-notifications, which is required to enable caldav-sharing.',
'link' => 'http://sabre.io/dav/caldav-sharing/',
];
}
}

1025
vendor/sabre/dav/lib/CalDAV/Plugin.php vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,7 @@
<?php
namespace Sabre\CalDAV\Principal;
use Sabre\DAVACL;
/**
@ -11,11 +12,11 @@ use Sabre\DAVACL;
* calendar-proxy-write sub-principals, as defined by the caldav-proxy
* specification.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Collection extends DAVACL\AbstractPrincipalCollection {
class Collection extends DAVACL\PrincipalCollection {
/**
* Returns a child object based on principal information
@ -23,7 +24,7 @@ class Collection extends DAVACL\AbstractPrincipalCollection {
* @param array $principalInfo
* @return User
*/
public function getChildForPrincipal(array $principalInfo) {
function getChildForPrincipal(array $principalInfo) {
return new User($this->principalBackend, $principalInfo);

View file

@ -10,7 +10,7 @@ use Sabre\DAVACL;
* Any principal node implementing this interface will be picked up as a 'proxy
* principal group'.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/

View file

@ -10,7 +10,7 @@ use Sabre\DAVACL;
* Any principal node implementing this interface will be picked up as a 'proxy
* principal group'.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/

View file

@ -1,6 +1,7 @@
<?php
namespace Sabre\CalDAV\Principal;
use Sabre\DAVACL;
use Sabre\DAV;
@ -11,7 +12,7 @@ use Sabre\DAV;
* This is needed to implement 'Calendar delegation' support. This class is
* instantiated by User.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
@ -39,7 +40,7 @@ class ProxyRead implements IProxyRead {
* @param DAVACL\PrincipalBackend\BackendInterface $principalBackend
* @param array $principalInfo
*/
public function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, array $principalInfo) {
function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, array $principalInfo) {
$this->principalInfo = $principalInfo;
$this->principalBackend = $principalBackend;
@ -51,7 +52,7 @@ class ProxyRead implements IProxyRead {
*
* @return string
*/
public function getName() {
function getName() {
return 'calendar-proxy-read';
@ -62,7 +63,7 @@ class ProxyRead implements IProxyRead {
*
* @return null
*/
public function getLastModified() {
function getLastModified() {
return null;
@ -74,7 +75,7 @@ class ProxyRead implements IProxyRead {
* @throws DAV\Exception\Forbidden
* @return void
*/
public function delete() {
function delete() {
throw new DAV\Exception\Forbidden('Permission denied to delete node');
@ -87,7 +88,7 @@ class ProxyRead implements IProxyRead {
* @param string $name The new name
* @return void
*/
public function setName($name) {
function setName($name) {
throw new DAV\Exception\Forbidden('Permission denied to rename file');
@ -101,9 +102,9 @@ class ProxyRead implements IProxyRead {
*
* @return array
*/
public function getAlternateUriSet() {
function getAlternateUriSet() {
return array();
return [];
}
@ -112,7 +113,7 @@ class ProxyRead implements IProxyRead {
*
* @return string
*/
public function getPrincipalUrl() {
function getPrincipalUrl() {
return $this->principalInfo['uri'] . '/' . $this->getName();
@ -126,7 +127,7 @@ class ProxyRead implements IProxyRead {
*
* @return array
*/
public function getGroupMemberSet() {
function getGroupMemberSet() {
return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl());
@ -140,7 +141,7 @@ class ProxyRead implements IProxyRead {
*
* @return array
*/
public function getGroupMembership() {
function getGroupMembership() {
return $this->principalBackend->getGroupMembership($this->getPrincipalUrl());
@ -157,7 +158,7 @@ class ProxyRead implements IProxyRead {
* @param array $principals
* @return void
*/
public function setGroupMemberSet(array $principals) {
function setGroupMemberSet(array $principals) {
$this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals);
@ -171,7 +172,7 @@ class ProxyRead implements IProxyRead {
*
* @return string
*/
public function getDisplayName() {
function getDisplayName() {
return $this->getName();

View file

@ -1,6 +1,7 @@
<?php
namespace Sabre\CalDAV\Principal;
use Sabre\DAVACL;
use Sabre\DAV;
@ -11,7 +12,7 @@ use Sabre\DAV;
* This is needed to implement 'Calendar delegation' support. This class is
* instantiated by User.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
@ -39,7 +40,7 @@ class ProxyWrite implements IProxyWrite {
* @param DAVACL\PrincipalBackend\BackendInterface $principalBackend
* @param array $principalInfo
*/
public function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, array $principalInfo) {
function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, array $principalInfo) {
$this->principalInfo = $principalInfo;
$this->principalBackend = $principalBackend;
@ -51,7 +52,7 @@ class ProxyWrite implements IProxyWrite {
*
* @return string
*/
public function getName() {
function getName() {
return 'calendar-proxy-write';
@ -62,7 +63,7 @@ class ProxyWrite implements IProxyWrite {
*
* @return null
*/
public function getLastModified() {
function getLastModified() {
return null;
@ -74,7 +75,7 @@ class ProxyWrite implements IProxyWrite {
* @throws DAV\Exception\Forbidden
* @return void
*/
public function delete() {
function delete() {
throw new DAV\Exception\Forbidden('Permission denied to delete node');
@ -87,7 +88,7 @@ class ProxyWrite implements IProxyWrite {
* @param string $name The new name
* @return void
*/
public function setName($name) {
function setName($name) {
throw new DAV\Exception\Forbidden('Permission denied to rename file');
@ -101,9 +102,9 @@ class ProxyWrite implements IProxyWrite {
*
* @return array
*/
public function getAlternateUriSet() {
function getAlternateUriSet() {
return array();
return [];
}
@ -112,7 +113,7 @@ class ProxyWrite implements IProxyWrite {
*
* @return string
*/
public function getPrincipalUrl() {
function getPrincipalUrl() {
return $this->principalInfo['uri'] . '/' . $this->getName();
@ -126,7 +127,7 @@ class ProxyWrite implements IProxyWrite {
*
* @return array
*/
public function getGroupMemberSet() {
function getGroupMemberSet() {
return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl());
@ -140,7 +141,7 @@ class ProxyWrite implements IProxyWrite {
*
* @return array
*/
public function getGroupMembership() {
function getGroupMembership() {
return $this->principalBackend->getGroupMembership($this->getPrincipalUrl());
@ -157,7 +158,7 @@ class ProxyWrite implements IProxyWrite {
* @param array $principals
* @return void
*/
public function setGroupMemberSet(array $principals) {
function setGroupMemberSet(array $principals) {
$this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals);
@ -171,7 +172,7 @@ class ProxyWrite implements IProxyWrite {
*
* @return string
*/
public function getDisplayName() {
function getDisplayName() {
return $this->getName();

View file

@ -1,6 +1,7 @@
<?php
namespace Sabre\CalDAV\Principal;
use Sabre\DAV;
use Sabre\DAVACL;
@ -11,7 +12,7 @@ use Sabre\DAVACL;
* collection and returns the caldav-proxy-read and caldav-proxy-write child
* principals.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
@ -25,7 +26,7 @@ class User extends DAVACL\Principal implements DAV\ICollection {
* @throws DAV\Exception\Forbidden
* @return void
*/
public function createFile($name, $data = null) {
function createFile($name, $data = null) {
throw new DAV\Exception\Forbidden('Permission denied to create file (filename ' . $name . ')');
@ -38,7 +39,7 @@ class User extends DAVACL\Principal implements DAV\ICollection {
* @throws DAV\Exception\Forbidden
* @return void
*/
public function createDirectory($name) {
function createDirectory($name) {
throw new DAV\Exception\Forbidden('Permission denied to create directory');
@ -50,7 +51,7 @@ class User extends DAVACL\Principal implements DAV\ICollection {
* @param string $name
* @return DAV\INode
*/
public function getChild($name) {
function getChild($name) {
$principal = $this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/' . $name);
if (!$principal) {
@ -71,9 +72,9 @@ class User extends DAVACL\Principal implements DAV\ICollection {
*
* @return DAV\INode[]
*/
public function getChildren() {
function getChildren() {
$r = array();
$r = [];
if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-read')) {
$r[] = new ProxyRead($this->principalBackend, $this->principalProperties);
}
@ -91,7 +92,7 @@ class User extends DAVACL\Principal implements DAV\ICollection {
* @param string $name
* @return bool
*/
public function childExists($name) {
function childExists($name) {
try {
$this->getChild($name);
@ -114,19 +115,19 @@ class User extends DAVACL\Principal implements DAV\ICollection {
*
* @return array
*/
public function getACL() {
function getACL() {
$acl = parent::getACL();
$acl[] = array(
$acl[] = [
'privilege' => '{DAV:}read',
'principal' => $this->principalProperties['uri'] . '/calendar-proxy-read',
'protected' => true,
);
$acl[] = array(
];
$acl[] = [
'privilege' => '{DAV:}read',
'principal' => $this->principalProperties['uri'] . '/calendar-proxy-write',
'protected' => true,
);
];
return $acl;
}

View file

@ -0,0 +1,15 @@
<?php
namespace Sabre\CalDAV\Schedule;
/**
* Implement this interface to have a node be recognized as a CalDAV scheduling
* inbox.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface IInbox extends \Sabre\CalDAV\ICalendarObjectContainer, \Sabre\DAVACL\IACL {
}

View file

@ -0,0 +1,190 @@
<?php
namespace Sabre\CalDAV\Schedule;
use Sabre\DAV;
use Sabre\VObject\ITip;
/**
* iMIP handler.
*
* This class is responsible for sending out iMIP messages. iMIP is the
* email-based transport for iTIP. iTIP deals with scheduling operations for
* iCalendar objects.
*
* If you want to customize the email that gets sent out, you can do so by
* extending this class and overriding the sendMessage method.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class IMipPlugin extends DAV\ServerPlugin {
/**
* Email address used in From: header.
*
* @var string
*/
protected $senderEmail;
/**
* ITipMessage
*
* @var ITip\Message
*/
protected $itipMessage;
/**
* Creates the email handler.
*
* @param string $senderEmail. The 'senderEmail' is the email that shows up
* in the 'From:' address. This should
* generally be some kind of no-reply email
* address you own.
*/
function __construct($senderEmail) {
$this->senderEmail = $senderEmail;
}
/*
* This initializes the plugin.
*
* This function is called by Sabre\DAV\Server, after
* addPlugin is called.
*
* This method should set up the required event subscriptions.
*
* @param DAV\Server $server
* @return void
*/
function initialize(DAV\Server $server) {
$server->on('schedule', [$this, 'schedule'], 120);
}
/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins
* using \Sabre\DAV\Server::getPlugin
*
* @return string
*/
function getPluginName() {
return 'imip';
}
/**
* Event handler for the 'schedule' event.
*
* @param ITip\Message $iTipMessage
* @return void
*/
function schedule(ITip\Message $iTipMessage) {
// Not sending any emails if the system considers the update
// insignificant.
if (!$iTipMessage->significantChange) {
if (!$iTipMessage->scheduleStatus) {
$iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
}
return;
}
$summary = $iTipMessage->message->VEVENT->SUMMARY;
if (parse_url($iTipMessage->sender, PHP_URL_SCHEME) !== 'mailto')
return;
if (parse_url($iTipMessage->recipient, PHP_URL_SCHEME) !== 'mailto')
return;
$sender = substr($iTipMessage->sender, 7);
$recipient = substr($iTipMessage->recipient, 7);
if ($iTipMessage->senderName) {
$sender = $iTipMessage->senderName . ' <' . $sender . '>';
}
if ($iTipMessage->recipientName) {
$recipient = $iTipMessage->recipientName . ' <' . $recipient . '>';
}
$subject = 'SabreDAV iTIP message';
switch (strtoupper($iTipMessage->method)) {
case 'REPLY' :
$subject = 'Re: ' . $summary;
break;
case 'REQUEST' :
$subject = $summary;
break;
case 'CANCEL' :
$subject = 'Cancelled: ' . $summary;
break;
}
$headers = [
'Reply-To: ' . $sender,
'From: ' . $this->senderEmail,
'Content-Type: text/calendar; charset=UTF-8; method=' . $iTipMessage->method,
];
if (DAV\Server::$exposeVersion) {
$headers[] = 'X-Sabre-Version: ' . DAV\Version::VERSION;
}
$this->mail(
$recipient,
$subject,
$iTipMessage->message->serialize(),
$headers
);
$iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip';
}
// @codeCoverageIgnoreStart
// This is deemed untestable in a reasonable manner
/**
* This function is responsible for sending the actual email.
*
* @param string $to Recipient email address
* @param string $subject Subject of the email
* @param string $body iCalendar body
* @param array $headers List of headers
* @return void
*/
protected function mail($to, $subject, $body, array $headers) {
mail($to, $subject, $body, implode("\r\n", $headers));
}
// @codeCoverageIgnoreEnd
/**
* Returns a bunch of meta-data about the plugin.
*
* Providing this information is optional, and is mainly displayed by the
* Browser plugin.
*
* The description key in the returned array may contain html and will not
* be sanitized.
*
* @return array
*/
function getPluginInfo() {
return [
'name' => $this->getPluginName(),
'description' => 'Email delivery (rfc6037) for CalDAV scheduling',
'link' => 'http://sabre.io/dav/scheduling/',
];
}
}

View file

@ -6,11 +6,10 @@ namespace Sabre\CalDAV\Schedule;
* Implement this interface to have a node be recognized as a CalDAV scheduling
* outbox.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface IOutbox extends \Sabre\DAV\ICollection, \Sabre\DAVACL\IACL {
}

View file

@ -0,0 +1,13 @@
<?php
namespace Sabre\CalDAV\Schedule;
/**
* The SchedulingObject represents a scheduling object in the Inbox collection
*
* @license http://sabre.io/license/ Modified BSD License
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
*/
interface ISchedulingObject extends \Sabre\CalDAV\ICalendarObject {
}

View file

@ -0,0 +1,261 @@
<?php
namespace Sabre\CalDAV\Schedule;
use Sabre\DAV;
use Sabre\CalDAV;
use Sabre\DAVACL;
use Sabre\CalDAV\Backend;
use Sabre\VObject;
/**
* The CalDAV scheduling inbox
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Inbox extends DAV\Collection implements IInbox {
/**
* CalDAV backend
*
* @var Backend\BackendInterface
*/
protected $caldavBackend;
/**
* The principal Uri
*
* @var string
*/
protected $principalUri;
/**
* Constructor
*
* @param Backend\SchedulingSupport $caldavBackend
* @param string $principalUri
*/
function __construct(Backend\SchedulingSupport $caldavBackend, $principalUri) {
$this->caldavBackend = $caldavBackend;
$this->principalUri = $principalUri;
}
/**
* Returns the name of the node.
*
* This is used to generate the url.
*
* @return string
*/
function getName() {
return 'inbox';
}
/**
* Returns an array with all the child nodes
*
* @return \Sabre\DAV\INode[]
*/
function getChildren() {
$objs = $this->caldavBackend->getSchedulingObjects($this->principalUri);
$children = [];
foreach ($objs as $obj) {
//$obj['acl'] = $this->getACL();
$obj['principaluri'] = $this->principalUri;
$children[] = new SchedulingObject($this->caldavBackend, $obj);
}
return $children;
}
/**
* Creates a new file in the directory
*
* Data will either be supplied as a stream resource, or in certain cases
* as a string. Keep in mind that you may have to support either.
*
* After succesful creation of the file, you may choose to return the ETag
* of the new file here.
*
* The returned ETag must be surrounded by double-quotes (The quotes should
* be part of the actual string).
*
* If you cannot accurately determine the ETag, you should not return it.
* If you don't store the file exactly as-is (you're transforming it
* somehow) you should also not return an ETag.
*
* This means that if a subsequent GET to this new file does not exactly
* return the same contents of what was submitted here, you are strongly
* recommended to omit the ETag.
*
* @param string $name Name of the file
* @param resource|string $data Initial payload
* @return null|string
*/
function createFile($name, $data = null) {
$this->caldavBackend->createSchedulingObject($this->principalUri, $name, $data);
}
/**
* Returns the owner principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
function getOwner() {
return $this->principalUri;
}
/**
* Returns a group principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
function getGroup() {
return null;
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
function getACL() {
return [
[
'privilege' => '{DAV:}read',
'principal' => '{DAV:}authenticated',
'protected' => true,
],
[
'privilege' => '{DAV:}write-properties',
'principal' => $this->getOwner(),
'protected' => true,
],
[
'privilege' => '{DAV:}unbind',
'principal' => $this->getOwner(),
'protected' => true,
],
[
'privilege' => '{DAV:}unbind',
'principal' => $this->getOwner() . '/calendar-proxy-write',
'protected' => true,
],
[
'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-deliver-invite',
'principal' => '{DAV:}authenticated',
'protected' => true,
],
[
'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-deliver-reply',
'principal' => '{DAV:}authenticated',
'protected' => true,
],
];
}
/**
* Updates the ACL
*
* This method will receive a list of new ACE's.
*
* @param array $acl
* @return void
*/
function setACL(array $acl) {
throw new DAV\Exception\MethodNotAllowed('You\'re not allowed to update the ACL');
}
/**
* Returns the list of supported privileges for this node.
*
* The returned data structure is a list of nested privileges.
* See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
* standard structure.
*
* If null is returned from this method, the default privilege set is used,
* which is fine for most common usecases.
*
* @return array|null
*/
function getSupportedPrivilegeSet() {
$ns = '{' . CalDAV\Plugin::NS_CALDAV . '}';
$default = DAVACL\Plugin::getDefaultSupportedPrivilegeSet();
$default['aggregates'][] = [
'privilege' => $ns . 'schedule-deliver',
'aggregates' => [
['privilege' => $ns . 'schedule-deliver-invite'],
['privilege' => $ns . 'schedule-deliver-reply'],
],
];
return $default;
}
/**
* Performs a calendar-query on the contents of this calendar.
*
* The calendar-query is defined in RFC4791 : CalDAV. Using the
* calendar-query it is possible for a client to request a specific set of
* object, based on contents of iCalendar properties, date-ranges and
* iCalendar component types (VTODO, VEVENT).
*
* This method should just return a list of (relative) urls that match this
* query.
*
* The list of filters are specified as an array. The exact array is
* documented by \Sabre\CalDAV\CalendarQueryParser.
*
* @param array $filters
* @return array
*/
function calendarQuery(array $filters) {
$result = [];
$validator = new CalDAV\CalendarQueryValidator();
$objects = $this->caldavBackend->getSchedulingObjects($this->principalUri);
foreach ($objects as $object) {
$vObject = VObject\Reader::read($object['calendardata']);
if ($validator->validate($vObject, $filters)) {
$result[] = $object['uri'];
}
// Destroy circular references to PHP will GC the object.
$vObject->destroy();
}
return $result;
}
}

View file

@ -1,6 +1,7 @@
<?php
namespace Sabre\CalDAV\Schedule;
use Sabre\DAV;
use Sabre\CalDAV;
use Sabre\DAVACL;
@ -12,7 +13,7 @@ use Sabre\DAVACL;
* free-busy requests. This functionality is completely handled by the
* Scheduling plugin, so this object is actually mostly static.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
@ -30,7 +31,7 @@ class Outbox extends DAV\Collection implements IOutbox {
*
* @param string $principalUri
*/
public function __construct($principalUri) {
function __construct($principalUri) {
$this->principalUri = $principalUri;
@ -43,7 +44,7 @@ class Outbox extends DAV\Collection implements IOutbox {
*
* @return string
*/
public function getName() {
function getName() {
return 'outbox';
@ -54,9 +55,9 @@ class Outbox extends DAV\Collection implements IOutbox {
*
* @return \Sabre\DAV\INode[]
*/
public function getChildren() {
function getChildren() {
return array();
return [];
}
@ -67,7 +68,7 @@ class Outbox extends DAV\Collection implements IOutbox {
*
* @return string|null
*/
public function getOwner() {
function getOwner() {
return $this->principalUri;
@ -80,7 +81,7 @@ class Outbox extends DAV\Collection implements IOutbox {
*
* @return string|null
*/
public function getGroup() {
function getGroup() {
return null;
@ -98,25 +99,45 @@ class Outbox extends DAV\Collection implements IOutbox {
*
* @return array
*/
public function getACL() {
function getACL() {
return array(
array(
return [
[
'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy',
'principal' => $this->getOwner(),
'protected' => true,
),
array(
],
[
'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent',
'principal' => $this->getOwner(),
'protected' => true,
),
array(
],
[
'privilege' => '{DAV:}read',
'principal' => $this->getOwner(),
'protected' => true,
),
);
],
[
'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy',
'principal' => $this->getOwner() . '/calendar-proxy-write',
'protected' => true,
],
[
'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent',
'principal' => $this->getOwner() . '/calendar-proxy-write',
'protected' => true,
],
[
'privilege' => '{DAV:}read',
'principal' => $this->getOwner() . '/calendar-proxy-read',
'protected' => true,
],
[
'privilege' => '{DAV:}read',
'principal' => $this->getOwner() . '/calendar-proxy-write',
'protected' => true,
],
];
}
@ -128,7 +149,7 @@ class Outbox extends DAV\Collection implements IOutbox {
* @param array $acl
* @return void
*/
public function setACL(array $acl) {
function setACL(array $acl) {
throw new DAV\Exception\MethodNotAllowed('You\'re not allowed to update the ACL');
@ -146,15 +167,15 @@ class Outbox extends DAV\Collection implements IOutbox {
*
* @return array|null
*/
public function getSupportedPrivilegeSet() {
function getSupportedPrivilegeSet() {
$default = DAVACL\Plugin::getDefaultSupportedPrivilegeSet();
$default['aggregates'][] = array(
$default['aggregates'][] = [
'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy',
);
$default['aggregates'][] = array(
];
$default['aggregates'][] = [
'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent',
);
];
return $default;

View file

@ -0,0 +1,994 @@
<?php
namespace Sabre\CalDAV\Schedule;
use DateTimeZone;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\DAV\PropFind;
use Sabre\DAV\PropPatch;
use Sabre\DAV\INode;
use Sabre\DAV\Xml\Property\Href;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\VObject;
use Sabre\VObject\Reader;
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\ITip;
use Sabre\VObject\ITip\Message;
use Sabre\DAVACL;
use Sabre\CalDAV\ICalendar;
use Sabre\CalDAV\ICalendarObject;
use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\Exception\NotImplemented;
/**
* CalDAV scheduling plugin.
* =========================
*
* This plugin provides the functionality added by the "Scheduling Extensions
* to CalDAV" standard, as defined in RFC6638.
*
* calendar-auto-schedule largely works by intercepting a users request to
* update their local calendar. If a user creates a new event with attendees,
* this plugin is supposed to grab the information from that event, and notify
* the attendees of this.
*
* There's 3 possible transports for this:
* * local delivery
* * delivery through email (iMip)
* * server-to-server delivery (iSchedule)
*
* iMip is simply, because we just need to add the iTip message as an email
* attachment. Local delivery is harder, because we both need to add this same
* message to a local DAV inbox, as well as live-update the relevant events.
*
* iSchedule is something for later.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Plugin extends ServerPlugin {
/**
* This is the official CalDAV namespace
*/
const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav';
/**
* Reference to main Server object.
*
* @var Server
*/
protected $server;
/**
* Returns a list of features for the DAV: HTTP header.
*
* @return array
*/
function getFeatures() {
return ['calendar-auto-schedule', 'calendar-availability'];
}
/**
* Returns the name of the plugin.
*
* Using this name other plugins will be able to access other plugins
* using Server::getPlugin
*
* @return string
*/
function getPluginName() {
return 'caldav-schedule';
}
/**
* Initializes the plugin
*
* @param Server $server
* @return void
*/
function initialize(Server $server) {
$this->server = $server;
$server->on('method:POST', [$this, 'httpPost']);
$server->on('propFind', [$this, 'propFind']);
$server->on('propPatch', [$this, 'propPatch']);
$server->on('calendarObjectChange', [$this, 'calendarObjectChange']);
$server->on('beforeUnbind', [$this, 'beforeUnbind']);
$server->on('schedule', [$this, 'scheduleLocalDelivery']);
$ns = '{' . self::NS_CALDAV . '}';
/**
* This information ensures that the {DAV:}resourcetype property has
* the correct values.
*/
$server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IOutbox'] = $ns . 'schedule-outbox';
$server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IInbox'] = $ns . 'schedule-inbox';
/**
* Properties we protect are made read-only by the server.
*/
array_push($server->protectedProperties,
$ns . 'schedule-inbox-URL',
$ns . 'schedule-outbox-URL',
$ns . 'calendar-user-address-set',
$ns . 'calendar-user-type',
$ns . 'schedule-default-calendar-URL'
);
}
/**
* Use this method to tell the server this plugin defines additional
* HTTP methods.
*
* This method is passed a uri. It should only return HTTP methods that are
* available for the specified uri.
*
* @param string $uri
* @return array
*/
function getHTTPMethods($uri) {
try {
$node = $this->server->tree->getNodeForPath($uri);
} catch (NotFound $e) {
return [];
}
if ($node instanceof IOutbox) {
return ['POST'];
}
return [];
}
/**
* This method handles POST request for the outbox.
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @return bool
*/
function httpPost(RequestInterface $request, ResponseInterface $response) {
// Checking if this is a text/calendar content type
$contentType = $request->getHeader('Content-Type');
if (strpos($contentType, 'text/calendar') !== 0) {
return;
}
$path = $request->getPath();
// Checking if we're talking to an outbox
try {
$node = $this->server->tree->getNodeForPath($path);
} catch (NotFound $e) {
return;
}
if (!$node instanceof IOutbox)
return;
$this->server->transactionType = 'post-caldav-outbox';
$this->outboxRequest($node, $request, $response);
// Returning false breaks the event chain and tells the server we've
// handled the request.
return false;
}
/**
* This method handler is invoked during fetching of properties.
*
* We use this event to add calendar-auto-schedule-specific properties.
*
* @param PropFind $propFind
* @param INode $node
* @return void
*/
function propFind(PropFind $propFind, INode $node) {
if ($node instanceof DAVACL\IPrincipal) {
$caldavPlugin = $this->server->getPlugin('caldav');
$principalUrl = $node->getPrincipalUrl();
// schedule-outbox-URL property
$propFind->handle('{' . self::NS_CALDAV . '}schedule-outbox-URL', function() use ($principalUrl, $caldavPlugin) {
$calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
if (!$calendarHomePath) {
return null;
}
$outboxPath = $calendarHomePath . '/outbox/';
return new Href($outboxPath);
});
// schedule-inbox-URL property
$propFind->handle('{' . self::NS_CALDAV . '}schedule-inbox-URL', function() use ($principalUrl, $caldavPlugin) {
$calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
if (!$calendarHomePath) {
return null;
}
$inboxPath = $calendarHomePath . '/inbox/';
return new Href($inboxPath);
});
$propFind->handle('{' . self::NS_CALDAV . '}schedule-default-calendar-URL', function() use ($principalUrl, $caldavPlugin) {
// We don't support customizing this property yet, so in the
// meantime we just grab the first calendar in the home-set.
$calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
if (!$calendarHomePath) {
return null;
}
$sccs = '{' . self::NS_CALDAV . '}supported-calendar-component-set';
$result = $this->server->getPropertiesForPath($calendarHomePath, [
'{DAV:}resourcetype',
$sccs,
], 1);
foreach ($result as $child) {
if (!isset($child[200]['{DAV:}resourcetype']) || !$child[200]['{DAV:}resourcetype']->is('{' . self::NS_CALDAV . '}calendar') || $child[200]['{DAV:}resourcetype']->is('{http://calendarserver.org/ns/}shared')) {
// Node is either not a calendar or a shared instance.
continue;
}
if (!isset($child[200][$sccs]) || in_array('VEVENT', $child[200][$sccs]->getValue())) {
// Either there is no supported-calendar-component-set
// (which is fine) or we found one that supports VEVENT.
return new Href($child['href']);
}
}
});
// The server currently reports every principal to be of type
// 'INDIVIDUAL'
$propFind->handle('{' . self::NS_CALDAV . '}calendar-user-type', function() {
return 'INDIVIDUAL';
});
}
// Mapping the old property to the new property.
$propFind->handle('{http://calendarserver.org/ns/}calendar-availability', function() use ($propFind, $node) {
// In case it wasn't clear, the only difference is that we map the
// old property to a different namespace.
$availProp = '{' . self::NS_CALDAV . '}calendar-availability';
$subPropFind = new PropFind(
$propFind->getPath(),
[$availProp]
);
$this->server->getPropertiesByNode(
$subPropFind,
$node
);
$propFind->set(
'{http://calendarserver.org/ns/}calendar-availability',
$subPropFind->get($availProp),
$subPropFind->getStatus($availProp)
);
});
}
/**
* This method is called during property updates.
*
* @param string $path
* @param PropPatch $propPatch
* @return void
*/
function propPatch($path, PropPatch $propPatch) {
// Mapping the old property to the new property.
$propPatch->handle('{http://calendarserver.org/ns/}calendar-availability', function($value) use ($path) {
$availProp = '{' . self::NS_CALDAV . '}calendar-availability';
$subPropPatch = new PropPatch([$availProp => $value]);
$this->server->emit('propPatch', [$path, $subPropPatch]);
$subPropPatch->commit();
return $subPropPatch->getResult()[$availProp];
});
}
/**
* This method is triggered whenever there was a calendar object gets
* created or updated.
*
* @param RequestInterface $request HTTP request
* @param ResponseInterface $response HTTP Response
* @param VCalendar $vCal Parsed iCalendar object
* @param mixed $calendarPath Path to calendar collection
* @param mixed $modified The iCalendar object has been touched.
* @param mixed $isNew Whether this was a new item or we're updating one
* @return void
*/
function calendarObjectChange(RequestInterface $request, ResponseInterface $response, VCalendar $vCal, $calendarPath, &$modified, $isNew) {
if (!$this->scheduleReply($this->server->httpRequest)) {
return;
}
$calendarNode = $this->server->tree->getNodeForPath($calendarPath);
$addresses = $this->getAddressesForPrincipal(
$calendarNode->getOwner()
);
if (!$isNew) {
$node = $this->server->tree->getNodeForPath($request->getPath());
$oldObj = Reader::read($node->get());
} else {
$oldObj = null;
}
$this->processICalendarChange($oldObj, $vCal, $addresses, [], $modified);
if ($oldObj) {
// Destroy circular references so PHP will GC the object.
$oldObj->destroy();
}
}
/**
* This method is responsible for delivering the ITip message.
*
* @param ITip\Message $itipMessage
* @return void
*/
function deliver(ITip\Message $iTipMessage) {
$this->server->emit('schedule', [$iTipMessage]);
if (!$iTipMessage->scheduleStatus) {
$iTipMessage->scheduleStatus = '5.2;There was no system capable of delivering the scheduling message';
}
// In case the change was considered 'insignificant', we are going to
// remove any error statuses, if any. See ticket #525.
list($baseCode) = explode('.', $iTipMessage->scheduleStatus);
if (!$iTipMessage->significantChange && in_array($baseCode, ['3', '5'])) {
$iTipMessage->scheduleStatus = null;
}
}
/**
* This method is triggered before a file gets deleted.
*
* We use this event to make sure that when this happens, attendees get
* cancellations, and organizers get 'DECLINED' statuses.
*
* @param string $path
* @return void
*/
function beforeUnbind($path) {
// FIXME: We shouldn't trigger this functionality when we're issuing a
// MOVE. This is a hack.
if ($this->server->httpRequest->getMethod() === 'MOVE') return;
$node = $this->server->tree->getNodeForPath($path);
if (!$node instanceof ICalendarObject || $node instanceof ISchedulingObject) {
return;
}
if (!$this->scheduleReply($this->server->httpRequest)) {
return;
}
$addresses = $this->getAddressesForPrincipal(
$node->getOwner()
);
$broker = new ITip\Broker();
$messages = $broker->parseEvent(null, $addresses, $node->get());
foreach ($messages as $message) {
$this->deliver($message);
}
}
/**
* Event handler for the 'schedule' event.
*
* This handler attempts to look at local accounts to deliver the
* scheduling object.
*
* @param ITip\Message $iTipMessage
* @return void
*/
function scheduleLocalDelivery(ITip\Message $iTipMessage) {
$aclPlugin = $this->server->getPlugin('acl');
// Local delivery is not available if the ACL plugin is not loaded.
if (!$aclPlugin) {
return;
}
$caldavNS = '{' . self::NS_CALDAV . '}';
$principalUri = $aclPlugin->getPrincipalByUri($iTipMessage->recipient);
if (!$principalUri) {
$iTipMessage->scheduleStatus = '3.7;Could not find principal.';
return;
}
// We found a principal URL, now we need to find its inbox.
// Unfortunately we may not have sufficient privileges to find this, so
// we are temporarily turning off ACL to let this come through.
//
// Once we support PHP 5.5, this should be wrapped in a try..finally
// block so we can ensure that this privilege gets added again after.
$this->server->removeListener('propFind', [$aclPlugin, 'propFind']);
$result = $this->server->getProperties(
$principalUri,
[
'{DAV:}principal-URL',
$caldavNS . 'calendar-home-set',
$caldavNS . 'schedule-inbox-URL',
$caldavNS . 'schedule-default-calendar-URL',
'{http://sabredav.org/ns}email-address',
]
);
// Re-registering the ACL event
$this->server->on('propFind', [$aclPlugin, 'propFind'], 20);
if (!isset($result[$caldavNS . 'schedule-inbox-URL'])) {
$iTipMessage->scheduleStatus = '5.2;Could not find local inbox';
return;
}
if (!isset($result[$caldavNS . 'calendar-home-set'])) {
$iTipMessage->scheduleStatus = '5.2;Could not locate a calendar-home-set';
return;
}
if (!isset($result[$caldavNS . 'schedule-default-calendar-URL'])) {
$iTipMessage->scheduleStatus = '5.2;Could not find a schedule-default-calendar-URL property';
return;
}
$calendarPath = $result[$caldavNS . 'schedule-default-calendar-URL']->getHref();
$homePath = $result[$caldavNS . 'calendar-home-set']->getHref();
$inboxPath = $result[$caldavNS . 'schedule-inbox-URL']->getHref();
if ($iTipMessage->method === 'REPLY') {
$privilege = 'schedule-deliver-reply';
} else {
$privilege = 'schedule-deliver-invite';
}
if (!$aclPlugin->checkPrivileges($inboxPath, $caldavNS . $privilege, DAVACL\Plugin::R_PARENT, false)) {
$iTipMessage->scheduleStatus = '3.8;organizer did not have the ' . $privilege . ' privilege on the attendees inbox';
return;
}
// Next, we're going to find out if the item already exits in one of
// the users' calendars.
$uid = $iTipMessage->uid;
$newFileName = 'sabredav-' . \Sabre\DAV\UUIDUtil::getUUID() . '.ics';
$home = $this->server->tree->getNodeForPath($homePath);
$inbox = $this->server->tree->getNodeForPath($inboxPath);
$currentObject = null;
$objectNode = null;
$isNewNode = false;
$result = $home->getCalendarObjectByUID($uid);
if ($result) {
// There was an existing object, we need to update probably.
$objectPath = $homePath . '/' . $result;
$objectNode = $this->server->tree->getNodeForPath($objectPath);
$oldICalendarData = $objectNode->get();
$currentObject = Reader::read($oldICalendarData);
} else {
$isNewNode = true;
}
$broker = new ITip\Broker();
$newObject = $broker->processMessage($iTipMessage, $currentObject);
$inbox->createFile($newFileName, $iTipMessage->message->serialize());
if (!$newObject) {
// We received an iTip message referring to a UID that we don't
// have in any calendars yet, and processMessage did not give us a
// calendarobject back.
//
// The implication is that processMessage did not understand the
// iTip message.
$iTipMessage->scheduleStatus = '5.0;iTip message was not processed by the server, likely because we didn\'t understand it.';
return;
}
// Note that we are bypassing ACL on purpose by calling this directly.
// We may need to look a bit deeper into this later. Supporting ACL
// here would be nice.
if ($isNewNode) {
$calendar = $this->server->tree->getNodeForPath($calendarPath);
$calendar->createFile($newFileName, $newObject->serialize());
} else {
// If the message was a reply, we may have to inform other
// attendees of this attendees status. Therefore we're shooting off
// another itipMessage.
if ($iTipMessage->method === 'REPLY') {
$this->processICalendarChange(
$oldICalendarData,
$newObject,
[$iTipMessage->recipient],
[$iTipMessage->sender]
);
}
$objectNode->put($newObject->serialize());
}
$iTipMessage->scheduleStatus = '1.2;Message delivered locally';
}
/**
* This method looks at an old iCalendar object, a new iCalendar object and
* starts sending scheduling messages based on the changes.
*
* A list of addresses needs to be specified, so the system knows who made
* the update, because the behavior may be different based on if it's an
* attendee or an organizer.
*
* This method may update $newObject to add any status changes.
*
* @param VCalendar|string $oldObject
* @param VCalendar $newObject
* @param array $addresses
* @param array $ignore Any addresses to not send messages to.
* @param bool $modified A marker to indicate that the original object
* modified by this process.
* @return void
*/
protected function processICalendarChange($oldObject = null, VCalendar $newObject, array $addresses, array $ignore = [], &$modified = false) {
$broker = new ITip\Broker();
$messages = $broker->parseEvent($newObject, $addresses, $oldObject);
if ($messages) $modified = true;
foreach ($messages as $message) {
if (in_array($message->recipient, $ignore)) {
continue;
}
$this->deliver($message);
if (isset($newObject->VEVENT->ORGANIZER) && ($newObject->VEVENT->ORGANIZER->getNormalizedValue() === $message->recipient)) {
if ($message->scheduleStatus) {
$newObject->VEVENT->ORGANIZER['SCHEDULE-STATUS'] = $message->getScheduleStatus();
}
unset($newObject->VEVENT->ORGANIZER['SCHEDULE-FORCE-SEND']);
} else {
if (isset($newObject->VEVENT->ATTENDEE)) foreach ($newObject->VEVENT->ATTENDEE as $attendee) {
if ($attendee->getNormalizedValue() === $message->recipient) {
if ($message->scheduleStatus) {
$attendee['SCHEDULE-STATUS'] = $message->getScheduleStatus();
}
unset($attendee['SCHEDULE-FORCE-SEND']);
break;
}
}
}
}
}
/**
* Returns a list of addresses that are associated with a principal.
*
* @param string $principal
* @return array
*/
protected function getAddressesForPrincipal($principal) {
$CUAS = '{' . self::NS_CALDAV . '}calendar-user-address-set';
$properties = $this->server->getProperties(
$principal,
[$CUAS]
);
// If we can't find this information, we'll stop processing
if (!isset($properties[$CUAS])) {
return;
}
$addresses = $properties[$CUAS]->getHrefs();
return $addresses;
}
/**
* This method handles POST requests to the schedule-outbox.
*
* Currently, two types of requests are support:
* * FREEBUSY requests from RFC 6638
* * Simple iTIP messages from draft-desruisseaux-caldav-sched-04
*
* The latter is from an expired early draft of the CalDAV scheduling
* extensions, but iCal depends on a feature from that spec, so we
* implement it.
*
* @param IOutbox $outboxNode
* @param RequestInterface $request
* @param ResponseInterface $response
* @return void
*/
function outboxRequest(IOutbox $outboxNode, RequestInterface $request, ResponseInterface $response) {
$outboxPath = $request->getPath();
// Parsing the request body
try {
$vObject = VObject\Reader::read($request->getBody());
} catch (VObject\ParseException $e) {
throw new BadRequest('The request body must be a valid iCalendar object. Parse error: ' . $e->getMessage());
}
// The incoming iCalendar object must have a METHOD property, and a
// component. The combination of both determines what type of request
// this is.
$componentType = null;
foreach ($vObject->getComponents() as $component) {
if ($component->name !== 'VTIMEZONE') {
$componentType = $component->name;
break;
}
}
if (is_null($componentType)) {
throw new BadRequest('We expected at least one VTODO, VJOURNAL, VFREEBUSY or VEVENT component');
}
// Validating the METHOD
$method = strtoupper((string)$vObject->METHOD);
if (!$method) {
throw new BadRequest('A METHOD property must be specified in iTIP messages');
}
// So we support one type of request:
//
// REQUEST with a VFREEBUSY component
$acl = $this->server->getPlugin('acl');
if ($componentType === 'VFREEBUSY' && $method === 'REQUEST') {
$acl && $acl->checkPrivileges($outboxPath, '{' . self::NS_CALDAV . '}schedule-query-freebusy');
$this->handleFreeBusyRequest($outboxNode, $vObject, $request, $response);
// Destroy circular references so PHP can GC the object.
$vObject->destroy();
unset($vObject);
} else {
throw new NotImplemented('We only support VFREEBUSY (REQUEST) on this endpoint');
}
}
/**
* This method is responsible for parsing a free-busy query request and
* returning it's result.
*
* @param IOutbox $outbox
* @param VObject\Component $vObject
* @param RequestInterface $request
* @param ResponseInterface $response
* @return string
*/
protected function handleFreeBusyRequest(IOutbox $outbox, VObject\Component $vObject, RequestInterface $request, ResponseInterface $response) {
$vFreeBusy = $vObject->VFREEBUSY;
$organizer = $vFreeBusy->organizer;
$organizer = (string)$organizer;
// Validating if the organizer matches the owner of the inbox.
$owner = $outbox->getOwner();
$caldavNS = '{' . self::NS_CALDAV . '}';
$uas = $caldavNS . 'calendar-user-address-set';
$props = $this->server->getProperties($owner, [$uas]);
if (empty($props[$uas]) || !in_array($organizer, $props[$uas]->getHrefs())) {
throw new Forbidden('The organizer in the request did not match any of the addresses for the owner of this inbox');
}
if (!isset($vFreeBusy->ATTENDEE)) {
throw new BadRequest('You must at least specify 1 attendee');
}
$attendees = [];
foreach ($vFreeBusy->ATTENDEE as $attendee) {
$attendees[] = (string)$attendee;
}
if (!isset($vFreeBusy->DTSTART) || !isset($vFreeBusy->DTEND)) {
throw new BadRequest('DTSTART and DTEND must both be specified');
}
$startRange = $vFreeBusy->DTSTART->getDateTime();
$endRange = $vFreeBusy->DTEND->getDateTime();
$results = [];
foreach ($attendees as $attendee) {
$results[] = $this->getFreeBusyForEmail($attendee, $startRange, $endRange, $vObject);
}
$dom = new \DOMDocument('1.0', 'utf-8');
$dom->formatOutput = true;
$scheduleResponse = $dom->createElement('cal:schedule-response');
foreach ($this->server->xml->namespaceMap as $namespace => $prefix) {
$scheduleResponse->setAttribute('xmlns:' . $prefix, $namespace);
}
$dom->appendChild($scheduleResponse);
foreach ($results as $result) {
$xresponse = $dom->createElement('cal:response');
$recipient = $dom->createElement('cal:recipient');
$recipientHref = $dom->createElement('d:href');
$recipientHref->appendChild($dom->createTextNode($result['href']));
$recipient->appendChild($recipientHref);
$xresponse->appendChild($recipient);
$reqStatus = $dom->createElement('cal:request-status');
$reqStatus->appendChild($dom->createTextNode($result['request-status']));
$xresponse->appendChild($reqStatus);
if (isset($result['calendar-data'])) {
$calendardata = $dom->createElement('cal:calendar-data');
$calendardata->appendChild($dom->createTextNode(str_replace("\r\n", "\n", $result['calendar-data']->serialize())));
$xresponse->appendChild($calendardata);
}
$scheduleResponse->appendChild($xresponse);
}
$response->setStatus(200);
$response->setHeader('Content-Type', 'application/xml');
$response->setBody($dom->saveXML());
}
/**
* Returns free-busy information for a specific address. The returned
* data is an array containing the following properties:
*
* calendar-data : A VFREEBUSY VObject
* request-status : an iTip status code.
* href: The principal's email address, as requested
*
* The following request status codes may be returned:
* * 2.0;description
* * 3.7;description
*
* @param string $email address
* @param DateTimeInterface $start
* @param DateTimeInterface $end
* @param VObject\Component $request
* @return array
*/
protected function getFreeBusyForEmail($email, \DateTimeInterface $start, \DateTimeInterface $end, VObject\Component $request) {
$caldavNS = '{' . self::NS_CALDAV . '}';
$aclPlugin = $this->server->getPlugin('acl');
if (substr($email, 0, 7) === 'mailto:') $email = substr($email, 7);
$result = $aclPlugin->principalSearch(
['{http://sabredav.org/ns}email-address' => $email],
[
'{DAV:}principal-URL',
$caldavNS . 'calendar-home-set',
$caldavNS . 'schedule-inbox-URL',
'{http://sabredav.org/ns}email-address',
]
);
if (!count($result)) {
return [
'request-status' => '3.7;Could not find principal',
'href' => 'mailto:' . $email,
];
}
if (!isset($result[0][200][$caldavNS . 'calendar-home-set'])) {
return [
'request-status' => '3.7;No calendar-home-set property found',
'href' => 'mailto:' . $email,
];
}
if (!isset($result[0][200][$caldavNS . 'schedule-inbox-URL'])) {
return [
'request-status' => '3.7;No schedule-inbox-URL property found',
'href' => 'mailto:' . $email,
];
}
$homeSet = $result[0][200][$caldavNS . 'calendar-home-set']->getHref();
$inboxUrl = $result[0][200][$caldavNS . 'schedule-inbox-URL']->getHref();
// Grabbing the calendar list
$objects = [];
$calendarTimeZone = new DateTimeZone('UTC');
foreach ($this->server->tree->getNodeForPath($homeSet)->getChildren() as $node) {
if (!$node instanceof ICalendar) {
continue;
}
$sct = $caldavNS . 'schedule-calendar-transp';
$ctz = $caldavNS . 'calendar-timezone';
$props = $node->getProperties([$sct, $ctz]);
if (isset($props[$sct]) && $props[$sct]->getValue() == ScheduleCalendarTransp::TRANSPARENT) {
// If a calendar is marked as 'transparent', it means we must
// ignore it for free-busy purposes.
continue;
}
$aclPlugin->checkPrivileges($homeSet . $node->getName(), $caldavNS . 'read-free-busy');
if (isset($props[$ctz])) {
$vtimezoneObj = VObject\Reader::read($props[$ctz]);
$calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
// Destroy circular references so PHP can garbage collect the object.
$vtimezoneObj->destroy();
}
// Getting the list of object uris within the time-range
$urls = $node->calendarQuery([
'name' => 'VCALENDAR',
'comp-filters' => [
[
'name' => 'VEVENT',
'comp-filters' => [],
'prop-filters' => [],
'is-not-defined' => false,
'time-range' => [
'start' => $start,
'end' => $end,
],
],
],
'prop-filters' => [],
'is-not-defined' => false,
'time-range' => null,
]);
$calObjects = array_map(function($url) use ($node) {
$obj = $node->getChild($url)->get();
return $obj;
}, $urls);
$objects = array_merge($objects, $calObjects);
}
$inboxProps = $this->server->getProperties(
$inboxUrl,
$caldavNS . 'calendar-availability'
);
$vcalendar = new VObject\Component\VCalendar();
$vcalendar->METHOD = 'REPLY';
$generator = new VObject\FreeBusyGenerator();
$generator->setObjects($objects);
$generator->setTimeRange($start, $end);
$generator->setBaseObject($vcalendar);
$generator->setTimeZone($calendarTimeZone);
if ($inboxProps) {
$generator->setVAvailability(
VObject\Reader::read(
$inboxProps[$caldavNS . 'calendar-availability']
)
);
}
$result = $generator->getResult();
$vcalendar->VFREEBUSY->ATTENDEE = 'mailto:' . $email;
$vcalendar->VFREEBUSY->UID = (string)$request->VFREEBUSY->UID;
$vcalendar->VFREEBUSY->ORGANIZER = clone $request->VFREEBUSY->ORGANIZER;
return [
'calendar-data' => $result,
'request-status' => '2.0;Success',
'href' => 'mailto:' . $email,
];
}
/**
* This method checks the 'Schedule-Reply' header
* and returns false if it's 'F', otherwise true.
*
* @param RequestInterface $request
* @return bool
*/
private function scheduleReply(RequestInterface $request) {
$scheduleReply = $request->getHeader('Schedule-Reply');
return $scheduleReply !== 'F';
}
/**
* Returns a bunch of meta-data about the plugin.
*
* Providing this information is optional, and is mainly displayed by the
* Browser plugin.
*
* The description key in the returned array may contain html and will not
* be sanitized.
*
* @return array
*/
function getPluginInfo() {
return [
'name' => $this->getPluginName(),
'description' => 'Adds calendar-auto-schedule, as defined in rf6868',
'link' => 'http://sabre.io/dav/scheduling/',
];
}
}

View file

@ -0,0 +1,165 @@
<?php
namespace Sabre\CalDAV\Schedule;
use Sabre\CalDAV\Backend;
use Sabre\DAV\Exception\MethodNotAllowed;
/**
* The SchedulingObject represents a scheduling object in the Inbox collection
*
* @author Brett (https://github.com/bretten)
* @license http://sabre.io/license/ Modified BSD License
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
*/
class SchedulingObject extends \Sabre\CalDAV\CalendarObject implements ISchedulingObject {
/**
/* The CalDAV backend
*
* @var Backend\SchedulingSupport
*/
protected $caldavBackend;
/**
* Array with information about this SchedulingObject
*
* @var array
*/
protected $objectData;
/**
* Constructor
*
* The following properties may be passed within $objectData:
*
* * uri - A unique uri. Only the 'basename' must be passed.
* * principaluri - the principal that owns the object.
* * calendardata (optional) - The iCalendar data
* * etag - (optional) The etag for this object, MUST be encloded with
* double-quotes.
* * size - (optional) The size of the data in bytes.
* * lastmodified - (optional) format as a unix timestamp.
* * acl - (optional) Use this to override the default ACL for the node.
*
* @param Backend\BackendInterface $caldavBackend
* @param array $objectData
*/
function __construct(Backend\SchedulingSupport $caldavBackend, array $objectData) {
$this->caldavBackend = $caldavBackend;
if (!isset($objectData['uri'])) {
throw new \InvalidArgumentException('The objectData argument must contain an \'uri\' property');
}
$this->objectData = $objectData;
}
/**
* Returns the ICalendar-formatted object
*
* @return string
*/
function get() {
// Pre-populating the 'calendardata' is optional, if we don't have it
// already we fetch it from the backend.
if (!isset($this->objectData['calendardata'])) {
$this->objectData = $this->caldavBackend->getSchedulingObject($this->objectData['principaluri'], $this->objectData['uri']);
}
return $this->objectData['calendardata'];
}
/**
* Updates the ICalendar-formatted object
*
* @param string|resource $calendarData
* @return string
*/
function put($calendarData) {
throw new MethodNotAllowed('Updating scheduling objects is not supported');
}
/**
* Deletes the scheduling message
*
* @return void
*/
function delete() {
$this->caldavBackend->deleteSchedulingObject($this->objectData['principaluri'], $this->objectData['uri']);
}
/**
* Returns the owner principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
function getOwner() {
return $this->objectData['principaluri'];
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
function getACL() {
// An alternative acl may be specified in the object data.
//
if (isset($this->objectData['acl'])) {
return $this->objectData['acl'];
}
// The default ACL
return [
[
'privilege' => '{DAV:}read',
'principal' => $this->objectData['principaluri'],
'protected' => true,
],
[
'privilege' => '{DAV:}write',
'principal' => $this->objectData['principaluri'],
'protected' => true,
],
[
'privilege' => '{DAV:}read',
'principal' => $this->objectData['principaluri'] . '/calendar-proxy-write',
'protected' => true,
],
[
'privilege' => '{DAV:}write',
'principal' => $this->objectData['principaluri'] . '/calendar-proxy-write',
'protected' => true,
],
[
'privilege' => '{DAV:}read',
'principal' => $this->objectData['principaluri'] . '/calendar-proxy-read',
'protected' => true,
],
];
}
}

View file

@ -6,7 +6,7 @@ namespace Sabre\CalDAV;
* This object represents a CalDAV calendar that can be shared with other
* users.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
@ -30,7 +30,7 @@ class ShareableCalendar extends Calendar implements IShareableCalendar {
* @param array $remove
* @return void
*/
public function updateShares(array $add, array $remove) {
function updateShares(array $add, array $remove) {
$this->caldavBackend->updateShares($this->calendarInfo['id'], $add, $remove);
@ -48,7 +48,7 @@ class ShareableCalendar extends Calendar implements IShareableCalendar {
*
* @return array
*/
public function getShares() {
function getShares() {
return $this->caldavBackend->getShares($this->calendarInfo['id']);
@ -63,7 +63,7 @@ class ShareableCalendar extends Calendar implements IShareableCalendar {
* @param bool $value
* @return void
*/
public function setPublishStatus($value) {
function setPublishStatus($value) {
$this->caldavBackend->setPublishStatus($this->calendarInfo['id'], $value);

View file

@ -2,12 +2,10 @@
namespace Sabre\CalDAV;
use Sabre\DAVACL;
/**
* This object represents a CalDAV calendar that is shared by a different user.
*
* @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
@ -19,13 +17,13 @@ class SharedCalendar extends Calendar implements ISharedCalendar {
* @param Backend\BackendInterface $caldavBackend
* @param array $calendarInfo
*/
public function __construct(Backend\BackendInterface $caldavBackend, $calendarInfo) {
function __construct(Backend\BackendInterface $caldavBackend, $calendarInfo) {
$required = array(
$required = [
'{http://calendarserver.org/ns/}shared-url',
'{http://sabredav.org/ns}owner-principal',
'{http://sabredav.org/ns}read-only',
);
];
foreach ($required as $r) {
if (!isset($calendarInfo[$r])) {
throw new \InvalidArgumentException('The ' . $r . ' property must be specified for SharedCalendar(s)');
@ -42,7 +40,7 @@ class SharedCalendar extends Calendar implements ISharedCalendar {
*
* @return string
*/
public function getSharedUrl() {
function getSharedUrl() {
return $this->calendarInfo['{http://calendarserver.org/ns/}shared-url'];
@ -55,7 +53,7 @@ class SharedCalendar extends Calendar implements ISharedCalendar {
*
* @return string|null
*/
public function getOwner() {
function getOwner() {
return $this->calendarInfo['{http://sabredav.org/ns}owner-principal'];
@ -73,28 +71,62 @@ class SharedCalendar extends Calendar implements ISharedCalendar {
*
* @return array
*/
public function getACL() {
function getACL() {
// The top-level ACL only contains access information for the true
// owner of the calendar, so we need to add the information for the
// sharee.
$acl = parent::getACL();
$acl[] = array(
$acl[] = [
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
);
if (!$this->calendarInfo['{http://sabredav.org/ns}read-only']) {
$acl[] = array(
];
if ($this->calendarInfo['{http://sabredav.org/ns}read-only']) {
$acl[] = [
'privilege' => '{DAV:}write-properties',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
];
} else {
$acl[] = [
'privilege' => '{DAV:}write',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
);
];
}
return $acl;
}
/**
* This method returns the ACL's for calendar objects in this calendar.
* The result of this method automatically gets passed to the
* calendar-object nodes in the calendar.
*
* @return array
*/
function getChildACL() {
$acl = parent::getChildACL();
$acl[] = [
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
];
if (!$this->calendarInfo['{http://sabredav.org/ns}read-only']) {
$acl[] = [
'privilege' => '{DAV:}write',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
];
}
return $acl;
}
/**
* Returns the list of people whom this calendar is shared with.
*
@ -107,7 +139,7 @@ class SharedCalendar extends Calendar implements ISharedCalendar {
*
* @return array
*/
public function getShares() {
function getShares() {
return $this->caldavBackend->getShares($this->calendarInfo['id']);

View file

@ -0,0 +1,426 @@
<?php
namespace Sabre\CalDAV;
use Sabre\DAV;
use Sabre\DAV\Xml\Property\Href;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
/**
* This plugin implements support for caldav sharing.
*
* This spec is defined at:
* http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
*
* See:
* Sabre\CalDAV\Backend\SharingSupport for all the documentation.
*
* Note: This feature is experimental, and may change in between different
* SabreDAV versions.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class SharingPlugin extends DAV\ServerPlugin {
/**
* These are the various status constants used by sharing-messages.
*/
const STATUS_ACCEPTED = 1;
const STATUS_DECLINED = 2;
const STATUS_DELETED = 3;
const STATUS_NORESPONSE = 4;
const STATUS_INVALID = 5;
/**
* Reference to SabreDAV server object.
*
* @var Sabre\DAV\Server
*/
protected $server;
/**
* This method should return a list of server-features.
*
* This is for example 'versioning' and is added to the DAV: header
* in an OPTIONS response.
*
* @return array
*/
function getFeatures() {
return ['calendarserver-sharing'];
}
/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins
* using Sabre\DAV\Server::getPlugin
*
* @return string
*/
function getPluginName() {
return 'caldav-sharing';
}
/**
* This initializes the plugin.
*
* This function is called by Sabre\DAV\Server, after
* addPlugin is called.
*
* This method should set up the required event subscriptions.
*
* @param DAV\Server $server
* @return void
*/
function initialize(DAV\Server $server) {
$this->server = $server;
$server->resourceTypeMapping['Sabre\\CalDAV\\ISharedCalendar'] = '{' . Plugin::NS_CALENDARSERVER . '}shared';
array_push(
$this->server->protectedProperties,
'{' . Plugin::NS_CALENDARSERVER . '}invite',
'{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes',
'{' . Plugin::NS_CALENDARSERVER . '}shared-url'
);
$this->server->xml->elementMap['{' . Plugin::NS_CALENDARSERVER . '}share'] = 'Sabre\\CalDAV\\Xml\\Request\\Share';
$this->server->xml->elementMap['{' . Plugin::NS_CALENDARSERVER . '}invite-reply'] = 'Sabre\\CalDAV\\Xml\\Request\\InviteReply';
$this->server->on('propFind', [$this, 'propFindEarly']);
$this->server->on('propFind', [$this, 'propFindLate'], 150);
$this->server->on('propPatch', [$this, 'propPatch'], 40);
$this->server->on('method:POST', [$this, 'httpPost']);
}
/**
* This event is triggered when properties are requested for a certain
* node.
*
* This allows us to inject any properties early.
*
* @param DAV\PropFind $propFind
* @param DAV\INode $node
* @return void
*/
function propFindEarly(DAV\PropFind $propFind, DAV\INode $node) {
if ($node instanceof IShareableCalendar) {
$propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}invite', function() use ($node) {
return new Xml\Property\Invite(
$node->getShares()
);
});
}
if ($node instanceof ISharedCalendar) {
$propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}shared-url', function() use ($node) {
return new Href(
$node->getSharedUrl()
);
});
$propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}invite', function() use ($node) {
// Fetching owner information
$props = $this->server->getPropertiesForPath($node->getOwner(), [
'{http://sabredav.org/ns}email-address',
'{DAV:}displayname',
], 0);
$ownerInfo = [
'href' => $node->getOwner(),
];
if (isset($props[0][200])) {
// We're mapping the internal webdav properties to the
// elements caldav-sharing expects.
if (isset($props[0][200]['{http://sabredav.org/ns}email-address'])) {
$ownerInfo['href'] = 'mailto:' . $props[0][200]['{http://sabredav.org/ns}email-address'];
}
if (isset($props[0][200]['{DAV:}displayname'])) {
$ownerInfo['commonName'] = $props[0][200]['{DAV:}displayname'];
}
}
return new Xml\Property\Invite(
$node->getShares(),
$ownerInfo
);
});
}
}
/**
* This method is triggered *after* all properties have been retrieved.
* This allows us to inject the correct resourcetype for calendars that
* have been shared.
*
* @param DAV\PropFind $propFind
* @param DAV\INode $node
* @return void
*/
function propFindLate(DAV\PropFind $propFind, DAV\INode $node) {
if ($node instanceof IShareableCalendar) {
if ($rt = $propFind->get('{DAV:}resourcetype')) {
if (count($node->getShares()) > 0) {
$rt->add('{' . Plugin::NS_CALENDARSERVER . '}shared-owner');
}
}
$propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes', function() {
return new Xml\Property\AllowedSharingModes(true, false);
});
}
}
/**
* This method is trigged when a user attempts to update a node's
* properties.
*
* A previous draft of the sharing spec stated that it was possible to use
* PROPPATCH to remove 'shared-owner' from the resourcetype, thus unsharing
* the calendar.
*
* Even though this is no longer in the current spec, we keep this around
* because OS X 10.7 may still make use of this feature.
*
* @param string $path
* @param DAV\PropPatch $propPatch
* @return void
*/
function propPatch($path, DAV\PropPatch $propPatch) {
$node = $this->server->tree->getNodeForPath($path);
if (!$node instanceof IShareableCalendar)
return;
$propPatch->handle('{DAV:}resourcetype', function($value) use ($node) {
if ($value->is('{' . Plugin::NS_CALENDARSERVER . '}shared-owner')) return false;
$shares = $node->getShares();
$remove = [];
foreach ($shares as $share) {
$remove[] = $share['href'];
}
$node->updateShares([], $remove);
return true;
});
}
/**
* We intercept this to handle POST requests on calendars.
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @return null|bool
*/
function httpPost(RequestInterface $request, ResponseInterface $response) {
$path = $request->getPath();
// Only handling xml
$contentType = $request->getHeader('Content-Type');
if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false)
return;
// Making sure the node exists
try {
$node = $this->server->tree->getNodeForPath($path);
} catch (DAV\Exception\NotFound $e) {
return;
}
$requestBody = $request->getBodyAsString();
// If this request handler could not deal with this POST request, it
// will return 'null' and other plugins get a chance to handle the
// request.
//
// However, we already requested the full body. This is a problem,
// because a body can only be read once. This is why we preemptively
// re-populated the request body with the existing data.
$request->setBody($requestBody);
$message = $this->server->xml->parse($requestBody, $request->getUrl(), $documentType);
switch ($documentType) {
// Dealing with the 'share' document, which modified invitees on a
// calendar.
case '{' . Plugin::NS_CALENDARSERVER . '}share' :
// We can only deal with IShareableCalendar objects
if (!$node instanceof IShareableCalendar) {
return;
}
$this->server->transactionType = 'post-calendar-share';
// Getting ACL info
$acl = $this->server->getPlugin('acl');
// If there's no ACL support, we allow everything
if ($acl) {
$acl->checkPrivileges($path, '{DAV:}write');
}
$node->updateShares($message->set, $message->remove);
$response->setStatus(200);
// Adding this because sending a response body may cause issues,
// and I wanted some type of indicator the response was handled.
$response->setHeader('X-Sabre-Status', 'everything-went-well');
// Breaking the event chain
return false;
// The invite-reply document is sent when the user replies to an
// invitation of a calendar share.
case '{' . Plugin::NS_CALENDARSERVER . '}invite-reply' :
// This only works on the calendar-home-root node.
if (!$node instanceof CalendarHome) {
return;
}
$this->server->transactionType = 'post-invite-reply';
// Getting ACL info
$acl = $this->server->getPlugin('acl');
// If there's no ACL support, we allow everything
if ($acl) {
$acl->checkPrivileges($path, '{DAV:}write');
}
$url = $node->shareReply(
$message->href,
$message->status,
$message->calendarUri,
$message->inReplyTo,
$message->summary
);
$response->setStatus(200);
// Adding this because sending a response body may cause issues,
// and I wanted some type of indicator the response was handled.
$response->setHeader('X-Sabre-Status', 'everything-went-well');
if ($url) {
$writer = $this->server->xml->getWriter($this->server->getBaseUri());
$writer->openMemory();
$writer->startDocument();
$writer->startElement('{' . Plugin::NS_CALENDARSERVER . '}shared-as');
$writer->write(new Href($url));
$writer->endElement();
$response->setHeader('Content-Type', 'application/xml');
$response->setBody($writer->outputMemory());
}
// Breaking the event chain
return false;
case '{' . Plugin::NS_CALENDARSERVER . '}publish-calendar' :
// We can only deal with IShareableCalendar objects
if (!$node instanceof IShareableCalendar) {
return;
}
$this->server->transactionType = 'post-publish-calendar';
// Getting ACL info
$acl = $this->server->getPlugin('acl');
// If there's no ACL support, we allow everything
if ($acl) {
$acl->checkPrivileges($path, '{DAV:}write');
}
$node->setPublishStatus(true);
// iCloud sends back the 202, so we will too.
$response->setStatus(202);
// Adding this because sending a response body may cause issues,
// and I wanted some type of indicator the response was handled.
$response->setHeader('X-Sabre-Status', 'everything-went-well');
// Breaking the event chain
return false;
case '{' . Plugin::NS_CALENDARSERVER . '}unpublish-calendar' :
// We can only deal with IShareableCalendar objects
if (!$node instanceof IShareableCalendar) {
return;
}
$this->server->transactionType = 'post-unpublish-calendar';
// Getting ACL info
$acl = $this->server->getPlugin('acl');
// If there's no ACL support, we allow everything
if ($acl) {
$acl->checkPrivileges($path, '{DAV:}write');
}
$node->setPublishStatus(false);
$response->setStatus(200);
// Adding this because sending a response body may cause issues,
// and I wanted some type of indicator the response was handled.
$response->setHeader('X-Sabre-Status', 'everything-went-well');
// Breaking the event chain
return false;
}
}
/**
* Returns a bunch of meta-data about the plugin.
*
* Providing this information is optional, and is mainly displayed by the
* Browser plugin.
*
* The description key in the returned array may contain html and will not
* be sanitized.
*
* @return array
*/
function getPluginInfo() {
return [
'name' => $this->getPluginName(),
'description' => 'Adds support for caldav-sharing.',
'link' => 'http://sabre.io/dav/caldav-sharing/',
];
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace Sabre\CalDAV\Subscriptions;
use Sabre\DAV\ICollection;
use Sabre\DAV\IProperties;
/**
* ISubscription
*
* Nodes implementing this interface represent calendar subscriptions.
*
* The subscription node doesn't do much, other than returning and updating
* subscription-related properties.
*
* The following properties should be supported:
*
* 1. {DAV:}displayname
* 2. {http://apple.com/ns/ical/}refreshrate
* 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
* should not be stripped).
* 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
* should not be stripped).
* 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
* attachments should not be stripped).
* 6. {http://calendarserver.org/ns/}source (Must be a
* Sabre\DAV\Property\Href).
* 7. {http://apple.com/ns/ical/}calendar-color
* 8. {http://apple.com/ns/ical/}calendar-order
*
* It is recommended to support every property.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface ISubscription extends ICollection, IProperties {
}

View file

@ -0,0 +1,120 @@
<?php
namespace Sabre\CalDAV\Subscriptions;
use Sabre\DAV\INode;
use Sabre\DAV\PropFind;
use Sabre\DAV\ServerPlugin;
use Sabre\DAV\Server;
/**
* This plugin adds calendar-subscription support to your CalDAV server.
*
* Some clients support 'managed subscriptions' server-side. This is basically
* a list of subscription urls a user is using.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Plugin extends ServerPlugin {
/**
* This initializes the plugin.
*
* This function is called by Sabre\DAV\Server, after
* addPlugin is called.
*
* This method should set up the required event subscriptions.
*
* @param Server $server
* @return void
*/
function initialize(Server $server) {
$server->resourceTypeMapping['Sabre\\CalDAV\\Subscriptions\\ISubscription'] =
'{http://calendarserver.org/ns/}subscribed';
$server->xml->elementMap['{http://calendarserver.org/ns/}source'] =
'Sabre\\DAV\\Xml\\Property\\Href';
$server->on('propFind', [$this, 'propFind'], 150);
}
/**
* This method should return a list of server-features.
*
* This is for example 'versioning' and is added to the DAV: header
* in an OPTIONS response.
*
* @return array
*/
function getFeatures() {
return ['calendarserver-subscribed'];
}
/**
* Triggered after properties have been fetched.
*
* @param PropFind $propFind
* @param INode $node
* @return void
*/
function propFind(PropFind $propFind, INode $node) {
// There's a bunch of properties that must appear as a self-closing
// xml-element. This event handler ensures that this will be the case.
$props = [
'{http://calendarserver.org/ns/}subscribed-strip-alarms',
'{http://calendarserver.org/ns/}subscribed-strip-attachments',
'{http://calendarserver.org/ns/}subscribed-strip-todos',
];
foreach ($props as $prop) {
if ($propFind->getStatus($prop) === 200) {
$propFind->set($prop, '', 200);
}
}
}
/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins
* using \Sabre\DAV\Server::getPlugin
*
* @return string
*/
function getPluginName() {
return 'subscriptions';
}
/**
* Returns a bunch of meta-data about the plugin.
*
* Providing this information is optional, and is mainly displayed by the
* Browser plugin.
*
* The description key in the returned array may contain html and will not
* be sanitized.
*
* @return array
*/
function getPluginInfo() {
return [
'name' => $this->getPluginName(),
'description' => 'This plugin allows users to store iCalendar subscriptions in their calendar-home.',
'link' => null,
];
}
}

View file

@ -0,0 +1,274 @@
<?php
namespace Sabre\CalDAV\Subscriptions;
use Sabre\DAV\Collection;
use Sabre\DAV\Xml\Property\Href;
use Sabre\DAV\PropPatch;
use Sabre\DAV\Exception\MethodNotAllowed;
use Sabre\DAVACL\IACL;
use Sabre\CalDAV\Backend\SubscriptionSupport;
/**
* Subscription Node
*
* This node represents a subscription.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Subscription extends Collection implements ISubscription, IACL {
/**
* caldavBackend
*
* @var SupportsSubscriptions
*/
protected $caldavBackend;
/**
* subscriptionInfo
*
* @var array
*/
protected $subscriptionInfo;
/**
* Constructor
*
* @param SubscriptionSupport $caldavBackend
* @param array $calendarInfo
*/
function __construct(SubscriptionSupport $caldavBackend, array $subscriptionInfo) {
$this->caldavBackend = $caldavBackend;
$this->subscriptionInfo = $subscriptionInfo;
$required = [
'id',
'uri',
'principaluri',
'source',
];
foreach ($required as $r) {
if (!isset($subscriptionInfo[$r])) {
throw new \InvalidArgumentException('The ' . $r . ' field is required when creating a subscription node');
}
}
}
/**
* Returns the name of the node.
*
* This is used to generate the url.
*
* @return string
*/
function getName() {
return $this->subscriptionInfo['uri'];
}
/**
* Returns the last modification time
*
* @return int
*/
function getLastModified() {
if (isset($this->subscriptionInfo['lastmodified'])) {
return $this->subscriptionInfo['lastmodified'];
}
}
/**
* Deletes the current node
*
* @return void
*/
function delete() {
$this->caldavBackend->deleteSubscription(
$this->subscriptionInfo['id']
);
}
/**
* Returns an array with all the child nodes
*
* @return DAV\INode[]
*/
function getChildren() {
return [];
}
/**
* Updates properties on this node.
*
* This method received a PropPatch object, which contains all the
* information about the update.
*
* To update specific properties, call the 'handle' method on this object.
* Read the PropPatch documentation for more information.
*
* @param PropPatch $propPatch
* @return void
*/
function propPatch(PropPatch $propPatch) {
return $this->caldavBackend->updateSubscription(
$this->subscriptionInfo['id'],
$propPatch
);
}
/**
* Returns a list of properties for this nodes.
*
* The properties list is a list of propertynames the client requested,
* encoded in clark-notation {xmlnamespace}tagname.
*
* If the array is empty, it means 'all properties' were requested.
*
* Note that it's fine to liberally give properties back, instead of
* conforming to the list of requested properties.
* The Server class will filter out the extra.
*
* @param array $properties
* @return void
*/
function getProperties($properties) {
$r = [];
foreach ($properties as $prop) {
switch ($prop) {
case '{http://calendarserver.org/ns/}source' :
$r[$prop] = new Href($this->subscriptionInfo['source'], false);
break;
default :
if (array_key_exists($prop, $this->subscriptionInfo)) {
$r[$prop] = $this->subscriptionInfo[$prop];
}
break;
}
}
return $r;
}
/**
* Returns the owner principal.
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
function getOwner() {
return $this->subscriptionInfo['principaluri'];
}
/**
* Returns a group principal.
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
function getGroup() {
return null;
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
function getACL() {
return [
[
'privilege' => '{DAV:}read',
'principal' => $this->getOwner(),
'protected' => true,
],
[
'privilege' => '{DAV:}write',
'principal' => $this->getOwner(),
'protected' => true,
],
[
'privilege' => '{DAV:}read',
'principal' => $this->getOwner() . '/calendar-proxy-write',
'protected' => true,
],
[
'privilege' => '{DAV:}write',
'principal' => $this->getOwner() . '/calendar-proxy-write',
'protected' => true,
],
[
'privilege' => '{DAV:}read',
'principal' => $this->getOwner() . '/calendar-proxy-read',
'protected' => true,
]
];
}
/**
* Updates the ACL.
*
* This method will receive a list of new ACE's.
*
* @param array $acl
* @return void
*/
function setACL(array $acl) {
throw new MethodNotAllowed('Changing ACL is not yet supported');
}
/**
* Returns the list of supported privileges for this node.
*
* The returned data structure is a list of nested privileges.
* See \Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
* standard structure.
*
* If null is returned from this method, the default privilege set is used,
* which is fine for most common usecases.
*
* @return array|null
*/
function getSupportedPrivilegeSet() {
return null;
}
}

Some files were not shown because too many files have changed in this diff Show more