upgrade to sabre32

This commit is contained in:
Mario Vavti 2016-05-28 17:46:24 +02:00
parent ac4688eac0
commit 66effbfe08
228 changed files with 10088 additions and 8576 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;

2
vendor/autoload.php vendored
View File

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

View File

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

241
vendor/bin/generate_vcards vendored Executable file
View File

@ -0,0 +1,241 @@
#!/usr/bin/env php
<?php
namespace Sabre\VObject;
// This sucks.. we have to try to find the composer autoloader. But chances
// are, we can't find it this way. So we'll do our bestest
$paths = [
__DIR__ . '/../vendor/autoload.php', // In case vobject is cloned directly
__DIR__ . '/../../../autoload.php', // In case vobject is a composer dependency.
];
foreach($paths as $path) {
if (file_exists($path)) {
include $path;
break;
}
}
if (!class_exists('Sabre\\VObject\\Version')) {
fwrite(STDERR, "Composer autoloader could not be properly loaded.\n");
die(1);
}
if ($argc < 2) {
$version = Version::VERSION;
$help = <<<HI
sabre/vobject $version
Usage:
generate_vcards [count]
Options:
count The number of random vcards to generate
Examples:
generate_vcards 1000 > testdata.vcf
HI;
fwrite(STDERR, $help);
exit(2);
}
$count = (int)$argv[1];
if ($count < 1) {
fwrite(STDERR, "Count must be at least 1\n");
exit(2);
}
fwrite(STDERR, "sabre/vobject " . Version::VERSION . "\n");
fwrite(STDERR, "Generating " . $count . " vcards in vCard 4.0 format\n");
/**
* The following list is just some random data we compiled from various
* sources online.
*
* Very little thought went into compiling this list, and certainly nothing
* political or ethical.
*
* We would _love_ more additions to this to add more variation to this list.
*
* Send us PR's and don't be shy adding your own first and last name for fun.
*/
$sets = array(
"nl" => array(
"country" => "Netherlands",
"boys" => array(
"Anno",
"Bram",
"Daan",
"Evert",
"Finn",
"Jayden",
"Jens",
"Jesse",
"Levi",
"Lucas",
"Luuk",
"Milan",
"René",
"Sem",
"Sibrand",
"Willem",
),
"girls" => array(
"Celia",
"Emma",
"Fenna",
"Geke",
"Inge",
"Julia",
"Lisa",
"Lotte",
"Mila",
"Sara",
"Sophie",
"Tess",
"Zoë",
),
"last" => array(
"Bakker",
"Bos",
"De Boer",
"De Groot",
"De Jong",
"De Vries",
"Jansen",
"Janssen",
"Meyer",
"Mulder",
"Peters",
"Smit",
"Van Dijk",
"Van den Berg",
"Visser",
"Vos",
),
),
"us" => array(
"country" => "United States",
"boys" => array(
"Aiden",
"Alexander",
"Charles",
"David",
"Ethan",
"Jacob",
"James",
"Jayden",
"John",
"Joseph",
"Liam",
"Mason",
"Michael",
"Noah",
"Richard",
"Robert",
"Thomas",
"William",
),
"girls" => array(
"Ava",
"Barbara",
"Chloe",
"Dorothy",
"Elizabeth",
"Emily",
"Emma",
"Isabella",
"Jennifer",
"Lily",
"Linda",
"Margaret",
"Maria",
"Mary",
"Mia",
"Olivia",
"Patricia",
"Roxy",
"Sophia",
"Susan",
"Zoe",
),
"last" => array(
"Smith",
"Johnson",
"Williams",
"Jones",
"Brown",
"Davis",
"Miller",
"Wilson",
"Moore",
"Taylor",
"Anderson",
"Thomas",
"Jackson",
"White",
"Harris",
"Martin",
"Thompson",
"Garcia",
"Martinez",
"Robinson",
),
),
);
$current = 0;
$r = function($arr) {
return $arr[mt_rand(0,count($arr)-1)];
};
$bdayStart = strtotime('-85 years');
$bdayEnd = strtotime('-20 years');
while($current < $count) {
$current++;
fwrite(STDERR, "\033[100D$current/$count");
$country = array_rand($sets);
$gender = mt_rand(0,1)?'girls':'boys';
$vcard = new Component\VCard(array(
'VERSION' => '4.0',
'FN' => $r($sets[$country][$gender]) . ' ' . $r($sets[$country]['last']),
'UID' => UUIDUtil::getUUID(),
));
$bdayRatio = mt_rand(0,9);
if($bdayRatio < 2) {
// 20% has a birthday property with a full date
$dt = new \DateTime('@' . mt_rand($bdayStart, $bdayEnd));
$vcard->add('BDAY', $dt->format('Ymd'));
} elseif ($bdayRatio < 3) {
// 10% we only know the month and date of
$dt = new \DateTime('@' . mt_rand($bdayStart, $bdayEnd));
$vcard->add('BDAY', '--' . $dt->format('md'));
}
if ($result = $vcard->validate()) {
ob_start();
echo "\nWe produced an invalid vcard somehow!\n";
foreach($result as $message) {
echo " " . $message['message'] . "\n";
}
fwrite(STDERR, ob_get_clean());
}
echo $vcard->serialize();
}
fwrite(STDERR,"\nDone.\n");

View File

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

140
vendor/bin/naturalselection vendored Executable file
View File

