Merge pull request #444 from git-marijus/dev

Merge branch sabre32 into dev
This commit is contained in:
hubzilla 2016-06-30 09:34:22 +10:00 committed by GitHub
commit 05aba0b4dd
252 changed files with 9169 additions and 7369 deletions

View File

@ -219,7 +219,7 @@ class Browser extends DAV\Browser\Plugin {
$output = '';
if ($this->enablePost) {
$this->server->emit('onHTMLActionsPanel', array($parent, &$output));
$this->server->emit('onHTMLActionsPanel', array($parent, &$output, $path));
}
$html .= replace_macros(get_markup_template('cloud.tpl'), array(
@ -266,7 +266,7 @@ class Browser extends DAV\Browser\Plugin {
* @param \Sabre\DAV\INode $node
* @param string &$output
*/
public function htmlActionsPanel(DAV\INode $node, &$output) {
public function htmlActionsPanel(DAV\INode $node, &$output, $path) {
if (! $node instanceof DAV\ICollection)
return;

View File

@ -1909,4 +1909,4 @@ function get_attach_binname($s) {
$p = substr($p,strpos($p,'/')+1);
}
return $p;
}
}

2
vendor/autoload.php vendored
View File

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

View File

@ -7,10 +7,10 @@ $baseDir = dirname($vendorDir);
return array(
'383eaff206634a77a1be54e64e6459c7' => $vendorDir . '/sabre/uri/lib/functions.php',
'3569eecfeed3bcf0bad3c998a494ecb8' => $vendorDir . '/sabre/xml/lib/Deserializer/functions.php',
'93aa591bc4ca510c520999e34229ee79' => $vendorDir . '/sabre/xml/lib/Serializer/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,4 +6,5 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Psr\\Log\\' => array($vendorDir . '/psr/log'),
);

View File

@ -2,7 +2,7 @@
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit85a1cefa95be2f464cf7f947cbc4c785
class ComposerAutoloaderInit02c7a5bb99a87a4c8dbf069d69b1a15c
{
private static $loader;
@ -19,15 +19,15 @@ class ComposerAutoloaderInit85a1cefa95be2f464cf7f947cbc4c785
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit85a1cefa95be2f464cf7f947cbc4c785', 'loadClassLoader'), true, true);
spl_autoload_register(array('ComposerAutoloaderInit02c7a5bb99a87a4c8dbf069d69b1a15c', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit85a1cefa95be2f464cf7f947cbc4c785', 'loadClassLoader'));
spl_autoload_unregister(array('ComposerAutoloaderInit02c7a5bb99a87a4c8dbf069d69b1a15c', '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));
call_user_func(\Composer\Autoload\ComposerStaticInit02c7a5bb99a87a4c8dbf069d69b1a15c::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
@ -48,19 +48,19 @@ class ComposerAutoloaderInit85a1cefa95be2f464cf7f947cbc4c785
$loader->register(true);
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInit85a1cefa95be2f464cf7f947cbc4c785::$files;
$includeFiles = Composer\Autoload\ComposerStaticInit02c7a5bb99a87a4c8dbf069d69b1a15c::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire85a1cefa95be2f464cf7f947cbc4c785($fileIdentifier, $file);
composerRequire02c7a5bb99a87a4c8dbf069d69b1a15c($fileIdentifier, $file);
}
return $loader;
}
}
function composerRequire85a1cefa95be2f464cf7f947cbc4c785($fileIdentifier, $file)
function composerRequire02c7a5bb99a87a4c8dbf069d69b1a15c($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
require $file;

View File

@ -4,15 +4,15 @@
namespace Composer\Autoload;
class ComposerStaticInit85a1cefa95be2f464cf7f947cbc4c785
class ComposerStaticInit02c7a5bb99a87a4c8dbf069d69b1a15c
{
public static $files = array (
'383eaff206634a77a1be54e64e6459c7' => __DIR__ . '/..' . '/sabre/uri/lib/functions.php',
'3569eecfeed3bcf0bad3c998a494ecb8' => __DIR__ . '/..' . '/sabre/xml/lib/Deserializer/functions.php',
'93aa591bc4ca510c520999e34229ee79' => __DIR__ . '/..' . '/sabre/xml/lib/Serializer/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',
);
@ -70,11 +70,22 @@ class ComposerStaticInit85a1cefa95be2f464cf7f947cbc4c785
),
);
public static $prefixesPsr0 = array (
'P' =>
array (
'Psr\\Log\\' =>
array (
0 => __DIR__ . '/..' . '/psr/log',
),
),
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit85a1cefa95be2f464cf7f947cbc4c785::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit85a1cefa95be2f464cf7f947cbc4c785::$prefixDirsPsr4;
$loader->prefixLengthsPsr4 = ComposerStaticInit02c7a5bb99a87a4c8dbf069d69b1a15c::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit02c7a5bb99a87a4c8dbf069d69b1a15c::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInit02c7a5bb99a87a4c8dbf069d69b1a15c::$prefixesPsr0;
}, null, ClassLoader::class);
}

View File

@ -52,135 +52,19 @@
"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",
"version": "1.4.2",
"version_normalized": "1.4.2.0",
"source": {
"type": "git",
"url": "https://github.com/fruux/sabre-xml.git",
"reference": "59998046db252634259a878baf1af18159f508f3"
"reference": "f48d98c22a4a4bef76cabb5968ffaddbb2bb593e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fruux/sabre-xml/zipball/59998046db252634259a878baf1af18159f508f3",
"reference": "59998046db252634259a878baf1af18159f508f3",
"url": "https://api.github.com/repos/fruux/sabre-xml/zipball/f48d98c22a4a4bef76cabb5968ffaddbb2bb593e",
"reference": "f48d98c22a4a4bef76cabb5968ffaddbb2bb593e",
"shasum": ""
},
"require": {
@ -195,7 +79,7 @@
"phpunit/phpunit": "*",
"sabre/cs": "~0.0.2"
},
"time": "2016-03-12 22:23:16",
"time": "2016-05-19 21:56:49",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -331,18 +215,174 @@
]
},
{
"name": "sabre/dav",
"version": "3.1.3",
"version_normalized": "3.1.3.0",
"name": "sabre/event",
"version": "3.0.0",
"version_normalized": "3.0.0.0",
"source": {
"type": "git",
"url": "https://github.com/fruux/sabre-dav.git",
"reference": "8a266c7b5e140da79529414b9cde2a2d058b536b"
"url": "https://github.com/fruux/sabre-event.git",
"reference": "831d586f5a442dceacdcf5e9c4c36a4db99a3534"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fruux/sabre-dav/zipball/8a266c7b5e140da79529414b9cde2a2d058b536b",
"reference": "8a266c7b5e140da79529414b9cde2a2d058b536b",
"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": "psr/log",
"version": "1.0.0",
"version_normalized": "1.0.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "fe0936ee26643249e916849d48e3a51d5f5e278b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b",
"reference": "fe0936ee26643249e916849d48e3a51d5f5e278b",
"shasum": ""
},
"time": "2012-12-21 11:40:51",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-0": {
"Psr\\Log\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"keywords": [
"log",
"psr",
"psr-3"
]
},
{
"name": "sabre/dav",
"version": "3.2.0",
"version_normalized": "3.2.0.0",
"source": {
"type": "git",
"url": "https://github.com/fruux/sabre-dav.git",
"reference": "5b9737cc2f0182e368d14c80df7f6b2d77dc1457"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fruux/sabre-dav/zipball/5b9737cc2f0182e368d14c80df7f6b2d77dc1457",
"reference": "5b9737cc2f0182e368d14c80df7f6b2d77dc1457",
"shasum": ""
},
"require": {
@ -356,14 +396,16 @@
"ext-spl": "*",
"lib-libxml": ">=2.7.0",
"php": ">=5.5.0",
"psr/log": "^1.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"
"sabre/uri": "^1.0.1",
"sabre/vobject": "^4.1.0",
"sabre/xml": "^1.4.0"
},
"require-dev": {
"evert/phpdoc-md": "~0.1.0",
"monolog/monolog": "^1.18",
"phpunit/phpunit": "> 4.8, <=6.0.0",
"sabre/cs": "~0.0.5"
},
@ -371,7 +413,7 @@
"ext-curl": "*",
"ext-pdo": "*"
},
"time": "2016-04-07 01:02:57",
"time": "2016-06-28 02:44:05",
"bin": [
"bin/sabredav",
"bin/naturalselection"

1
vendor/psr/log/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
vendor

19
vendor/psr/log/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2012 PHP Framework Interoperability Group
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.

View File

@ -0,0 +1,120 @@
<?php
namespace Psr\Log;
/**
* This is a simple Logger implementation that other Loggers can inherit from.
*
* It simply delegates all log-level-specific methods to the `log` method to
* reduce boilerplate code that a simple Logger that does the same thing with
* messages regardless of the error level has to implement.
*/
abstract class AbstractLogger implements LoggerInterface
{
/**
* System is unusable.
*
* @param string $message
* @param array $context
* @return null
*/
public function emergency($message, array $context = array())
{
$this->log(LogLevel::EMERGENCY, $message, $context);
}
/**
* Action must be taken immediately.
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*
* @param string $message
* @param array $context
* @return null
*/
public function alert($message, array $context = array())
{
$this->log(LogLevel::ALERT, $message, $context);
}
/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*
* @param string $message
* @param array $context
* @return null
*/
public function critical($message, array $context = array())
{
$this->log(LogLevel::CRITICAL, $message, $context);
}
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*
* @param string $message
* @param array $context
* @return null
*/
public function error($message, array $context = array())
{
$this->log(LogLevel::ERROR, $message, $context);
}
/**
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*
* @param string $message
* @param array $context
* @return null
*/
public function warning($message, array $context = array())
{
$this->log(LogLevel::WARNING, $message, $context);
}
/**
* Normal but significant events.
*
* @param string $message
* @param array $context
* @return null
*/
public function notice($message, array $context = array())
{
$this->log(LogLevel::NOTICE, $message, $context);
}
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*
* @param string $message
* @param array $context
* @return null
*/
public function info($message, array $context = array())
{
$this->log(LogLevel::INFO, $message, $context);
}
/**
* Detailed debug information.
*
* @param string $message
* @param array $context
* @return null
*/
public function debug($message, array $context = array())
{
$this->log(LogLevel::DEBUG, $message, $context);
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace Psr\Log;
class InvalidArgumentException extends \InvalidArgumentException
{
}

18
vendor/psr/log/Psr/Log/LogLevel.php vendored Normal file
View File

@ -0,0 +1,18 @@
<?php
namespace Psr\Log;
/**
* Describes log levels
*/
class LogLevel
{
const EMERGENCY = 'emergency';
const ALERT = 'alert';
const CRITICAL = 'critical';
const ERROR = 'error';
const WARNING = 'warning';
const NOTICE = 'notice';
const INFO = 'info';
const DEBUG = 'debug';
}

View File

@ -0,0 +1,17 @@
<?php
namespace Psr\Log;
/**
* Describes a logger-aware instance
*/
interface LoggerAwareInterface
{
/**
* Sets a logger instance on the object
*
* @param LoggerInterface $logger
* @return null
*/
public function setLogger(LoggerInterface $logger);
}

View File

@ -0,0 +1,22 @@
<?php
namespace Psr\Log;
/**
* Basic Implementation of LoggerAwareInterface.
*/
trait LoggerAwareTrait
{
/** @var LoggerInterface */
protected $logger;
/**
* Sets a logger.
*
* @param LoggerInterface $logger
*/
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;
}
}

View File

@ -0,0 +1,114 @@
<?php
namespace Psr\Log;
/**
* Describes a logger instance
*
* The message MUST be a string or object implementing __toString().
*
* The message MAY contain placeholders in the form: {foo} where foo
* will be replaced by the context data in key "foo".
*
* The context array can contain arbitrary data, the only assumption that
* can be made by implementors is that if an Exception instance is given
* to produce a stack trace, it MUST be in a key named "exception".
*
* See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
* for the full interface specification.
*/
interface LoggerInterface
{
/**
* System is unusable.
*
* @param string $message
* @param array $context
* @return null
*/
public function emergency($message, array $context = array());
/**
* Action must be taken immediately.
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*
* @param string $message
* @param array $context
* @return null
*/
public function alert($message, array $context = array());
/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*
* @param string $message
* @param array $context
* @return null
*/
public function critical($message, array $context = array());
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*
* @param string $message
* @param array $context
* @return null
*/
public function error($message, array $context = array());
/**
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*
* @param string $message
* @param array $context
* @return null
*/
public function warning($message, array $context = array());
/**
* Normal but significant events.
*
* @param string $message
* @param array $context
* @return null
*/
public function notice($message, array $context = array());
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*
* @param string $message
* @param array $context
* @return null
*/
public function info($message, array $context = array());
/**
* Detailed debug information.
*
* @param string $message
* @param array $context
* @return null
*/
public function debug($message, array $context = array());
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
* @return null
*/
public function log($level, $message, array $context = array());
}

131
vendor/psr/log/Psr/Log/LoggerTrait.php vendored Normal file
View File

@ -0,0 +1,131 @@
<?php
namespace Psr\Log;
/**
* This is a simple Logger trait that classes unable to extend AbstractLogger
* (because they extend another class, etc) can include.
*
* It simply delegates all log-level-specific methods to the `log` method to
* reduce boilerplate code that a simple Logger that does the same thing with
* messages regardless of the error level has to implement.
*/
trait LoggerTrait
{
/**
* System is unusable.
*
* @param string $message
* @param array $context
* @return null
*/
public function emergency($message, array $context = array())
{
$this->log(LogLevel::EMERGENCY, $message, $context);
}
/**
* Action must be taken immediately.
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*
* @param string $message
* @param array $context
* @return null
*/
public function alert($message, array $context = array())
{
$this->log(LogLevel::ALERT, $message, $context);
}
/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*
* @param string $message
* @param array $context
* @return null
*/
public function critical($message, array $context = array())
{
$this->log(LogLevel::CRITICAL, $message, $context);
}
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*
* @param string $message
* @param array $context
* @return null
*/
public function error($message, array $context = array())
{
$this->log(LogLevel::ERROR, $message, $context);
}
/**
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*
* @param string $message
* @param array $context
* @return null
*/
public function warning($message, array $context = array())
{
$this->log(LogLevel::WARNING, $message, $context);
}
/**
* Normal but significant events.
*
* @param string $message
* @param array $context
* @return null
*/
public function notice($message, array $context = array())
{
$this->log(LogLevel::NOTICE, $message, $context);
}
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*
* @param string $message
* @param array $context
* @return null
*/
public function info($message, array $context = array())
{
$this->log(LogLevel::INFO, $message, $context);
}
/**
* Detailed debug information.
*
* @param string $message
* @param array $context
* @return null
*/
public function debug($message, array $context = array())
{
$this->log(LogLevel::DEBUG, $message, $context);
}
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
* @return null
*/
abstract public function log($level, $message, array $context = array());
}

27
vendor/psr/log/Psr/Log/NullLogger.php vendored Normal file
View File

@ -0,0 +1,27 @@
<?php
namespace Psr\Log;
/**
* This Logger can be used to avoid conditional log calls
*
* Logging should always be optional, and if no logger is provided to your
* library creating a NullLogger instance to have something to throw logs at
* is a good way to avoid littering your code with `if ($this->logger) { }`
* blocks.
*/
class NullLogger extends AbstractLogger
{
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
* @return null
*/
public function log($level, $message, array $context = array())
{
// noop
}
}

View File

@ -0,0 +1,116 @@
<?php
namespace Psr\Log\Test;
use Psr\Log\LogLevel;
/**
* Provides a base test class for ensuring compliance with the LoggerInterface
*
* Implementors can extend the class and implement abstract methods to run this as part of their test suite
*/
abstract class LoggerInterfaceTest extends \PHPUnit_Framework_TestCase
{
/**
* @return LoggerInterface
*/
abstract function getLogger();
/**
* This must return the log messages in order with a simple formatting: "<LOG LEVEL> <MESSAGE>"
*
* Example ->error('Foo') would yield "error Foo"
*
* @return string[]
*/
abstract function getLogs();
public function testImplements()
{
$this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger());
}
/**
* @dataProvider provideLevelsAndMessages
*/
public function testLogsAtAllLevels($level, $message)
{
$logger = $this->getLogger();
$logger->{$level}($message, array('user' => 'Bob'));
$logger->log($level, $message, array('user' => 'Bob'));
$expected = array(
$level.' message of level '.$level.' with context: Bob',
$level.' message of level '.$level.' with context: Bob',
);
$this->assertEquals($expected, $this->getLogs());
}
public function provideLevelsAndMessages()
{
return array(
LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'),
LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'),
LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'),
LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'),
LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'),
LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'),
LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'),
LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'),
);
}
/**
* @expectedException Psr\Log\InvalidArgumentException
*/
public function testThrowsOnInvalidLevel()
{
$logger = $this->getLogger();
$logger->log('invalid level', 'Foo');
}
public function testContextReplacement()
{
$logger = $this->getLogger();
$logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar'));
$expected = array('info {Message {nothing} Bob Bar a}');
$this->assertEquals($expected, $this->getLogs());
}
public function testObjectCastToString()
{
$dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString'));
$dummy->expects($this->once())
->method('__toString')
->will($this->returnValue('DUMMY'));
$this->getLogger()->warning($dummy);
}
public function testContextCanContainAnything()
{
$context = array(
'bool' => true,
'null' => null,
'string' => 'Foo',
'int' => 0,
'float' => 0.5,
'nested' => array('with object' => new DummyTest),
'object' => new \DateTime,
'resource' => fopen('php://memory', 'r'),
);
$this->getLogger()->warning('Crazy context data', $context);
}
public function testContextExceptionKeyCanBeExceptionOrOtherValues()
{
$this->getLogger()->warning('Random message', array('exception' => 'oops'));
$this->getLogger()->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail')));
}
}
class DummyTest
{
}

45
vendor/psr/log/README.md vendored Normal file
View File

@ -0,0 +1,45 @@
PSR Log
=======
This repository holds all interfaces/classes/traits related to
[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md).
Note that this is not a logger of its own. It is merely an interface that
describes a logger. See the specification for more details.
Usage
-----
If you need a logger, you can use the interface like this:
```php
<?php
use Psr\Log\LoggerInterface;
class Foo
{
private $logger;
public function __construct(LoggerInterface $logger = null)
{
$this->logger = $logger;
}
public function doSomething()
{
if ($this->logger) {
$this->logger->info('Doing work');
}
// do something useful
}
}
```
You can then pick one of the implementations of the interface to get a logger.
If you want to implement the interface, you can require this package and
implement `Psr\Log\LoggerInterface` in your code. Please read the
[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
for details.

View File

@ -3,12 +3,6 @@ php:
- 5.5
- 5.6
- 7
- hhvm
matrix:
fast_finish: true
allow_failures:
- php: hhvm
env:
matrix:
@ -17,17 +11,25 @@ env:
services:
- mysql
- postgresql
sudo: false
cache: vendor
before_script:
- mysql -e 'create database sabredav'
- mysql -e 'create database sabredav_test'
- psql -c "create database sabredav_test" -U postgres
- psql -c "create user sabredav with PASSWORD 'sabredav';GRANT ALL PRIVILEGES ON DATABASE sabredav_test TO sabredav" -U postgres
- phpenv config-rm xdebug.ini; true
# - composer self-update
- composer update --prefer-source $LOWEST_DEPS
- composer update --prefer-dist $LOWEST_DEPS
# addons:
# postgresql: "9.5"
script:
- ./bin/phpunit --configuration tests/phpunit.xml $TEST_DEPS
- ./bin/phpunit --configuration tests/phpunit.xml.dist $TEST_DEPS
- ./bin/sabre-cs-fixer fix lib/ --dry-run --diff
cache:
directories:
- $HOME/.composer/cache

View File

@ -1,6 +1,106 @@
ChangeLog
=========
3.2.0 (2016-06-27)
------------------
* The default ACL rules allow an unauthenticated user to read information
about nodes that don't have their own ACL defined. This was a security
problem.
* The zip release ships with [sabre/vobject 4.1.0][vobj],
[sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
[sabre/uri 1.1.0][uri] and [sabre/xml 1.4.2][xml].
3.2.0-beta1 (2016-05-20)
------------------------
* #833: Calendars throw exceptions when the sharing plugin is not enabled.
* #834: Return vCards exactly as they were stored if we don't need to convert
in between versions.
* The zip release ships with [sabre/vobject 4.1.0][vobj],
[sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
[sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml].
3.2.0-alpha1 (2016-05-09)
-------------------------
* Database changes for CalDAV. If you are using the CalDAV PDO backends, you
must migrate. Run `./bin/migrateto32.php` for more info.
* Support for WebDAV Resource Sharing, an upcoming standard.
* Added support for sharing in the CalDAV PDO backend! Users can now invite
others to their calendar and give them read/read-write access!
* #397: Support for PSR-3. You can now log exceptions with your favourite
psr3-compatible logging tool.
* #825: Actual proper, tested support for PostgreSQL. We require version 9.5.
* Removed database migration script for sabre/dav 1.7. To update from that
version you now first need to update to sabre/dav 3.1.
* Removed deprecated function: `Sabre\DAV\Auth\Plugin::getCurrentUser()`.
* #774: Fixes for getting free disk space on Windows.
* #803: Major changes in the sharing API. If you were using an old sabre/dav
sharing api, head to the website for more detailed migration notes.
* #657: Support for optional auth using `{DAV:}unauthorized` and `{DAV:}all`
privileges. This allows you to assign a privilege to a resource, allowing
non-authenticated users to access it. For instance, this could allow you
to create a public read-only collection.
* #812 #814: ICS/VCF exporter now includes a more useful filename in its
`Content-Disposition` header. (@Xenopathic).
* #801: BC break: If you were using the `Href` object before, it's behavior
now changed a bit, and `LocalHref` was added to replace the old, default
behavior of `Href`. See the migration doc for more info.
* Removed `Sabre\DAVACL\Plugin::$allowAccessToNodesWithoutACL` setting.
Instead, you can provide a set of default ACL rules with
`Sabre\DAVACL\Plugin::setDefaultAcl()`.
* Introduced `Sabre\DAVACL\ACLTrait` which contains a default implementation
of `Sabre\DAV\IACL` with some sane defaults. We're using this trait all over
the place now, reducing the amount of boilerplate.
* Plugins can now control the "Supported Privilege Set".
* Added Sharing, ICSExport and VCFExport plugins to `groupwareserver.php`
example.
* The `{DAV:}all` privilege is now no longer abstract, so it can be assigned
directly. We're using the `{DAV:}all` privilege now in a lot of cases where
we before assigned both `{DAV:}read` and `{DAV:}write`.
* Resources that are not collections no longer support the `{DAV:}bind` and
`{DAV:}unbind` privileges.
* Corrected the CalDAV-scheduling related privileges.
* Doing an `UNLOCK` no longer requires the `{DAV:}write-content` privilege.
* Added a new `getPrincipalByUri` plugin event. Allowing plugins to request
quickly where a principal lives on a server.
* Renamed `phpunit.xml` to `phpunit.xml.dist` to make local modifications easy.
* Functionality from `IShareableCalendar` is merged into `ISharedCalendar`.
* #751: Fixed XML responses from failing `MKCOL` requests.
* #600: Support for `principal-match` ACL `REPORT`.
* #599: Support for `acl-principal-prop-set` ACL `REPORT`.
* #798: Added an index on `firstoccurence` field in MySQL CalDAV backend. This
should speed up common calendar-query requests.
* #759: DAV\Client is now able to actually correctly resolve relative urls.
* #671: We are no longer checking the `read-free-busy` privilege on individual
calendars during freebusy operations in the scheduling plugin. Instead, we
check the `schedule-query-freebusy` privilege on the target users' inbox,
which validates access for the entire account, per the spec.
* The zip release ships with [sabre/vobject 4.1.0][vobj],
[sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
[sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml].
3.1.5 (????-??-??)
------------------
* Fixed: Creating a new calendar on some MySQL configurations caused an error.
3.1.4 (2016-05-28)
------------------
* #834: Backport from `master`: Return vCards exactly as they were stored if
we don't need to convert in between versions. This should speed up many
large addressbook syncs sometimes up to 50%.
* The zip release ships with [sabre/vobject 4.1.0][vobj],
[sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
[sabre/uri 1.1.0][uri] and [sabre/xml 1.4.2][xml].
3.1.3 (2016-04-06)
------------------

View File

@ -1,10 +1,10 @@
![sabre's logo](http://sabre.io/img/logo.png) SabreDAV
======================================================
![sabre's logo](http://sabre.io/img/logo.png) sabre/dav
=======================================================
Introduction
------------
SabreDAV is the most popular WebDAV framework for PHP. Use it to create WebDAV, CalDAV and CardDAV servers.
sabre/dav is the most popular WebDAV framework for PHP. Use it to create WebDAV, CalDAV and CardDAV servers.
Full documentation can be found on the website:
@ -16,6 +16,7 @@ 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.1 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=3.0)](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 |
@ -23,6 +24,12 @@ Build status
| 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 |
Documentation
-------------
* [Introduction](http://sabre.io/dav/).
* [Installation](http://sabre.io/dav/install/).
Made at fruux
-------------

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

@ -110,7 +110,7 @@ function test() {
echo " Running all unittests.\n";
echo " This may take a while.\n\n";
system(__DIR__ . '/phpunit --configuration ' . $baseDir . '/tests/phpunit.xml --stop-on-failure', $code);
system(__DIR__ . '/phpunit --configuration ' . $baseDir . '/tests/phpunit.xml.dist --stop-on-failure', $code);
if ($code != 0) {
echo "PHPUnit reported error code $code\n";
die(1);

0
vendor/sabre/dav/bin/googlecode_upload.py vendored Executable file → Normal file
View File

View File

@ -1,284 +0,0 @@
#!/usr/bin/env php
<?php
echo "SabreDAV migrate script for version 1.7\n";
if ($argc < 2) {
echo <<<HELLO
This script help you migrate from a pre-1.7 database to 1.7 and later\n
Both the 'calendarobjects' and 'calendars' tables will be upgraded.
If you do not have this table, or don't use the default PDO CalDAV backend
it's pointless to run this script.
Keep in mind that some processing will be done on every single record of this
table and in addition, ALTER TABLE commands will be executed.
If you have a large calendarobjects table, this may mean that this process
takes a while.
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);
echo "Validating existing table layout\n";
// The only cross-db way to do this, is to just fetch a single record.
$row = $pdo->query("SELECT * FROM calendarobjects LIMIT 1")->fetch();
if (!$row) {
echo "Error: This database did not have any records in the calendarobjects table, you should just recreate the table.\n";
exit(-1);
}
$requiredFields = [
'id',
'calendardata',
'uri',
'calendarid',
'lastmodified',
];
foreach ($requiredFields as $requiredField) {
if (!array_key_exists($requiredField, $row)) {
echo "Error: The current 'calendarobjects' table was missing a field we expected to exist.\n";
echo "For safety reasons, this process is stopped.\n";
exit(-1);
}
}
$fields17 = [
'etag',
'size',
'componenttype',
'firstoccurence',
'lastoccurence',
];
$found = 0;
foreach ($fields17 as $field) {
if (array_key_exists($field, $row)) {
$found++;
}
}
if ($found === 0) {
echo "The database had the 1.6 schema. Table will now be altered.\n";
echo "This may take some time for large tables\n";
switch ($pdo->getAttribute(PDO::ATTR_DRIVER_NAME)) {
case 'mysql' :
$pdo->exec(<<<SQL
ALTER TABLE calendarobjects
ADD etag VARCHAR(32),
ADD size INT(11) UNSIGNED,
ADD componenttype VARCHAR(8),
ADD firstoccurence INT(11) UNSIGNED,
ADD lastoccurence INT(11) UNSIGNED
SQL
);
break;
case 'sqlite' :
$pdo->exec('ALTER TABLE calendarobjects ADD etag text');
$pdo->exec('ALTER TABLE calendarobjects ADD size integer');
$pdo->exec('ALTER TABLE calendarobjects ADD componenttype TEXT');
$pdo->exec('ALTER TABLE calendarobjects ADD firstoccurence integer');
$pdo->exec('ALTER TABLE calendarobjects ADD lastoccurence integer');
break;
default :
die('This upgrade script does not support this driver (' . $pdo->getAttribute(PDO::ATTR_DRIVER_NAME) . ")\n");
}
echo "Database schema upgraded.\n";
} elseif ($found === 5) {
echo "Database already had the 1.7 schema\n";
} else {
echo "The database had $found out of 5 from the changes for 1.7. This is scary and unusual, so we have to abort.\n";
echo "You can manually try to upgrade the schema, and then run this script again.\n";
exit(-1);
}
echo "Now, we need to parse every record and pull out some information.\n";
$result = $pdo->query('SELECT id, calendardata FROM calendarobjects');
$stmt = $pdo->prepare('UPDATE calendarobjects SET etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ? WHERE id = ?');
echo "Total records found: " . $result->rowCount() . "\n";
$done = 0;
$total = $result->rowCount();
while ($row = $result->fetch()) {
try {
$newData = getDenormalizedData($row['calendardata']);
} catch (Exception $e) {
echo "===\nException caught will trying to parser calendarobject.\n";
echo "Error message: " . $e->getMessage() . "\n";
echo "Record id: " . $row['id'] . "\n";
echo "This record is ignored, you should inspect it to see if there's anything wrong.\n===\n";
continue;
}
$stmt->execute([
$newData['etag'],
$newData['size'],
$newData['componentType'],
$newData['firstOccurence'],
$newData['lastOccurence'],
$row['id'],
]);
$done++;
if ($done % 500 === 0) {
echo "Completed: $done / $total\n";
}
}
echo "Completed: $done / $total\n";
echo "Checking the calendars table needs changes.\n";
$row = $pdo->query("SELECT * FROM calendars LIMIT 1")->fetch();
if (array_key_exists('transparent', $row)) {
echo "The calendars table is already up to date\n";
} else {
echo "Adding the 'transparent' field to the calendars table\n";
switch ($pdo->getAttribute(PDO::ATTR_DRIVER_NAME)) {
case 'mysql' :
$pdo->exec("ALTER TABLE calendars ADD transparent TINYINT(1) NOT NULL DEFAULT '0'");
break;
case 'sqlite' :
$pdo->exec("ALTER TABLE calendars ADD transparent bool");
break;
default :
die('This upgrade script does not support this driver (' . $pdo->getAttribute(PDO::ATTR_DRIVER_NAME) . ")\n");
}
}
echo "Process completed!\n";
/**
* Parses some information from calendar objects, used for optimized
* calendar-queries.
*
* Blantently copied from Sabre\CalDAV\Backend\PDO
*
* Returns an array with the following keys:
* * etag
* * size
* * componentType
* * firstOccurence
* * lastOccurence
*
* @param string $calendarData
* @return array
*/
function getDenormalizedData($calendarData) {
$vObject = \Sabre\VObject\Reader::read($calendarData);
$componentType = null;
$component = null;
$firstOccurence = null;
$lastOccurence = null;
foreach ($vObject->getComponents() as $component) {
if ($component->name !== 'VTIMEZONE') {
$componentType = $component->name;
break;
}
}
if (!$componentType) {
throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
}
if ($componentType === 'VEVENT') {
$firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp();
// Finding the last occurence is a bit harder
if (!isset($component->RRULE)) {
if (isset($component->DTEND)) {
$lastOccurence = $component->DTEND->getDateTime()->getTimeStamp();
} elseif (isset($component->DURATION)) {
$endDate = clone $component->DTSTART->getDateTime();
$endDate->add(\Sabre\VObject\DateTimeParser::parse($component->DURATION->value));
$lastOccurence = $endDate->getTimeStamp();
} elseif (!$component->DTSTART->hasTime()) {
$endDate = clone $component->DTSTART->getDateTime();
$endDate->modify('+1 day');
$lastOccurence = $endDate->getTimeStamp();
} else {
$lastOccurence = $firstOccurence;
}
} else {
$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();
} else {
$end = $it->getDtEnd();
while ($it->valid() && $end < $maxDate) {
$end = $it->getDtEnd();
$it->next();
}
$lastOccurence = $end->getTimeStamp();
}
}
}
return [
'etag' => md5($calendarData),
'size' => strlen($calendarData),
'componentType' => $componentType,
'firstOccurence' => $firstOccurence,
'lastOccurence' => $lastOccurence,
];
}

0
vendor/sabre/dav/bin/migrateto20.php vendored Executable file → Normal file
View File

4
vendor/sabre/dav/bin/migrateto21.php vendored Executable file → Normal file
View File

@ -169,10 +169,6 @@ switch ($driver) {
)
');
break;
$pdo->exec('
CREATE INDEX principaluri_uri ON calendarsubscriptions (principaluri, uri);
');
break;
}
echo "Done.\n";

0
vendor/sabre/dav/bin/migrateto30.php vendored Executable file → Normal file
View File

268
vendor/sabre/dav/bin/migrateto32.php vendored Normal file
View File

@ -0,0 +1,268 @@
#!/usr/bin/env php
<?php
echo "SabreDAV migrate script for version 3.2\n";
if ($argc < 2) {
echo <<<HELLO
This script help you migrate from a 3.1 database to 3.2 and later
Changes:
* Created a new calendarinstances table to support calendar sharing.
* Remove a lot of columns from calendars.
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.
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.
Lastly, if you are upgrading from an older version than 3.1, make sure you run
the earlier migration script first. Migration scripts must be ran in order.
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;
$backupPostfix = time();
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 "Creating 'calendarinstances'\n";
$addValueType = false;
try {
$result = $pdo->query('SELECT * FROM calendarinstances LIMIT 1');
$result->fetch(\PDO::FETCH_ASSOC);
echo "calendarinstances exists. Assuming this part of the migration has already been done.\n";
} catch (Exception $e) {
echo "calendarinstances does not yet exist. Creating table and migrating data.\n";
switch ($driver) {
case 'mysql' :
$pdo->exec(<<<SQL
CREATE TABLE calendarinstances (
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
calendarid INTEGER UNSIGNED NOT NULL,
principaluri VARBINARY(100),
access TINYINT(1) NOT NULL DEFAULT '1' COMMENT '1 = owner, 2 = read, 3 = readwrite',
displayname VARCHAR(100),
uri VARBINARY(200),
description TEXT,
calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0',
calendarcolor VARBINARY(10),
timezone TEXT,
transparent TINYINT(1) NOT NULL DEFAULT '0',
share_href VARBINARY(100),
share_displayname VARCHAR(100),
share_invitestatus TINYINT(1) NOT NULL DEFAULT '2' COMMENT '1 = noresponse, 2 = accepted, 3 = declined, 4 = invalid',
UNIQUE(principaluri, uri),
UNIQUE(calendarid, principaluri),
UNIQUE(calendarid, share_href)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
SQL
);
$pdo->exec("
INSERT INTO calendarinstances
(
calendarid,
principaluri,
access,
displayname,
uri,
description,
calendarorder,
calendarcolor,
transparent
)
SELECT
id,
principaluri,
1,
displayname,
uri,
description,
calendarorder,
calendarcolor,
transparent
FROM calendars
");
break;
case 'sqlite' :
$pdo->exec(<<<SQL
CREATE TABLE calendarinstances (
id integer primary key asc NOT NULL,
calendarid integer,
principaluri text,
access integer COMMENT '1 = owner, 2 = read, 3 = readwrite' NOT NULL DEFAULT '1',
displayname text,
uri text NOT NULL,
description text,
calendarorder integer,
calendarcolor text,
timezone text,
transparent bool,
share_href text,
share_displayname text,
share_invitestatus integer DEFAULT '2',
UNIQUE (principaluri, uri),
UNIQUE (calendarid, principaluri),
UNIQUE (calendarid, share_href)
);
SQL
);
$pdo->exec("
INSERT INTO calendarinstances
(
calendarid,
principaluri,
access,
displayname,
uri,
description,
calendarorder,
calendarcolor,
transparent
)
SELECT
id,
principaluri,
1,
displayname,
uri,
description,
calendarorder,
calendarcolor,
transparent
FROM calendars
");
break;
}
}
try {
$result = $pdo->query('SELECT * FROM calendars LIMIT 1');
$row = $result->fetch(\PDO::FETCH_ASSOC);
if (!$row) {
echo "Source table is empty.\n";
$migrateCalendars = true;
}
$columnCount = count($row);
if ($columnCount === 3) {
echo "The calendars table has 3 columns already. Assuming this part of the migration was already done.\n";
$migrateCalendars = false;
} else {
echo "The calendars table has " . $columnCount . " columns.\n";
$migrateCalendars = true;
}
} catch (Exception $e) {
echo "calendars table does not exist. This is a major problem. Exiting.\n";
exit(-1);
}
if ($migrateCalendars) {
$calendarBackup = 'calendars_3_1_' . $backupPostfix;
echo "Backing up 'calendars' to '", $calendarBackup, "'\n";
switch ($driver) {
case 'mysql' :
$pdo->exec('RENAME TABLE calendars TO ' . $calendarBackup);
break;
case 'sqlite' :
$pdo->exec('ALTER TABLE calendars RENAME TO ' . $calendarBackup);
break;
}
echo "Creating new calendars table.\n";
switch ($driver) {
case 'mysql' :
$pdo->exec(<<<SQL
CREATE TABLE calendars (
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
synctoken INTEGER UNSIGNED NOT NULL DEFAULT '1',
components VARBINARY(21)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
SQL
);
break;
case 'sqlite' :
$pdo->exec(<<<SQL
CREATE TABLE calendars (
id integer primary key asc NOT NULL,
synctoken integer DEFAULT 1 NOT NULL,
components text NOT NULL
);
SQL
);
break;
}
echo "Migrating data from old to new table\n";
$pdo->exec(<<<SQL
INSERT INTO calendars (id, synctoken, components) SELECT id, synctoken, COALESCE(components,"VEVENT,VTODO,VJOURNAL") as components FROM $calendarBackup
SQL
);
}
echo "Upgrade to 3.2 schema completed.\n";

View File

@ -107,7 +107,7 @@ def main():
parser.add_option(
'-m', '--min-erase',
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. " +
"Setting this option higher will reduce the number of times the cache directory will need to be scanned. " +
"(the default is 1073741824, which is 1GB.)",
type="int",
dest="min_erase",

0
vendor/sabre/dav/bin/sabredav.php vendored Executable file → Normal file
View File

View File

@ -1,57 +0,0 @@
<?php
/*
Addressbook/CardDAV server example
This server features CardDAV support
*/
// settings
date_default_timezone_set('Canada/Eastern');
// Make sure this setting is turned on and reflect the root url for your WebDAV server.
// This can be for example the root / or a complete path to your server script
$baseUri = '/';
/* Database */
$pdo = new PDO('sqlite:data/db.sqlite');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//Mapping PHP errors to exceptions
function exception_error_handler($errno, $errstr, $errfile, $errline) {
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
set_error_handler("exception_error_handler");
// Autoloader
require_once 'vendor/autoload.php';
// Backends
$authBackend = new Sabre\DAV\Auth\Backend\PDO($pdo);
$principalBackend = new Sabre\DAVACL\PrincipalBackend\PDO($pdo);
$carddavBackend = new Sabre\CardDAV\Backend\PDO($pdo);
//$caldavBackend = new Sabre\CalDAV\Backend\PDO($pdo);
// Setting up the directory tree //
$nodes = [
new Sabre\DAVACL\PrincipalCollection($principalBackend),
// 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));
$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,28 +0,0 @@
CREATE TABLE addressbooks (
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
principaluri VARBINARY(255),
displayname VARCHAR(255),
uri VARBINARY(200),
description TEXT,
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 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,64 +0,0 @@
CREATE TABLE calendarobjects (
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
calendardata MEDIUMBLOB,
uri VARBINARY(200),
calendarid INTEGER UNSIGNED NOT NULL,
lastmodified INT(11) UNSIGNED,
etag VARBINARY(32),
size INT(11) UNSIGNED NOT NULL,
componenttype VARBINARY(8),
firstoccurence INT(11) UNSIGNED,
lastoccurence INT(11) UNSIGNED,
uid VARBINARY(200),
UNIQUE(calendarid, uri)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE calendars (
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
principaluri VARBINARY(100),
displayname VARCHAR(100),
uri VARBINARY(200),
synctoken INTEGER UNSIGNED NOT NULL DEFAULT '1',
description TEXT,
calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0',
calendarcolor VARBINARY(10),
timezone TEXT,
components VARBINARY(21),
transparent TINYINT(1) NOT NULL DEFAULT '0',
UNIQUE(principaluri, uri)
) 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

@ -1,12 +0,0 @@
CREATE TABLE locks (
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
owner VARCHAR(100),
timeout INTEGER UNSIGNED,
created INTEGER,
token VARBINARY(100),
scope TINYINT,
depth TINYINT,
uri VARBINARY(1000),
INDEX(token),
INDEX(uri(100))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -1,20 +0,0 @@
CREATE TABLE principals (
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
uri VARBINARY(200) NOT NULL,
email VARBINARY(80),
displayname VARCHAR(80),
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'),
('principals/admin/calendar-proxy-read', null, null),
('principals/admin/calendar-proxy-write', null, null);

View File

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

View File

@ -1,52 +0,0 @@
CREATE TABLE addressbooks (
id SERIAL NOT NULL,
principaluri VARCHAR(255),
displayname VARCHAR(255),
uri VARCHAR(200),
description TEXT,
synctoken INTEGER NOT NULL DEFAULT 1
);
ALTER TABLE ONLY addressbooks
ADD CONSTRAINT addressbooks_pkey PRIMARY KEY (id);
CREATE UNIQUE INDEX addressbooks_ukey
ON addressbooks USING btree (principaluri, uri);
CREATE TABLE cards (
id SERIAL NOT NULL,
addressbookid INTEGER NOT NULL,
carddata TEXT,
uri VARCHAR(200),
lastmodified INTEGER,
etag VARCHAR(32),
size INTEGER NOT NULL
);
ALTER TABLE ONLY cards
ADD CONSTRAINT cards_pkey PRIMARY KEY (id);
CREATE UNIQUE INDEX cards_ukey
ON cards USING btree (addressbookid, uri);
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

@ -1,93 +0,0 @@
CREATE TABLE calendars (
id SERIAL NOT NULL,
principaluri VARCHAR(100),
displayname VARCHAR(100),
uri VARCHAR(200),
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'
);
ALTER TABLE ONLY calendars
ADD CONSTRAINT calendars_pkey PRIMARY KEY (id);
CREATE UNIQUE INDEX calendars_ukey
ON calendars USING btree (principaluri, uri);
CREATE TABLE calendarobjects (
id SERIAL NOT NULL,
calendardata TEXT,
uri VARCHAR(200),
calendarid INTEGER NOT NULL,
lastmodified INTEGER,
etag VARCHAR(32),
size INTEGER NOT NULL,
componenttype VARCHAR(8),
firstoccurence INTEGER,
lastoccurence INTEGER,
uid VARCHAR(200)
);
ALTER TABLE ONLY calendarobjects
ADD CONSTRAINT calendarobjects_pkey PRIMARY KEY (id);
CREATE UNIQUE INDEX calendarobjects_ukey
ON calendarobjects USING btree (calendarid, uri);
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

@ -1,19 +0,0 @@
CREATE TABLE locks (
id SERIAL NOT NULL,
owner VARCHAR(100),
timeout INTEGER,
created INTEGER,
token VARCHAR(100),
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,38 +0,0 @@
CREATE TABLE principals (
id SERIAL NOT NULL,
uri VARCHAR(200) NOT NULL,
email VARCHAR(80),
displayname VARCHAR(80)
);
ALTER TABLE ONLY principals
ADD CONSTRAINT principals_pkey PRIMARY KEY (id);
CREATE UNIQUE INDEX principals_ukey
ON principals USING btree (uri);
CREATE TABLE groupmembers (
id SERIAL NOT NULL,
principal_id INTEGER NOT NULL,
member_id INTEGER NOT NULL
);
ALTER TABLE ONLY groupmembers
ADD CONSTRAINT groupmembers_pkey PRIMARY KEY (id);
CREATE UNIQUE INDEX groupmembers_ukey
ON groupmembers USING btree (principal_id, member_id);
ALTER TABLE ONLY groupmembers
ADD CONSTRAINT groupmembers_principal_id_fkey FOREIGN KEY (principal_id) REFERENCES principals(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'),
('principals/admin/calendar-proxy-read', null, null),
('principals/admin/calendar-proxy-write', null, null);

View File

@ -1,14 +0,0 @@
CREATE TABLE users (
id SERIAL NOT NULL,
username VARCHAR(50),
digesta1 VARCHAR(32)
);
ALTER TABLE ONLY users
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
CREATE UNIQUE INDEX users_ukey
ON users USING btree (username);
INSERT INTO users (username,digesta1) VALUES
('admin', '87fd274b7b6c01e48d7c2f965da8ddf7');

View File

@ -1,28 +0,0 @@
CREATE TABLE addressbooks (
id integer primary key asc NOT NULL,
principaluri text NOT NULL,
displayname text,
uri text NOT NULL,
description text,
synctoken integer DEFAULT 1 NOT NULL
);
CREATE TABLE cards (
id integer primary key asc NOT NULL,
addressbookid integer NOT NULL,
carddata blob,
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,64 +0,0 @@
CREATE TABLE calendarobjects (
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,
uid text
);
CREATE TABLE calendars (
id integer primary key asc NOT NULL,
principaluri text NOT NULL,
displayname text,
uri text NOT NULL,
synctoken integer DEFAULT 1 NOT NULL,
description text,
calendarorder integer,
calendarcolor text,
timezone 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,12 +0,0 @@
BEGIN TRANSACTION;
CREATE TABLE locks (
id integer primary key asc NOT NULL,
owner text,
timeout integer,
created integer,
token text,
scope integer,
depth integer,
uri text
);
COMMIT;

View File

@ -1,20 +0,0 @@
CREATE TABLE principals (
id INTEGER PRIMARY KEY ASC NOT NULL,
uri TEXT NOT NULL,
email TEXT,
displayname TEXT,
UNIQUE(uri)
);
CREATE TABLE groupmembers (
id INTEGER PRIMARY KEY ASC NOT NULL,
principal_id INTEGER NOT NULL,
member_id INTEGER NOT NULL,
UNIQUE(principal_id, member_id)
);
INSERT INTO principals (uri,email,displayname) VALUES ('principals/admin', 'admin@example.org','Administrator');
INSERT INTO principals (uri,email,displayname) VALUES ('principals/admin/calendar-proxy-read', null, null);
INSERT INTO principals (uri,email,displayname) VALUES ('principals/admin/calendar-proxy-write', null, null);

View File

@ -1,9 +0,0 @@
CREATE TABLE users (
id integer primary key asc NOT NULL,
username TEXT NOT NULL,
digesta1 TEXT NOT NULL,
UNIQUE(username)
);
INSERT INTO users (username,digesta1) VALUES
('admin', '87fd274b7b6c01e48d7c2f965da8ddf7');

View File

@ -1,16 +0,0 @@
RewriteEngine On
# This makes every request go to server.php
RewriteRule (.*) server.php [L]
# Output buffering needs to be off, to prevent high memory usage
php_flag output_buffering off
# 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

@ -1,29 +0,0 @@
# This is a sample configuration to setup a dedicated Apache vhost for WebDAV.
#
# The main thing that should be configured is the servername, and the path to
# your SabreDAV installation (DocumentRoot).
#
# This configuration assumed mod_php5 is used, as it sets a few default php
# settings as well.
<VirtualHost *:*>
# Don't forget to change the server name
# ServerName dav.example.org
# The DocumentRoot is also required
# DocumentRoot /home/sabredav/
RewriteEngine On
# This makes every request go to server.php
RewriteRule ^/(.*)$ /server.php [L]
# Output buffering needs to be off, to prevent high memory usage
php_flag output_buffering off
# This is also to prevent high memory usage
php_flag always_populate_raw_post_data off
# SabreDAV is not compatible with mbstring function overloading
php_flag mbstring.func_overload off
</VirtualHost *:*>

View File

@ -1,21 +0,0 @@
# This is a sample configuration to setup a dedicated Apache vhost for WebDAV.
#
# The main thing that should be configured is the servername, and the path to
# your SabreDAV installation (DocumentRoot).
#
# This configuration assumes CGI or FastCGI is used.
<VirtualHost *:*>
# Don't forget to change the server name
# ServerName dav.example.org
# The DocumentRoot is also required
# DocumentRoot /home/sabredav/
# This makes every request go to server.php. This also makes sure
# the Authentication information is available. If your server script is
# not called server.php, be sure to change it.
RewriteEngine On
RewriteRule ^/(.*)$ /server.php [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
</VirtualHost *:*>

View File

@ -44,10 +44,12 @@ interface BackendInterface {
* 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.
*
* The id can be any type, including ints, strings, objects or array.
*
* @param string $principalUri
* @param string $calendarUri
* @param array $properties
* @return void
* @return mixed
*/
function createCalendar($principalUri, $calendarUri, array $properties);
@ -63,7 +65,7 @@ interface BackendInterface {
*
* Read the PropPatch documentation for more info and examples.
*
* @param string $path
* @param mixed $calendarId
* @param \Sabre\DAV\PropPatch $propPatch
* @return void
*/

View File

@ -43,4 +43,19 @@ interface NotificationSupport extends BackendInterface {
*/
function deleteNotification($principalUri, NotificationInterface $notification);
/**
* This method is called when a user replied to a request to share.
*
* If the user chose to accept the share, this method should return the
* newly created calendar url.
*
* @param string href The sharee who is replying (often a mailto: address)
* @param int status One of the SharingPlugin::STATUS_* constants
* @param string $calendarUri The url to the calendar thats being shared
* @param string $inReplyTo The unique id this message is a response to
* @param string $summary A description of the reply
* @return null|string
*/
function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null);
}

View File

@ -2,10 +2,11 @@
namespace Sabre\CalDAV\Backend;
use Sabre\VObject;
use Sabre\CalDAV;
use Sabre\DAV;
use Sabre\DAV\Exception\Forbidden;
use Sabre\VObject;
use Sabre\DAV\Xml\Element\Sharee;
/**
* PDO CalDAV backend
@ -17,7 +18,12 @@ use Sabre\DAV\Exception\Forbidden;
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, SchedulingSupport {
class PDO extends AbstractBackend
implements
SyncSupport,
SubscriptionSupport,
SchedulingSupport,
SharingSupport {
/**
* We need to specify a max date, because we need to stop *somewhere*
@ -43,6 +49,16 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
*/
public $calendarTableName = 'calendars';
/**
* The table name that will be used for calendars instances.
*
* A single calendar can have multiple instances, if the calendar is
* shared.
*
* @var string
*/
public $calendarInstancesTableName = 'calendarinstances';
/**
* The table name that will be used for calendar objects
*
@ -140,16 +156,23 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
function getCalendarsForUser($principalUri) {
$fields = array_values($this->propertyMap);
$fields[] = 'id';
$fields[] = 'calendarid';
$fields[] = 'uri';
$fields[] = 'synctoken';
$fields[] = 'components';
$fields[] = 'principaluri';
$fields[] = 'transparent';
$fields[] = 'access';
// Making fields a comma-delimited list
$fields = implode(', ', $fields);
$stmt = $this->pdo->prepare("SELECT " . $fields . " FROM " . $this->calendarTableName . " WHERE principaluri = ? ORDER BY calendarorder ASC");
$stmt = $this->pdo->prepare(<<<SQL
SELECT {$this->calendarInstancesTableName}.id as id, $fields FROM {$this->calendarInstancesTableName}
LEFT JOIN {$this->calendarTableName} ON
{$this->calendarInstancesTableName}.calendarid = {$this->calendarTableName}.id
WHERE principaluri = ? ORDER BY calendarorder ASC
SQL
);
$stmt->execute([$principalUri]);
$calendars = [];
@ -161,15 +184,27 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
}
$calendar = [
'id' => $row['id'],
'id' => [(int)$row['calendarid'], (int)$row['id']],
'uri' => $row['uri'],
'principaluri' => $row['principaluri'],
'{' . CalDAV\Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken'] ? $row['synctoken'] : '0'),
'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
'{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet($components),
'{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' => new CalDAV\Xml\Property\ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
'share-resource-uri' => '/ns/share/' . $row['calendarid'],
];
$calendar['share-access'] = (int)$row['access'];
// 1 = owner, 2 = readonly, 3 = readwrite
if ($row['access'] > 1) {
// We need to find more information about the original owner.
//$stmt2 = $this->pdo->prepare('SELECT principaluri FROM ' . $this->calendarInstancesTableName . ' WHERE access = 1 AND id = ?');
//$stmt2->execute([$row['id']]);
// read-only is for backwards compatbility. Might go away in
// the future.
$calendar['read-only'] = (int)$row['access'] === \Sabre\DAV\Sharing\Plugin::ACCESS_READ;
}
foreach ($this->propertyMap as $xmlName => $dbName) {
$calendar[$xmlName] = $row[$dbName];
@ -199,31 +234,38 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
$fieldNames = [
'principaluri',
'uri',
'synctoken',
'transparent',
'calendarid',
];
$values = [
':principaluri' => $principalUri,
':uri' => $calendarUri,
':synctoken' => 1,
':transparent' => 0,
];
// Default value
$sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
$fieldNames[] = 'components';
if (!isset($properties[$sccs])) {
$values[':components'] = 'VEVENT,VTODO';
// Default value
$components = 'VEVENT,VTODO';
} else {
if (!($properties[$sccs] instanceof CalDAV\Xml\Property\SupportedCalendarComponentSet)) {
throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet');
}
$values[':components'] = implode(',', $properties[$sccs]->getValue());
$components = implode(',', $properties[$sccs]->getValue());
}
$transp = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp';
if (isset($properties[$transp])) {
$values[':transparent'] = $properties[$transp]->getValue() === 'transparent';
$values[':transparent'] = $properties[$transp]->getValue() === 'transparent' ? 1 : 0;
}
$stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarTableName . " (synctoken, components) VALUES (1, ?)");
$stmt->execute([$components]);
$calendarId = $this->pdo->lastInsertId(
$this->calendarTableName . '_id_seq'
);
$values[':calendarid'] = $calendarId;
foreach ($this->propertyMap as $xmlName => $dbName) {
if (isset($properties[$xmlName])) {
@ -233,10 +275,14 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
}
}
$stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")");
$stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarInstancesTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")");
$stmt->execute($values);
return $this->pdo->lastInsertId();
return [
$calendarId,
$this->pdo->lastInsertId($this->calendarInstancesTableName . '_id_seq')
];
}
@ -252,16 +298,21 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
*
* Read the PropPatch documenation for more info and examples.
*
* @param string $calendarId
* @param mixed $calendarId
* @param \Sabre\DAV\PropPatch $propPatch
* @return void
*/
function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch) {
if (!is_array($calendarId)) {
throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
}
list($calendarId, $instanceId) = $calendarId;
$supportedProperties = array_keys($this->propertyMap);
$supportedProperties[] = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp';
$propPatch->handle($supportedProperties, function($mutations) use ($calendarId) {
$propPatch->handle($supportedProperties, function($mutations) use ($calendarId, $instanceId) {
$newValues = [];
foreach ($mutations as $propertyName => $propertyValue) {
@ -282,8 +333,8 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
$valuesSql[] = $fieldName . ' = ?';
}
$stmt = $this->pdo->prepare("UPDATE " . $this->calendarTableName . " SET " . implode(', ', $valuesSql) . " WHERE id = ?");
$newValues['id'] = $calendarId;
$stmt = $this->pdo->prepare("UPDATE " . $this->calendarInstancesTableName . " SET " . implode(', ', $valuesSql) . " WHERE id = ?");
$newValues['id'] = $instanceId;
$stmt->execute(array_values($newValues));
$this->addChange($calendarId, "", 2);
@ -297,19 +348,49 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
/**
* Delete a calendar and all it's objects
*
* @param string $calendarId
* @param mixed $calendarId
* @return void
*/
function deleteCalendar($calendarId) {
$stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ?');
$stmt->execute([$calendarId]);
if (!is_array($calendarId)) {
throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
}
list($calendarId, $instanceId) = $calendarId;
$stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarTableName . ' WHERE id = ?');
$stmt->execute([$calendarId]);
$stmt = $this->pdo->prepare('SELECT access FROM ' . $this->calendarInstancesTableName . ' where id = ?');
$stmt->execute([$instanceId]);
$access = (int)$stmt->fetchColumn();
if ($access === \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER) {
/**
* If the user is the owner of the calendar, we delete all data and all
* instances.
**/
$stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ?');
$stmt->execute([$calendarId]);
$stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarChangesTableName . ' WHERE calendarid = ?');
$stmt->execute([$calendarId]);
$stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarInstancesTableName . ' WHERE calendarid = ?');
$stmt->execute([$calendarId]);
$stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarTableName . ' WHERE id = ?');
$stmt->execute([$calendarId]);
} else {
/**
* If it was an instance of a shared calendar, we only delete that
* instance.
*/
$stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarInstancesTableName . ' WHERE id = ?');
$stmt->execute([$instanceId]);
}
$stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarChangesTableName . ' WHERE calendarid = ?');
$stmt->execute([$calendarId]);
}
@ -341,11 +422,16 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
* used/fetched to determine these numbers. If both are specified the
* amount of times this is needed is reduced by a great degree.
*
* @param string $calendarId
* @param mixed $calendarId
* @return array
*/
function getCalendarObjects($calendarId) {
if (!is_array($calendarId)) {
throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
}
list($calendarId, $instanceId) = $calendarId;
$stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ?');
$stmt->execute([$calendarId]);
@ -354,9 +440,8 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
$result[] = [
'id' => $row['id'],
'uri' => $row['uri'],
'lastmodified' => $row['lastmodified'],
'lastmodified' => (int)$row['lastmodified'],
'etag' => '"' . $row['etag'] . '"',
'calendarid' => $row['calendarid'],
'size' => (int)$row['size'],
'component' => strtolower($row['componenttype']),
];
@ -378,12 +463,17 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
*
* This method must return null if the object did not exist.
*
* @param string $calendarId
* @param mixed $calendarId
* @param string $objectUri
* @return array|null
*/
function getCalendarObject($calendarId, $objectUri) {
if (!is_array($calendarId)) {
throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
}
list($calendarId, $instanceId) = $calendarId;
$stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri = ?');
$stmt->execute([$calendarId, $objectUri]);
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
@ -393,9 +483,8 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
return [
'id' => $row['id'],
'uri' => $row['uri'],
'lastmodified' => $row['lastmodified'],
'lastmodified' => (int)$row['lastmodified'],
'etag' => '"' . $row['etag'] . '"',
'calendarid' => $row['calendarid'],
'size' => (int)$row['size'],
'calendardata' => $row['calendardata'],
'component' => strtolower($row['componenttype']),
@ -417,6 +506,11 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
*/
function getMultipleCalendarObjects($calendarId, array $uris) {
if (!is_array($calendarId)) {
throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
}
list($calendarId, $instanceId) = $calendarId;
$query = 'SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri IN (';
// Inserting a whole bunch of question marks
$query .= implode(',', array_fill(0, count($uris), '?'));
@ -431,9 +525,8 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
$result[] = [
'id' => $row['id'],
'uri' => $row['uri'],
'lastmodified' => $row['lastmodified'],
'lastmodified' => (int)$row['lastmodified'],
'etag' => '"' . $row['etag'] . '"',
'calendarid' => $row['calendarid'],
'size' => (int)$row['size'],
'calendardata' => $row['calendardata'],
'component' => strtolower($row['componenttype']),
@ -465,6 +558,11 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
*/
function createCalendarObject($calendarId, $objectUri, $calendarData) {
if (!is_array($calendarId)) {
throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
}
list($calendarId, $instanceId) = $calendarId;
$extraData = $this->getDenormalizedData($calendarData);
$stmt = $this->pdo->prepare('INSERT INTO ' . $this->calendarObjectTableName . ' (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES (?,?,?,?,?,?,?,?,?,?)');
@ -506,6 +604,11 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
*/
function updateCalendarObject($calendarId, $objectUri, $calendarData) {
if (!is_array($calendarId)) {
throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
}
list($calendarId, $instanceId) = $calendarId;
$extraData = $this->getDenormalizedData($calendarData);
$stmt = $this->pdo->prepare('UPDATE ' . $this->calendarObjectTableName . ' SET calendardata = ?, lastmodified = ?, etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ?, uid = ? WHERE calendarid = ? AND uri = ?');
@ -583,6 +686,10 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
}
}
// Ensure Occurence values are positive
if ($firstOccurence < 0) $firstOccurence = 0;
if ($lastOccurence < 0) $lastOccurence = 0;
}
// Destroy circular references to PHP will GC the object.
@ -604,12 +711,17 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
*
* The object uri is only the basename, or filename and not a full path.
*
* @param string $calendarId
* @param mixed $calendarId
* @param string $objectUri
* @return void
*/
function deleteCalendarObject($calendarId, $objectUri) {
if (!is_array($calendarId)) {
throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
}
list($calendarId, $instanceId) = $calendarId;
$stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri = ?');
$stmt->execute([$calendarId, $objectUri]);
@ -665,12 +777,17 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
* This specific implementation (for the PDO) backend optimizes filters on
* specific components, and VEVENT time-ranges.
*
* @param string $calendarId
* @param mixed $calendarId
* @param array $filters
* @return array
*/
function calendarQuery($calendarId, array $filters) {
if (!is_array($calendarId)) {
throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
}
list($calendarId, $instanceId) = $calendarId;
$componentType = null;
$requirePostFilter = true;
$timeRange = null;
@ -766,14 +883,14 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
$query = <<<SQL
SELECT
calendars.uri AS calendaruri, calendarobjects.uri as objecturi
calendar_instances.uri AS calendaruri, calendarobjects.uri as objecturi
FROM
$this->calendarObjectTableName AS calendarobjects
LEFT JOIN
$this->calendarTableName AS calendars
ON calendarobjects.calendarid = calendars.id
$this->calendarInstancesTableName AS calendar_instances
ON calendarobjects.calendarid = calendar_instances.calendarid
WHERE
calendars.principaluri = ?
calendar_instances.principaluri = ?
AND
calendarobjects.uid = ?
SQL;
@ -837,7 +954,7 @@ SQL;
*
* The limit is 'suggestive'. You are free to ignore it.
*
* @param string $calendarId
* @param mixed $calendarId
* @param string $syncToken
* @param int $syncLevel
* @param int $limit
@ -845,6 +962,11 @@ SQL;
*/
function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null) {
if (!is_array($calendarId)) {
throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
}
list($calendarId, $instanceId) = $calendarId;
// Current synctoken
$stmt = $this->pdo->prepare('SELECT synctoken FROM ' . $this->calendarTableName . ' WHERE id = ?');
$stmt->execute([ $calendarId ]);
@ -1043,7 +1165,9 @@ SQL;
$stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarSubscriptionsTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")");
$stmt->execute($values);
return $this->pdo->lastInsertId();
return $this->pdo->lastInsertId(
$this->calendarSubscriptionsTableName . '_id_seq'
);
}
@ -1207,4 +1331,179 @@ SQL;
}
/**
* Updates the list of shares.
*
* @param mixed $calendarId
* @param \Sabre\DAV\Xml\Element\Sharee[] $sharees
* @return void
*/
function updateInvites($calendarId, array $sharees) {
if (!is_array($calendarId)) {
throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
}
$currentInvites = $this->getInvites($calendarId);
list($calendarId, $instanceId) = $calendarId;
$removeStmt = $this->pdo->prepare("DELETE FROM " . $this->calendarInstancesTableName . " WHERE calendarid = ? AND share_href = ? AND access IN (2,3)");
$updateStmt = $this->pdo->prepare("UPDATE " . $this->calendarInstancesTableName . " SET access = ?, share_displayname = ?, share_invitestatus = ? WHERE calendarid = ? AND share_href = ?");
$insertStmt = $this->pdo->prepare('
INSERT INTO ' . $this->calendarInstancesTableName . '
(
calendarid,
principaluri,
access,
displayname,
uri,
description,
calendarorder,
calendarcolor,
timezone,
transparent,
share_href,
share_displayname,
share_invitestatus
)
SELECT
?,
?,
?,
displayname,
?,
description,
calendarorder,
calendarcolor,
timezone,
1,
?,
?,
?
FROM ' . $this->calendarInstancesTableName . ' WHERE id = ?');
foreach ($sharees as $sharee) {
if ($sharee->access === \Sabre\DAV\Sharing\Plugin::ACCESS_NOACCESS) {
// if access was set no NOACCESS, it means access for an
// existing sharee was removed.
$removeStmt->execute([$calendarId, $sharee->href]);
continue;
}
if (is_null($sharee->principal)) {
// If the server could not determine the principal automatically,
// we will mark the invite status as invalid.
$sharee->inviteStatus = \Sabre\DAV\Sharing\Plugin::INVITE_INVALID;
} else {
// Because sabre/dav does not yet have an invitation system,
// every invite is automatically accepted for now.
$sharee->inviteStatus = \Sabre\DAV\Sharing\Plugin::INVITE_ACCEPTED;
}
foreach ($currentInvites as $oldSharee) {
if ($oldSharee->href === $sharee->href) {
// This is an update
$sharee->properties = array_merge(
$oldSharee->properties,
$sharee->properties
);
$updateStmt->execute([
$sharee->access,
isset($sharee->properties['{DAV:}displayname']) ? $sharee->properties['{DAV:}displayname'] : null,
$sharee->inviteStatus ?: $oldSharee->inviteStatus,
$calendarId,
$sharee->href
]);
continue 2;
}
}
// If we got here, it means it was a new sharee
$insertStmt->execute([
$calendarId,
$sharee->principal,
$sharee->access,
\Sabre\DAV\UUIDUtil::getUUID(),
$sharee->href,
isset($sharee->properties['{DAV:}displayname']) ? $sharee->properties['{DAV:}displayname'] : null,
$sharee->inviteStatus ?: \Sabre\DAV\Sharing\Plugin::INVITE_NORESPONSE,
$instanceId
]);
}
}
/**
* Returns the list of people whom a calendar is shared with.
*
* Every item in the returned list must be a Sharee object with at
* least the following properties set:
* $href
* $shareAccess
* $inviteStatus
*
* and optionally:
* $properties
*
* @param mixed $calendarId
* @return \Sabre\DAV\Xml\Element\Sharee[]
*/
function getInvites($calendarId) {
if (!is_array($calendarId)) {
throw new \InvalidArgumentException('The value passed to getInvites() is expected to be an array with a calendarId and an instanceId');
}
list($calendarId, $instanceId) = $calendarId;
$query = <<<SQL
SELECT
principaluri,
access,
share_href,
share_displayname,
share_invitestatus
FROM {$this->calendarInstancesTableName}
WHERE
calendarid = ?
SQL;
$stmt = $this->pdo->prepare($query);
$stmt->execute([$calendarId]);
$result = [];
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
$result[] = new Sharee([
'href' => isset($row['share_href']) ? $row['share_href'] : \Sabre\HTTP\encodePath($row['principaluri']),
'access' => (int)$row['access'],
/// Everyone is always immediately accepted, for now.
'inviteStatus' => (int)$row['share_invitestatus'],
'properties' =>
!empty($row['share_displayname'])
? [ '{DAV:}displayname' => $row['share_displayname'] ]
: [],
'principal' => $row['principaluri'],
]);
}
return $result;
}
/**
* Publishes a calendar
*
* @param mixed $calendarId
* @param bool $value
* @return void
*/
function setPublishStatus($calendarId, $value) {
throw new \Sabre\DAV\Exception\NotImplemented('Not implemented');
}
}

View File

@ -5,231 +5,48 @@ namespace Sabre\CalDAV\Backend;
/**
* Adds support for sharing features to a CalDAV server.
*
* Note: This feature is experimental, and may change in between different
* SabreDAV versions.
* CalDAV backends that implement this interface, must make the following
* modifications to getCalendarsForUser:
*
* Early warning: Currently SabreDAV provides no implementation for this. This
* is, because in it's current state there is no elegant way to do this.
* The problem lies in the fact that a real CalDAV server with sharing support
* would first need email support (with invite notifications), and really also
* a browser-frontend that allows people to accept or reject these shares.
*
* In addition, the CalDAV backends are currently kept as independent as
* possible, and should not be aware of principals, email addresses or
* accounts.
*
* Adding an implementation for Sharing to standard-sabredav would contradict
* these goals, so for this reason this is currently not implemented, although
* it may very well in the future; but probably not before SabreDAV 2.0.
*
* The interface works however, so if you implement all this, and do it
* correctly sharing _will_ work. It's not particularly easy, and I _urge you_
* to make yourself acquainted with the following document first:
*
* https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
*
* An overview
* ===========
*
* Implementing this interface will allow a user to share his or her calendars
* to other users. Effectively, when a calendar is shared the calendar will
* show up in both the Sharer's and Sharee's calendar-home root.
* This interface adds a few methods that ensure that this happens, and there
* are also a number of new requirements in the base-class you must now follow.
*
*
* How it works
* ============
*
* When a user shares a calendar, the updateShares() method will be called with
* a list of sharees that are now added, and a list of sharees that have been
* removed.
* Removal is instant, but when a sharee is added the sharee first gets a
* chance to accept or reject the invitation for a share.
*
* After a share is accepted, the calendar will be returned from
* getUserCalendars for both the sharer, and the sharee.
*
* If the sharee deletes the calendar, only their share gets deleted. When the
* owner deletes a calendar, it will be removed for everybody.
*
*
* Notifications
* =============
*
* During all these sharing operations, a lot of notifications are sent back
* and forward.
*
* Whenever the list of sharees for a calendar has been changed (they have been
* added, removed or modified) all sharees should get a notification for this
* change.
* This notification is always represented by:
*
* Sabre\CalDAV\Notifications\Notification\Invite
*
* In the case of an invite, the sharee may reply with an 'accept' or
* 'decline'. These are always represented by:
*
* Sabre\CalDAV\Notifications\Notification\InviteReply
*
*
* Calendar access by sharees
* ==========================
*
* As mentioned earlier, shared calendars must now also be returned for
* getCalendarsForUser for sharees. A few things change though.
*
* The following properties must be specified:
*
* 1. {http://calendarserver.org/ns/}shared-url
*
* This property MUST contain the url to the original calendar, that is.. the
* path to the calendar from the owner.
*
* 2. {http://sabredav.org/ns}owner-principal
*
* This is a url to to the principal who is sharing the calendar.
*
* 3. {http://sabredav.org/ns}read-only
*
* This should be either 0 or 1, depending on if the user has read-only or
* read-write access to the calendar.
*
* Only when this is done, the calendar will correctly be marked as a calendar
* that's shared to him, thus allowing clients to display the correct interface
* and ACL enforcement.
*
* If a sharee deletes their calendar, only their instance of the calendar
* should be deleted, the original should still exists.
* Pretty much any 'dead' WebDAV properties on these shared calendars should be
* specific to a user. This means that if the displayname is changed by a
* sharee, the original is not affected. This is also true for:
* * The description
* * The color
* * The order
* * And any other dead properties.
*
* Properties like a ctag should not be different for multiple instances of the
* calendar.
*
* Lastly, objects *within* calendars should also have user-specific data. The
* two things that are user-specific are:
* * VALARM objects
* * The TRANSP property
*
* This _also_ implies that if a VALARM is deleted by a sharee for some event,
* this has no effect on the original VALARM.
*
* Understandably, the this last requirement is one of the hardest.
* Realisticly, I can see people ignoring this part of the spec, but that could
* cause a different set of issues.
*
*
* Publishing
* ==========
*
* When a user publishes a url, the server should generate a 'publish url'.
* This is a read-only url, anybody can use to consume the calendar feed.
*
* Calendars are in one of two states:
* * published
* * unpublished
*
* If a calendar is published, the following property should be returned
* for each calendar in getCalendarsForUser.
*
* {http://calendarserver.org/ns/}publish-url
*
* This element should contain a {DAV:}href element, which points to the
* public url that does not require authentication. Unlike every other href,
* this url must be absolute.
*
* Ideally, the following property is always returned
*
* {http://calendarserver.org/ns/}pre-publish-url
*
* This property should contain the url that the calendar _would_ have, if it
* were to be published. iCal uses this to display the url, before the user
* will actually publish it.
*
*
* Selectively disabling publish or share feature
* ==============================================
*
* If Sabre\CalDAV\Property\AllowedSharingModes is returned from
* 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,
* and update the interface appropriately. If this property is not returned by
* the backend, the SharingPlugin automatically injects it and assumes both
* features are available.
* 1. Return shared calendars for users.
* 2. For every calendar, return calendar-resource-uri. This strings is a URI or
* relative URI reference that must be unique for every calendar, but
* identical for every instance of the same shared calenar.
* 3. For every calenar, you must return a share-access element. This element
* should contain one of the Sabre\DAV\Sharing\Plugin:ACCESS_* contants and
* indicates the access level the user has.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface SharingSupport extends NotificationSupport {
interface SharingSupport extends BackendInterface {
/**
* Updates the list of shares.
*
* The first array is a list of people that are to be added to the
* calendar.
*
* Every element in the add array has the following properties:
* * href - A url. Usually a mailto: address
* * commonName - Usually a first and last name, or false
* * summary - A description of the share, can also be false
* * readOnly - A boolean value
*
* Every element in the remove array is just the address string.
*
* Note that if the calendar is currently marked as 'not shared' by and
* this method is called, the calendar should be 'upgraded' to a shared
* calendar.
*
* @param mixed $calendarId
* @param array $add
* @param array $remove
* @param \Sabre\DAV\Xml\Element\Sharee[] $sharees
* @return void
*/
function updateShares($calendarId, array $add, array $remove);
function updateInvites($calendarId, array $sharees);
/**
* Returns the list of people whom this calendar is shared with.
*
* Every element in this array should have the following properties:
* * href - Often a mailto: address
* * commonName - Optional, for example a first + last name
* * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
* * readOnly - boolean
* * summary - Optional, a description for the share
* Every item in the returned list must be a Sharee object with at
* least the following properties set:
* $href
* $shareAccess
* $inviteStatus
*
* This method may be called by either the original instance of the
* calendar, as well as the shared instances. In the case of the shared
* instances, it is perfectly acceptable to return an empty array in case
* there are privacy concerns.
* and optionally:
* $properties
*
* @param mixed $calendarId
* @return array
* @return \Sabre\DAV\Xml\Element\Sharee[]
*/
function getShares($calendarId);
/**
* This method is called when a user replied to a request to share.
*
* If the user chose to accept the share, this method should return the
* newly created calendar url.
*
* @param string href The sharee who is replying (often a mailto: address)
* @param int status One of the SharingPlugin::STATUS_* constants
* @param string $calendarUri The url to the calendar thats being shared
* @param string $inReplyTo The unique id this message is a response to
* @param string $summary A description of the reply
* @return null|string
*/
function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null);
function getInvites($calendarId);
/**
* Publishes a calendar

View File

@ -0,0 +1,296 @@
<?php
namespace Sabre\CalDAV\Backend;
use Sabre\CalDAV;
use Sabre\DAV;
/**
* Simple PDO CalDAV backend.
*
* This class is basically the most minmum example to get a caldav backend up
* and running. This class uses the following schema (MySQL example):
*
* CREATE TABLE simple_calendars (
* id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
* uri VARBINARY(200) NOT NULL,
* principaluri VARBINARY(200) NOT NULL
* );
*
* CREATE TABLE simple_calendarobjects (
* id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
* calendarid INT UNSIGNED NOT NULL,
* uri VARBINARY(200) NOT NULL,
* calendardata MEDIUMBLOB
* )
*
* To make this class work, you absolutely need to have the PropertyStorage
* plugin enabled.
*
* @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class SimplePDO extends AbstractBackend {
/**
* pdo
*
* @var \PDO
*/
protected $pdo;
/**
* Creates the backend
*
* @param \PDO $pdo
*/
function __construct(\PDO $pdo) {
$this->pdo = $pdo;
}
/**
* Returns a list of calendars for a principal.
*
* Every project is an array with the following keys:
* * id, a unique id that will be used by other functions to modify the
* calendar. This can be the same as the uri or a database key.
* * uri. This is just the 'base uri' or 'filename' of the calendar.
* * principaluri. The owner of the calendar. Almost always the same as
* principalUri passed to this method.
*
* Furthermore it can contain webdav properties in clark notation. A very
* common one is '{DAV:}displayname'.
*
* Many clients also require:
* {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
* For this property, you can just return an instance of
* Sabre\CalDAV\Xml\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
*/
function getCalendarsForUser($principalUri) {
// Making fields a comma-delimited list
$stmt = $this->pdo->prepare("SELECT id, uri FROM simple_calendars WHERE principaluri = ? ORDER BY id ASC");
$stmt->execute([$principalUri]);
$calendars = [];
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
$calendars[] = [
'id' => $row['id'],
'uri' => $row['uri'],
'principaluri' => $principalUri,
];
}
return $calendars;
}
/**
* Creates a new calendar for a principal.
*
* If the creation was a success, an id must be returned that can be used
* to reference this calendar in other methods, such as updateCalendar.
*
* @param string $principalUri
* @param string $calendarUri
* @param array $properties
* @return string
*/
function createCalendar($principalUri, $calendarUri, array $properties) {
$stmt = $this->pdo->prepare("INSERT INTO simple_calendars (principaluri, uri) VALUES (?, ?)");
$stmt->execute([$principalUri, $calendarUri]);
return $this->pdo->lastInsertId();
}
/**
* Delete a calendar and all it's objects
*
* @param string $calendarId
* @return void
*/
function deleteCalendar($calendarId) {
$stmt = $this->pdo->prepare('DELETE FROM simple_calendarobjects WHERE calendarid = ?');
$stmt->execute([$calendarId]);
$stmt = $this->pdo->prepare('DELETE FROM simple_calendars WHERE id = ?');
$stmt->execute([$calendarId]);
}
/**
* Returns all calendar objects within a calendar.
*
* Every item contains an array with the following keys:
* * calendardata - The iCalendar-compatible calendar data
* * uri - a unique key which will be used to construct the uri. This can
* be any arbitrary string, but making sure it ends with '.ics' is a
* good idea. This is only the basename, or filename, not the full
* path.
* * lastmodified - a timestamp of the last modification time
* * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
* ' "abcdef"')
* * size - The size of the calendar objects, in bytes.
* * component - optional, a string containing the type of object, such
* as 'vevent' or 'vtodo'. If specified, this will be used to populate
* the Content-Type header.
*
* Note that the etag is optional, but it's highly encouraged to return for
* speed reasons.
*
* The calendardata is also optional. If it's not returned
* 'getCalendarObject' will be called later, which *is* expected to return
* calendardata.
*
* If neither etag or size are specified, the calendardata will be
* used/fetched to determine these numbers. If both are specified the
* amount of times this is needed is reduced by a great degree.
*
* @param string $calendarId
* @return array
*/
function getCalendarObjects($calendarId) {
$stmt = $this->pdo->prepare('SELECT id, uri, calendardata FROM simple_calendarobjects WHERE calendarid = ?');
$stmt->execute([$calendarId]);
$result = [];
foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
$result[] = [
'id' => $row['id'],
'uri' => $row['uri'],
'etag' => '"' . md5($row['calendardata']) . '"',
'calendarid' => $calendarId,
'size' => strlen($row['calendardata']),
'calendardata' => $row['calendardata'],
];
}
return $result;
}
/**
* Returns information from a single calendar object, based on it's object
* uri.
*
* The object uri is only the basename, or filename and not a full path.
*
* The returned array must have the same keys as getCalendarObjects. The
* 'calendardata' object is required here though, while it's not required
* for getCalendarObjects.
*
* This method must return null if the object did not exist.
*
* @param string $calendarId
* @param string $objectUri
* @return array|null
*/
function getCalendarObject($calendarId, $objectUri) {
$stmt = $this->pdo->prepare('SELECT id, uri, calendardata FROM simple_calendarobjects WHERE calendarid = ? AND uri = ?');
$stmt->execute([$calendarId, $objectUri]);
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
if (!$row) return null;
return [
'id' => $row['id'],
'uri' => $row['uri'],
'etag' => '"' . md5($row['calendardata']) . '"',
'calendarid' => $calendarId,
'size' => strlen($row['calendardata']),
'calendardata' => $row['calendardata'],
];
}
/**
* Creates a new calendar object.
*
* The object uri is only the basename, or filename and not a full path.
*
* It is possible return an etag from this function, which will be used in
* the response to this PUT request. Note that the ETag must be surrounded
* by double-quotes.
*
* However, you should only really return this ETag if you don't mangle the
* calendar-data. If the result of a subsequent GET to this object is not
* the exact same as this request body, you should omit the ETag.
*
* @param mixed $calendarId
* @param string $objectUri
* @param string $calendarData
* @return string|null
*/
function createCalendarObject($calendarId, $objectUri, $calendarData) {
$stmt = $this->pdo->prepare('INSERT INTO simple_calendarobjects (calendarid, uri, calendardata) VALUES (?,?,?)');
$stmt->execute([
$calendarId,
$objectUri,
$calendarData
]);
return '"' . md5($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.
*
* However, you should only really return this ETag if you don't mangle the
* calendar-data. If the result of a subsequent GET to this object is not
* the exact same as this request body, you should omit the ETag.
*
* @param mixed $calendarId
* @param string $objectUri
* @param string $calendarData
* @return string|null
*/
function updateCalendarObject($calendarId, $objectUri, $calendarData) {
$stmt = $this->pdo->prepare('UPDATE simple_calendarobjects SET calendardata = ? WHERE calendarid = ? AND uri = ?');
$stmt->execute([$calendarData, $calendarId, $objectUri]);
return '"' . md5($calendarData) . '"';
}
/**
* Deletes an existing calendar object.
*
* The object uri is only the basename, or filename and not a full path.
*
* @param string $calendarId
* @param string $objectUri
* @return void
*/
function deleteCalendarObject($calendarId, $objectUri) {
$stmt = $this->pdo->prepare('DELETE FROM simple_calendarobjects WHERE calendarid = ? AND uri = ?');
$stmt->execute([$calendarId, $objectUri]);
}
}

View File

@ -18,6 +18,8 @@ use Sabre\DAV\PropPatch;
*/
class Calendar implements ICalendar, DAV\IProperties, DAV\Sync\ISyncCollection, DAV\IMultiGet {
use DAVACL\ACLTrait;
/**
* This is an array with calendar information
*
@ -86,7 +88,7 @@ class Calendar implements ICalendar, DAV\IProperties, DAV\Sync\ISyncCollection,
foreach ($this->calendarInfo as $propName => $propValue) {
if ($propName[0] === '{')
if (!is_null($propValue) && $propName[0] === '{')
$response[$propName] = $this->calendarInfo[$propName];
}
@ -227,7 +229,7 @@ class Calendar implements ICalendar, DAV\IProperties, DAV\Sync\ISyncCollection,
/**
* Returns the last modification date as a unix timestamp.
*
* @return void
* @return null
*/
function getLastModified() {
@ -248,19 +250,6 @@ class Calendar implements ICalendar, DAV\IProperties, DAV\Sync\ISyncCollection,
}
/**
* 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.
*
@ -360,50 +349,6 @@ class Calendar implements ICalendar, DAV\IProperties, DAV\Sync\ISyncCollection,
}
/**
* 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.

View File

@ -22,6 +22,8 @@ use Sabre\HTTP\URLUtil;
*/
class CalendarHome implements DAV\IExtendedCollection, DAVACL\IACL {
use DAVACL\ACLTrait;
/**
* CalDAV backend
*
@ -147,11 +149,7 @@ class CalendarHome implements DAV\IExtendedCollection, DAVACL\IACL {
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);
}
return new SharedCalendar($this->caldavBackend, $calendar);
} else {
return new Calendar($this->caldavBackend, $calendar);
}
@ -198,11 +196,7 @@ class CalendarHome implements DAV\IExtendedCollection, DAVACL\IACL {
$objs = [];
foreach ($calendars as $calendar) {
if ($this->caldavBackend instanceof Backend\SharingSupport) {
if (isset($calendar['{http://calendarserver.org/ns/}shared-url'])) {
$objs[] = new SharedCalendar($this->caldavBackend, $calendar);
} else {
$objs[] = new ShareableCalendar($this->caldavBackend, $calendar);
}
$objs[] = new SharedCalendar($this->caldavBackend, $calendar);
} else {
$objs[] = new Calendar($this->caldavBackend, $calendar);
}
@ -278,11 +272,9 @@ class CalendarHome implements DAV\IExtendedCollection, DAVACL\IACL {
}
/**
* Returns the owner principal
* Returns the owner of the calendar home.
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
* @return string
*/
function getOwner() {
@ -290,19 +282,6 @@ class CalendarHome implements DAV\IExtendedCollection, DAVACL\IACL {
}
/**
* 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.
*
@ -348,37 +327,6 @@ class CalendarHome implements DAV\IExtendedCollection, DAVACL\IACL {
}
/**
* 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() {
return null;
}
/**
* This method is called when a user replied to a request to share.

View File

@ -11,6 +11,8 @@ namespace Sabre\CalDAV;
*/
class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\DAVACL\IACL {
use \Sabre\DAVACL\ACLTrait;
/**
* Sabre\CalDAV\Backend\BackendInterface
*
@ -191,19 +193,6 @@ class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\
}
/**
* 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.
*
@ -226,22 +215,12 @@ class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\
// The default ACL
return [
[
'privilege' => '{DAV:}read',
'privilege' => '{DAV:}all',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
],
[
'privilege' => '{DAV:}write',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
],
[
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
'protected' => true,
],
[
'privilege' => '{DAV:}write',
'privilege' => '{DAV:}all',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
'protected' => true,
],
@ -255,36 +234,4 @@ class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\
}
/**
* Updates the ACL
*
* This method will receive a list of new ACE's.
*
* @param array $acl
* @return void
*/
function setACL(array $acl) {
throw new \Sabre\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() {
return null;
}
}

View File

@ -170,13 +170,13 @@ class ICSExportPlugin extends DAV\ServerPlugin {
protected function generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, ResponseInterface $response) {
$calDataProp = '{' . Plugin::NS_CALDAV . '}calendar-data';
$calendarNode = $this->server->tree->getNodeForPath($path);
$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' => [
@ -246,17 +246,29 @@ class ICSExportPlugin extends DAV\ServerPlugin {
$mergedCalendar = $mergedCalendar->expand($start, $end, $calendarTimeZone);
}
$response->setHeader('Content-Type', $format);
$filenameExtension = '.ics';
switch ($format) {
case 'text/calendar' :
$mergedCalendar = $mergedCalendar->serialize();
$filenameExtension = '.ics';
break;
case 'application/calendar+json' :
$mergedCalendar = json_encode($mergedCalendar->jsonSerialize());
$filenameExtension = '.json';
break;
}
$filename = preg_replace(
'/[^a-zA-Z0-9-_ ]/um',
'',
$calendarNode->getName()
);
$filename .= '-' . date('Y-m-d') . $filenameExtension;
$response->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
$response->setHeader('Content-Type', $format);
$response->setStatus(200);
$response->setBody($mergedCalendar);
@ -272,11 +284,11 @@ class ICSExportPlugin extends DAV\ServerPlugin {
function mergeObjects(array $properties, array $inputObjects) {
$calendar = new VObject\Component\VCalendar();
$calendar->version = '2.0';
$calendar->VERSION = '2.0';
if (DAV\Server::$exposeVersion) {
$calendar->prodid = '-//SabreDAV//SabreDAV ' . DAV\Version::VERSION . '//EN';
$calendar->PRODID = '-//SabreDAV//SabreDAV ' . DAV\Version::VERSION . '//EN';
} else {
$calendar->prodid = '-//SabreDAV//SabreDAV//EN';
$calendar->PRODID = '-//SabreDAV//SabreDAV//EN';
}
if (isset($properties['{DAV:}displayname'])) {
$calendar->{'X-WR-CALNAME'} = $properties['{DAV:}displayname'];

View File

@ -1,48 +0,0 @@
<?php
namespace Sabre\CalDAV;
/**
* This interface represents a Calendar that can be shared with other users.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface IShareableCalendar extends ICalendar {
/**
* Updates the list of shares.
*
* The first array is a list of people that are to be added to the
* calendar.
*
* Every element in the add array has the following properties:
* * href - A url. Usually a mailto: address
* * commonName - Usually a first and last name, or false
* * summary - A description of the share, can also be false
* * readOnly - A boolean value
*
* Every element in the remove array is just the address string.
*
* @param array $add
* @param array $remove
* @return void
*/
function updateShares(array $add, array $remove);
/**
* Returns the list of people whom this calendar is shared with.
*
* Every element in this array should have the following properties:
* * href - Often a mailto: address
* * commonName - Optional, for example a first + last name
* * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
* * readOnly - boolean
* * summary - Optional, a description for the share
*
* @return array
*/
function getShares();
}

View File

@ -2,6 +2,8 @@
namespace Sabre\CalDAV;
use Sabre\DAV\Sharing\ISharedNode;
/**
* This interface represents a Calendar that is shared by a different user.
*
@ -9,28 +11,16 @@ namespace Sabre\CalDAV;
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface ISharedCalendar extends ICalendar {
interface ISharedCalendar extends ISharedNode {
/**
* This method should return the url of the owners' copy of the shared
* calendar.
* Marks this calendar as published.
*
* @return string
* Publishing a calendar should automatically create a read-only, public,
* subscribable calendar.
*
* @param bool $value
* @return void
*/
function getSharedUrl();
/**
* Returns the list of people whom this calendar is shared with.
*
* Every element in this array should have the following properties:
* * href - Often a mailto: address
* * commonName - Optional, for example a first + last name
* * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
* * readOnly - boolean
* * summary - Optional, a description for the share
*
* @return array
*/
function getShares();
function setPublishStatus($value);
}

View File

@ -22,6 +22,8 @@ use Sabre\DAVACL;
*/
class Collection extends DAV\Collection implements ICollection, DAVACL\IACL {
use DAVACL\ACLTrait;
/**
* The notification backend
*
@ -96,78 +98,4 @@ class Collection extends DAV\Collection implements ICollection, DAVACL\IACL {
}
/**
* 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 [
[
'principal' => $this->getOwner(),
'privilege' => '{DAV:}read',
'protected' => true,
],
[
'principal' => $this->getOwner(),
'privilege' => '{DAV:}write',
'protected' => true,
]
];
}
/**
* Updates the ACL
*
* This method will receive a list of new ACE's as an array argument.
*
* @param array $acl
* @return void
*/
function setACL(array $acl) {
throw new DAV\Exception\NotImplemented('Updating ACLs is not implemented here');
}
/**
* 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;
}
}

View File

@ -20,6 +20,8 @@ use Sabre\DAVACL;
*/
class Node extends DAV\File implements INode, DAVACL\IACL {
use DAVACL\ACLTrait;
/**
* The notification backend
*
@ -116,78 +118,4 @@ class Node extends DAV\File implements INode, DAVACL\IACL {
}
/**
* 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 [
[
'principal' => $this->getOwner(),
'privilege' => '{DAV:}read',
'protected' => true,
],
[
'principal' => $this->getOwner(),
'privilege' => '{DAV:}write',
'protected' => true,
]
];
}
/**
* Updates the ACL
*
* This method will receive a list of new ACE's as an array argument.
*
* @param array $acl
* @return void
*/
function setACL(array $acl) {
throw new DAV\Exception\NotImplemented('Updating ACLs is not implemented here');
}
/**
* 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;
}
}

View File

@ -5,8 +5,9 @@ namespace Sabre\CalDAV;
use DateTimeZone;
use Sabre\DAV;
use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\INode;
use Sabre\DAV\MkCol;
use Sabre\DAV\Xml\Property\Href;
use Sabre\DAV\Xml\Property\LocalHref;
use Sabre\DAVACL;
use Sabre\VObject;
use Sabre\HTTP;
@ -186,6 +187,7 @@ class Plugin extends DAV\ServerPlugin {
$server->on('beforeCreateFile', [$this, 'beforeCreateFile']);
$server->on('beforeWriteContent', [$this, 'beforeWriteContent']);
$server->on('afterMethod:GET', [$this, 'httpAfterGET']);
$server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']);
$server->xml->namespaceMap[self::NS_CALDAV] = 'cal';
$server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs';
@ -233,9 +235,10 @@ class Plugin extends DAV\ServerPlugin {
*
* @param string $reportName
* @param mixed $report
* @param mixed $path
* @return bool
*/
function report($reportName, $report) {
function report($reportName, $report, $path) {
switch ($reportName) {
case '{' . self::NS_CALDAV . '}calendar-multiget' :
@ -341,7 +344,7 @@ class Plugin extends DAV\ServerPlugin {
$calendarHomePath = $this->getCalendarHomeForPrincipal($principalUrl);
if (is_null($calendarHomePath)) return null;
return new Href($calendarHomePath . '/');
return new LocalHref($calendarHomePath . '/');
});
// The calendar-user-address-set property is basically mapped to
@ -349,7 +352,7 @@ class Plugin extends DAV\ServerPlugin {
$propFind->handle('{' . self::NS_CALDAV . '}calendar-user-address-set', function() use ($node) {
$addresses = $node->getAlternateUriSet();
$addresses[] = $this->server->getBaseUri() . $node->getPrincipalUrl() . '/';
return new Href($addresses, false);
return new LocalHref($addresses);
});
// For some reason somebody thought it was a good idea to add
// another one of these properties. We're supporting it too.
@ -394,8 +397,8 @@ class Plugin extends DAV\ServerPlugin {
}
$propFind->set($propRead, new Href($readList));
$propFind->set($propWrite, new Href($writeList));
$propFind->set($propRead, new LocalHref($readList));
$propFind->set($propWrite, new LocalHref($writeList));
}
@ -821,11 +824,7 @@ class Plugin extends DAV\ServerPlugin {
$data = stream_get_contents($data);
}
$before = md5($data);
// Converting the data to unicode, if needed.
$data = DAV\StringUtil::ensureUTF8($data);
if ($before !== md5($data)) $modified = true;
$before = $data;
try {
@ -865,7 +864,7 @@ class Plugin extends DAV\ServerPlugin {
}
$foundType = null;
$foundUID = null;
foreach ($vobj->getComponents() as $component) {
switch ($component->name) {
case 'VTIMEZONE' :
@ -873,31 +872,59 @@ class Plugin extends DAV\ServerPlugin {
case 'VEVENT' :
case 'VTODO' :
case 'VJOURNAL' :
if (is_null($foundType)) {
$foundType = $component->name;
if (!in_array($foundType, $supportedComponents)) {
throw new Exception\InvalidComponentType('This calendar only supports ' . implode(', ', $supportedComponents) . '. We found a ' . $foundType);
}
if (!isset($component->UID)) {
throw new DAV\Exception\BadRequest('Every ' . $component->name . ' component must have an UID');
}
$foundUID = (string)$component->UID;
} else {
if ($foundType !== $component->name) {
throw new DAV\Exception\BadRequest('A calendar object must only contain 1 component. We found a ' . $component->name . ' as well as a ' . $foundType);
}
if ($foundUID !== (string)$component->UID) {
throw new DAV\Exception\BadRequest('Every ' . $component->name . ' in this object must have identical UIDs');
}
}
$foundType = $component->name;
break;
default :
throw new DAV\Exception\BadRequest('You are not allowed to create components of type: ' . $component->name . ' here');
}
}
if (!$foundType || !in_array($foundType, $supportedComponents)) {
throw new Exception\InvalidComponentType('iCalendar objects must at least have a component of type ' . implode(', ', $supportedComponents));
}
$options = VObject\Node::PROFILE_CALDAV;
$prefer = $this->server->getHTTPPrefer();
if ($prefer['handling'] !== 'strict') {
$options |= VObject\Node::REPAIR;
}
$messages = $vobj->validate($options);
$highestLevel = 0;
$warningMessage = null;
// $messages contains a list of problems with the vcard, along with
// their severity.
foreach ($messages as $message) {
if ($message['level'] > $highestLevel) {
// Recording the highest reported error level.
$highestLevel = $message['level'];
$warningMessage = $message['message'];
}
switch ($message['level']) {
case 1 :
// Level 1 means that there was a problem, but it was repaired.
$modified = true;
break;
case 2 :
// Level 2 means a warning, but not critical
break;
case 3 :
// Level 3 means a critical error
throw new DAV\Exception\UnsupportedMediaType('Validation error in iCalendar: ' . $message['message']);
}
}
if ($warningMessage) {
$response->setHeader(
'X-Sabre-Ew-Gross',
'iCalendar validation warning: ' . $warningMessage
);
}
if (!$foundType)
throw new DAV\Exception\BadRequest('iCalendar object must contain at least 1 of VEVENT, VTODO or VJOURNAL');
// We use an extra variable to allow event handles to tell us wether
// the object was modified or not.
@ -917,12 +944,12 @@ class Plugin extends DAV\ServerPlugin {
]
);
if ($subModified) {
if ($modified || $subModified) {
// An event handler told us that it modified the object.
$data = $vobj->serialize();
// Using md5 to figure out if there was an *actual* change.
if (!$modified && $before !== md5($data)) {
if (!$modified && strcmp($data, $before) !== 0) {
$modified = true;
}
@ -933,6 +960,22 @@ class Plugin extends DAV\ServerPlugin {
}
/**
* This method is triggered whenever a subsystem reqeuests the privileges
* that are supported on a particular node.
*
* @param INode $node
* @param array $supportedPrivilegeSet
*/
function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet) {
if ($node instanceof ICalendar) {
$supportedPrivilegeSet['{DAV:}read']['aggregates']['{' . self::NS_CALDAV . '}read-free-busy'] = [
'abstract' => false,
'aggregates' => [],
];
}
}
/**
* This method is used to generate HTML output for the

View File

@ -17,6 +17,8 @@ use Sabre\VObject;
*/
class Inbox extends DAV\Collection implements IInbox {
use DAVACL\ACLTrait;
/**
* CalDAV backend
*
@ -118,19 +120,6 @@ class Inbox extends DAV\Collection implements IInbox {
}
/**
* 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.
*
@ -167,12 +156,7 @@ class Inbox extends DAV\Collection implements IInbox {
'protected' => true,
],
[
'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-deliver-invite',
'principal' => '{DAV:}authenticated',
'protected' => true,
],
[
'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-deliver-reply',
'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-deliver',
'principal' => '{DAV:}authenticated',
'protected' => true,
],
@ -180,48 +164,6 @@ class Inbox extends DAV\Collection implements IInbox {
}
/**
* 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.
*

View File

@ -19,6 +19,8 @@ use Sabre\DAVACL;
*/
class Outbox extends DAV\Collection implements IOutbox {
use DAVACL\ACLTrait;
/**
* The principal Uri
*
@ -74,19 +76,6 @@ class Outbox extends DAV\Collection implements IOutbox {
}
/**
* 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.
*
@ -103,12 +92,7 @@ class Outbox extends DAV\Collection implements IOutbox {
return [
[
'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy',
'principal' => $this->getOwner(),
'protected' => true,
],
[
'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent',
'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-send',
'principal' => $this->getOwner(),
'protected' => true,
],
@ -118,12 +102,7 @@ class Outbox extends DAV\Collection implements IOutbox {
'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',
'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-send',
'principal' => $this->getOwner() . '/calendar-proxy-write',
'protected' => true,
],
@ -141,44 +120,4 @@ class Outbox extends DAV\Collection implements IOutbox {
}
/**
* 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() {
$default = DAVACL\Plugin::getDefaultSupportedPrivilegeSet();
$default['aggregates'][] = [
'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy',
];
$default['aggregates'][] = [
'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent',
];
return $default;
}
}

View File

@ -5,10 +5,12 @@ namespace Sabre\CalDAV\Schedule;
use DateTimeZone;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\DAV\Sharing;
use Sabre\DAV\PropFind;
use Sabre\DAV\PropPatch;
use Sabre\DAV\INode;
use Sabre\DAV\Xml\Property\Href;
use Sabre\DAV\Xml\Property\LocalHref;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\VObject;
@ -100,12 +102,13 @@ class Plugin extends ServerPlugin {
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']);
$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']);
$server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']);
$ns = '{' . self::NS_CALDAV . '}';
@ -215,7 +218,7 @@ class Plugin extends ServerPlugin {
}
$outboxPath = $calendarHomePath . '/outbox/';
return new Href($outboxPath);
return new LocalHref($outboxPath);
});
// schedule-inbox-URL property
@ -227,7 +230,7 @@ class Plugin extends ServerPlugin {
}
$inboxPath = $calendarHomePath . '/inbox/';
return new Href($inboxPath);
return new LocalHref($inboxPath);
});
@ -245,18 +248,28 @@ class Plugin extends ServerPlugin {
$result = $this->server->getPropertiesForPath($calendarHomePath, [
'{DAV:}resourcetype',
'{DAV:}share-access',
$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.
if (!isset($child[200]['{DAV:}resourcetype']) || !$child[200]['{DAV:}resourcetype']->is('{' . self::NS_CALDAV . '}calendar')) {
// Node is either not a calendar
continue;
}
if (isset($child[200]['{DAV:}share-access'])) {
$shareAccess = $child[200]['{DAV:}share-access']->getValue();
if ($shareAccess !== Sharing\Plugin::ACCESS_NOTSHARED && $shareAccess !== Sharing\Plugin::ACCESS_SHAREDOWNER) {
// Node is a shared node, not owned by the relevant
// user.
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']);
return new LocalHref($child['href']);
}
}
@ -492,7 +505,7 @@ class Plugin extends ServerPlugin {
}
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';
$iTipMessage->scheduleStatus = '3.8;insufficient privileges: ' . $privilege . ' is required on the recipient schedule inbox.';
return;
}
@ -560,6 +573,65 @@ class Plugin extends ServerPlugin {
}
/**
* This method is triggered whenever a subsystem requests the privileges
* that are supported on a particular node.
*
* We need to add a number of privileges for scheduling purposes.
*
* @param INode $node
* @param array $supportedPrivilegeSet
*/
function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet) {
$ns = '{' . self::NS_CALDAV . '}';
if ($node instanceof IOutbox) {
$supportedPrivilegeSet[$ns . 'schedule-send'] = [
'abstract' => false,
'aggregates' => [
$ns . 'schedule-send-invite' => [
'abstract' => false,
'aggregates' => [],
],
$ns . 'schedule-send-reply' => [
'abstract' => false,
'aggregates' => [],
],
$ns . 'schedule-send-freebusy' => [
'abstract' => false,
'aggregates' => [],
],
// Privilege from an earlier scheduling draft, but still
// used by some clients.
$ns . 'schedule-post-vevent' => [
'abstract' => false,
'aggregates' => [],
],
]
];
}
if ($node instanceof IInbox) {
$supportedPrivilegeSet[$ns . 'schedule-deliver'] = [
'abstract' => false,
'aggregates' => [
$ns . 'schedule-deliver-invite' => [
'abstract' => false,
'aggregates' => [],
],
$ns . 'schedule-deliver-reply' => [
'abstract' => false,
'aggregates' => [],
],
$ns . 'schedule-query-freebusy' => [
'abstract' => false,
'aggregates' => [],
],
]
];
}
}
/**
* This method looks at an old iCalendar object, a new iCalendar object and
* starts sending scheduling messages based on the changes.
@ -647,7 +719,7 @@ class Plugin extends ServerPlugin {
/**
* This method handles POST requests to the schedule-outbox.
*
* Currently, two types of requests are support:
* Currently, two types of requests are supported:
* * FREEBUSY requests from RFC 6638
* * Simple iTIP messages from draft-desruisseaux-caldav-sched-04
*
@ -699,7 +771,7 @@ class Plugin extends ServerPlugin {
if ($componentType === 'VFREEBUSY' && $method === 'REQUEST') {
$acl && $acl->checkPrivileges($outboxPath, '{' . self::NS_CALDAV . '}schedule-query-freebusy');
$acl && $acl->checkPrivileges($outboxPath, '{' . self::NS_CALDAV . '}schedule-send-freebusy');
$this->handleFreeBusyRequest($outboxNode, $vObject, $request, $response);
// Destroy circular references so PHP can GC the object.
@ -727,7 +799,7 @@ class Plugin extends ServerPlugin {
protected function handleFreeBusyRequest(IOutbox $outbox, VObject\Component $vObject, RequestInterface $request, ResponseInterface $response) {
$vFreeBusy = $vObject->VFREEBUSY;
$organizer = $vFreeBusy->organizer;
$organizer = $vFreeBusy->ORGANIZER;
$organizer = (string)$organizer;
@ -863,6 +935,9 @@ class Plugin extends ServerPlugin {
$homeSet = $result[0][200][$caldavNS . 'calendar-home-set']->getHref();
$inboxUrl = $result[0][200][$caldavNS . 'schedule-inbox-URL']->getHref();
// Do we have permission?
$aclPlugin->checkPrivileges($inboxUrl, $caldavNS . 'schedule-query-freebusy');
// Grabbing the calendar list
$objects = [];
$calendarTimeZone = new DateTimeZone('UTC');
@ -882,8 +957,6 @@ class Plugin extends ServerPlugin {
continue;
}
$aclPlugin->checkPrivileges($homeSet . $node->getName(), $caldavNS . 'read-free-busy');
if (isset($props[$ctz])) {
$vtimezoneObj = VObject\Reader::read($props[$ctz]);
$calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();

View File

@ -134,22 +134,12 @@ class SchedulingObject extends \Sabre\CalDAV\CalendarObject implements IScheduli
// The default ACL
return [
[
'privilege' => '{DAV:}read',
'principal' => $this->objectData['principaluri'],
'privilege' => '{DAV:}all',
'principal' => '{DAV:}owner',
'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',
'privilege' => '{DAV:}all',
'principal' => $this->objectData['principaluri'] . '/calendar-proxy-write',
'protected' => true,
],

View File

@ -1,72 +0,0 @@
<?php
namespace Sabre\CalDAV;
/**
* This object represents a CalDAV calendar that can be shared with other
* users.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class ShareableCalendar extends Calendar implements IShareableCalendar {
/**
* Updates the list of shares.
*
* The first array is a list of people that are to be added to the
* calendar.
*
* Every element in the add array has the following properties:
* * href - A url. Usually a mailto: address
* * commonName - Usually a first and last name, or false
* * summary - A description of the share, can also be false
* * readOnly - A boolean value
*
* Every element in the remove array is just the address string.
*
* @param array $add
* @param array $remove
* @return void
*/
function updateShares(array $add, array $remove) {
$this->caldavBackend->updateShares($this->calendarInfo['id'], $add, $remove);
}
/**
* Returns the list of people whom this calendar is shared with.
*
* Every element in this array should have the following properties:
* * href - Often a mailto: address
* * commonName - Optional, for example a first + last name
* * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
* * readOnly - boolean
* * summary - Optional, a description for the share
*
* @return array
*/
function getShares() {
return $this->caldavBackend->getShares($this->calendarInfo['id']);
}
/**
* Marks this calendar as published.
*
* Publishing a calendar should automatically create a read-only, public,
* subscribable calendar.
*
* @param bool $value
* @return void
*/
function setPublishStatus($value) {
$this->caldavBackend->setPublishStatus($this->calendarInfo['id'], $value);
}
}

View File

@ -2,6 +2,8 @@
namespace Sabre\CalDAV;
use Sabre\DAV\Sharing\Plugin as SPlugin;
/**
* This object represents a CalDAV calendar that is shared by a different user.
*
@ -12,50 +14,84 @@ namespace Sabre\CalDAV;
class SharedCalendar extends Calendar implements ISharedCalendar {
/**
* Constructor
* Returns the 'access level' for the instance of this shared resource.
*
* @param Backend\BackendInterface $caldavBackend
* @param array $calendarInfo
* The value should be one of the Sabre\DAV\Sharing\Plugin::ACCESS_
* constants.
*
* @return int
*/
function __construct(Backend\BackendInterface $caldavBackend, $calendarInfo) {
function getShareAccess() {
$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)');
}
}
parent::__construct($caldavBackend, $calendarInfo);
return isset($this->calendarInfo['share-access']) ? $this->calendarInfo['share-access'] : SPlugin::ACCESS_NOTSHARED;
}
/**
* This method should return the url of the owners' copy of the shared
* calendar.
* This function must return a URI that uniquely identifies the shared
* resource. This URI should be identical across instances, and is
* also used in several other XML bodies to connect invites to
* resources.
*
* This may simply be a relative reference to the original shared instance,
* but it could also be a urn. As long as it's a valid URI and unique.
*
* @return string
*/
function getSharedUrl() {
function getShareResourceUri() {
return $this->calendarInfo['{http://calendarserver.org/ns/}shared-url'];
return $this->calendarInfo['share-resource-uri'];
}
/**
* Returns the owner principal
* Updates the list of sharees.
*
* This must be a url to a principal, or null if there's no owner
* Every item must be a Sharee object.
*
* @return string|null
* @param \Sabre\DAV\Xml\Element\Sharee[] $sharees
* @return void
*/
function getOwner() {
function updateInvites(array $sharees) {
return $this->calendarInfo['{http://sabredav.org/ns}owner-principal'];
$this->caldavBackend->updateInvites($this->calendarInfo['id'], $sharees);
}
/**
* Returns the list of people whom this resource is shared with.
*
* Every item in the returned array must be a Sharee object with
* at least the following properties set:
*
* * $href
* * $shareAccess
* * $inviteStatus
*
* and optionally:
*
* * $properties
*
* @return \Sabre\DAV\Xml\Element\Sharee[]
*/
function getInvites() {
return $this->caldavBackend->getInvites($this->calendarInfo['id']);
}
/**
* Marks this calendar as published.
*
* Publishing a calendar should automatically create a read-only, public,
* subscribable calendar.
*
* @param bool $value
* @return void
*/
function setPublishStatus($value) {
$this->caldavBackend->setPublishStatus($this->calendarInfo['id'], $value);
}
@ -73,32 +109,72 @@ class SharedCalendar extends Calendar implements ISharedCalendar {
*/
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[] = [
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
];
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,
];
$acl = [];
switch ($this->getShareAccess()) {
case SPlugin::ACCESS_NOTSHARED :
case SPlugin::ACCESS_SHAREDOWNER :
$acl[] = [
'privilege' => '{DAV:}share',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
];
$acl[] = [
'privilege' => '{DAV:}share',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
'protected' => true,
];
// No break intentional!
case SPlugin::ACCESS_READWRITE :
$acl[] = [
'privilege' => '{DAV:}write',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
];
$acl[] = [
'privilege' => '{DAV:}write',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
'protected' => true,
];
// No break intentional!
case SPlugin::ACCESS_READ :
$acl[] = [
'privilege' => '{DAV:}write-properties',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
];
$acl[] = [
'privilege' => '{DAV:}write-properties',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
'protected' => true,
];
$acl[] = [
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
];
$acl[] = [
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read',
'protected' => true,
];
$acl[] = [
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
'protected' => true,
];
$acl[] = [
'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy',
'principal' => '{DAV:}authenticated',
'protected' => true,
];
break;
}
return $acl;
}
/**
* This method returns the ACL's for calendar objects in this calendar.
* The result of this method automatically gets passed to the
@ -108,41 +184,46 @@ class SharedCalendar extends Calendar implements ISharedCalendar {
*/
function getChildACL() {
$acl = parent::getChildACL();
$acl[] = [
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
];
$acl = [];
if (!$this->calendarInfo['{http://sabredav.org/ns}read-only']) {
$acl[] = [
'privilege' => '{DAV:}write',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
];
switch ($this->getShareAccess()) {
case SPlugin::ACCESS_NOTSHARED :
// No break intentional
case SPlugin::ACCESS_SHAREDOWNER :
// No break intentional
case SPlugin::ACCESS_READWRITE:
$acl[] = [
'privilege' => '{DAV:}write',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
];
$acl[] = [
'privilege' => '{DAV:}write',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
'protected' => true,
];
// No break intentional
case SPlugin::ACCESS_READ:
$acl[] = [
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
];
$acl[] = [
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
'protected' => true,
];
$acl[] = [
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read',
'protected' => true,
];
break;
}
return $acl;
}
/**
* Returns the list of people whom this calendar is shared with.
*
* Every element in this array should have the following properties:
* * href - Often a mailto: address
* * commonName - Optional, for example a first + last name
* * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
* * readOnly - boolean
* * summary - Optional, a description for the share
*
* @return array
*/
function getShares() {
return $this->caldavBackend->getShares($this->calendarInfo['id']);
}
}

View File

@ -4,6 +4,7 @@ namespace Sabre\CalDAV;
use Sabre\DAV;
use Sabre\DAV\Xml\Property\Href;
use Sabre\DAV\Xml\Property\LocalHref;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
@ -25,15 +26,6 @@ use Sabre\HTTP\ResponseInterface;
*/
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.
*
@ -83,7 +75,10 @@ class SharingPlugin extends DAV\ServerPlugin {
function initialize(DAV\Server $server) {
$this->server = $server;
$server->resourceTypeMapping['Sabre\\CalDAV\\ISharedCalendar'] = '{' . Plugin::NS_CALENDARSERVER . '}shared';
if (is_null($this->server->getPlugin('sharing'))) {
throw new \LogicException('The generic "sharing" plugin must be loaded before the caldav sharing plugin. Call $server->addPlugin(new \Sabre\DAV\Sharing\Plugin()); before this one.');
}
array_push(
$this->server->protectedProperties,
@ -114,24 +109,8 @@ class SharingPlugin extends DAV\ServerPlugin {
*/
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
@ -158,7 +137,7 @@ class SharingPlugin extends DAV\ServerPlugin {
}
return new Xml\Property\Invite(
$node->getShares(),
$node->getInvites(),
$ownerInfo
);
@ -179,10 +158,18 @@ class SharingPlugin extends DAV\ServerPlugin {
*/
function propFindLate(DAV\PropFind $propFind, DAV\INode $node) {
if ($node instanceof IShareableCalendar) {
if ($node instanceof ISharedCalendar) {
$shareAccess = $node->getShareAccess();
if ($rt = $propFind->get('{DAV:}resourcetype')) {
if (count($node->getShares()) > 0) {
$rt->add('{' . Plugin::NS_CALENDARSERVER . '}shared-owner');
switch ($shareAccess) {
case \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER :
$rt->add('{' . Plugin::NS_CALENDARSERVER . '}shared-owner');
break;
case \Sabre\DAV\Sharing\Plugin::ACCESS_READ :
case \Sabre\DAV\Sharing\Plugin::ACCESS_READWRITE :
$rt->add('{' . Plugin::NS_CALENDARSERVER . '}shared');
break;
}
}
$propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes', function() {
@ -211,21 +198,24 @@ class SharingPlugin extends DAV\ServerPlugin {
function propPatch($path, DAV\PropPatch $propPatch) {
$node = $this->server->tree->getNodeForPath($path);
if (!$node instanceof IShareableCalendar)
if (!$node instanceof ISharedCalendar)
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);
if ($node->getShareAccess() === \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER || $node->getShareAccess() === \Sabre\DAV\Sharing\Plugin::ACCESS_NOTSHARED) {
return true;
$propPatch->handle('{DAV:}resourcetype', function($value) use ($node) {
if ($value->is('{' . Plugin::NS_CALENDARSERVER . '}shared-owner')) return false;
$shares = $node->getInvites();
foreach ($shares as $share) {
$share->access = DAV\Sharing\Plugin::ACCESS_NOACCESS;
}
$node->updateInvites($shares);
});
return true;
});
}
}
@ -267,26 +257,12 @@ class SharingPlugin extends DAV\ServerPlugin {
switch ($documentType) {
// Dealing with the 'share' document, which modified invitees on a
// calendar.
// Both the DAV:share-resource and CALENDARSERVER:share requests
// behave identically.
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);
$sharingPlugin = $this->server->getPlugin('sharing');
$sharingPlugin->shareResource($path, $message->sharees);
$response->setStatus(200);
// Adding this because sending a response body may cause issues,
@ -328,11 +304,11 @@ class SharingPlugin extends DAV\ServerPlugin {
$response->setHeader('X-Sabre-Status', 'everything-went-well');
if ($url) {
$writer = $this->server->xml->getWriter($this->server->getBaseUri());
$writer = $this->server->xml->getWriter();
$writer->openMemory();
$writer->startDocument();
$writer->startElement('{' . Plugin::NS_CALENDARSERVER . '}shared-as');
$writer->write(new Href($url));
$writer->write(new LocalHref($url));
$writer->endElement();
$response->setHeader('Content-Type', 'application/xml');
$response->setBody($writer->outputMemory());
@ -345,7 +321,7 @@ class SharingPlugin extends DAV\ServerPlugin {
case '{' . Plugin::NS_CALENDARSERVER . '}publish-calendar' :
// We can only deal with IShareableCalendar objects
if (!$node instanceof IShareableCalendar) {
if (!$node instanceof ISharedCalendar) {
return;
}
$this->server->transactionType = 'post-publish-calendar';
@ -355,7 +331,7 @@ class SharingPlugin extends DAV\ServerPlugin {
// If there's no ACL support, we allow everything
if ($acl) {
$acl->checkPrivileges($path, '{DAV:}write');
$acl->checkPrivileges($path, '{DAV:}share');
}
$node->setPublishStatus(true);
@ -373,7 +349,7 @@ class SharingPlugin extends DAV\ServerPlugin {
case '{' . Plugin::NS_CALENDARSERVER . '}unpublish-calendar' :
// We can only deal with IShareableCalendar objects
if (!$node instanceof IShareableCalendar) {
if (!$node instanceof ISharedCalendar) {
return;
}
$this->server->transactionType = 'post-unpublish-calendar';
@ -383,7 +359,7 @@ class SharingPlugin extends DAV\ServerPlugin {
// If there's no ACL support, we allow everything
if ($acl) {
$acl->checkPrivileges($path, '{DAV:}write');
$acl->checkPrivileges($path, '{DAV:}share');
}
$node->setPublishStatus(false);

View File

@ -5,8 +5,8 @@ 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\DAVACL\ACLTrait;
use Sabre\CalDAV\Backend\SubscriptionSupport;
/**
@ -20,6 +20,8 @@ use Sabre\CalDAV\Backend\SubscriptionSupport;
*/
class Subscription extends Collection implements ISubscription, IACL {
use ACLTrait;
/**
* caldavBackend
*
@ -144,7 +146,7 @@ class Subscription extends Collection implements ISubscription, IACL {
* The Server class will filter out the extra.
*
* @param array $properties
* @return void
* @return array
*/
function getProperties($properties) {
@ -154,7 +156,7 @@ class Subscription extends Collection implements ISubscription, IACL {
switch ($prop) {
case '{http://calendarserver.org/ns/}source' :
$r[$prop] = new Href($this->subscriptionInfo['source'], false);
$r[$prop] = new Href($this->subscriptionInfo['source']);
break;
default :
if (array_key_exists($prop, $this->subscriptionInfo)) {
@ -182,19 +184,6 @@ class Subscription extends Collection implements ISubscription, IACL {
}
/**
* 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.
*
@ -211,22 +200,12 @@ class Subscription extends Collection implements ISubscription, IACL {
return [
[
'privilege' => '{DAV:}read',
'privilege' => '{DAV:}all',
'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',
'privilege' => '{DAV:}all',
'principal' => $this->getOwner() . '/calendar-proxy-write',
'protected' => true,
],
@ -239,36 +218,4 @@ class Subscription extends Collection implements ISubscription, IACL {
}
/**
* 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;
}
}

View File

@ -5,6 +5,7 @@ namespace Sabre\CalDAV\Xml\Notification;
use Sabre\Xml\Writer;
use Sabre\CalDAV\SharingPlugin as SharingPlugin;
use Sabre\CalDAV;
use Sabre\DAV;
/**
* This class represents the cs:invite-notification notification element.
@ -210,16 +211,10 @@ class Invite implements NotificationInterface {
switch ($this->type) {
case SharingPlugin::STATUS_ACCEPTED :
case DAV\Sharing\Plugin::INVITE_ACCEPTED :
$writer->writeElement($cs . 'invite-accepted');
break;
case SharingPlugin::STATUS_DECLINED :
$writer->writeElement($cs . 'invite-declined');
break;
case SharingPlugin::STATUS_DELETED :
$writer->writeElement($cs . 'invite-deleted');
break;
case SharingPlugin::STATUS_NORESPONSE :
case DAV\Sharing\Plugin::INVITE_NORESPONSE :
$writer->writeElement($cs . 'invite-noresponse');
break;

View File

@ -5,6 +5,7 @@ namespace Sabre\CalDAV\Xml\Notification;
use Sabre\Xml\Writer;
use Sabre\CalDAV;
use Sabre\CalDAV\SharingPlugin;
use Sabre\DAV;
/**
* This class represents the cs:invite-reply notification element.
@ -162,10 +163,10 @@ class InviteReply implements NotificationInterface {
switch ($this->type) {
case SharingPlugin::STATUS_ACCEPTED :
case DAV\Sharing\Plugin::INVITE_ACCEPTED :
$writer->writeElement($cs . 'invite-accepted');
break;
case SharingPlugin::STATUS_DECLINED :
case DAV\Sharing\Plugin::INVITE_DECLINED :
$writer->writeElement($cs . 'invite-declined');
break;

View File

@ -2,11 +2,10 @@
namespace Sabre\CalDAV\Xml\Property;
use Sabre\Xml\Element;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlSerializable;
use Sabre\Xml\Writer;
use Sabre\CalDAV\Plugin;
use Sabre\CalDAV\SharingPlugin;
use Sabre\DAV;
/**
* Invite property
@ -20,53 +19,23 @@ use Sabre\CalDAV\SharingPlugin;
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Invite implements Element {
class Invite implements XmlSerializable {
/**
* The list of users a calendar has been shared to.
*
* @var array
* @var Sharee[]
*/
protected $users;
/**
* The organizer contains information about the person who shared the
* object.
*
* @var array
*/
protected $organizer;
protected $sharees;
/**
* Creates the property.
*
* Users is an array. Each element of the array has the following
* properties:
*
* * href - Often a mailto: address
* * commonName - Optional, for example a first and lastname for a user.
* * status - One of the SharingPlugin::STATUS_* constants.
* * readOnly - true or false
* * summary - Optional, description of the share
*
* The organizer key is optional to specify. It's only useful when a
* 'sharee' requests the sharing information.
*
* The organizer may have the following properties:
* * href - Often a mailto: address.
* * commonName - Optional human-readable name.
* * firstName - Optional first name.
* * lastName - Optional last name.
*
* If you wonder why these two structures are so different, I guess a
* valid answer is that the current spec is still a draft.
*
* @param array $users
* @param Sharee[] $sharees
*/
function __construct(array $users, array $organizer = null) {
function __construct(array $sharees) {
$this->users = $users;
$this->organizer = $organizer;
$this->sharees = $sharees;
}
@ -77,7 +46,7 @@ class Invite implements Element {
*/
function getValue() {
return $this->users;
return $this->sharees;
}
@ -104,149 +73,55 @@ class Invite implements Element {
$cs = '{' . Plugin::NS_CALENDARSERVER . '}';
if (!is_null($this->organizer)) {
foreach ($this->sharees as $sharee) {
$writer->startElement($cs . 'organizer');
$writer->writeElement('{DAV:}href', $this->organizer['href']);
if (isset($this->organizer['commonName']) && $this->organizer['commonName']) {
$writer->writeElement($cs . 'common-name', $this->organizer['commonName']);
}
if (isset($this->organizer['firstName']) && $this->organizer['firstName']) {
$writer->writeElement($cs . 'first-name', $this->organizer['firstName']);
}
if (isset($this->organizer['lastName']) && $this->organizer['lastName']) {
$writer->writeElement($cs . 'last-name', $this->organizer['lastName']);
}
$writer->endElement(); // organizer
}
foreach ($this->users as $user) {
$writer->startElement($cs . 'user');
$writer->writeElement('{DAV:}href', $user['href']);
if (isset($user['commonName']) && $user['commonName']) {
$writer->writeElement($cs . 'common-name', $user['commonName']);
}
switch ($user['status']) {
case SharingPlugin::STATUS_ACCEPTED :
$writer->writeElement($cs . 'invite-accepted');
break;
case SharingPlugin::STATUS_DECLINED :
$writer->writeElement($cs . 'invite-declined');
break;
case SharingPlugin::STATUS_NORESPONSE :
$writer->writeElement($cs . 'invite-noresponse');
break;
case SharingPlugin::STATUS_INVALID :
$writer->writeElement($cs . 'invite-invalid');
break;
}
$writer->startElement($cs . 'access');
if ($user['readOnly']) {
$writer->writeElement($cs . 'read');
if ($sharee->access === \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER) {
$writer->startElement($cs . 'organizer');
} else {
$writer->writeElement($cs . 'read-write');
}
$writer->endElement(); // access
$writer->startElement($cs . 'user');
if (isset($user['summary']) && $user['summary']) {
$writer->writeElement($cs . 'summary', $user['summary']);
}
$writer->endElement(); //user
}
}
/**
* The deserialize method is called during xml parsing.
*
* This method is called statictly, this is because in theory this method
* may be used as a type of constructor, or factory method.
*
* Often you want to return an instance of the current class, but you are
* free to return other data as well.
*
* You are responsible for advancing the reader to the next element. Not
* doing anything will result in a never-ending loop.
*
* If you just want to skip parsing for this element altogether, you can
* just call $reader->next();
*
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
* the next element.
*
* @param Reader $reader
* @return mixed
*/
static function xmlDeserialize(Reader $reader) {
$cs = '{' . Plugin::NS_CALENDARSERVER . '}';
$users = [];
foreach ($reader->parseInnerTree() as $elem) {
if ($elem['name'] !== $cs . 'user')
continue;
$user = [
'href' => null,
'commonName' => null,
'readOnly' => null,
'summary' => null,
'status' => null,
];
foreach ($elem['value'] as $userElem) {
switch ($userElem['name']) {
case $cs . 'invite-accepted' :
$user['status'] = SharingPlugin::STATUS_ACCEPTED;
switch ($sharee->inviteStatus) {
case DAV\Sharing\Plugin::INVITE_ACCEPTED :
$writer->writeElement($cs . 'invite-accepted');
break;
case $cs . 'invite-declined' :
$user['status'] = SharingPlugin::STATUS_DECLINED;
case DAV\Sharing\Plugin::INVITE_DECLINED :
$writer->writeElement($cs . 'invite-declined');
break;
case $cs . 'invite-noresponse' :
$user['status'] = SharingPlugin::STATUS_NORESPONSE;
case DAV\Sharing\Plugin::INVITE_NORESPONSE :
$writer->writeElement($cs . 'invite-noresponse');
break;
case $cs . 'invite-invalid' :
$user['status'] = SharingPlugin::STATUS_INVALID;
case DAV\Sharing\Plugin::INVITE_INVALID :
$writer->writeElement($cs . 'invite-invalid');
break;
case '{DAV:}href' :
$user['href'] = $userElem['value'];
}
$writer->startElement($cs . 'access');
switch ($sharee->access) {
case DAV\Sharing\Plugin::ACCESS_READWRITE :
$writer->writeElement($cs . 'read-write');
break;
case $cs . 'common-name' :
$user['commonName'] = $userElem['value'];
break;
case $cs . 'access' :
foreach ($userElem['value'] as $accessHref) {
if ($accessHref['name'] === $cs . 'read') {
$user['readOnly'] = true;
}
}
break;
case $cs . 'summary' :
$user['summary'] = $userElem['value'];
case DAV\Sharing\Plugin::ACCESS_READ :
$writer->writeElement($cs . 'read');
break;
}
$writer->endElement(); // access
}
if (!$user['status']) {
throw new \InvalidArgumentException('Every user must have one of cs:invite-accepted, cs:invite-declined, cs:invite-noresponse or cs:invite-invalid');
}
$users[] = $user;
$href = new \Sabre\DAV\Xml\Property\Href($sharee->href);
$href->xmlSerialize($writer);
if (isset($sharee->properties['{DAV:}displayname'])) {
$writer->writeElement($cs . 'common-name', $sharee->properties['{DAV:}displayname']);
}
if ($sharee->comment) {
$writer->writeElement($cs . 'summary', $sharee->comment);
}
$writer->endElement(); // organizer or user
}
return new self($users);
}
}

View File

@ -4,8 +4,8 @@ namespace Sabre\CalDAV\Xml\Property;
use Sabre\Xml\Element;
use Sabre\Xml\Reader;
use Sabre\Xml\Deserializer;
use Sabre\Xml\Writer;
use Sabre\Xml\Element\Elements;
use Sabre\CalDAV\Plugin;
/**
@ -116,23 +116,13 @@ class ScheduleCalendarTransp implements Element {
*/
static function xmlDeserialize(Reader $reader) {
$elems = Elements::xmlDeserialize($reader);
$elems = Deserializer\enum($reader, Plugin::NS_CALDAV);
$value = null;
foreach ($elems as $elem) {
switch ($elem) {
case '{' . Plugin::NS_CALDAV . '}opaque' :
$value = self::OPAQUE;
break;
case '{' . Plugin::NS_CALDAV . '}transparent' :
$value = self::TRANSPARENT;
break;
}
if (in_array('transparent', $elems)) {
$value = self::TRANSPARENT;
} else {
$value = self::OPAQUE;
}
if (is_null($value))
return null;
return new self($value);
}

View File

@ -2,12 +2,13 @@
namespace Sabre\CalDAV\Xml\Request;
use Sabre\CalDAV\Plugin;
use Sabre\CalDAV\SharingPlugin;
use Sabre\DAV;
use Sabre\DAV\Exception\BadRequest;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;
use Sabre\Xml\Element\KeyValue;
use Sabre\DAV\Exception\BadRequest;
use Sabre\CalDAV\Plugin;
use Sabre\CalDAV\SharingPlugin;
/**
* Invite-reply POST request parser
@ -121,10 +122,10 @@ class InviteReply implements XmlDeserializable {
}
break;
case '{' . Plugin::NS_CALENDARSERVER . '}invite-accepted' :
$status = SharingPlugin::STATUS_ACCEPTED;
$status = DAV\Sharing\Plugin::INVITE_ACCEPTED;
break;
case '{' . Plugin::NS_CALENDARSERVER . '}invite-declined' :
$status = SharingPlugin::STATUS_DECLINED;
$status = DAV\Sharing\Plugin::INVITE_DECLINED;
break;
case '{' . Plugin::NS_CALENDARSERVER . '}in-reply-to' :
$inReplyTo = $value;

View File

@ -2,9 +2,10 @@
namespace Sabre\CalDAV\Xml\Request;
use Sabre\CalDAV\Plugin;
use Sabre\DAV\Xml\Element\Sharee;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;
use Sabre\CalDAV\Plugin;
/**
* Share POST request parser
@ -20,37 +21,20 @@ use Sabre\CalDAV\Plugin;
class Share implements XmlDeserializable {
/**
* The list of new people added or updated.
* The list of new people added or updated or removed from the share.
*
* Every element has the following keys:
* 1. href - An email address
* 2. commonName - Some name
* 3. summary - An optional description of the share
* 4. readOnly - true or false
*
* @var array
* @var Sharee[]
*/
public $set = [];
/**
* List of people removed from the share list.
*
* The list is a flat list of email addresses (including mailto:).
*
* @var array
*/
public $remove = [];
public $sharees = [];
/**
* Constructor
*
* @param array $set
* @param array $remove
* @param Sharee[] $sharees
*/
function __construct(array $set, array $remove) {
function __construct(array $sharees) {
$this->set = $set;
$this->remove = $remove;
$this->sharees = $sharees;
}
@ -77,13 +61,12 @@ class Share implements XmlDeserializable {
*/
static function xmlDeserialize(Reader $reader) {
$elems = $reader->parseInnerTree([
$elems = $reader->parseGetElements([
'{' . Plugin::NS_CALENDARSERVER . '}set' => 'Sabre\\Xml\\Element\\KeyValue',
'{' . Plugin::NS_CALENDARSERVER . '}remove' => 'Sabre\\Xml\\Element\\KeyValue',
]);
$set = [];
$remove = [];
$sharees = [];
foreach ($elems as $elem) {
switch ($elem['name']) {
@ -94,22 +77,34 @@ class Share implements XmlDeserializable {
$sumElem = '{' . Plugin::NS_CALENDARSERVER . '}summary';
$commonName = '{' . Plugin::NS_CALENDARSERVER . '}common-name';
$set[] = [
$properties = [];
if (isset($sharee[$commonName])) {
$properties['{DAV:}displayname'] = $sharee[$commonName];
}
$access = array_key_exists('{' . Plugin::NS_CALENDARSERVER . '}read-write', $sharee)
? \Sabre\DAV\Sharing\Plugin::ACCESS_READWRITE
: \Sabre\DAV\Sharing\Plugin::ACCESS_READ;
$sharees[] = new Sharee([
'href' => $sharee['{DAV:}href'],
'commonName' => isset($sharee[$commonName]) ? $sharee[$commonName] : null,
'summary' => isset($sharee[$sumElem]) ? $sharee[$sumElem] : null,
'readOnly' => !array_key_exists('{' . Plugin::NS_CALENDARSERVER . '}read-write', $sharee),
];
'properties' => $properties,
'access' => $access,
'comment' => isset($sharee[$sumElem]) ? $sharee[$sumElem] : null
]);
break;
case '{' . Plugin::NS_CALENDARSERVER . '}remove' :
$remove[] = $elem['value']['{DAV:}href'];
$sharees[] = new Sharee([
'href' => $elem['value']['{DAV:}href'],
'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_NOACCESS
]);
break;
}
}
return new self($set, $remove);
return new self($sharees);
}

View File

@ -16,6 +16,8 @@ use Sabre\DAVACL;
*/
class AddressBook extends DAV\Collection implements IAddressBook, DAV\IProperties, DAVACL\IACL, DAV\Sync\ISyncCollection, DAV\IMultiGet {
use DAVACL\ACLTrait;
/**
* This is an array with addressbook information
*
@ -236,48 +238,6 @@ class AddressBook extends DAV\Collection implements IAddressBook, DAV\IPropertie
}
/**
* 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,
],
];
}
/**
* This method returns the ACL's for card nodes in this address book.
@ -290,12 +250,7 @@ class AddressBook extends DAV\Collection implements IAddressBook, DAV\IPropertie
return [
[
'privilege' => '{DAV:}read',
'principal' => $this->getOwner(),
'protected' => true,
],
[
'privilege' => '{DAV:}write',
'privilege' => '{DAV:}all',
'principal' => $this->getOwner(),
'protected' => true,
],
@ -303,37 +258,6 @@ class AddressBook extends DAV\Collection implements IAddressBook, DAV\IPropertie
}
/**
* 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() {
return null;
}
/**
* This method returns the current sync-token for this collection.

View File

@ -18,6 +18,8 @@ use Sabre\Uri;
*/
class AddressBookHome extends DAV\Collection implements DAV\IExtendedCollection, DAVACL\IACL {
use DAVACL\ACLTrait;
/**
* Principal uri
*
@ -186,78 +188,4 @@ class AddressBookHome extends DAV\Collection implements DAV\IExtendedCollection,
}
/**
* 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->principalUri,
'protected' => true,
],
[
'privilege' => '{DAV:}write',
'principal' => $this->principalUri,
'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('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;
}
}

View File

@ -55,12 +55,15 @@ interface BackendInterface {
function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch);
/**
* Creates a new address book
* Creates a new address book.
*
* This method should return the id of the new address book. The id can be
* in any format, including ints, strings, arrays or objects.
*
* @param string $principalUri
* @param string $url Just the 'basename' of the url.
* @param array $properties
* @return void
* @return mixed
*/
function createAddressBook($principalUri, $url, array $properties);

View File

@ -128,7 +128,7 @@ class PDO extends AbstractBackend implements SyncSupport {
} else {
$query .= ', ';
}
$query .= ' `' . $key . '` = :' . $key . ' ';
$query .= ' ' . $key . ' = :' . $key . ' ';
}
$query .= ' WHERE id = :addressbookid';
@ -180,7 +180,9 @@ class PDO extends AbstractBackend implements SyncSupport {
$query = 'INSERT INTO ' . $this->addressBooksTableName . ' (uri, displayname, description, principaluri, synctoken) VALUES (:uri, :displayname, :description, :principaluri, 1)';
$stmt = $this->pdo->prepare($query);
$stmt->execute($values);
return $this->pdo->lastInsertId();
return $this->pdo->lastInsertId(
$this->addressBooksTableName . '_id_seq'
);
}
@ -230,6 +232,7 @@ class PDO extends AbstractBackend implements SyncSupport {
$result = [];
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
$row['etag'] = '"' . $row['etag'] . '"';
$row['lastmodified'] = (int)$row['lastmodified'];
$result[] = $row;
}
return $result;
@ -258,6 +261,7 @@ class PDO extends AbstractBackend implements SyncSupport {
if (!$result) return false;
$result['etag'] = '"' . $result['etag'] . '"';
$result['lastmodified'] = (int)$result['lastmodified'];
return $result;
}
@ -286,6 +290,7 @@ class PDO extends AbstractBackend implements SyncSupport {
$result = [];
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
$row['etag'] = '"' . $row['etag'] . '"';
$row['lastmodified'] = (int)$row['lastmodified'];
$result[] = $row;
}
return $result;

View File

@ -14,6 +14,8 @@ use Sabre\DAV;
*/
class Card extends DAV\File implements ICard, DAVACL\IACL {
use DAVACL\ACLTrait;
/**
* CardDAV backend
*
@ -181,18 +183,6 @@ class Card extends DAV\File implements ICard, DAVACL\IACL {
}
/**
* 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.
@ -215,12 +205,7 @@ class Card extends DAV\File implements ICard, DAVACL\IACL {
return [
[
'privilege' => '{DAV:}read',
'principal' => $this->addressBookInfo['principaluri'],
'protected' => true,
],
[
'privilege' => '{DAV:}write',
'privilege' => '{DAV:}all',
'principal' => $this->addressBookInfo['principaluri'],
'protected' => true,
],
@ -228,36 +213,4 @@ class Card extends DAV\File implements ICard, DAVACL\IACL {
}
/**
* 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() {
return null;
}
}

View File

@ -4,7 +4,7 @@ namespace Sabre\CardDAV;
use Sabre\DAV;
use Sabre\DAV\Exception\ReportNotSupported;
use Sabre\DAV\Xml\Property\Href;
use Sabre\DAV\Xml\Property\LocalHref;
use Sabre\DAVACL;
use Sabre\HTTP;
use Sabre\HTTP\RequestInterface;
@ -156,11 +156,11 @@ class Plugin extends DAV\ServerPlugin {
$path = $propFind->getPath();
$propFind->handle('{' . self::NS_CARDDAV . '}addressbook-home-set', function() use ($path) {
return new Href($this->getAddressBookHomeForPrincipal($path) . '/');
return new LocalHref($this->getAddressBookHomeForPrincipal($path) . '/');
});
if ($this->directories) $propFind->handle('{' . self::NS_CARDDAV . '}directory-gateway', function() {
return new Href($this->directories);
return new LocalHref($this->directories);
});
}
@ -334,12 +334,7 @@ class Plugin extends DAV\ServerPlugin {
$data = stream_get_contents($data);
}
$before = md5($data);
// Converting the data to unicode, if needed.
$data = DAV\StringUtil::ensureUTF8($data);
if (md5($data) !== $before) $modified = true;
$before = $data;
try {
@ -366,11 +361,56 @@ class Plugin extends DAV\ServerPlugin {
throw new DAV\Exception\UnsupportedMediaType('This collection can only support vcard objects.');
}
if (!isset($vobj->UID)) {
// No UID in vcards is invalid, but we'll just add it in anyway.
$vobj->add('UID', DAV\UUIDUtil::getUUID());
$options = VObject\Node::PROFILE_CARDDAV;
$prefer = $this->server->getHTTPPrefer();
if ($prefer['handling'] !== 'strict') {
$options |= VObject\Node::REPAIR;
}
$messages = $vobj->validate($options);
$highestLevel = 0;
$warningMessage = null;
// $messages contains a list of problems with the vcard, along with
// their severity.
foreach ($messages as $message) {
if ($message['level'] > $highestLevel) {
// Recording the highest reported error level.
$highestLevel = $message['level'];
$warningMessage = $message['message'];
}
switch ($message['level']) {
case 1 :
// Level 1 means that there was a problem, but it was repaired.
$modified = true;
break;
case 2 :
// Level 2 means a warning, but not critical
break;
case 3 :
// Level 3 means a critical error
throw new DAV\Exception\UnsupportedMediaType('Validation error in vCard: ' . $message['message']);
}
}
if ($warningMessage) {
$this->server->httpResponse->setHeader(
'X-Sabre-Ew-Gross',
'vCard validation warning: ' . $warningMessage
);
// Re-serializing object.
$data = $vobj->serialize();
$modified = true;
if (!$modified && strcmp($data, $before) !== 0) {
// This ensures that the system does not send an ETag back.
$modified = true;
}
}
// Destroy circular references to PHP will GC the object.
@ -803,33 +843,49 @@ class Plugin extends DAV\ServerPlugin {
/**
* Converts a vcard blob to a different version, or jcard.
*
* @param string $data
* @param string|resource $data
* @param string $target
* @return string
*/
protected function convertVCard($data, $target) {
$data = VObject\Reader::read($data);
switch ($target) {
default :
case 'vcard3' :
$data = $data->convert(VObject\Document::VCARD30);
$newResult = $data->serialize();
break;
case 'vcard4' :
$data = $data->convert(VObject\Document::VCARD40);
$newResult = $data->serialize();
break;
case 'jcard' :
$data = $data->convert(VObject\Document::VCARD40);
$newResult = json_encode($data->jsonSerialize());
break;
if (is_resource($data)) {
$data = stream_get_contents($data);
}
// Destroy circular references to PHP will GC the object.
$data->destroy();
$input = VObject\Reader::read($data);
$output = null;
try {
return $newResult;
switch ($target) {
default :
case 'vcard3' :
if ($input->getDocumentType() === VObject\Document::VCARD30) {
// Do nothing
return $data;
}
$output = $input->convert(VObject\Document::VCARD30);
return $output->serialize();
case 'vcard4' :
if ($input->getDocumentType() === VObject\Document::VCARD40) {
// Do nothing
return $data;
}
$output = $input->convert(VObject\Document::VCARD40);
return $output->serialize();
case 'jcard' :
$output = $input->convert(VObject\Document::VCARD40);
return json_encode($output);
}
} finally {
// Destroy circular references to PHP will GC the object.
$input->destroy();
if (!is_null($output)) {
$output->destroy();
}
}
}

View File

@ -70,14 +70,34 @@ class VCFExportPlugin extends DAV\ServerPlugin {
$aclPlugin->checkPrivileges($path, '{DAV:}read');
}
$response->setHeader('Content-Type', 'text/directory');
$response->setStatus(200);
$nodes = $this->server->getPropertiesForPath($path, [
'{' . Plugin::NS_CARDDAV . '}address-data',
], 1);
$response->setBody($this->generateVCF($nodes));
$format = 'text/directory';
$output = null;
$filenameExtension = null;
switch ($format) {
case 'text/directory':
$output = $this->generateVCF($nodes);
$filenameExtension = '.vcf';
break;
}
$filename = preg_replace(
'/[^a-zA-Z0-9-_ ]/um',
'',
$node->getName()
);
$filename .= '-' . date('Y-m-d') . $filenameExtension;
$response->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
$response->setHeader('Content-Type', $format);
$response->setStatus(200);
$response->setBody($output);
// Returning false to break the event chain
return false;

View File

@ -155,8 +155,14 @@ abstract class AbstractDigest implements BackendInterface {
$response
);
$auth->init();
$oldStatus = $response->getStatus() ?: 200;
$auth->requireLogin();
// Preventing the digest utility from modifying the http status code,
// this should be handled by the main plugin.
$response->setStatus($oldStatus);
}
}

View File

@ -25,7 +25,7 @@ class File extends AbstractDigest {
/**
* Creates the backend object.
*
* If the filename argument is passed in, it will parse out the specified file fist.
* If the filename argument is passed in, it will parse out the specified file first.
*
* @param string|null $filename
*/

View File

@ -4,7 +4,6 @@ namespace Sabre\DAV\Auth;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\HTTP\URLUtil;
use Sabre\DAV\Exception\NotAuthenticated;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
@ -25,6 +24,20 @@ use Sabre\DAV\ServerPlugin;
*/
class Plugin extends ServerPlugin {
/**
* By default this plugin will require that the user is authenticated,
* and refuse any access if the user is not authenticated.
*
* If this setting is set to false, we let the user through, whether they
* are authenticated or not.
*
* This is useful if you want to allow both authenticated and
* unauthenticated access to your server.
*
* @param bool
*/
public $autoRequireLogin = true;
/**
* authentication backends
*/
@ -107,27 +120,6 @@ class Plugin extends ServerPlugin {
}
/**
* Returns the current username.
*
* This method is deprecated and is only kept for backwards compatibility
* purposes. Please switch to getCurrentPrincipal().
*
* @deprecated Will be removed in a future version!
* @return string|null
*/
function getCurrentUser() {
// We just do a 'basename' on the principal to give back a sane value
// here.
list(, $userName) = URLUtil::splitPath(
$this->getCurrentPrincipal()
);
return $userName;
}
/**
* This method is called before any HTTP method and forces users to be authenticated
*
@ -154,6 +146,50 @@ class Plugin extends ServerPlugin {
return;
}
$authResult = $this->check($request, $response);
if ($authResult[0]) {
// Auth was successful
$this->currentPrincipal = $authResult[1];
$this->loginFailedReasons = null;
return;
}
// If we got here, it means that no authentication backend was
// successful in authenticating the user.
$this->currentPrincipal = null;
$this->loginFailedReasons = $authResult[1];
if ($this->autoRequireLogin) {
$this->challenge($request, $response);
throw new NotAuthenticated(implode(', ', $authResult[1]));
}
}
/**
* Checks authentication credentials, and logs the user in if possible.
*
* This method returns an array. The first item in the array is a boolean
* indicating if login was successful.
*
* If login was successful, the second item in the array will contain the
* current principal url/path of the logged in user.
*
* If login was not successful, the second item in the array will contain a
* an array with strings. The strings are a list of reasons why login was
* unsuccesful. For every auth backend there will be one reason, so usually
* there's just one.
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @return array
*/
function check(RequestInterface $request, ResponseInterface $response) {
if (!$this->backends) {
throw new \Sabre\DAV\Exception('No authentication backends were configured on this server.');
}
@ -172,20 +208,56 @@ class Plugin extends ServerPlugin {
if ($result[0]) {
$this->currentPrincipal = $result[1];
// Exit early
return;
return [true, $result[1]];
}
$reasons[] = $result[1];
}
// If we got here, it means that no authentication backend was
// successful in authenticating the user.
$this->currentPrincipal = null;
return [false, $reasons];
}
/**
* This method sends authentication challenges to the user.
*
* This method will for example cause a HTTP Basic backend to set a
* WWW-Authorization header, indicating to the client that it should
* authenticate.
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @return array
*/
function challenge(RequestInterface $request, ResponseInterface $response) {
foreach ($this->backends as $backend) {
$backend->challenge($request, $response);
}
throw new NotAuthenticated(implode(', ', $reasons));
}
/**
* List of reasons why login failed for the last login operation.
*
* @var string[]|null
*/
protected $loginFailedReasons;
/**
* Returns a list of reasons why login was unsuccessful.
*
* This method will return the login failed reasons for the last login
* operation. One for each auth backend.
*
* This method returns null if the last authentication attempt was
* successful, or if there was no authentication attempt yet.
*
* @return string[]|null
*/
function getLoginFailedReasons() {
return $this->loginFailedReasons;
}

View File

@ -48,7 +48,7 @@ class Plugin extends DAV\ServerPlugin {
public $uninterestingProperties = [
'{DAV:}supportedlock',
'{DAV:}acl-restrictions',
'{DAV:}supported-privilege-set',
// '{DAV:}supported-privilege-set',
'{DAV:}supported-method-set',
];
@ -112,7 +112,7 @@ class Plugin extends DAV\ServerPlugin {
$getVars = $request->getQueryParameters();
// CSP headers
$this->server->httpResponse->setHeader('Content-Security-Policy', "img-src 'self'; style-src 'self';");
$this->server->httpResponse->setHeader('Content-Security-Policy', "default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';");
$sabreAction = isset($getVars['sabreAction']) ? $getVars['sabreAction'] : null;
@ -354,7 +354,7 @@ class Plugin extends DAV\ServerPlugin {
$output = '';
if ($this->enablePost) {
$this->server->emit('onHTMLActionsPanel', [$node, &$output]);
$this->server->emit('onHTMLActionsPanel', [$node, &$output, $path]);
}
if ($output) {
@ -368,7 +368,7 @@ class Plugin extends DAV\ServerPlugin {
$html .= $this->generateFooter();
$this->server->httpResponse->setHeader('Content-Security-Policy', "img-src 'self'; style-src 'self';");
$this->server->httpResponse->setHeader('Content-Security-Policy', "default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';");
return $html;
@ -477,7 +477,7 @@ HTML;
$version = DAV\Version::VERSION;
return <<<HTML
<footer>Generated by SabreDAV $version (c)2007-2015 <a href="http://sabre.io/">http://sabre.io/</a></footer>
<footer>Generated by SabreDAV $version (c)2007-2016 <a href="http://sabre.io/">http://sabre.io/</a></footer>
</body>
</html>
HTML;
@ -493,9 +493,10 @@ HTML;
*
* @param DAV\INode $node
* @param mixed $output
* @param string $path
* @return void
*/
function htmlActionsPanel(DAV\INode $node, &$output) {
function htmlActionsPanel(DAV\INode $node, &$output, $path) {
if (!$node instanceof DAV\ICollection)
return;

View File

@ -96,12 +96,12 @@ header a {
vertical-align: middle;
border: 0;
}
input, button {
input, button, select {
font: inherit;
color: inherit;
}
input[type=text] {
input[type=text], select {
border: 1px solid #bbbbbb;
line-height: 22px;
padding: 5px 10px;
@ -200,7 +200,7 @@ section table {
line-height: 40px;
}
.actions input[type=text] {
.actions input[type=text], select {
width: 450px;
}

View File

@ -3,6 +3,7 @@
namespace Sabre\DAV;
use Sabre\HTTP;
use Sabre\Uri;
/**
* SabreDAV DAV client
@ -387,20 +388,10 @@ class Client extends HTTP\Client {
*/
function getAbsoluteUrl($url) {
// If the url starts with http:// or https://, the url is already absolute.
if (preg_match('/^http(s?):\/\//', $url)) {
return $url;
}
// If the url starts with a slash, we must calculate the url based off
// the root of the base url.
if (strpos($url, '/') === 0) {
$parts = parse_url($this->baseUri);
return $parts['scheme'] . '://' . $parts['host'] . (isset($parts['port']) ? ':' . $parts['port'] : '') . $url;
}
// Otherwise...
return $this->baseUri . $url;
return Uri\resolve(
$this->baseUri,
$url
);
}

View File

@ -50,6 +50,8 @@ class CorePlugin extends ServerPlugin {
$server->on('propFind', [$this, 'propFindNode'], 120);
$server->on('propFind', [$this, 'propFindLate'], 200);
$server->on('exception', [$this, 'exception']);
}
/**
@ -844,10 +846,8 @@ class CorePlugin extends ServerPlugin {
if ($node instanceof IProperties && $propertyNames = $propFind->get404Properties()) {
$nodeProperties = $node->getProperties($propertyNames);
foreach ($propertyNames as $propertyName) {
if (array_key_exists($propertyName, $nodeProperties)) {
$propFind->set($propertyName, $nodeProperties[$propertyName], 200);
}
foreach ($nodeProperties as $propertyName => $propertyValue) {
$propFind->set($propertyName, $propertyValue, 200);
}
}
@ -904,6 +904,38 @@ class CorePlugin extends ServerPlugin {
}
/**
* Listens for exception events, and automatically logs them.
*
* @param Exception $e
*/
function exception($e) {
$logLevel = \Psr\Log\LogLevel::CRITICAL;
if ($e instanceof \Sabre\DAV\Exception) {
// If it's a standard sabre/dav exception, it means we have a http
// status code available.
$code = $e->getHTTPCode();
if ($code >= 400 && $code < 500) {
// user error
$logLevel = \Psr\Log\LogLevel::INFO;
} else {
// Server-side error. We mark it's as an error, but it's not
// critical.
$logLevel = \Psr\Log\LogLevel::ERROR;
}
}
$this->server->getLogger()->log(
$logLevel,
'Uncaught exception',
[
'exception' => $e,
]
);
}
/**
* Returns a bunch of meta-data about the plugin.
*

View File

@ -140,10 +140,10 @@ class Directory extends Node implements DAV\ICollection, DAV\IQuota {
* @return array
*/
function getQuotaInfo() {
$absolute = realpath($this->path);
return [
disk_total_space($this->path) - disk_free_space($this->path),
disk_free_space($this->path)
disk_total_space($absolute) - disk_free_space($absolute),
disk_free_space($absolute)
];
}

View File

@ -131,12 +131,7 @@ class Directory extends Node implements DAV\ICollection, DAV\IQuota, DAV\IMoveTa
foreach ($iterator as $entry) {
$node = $entry->getFilename();
if ($node === '.sabredav')
continue;
$nodes[] = $this->getChild($node);
$nodes[] = $this->getChild($entry->getFilename());
}
return $nodes;
@ -153,9 +148,6 @@ class Directory extends Node implements DAV\ICollection, DAV\IQuota, DAV\IMoveTa
// Deleting all children
foreach ($this->getChildren() as $child) $child->delete();
// Removing resource info, if its still around
if (file_exists($this->path . '/.sabredav')) unlink($this->path . '/.sabredav');
// Removing the directory itself
rmdir($this->path);

View File

@ -15,12 +15,24 @@ namespace Sabre\DAV;
abstract class File extends Node implements IFile {
/**
* Updates the data
* Replaces the contents of the file.
*
* data is a readable stream resource.
* The data argument is a readable stream resource.
*
* @param resource $data
* @return void
* After a succesful put operation, you may choose to return an ETag. The
* etag must always be surrounded by double-quotes. These quotes must
* appear in the actual string you're returning.
*
* Clients may use the ETag from a PUT request to later on make sure that
* when they update the file, the contents haven't changed in the mean
* time.
*
* If you don't plan to store the file byte-by-byte, and you return a
* different object on a subsequent GET you are strongly recommended to not
* return an ETag, and just return null.
*
* @param string|resource $data
* @return string|null
*/
function put($data) {

View File

@ -54,14 +54,14 @@ interface ICollection extends INode {
* exist.
*
* @param string $name
* @return DAV\INode
* @return INode
*/
function getChild($name);
/**
* Returns an array with all the child nodes
*
* @return DAV\INode[]
* @return INode[]
*/
function getChildren();

View File

@ -32,7 +32,7 @@ interface IFile extends INode {
* different object on a subsequent GET you are strongly recommended to not
* return an ETag, and just return null.
*
* @param resource $data
* @param resource|data $data
* @return string|null
*/
function put($data);

View File

@ -36,9 +36,10 @@ interface INode {
function setName($name);
/**
* Returns the last modification time, as a unix timestamp
* Returns the last modification time, as a unix timestamp. Return null
* if the information is not available.
*
* @return int
* @return int|null
*/
function getLastModified();

View File

@ -88,6 +88,9 @@ class PDO implements BackendInterface {
$stmt->execute([$path]);
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
if (gettype($row['value']) === 'resource') {
$row['value'] = stream_get_contents($row['value']);
}
switch ($row['valuetype']) {
case null :
case self::VT_STRING :
@ -121,7 +124,26 @@ class PDO implements BackendInterface {
$propPatch->handleRemaining(function($properties) use ($path) {
$updateStmt = $this->pdo->prepare("REPLACE INTO " . $this->tableName . " (path, name, valuetype, value) VALUES (?, ?, ?, ?)");
if ($this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME) === 'pgsql') {
$updateSql = <<<SQL
INSERT INTO {$this->tableName} (path, name, valuetype, value)
VALUES (:path, :name, :valuetype, :value)
ON CONFLICT (path, name)
DO UPDATE SET valuetype = :valuetype, value = :value
SQL;
} else {
$updateSql = <<<SQL
REPLACE INTO {$this->tableName} (path, name, valuetype, value)
VALUES (:path, :name, :valuetype, :value)
SQL;
}
$updateStmt = $this->pdo->prepare($updateSql);
$deleteStmt = $this->pdo->prepare("DELETE FROM " . $this->tableName . " WHERE path = ? AND name = ?");
foreach ($properties as $name => $value) {
@ -136,7 +158,14 @@ class PDO implements BackendInterface {
$valueType = self::VT_OBJECT;
$value = serialize($value);
}
$updateStmt->execute([$path, $name, $valueType, $value]);
$updateStmt->bindParam('path', $path, \PDO::PARAM_STR);
$updateStmt->bindParam('name', $name, \PDO::PARAM_STR);
$updateStmt->bindParam('valuetype', $valueType, \PDO::PARAM_INT);
$updateStmt->bindParam('value', $value, \PDO::PARAM_LOB);
$updateStmt->execute();
} else {
$deleteStmt->execute([$path, $name]);
}

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