@ -0,0 +1,140 @@
#!/usr/bin/env python
#
# Copyright (c) 2009-2010 Evert Pot
# All rights reserved.
# http://www.rooftopsolutions.nl/
#
# This utility is distributed along with SabreDAV
# license: http://sabre.io/license/ Modified BSD License
import os
from optparse import OptionParser
import time
def getfreespace(path):
stat = os.statvfs(path)
return stat.f_frsize * stat.f_bavail
def getbytesleft(path,threshold):
return getfreespace(path)-threshold
def run(cacheDir, threshold, sleep=5, simulate=False, min_erase = 0):
bytes = getbytesleft(cacheDir,threshold)
if (bytes>0):
print "Bytes to go before we hit threshold:", bytes
else:
print "Threshold exceeded with:", -bytes, "bytes"
dir = os.listdir(cacheDir)
dir2 = []
for file in dir:
path = cacheDir + '/' + file
dir2.append({
"path" : path,
"atime": os.stat(path).st_atime,
"size" : os.stat(path).st_size
})
dir2.sort(lambda x,y: int(x["atime"]-y["atime"]))
filesunlinked = 0
gainedspace = 0
# Left is the amount of bytes that need to be freed up
# The default is the 'min_erase setting'
left = min_erase
# If the min_erase setting is lower than the amount of bytes over
# the threshold, we use that number instead.
if left < -bytes :
left = -bytes
print "Need to delete at least:", left;
for file in dir2:
# Only deleting files if we're not simulating
if not simulate: os.unlink(file["path"])
left = int(left - file["size"])
gainedspace = gainedspace + file["size"]
filesunlinked = filesunlinked + 1
if(left<0):
break
print "%d files deleted (%d bytes)" % (filesunlinked, gainedspace)
time.sleep(sleep)
def main():
parser = OptionParser(
version="naturalselection v0.3",
description="Cache directory manager. Deletes cache entries based on accesstime and free space thresholds.\n" +
"This utility is distributed alongside SabreDAV.",
usage="usage: %prog [options] cacheDirectory",
)
parser.add_option(
'-s',
dest="simulate",
action="store_true",
help="Don't actually make changes, but just simulate the behaviour",
)
parser.add_option(
'-r','--runs',
help="How many times to check before exiting. -1 is infinite, which is the default",
type="int",
dest="runs",
default=-1
)
parser.add_option(
'-n','--interval',
help="Sleep time in seconds (default = 5)",
type="int",
dest="sleep",
default=5
)
parser.add_option(
'-l','--threshold',
help="Threshold in bytes (default = 10737418240, which is 10GB)",
type="int",
dest="threshold",
default=10737418240
)
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 number of times the cache directory will need to be scanned. " +
"(the default is 1073741824, which is 1GB.)",
type="int",
dest="min_erase",
default=1073741824
)
options,args = parser.parse_args()
if len(args)<1:
parser.error("This utility requires at least 1 argument")
cacheDir = args[0]
print "Natural Selection"
print "Cache directory:", cacheDir
free = getfreespace(cacheDir);
print "Current free disk space:", free
runs = options.runs;
while runs!=0 :
run(
cacheDir,
sleep=options.sleep,
simulate=options.simulate,
threshold=options.threshold,
min_erase=options.min_erase
)
if runs>0:
runs = runs - 1
if __name__ == '__main__' :
main()

1
vendor/bin/sabredav vendored
View File

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

2
vendor/bin/sabredav vendored Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
php -S 0.0.0.0:8080 `dirname $0`/sabredav.php

1
vendor/bin/vobject vendored
View File

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

27
vendor/bin/vobject vendored Executable file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env php
<?php
namespace Sabre\VObject;
// This sucks.. we have to try to find the composer autoloader. But chances
// are, we can't find it this way. So we'll do our bestest
$paths = [
__DIR__ . '/../vendor/autoload.php', // In case vobject is cloned directly
__DIR__ . '/../../../autoload.php', // In case vobject is a composer dependency.
];
foreach($paths as $path) {
if (file_exists($path)) {
include $path;
break;
}
}
if (!class_exists('Sabre\\VObject\\Version')) {
fwrite(STDERR, "Composer autoloader could not be loaded.\n");
die(1);
}
$cli = new Cli();
exit($cli->main($argv));

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 ComposerAutoloaderInite7c34560857712ac82f3f77ff9c61261
{
private static $loader;
@ -19,48 +19,37 @@ class ComposerAutoloaderInit85a1cefa95be2f464cf7f947cbc4c785
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit85a1cefa95be2f464cf7f947cbc4c785', 'loadClassLoader'), true, true);
spl_autoload_register(array('ComposerAutoloaderInite7c34560857712ac82f3f77ff9c61261', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit85a1cefa95be2f464cf7f947cbc4c785', 'loadClassLoader'));
spl_autoload_unregister(array('ComposerAutoloaderInite7c34560857712ac82f3f77ff9c61261', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION');
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
call_user_func(\Composer\Autoload\ComposerStaticInit85a1cefa95be2f464cf7f947cbc4c785::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
$loader->register(true);
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInit85a1cefa95be2f464cf7f947cbc4c785::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
$includeFiles = require __DIR__ . '/autoload_files.php';
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire85a1cefa95be2f464cf7f947cbc4c785($fileIdentifier, $file);
composerRequiree7c34560857712ac82f3f77ff9c61261($fileIdentifier, $file);
}
return $loader;
}
}
function composerRequire85a1cefa95be2f464cf7f947cbc4c785($fileIdentifier, $file)
function composerRequiree7c34560857712ac82f3f77ff9c61261($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
require $file;

View File

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

View File

@ -1,4 +1,44 @@
[
{
"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/uri",
"version": "1.1.0",
@ -170,17 +210,17 @@
},
{
"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 +235,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": {
@ -332,17 +372,17 @@
},
{
"name": "sabre/dav",
"version": "3.1.3",
"version_normalized": "3.1.3.0",
"version": "3.2.0-beta1",
"version_normalized": "3.2.0.0-beta1",
"source": {
"type": "git",
"url": "https://github.com/fruux/sabre-dav.git",
"reference": "8a266c7b5e140da79529414b9cde2a2d058b536b"
"reference": "32524c79a5890056123aa64499719e7c061bfde8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fruux/sabre-dav/zipball/8a266c7b5e140da79529414b9cde2a2d058b536b",
"reference": "8a266c7b5e140da79529414b9cde2a2d058b536b",
"url": "https://api.github.com/repos/fruux/sabre-dav/zipball/32524c79a5890056123aa64499719e7c061bfde8",
"reference": "32524c79a5890056123aa64499719e7c061bfde8",
"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-05-21 03:49:31",
"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

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +0,0 @@
Copyright (C) 2007-2016 fruux GmbH (https://fruux.com/).
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of SabreDAV nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,29 +0,0 @@
![sabre's logo](http://sabre.io/img/logo.png) SabreDAV
======================================================
Introduction
------------
SabreDAV 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:
http://sabre.io/
Build status
------------
| branch | status | minimum PHP version |
| ------------ | ------ | ------------------- |
| master | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=master)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.5 |
| 3.0 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=3.0)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.4 |
| 2.1 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=2.1)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.4 |
| 2.0 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=2.0)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.4 |
| 1.8 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=1.8)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.3 |
| 1.7 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=1.7)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.3 |
| 1.6 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=1.6)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.3 |
Made at fruux
-------------
SabreDAV is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support.

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);

View File

@ -3,7 +3,7 @@
echo "SabreDAV migrate script for version 1.7\n";
if ($argc < 2) {
if ($argc<2) {
echo <<<HELLO
@ -35,12 +35,12 @@ HELLO;
// There's a bunch of places where the autoloader could be, so we'll try all of
// them.
$paths = [
$paths = array(
__DIR__ . '/../vendor/autoload.php',
__DIR__ . '/../../../autoload.php',
];
);
foreach ($paths as $path) {
foreach($paths as $path) {
if (file_exists($path)) {
include $path;
break;
@ -48,8 +48,8 @@ foreach ($paths as $path) {
}
$dsn = $argv[1];
$user = isset($argv[2]) ? $argv[2] : null;
$pass = isset($argv[3]) ? $argv[3] : null;
$user = isset($argv[2])?$argv[2]:null;
$pass = isset($argv[3])?$argv[3]:null;
echo "Connecting to database: " . $dsn . "\n";
@ -67,32 +67,32 @@ if (!$row) {
exit(-1);
}
$requiredFields = [
$requiredFields = array(
'id',
'calendardata',
'uri',
'calendarid',
'lastmodified',
];
);
foreach ($requiredFields as $requiredField) {
if (!array_key_exists($requiredField, $row)) {
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 = [
$fields17 = array(
'etag',
'size',
'componenttype',
'firstoccurence',
'lastoccurence',
];
);
$found = 0;
foreach ($fields17 as $field) {
foreach($fields17 as $field) {
if (array_key_exists($field, $row)) {
$found++;
}
@ -102,7 +102,7 @@ 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)) {
switch($pdo->getAttribute(PDO::ATTR_DRIVER_NAME)) {
case 'mysql' :
@ -150,7 +150,7 @@ $stmt = $pdo->prepare('UPDATE calendarobjects SET etag = ?, size = ?, componentt
echo "Total records found: " . $result->rowCount() . "\n";
$done = 0;
$total = $result->rowCount();
while ($row = $result->fetch()) {
while($row = $result->fetch()) {
try {
$newData = getDenormalizedData($row['calendardata']);
@ -161,14 +161,14 @@ while ($row = $result->fetch()) {
echo "This record is ignored, you should inspect it to see if there's anything wrong.\n===\n";
continue;
}
$stmt->execute([
$stmt->execute(array(
$newData['etag'],
$newData['size'],
$newData['componentType'],
$newData['firstOccurence'],
$newData['lastOccurence'],
$row['id'],
]);
));
$done++;
if ($done % 500 === 0) {
@ -188,7 +188,7 @@ if (array_key_exists('transparent', $row)) {
echo "Adding the 'transparent' field to the calendars table\n";
switch ($pdo->getAttribute(PDO::ATTR_DRIVER_NAME)) {
switch($pdo->getAttribute(PDO::ATTR_DRIVER_NAME)) {
case 'mysql' :
$pdo->exec("ALTER TABLE calendars ADD transparent TINYINT(1) NOT NULL DEFAULT '0'");
@ -229,8 +229,8 @@ function getDenormalizedData($calendarData) {
$component = null;
$firstOccurence = null;
$lastOccurence = null;
foreach ($vObject->getComponents() as $component) {
if ($component->name !== 'VTIMEZONE') {
foreach($vObject->getComponents() as $component) {
if ($component->name!=='VTIMEZONE') {
$componentType = $component->name;
break;
}
@ -262,7 +262,7 @@ function getDenormalizedData($calendarData) {
$lastOccurence = $maxDate->getTimeStamp();
} else {
$end = $it->getDtEnd();
while ($it->valid() && $end < $maxDate) {
while($it->valid() && $end < $maxDate) {
$end = $it->getDtEnd();
$it->next();
@ -273,12 +273,12 @@ function getDenormalizedData($calendarData) {
}
}
return [
'etag' => md5($calendarData),
'size' => strlen($calendarData),
'componentType' => $componentType,
return array(
'etag' => md5($calendarData),
'size' => strlen($calendarData),
'componentType' => $componentType,
'firstOccurence' => $firstOccurence,
'lastOccurence' => $lastOccurence,
];
);
}

View File

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

268
vendor/sabre/dav/bin/migrateto32.php vendored Executable 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(20)
) 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",

View File

@ -6,7 +6,7 @@ CREATE TABLE addressbooks (
description TEXT,
synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1',
UNIQUE(principaluri(100), uri(100))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE cards (
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
@ -16,7 +16,7 @@ CREATE TABLE cards (
lastmodified INT(11) UNSIGNED,
etag VARBINARY(32),
size INT(11) UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE addressbookchanges (
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
@ -25,4 +25,4 @@ CREATE TABLE addressbookchanges (
addressbookid INT(11) UNSIGNED NOT NULL,
operation TINYINT(1) NOT NULL,
INDEX addressbookid_synctoken (addressbookid, synctoken)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

View File

@ -10,22 +10,34 @@ CREATE TABLE calendarobjects (
firstoccurence INT(11) UNSIGNED,
lastoccurence INT(11) UNSIGNED,
uid VARBINARY(200),
UNIQUE(calendarid, uri)
UNIQUE(calendarid, uri),
INDEX calendarid_time (calendarid, firstoccurence)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
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=utf8mb4;
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),
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)
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;
CREATE TABLE calendarchanges (

View File

@ -1,27 +1,6 @@
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,
calendardata BYTEA,
uri VARCHAR(200),
calendarid INTEGER NOT NULL,
lastmodified INTEGER,
@ -39,9 +18,46 @@ ALTER TABLE ONLY calendarobjects
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 calendars (
id SERIAL NOT NULL,
synctoken INTEGER NOT NULL DEFAULT 1,
components VARCHAR(21)
);
ALTER TABLE ONLY calendars
ADD CONSTRAINT calendars_pkey PRIMARY KEY (id);
CREATE TABLE calendarinstances (
id SERIAL NOT NULL,
calendarid INTEGER NOT NULL,
principaluri VARCHAR(100),
access SMALLINT NOT NULL DEFAULT '1', -- '1 = owner, 2 = read, 3 = readwrite'
displayname VARCHAR(100),
uri VARCHAR(200),
description TEXT,
calendarorder INTEGER NOT NULL DEFAULT 0,
calendarcolor VARCHAR(10),
timezone TEXT,
transparent SMALLINT NOT NULL DEFAULT '0',
share_href VARCHAR(100),
share_displayname VARCHAR(100),
share_invitestatus SMALLINT NOT NULL DEFAULT '2' -- '1 = noresponse, 2 = accepted, 3 = declined, 4 = invalid'
);
ALTER TABLE ONLY calendarinstances
ADD CONSTRAINT calendarinstances_pkey PRIMARY KEY (id);
CREATE UNIQUE INDEX calendarinstances_principaluri_uri
ON calendarinstances USING btree (principaluri, uri);
CREATE UNIQUE INDEX calendarinstances_principaluri_calendarid
ON calendarinstances USING btree (principaluri, calendarid);
CREATE UNIQUE INDEX calendarinstances_principaluri_share_href
ON calendarinstances USING btree (principaluri, share_href);
CREATE TABLE calendarsubscriptions (
id SERIAL NOT NULL,
@ -78,10 +94,6 @@ ALTER TABLE ONLY calendarchanges
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),

View File

@ -23,14 +23,6 @@ ALTER TABLE ONLY groupmembers
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),

View File

@ -14,16 +14,28 @@ CREATE TABLE calendarobjects (
CREATE TABLE calendars (
id integer primary key asc NOT NULL,
principaluri text NOT NULL,
synctoken integer DEFAULT 1 NOT NULL,
components text NOT NULL
);
CREATE TABLE calendarinstances (
id integer primary key asc NOT NULL,
calendarid integer NOT NULL,
principaluri text NULL,
access integer COMMENT '1 = owner, 2 = read, 3 = readwrite' NOT NULL DEFAULT '1',
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
transparent bool,
share_href text,
share_displayname text,
share_invitestatus integer DEFAULT '2',
UNIQUE (principaluri, uri),
UNIQUE (calendarid, principaluri),
UNIQUE (calendarid, share_href)
);
CREATE TABLE calendarchanges (

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 \Exception('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

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

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

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

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

@ -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

@ -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

@ -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]);
}

View File

@ -8,6 +8,10 @@ use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\HTTP\URLUtil;
use Sabre\Uri;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
/**
* Main DAV server class
@ -16,7 +20,9 @@ use Sabre\Uri;
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Server extends EventEmitter {
class Server extends EventEmitter implements LoggerAwareInterface {
use LoggerAwareTrait;
/**
* Infinity is used for some request supporting the HTTP Depth header and indicates that the operation should traverse the entire tree
@ -430,6 +436,20 @@ class Server extends EventEmitter {
}
/**
* Returns the PSR-3 logger objcet.
*
* @return LoggerInterface
*/
function getLogger() {
if (!$this->logger) {
$this->logger = new NullLogger();
}
return $this->logger;
}
/**
* Handles a http request, and execute a method based on its name
*
@ -1177,9 +1197,20 @@ class Server extends EventEmitter {
if (!$success) {
$result = $mkCol->getResult();
// generateMkCol needs the href key to exist.
$result['href'] = $uri;
return $result;
$formattedResult = [
'href' => $uri,
];
foreach ($result as $propertyName => $status) {
if (!isset($formattedResult[$status])) {
$formattedResult[$status] = [];
}
$formattedResult[$status][$propertyName] = null;
}
return $formattedResult;
}
$this->tree->markDirty($parentUri);

View File

@ -0,0 +1,69 @@
<?php
namespace Sabre\DAV\Sharing;
use Sabre\DAV\INode;
/**
* This interface represents a resource that has sharing capabilities, either
* because it's possible for an owner to share the resource, or because this is
* an instance of a shared resource.
*
* @copyright Copyright (C) fruux GmbH. (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface ISharedNode extends INode {
/**
* Returns the 'access level' for the instance of this shared resource.
*
* The value should be one of the Sabre\DAV\Sharing\Plugin::ACCESS_
* constants.
*
* @return int
*/
function getShareAccess();
/**
* 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 getShareResourceUri();
/**
* Updates the list of sharees.
*
* Every item must be a Sharee object.
*
* @param \Sabre\DAV\Xml\Element\Sharee[] $sharees
* @return void
*/
function updateInvites(array $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();
}

View File

@ -0,0 +1,342 @@
<?php
namespace Sabre\DAV\Sharing;
use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\INode;
use Sabre\DAV\PropFind;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\DAV\Xml\Property;
use Sabre\DAV\Xml\Element\Sharee;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
/**
* This plugin implements HTTP requests and properties related to:
*
* draft-pot-webdav-resource-sharing
*
* This specification allows people to share webdav resources with others.
*
* @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 Plugin extends ServerPlugin {
const ACCESS_NOTSHARED = 0;
const ACCESS_SHAREDOWNER = 1;
const ACCESS_READ = 2;
const ACCESS_READWRITE = 3;
const ACCESS_NOACCESS = 4;
const INVITE_NORESPONSE = 1;
const INVITE_ACCEPTED = 2;
const INVITE_DECLINED = 3;
const INVITE_INVALID = 4;
/**
* Reference to SabreDAV server object.
*
* @var Sabre\DAV\Server
*/
protected $server;
/**
* This method should return a list of server-features.
*
* This is for example 'versioning' and is added to the DAV: header
* in an OPTIONS response.
*
* @return array
*/
function getFeatures() {
return ['resource-sharing'];
}
/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins
* using \Sabre\DAV\Server::getPlugin
*
* @return string
*/
function getPluginName() {
return 'sharing';
}
/**
* This initializes the plugin.
*
* This function is called by Sabre\DAV\Server, after
* addPlugin is called.
*
* This method should set up the required event subscriptions.
*
* @param Server $server
* @return void
*/
function initialize(Server $server) {
$this->server = $server;
$server->xml->elementMap['{DAV:}share-resource'] = 'Sabre\\DAV\\Xml\\Request\\ShareResource';
array_push(
$server->protectedProperties,
'{DAV:}share-mode'
);
$server->on('method:POST', [$this, 'httpPost']);
$server->on('propFind', [$this, 'propFind']);
$server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']);
$server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
$server->on('onBrowserPostAction', [$this, 'browserPostAction']);
}
/**
* Updates the list of sharees on a shared resource.
*
* The sharees array is a list of people that are to be added modified
* or removed in the list of shares.
*
* @param string $path
* @param Sharee[] $sharees
* @return void
*/
function shareResource($path, array $sharees) {
$node = $this->server->tree->getNodeForPath($path);
if (!$node instanceof ISharedNode) {
throw new Forbidden('Sharing is not allowed on this node');
}
// Getting ACL info
$acl = $this->server->getPlugin('acl');
// If there's no ACL support, we allow everything
if ($acl) {
$acl->checkPrivileges($path, '{DAV:}share');
}
foreach ($sharees as $sharee) {
// We're going to attempt to get a local principal uri for a share
// href by emitting the getPrincipalByUri event.
$principal = null;
$this->server->emit('getPrincipalByUri', [$sharee->href, &$principal]);
$sharee->principal = $principal;
}
$node->updateInvites($sharees);
}
/**
* This event is triggered when properties are requested for nodes.
*
* This allows us to inject any sharings-specific properties.
*
* @param PropFind $propFind
* @param INode $node
* @return void
*/
function propFind(PropFind $propFind, INode $node) {
if ($node instanceof ISharedNode) {
$propFind->handle('{DAV:}share-access', function() use ($node) {
return new Property\ShareAccess($node->getShareAccess());
});
$propFind->handle('{DAV:}invite', function() use ($node) {
return new Property\Invite($node->getInvites());
});
$propFind->handle('{DAV:}share-resource-uri', function() use ($node) {
return new Property\Href($node->getShareResourceUri());
});
}
}
/**
* We intercept this to handle POST requests on shared resources
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @return null|bool
*/
function httpPost(RequestInterface $request, ResponseInterface $response) {
$path = $request->getPath();
$contentType = $request->getHeader('Content-Type');
// We're only interested in the davsharing content type.
if (strpos($contentType, 'application/davsharing+xml') === false) {
return;
}
$message = $this->server->xml->parse(
$request->getBody(),
$request->getUrl(),
$documentType
);
switch ($documentType) {
case '{DAV:}share-resource':
$this->shareResource($path, $message->sharees);
$response->setStatus(200);
// Adding this because sending a response body may cause issues,
// and I wanted some type of indicator the response was handled.
$response->setHeader('X-Sabre-Status', 'everything-went-well');
// Breaking the event chain
return false;
default :
throw new BadRequest('Unexpected document type: ' . $documentType . ' for this Content-Type');
}
}
/**
* This method is triggered whenever a subsystem reqeuests the privileges
* hat 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) {
if ($node instanceof ISharedNode) {
$supportedPrivilegeSet['{DAV:}share'] = [
'abstract' => false,
'aggregates' => [],
];
}
}
/**
* Returns a bunch of meta-data about the plugin.
*
* Providing this information is optional, and is mainly displayed by the
* Browser plugin.
*
* The description key in the returned array may contain html and will not
* be sanitized.
*
* @return array
*/
function getPluginInfo() {
return [
'name' => $this->getPluginName(),
'description' => 'This plugin implements WebDAV resource sharing',
'link' => 'https://github.com/evert/webdav-sharing'
];
}
/**
* This method is used to generate HTML output for the
* DAV\Browser\Plugin.
*
* @param INode $node
* @param string $output
* @param string $path
* @return bool|null
*/
function htmlActionsPanel(INode $node, &$output, $path) {
if (!$node instanceof ISharedNode) {
return;
}
$aclPlugin = $this->server->getPlugin('acl');
if ($aclPlugin) {
if (!$aclPlugin->checkPrivileges($path, '{DAV:}share', \Sabre\DAVACL\Plugin::R_PARENT, false)) {
// Sharing is not permitted, we will not draw this interface.
return;
}
}
$output .= '<tr><td colspan="2"><form method="post" action="">
<h3>Share this resource</h3>
<input type="hidden" name="sabreAction" value="share" />
<label>Share with (uri):</label> <input type="text" name="href" placeholder="mailto:user@example.org"/><br />
<label>Access</label>
<select name="access">
<option value="readwrite">Read-write</option>
<option value="read">Read-only</option>
<option value="no-access">Revoke access</option>
</select><br />
<input type="submit" value="share" />
</form>
</td></tr>';
}
/**
* This method is triggered for POST actions generated by the browser
* plugin.
*
* @param string $path
* @param string $action
* @param array $postVars
*/
function browserPostAction($path, $action, $postVars) {
if ($action !== 'share') {
return;
}
if (empty($postVars['href'])) {
throw new BadRequest('The "href" POST parameter is required');
}
if (empty($postVars['access'])) {
throw new BadRequest('The "access" POST parameter is required');
}
$accessMap = [
'readwrite' => self::ACCESS_READWRITE,
'read' => self::ACCESS_READ,
'no-access' => self::ACCESS_NOACCESS,
];
if (!isset($accessMap[$postVars['access']])) {
throw new BadRequest('The "access" POST must be readwrite, read or no-access');
}
$sharee = new Sharee([
'href' => $postVars['href'],
'access' => $accessMap[$postVars['access']],
]);
$this->shareResource(
$path,
[$sharee]
);
return false;
}
}

View File

@ -229,7 +229,7 @@ class Tree {
// flushing the entire cache
$path = trim($path, '/');
foreach ($this->cache as $nodePath => $node) {
if ($nodePath == $path || strpos($nodePath, $path . '/') === 0)
if ($path === '' || $nodePath == $path || strpos($nodePath, $path . '/') === 0)
unset($this->cache[$nodePath]);
}

View File

@ -14,6 +14,6 @@ class Version {
/**
* Full version number
*/
const VERSION = '3.1.3';
const VERSION = '3.2.0-beta1';
}

View File

@ -0,0 +1,199 @@
<?php
namespace Sabre\DAV\Xml\Element;
use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\Sharing\Plugin;
use Sabre\DAV\Xml\Property\Href;
use Sabre\DAV\Xml\Property\ShareAccess;
use Sabre\Xml\Deserializer;
use Sabre\Xml\Element;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;
/**
* This class represents the {DAV:}sharee element.
*
* @copyright Copyright (C) fruux GmbH. (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Sharee implements Element {
/**
* A URL. Usually a mailto: address, could also be a principal url.
* This uniquely identifies the sharee.
*
* @var string
*/
public $href;
/**
* A local principal path. The server will do its best to locate the
* principal uri based on the given uri. If we could find a local matching
* principal uri, this property will contain the value.
*
* @var string|null
*/
public $principal;
/**
* A list of WebDAV properties that describe the sharee. This might for
* example contain a {DAV:}displayname with the real name of the user.
*
* @var array
*/
public $properties = [];
/**
* Share access level. One of the Sabre\DAV\Sharing\Plugin::ACCESS
* constants.
*
* Can be one of:
*
* ACCESS_READ
* ACCESS_READWRITE
* ACCESS_SHAREDOWNER
* ACCESS_NOACCESS
*
* depending on context.
*
* @var int
*/
public $access;
/**
* When a sharee is originally invited to a share, the sharer may add
* a comment. This will be placed in this property.
*
* @var string
*/
public $comment;
/**
* The status of the invite, should be one of the
* Sabre\DAV\Sharing\Plugin::INVITE constants.
*
* @var int
*/
public $inviteStatus;
/**
* Creates the object
*
* $properties will be used to populate all internal properties.
*
* @param array $properties
*/
function __construct(array $properties = []) {
foreach ($properties as $k => $v) {
if (property_exists($this, $k)) {
$this->$k = $v;
} else {
throw new \InvalidArgumentException('Unknown property: ' . $k);
}
}
}
/**
* The xmlSerialize method is called during xml writing.
*
* Use the $writer argument to write its own xml serialization.
*
* An important note: do _not_ create a parent element. Any element
* implementing XmlSerializble should only ever write what's considered
* its 'inner xml'.
*
* The parent of the current element is responsible for writing a
* containing element.
*
* This allows serializers to be re-used for different element names.
*
* If you are opening new elements, you must also close them again.
*
* @param Writer $writer
* @return void
*/
function xmlSerialize(Writer $writer) {
$writer->write([
new Href($this->href),
'{DAV:}prop' => $this->properties,
'{DAV:}share-access' => new ShareAccess($this->access),
]);
switch ($this->inviteStatus) {
case Plugin::INVITE_NORESPONSE :
$writer->writeElement('{DAV:}invite-noresponse');
break;
case Plugin::INVITE_ACCEPTED :
$writer->writeElement('{DAV:}invite-accepted');
break;
case Plugin::INVITE_DECLINED :
$writer->writeElement('{DAV:}invite-declined');
break;
case Plugin::INVITE_INVALID :
$writer->writeElement('{DAV:}invite-invalid');
break;
}
}
/**
* 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) {
// Temporarily override configuration
$reader->pushContext();
$reader->elementMap['{DAV:}share-access'] = 'Sabre\DAV\Xml\Property\ShareAccess';
$reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Deserializer\keyValue';
$elems = Deserializer\keyValue($reader, 'DAV:');
// Restore previous configuration
$reader->popContext();
$sharee = new self();
if (!isset($elems['href'])) {
throw new BadRequest('Every {DAV:}sharee must have a {DAV:}href child-element');
}
$sharee->href = $elems['href'];
if (isset($elems['prop'])) {
$sharee->properties = $elems['prop'];
}
if (isset($elems['comment'])) {
$sharee->comment = $elems['comment'];
}
if (!isset($elems['share-access'])) {
throw new BadRequest('Every {DAV:}sharee must have a {DAV:}share-access child element');
}
$sharee->access = $elems['share-access']->getValue();
return $sharee;
}
}

View File

@ -7,6 +7,7 @@ use Sabre\DAV\Browser\HtmlOutputHelper;
use Sabre\Xml\Element;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;
use Sabre\Uri;
/**
* Href property
@ -31,13 +32,6 @@ class Href implements Element, HtmlOutput {
*/
protected $hrefs;
/**
* Automatically prefix the url with the server base directory
*
* @var bool
*/
protected $autoPrefix = true;
/**
* Constructor
*
@ -47,16 +41,13 @@ class Href implements Element, HtmlOutput {
* and not relative to the servers base uri.
*
* @param string|string[] $href
* @param bool $autoPrefix
*/
function __construct($hrefs, $autoPrefix = true) {
function __construct($hrefs) {
if (is_string($hrefs)) {
$hrefs = [$hrefs];
}
$this->hrefs = $hrefs;
$this->autoPrefix = $autoPrefix;
}
@ -104,9 +95,7 @@ class Href implements Element, HtmlOutput {
function xmlSerialize(Writer $writer) {
foreach ($this->getHrefs() as $href) {
if ($this->autoPrefix) {
$href = $writer->contextUri . \Sabre\HTTP\encodePath($href);
}
$href = Uri\resolve($writer->contextUri, $href);
$writer->writeElement('{DAV:}href', $href);
}

View File

@ -0,0 +1,70 @@
<?php
namespace Sabre\DAV\Xml\Property;
use Sabre\DAV\Sharing\Sharee;
use Sabre\Xml\XmlSerializable;
use Sabre\Xml\Writer;
/**
* This class represents the {DAV:}invite property.
*
* This property is defined here:
* https://tools.ietf.org/html/draft-pot-webdav-resource-sharing-03#section-4.4.2
*
* This property is used by clients to determine who currently has access to
* a shared resource, what their access level is and what their invite status
* is.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Invite implements XmlSerializable {
/**
* A list of sharees
*
* @var Sharee[]
*/
public $sharees = [];
/**
* Creates the property.
*
* @param Sharee[] $sharees
*/
function __construct(array $sharees) {
$this->sharees = $sharees;
}
/**
* The xmlSerialize method is called during xml writing.
*
* Use the $writer argument to write its own xml serialization.
*
* An important note: do _not_ create a parent element. Any element
* implementing XmlSerializble should only ever write what's considered
* its 'inner xml'.
*
* The parent of the current element is responsible for writing a
* containing element.
*
* This allows serializers to be re-used for different element names.
*
* If you are opening new elements, you must also close them again.
*
* @param Writer $writer
* @return void
*/
function xmlSerialize(Writer $writer) {
foreach ($this->sharees as $sharee) {
$writer->writeElement('{DAV:}sharee', $sharee);
}
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace Sabre\DAV\Xml\Property;
use Sabre\HTTP;
/**
* LocalHref property
*
* Like the Href property, this element represents {DAV:}href. The difference
* is that this is used stricly for paths on the server. The LocalHref property
* will prepare the path so it's a valid URI.
*
* These two objects behave identically:
* new LocalHref($path)
* new Href(\Sabre\HTTP\encodePath($path))
*
* LocalPath basically ensures that your spaces are %20, and everything that
* needs to be is uri encoded.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://sabre.io/license/ Modified BSD License
*/
class LocalHref extends Href {
/**
* Constructor
*
* You must either pass a string for a single href, or an array of hrefs.
*
* If auto-prefix is set to false, the hrefs will be treated as absolute
* and not relative to the servers base uri.
*
* @param string|string[] $href
*/
function __construct($hrefs) {
parent::__construct(array_map(
function($href) {
return \Sabre\HTTP\encodePath($href);
},
(array)$hrefs
));
}
}

View File

@ -0,0 +1,143 @@
<?php
namespace Sabre\DAV\Xml\Property;
use Sabre\DAV\Sharing\Plugin as SharingPlugin;
use Sabre\DAV\Exception\BadRequest;
use Sabre\Xml\Element;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;
/**
* This class represents the {DAV:}share-access property.
*
* This property is defined here:
* https://tools.ietf.org/html/draft-pot-webdav-resource-sharing-03#section-4.4.1
*
* This property is used to indicate if a resource is a shared resource, and
* whether the instance of the shared resource is the original instance, or
* an instance belonging to a sharee.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class ShareAccess implements Element {
/**
* Either SHARED or SHAREDOWNER
*
* @var int
*/
protected $value;
/**
* Creates the property.
*
* The constructor value must be one of the
* \Sabre\DAV\Sharing\Plugin::ACCESS_ constants.
*
* @param int $shareAccess
*/
function __construct($shareAccess) {
$this->value = $shareAccess;
}
/**
* Returns the current value.
*
* @return int
*/
function getValue() {
return $this->value;
}
/**
* The xmlSerialize method is called during xml writing.
*
* Use the $writer argument to write its own xml serialization.
*
* An important note: do _not_ create a parent element. Any element
* implementing XmlSerializble should only ever write what's considered
* its 'inner xml'.
*
* The parent of the current element is responsible for writing a
* containing element.
*
* This allows serializers to be re-used for different element names.
*
* If you are opening new elements, you must also close them again.
*
* @param Writer $writer
* @return void
*/
function xmlSerialize(Writer $writer) {
switch ($this->value) {
case SharingPlugin::ACCESS_NOTSHARED :
$writer->writeElement('{DAV:}not-shared');
break;
case SharingPlugin::ACCESS_SHAREDOWNER :
$writer->writeElement('{DAV:}shared-owner');
break;
case SharingPlugin::ACCESS_READ :
$writer->writeElement('{DAV:}read');
break;
case SharingPlugin::ACCESS_READWRITE :
$writer->writeElement('{DAV:}read-write');
break;
case SharingPlugin::ACCESS_NOACCESS :
$writer->writeElement('{DAV:}no-access');
break;
}
}
/**
* 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) {
$elems = $reader->parseInnerTree();
foreach ($elems as $elem) {
switch ($elem['name']) {
case '{DAV:}not-shared' :
return new self(SharingPlugin::ACCESS_NOTSHARED);
case '{DAV:}sharedowner' :
return new self(SharingPlugin::ACCESS_SHAREDOWNER);
case '{DAV:}read' :
return new self(SharingPlugin::ACCESS_READ);
case '{DAV:}read-write' :
return new self(SharingPlugin::ACCESS_READWRITE);
case '{DAV:}no-access' :
return new self(SharingPlugin::ACCESS_NOACCESS);
}
}
throw new BadRequest('Invalid value for {DAV:}share-access element');
}
}

View File

@ -0,0 +1,81 @@
<?php
namespace Sabre\DAV\Xml\Request;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;
use Sabre\DAV\Xml\Element\Sharee;
/**
* ShareResource request parser.
*
* This class parses the {DAV:}share-resource POST request as defined in:
*
* https://tools.ietf.org/html/draft-pot-webdav-resource-sharing-01#section-5.3.2.1
*
* @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://sabre.io/license/ Modified BSD License
*/
class ShareResource implements XmlDeserializable {
/**
* The list of new people added or updated or removed from the share.
*
* @var Sharee[]
*/
public $sharees = [];
/**
* Constructor
*
* @param Sharee[] $sharees
*/
function __construct(array $sharees) {
$this->sharees = $sharees;
}
/**
* 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) {
$elems = $reader->parseInnerTree([
'{DAV:}sharee' => 'Sabre\DAV\Xml\Element\Sharee',
'{DAV:}share-access' => 'Sabre\DAV\Xml\Property\ShareAccess',
'{DAV:}prop' => 'Sabre\Xml\Deserializer\keyValue',
]);
$sharees = [];
foreach ($elems as $elem) {
if ($elem['name'] !== '{DAV:}sharee') continue;
$sharees[] = $elem['value'];
}
return new self($sharees);
}
}

100
vendor/sabre/dav/lib/DAVACL/ACLTrait.php vendored Normal file
View File

@ -0,0 +1,100 @@
<?php
namespace Sabre\DAVACL;
/**
* This trait is a default implementation of the IACL interface.
*
* In many cases you only want to implement 1 or to of the IACL functions,
* this trait allows you to be a bit lazier.
*
* By default this trait grants all privileges to the owner of the resource.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (https://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
trait ACLTrait {
/**
* Returns the owner principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
function getOwner() {
return null;
}
/**
* 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:}all',
'principal' => '{DAV:}owner',
'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 \Sabre\DAV\Exception\Forbidden('Setting ACL is not supported on this node');
}
/**
* 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

@ -110,7 +110,7 @@ abstract class AbstractPrincipalCollection extends DAV\Collection implements IPr
*
* @param string $name
* @throws DAV\Exception\NotFound
* @return IPrincipal
* @return DAV\INode
*/
function getChild($name) {

View File

@ -3,6 +3,7 @@
namespace Sabre\DAVACL\FS;
use Sabre\DAV\FSExt\Directory as BaseCollection;
use Sabre\DAVACL\ACLTrait;
use Sabre\DAVACL\IACL;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
@ -16,6 +17,8 @@ use Sabre\DAV\Exception\NotFound;
*/
class Collection extends BaseCollection implements IACL {
use ACLTrait;
/**
* A list of ACL rules.
*
@ -52,8 +55,8 @@ class Collection extends BaseCollection implements IACL {
* exist.
*
* @param string $name
* @throws DAV\Exception\NotFound
* @return DAV\INode
* @throws NotFound
* @return \Sabre\DAV\INode
*/
function getChild($name) {
@ -87,19 +90,6 @@ class Collection extends BaseCollection implements 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.
*
@ -118,36 +108,4 @@ class Collection extends BaseCollection implements IACL {
}
/**
* 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 Forbidden('Setting ACL is not allowed 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;
}
}

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