This commit is contained in:
Simon Vieille 2025-10-30 10:55:33 +01:00
commit e1dd5ee0e0
No known key found for this signature in database
GPG key ID: 477F8D32E8E286EB
481 changed files with 71417 additions and 0 deletions

10
composer.json Normal file
View file

@ -0,0 +1,10 @@
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"require": {
"sabre/dav": "^4.7"
}
}

514
composer.lock generated Normal file
View file

@ -0,0 +1,514 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "0e79da8d77b3dec3d9a0fc451cb0d118",
"packages": [
{
"name": "psr/log",
"version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
"shasum": ""
},
"require": {
"php": ">=8.0.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Log\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
],
"support": {
"source": "https://github.com/php-fig/log/tree/3.0.2"
},
"time": "2024-09-11T13:17:53+00:00"
},
{
"name": "sabre/dav",
"version": "4.7.0",
"source": {
"type": "git",
"url": "https://github.com/sabre-io/dav.git",
"reference": "074373bcd689a30bcf5aaa6bbb20a3395964ce7a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sabre-io/dav/zipball/074373bcd689a30bcf5aaa6bbb20a3395964ce7a",
"reference": "074373bcd689a30bcf5aaa6bbb20a3395964ce7a",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-date": "*",
"ext-dom": "*",
"ext-iconv": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-pcre": "*",
"ext-simplexml": "*",
"ext-spl": "*",
"lib-libxml": ">=2.7.0",
"php": "^7.1.0 || ^8.0",
"psr/log": "^1.0 || ^2.0 || ^3.0",
"sabre/event": "^5.0",
"sabre/http": "^5.0.5",
"sabre/uri": "^2.0",
"sabre/vobject": "^4.2.1",
"sabre/xml": "^2.0.1"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.19",
"monolog/monolog": "^1.27 || ^2.0",
"phpstan/phpstan": "^0.12 || ^1.0",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
},
"suggest": {
"ext-curl": "*",
"ext-imap": "*",
"ext-pdo": "*"
},
"bin": [
"bin/sabredav",
"bin/naturalselection"
],
"type": "library",
"autoload": {
"psr-4": {
"Sabre\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Evert Pot",
"email": "me@evertpot.com",
"homepage": "http://evertpot.com/",
"role": "Developer"
}
],
"description": "WebDAV Framework for PHP",
"homepage": "http://sabre.io/",
"keywords": [
"CalDAV",
"CardDAV",
"WebDAV",
"framework",
"iCalendar"
],
"support": {
"forum": "https://groups.google.com/group/sabredav-discuss",
"issues": "https://github.com/sabre-io/dav/issues",
"source": "https://github.com/fruux/sabre-dav"
},
"time": "2024-10-29T11:46:02+00:00"
},
{
"name": "sabre/event",
"version": "5.1.7",
"source": {
"type": "git",
"url": "https://github.com/sabre-io/event.git",
"reference": "86d57e305c272898ba3c28e9bd3d65d5464587c2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sabre-io/event/zipball/86d57e305c272898ba3c28e9bd3d65d5464587c2",
"reference": "86d57e305c272898ba3c28e9bd3d65d5464587c2",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "~2.17.1||^3.63",
"phpstan/phpstan": "^0.12",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
},
"type": "library",
"autoload": {
"files": [
"lib/coroutine.php",
"lib/Loop/functions.php",
"lib/Promise/functions.php"
],
"psr-4": {
"Sabre\\Event\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Evert Pot",
"email": "me@evertpot.com",
"homepage": "http://evertpot.com/",
"role": "Developer"
}
],
"description": "sabre/event is a library for lightweight event-based programming",
"homepage": "http://sabre.io/event/",
"keywords": [
"EventEmitter",
"async",
"coroutine",
"eventloop",
"events",
"hooks",
"plugin",
"promise",
"reactor",
"signal"
],
"support": {
"forum": "https://groups.google.com/group/sabredav-discuss",
"issues": "https://github.com/sabre-io/event/issues",
"source": "https://github.com/fruux/sabre-event"
},
"time": "2024-08-27T11:23:05+00:00"
},
{
"name": "sabre/http",
"version": "5.1.13",
"source": {
"type": "git",
"url": "https://github.com/sabre-io/http.git",
"reference": "7c2a14097d1a0de2347dcbdc91a02f38e338f4db"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sabre-io/http/zipball/7c2a14097d1a0de2347dcbdc91a02f38e338f4db",
"reference": "7c2a14097d1a0de2347dcbdc91a02f38e338f4db",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-curl": "*",
"ext-mbstring": "*",
"php": "^7.1 || ^8.0",
"sabre/event": ">=4.0 <6.0",
"sabre/uri": "^2.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "~2.17.1||3.63.2",
"phpstan/phpstan": "^0.12",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
},
"suggest": {
"ext-curl": " to make http requests with the Client class"
},
"type": "library",
"autoload": {
"files": [
"lib/functions.php"
],
"psr-4": {
"Sabre\\HTTP\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Evert Pot",
"email": "me@evertpot.com",
"homepage": "http://evertpot.com/",
"role": "Developer"
}
],
"description": "The sabre/http library provides utilities for dealing with http requests and responses. ",
"homepage": "https://github.com/fruux/sabre-http",
"keywords": [
"http"
],
"support": {
"forum": "https://groups.google.com/group/sabredav-discuss",
"issues": "https://github.com/sabre-io/http/issues",
"source": "https://github.com/fruux/sabre-http"
},
"time": "2025-09-09T10:21:47+00:00"
},
{
"name": "sabre/uri",
"version": "2.3.4",
"source": {
"type": "git",
"url": "https://github.com/sabre-io/uri.git",
"reference": "b76524c22de90d80ca73143680a8e77b1266c291"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sabre-io/uri/zipball/b76524c22de90d80ca73143680a8e77b1266c291",
"reference": "b76524c22de90d80ca73143680a8e77b1266c291",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.63",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan": "^1.12",
"phpstan/phpstan-phpunit": "^1.4",
"phpstan/phpstan-strict-rules": "^1.6",
"phpunit/phpunit": "^9.6"
},
"type": "library",
"autoload": {
"files": [
"lib/functions.php"
],
"psr-4": {
"Sabre\\Uri\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Evert Pot",
"email": "me@evertpot.com",
"homepage": "http://evertpot.com/",
"role": "Developer"
}
],
"description": "Functions for making sense out of URIs.",
"homepage": "http://sabre.io/uri/",
"keywords": [
"rfc3986",
"uri",
"url"
],
"support": {
"forum": "https://groups.google.com/group/sabredav-discuss",
"issues": "https://github.com/sabre-io/uri/issues",
"source": "https://github.com/fruux/sabre-uri"
},
"time": "2024-08-27T12:18:16+00:00"
},
{
"name": "sabre/vobject",
"version": "4.5.7",
"source": {
"type": "git",
"url": "https://github.com/sabre-io/vobject.git",
"reference": "ff22611a53782e90c97be0d0bc4a5f98a5c0a12c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sabre-io/vobject/zipball/ff22611a53782e90c97be0d0bc4a5f98a5c0a12c",
"reference": "ff22611a53782e90c97be0d0bc4a5f98a5c0a12c",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^7.1 || ^8.0",
"sabre/xml": "^2.1 || ^3.0 || ^4.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "~2.17.1",
"phpstan/phpstan": "^0.12 || ^1.12 || ^2.0",
"phpunit/php-invoker": "^2.0 || ^3.1",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
},
"suggest": {
"hoa/bench": "If you would like to run the benchmark scripts"
},
"bin": [
"bin/vobject",
"bin/generate_vcards"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Sabre\\VObject\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Evert Pot",
"email": "me@evertpot.com",
"homepage": "http://evertpot.com/",
"role": "Developer"
},
{
"name": "Dominik Tobschall",
"email": "dominik@fruux.com",
"homepage": "http://tobschall.de/",
"role": "Developer"
},
{
"name": "Ivan Enderlin",
"email": "ivan.enderlin@hoa-project.net",
"homepage": "http://mnt.io/",
"role": "Developer"
}
],
"description": "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects",
"homepage": "http://sabre.io/vobject/",
"keywords": [
"availability",
"freebusy",
"iCalendar",
"ical",
"ics",
"jCal",
"jCard",
"recurrence",
"rfc2425",
"rfc2426",
"rfc2739",
"rfc4770",
"rfc5545",
"rfc5546",
"rfc6321",
"rfc6350",
"rfc6351",
"rfc6474",
"rfc6638",
"rfc6715",
"rfc6868",
"vCalendar",
"vCard",
"vcf",
"xCal",
"xCard"
],
"support": {
"forum": "https://groups.google.com/group/sabredav-discuss",
"issues": "https://github.com/sabre-io/vobject/issues",
"source": "https://github.com/fruux/sabre-vobject"
},
"time": "2025-04-17T09:22:48+00:00"
},
{
"name": "sabre/xml",
"version": "2.2.11",
"source": {
"type": "git",
"url": "https://github.com/sabre-io/xml.git",
"reference": "01a7927842abf3e10df3d9c2d9b0cc9d813a3fcc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sabre-io/xml/zipball/01a7927842abf3e10df3d9c2d9b0cc9d813a3fcc",
"reference": "01a7927842abf3e10df3d9c2d9b0cc9d813a3fcc",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"lib-libxml": ">=2.6.20",
"php": "^7.1 || ^8.0",
"sabre/uri": ">=1.0,<3.0.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "~2.17.1||3.63.2",
"phpstan/phpstan": "^0.12",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
},
"type": "library",
"autoload": {
"files": [
"lib/Deserializer/functions.php",
"lib/Serializer/functions.php"
],
"psr-4": {
"Sabre\\Xml\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Evert Pot",
"email": "me@evertpot.com",
"homepage": "http://evertpot.com/",
"role": "Developer"
},
{
"name": "Markus Staab",
"email": "markus.staab@redaxo.de",
"role": "Developer"
}
],
"description": "sabre/xml is an XML library that you may not hate.",
"homepage": "https://sabre.io/xml/",
"keywords": [
"XMLReader",
"XMLWriter",
"dom",
"xml"
],
"support": {
"forum": "https://groups.google.com/group/sabredav-discuss",
"issues": "https://github.com/sabre-io/xml/issues",
"source": "https://github.com/fruux/sabre-xml"
},
"time": "2024-09-06T07:37:46+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {},
"platform-dev": {},
"plugin-api-version": "2.6.0"
}

36
index.php Normal file
View file

@ -0,0 +1,36 @@
<?php
use App\Api\Webdav\Client;
require __DIR__.'/vendor/autoload.php';
$client = new Client(
server: "http://127.0.0.1:9977", // URL de nextcloud, ici c'est un proxy qui capture les requêtes/réponses HTTP
baseUrl: "/remote.php/dav/files/myUsername",
username: "myUsername",
password: "my-application-password",
);
$client->ls("/");
// Request: https://privatebin.deblan.org/?66febc015c225eca#J3vZ4kiETeoWbT4tzMTXohGFcsoXNJF1oGcwsV4Xzrf2
// Response: https://privatebin.deblan.org/?9ca392dfc51031e0#G1F9yp6UaMxdnjERLK21CXUAWhfmy95sGZiymhCumDAB
$client->rm('/Nextcloud%20intro.mp4');
// Request: https://privatebin.deblan.org/?a5d6ba25ba3f51fe#BgCwDoivjqNxbBunNG4ys3SUKLCZmoN5ksmu216YtiNH
// Response: https://privatebin.deblan.org/?841a24dd6fad7ba0#7QENgLxesydSXQhVv3YouaXLjVcroKZUZDFSKKiMZik
$client->mv('/Nextcloud.png', '/foo.png');
// Request: https://privatebin.deblan.org/?d978bf3ccd75eefb#GYbT9Ba6dnXrQSdAipcemwgGPpGpPPiqdyjnHMB6WUd3
// Response: https://privatebin.deblan.org/?74a72a4e32b29153#9yoWpodeE365NftiiZ5jafPVafJiJPGbM4UHVqev986A
$client->sendFile('newfile.txt', '/newfile.txt');
// Request: https://privatebin.deblan.org/?1ff6666b3b95b02c#BY5vLoFjGHw8xMChWJwuSrGycsQSo86iewczP9uKgdYd
// Response: https://privatebin.deblan.org/?6049b0c478947bea#9ZWzVMXvfSWiMguXmx63qwogYR8HZDZjqWL9Rb2mNk8v
$client->get('/newfile.txt');
// Request: https://privatebin.deblan.org/?409193e4233d0682#7LiQe4Dpm1u5RuuWbCkXEPhSuejVhFDTZ11gGyxFWJho
// Response: https://privatebin.deblan.org/?44860aca2cd519a8#6c1ABAq6zpLKdkNvttxqvKjnZ5yyoKPvmjp2X8p1a7sY
$client->mkdir('/NewDir');
// Request: https://privatebin.deblan.org/?43b69b23868f22c8#88U8HhFhC7hgJLabTuQBgMLDCmfTga22aQANbwtM9dX4
// Response: https://privatebin.deblan.org/?92f8b0c79ea67e2d#BtHcaQuUBroAFku1FXCuZwmupYenC697zFQrfsXQtZhC

1
newfile.txt Normal file
View file

@ -0,0 +1 @@
Dolor veniam quis dolore voluptate aliquip labore deserunt enim aute ullamco excepteur do ad. Laboris magna mollit sit qui quis. Non adipisicing do dolore reprehenderit dolor. Nostrud occaecat eiusmod ex cillum consectetur duis eu proident duis nulla ex nulla aute. Deserunt incididunt quis magna eiusmod eu adipisicing nisi pariatur.

79
src/Api/Webdav/Client.php Normal file
View file

@ -0,0 +1,79 @@
<?php
namespace App\Api\Webdav;
use Sabre\DAV\Client as BaseClient;
/**
* class Client.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class Client
{
protected BaseClient $client;
protected string $baseUrl;
public function __construct(string $server, string $baseUrl, ?string $username, ?string $password)
{
$settings = [
'baseUri' => $server,
'userName' => $username,
'password' => $password,
'authType' => 1,
];
$this->baseUrl = rtrim($baseUrl, '/');
$this->client = new BaseClient($settings);
}
public function sendFile(string $localFile, string $remoteFile): array
{
return $this->client->request(
'PUT',
$this->baseUrl.'/'.$remoteFile,
fopen($localFile, 'r')
);
}
public function mv(string $source, string $destination): array
{
return $this->client->request(
'MOVE',
$this->baseUrl.'/'.$source,
null,
[
'Destination' => $this->baseUrl.'/'.$destination,
]
);
}
public function mkdir(string $directory): array
{
return $this->client->request(
'MKCOL',
$this->baseUrl.'/'.$directory
);
}
public function rm(string $file): array
{
return $this->client->request(
'DELETE',
$this->baseUrl.'/'.$file
);
}
public function ls(string $directory): array
{
return $this->client->propfind($this->baseUrl.'/'.$directory, [
'{DAV:}displayname',
'{DAV:}getcontentlength',
], 1);
}
public function get(string $file)
{
return $this->client->request('GET', $this->baseUrl.'/'.$file);
}
}

25
vendor/autoload.php vendored Normal file
View file

@ -0,0 +1,25 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
trigger_error(
$err,
E_USER_ERROR
);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInite6a6e754980b2e4ee0099ab3b538a42e::getLoader();

119
vendor/bin/generate_vcards vendored Executable file
View file

@ -0,0 +1,119 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../sabre/vobject/bin/generate_vcards)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/sabre/vobject/bin/generate_vcards');
}
}
return include __DIR__ . '/..'.'/sabre/vobject/bin/generate_vcards';

37
vendor/bin/naturalselection vendored Executable file
View file

@ -0,0 +1,37 @@
#!/usr/bin/env sh
# Support bash to support `source` with fallback on $0 if this does not run with bash
# https://stackoverflow.com/a/35006505/6512
selfArg="$BASH_SOURCE"
if [ -z "$selfArg" ]; then
selfArg="$0"
fi
self=$(realpath $selfArg 2> /dev/null)
if [ -z "$self" ]; then
self="$selfArg"
fi
dir=$(cd "${self%[/\\]*}" > /dev/null; cd '../sabre/dav/bin' && pwd)
if [ -d /proc/cygdrive ]; then
case $(which php) in
$(readlink -n /proc/cygdrive)/*)
# We are in Cygwin using Windows php, so the path must be translated
dir=$(cygpath -m "$dir");
;;
esac
fi
export COMPOSER_RUNTIME_BIN_DIR="$(cd "${self%[/\\]*}" > /dev/null; pwd)"
# If bash is sourcing this file, we have to source the target as well
bashSource="$BASH_SOURCE"
if [ -n "$bashSource" ]; then
if [ "$bashSource" != "$0" ]; then
source "${dir}/naturalselection" "$@"
return
fi
fi
exec "${dir}/naturalselection" "$@"

37
vendor/bin/sabredav vendored Executable file
View file

@ -0,0 +1,37 @@
#!/usr/bin/env sh
# Support bash to support `source` with fallback on $0 if this does not run with bash
# https://stackoverflow.com/a/35006505/6512
selfArg="$BASH_SOURCE"
if [ -z "$selfArg" ]; then
selfArg="$0"
fi
self=$(realpath $selfArg 2> /dev/null)
if [ -z "$self" ]; then
self="$selfArg"
fi
dir=$(cd "${self%[/\\]*}" > /dev/null; cd '../sabre/dav/bin' && pwd)
if [ -d /proc/cygdrive ]; then
case $(which php) in
$(readlink -n /proc/cygdrive)/*)
# We are in Cygwin using Windows php, so the path must be translated
dir=$(cygpath -m "$dir");
;;
esac
fi
export COMPOSER_RUNTIME_BIN_DIR="$(cd "${self%[/\\]*}" > /dev/null; pwd)"
# If bash is sourcing this file, we have to source the target as well
bashSource="$BASH_SOURCE"
if [ -n "$bashSource" ]; then
if [ "$bashSource" != "$0" ]; then
source "${dir}/sabredav" "$@"
return
fi
fi
exec "${dir}/sabredav" "$@"

119
vendor/bin/vobject vendored Executable file
View file

@ -0,0 +1,119 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../sabre/vobject/bin/vobject)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/sabre/vobject/bin/vobject');
}
}
return include __DIR__ . '/..'.'/sabre/vobject/bin/vobject';

579
vendor/composer/ClassLoader.php vendored Normal file
View file

@ -0,0 +1,579 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var \Closure(string):void */
private static $includeFile;
/** @var string|null */
private $vendorDir;
// PSR-4
/**
* @var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array<string, list<string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* List of PSR-0 prefixes
*
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
*
* @var array<string, array<string, list<string>>>
*/
private $prefixesPsr0 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var array<string, bool>
*/
private $missingClasses = array();
/** @var string|null */
private $apcuPrefix;
/**
* @var array<string, self>
*/
private static $registeredLoaders = array();
/**
* @param string|null $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return list<string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return list<string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return array<string, string> Array of classname => path
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array<string, string> $classMap Class to filename map
*
* @return void
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
$paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
$paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
$paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
$paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
$includeFile = self::$includeFile;
$includeFile($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders keyed by their corresponding vendor directories.
*
* @return array<string, self>
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
/**
* @return void
*/
private static function initializeIncludeClosure()
{
if (self::$includeFile !== null) {
return;
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
*/
self::$includeFile = \Closure::bind(static function($file) {
include $file;
}, null, null);
}
}

378
vendor/composer/InstalledVersions.php vendored Normal file
View file

@ -0,0 +1,378 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool
*/
private static $installedIsLocalDir;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints((string) $constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
// when using reload, we disable the duplicate protection to ensure that self::$installed data is
// always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
// so we have to assume it does not, and that may result in duplicate data being returned when listing
// all installed packages for example
self::$installedIsLocalDir = false;
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
$copiedLocalDir = false;
if (self::$canGetVendors) {
$selfDir = strtr(__DIR__, '\\', '/');
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
$vendorDir = strtr($vendorDir, '\\', '/');
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
self::$installedByVendor[$vendorDir] = $required;
$installed[] = $required;
if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
self::$installed = $required;
self::$installedIsLocalDir = true;
}
}
if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
$copiedLocalDir = true;
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require __DIR__ . '/installed.php';
self::$installed = $required;
} else {
self::$installed = array();
}
}
if (self::$installed !== array() && !$copiedLocalDir) {
$installed[] = self::$installed;
}
return $installed;
}
}

21
vendor/composer/LICENSE vendored Normal file
View file

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

10
vendor/composer/autoload_classmap.php vendored Normal file
View file

@ -0,0 +1,10 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
);

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

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

View file

@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
);

17
vendor/composer/autoload_psr4.php vendored Normal file
View file

@ -0,0 +1,17 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Sabre\\Xml\\' => array($vendorDir . '/sabre/xml/lib'),
'Sabre\\VObject\\' => array($vendorDir . '/sabre/vobject/lib'),
'Sabre\\Uri\\' => array($vendorDir . '/sabre/uri/lib'),
'Sabre\\HTTP\\' => array($vendorDir . '/sabre/http/lib'),
'Sabre\\Event\\' => array($vendorDir . '/sabre/event/lib'),
'Sabre\\' => array($vendorDir . '/sabre/dav/lib'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/src'),
'App\\' => array($baseDir . '/src'),
);

50
vendor/composer/autoload_real.php vendored Normal file
View file

@ -0,0 +1,50 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInite6a6e754980b2e4ee0099ab3b538a42e
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInite6a6e754980b2e4ee0099ab3b538a42e', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInite6a6e754980b2e4ee0099ab3b538a42e', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInite6a6e754980b2e4ee0099ab3b538a42e::getInitializer($loader));
$loader->register(true);
$filesToLoad = \Composer\Autoload\ComposerStaticInite6a6e754980b2e4ee0099ab3b538a42e::$files;
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
require $file;
}
}, null, null);
foreach ($filesToLoad as $fileIdentifier => $file) {
$requireFile($fileIdentifier, $file);
}
return $loader;
}
}

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

@ -0,0 +1,87 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInite6a6e754980b2e4ee0099ab3b538a42e
{
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\\' => 6,
),
'P' =>
array (
'Psr\\Log\\' => 8,
),
'A' =>
array (
'App\\' => 4,
),
);
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\\' =>
array (
0 => __DIR__ . '/..' . '/sabre/dav/lib',
),
'Psr\\Log\\' =>
array (
0 => __DIR__ . '/..' . '/psr/log/src',
),
'App\\' =>
array (
0 => __DIR__ . '/../..' . '/src',
),
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInite6a6e754980b2e4ee0099ab3b538a42e::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInite6a6e754980b2e4ee0099ab3b538a42e::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInite6a6e754980b2e4ee0099ab3b538a42e::$classMap;
}, null, ClassLoader::class);
}
}

522
vendor/composer/installed.json vendored Normal file
View file

@ -0,0 +1,522 @@
{
"packages": [
{
"name": "psr/log",
"version": "3.0.2",
"version_normalized": "3.0.2.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
"shasum": ""
},
"require": {
"php": ">=8.0.0"
},
"time": "2024-09-11T13:17:53+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Psr\\Log\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
],
"support": {
"source": "https://github.com/php-fig/log/tree/3.0.2"
},
"install-path": "../psr/log"
},
{
"name": "sabre/dav",
"version": "4.7.0",
"version_normalized": "4.7.0.0",
"source": {
"type": "git",
"url": "https://github.com/sabre-io/dav.git",
"reference": "074373bcd689a30bcf5aaa6bbb20a3395964ce7a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sabre-io/dav/zipball/074373bcd689a30bcf5aaa6bbb20a3395964ce7a",
"reference": "074373bcd689a30bcf5aaa6bbb20a3395964ce7a",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-date": "*",
"ext-dom": "*",
"ext-iconv": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-pcre": "*",
"ext-simplexml": "*",
"ext-spl": "*",
"lib-libxml": ">=2.7.0",
"php": "^7.1.0 || ^8.0",
"psr/log": "^1.0 || ^2.0 || ^3.0",
"sabre/event": "^5.0",
"sabre/http": "^5.0.5",
"sabre/uri": "^2.0",
"sabre/vobject": "^4.2.1",
"sabre/xml": "^2.0.1"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.19",
"monolog/monolog": "^1.27 || ^2.0",
"phpstan/phpstan": "^0.12 || ^1.0",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
},
"suggest": {
"ext-curl": "*",
"ext-imap": "*",
"ext-pdo": "*"
},
"time": "2024-10-29T11:46:02+00:00",
"bin": [
"bin/sabredav",
"bin/naturalselection"
],
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Sabre\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Evert Pot",
"email": "me@evertpot.com",
"homepage": "http://evertpot.com/",
"role": "Developer"
}
],
"description": "WebDAV Framework for PHP",
"homepage": "http://sabre.io/",
"keywords": [
"CalDAV",
"CardDAV",
"WebDAV",
"framework",
"iCalendar"
],
"support": {
"forum": "https://groups.google.com/group/sabredav-discuss",
"issues": "https://github.com/sabre-io/dav/issues",
"source": "https://github.com/fruux/sabre-dav"
},
"install-path": "../sabre/dav"
},
{
"name": "sabre/event",
"version": "5.1.7",
"version_normalized": "5.1.7.0",
"source": {
"type": "git",
"url": "https://github.com/sabre-io/event.git",
"reference": "86d57e305c272898ba3c28e9bd3d65d5464587c2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sabre-io/event/zipball/86d57e305c272898ba3c28e9bd3d65d5464587c2",
"reference": "86d57e305c272898ba3c28e9bd3d65d5464587c2",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "~2.17.1||^3.63",
"phpstan/phpstan": "^0.12",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
},
"time": "2024-08-27T11:23:05+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"files": [
"lib/coroutine.php",
"lib/Loop/functions.php",
"lib/Promise/functions.php"
],
"psr-4": {
"Sabre\\Event\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Evert Pot",
"email": "me@evertpot.com",
"homepage": "http://evertpot.com/",
"role": "Developer"
}
],
"description": "sabre/event is a library for lightweight event-based programming",
"homepage": "http://sabre.io/event/",
"keywords": [
"EventEmitter",
"async",
"coroutine",
"eventloop",
"events",
"hooks",
"plugin",
"promise",
"reactor",
"signal"
],
"support": {
"forum": "https://groups.google.com/group/sabredav-discuss",
"issues": "https://github.com/sabre-io/event/issues",
"source": "https://github.com/fruux/sabre-event"
},
"install-path": "../sabre/event"
},
{
"name": "sabre/http",
"version": "5.1.13",
"version_normalized": "5.1.13.0",
"source": {
"type": "git",
"url": "https://github.com/sabre-io/http.git",
"reference": "7c2a14097d1a0de2347dcbdc91a02f38e338f4db"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sabre-io/http/zipball/7c2a14097d1a0de2347dcbdc91a02f38e338f4db",
"reference": "7c2a14097d1a0de2347dcbdc91a02f38e338f4db",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-curl": "*",
"ext-mbstring": "*",
"php": "^7.1 || ^8.0",
"sabre/event": ">=4.0 <6.0",
"sabre/uri": "^2.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "~2.17.1||3.63.2",
"phpstan/phpstan": "^0.12",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
},
"suggest": {
"ext-curl": " to make http requests with the Client class"
},
"time": "2025-09-09T10:21:47+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"files": [
"lib/functions.php"
],
"psr-4": {
"Sabre\\HTTP\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Evert Pot",
"email": "me@evertpot.com",
"homepage": "http://evertpot.com/",
"role": "Developer"
}
],
"description": "The sabre/http library provides utilities for dealing with http requests and responses. ",
"homepage": "https://github.com/fruux/sabre-http",
"keywords": [
"http"
],
"support": {
"forum": "https://groups.google.com/group/sabredav-discuss",
"issues": "https://github.com/sabre-io/http/issues",
"source": "https://github.com/fruux/sabre-http"
},
"install-path": "../sabre/http"
},
{
"name": "sabre/uri",
"version": "2.3.4",
"version_normalized": "2.3.4.0",
"source": {
"type": "git",
"url": "https://github.com/sabre-io/uri.git",
"reference": "b76524c22de90d80ca73143680a8e77b1266c291"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sabre-io/uri/zipball/b76524c22de90d80ca73143680a8e77b1266c291",
"reference": "b76524c22de90d80ca73143680a8e77b1266c291",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.63",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan": "^1.12",
"phpstan/phpstan-phpunit": "^1.4",
"phpstan/phpstan-strict-rules": "^1.6",
"phpunit/phpunit": "^9.6"
},
"time": "2024-08-27T12:18:16+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"files": [
"lib/functions.php"
],
"psr-4": {
"Sabre\\Uri\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Evert Pot",
"email": "me@evertpot.com",
"homepage": "http://evertpot.com/",
"role": "Developer"
}
],
"description": "Functions for making sense out of URIs.",
"homepage": "http://sabre.io/uri/",
"keywords": [
"rfc3986",
"uri",
"url"
],
"support": {
"forum": "https://groups.google.com/group/sabredav-discuss",
"issues": "https://github.com/sabre-io/uri/issues",
"source": "https://github.com/fruux/sabre-uri"
},
"install-path": "../sabre/uri"
},
{
"name": "sabre/vobject",
"version": "4.5.7",
"version_normalized": "4.5.7.0",
"source": {
"type": "git",
"url": "https://github.com/sabre-io/vobject.git",
"reference": "ff22611a53782e90c97be0d0bc4a5f98a5c0a12c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sabre-io/vobject/zipball/ff22611a53782e90c97be0d0bc4a5f98a5c0a12c",
"reference": "ff22611a53782e90c97be0d0bc4a5f98a5c0a12c",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^7.1 || ^8.0",
"sabre/xml": "^2.1 || ^3.0 || ^4.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "~2.17.1",
"phpstan/phpstan": "^0.12 || ^1.12 || ^2.0",
"phpunit/php-invoker": "^2.0 || ^3.1",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
},
"suggest": {
"hoa/bench": "If you would like to run the benchmark scripts"
},
"time": "2025-04-17T09:22:48+00:00",
"bin": [
"bin/vobject",
"bin/generate_vcards"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Sabre\\VObject\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Evert Pot",
"email": "me@evertpot.com",
"homepage": "http://evertpot.com/",
"role": "Developer"
},
{
"name": "Dominik Tobschall",
"email": "dominik@fruux.com",
"homepage": "http://tobschall.de/",
"role": "Developer"
},
{
"name": "Ivan Enderlin",
"email": "ivan.enderlin@hoa-project.net",
"homepage": "http://mnt.io/",
"role": "Developer"
}
],
"description": "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects",
"homepage": "http://sabre.io/vobject/",
"keywords": [
"availability",
"freebusy",
"iCalendar",
"ical",
"ics",
"jCal",
"jCard",
"recurrence",
"rfc2425",
"rfc2426",
"rfc2739",
"rfc4770",
"rfc5545",
"rfc5546",
"rfc6321",
"rfc6350",
"rfc6351",
"rfc6474",
"rfc6638",
"rfc6715",
"rfc6868",
"vCalendar",
"vCard",
"vcf",
"xCal",
"xCard"
],
"support": {
"forum": "https://groups.google.com/group/sabredav-discuss",
"issues": "https://github.com/sabre-io/vobject/issues",
"source": "https://github.com/fruux/sabre-vobject"
},
"install-path": "../sabre/vobject"
},
{
"name": "sabre/xml",
"version": "2.2.11",
"version_normalized": "2.2.11.0",
"source": {
"type": "git",
"url": "https://github.com/sabre-io/xml.git",
"reference": "01a7927842abf3e10df3d9c2d9b0cc9d813a3fcc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sabre-io/xml/zipball/01a7927842abf3e10df3d9c2d9b0cc9d813a3fcc",
"reference": "01a7927842abf3e10df3d9c2d9b0cc9d813a3fcc",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"lib-libxml": ">=2.6.20",
"php": "^7.1 || ^8.0",
"sabre/uri": ">=1.0,<3.0.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "~2.17.1||3.63.2",
"phpstan/phpstan": "^0.12",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
},
"time": "2024-09-06T07:37:46+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"files": [
"lib/Deserializer/functions.php",
"lib/Serializer/functions.php"
],
"psr-4": {
"Sabre\\Xml\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Evert Pot",
"email": "me@evertpot.com",
"homepage": "http://evertpot.com/",
"role": "Developer"
},
{
"name": "Markus Staab",
"email": "markus.staab@redaxo.de",
"role": "Developer"
}
],
"description": "sabre/xml is an XML library that you may not hate.",
"homepage": "https://sabre.io/xml/",
"keywords": [
"XMLReader",
"XMLWriter",
"dom",
"xml"
],
"support": {
"forum": "https://groups.google.com/group/sabredav-discuss",
"issues": "https://github.com/sabre-io/xml/issues",
"source": "https://github.com/fruux/sabre-xml"
},
"install-path": "../sabre/xml"
}
],
"dev": true,
"dev-package-names": []
}

86
vendor/composer/installed.php vendored Normal file
View file

@ -0,0 +1,86 @@
<?php return array(
'root' => array(
'name' => '__root__',
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'reference' => null,
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev' => true,
),
'versions' => array(
'__root__' => array(
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'reference' => null,
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/log' => array(
'pretty_version' => '3.0.2',
'version' => '3.0.2.0',
'reference' => 'f16e1d5863e37f8d8c2a01719f5b34baa2b714d3',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/log',
'aliases' => array(),
'dev_requirement' => false,
),
'sabre/dav' => array(
'pretty_version' => '4.7.0',
'version' => '4.7.0.0',
'reference' => '074373bcd689a30bcf5aaa6bbb20a3395964ce7a',
'type' => 'library',
'install_path' => __DIR__ . '/../sabre/dav',
'aliases' => array(),
'dev_requirement' => false,
),
'sabre/event' => array(
'pretty_version' => '5.1.7',
'version' => '5.1.7.0',
'reference' => '86d57e305c272898ba3c28e9bd3d65d5464587c2',
'type' => 'library',
'install_path' => __DIR__ . '/../sabre/event',
'aliases' => array(),
'dev_requirement' => false,
),
'sabre/http' => array(
'pretty_version' => '5.1.13',
'version' => '5.1.13.0',
'reference' => '7c2a14097d1a0de2347dcbdc91a02f38e338f4db',
'type' => 'library',
'install_path' => __DIR__ . '/../sabre/http',
'aliases' => array(),
'dev_requirement' => false,
),
'sabre/uri' => array(
'pretty_version' => '2.3.4',
'version' => '2.3.4.0',
'reference' => 'b76524c22de90d80ca73143680a8e77b1266c291',
'type' => 'library',
'install_path' => __DIR__ . '/../sabre/uri',
'aliases' => array(),
'dev_requirement' => false,
),
'sabre/vobject' => array(
'pretty_version' => '4.5.7',
'version' => '4.5.7.0',
'reference' => 'ff22611a53782e90c97be0d0bc4a5f98a5c0a12c',
'type' => 'library',
'install_path' => __DIR__ . '/../sabre/vobject',
'aliases' => array(),
'dev_requirement' => false,
),
'sabre/xml' => array(
'pretty_version' => '2.2.11',
'version' => '2.2.11.0',
'reference' => '01a7927842abf3e10df3d9c2d9b0cc9d813a3fcc',
'type' => 'library',
'install_path' => __DIR__ . '/../sabre/xml',
'aliases' => array(),
'dev_requirement' => false,
),
),
);

26
vendor/composer/platform_check.php vendored Normal file
View file

@ -0,0 +1,26 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 80000)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.0.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}

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.

58
vendor/psr/log/README.md vendored Normal file
View file

@ -0,0 +1,58 @@
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.
Installation
------------
```bash
composer require psr/log
```
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');
}
try {
$this->doSomethingElse();
} catch (Exception $exception) {
$this->logger->error('Oh no!', array('exception' => $exception));
}
// 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.

26
vendor/psr/log/composer.json vendored Normal file
View file

@ -0,0 +1,26 @@
{
"name": "psr/log",
"description": "Common interface for logging libraries",
"keywords": ["psr", "psr-3", "log"],
"homepage": "https://github.com/php-fig/log",
"license": "MIT",
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"require": {
"php": ">=8.0.0"
},
"autoload": {
"psr-4": {
"Psr\\Log\\": "src"
}
},
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
}
}

15
vendor/psr/log/src/AbstractLogger.php vendored Normal file
View file

@ -0,0 +1,15 @@
<?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
{
use LoggerTrait;
}

View file

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

18
vendor/psr/log/src/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,14 @@
<?php
namespace Psr\Log;
/**
* Describes a logger-aware instance.
*/
interface LoggerAwareInterface
{
/**
* Sets a logger instance on the object.
*/
public function setLogger(LoggerInterface $logger): void;
}

22
vendor/psr/log/src/LoggerAwareTrait.php vendored Normal file
View file

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

98
vendor/psr/log/src/LoggerInterface.php vendored Normal file
View file

@ -0,0 +1,98 @@
<?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 mixed[] $context
*/
public function emergency(string|\Stringable $message, array $context = []): void;
/**
* Action must be taken immediately.
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*
* @param mixed[] $context
*/
public function alert(string|\Stringable $message, array $context = []): void;
/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*
* @param mixed[] $context
*/
public function critical(string|\Stringable $message, array $context = []): void;
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*
* @param mixed[] $context
*/
public function error(string|\Stringable $message, array $context = []): void;
/**
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*
* @param mixed[] $context
*/
public function warning(string|\Stringable $message, array $context = []): void;
/**
* Normal but significant events.
*
* @param mixed[] $context
*/
public function notice(string|\Stringable $message, array $context = []): void;
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*
* @param mixed[] $context
*/
public function info(string|\Stringable $message, array $context = []): void;
/**
* Detailed debug information.
*
* @param mixed[] $context
*/
public function debug(string|\Stringable $message, array $context = []): void;
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param mixed[] $context
*
* @throws \Psr\Log\InvalidArgumentException
*/
public function log($level, string|\Stringable $message, array $context = []): void;
}

98
vendor/psr/log/src/LoggerTrait.php vendored Normal file
View file

@ -0,0 +1,98 @@
<?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.
*/
public function emergency(string|\Stringable $message, array $context = []): void
{
$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.
*/
public function alert(string|\Stringable $message, array $context = []): void
{
$this->log(LogLevel::ALERT, $message, $context);
}
/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*/
public function critical(string|\Stringable $message, array $context = []): void
{
$this->log(LogLevel::CRITICAL, $message, $context);
}
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*/
public function error(string|\Stringable $message, array $context = []): void
{
$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.
*/
public function warning(string|\Stringable $message, array $context = []): void
{
$this->log(LogLevel::WARNING, $message, $context);
}
/**
* Normal but significant events.
*/
public function notice(string|\Stringable $message, array $context = []): void
{
$this->log(LogLevel::NOTICE, $message, $context);
}
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*/
public function info(string|\Stringable $message, array $context = []): void
{
$this->log(LogLevel::INFO, $message, $context);
}
/**
* Detailed debug information.
*/
public function debug(string|\Stringable $message, array $context = []): void
{
$this->log(LogLevel::DEBUG, $message, $context);
}
/**
* Logs with an arbitrary level.
*
* @param mixed $level
*
* @throws \Psr\Log\InvalidArgumentException
*/
abstract public function log($level, string|\Stringable $message, array $context = []): void;
}

26
vendor/psr/log/src/NullLogger.php vendored Normal file
View file

@ -0,0 +1,26 @@
<?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[] $context
*
* @throws \Psr\Log\InvalidArgumentException
*/
public function log($level, string|\Stringable $message, array $context = []): void
{
// noop
}
}

27
vendor/sabre/dav/LICENSE vendored Normal file
View file

@ -0,0 +1,27 @@
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.

39
vendor/sabre/dav/README.md vendored Normal file
View file

@ -0,0 +1,39 @@
![sabre's logo](http://sabre.io/img/logo.png) sabre/dav
=======================================================
Introduction
------------
sabre/dav is the most popular WebDAV framework for PHP. Use it to create WebDAV, CalDAV and CardDAV servers.
Full documentation can be found on the website:
http://sabre.io/
Build status
------------
| branch | status | PHP version |
|------------|---------------------------------------------------------------------------|--------------------|
| master 4.* | ![CI](https://github.com/sabre-io/dav/actions/workflows/ci.yml/badge.svg) | PHP 7.1 up, 8.0 up |
| 3.2 | unmaintained | PHP 5.5 to 7.1 |
| 3.1 | unmaintained | PHP 5.5 |
| 3.0 | unmaintained | PHP 5.4 |
| 2.1 | unmaintained | PHP 5.4 |
| 2.0 | unmaintained | PHP 5.4 |
| 1.8 | unmaintained | PHP 5.3 |
| 1.7 | unmaintained | PHP 5.3 |
| 1.6 | unmaintained | PHP 5.3 |
Documentation
-------------
* [Introduction](http://sabre.io/dav/).
* [Installation](http://sabre.io/dav/install/).
Made at fruux
-------------
SabreDAV is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support.

169
vendor/sabre/dav/bin/build.php vendored Executable file
View file

@ -0,0 +1,169 @@
#!/usr/bin/env php
<?php
$tasks = [
'buildzip' => [
'init', 'test', 'clean',
],
'markrelease' => [
'init', 'test', 'clean',
],
'clean' => [],
'test' => [
'composerupdate',
],
'init' => [],
'composerupdate' => [],
];
$default = 'buildzip';
$baseDir = __DIR__.'/../';
chdir($baseDir);
$currentTask = $default;
if ($argc > 1) {
$currentTask = $argv[1];
}
$version = null;
if ($argc > 2) {
$version = $argv[2];
}
if (!isset($tasks[$currentTask])) {
echo 'Task not found: ', $currentTask, "\n";
exit(1);
}
// Creating the dependency graph
$newTaskList = [];
$oldTaskList = [$currentTask => true];
while (count($oldTaskList) > 0) {
foreach ($oldTaskList as $task => $foo) {
if (!isset($tasks[$task])) {
echo 'Dependency not found: '.$task, "\n";
exit(1);
}
$dependencies = $tasks[$task];
$fullFilled = true;
foreach ($dependencies as $dependency) {
if (isset($newTaskList[$dependency])) {
// Already in the fulfilled task list.
continue;
} else {
$oldTaskList[$dependency] = true;
$fullFilled = false;
}
}
if ($fullFilled) {
unset($oldTaskList[$task]);
$newTaskList[$task] = 1;
}
}
}
foreach (array_keys($newTaskList) as $task) {
echo 'task: '.$task, "\n";
call_user_func($task);
echo "\n";
}
function init()
{
global $version;
if (!$version) {
include __DIR__.'/../vendor/autoload.php';
$version = Sabre\DAV\Version::VERSION;
}
echo ' Building sabre/dav '.$version, "\n";
}
function clean()
{
global $baseDir;
echo " Removing build files\n";
$outputDir = $baseDir.'/build/SabreDAV';
if (is_dir($outputDir)) {
system('rm -r '.$baseDir.'/build/SabreDAV');
}
}
function composerupdate()
{
global $baseDir;
echo " Updating composer packages to latest version\n\n";
system('cd '.$baseDir.'; composer update');
}
function test()
{
global $baseDir;
echo " Running all unittests.\n";
echo " This may take a while.\n\n";
system(__DIR__.'/phpunit --configuration '.$baseDir.'/tests/phpunit.xml.dist --stop-on-failure', $code);
if (0 != $code) {
echo "PHPUnit reported error code $code\n";
exit(1);
}
}
function buildzip()
{
global $baseDir, $version;
echo " Generating composer.json\n";
$input = json_decode(file_get_contents(__DIR__.'/../composer.json'), true);
$newComposer = [
'require' => $input['require'],
'config' => [
'bin-dir' => './bin',
],
'prefer-stable' => true,
'minimum-stability' => 'alpha',
];
unset(
$newComposer['require']['sabre/vobject'],
$newComposer['require']['sabre/http'],
$newComposer['require']['sabre/uri'],
$newComposer['require']['sabre/event']
);
$newComposer['require']['sabre/dav'] = $version;
mkdir('build/SabreDAV');
file_put_contents('build/SabreDAV/composer.json', json_encode($newComposer, JSON_PRETTY_PRINT));
echo " Downloading dependencies\n";
system('cd build/SabreDAV; composer install -n', $code);
if (0 !== $code) {
echo "Composer reported error code $code\n";
exit(1);
}
echo " Removing pointless files\n";
unlink('build/SabreDAV/composer.json');
unlink('build/SabreDAV/composer.lock');
echo " Moving important files to the root of the project\n";
$fileNames = [
'CHANGELOG.md',
'LICENSE',
'README.md',
'examples',
];
foreach ($fileNames as $fileName) {
echo " $fileName\n";
rename('build/SabreDAV/vendor/sabre/dav/'.$fileName, 'build/SabreDAV/'.$fileName);
}
// <zip destfile="build/SabreDAV-${sabredav.version}.zip" basedir="build/SabreDAV" prefix="SabreDAV/" />
echo "\n";
echo "Zipping the sabredav distribution\n\n";
system('cd build; zip -qr sabredav-'.$version.'.zip SabreDAV');
echo 'Done.';
}

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

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

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

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

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

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

258
vendor/sabre/dav/bin/migrateto32.php vendored Executable file
View file

@ -0,0 +1,258 @@
#!/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";
exit(-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 (3 === $columnCount) {
echo "The calendars table has 3 columns already. Assuming this part of the migration was already done.\n";
$migrateCalendars = false;
} else {
echo 'The calendars table has '.$columnCount." columns.\n";
$migrateCalendars = true;
}
} catch (Exception $e) {
echo "calendars table does not exist. This is a major problem. Exiting.\n";
exit(-1);
}
if ($migrateCalendars) {
$calendarBackup = 'calendars_3_1_'.$backupPostfix;
echo "Backing up 'calendars' to '", $calendarBackup, "'\n";
switch ($driver) {
case 'mysql':
$pdo->exec('RENAME TABLE calendars TO '.$calendarBackup);
break;
case 'sqlite':
$pdo->exec('ALTER TABLE calendars RENAME TO '.$calendarBackup);
break;
}
echo "Creating new calendars table.\n";
switch ($driver) {
case 'mysql':
$pdo->exec(<<<SQL
CREATE TABLE calendars (
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
synctoken INTEGER UNSIGNED NOT NULL DEFAULT '1',
components VARBINARY(21)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
SQL
);
break;
case 'sqlite':
$pdo->exec(<<<SQL
CREATE TABLE calendars (
id integer primary key asc NOT NULL,
synctoken integer DEFAULT 1 NOT NULL,
components text NOT NULL
);
SQL
);
break;
}
echo "Migrating data from old to new table\n";
$pdo->exec(<<<SQL
INSERT INTO calendars (id, synctoken, components) SELECT id, synctoken, COALESCE(components,"VEVENT,VTODO,VJOURNAL") as components FROM $calendarBackup
SQL
);
}
echo "Upgrade to 3.2 schema completed.\n";

140
vendor/sabre/dav/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()

2
vendor/sabre/dav/bin/sabredav vendored Executable file
View file

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

51
vendor/sabre/dav/bin/sabredav.php vendored Executable file
View file

@ -0,0 +1,51 @@
<?php
// SabreDAV test server.
class CliLog
{
protected $stream;
public function __construct()
{
$this->stream = fopen('php://stdout', 'w');
}
public function log($msg)
{
fwrite($this->stream, $msg."\n");
}
}
$log = new CliLog();
if ('cli-server' !== php_sapi_name()) {
exit('This script is intended to run on the built-in php webserver');
}
// Finding composer
$paths = [
__DIR__.'/../vendor/autoload.php',
__DIR__.'/../../../autoload.php',
];
foreach ($paths as $path) {
if (file_exists($path)) {
include $path;
break;
}
}
use Sabre\DAV;
// Root
$root = new DAV\FS\Directory(getcwd());
// Setting up server.
$server = new DAV\Server($root);
// Browser plugin
$server->addPlugin(new DAV\Browser\Plugin());
$server->exec();

81
vendor/sabre/dav/composer.json vendored Normal file
View file

@ -0,0 +1,81 @@
{
"name": "sabre/dav",
"type": "library",
"description": "WebDAV Framework for PHP",
"keywords": ["Framework", "WebDAV", "CalDAV", "CardDAV", "iCalendar"],
"homepage": "http://sabre.io/",
"license" : "BSD-3-Clause",
"authors": [
{
"name": "Evert Pot",
"email": "me@evertpot.com",
"homepage" : "http://evertpot.com/",
"role" : "Developer"
}
],
"require": {
"php": "^7.1.0 || ^8.0",
"sabre/vobject": "^4.2.1",
"sabre/event" : "^5.0",
"sabre/xml" : "^2.0.1",
"sabre/http" : "^5.0.5",
"sabre/uri" : "^2.0",
"ext-dom": "*",
"ext-pcre": "*",
"ext-spl": "*",
"ext-simplexml": "*",
"ext-mbstring" : "*",
"ext-ctype" : "*",
"ext-date" : "*",
"ext-iconv" : "*",
"lib-libxml" : ">=2.7.0",
"psr/log": "^1.0 || ^2.0 || ^3.0",
"ext-json": "*"
},
"require-dev" : {
"friendsofphp/php-cs-fixer": "^2.19",
"monolog/monolog": "^1.27 || ^2.0",
"phpstan/phpstan": "^0.12 || ^1.0",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
},
"suggest" : {
"ext-curl" : "*",
"ext-pdo" : "*",
"ext-imap": "*"
},
"autoload": {
"psr-4" : {
"Sabre\\" : "lib/"
}
},
"autoload-dev" : {
"psr-4" : {
"Sabre\\" : "tests/Sabre/"
}
},
"support" : {
"forum" : "https://groups.google.com/group/sabredav-discuss",
"source" : "https://github.com/fruux/sabre-dav"
},
"bin" : [
"bin/sabredav",
"bin/naturalselection"
],
"scripts": {
"phpstan": [
"phpstan analyse lib tests"
],
"cs-fixer": [
"php-cs-fixer fix"
],
"phpunit": [
"phpunit --configuration tests/phpunit.xml"
],
"test": [
"composer phpstan",
"composer cs-fixer",
"composer phpunit"
]
}
}

View file

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

View file

@ -0,0 +1,273 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Backend;
/**
* Every CalDAV backend must at least implement this interface.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface BackendInterface
{
/**
* Returns a list of calendars for a principal.
*
* Every project is an array with the following keys:
* * id, a unique id that will be used by other functions to modify the
* calendar. This can be the same as the uri or a database key.
* * uri, which is the basename of the uri with which the calendar is
* accessed.
* * principaluri. The owner of the calendar. Almost always the same as
* principalUri passed to this method.
*
* Furthermore it can contain webdav properties in clark notation. A very
* common one is '{DAV:}displayname'.
*
* Many clients also require:
* {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
* For this property, you can just return an instance of
* Sabre\CalDAV\Property\SupportedCalendarComponentSet.
*
* If you return {http://sabredav.org/ns}read-only and set the value to 1,
* ACL will automatically be put in read-only mode.
*
* @param string $principalUri
*
* @return array
*/
public function getCalendarsForUser($principalUri);
/**
* 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.
*
* The id can be any type, including ints, strings, objects or array.
*
* @param string $principalUri
* @param string $calendarUri
*
* @return mixed
*/
public function createCalendar($principalUri, $calendarUri, array $properties);
/**
* Updates properties for a calendar.
*
* The list of mutations is stored in a Sabre\DAV\PropPatch object.
* To do the actual updates, you must tell this object which properties
* you're going to process with the handle() method.
*
* Calling the handle method is like telling the PropPatch object "I
* promise I can handle updating this property".
*
* Read the PropPatch documentation for more info and examples.
*
* @param mixed $calendarId
*/
public function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch);
/**
* Delete a calendar and all its objects.
*
* @param mixed $calendarId
*/
public function deleteCalendar($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 mixed $calendarId
*
* @return array
*/
public function getCalendarObjects($calendarId);
/**
* Returns information from a single calendar object, based on it's object
* uri.
*
* The object uri is only the basename, or filename and not a full path.
*
* The returned array must have the same keys as getCalendarObjects. The
* 'calendardata' object is required here though, while it's not required
* for getCalendarObjects.
*
* This method must return null if the object did not exist.
*
* @param mixed $calendarId
* @param string $objectUri
*
* @return array|null
*/
public function getCalendarObject($calendarId, $objectUri);
/**
* Returns a list of calendar objects.
*
* This method should work identical to getCalendarObject, but instead
* return all the calendar objects in the list as an array.
*
* If the backend supports this, it may allow for some speed-ups.
*
* @param mixed $calendarId
*
* @return array
*/
public function getMultipleCalendarObjects($calendarId, array $uris);
/**
* Creates a new calendar object.
*
* The object uri is only the basename, or filename and not a full path.
*
* It is possible to return an etag from this function, which will be used
* in the response to this PUT request. Note that the ETag must be
* surrounded by double-quotes.
*
* However, you should only really return this ETag if you don't mangle the
* calendar-data. If the result of a subsequent GET to this object is not
* the exact same as this request body, you should omit the ETag.
*
* @param mixed $calendarId
* @param string $objectUri
* @param string $calendarData
*
* @return string|null
*/
public function createCalendarObject($calendarId, $objectUri, $calendarData);
/**
* Updates an existing calendarobject, based on it's uri.
*
* The object uri is only the basename, or filename and not a full path.
*
* It is possible return an etag from this function, which will be used in
* the response to this PUT request. Note that the ETag must be surrounded
* by double-quotes.
*
* However, you should only really return this ETag if you don't mangle the
* calendar-data. If the result of a subsequent GET to this object is not
* the exact same as this request body, you should omit the ETag.
*
* @param mixed $calendarId
* @param string $objectUri
* @param string $calendarData
*
* @return string|null
*/
public function updateCalendarObject($calendarId, $objectUri, $calendarData);
/**
* Deletes an existing calendar object.
*
* The object uri is only the basename, or filename and not a full path.
*
* @param mixed $calendarId
* @param string $objectUri
*/
public function deleteCalendarObject($calendarId, $objectUri);
/**
* Performs a calendar-query on the contents of this calendar.
*
* The calendar-query is defined in RFC4791 : CalDAV. Using the
* calendar-query it is possible for a client to request a specific set of
* object, based on contents of iCalendar properties, date-ranges and
* iCalendar component types (VTODO, VEVENT).
*
* This method should just return a list of (relative) urls that match this
* query.
*
* The list of filters are specified as an array. The exact array is
* documented by Sabre\CalDAV\CalendarQueryParser.
*
* Note that it is extremely likely that getCalendarObject for every path
* returned from this method will be called almost immediately after. You
* may want to anticipate this to speed up these requests.
*
* This method provides a default implementation, which parses *all* the
* iCalendar objects in the specified calendar.
*
* This default may well be good enough for personal use, and calendars
* that aren't very large. But if you anticipate high usage, big calendars
* or high loads, you are strongly advised to optimize certain paths.
*
* The best way to do so is override this method and to optimize
* specifically for 'common filters'.
*
* Requests that are extremely common are:
* * requests for just VEVENTS
* * requests for just VTODO
* * requests with a time-range-filter on either VEVENT or VTODO.
*
* ..and combinations of these requests. It may not be worth it to try to
* handle every possible situation and just rely on the (relatively
* easy to use) CalendarQueryValidator to handle the rest.
*
* Note that especially time-range-filters may be difficult to parse. A
* time-range filter specified on a VEVENT must for instance also handle
* recurrence rules correctly.
* A good example of how to interprete all these filters can also simply
* be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
* as possible, so it gives you a good idea on what type of stuff you need
* to think of.
*
* @param mixed $calendarId
*
* @return array
*/
public function calendarQuery($calendarId, array $filters);
/**
* Searches through all of a users calendars and calendar objects to find
* an object with a specific UID.
*
* This method should return the path to this object, relative to the
* calendar home, so this path usually only contains two parts:
*
* calendarpath/objectpath.ics
*
* If the uid is not found, return null.
*
* This method should only consider * objects that the principal owns, so
* any calendars owned by other principals that also appear in this
* collection should be ignored.
*
* @param string $principalUri
* @param string $uid
*
* @return string|null
*/
public function getCalendarObjectByUID($principalUri, $uid);
}

View file

@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Backend;
use Sabre\CalDAV\Xml\Notification\NotificationInterface;
/**
* Adds caldav notification support to a backend.
*
* Note: This feature is experimental, and may change in between different
* SabreDAV versions.
*
* Notifications are defined at:
* http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-notifications.txt
*
* These notifications are basically a list of server-generated notifications
* displayed to the user. Users can dismiss notifications by deleting them.
*
* The primary usecase is to allow for calendar-sharing.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface NotificationSupport extends BackendInterface
{
/**
* Returns a list of notifications for a given principal url.
*
* @param string $principalUri
*
* @return NotificationInterface[]
*/
public function getNotificationsForPrincipal($principalUri);
/**
* This deletes a specific notification.
*
* This may be called by a client once it deems a notification handled.
*
* @param string $principalUri
*/
public 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 string|null
*/
public function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null);
}

File diff suppressed because it is too large Load diff

View file

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

View file

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Backend;
/**
* Adds support for sharing features to a CalDAV server.
*
* CalDAV backends that implement this interface, must make the following
* modifications to getCalendarsForUser:
*
* 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 calendar.
* 3. For every calendar, you must return a share-access element. This element
* should contain one of the Sabre\DAV\Sharing\Plugin:ACCESS_* constants 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 BackendInterface
{
/**
* Updates the list of shares.
*
* @param mixed $calendarId
* @param \Sabre\DAV\Xml\Element\Sharee[] $sharees
*/
public function updateInvites($calendarId, array $sharees);
/**
* Returns the list of people whom this 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[]
*/
public function getInvites($calendarId);
/**
* Publishes a calendar.
*
* @param mixed $calendarId
* @param bool $value
*/
public function setPublishStatus($calendarId, $value);
}

View file

@ -0,0 +1,289 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Backend;
use Sabre\CalDAV;
use Sabre\DAV;
/**
* Simple PDO CalDAV backend.
*
* This class is basically the most minimum 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.
*/
public 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
*/
public 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
*
* @return string
*/
public 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
*/
public 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
*/
public 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
*/
public 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
*/
public 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
*/
public 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
*/
public function deleteCalendarObject($calendarId, $objectUri)
{
$stmt = $this->pdo->prepare('DELETE FROM simple_calendarobjects WHERE calendarid = ? AND uri = ?');
$stmt->execute([$calendarId, $objectUri]);
}
}

View file

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

View file

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

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

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

View file

@ -0,0 +1,356 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV;
use Sabre\DAV;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\MkCol;
use Sabre\DAVACL;
use Sabre\Uri;
/**
* The CalendarHome represents a node that is usually in a users'
* calendar-homeset.
*
* It contains all the users' calendars, and can optionally contain a
* notifications collection, calendar subscriptions, a users' inbox, and a
* users' outbox.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class CalendarHome implements DAV\IExtendedCollection, DAVACL\IACL
{
use DAVACL\ACLTrait;
/**
* CalDAV backend.
*
* @var Backend\BackendInterface
*/
protected $caldavBackend;
/**
* Principal information.
*
* @var array
*/
protected $principalInfo;
/**
* Constructor.
*
* @param array $principalInfo
*/
public function __construct(Backend\BackendInterface $caldavBackend, $principalInfo)
{
$this->caldavBackend = $caldavBackend;
$this->principalInfo = $principalInfo;
}
/**
* Returns the name of this object.
*
* @return string
*/
public function getName()
{
list(, $name) = Uri\split($this->principalInfo['uri']);
return $name;
}
/**
* Updates the name of this object.
*
* @param string $name
*/
public function setName($name)
{
throw new DAV\Exception\Forbidden();
}
/**
* Deletes this object.
*/
public function delete()
{
throw new DAV\Exception\Forbidden();
}
/**
* Returns the last modification date.
*
* @return int
*/
public function getLastModified()
{
return null;
}
/**
* Creates a new file under this object.
*
* This is currently not allowed
*
* @param string $name
* @param resource $data
*/
public function createFile($name, $data = null)
{
throw new DAV\Exception\MethodNotAllowed('Creating new files in this collection is not supported');
}
/**
* Creates a new directory under this object.
*
* This is currently not allowed.
*
* @param string $filename
*/
public function createDirectory($filename)
{
throw new DAV\Exception\MethodNotAllowed('Creating new collections in this collection is not supported');
}
/**
* Returns a single calendar, by name.
*
* @param string $name
*
* @return Calendar
*/
public function getChild($name)
{
// Special nodes
if ('inbox' === $name && $this->caldavBackend instanceof Backend\SchedulingSupport) {
return new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']);
}
if ('outbox' === $name && $this->caldavBackend instanceof Backend\SchedulingSupport) {
return new Schedule\Outbox($this->principalInfo['uri']);
}
if ('notifications' === $name && $this->caldavBackend instanceof Backend\NotificationSupport) {
return new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
}
// Calendars
foreach ($this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']) as $calendar) {
if ($calendar['uri'] === $name) {
if ($this->caldavBackend instanceof Backend\SharingSupport) {
return new SharedCalendar($this->caldavBackend, $calendar);
} else {
return new Calendar($this->caldavBackend, $calendar);
}
}
}
if ($this->caldavBackend instanceof Backend\SubscriptionSupport) {
foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) {
if ($subscription['uri'] === $name) {
return new Subscriptions\Subscription($this->caldavBackend, $subscription);
}
}
}
throw new NotFound('Node with name \''.$name.'\' could not be found');
}
/**
* Checks if a calendar exists.
*
* @param string $name
*
* @return bool
*/
public function childExists($name)
{
try {
return (bool) $this->getChild($name);
} catch (NotFound $e) {
return false;
}
}
/**
* Returns a list of calendars.
*
* @return array
*/
public function getChildren()
{
$calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']);
$objs = [];
foreach ($calendars as $calendar) {
if ($this->caldavBackend instanceof Backend\SharingSupport) {
$objs[] = new SharedCalendar($this->caldavBackend, $calendar);
} else {
$objs[] = new Calendar($this->caldavBackend, $calendar);
}
}
if ($this->caldavBackend instanceof Backend\SchedulingSupport) {
$objs[] = new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']);
$objs[] = new Schedule\Outbox($this->principalInfo['uri']);
}
// We're adding a notifications node, if it's supported by the backend.
if ($this->caldavBackend instanceof Backend\NotificationSupport) {
$objs[] = new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
}
// If the backend supports subscriptions, we'll add those as well,
if ($this->caldavBackend instanceof Backend\SubscriptionSupport) {
foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) {
$objs[] = new Subscriptions\Subscription($this->caldavBackend, $subscription);
}
}
return $objs;
}
/**
* Creates a new calendar or subscription.
*
* @param string $name
*
* @throws DAV\Exception\InvalidResourceType
*/
public function createExtendedCollection($name, MkCol $mkCol)
{
$isCalendar = false;
$isSubscription = false;
foreach ($mkCol->getResourceType() as $rt) {
switch ($rt) {
case '{DAV:}collection':
case '{http://calendarserver.org/ns/}shared-owner':
// ignore
break;
case '{urn:ietf:params:xml:ns:caldav}calendar':
$isCalendar = true;
break;
case '{http://calendarserver.org/ns/}subscribed':
$isSubscription = true;
break;
default:
throw new DAV\Exception\InvalidResourceType('Unknown resourceType: '.$rt);
}
}
$properties = $mkCol->getRemainingValues();
$mkCol->setRemainingResultCode(201);
if ($isSubscription) {
if (!$this->caldavBackend instanceof Backend\SubscriptionSupport) {
throw new DAV\Exception\InvalidResourceType('This backend does not support subscriptions');
}
$this->caldavBackend->createSubscription($this->principalInfo['uri'], $name, $properties);
} elseif ($isCalendar) {
$this->caldavBackend->createCalendar($this->principalInfo['uri'], $name, $properties);
} else {
throw new DAV\Exception\InvalidResourceType('You can only create calendars and subscriptions in this collection');
}
}
/**
* Returns the owner of the calendar home.
*
* @return string
*/
public function getOwner()
{
return $this->principalInfo['uri'];
}
/**
* 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
*/
public function getACL()
{
return [
[
'privilege' => '{DAV:}read',
'principal' => $this->principalInfo['uri'],
'protected' => true,
],
[
'privilege' => '{DAV:}write',
'principal' => $this->principalInfo['uri'],
'protected' => true,
],
[
'privilege' => '{DAV:}read',
'principal' => $this->principalInfo['uri'].'/calendar-proxy-write',
'protected' => true,
],
[
'privilege' => '{DAV:}write',
'principal' => $this->principalInfo['uri'].'/calendar-proxy-write',
'protected' => true,
],
[
'privilege' => '{DAV:}read',
'principal' => $this->principalInfo['uri'].'/calendar-proxy-read',
'protected' => true,
],
];
}
/**
* This method is called when a user replied to a request to share.
*
* This method should return the url of the newly created calendar if the
* share was accepted.
*
* @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 string|null
*/
public function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null)
{
if (!$this->caldavBackend instanceof Backend\SharingSupport) {
throw new DAV\Exception\NotImplemented('Sharing support is not implemented by this backend.');
}
return $this->caldavBackend->shareReply($href, $status, $calendarUri, $inReplyTo, $summary);
}
/**
* Searches through all of a users calendars and calendar objects to find
* an object with a specific UID.
*
* This method should return the path to this object, relative to the
* calendar home, so this path usually only contains two parts:
*
* calendarpath/objectpath.ics
*
* If the uid is not found, return null.
*
* This method should only consider * objects that the principal owns, so
* any calendars owned by other principals that also appear in this
* collection should be ignored.
*
* @param string $uid
*
* @return string|null
*/
public function getCalendarObjectByUID($uid)
{
return $this->caldavBackend->getCalendarObjectByUID($this->principalInfo['uri'], $uid);
}
}

View file

@ -0,0 +1,223 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV;
/**
* The CalendarObject represents a single VEVENT or VTODO within a Calendar.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\DAVACL\IACL
{
use \Sabre\DAVACL\ACLTrait;
/**
* Sabre\CalDAV\Backend\BackendInterface.
*
* @var Backend\AbstractBackend
*/
protected $caldavBackend;
/**
* Array with information about this CalendarObject.
*
* @var array
*/
protected $objectData;
/**
* Array with information about the containing calendar.
*
* @var array
*/
protected $calendarInfo;
/**
* Constructor.
*
* The following properties may be passed within $objectData:
*
* * calendarid - This must refer to a calendarid from a caldavBackend
* * uri - A unique uri. Only the 'basename' must be passed.
* * calendardata (optional) - The iCalendar data
* * etag - (optional) The etag for this object, MUST be encloded with
* double-quotes.
* * size - (optional) The size of the data in bytes.
* * lastmodified - (optional) format as a unix timestamp.
* * acl - (optional) Use this to override the default ACL for the node.
*/
public function __construct(Backend\BackendInterface $caldavBackend, array $calendarInfo, array $objectData)
{
$this->caldavBackend = $caldavBackend;
if (!isset($objectData['uri'])) {
throw new \InvalidArgumentException('The objectData argument must contain an \'uri\' property');
}
$this->calendarInfo = $calendarInfo;
$this->objectData = $objectData;
}
/**
* Returns the uri for this object.
*
* @return string
*/
public function getName()
{
return $this->objectData['uri'];
}
/**
* Returns the ICalendar-formatted object.
*
* @return string
*/
public function get()
{
// Pre-populating the 'calendardata' is optional, if we don't have it
// already we fetch it from the backend.
if (!isset($this->objectData['calendardata'])) {
$this->objectData = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $this->objectData['uri']);
}
return $this->objectData['calendardata'];
}
/**
* Updates the ICalendar-formatted object.
*
* @param string|resource $calendarData
*
* @return string
*/
public function put($calendarData)
{
if (is_resource($calendarData)) {
$calendarData = stream_get_contents($calendarData);
}
$etag = $this->caldavBackend->updateCalendarObject($this->calendarInfo['id'], $this->objectData['uri'], $calendarData);
$this->objectData['calendardata'] = $calendarData;
$this->objectData['etag'] = $etag;
return $etag;
}
/**
* Deletes the calendar object.
*/
public function delete()
{
$this->caldavBackend->deleteCalendarObject($this->calendarInfo['id'], $this->objectData['uri']);
}
/**
* Returns the mime content-type.
*
* @return string
*/
public function getContentType()
{
$mime = 'text/calendar; charset=utf-8';
if (isset($this->objectData['component']) && $this->objectData['component']) {
$mime .= '; component='.$this->objectData['component'];
}
return $mime;
}
/**
* Returns an ETag for this object.
*
* The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
*
* @return string
*/
public function getETag()
{
if (isset($this->objectData['etag'])) {
return $this->objectData['etag'];
} else {
return '"'.md5($this->get()).'"';
}
}
/**
* Returns the last modification date as a unix timestamp.
*
* @return int
*/
public function getLastModified()
{
return $this->objectData['lastmodified'];
}
/**
* Returns the size of this object in bytes.
*
* @return int
*/
public function getSize()
{
if (array_key_exists('size', $this->objectData)) {
return $this->objectData['size'];
} else {
return strlen($this->get());
}
}
/**
* Returns the owner principal.
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getOwner()
{
return $this->calendarInfo['principaluri'];
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL()
{
// An alternative acl may be specified in the object data.
if (isset($this->objectData['acl'])) {
return $this->objectData['acl'];
}
// The default ACL
return [
[
'privilege' => '{DAV:}all',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
],
[
'privilege' => '{DAV:}all',
'principal' => $this->calendarInfo['principaluri'].'/calendar-proxy-write',
'protected' => true,
],
[
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'].'/calendar-proxy-read',
'protected' => true,
],
];
}
}

View file

@ -0,0 +1,354 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV;
use DateTime;
use Sabre\VObject;
/**
* CalendarQuery Validator.
*
* This class is responsible for checking if an iCalendar object matches a set
* of filters. The main function to do this is 'validate'.
*
* This is used to determine which icalendar objects should be returned for a
* calendar-query REPORT request.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class CalendarQueryValidator
{
/**
* Verify if a list of filters applies to the calendar data object.
*
* The list of filters must be formatted as parsed by \Sabre\CalDAV\CalendarQueryParser
*
* @return bool
*/
public function validate(VObject\Component\VCalendar $vObject, array $filters)
{
// The top level object is always a component filter.
// We'll parse it manually, as it's pretty simple.
if ($vObject->name !== $filters['name']) {
return false;
}
return
$this->validateCompFilters($vObject, $filters['comp-filters']) &&
$this->validatePropFilters($vObject, $filters['prop-filters']);
}
/**
* This method checks the validity of comp-filters.
*
* A list of comp-filters needs to be specified. Also the parent of the
* component we're checking should be specified, not the component to check
* itself.
*
* @return bool
*/
protected function validateCompFilters(VObject\Component $parent, array $filters)
{
foreach ($filters as $filter) {
$isDefined = isset($parent->{$filter['name']});
if ($filter['is-not-defined']) {
if ($isDefined) {
return false;
} else {
continue;
}
}
if (!$isDefined) {
return false;
}
if (array_key_exists('time-range', $filter) && $filter['time-range']) {
foreach ($parent->{$filter['name']} as $subComponent) {
$start = null;
$end = null;
if (array_key_exists('start', $filter['time-range'])) {
$start = $filter['time-range']['start'];
}
if (array_key_exists('end', $filter['time-range'])) {
$end = $filter['time-range']['end'];
}
if ($this->validateTimeRange($subComponent, $start, $end)) {
continue 2;
}
}
return false;
}
if (!$filter['comp-filters'] && !$filter['prop-filters']) {
continue;
}
// If there are sub-filters, we need to find at least one component
// for which the subfilters hold true.
foreach ($parent->{$filter['name']} as $subComponent) {
if (
$this->validateCompFilters($subComponent, $filter['comp-filters']) &&
$this->validatePropFilters($subComponent, $filter['prop-filters'])) {
// We had a match, so this comp-filter succeeds
continue 2;
}
}
// If we got here it means there were sub-comp-filters or
// sub-prop-filters and there was no match. This means this filter
// needs to return false.
return false;
}
// If we got here it means we got through all comp-filters alive so the
// filters were all true.
return true;
}
/**
* This method checks the validity of prop-filters.
*
* A list of prop-filters needs to be specified. Also the parent of the
* property we're checking should be specified, not the property to check
* itself.
*
* @return bool
*/
protected function validatePropFilters(VObject\Component $parent, array $filters)
{
foreach ($filters as $filter) {
$isDefined = isset($parent->{$filter['name']});
if ($filter['is-not-defined']) {
if ($isDefined) {
return false;
} else {
continue;
}
}
if (!$isDefined) {
return false;
}
if (array_key_exists('time-range', $filter) && $filter['time-range']) {
foreach ($parent->{$filter['name']} as $subComponent) {
$start = null;
$end = null;
if (array_key_exists('start', $filter['time-range'])) {
$start = $filter['time-range']['start'];
}
if (array_key_exists('end', $filter['time-range'])) {
$end = $filter['time-range']['end'];
}
if ($this->validateTimeRange($subComponent, $start, $end)) {
continue 2;
}
}
return false;
}
if (!$filter['param-filters'] && !$filter['text-match']) {
continue;
}
// If there are sub-filters, we need to find at least one property
// for which the subfilters hold true.
foreach ($parent->{$filter['name']} as $subComponent) {
if (
$this->validateParamFilters($subComponent, $filter['param-filters']) &&
(!$filter['text-match'] || $this->validateTextMatch($subComponent, $filter['text-match']))
) {
// We had a match, so this prop-filter succeeds
continue 2;
}
}
// If we got here it means there were sub-param-filters or
// text-match filters and there was no match. This means the
// filter needs to return false.
return false;
}
// If we got here it means we got through all prop-filters alive so the
// filters were all true.
return true;
}
/**
* This method checks the validity of param-filters.
*
* A list of param-filters needs to be specified. Also the parent of the
* parameter we're checking should be specified, not the parameter to check
* itself.
*
* @return bool
*/
protected function validateParamFilters(VObject\Property $parent, array $filters)
{
foreach ($filters as $filter) {
$isDefined = isset($parent[$filter['name']]);
if ($filter['is-not-defined']) {
if ($isDefined) {
return false;
} else {
continue;
}
}
if (!$isDefined) {
return false;
}
if (!$filter['text-match']) {
continue;
}
// If there are sub-filters, we need to find at least one parameter
// for which the subfilters hold true.
foreach ($parent[$filter['name']]->getParts() as $paramPart) {
if ($this->validateTextMatch($paramPart, $filter['text-match'])) {
// We had a match, so this param-filter succeeds
continue 2;
}
}
// If we got here it means there was a text-match filter and there
// were no matches. This means the filter needs to return false.
return false;
}
// If we got here it means we got through all param-filters alive so the
// filters were all true.
return true;
}
/**
* This method checks the validity of a text-match.
*
* A single text-match should be specified as well as the specific property
* or parameter we need to validate.
*
* @param VObject\Node|string $check value to check against
*
* @return bool
*/
protected function validateTextMatch($check, array $textMatch)
{
if ($check instanceof VObject\Node) {
$check = $check->getValue();
}
$isMatching = \Sabre\DAV\StringUtil::textMatch($check, $textMatch['value'], $textMatch['collation']);
return $textMatch['negate-condition'] xor $isMatching;
}
/**
* Validates if a component matches the given time range.
*
* This is all based on the rules specified in rfc4791, which are quite
* complex.
*
* @param DateTime $start
* @param DateTime $end
*
* @return bool
*/
protected function validateTimeRange(VObject\Node $component, $start, $end)
{
if (is_null($start)) {
$start = new DateTime('1900-01-01');
}
if (is_null($end)) {
$end = new DateTime('3000-01-01');
}
switch ($component->name) {
case 'VEVENT':
case 'VTODO':
case 'VJOURNAL':
return $component->isInTimeRange($start, $end);
case 'VALARM':
// If the valarm is wrapped in a recurring event, we need to
// expand the recursions, and validate each.
//
// Our datamodel doesn't easily allow us to do this straight
// in the VALARM component code, so this is a hack, and an
// expensive one too.
if ('VEVENT' === $component->parent->name && $component->parent->RRULE) {
// Fire up the iterator!
$it = new VObject\Recur\EventIterator($component->parent->parent, (string) $component->parent->UID);
while ($it->valid()) {
$expandedEvent = $it->getEventObject();
// We need to check from these expanded alarms, which
// one is the first to trigger. Based on this, we can
// determine if we can 'give up' expanding events.
$firstAlarm = null;
if (null !== $expandedEvent->VALARM) {
foreach ($expandedEvent->VALARM as $expandedAlarm) {
$effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime();
if ($expandedAlarm->isInTimeRange($start, $end)) {
return true;
}
if ('DATE-TIME' === (string) $expandedAlarm->TRIGGER['VALUE']) {
// This is an alarm with a non-relative trigger
// time, likely created by a buggy client. The
// implication is that every alarm in this
// recurring event trigger at the exact same
// time. It doesn't make sense to traverse
// further.
} else {
// We store the first alarm as a means to
// figure out when we can stop traversing.
if (!$firstAlarm || $effectiveTrigger < $firstAlarm) {
$firstAlarm = $effectiveTrigger;
}
}
}
}
if (is_null($firstAlarm)) {
// No alarm was found.
//
// Or technically: No alarm that will change for
// every instance of the recurrence was found,
// which means we can assume there was no match.
return false;
}
if ($firstAlarm > $end) {
return false;
}
$it->next();
}
return false;
} else {
return $component->isInTimeRange($start, $end);
}
// no break
case 'VFREEBUSY':
throw new \Sabre\DAV\Exception\NotImplemented('time-range filters are currently not supported on '.$component->name.' components');
case 'COMPLETED':
case 'CREATED':
case 'DTEND':
case 'DTSTAMP':
case 'DTSTART':
case 'DUE':
case 'LAST-MODIFIED':
return $start <= $component->getDateTime() && $end >= $component->getDateTime();
default:
throw new \Sabre\DAV\Exception\BadRequest('You cannot create a time-range filter on a '.$component->name.' component');
}
}
}

View file

@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV;
use Sabre\DAVACL\PrincipalBackend;
/**
* Calendars collection.
*
* This object is responsible for generating a list of calendar-homes for each
* user.
*
* This is the top-most node for the calendars tree. In most servers this class
* represents the "/calendars" path.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class CalendarRoot extends \Sabre\DAVACL\AbstractPrincipalCollection
{
/**
* CalDAV backend.
*
* @var Backend\BackendInterface
*/
protected $caldavBackend;
/**
* Constructor.
*
* This constructor needs both an authentication and a caldav backend.
*
* By default this class will show a list of calendar collections for
* principals in the 'principals' collection. If your main principals are
* actually located in a different path, use the $principalPrefix argument
* to override this.
*
* @param string $principalPrefix
*/
public function __construct(PrincipalBackend\BackendInterface $principalBackend, Backend\BackendInterface $caldavBackend, $principalPrefix = 'principals')
{
parent::__construct($principalBackend, $principalPrefix);
$this->caldavBackend = $caldavBackend;
}
/**
* Returns the nodename.
*
* We're overriding this, because the default will be the 'principalPrefix',
* and we want it to be Sabre\CalDAV\Plugin::CALENDAR_ROOT
*
* @return string
*/
public function getName()
{
return Plugin::CALENDAR_ROOT;
}
/**
* This method returns a node for a principal.
*
* The passed array contains principal information, and is guaranteed to
* at least contain a uri item. Other properties may or may not be
* supplied by the authentication backend.
*
* @return \Sabre\DAV\INode
*/
public function getChildForPrincipal(array $principal)
{
return new CalendarHome($this->caldavBackend, $principal);
}
}

View file

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Exception;
use Sabre\CalDAV;
use Sabre\DAV;
/**
* InvalidComponentType.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class InvalidComponentType extends DAV\Exception\Forbidden
{
/**
* Adds in extra information in the xml response.
*
* This method adds the {CALDAV:}supported-calendar-component as defined in rfc4791
*/
public function serialize(DAV\Server $server, \DOMElement $errorNode)
{
$doc = $errorNode->ownerDocument;
$np = $doc->createElementNS(CalDAV\Plugin::NS_CALDAV, 'cal:supported-calendar-component');
$errorNode->appendChild($np);
}
}

View file

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

View file

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

View file

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV;
use Sabre\DAV;
/**
* CalendarObject interface.
*
* Extend the ICalendarObject interface to allow your custom nodes to be picked up as
* CalendarObjects.
*
* Calendar objects are resources such as Events, Todo's or Journals.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface ICalendarObject extends DAV\IFile
{
}

View file

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV;
/**
* This interface represents a node that may contain calendar objects.
*
* This is the shared parent for both the Inbox collection and calendars
* resources.
*
* In most cases you will likely want to look at ICalendar instead of this
* interface.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface ICalendarObjectContainer extends \Sabre\DAV\ICollection
{
/**
* Performs a calendar-query on the contents of this calendar.
*
* The calendar-query is defined in RFC4791 : CalDAV. Using the
* calendar-query it is possible for a client to request a specific set of
* object, based on contents of iCalendar properties, date-ranges and
* iCalendar component types (VTODO, VEVENT).
*
* This method should just return a list of (relative) urls that match this
* query.
*
* The list of filters are specified as an array. The exact array is
* documented by \Sabre\CalDAV\CalendarQueryParser.
*
* @return array
*/
public function calendarQuery(array $filters);
}

View file

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV;
use Sabre\DAV\Sharing\ISharedNode;
/**
* This interface represents a Calendar that is shared by a different user.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface ISharedCalendar extends ISharedNode
{
/**
* Marks this calendar as published.
*
* Publishing a calendar should automatically create a read-only, public,
* subscribable calendar.
*
* @param bool $value
*/
public function setPublishStatus($value);
}

View file

@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Notifications;
use Sabre\CalDAV;
use Sabre\DAV;
use Sabre\DAVACL;
/**
* This node represents a list of notifications.
*
* It provides no additional functionality, but you must implement this
* interface to allow the Notifications plugin to mark the collection
* as a notifications collection.
*
* This collection should only return Sabre\CalDAV\Notifications\INode nodes as
* its children.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Collection extends DAV\Collection implements ICollection, DAVACL\IACL
{
use DAVACL\ACLTrait;
/**
* The notification backend.
*
* @var CalDAV\Backend\NotificationSupport
*/
protected $caldavBackend;
/**
* Principal uri.
*
* @var string
*/
protected $principalUri;
/**
* Constructor.
*
* @param string $principalUri
*/
public function __construct(CalDAV\Backend\NotificationSupport $caldavBackend, $principalUri)
{
$this->caldavBackend = $caldavBackend;
$this->principalUri = $principalUri;
}
/**
* Returns all notifications for a principal.
*
* @return array
*/
public function getChildren()
{
$children = [];
$notifications = $this->caldavBackend->getNotificationsForPrincipal($this->principalUri);
foreach ($notifications as $notification) {
$children[] = new Node(
$this->caldavBackend,
$this->principalUri,
$notification
);
}
return $children;
}
/**
* Returns the name of this object.
*
* @return string
*/
public function getName()
{
return 'notifications';
}
/**
* Returns the owner principal.
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getOwner()
{
return $this->principalUri;
}
}

View file

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Notifications;
use Sabre\DAV;
/**
* This node represents a list of notifications.
*
* It provides no additional functionality, but you must implement this
* interface to allow the Notifications plugin to mark the collection
* as a notifications collection.
*
* This collection should only return Sabre\CalDAV\Notifications\INode nodes as
* its children.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface ICollection extends DAV\ICollection
{
}

View file

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Notifications;
use Sabre\CalDAV\Xml\Notification\NotificationInterface;
/**
* This node represents a single notification.
*
* The signature is mostly identical to that of Sabre\DAV\IFile, but the get() method
* MUST return an xml document that matches the requirements of the
* 'caldav-notifications.txt' spec.
*
* For a complete example, check out the Notification class, which contains
* some helper functions.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface INode
{
/**
* This method must return an xml element, using the
* Sabre\CalDAV\Xml\Notification\NotificationInterface classes.
*
* @return NotificationInterface
*/
public function getNotificationType();
/**
* Returns the etag for the notification.
*
* The etag must be surrounded by literal double-quotes.
*
* @return string
*/
public function getETag();
}

View file

@ -0,0 +1,112 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Notifications;
use Sabre\CalDAV;
use Sabre\CalDAV\Xml\Notification\NotificationInterface;
use Sabre\DAV;
use Sabre\DAVACL;
/**
* This node represents a single notification.
*
* The signature is mostly identical to that of Sabre\DAV\IFile, but the get() method
* MUST return an xml document that matches the requirements of the
* 'caldav-notifications.txt' spec.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Node extends DAV\File implements INode, DAVACL\IACL
{
use DAVACL\ACLTrait;
/**
* The notification backend.
*
* @var CalDAV\Backend\NotificationSupport
*/
protected $caldavBackend;
/**
* The actual notification.
*
* @var NotificationInterface
*/
protected $notification;
/**
* Owner principal of the notification.
*
* @var string
*/
protected $principalUri;
/**
* Constructor.
*
* @param string $principalUri
*/
public function __construct(CalDAV\Backend\NotificationSupport $caldavBackend, $principalUri, NotificationInterface $notification)
{
$this->caldavBackend = $caldavBackend;
$this->principalUri = $principalUri;
$this->notification = $notification;
}
/**
* Returns the path name for this notification.
*
* @return string
*/
public function getName()
{
return $this->notification->getId().'.xml';
}
/**
* Returns the etag for the notification.
*
* The etag must be surrounded by literal double-quotes.
*
* @return string
*/
public function getETag()
{
return $this->notification->getETag();
}
/**
* This method must return an xml element, using the
* Sabre\CalDAV\Xml\Notification\NotificationInterface classes.
*
* @return NotificationInterface
*/
public function getNotificationType()
{
return $this->notification;
}
/**
* Deletes this notification.
*/
public function delete()
{
$this->caldavBackend->deleteNotification($this->getOwner(), $this->notification);
}
/**
* Returns the owner principal.
*
* This must be an url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getOwner()
{
return $this->principalUri;
}
}

View file

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

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

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Principal;
use Sabre\DAVACL;
/**
* Principal collection.
*
* This is an alternative collection to the standard ACL principal collection.
* This collection adds support for the calendar-proxy-read and
* calendar-proxy-write sub-principals, as defined by the caldav-proxy
* specification.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Collection extends DAVACL\PrincipalCollection
{
/**
* Returns a child object based on principal information.
*
* @return User
*/
public function getChildForPrincipal(array $principalInfo)
{
return new User($this->principalBackend, $principalInfo);
}
}

View file

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Principal;
use Sabre\DAVACL;
/**
* ProxyRead principal interface.
*
* Any principal node implementing this interface will be picked up as a 'proxy
* principal group'.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface IProxyRead extends DAVACL\IPrincipal
{
}

View file

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Principal;
use Sabre\DAVACL;
/**
* ProxyWrite principal interface.
*
* Any principal node implementing this interface will be picked up as a 'proxy
* principal group'.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface IProxyWrite extends DAVACL\IPrincipal
{
}

View file

@ -0,0 +1,161 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Principal;
use Sabre\DAV;
use Sabre\DAVACL;
/**
* ProxyRead principal.
*
* This class represents a principal group, hosted under the main principal.
* This is needed to implement 'Calendar delegation' support. This class is
* instantiated by User.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class ProxyRead implements IProxyRead
{
/**
* Principal information from the parent principal.
*
* @var array
*/
protected $principalInfo;
/**
* Principal backend.
*
* @var DAVACL\PrincipalBackend\BackendInterface
*/
protected $principalBackend;
/**
* Creates the object.
*
* Note that you MUST supply the parent principal information.
*/
public function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, array $principalInfo)
{
$this->principalInfo = $principalInfo;
$this->principalBackend = $principalBackend;
}
/**
* Returns this principals name.
*
* @return string
*/
public function getName()
{
return 'calendar-proxy-read';
}
/**
* Returns the last modification time.
*/
public function getLastModified()
{
return null;
}
/**
* Deletes the current node.
*
* @throws DAV\Exception\Forbidden
*/
public function delete()
{
throw new DAV\Exception\Forbidden('Permission denied to delete node');
}
/**
* Renames the node.
*
* @param string $name The new name
*
* @throws DAV\Exception\Forbidden
*/
public function setName($name)
{
throw new DAV\Exception\Forbidden('Permission denied to rename file');
}
/**
* Returns a list of alternative urls for a principal.
*
* This can for example be an email address, or ldap url.
*
* @return array
*/
public function getAlternateUriSet()
{
return [];
}
/**
* Returns the full principal url.
*
* @return string
*/
public function getPrincipalUrl()
{
return $this->principalInfo['uri'].'/'.$this->getName();
}
/**
* Returns the list of group members.
*
* If this principal is a group, this function should return
* all member principal uri's for the group.
*
* @return array
*/
public function getGroupMemberSet()
{
return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl());
}
/**
* Returns the list of groups this principal is member of.
*
* If this principal is a member of a (list of) groups, this function
* should return a list of principal uri's for it's members.
*
* @return array
*/
public function getGroupMembership()
{
return $this->principalBackend->getGroupMembership($this->getPrincipalUrl());
}
/**
* Sets a list of group members.
*
* If this principal is a group, this method sets all the group members.
* The list of members is always overwritten, never appended to.
*
* This method should throw an exception if the members could not be set.
*/
public function setGroupMemberSet(array $principals)
{
$this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals);
}
/**
* Returns the displayname.
*
* This should be a human readable name for the principal.
* If none is available, return the nodename.
*
* @return string
*/
public function getDisplayName()
{
return $this->getName();
}
}

View file

@ -0,0 +1,161 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Principal;
use Sabre\DAV;
use Sabre\DAVACL;
/**
* ProxyWrite principal.
*
* This class represents a principal group, hosted under the main principal.
* This is needed to implement 'Calendar delegation' support. This class is
* instantiated by User.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class ProxyWrite implements IProxyWrite
{
/**
* Parent principal information.
*
* @var array
*/
protected $principalInfo;
/**
* Principal Backend.
*
* @var DAVACL\PrincipalBackend\BackendInterface
*/
protected $principalBackend;
/**
* Creates the object.
*
* Note that you MUST supply the parent principal information.
*/
public function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, array $principalInfo)
{
$this->principalInfo = $principalInfo;
$this->principalBackend = $principalBackend;
}
/**
* Returns this principals name.
*
* @return string
*/
public function getName()
{
return 'calendar-proxy-write';
}
/**
* Returns the last modification time.
*/
public function getLastModified()
{
return null;
}
/**
* Deletes the current node.
*
* @throws DAV\Exception\Forbidden
*/
public function delete()
{
throw new DAV\Exception\Forbidden('Permission denied to delete node');
}
/**
* Renames the node.
*
* @param string $name The new name
*
* @throws DAV\Exception\Forbidden
*/
public function setName($name)
{
throw new DAV\Exception\Forbidden('Permission denied to rename file');
}
/**
* Returns a list of alternative urls for a principal.
*
* This can for example be an email address, or ldap url.
*
* @return array
*/
public function getAlternateUriSet()
{
return [];
}
/**
* Returns the full principal url.
*
* @return string
*/
public function getPrincipalUrl()
{
return $this->principalInfo['uri'].'/'.$this->getName();
}
/**
* Returns the list of group members.
*
* If this principal is a group, this function should return
* all member principal uri's for the group.
*
* @return array
*/
public function getGroupMemberSet()
{
return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl());
}
/**
* Returns the list of groups this principal is member of.
*
* If this principal is a member of a (list of) groups, this function
* should return a list of principal uri's for it's members.
*
* @return array
*/
public function getGroupMembership()
{
return $this->principalBackend->getGroupMembership($this->getPrincipalUrl());
}
/**
* Sets a list of group members.
*
* If this principal is a group, this method sets all the group members.
* The list of members is always overwritten, never appended to.
*
* This method should throw an exception if the members could not be set.
*/
public function setGroupMemberSet(array $principals)
{
$this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals);
}
/**
* Returns the displayname.
*
* This should be a human readable name for the principal.
* If none is available, return the nodename.
*
* @return string
*/
public function getDisplayName()
{
return $this->getName();
}
}

View file

@ -0,0 +1,136 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Principal;
use Sabre\DAV;
use Sabre\DAVACL;
/**
* CalDAV principal.
*
* This is a standard user-principal for CalDAV. This principal is also a
* collection and returns the caldav-proxy-read and caldav-proxy-write child
* principals.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class User extends DAVACL\Principal implements DAV\ICollection
{
/**
* Creates a new file in the directory.
*
* @param string $name Name of the file
* @param resource $data initial payload, passed as a readable stream resource
*
* @throws DAV\Exception\Forbidden
*/
public function createFile($name, $data = null)
{
throw new DAV\Exception\Forbidden('Permission denied to create file (filename '.$name.')');
}
/**
* Creates a new subdirectory.
*
* @param string $name
*
* @throws DAV\Exception\Forbidden
*/
public function createDirectory($name)
{
throw new DAV\Exception\Forbidden('Permission denied to create directory');
}
/**
* Returns a specific child node, referenced by its name.
*
* @param string $name
*
* @return DAV\INode
*/
public function getChild($name)
{
$principal = $this->principalBackend->getPrincipalByPath($this->getPrincipalURL().'/'.$name);
if (!$principal) {
throw new DAV\Exception\NotFound('Node with name '.$name.' was not found');
}
if ('calendar-proxy-read' === $name) {
return new ProxyRead($this->principalBackend, $this->principalProperties);
}
if ('calendar-proxy-write' === $name) {
return new ProxyWrite($this->principalBackend, $this->principalProperties);
}
throw new DAV\Exception\NotFound('Node with name '.$name.' was not found');
}
/**
* Returns an array with all the child nodes.
*
* @return DAV\INode[]
*/
public function getChildren()
{
$r = [];
if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL().'/calendar-proxy-read')) {
$r[] = new ProxyRead($this->principalBackend, $this->principalProperties);
}
if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL().'/calendar-proxy-write')) {
$r[] = new ProxyWrite($this->principalBackend, $this->principalProperties);
}
return $r;
}
/**
* Returns whether or not the child node exists.
*
* @param string $name
*
* @return bool
*/
public function childExists($name)
{
try {
$this->getChild($name);
return true;
} catch (DAV\Exception\NotFound $e) {
return false;
}
}
/**
* 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
*/
public function getACL()
{
$acl = parent::getACL();
$acl[] = [
'privilege' => '{DAV:}read',
'principal' => $this->principalProperties['uri'].'/calendar-proxy-read',
'protected' => true,
];
$acl[] = [
'privilege' => '{DAV:}read',
'principal' => $this->principalProperties['uri'].'/calendar-proxy-write',
'protected' => true,
];
return $acl;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Schedule;
use Sabre\CalDAV;
use Sabre\DAV;
use Sabre\DAVACL;
/**
* The CalDAV scheduling outbox.
*
* The outbox is mainly used as an endpoint in the tree for a client to do
* free-busy requests. This functionality is completely handled by the
* Scheduling plugin, so this object is actually mostly static.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Outbox extends DAV\Collection implements IOutbox
{
use DAVACL\ACLTrait;
/**
* The principal Uri.
*
* @var string
*/
protected $principalUri;
/**
* Constructor.
*
* @param string $principalUri
*/
public function __construct($principalUri)
{
$this->principalUri = $principalUri;
}
/**
* Returns the name of the node.
*
* This is used to generate the url.
*
* @return string
*/
public function getName()
{
return 'outbox';
}
/**
* Returns an array with all the child nodes.
*
* @return \Sabre\DAV\INode[]
*/
public function getChildren()
{
return [];
}
/**
* Returns the owner principal.
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getOwner()
{
return $this->principalUri;
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL()
{
return [
[
'privilege' => '{'.CalDAV\Plugin::NS_CALDAV.'}schedule-send',
'principal' => $this->getOwner(),
'protected' => true,
],
[
'privilege' => '{DAV:}read',
'principal' => $this->getOwner(),
'protected' => true,
],
[
'privilege' => '{'.CalDAV\Plugin::NS_CALDAV.'}schedule-send',
'principal' => $this->getOwner().'/calendar-proxy-write',
'protected' => true,
],
[
'privilege' => '{DAV:}read',
'principal' => $this->getOwner().'/calendar-proxy-read',
'protected' => true,
],
[
'privilege' => '{DAV:}read',
'principal' => $this->getOwner().'/calendar-proxy-write',
'protected' => true,
],
];
}
}

File diff suppressed because it is too large Load diff

View file

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

View file

@ -0,0 +1,219 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV;
use Sabre\DAV\Sharing\Plugin as SPlugin;
/**
* This object represents a CalDAV calendar that is shared by a different user.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class SharedCalendar extends Calendar implements ISharedCalendar
{
/**
* 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
*/
public function getShareAccess()
{
return isset($this->calendarInfo['share-access']) ? $this->calendarInfo['share-access'] : SPlugin::ACCESS_NOTSHARED;
}
/**
* 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
*/
public function getShareResourceUri()
{
return $this->calendarInfo['share-resource-uri'];
}
/**
* Updates the list of sharees.
*
* Every item must be a Sharee object.
*
* @param \Sabre\DAV\Xml\Element\Sharee[] $sharees
*/
public function updateInvites(array $sharees)
{
$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[]
*/
public 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
*/
public function setPublishStatus($value)
{
$this->caldavBackend->setPublishStatus($this->calendarInfo['id'], $value);
}
/**
* 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
*/
public function getACL()
{
$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
* calendar-object nodes in the calendar.
*
* @return array
*/
public function getChildACL()
{
$acl = [];
switch ($this->getShareAccess()) {
case SPlugin::ACCESS_NOTSHARED:
case SPlugin::ACCESS_SHAREDOWNER:
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;
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Xml\Filter;
use Sabre\CalDAV\Plugin;
use Sabre\DAV\Exception\BadRequest;
use Sabre\VObject\DateTimeParser;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;
/**
* CalendarData parser.
*
* This class parses the {urn:ietf:params:xml:ns:caldav}calendar-data XML
* element, as defined in:
*
* https://tools.ietf.org/html/rfc4791#section-9.6
*
* This element is used in three distinct places in the caldav spec, but in
* this case, this element class only implements the calendar-data element as
* it appears in a DAV:prop element, in a calendar-query or calendar-multiget
* REPORT request.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://sabre.io/license/ Modified BSD License
*/
class CalendarData implements XmlDeserializable
{
/**
* The deserialize method is called during xml parsing.
*
* This method is called statically, 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.
*
* @return mixed
*/
public static function xmlDeserialize(Reader $reader)
{
$result = [
'contentType' => $reader->getAttribute('content-type') ?: 'text/calendar',
'version' => $reader->getAttribute('version') ?: '2.0',
];
$elems = (array) $reader->parseInnerTree();
foreach ($elems as $elem) {
switch ($elem['name']) {
case '{'.Plugin::NS_CALDAV.'}expand':
$result['expand'] = [
'start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null,
'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null,
];
if (!$result['expand']['start'] || !$result['expand']['end']) {
throw new BadRequest('The "start" and "end" attributes are required when expanding calendar-data');
}
if ($result['expand']['end'] <= $result['expand']['start']) {
throw new BadRequest('The end-date must be larger than the start-date when expanding calendar-data');
}
break;
}
}
return $result;
}
}

View file

@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Xml\Filter;
use Sabre\CalDAV\Plugin;
use Sabre\DAV\Exception\BadRequest;
use Sabre\VObject\DateTimeParser;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;
/**
* CompFilter parser.
*
* This class parses the {urn:ietf:params:xml:ns:caldav}comp-filter XML
* element, as defined in:
*
* https://tools.ietf.org/html/rfc4791#section-9.6
*
* The result will be spit out as an array.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://sabre.io/license/ Modified BSD License
*/
class CompFilter implements XmlDeserializable
{
/**
* The deserialize method is called during xml parsing.
*
* This method is called statically, 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.
*
* @return mixed
*/
public static function xmlDeserialize(Reader $reader)
{
$result = [
'name' => null,
'is-not-defined' => false,
'comp-filters' => [],
'prop-filters' => [],
'time-range' => false,
];
$att = $reader->parseAttributes();
$result['name'] = $att['name'];
$elems = $reader->parseInnerTree();
if (is_array($elems)) {
foreach ($elems as $elem) {
switch ($elem['name']) {
case '{'.Plugin::NS_CALDAV.'}comp-filter':
$result['comp-filters'][] = $elem['value'];
break;
case '{'.Plugin::NS_CALDAV.'}prop-filter':
$result['prop-filters'][] = $elem['value'];
break;
case '{'.Plugin::NS_CALDAV.'}is-not-defined':
$result['is-not-defined'] = true;
break;
case '{'.Plugin::NS_CALDAV.'}time-range':
if ('VCALENDAR' === $result['name']) {
throw new BadRequest('You cannot add time-range filters on the VCALENDAR component');
}
$result['time-range'] = [
'start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null,
'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null,
];
if ($result['time-range']['start'] && $result['time-range']['end'] && $result['time-range']['end'] <= $result['time-range']['start']) {
throw new BadRequest('The end-date must be larger than the start-date');
}
break;
}
}
}
return $result;
}
}

View file

@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Xml\Filter;
use Sabre\CalDAV\Plugin;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;
/**
* PropFilter parser.
*
* This class parses the {urn:ietf:params:xml:ns:caldav}param-filter XML
* element, as defined in:
*
* https://tools.ietf.org/html/rfc4791#section-9.7.3
*
* The result will be spit out as an array.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://sabre.io/license/ Modified BSD License
*/
class ParamFilter implements XmlDeserializable
{
/**
* The deserialize method is called during xml parsing.
*
* This method is called statically, 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.
*
* Important note 2: 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.
*
* @return mixed
*/
public static function xmlDeserialize(Reader $reader)
{
$result = [
'name' => null,
'is-not-defined' => false,
'text-match' => null,
];
$att = $reader->parseAttributes();
$result['name'] = $att['name'];
$elems = $reader->parseInnerTree();
if (is_array($elems)) {
foreach ($elems as $elem) {
switch ($elem['name']) {
case '{'.Plugin::NS_CALDAV.'}is-not-defined':
$result['is-not-defined'] = true;
break;
case '{'.Plugin::NS_CALDAV.'}text-match':
$result['text-match'] = [
'negate-condition' => isset($elem['attributes']['negate-condition']) && 'yes' === $elem['attributes']['negate-condition'],
'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;ascii-casemap',
'value' => $elem['value'],
];
break;
}
}
}
return $result;
}
}

View file

@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Xml\Filter;
use Sabre\CalDAV\Plugin;
use Sabre\DAV\Exception\BadRequest;
use Sabre\VObject\DateTimeParser;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;
/**
* PropFilter parser.
*
* This class parses the {urn:ietf:params:xml:ns:caldav}prop-filter XML
* element, as defined in:
*
* https://tools.ietf.org/html/rfc4791#section-9.7.2
*
* The result will be spit out as an array.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://sabre.io/license/ Modified BSD License
*/
class PropFilter implements XmlDeserializable
{
/**
* The deserialize method is called during xml parsing.
*
* This method is called statically, 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.
*
* @return mixed
*/
public static function xmlDeserialize(Reader $reader)
{
$result = [
'name' => null,
'is-not-defined' => false,
'param-filters' => [],
'text-match' => null,
'time-range' => [],
];
$att = $reader->parseAttributes();
$result['name'] = $att['name'];
$elems = $reader->parseInnerTree();
if (is_array($elems)) {
foreach ($elems as $elem) {
switch ($elem['name']) {
case '{'.Plugin::NS_CALDAV.'}param-filter':
$result['param-filters'][] = $elem['value'];
break;
case '{'.Plugin::NS_CALDAV.'}is-not-defined':
$result['is-not-defined'] = true;
break;
case '{'.Plugin::NS_CALDAV.'}time-range':
$result['time-range'] = [
'start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null,
'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null,
];
if ($result['time-range']['start'] && $result['time-range']['end'] && $result['time-range']['end'] <= $result['time-range']['start']) {
throw new BadRequest('The end-date must be larger than the start-date');
}
break;
case '{'.Plugin::NS_CALDAV.'}text-match':
$result['text-match'] = [
'negate-condition' => isset($elem['attributes']['negate-condition']) && 'yes' === $elem['attributes']['negate-condition'],
'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;ascii-casemap',
'value' => $elem['value'],
];
break;
}
}
}
return $result;
}
}

View file

@ -0,0 +1,290 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Xml\Notification;
use Sabre\CalDAV;
use Sabre\CalDAV\SharingPlugin as SharingPlugin;
use Sabre\DAV;
use Sabre\Xml\Writer;
/**
* This class represents the cs:invite-notification notification element.
*
* This element is defined here:
* http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
*
* @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 NotificationInterface
{
/**
* A unique id for the message.
*
* @var string
*/
protected $id;
/**
* Timestamp of the notification.
*
* @var \DateTime
*/
protected $dtStamp;
/**
* A url to the recipient of the notification. This can be an email
* address (mailto:), or a principal url.
*
* @var string
*/
protected $href;
/**
* The type of message, see the SharingPlugin::STATUS_* constants.
*
* @var int
*/
protected $type;
/**
* True if access to a calendar is read-only.
*
* @var bool
*/
protected $readOnly;
/**
* A url to the shared calendar.
*
* @var string
*/
protected $hostUrl;
/**
* Url to the sharer of the calendar.
*
* @var string
*/
protected $organizer;
/**
* The name of the sharer.
*
* @var string
*/
protected $commonName;
/**
* The name of the sharer.
*
* @var string
*/
protected $firstName;
/**
* The name of the sharer.
*
* @var string
*/
protected $lastName;
/**
* A description of the share request.
*
* @var string
*/
protected $summary;
/**
* The Etag for the notification.
*
* @var string
*/
protected $etag;
/**
* The list of supported components.
*
* @var CalDAV\Xml\Property\SupportedCalendarComponentSet
*/
protected $supportedComponents;
/**
* Creates the Invite notification.
*
* This constructor receives an array with the following elements:
*
* * id - A unique id
* * etag - The etag
* * dtStamp - A DateTime object with a timestamp for the notification.
* * type - The type of notification, see SharingPlugin::STATUS_*
* constants for details.
* * readOnly - This must be set to true, if this is an invite for
* read-only access to a calendar.
* * hostUrl - A url to the shared calendar.
* * organizer - Url to the sharer principal.
* * commonName - The real name of the sharer (optional).
* * firstName - The first name of the sharer (optional).
* * lastName - The last name of the sharer (optional).
* * summary - Description of the share, can be the same as the
* calendar, but may also be modified (optional).
* * supportedComponents - An instance of
* Sabre\CalDAV\Property\SupportedCalendarComponentSet.
* This allows the client to determine which components
* will be supported in the shared calendar. This is
* also optional.
*
* @param array $values All the options
*/
public function __construct(array $values)
{
$required = [
'id',
'etag',
'href',
'dtStamp',
'type',
'readOnly',
'hostUrl',
'organizer',
];
foreach ($required as $item) {
if (!isset($values[$item])) {
throw new \InvalidArgumentException($item.' is a required constructor option');
}
}
foreach ($values as $key => $value) {
if (!property_exists($this, $key)) {
throw new \InvalidArgumentException('Unknown option: '.$key);
}
$this->$key = $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 XmlSerializable 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.
*/
public function xmlSerialize(Writer $writer)
{
$writer->writeElement('{'.CalDAV\Plugin::NS_CALENDARSERVER.'}invite-notification');
}
/**
* This method serializes the entire notification, as it is used in the
* response body.
*/
public function xmlSerializeFull(Writer $writer)
{
$cs = '{'.CalDAV\Plugin::NS_CALENDARSERVER.'}';
$this->dtStamp->setTimezone(new \DateTimeZone('GMT'));
$writer->writeElement($cs.'dtstamp', $this->dtStamp->format('Ymd\\THis\\Z'));
$writer->startElement($cs.'invite-notification');
$writer->writeElement($cs.'uid', $this->id);
$writer->writeElement('{DAV:}href', $this->href);
switch ($this->type) {
case DAV\Sharing\Plugin::INVITE_ACCEPTED:
$writer->writeElement($cs.'invite-accepted');
break;
case DAV\Sharing\Plugin::INVITE_NORESPONSE:
$writer->writeElement($cs.'invite-noresponse');
break;
}
$writer->writeElement($cs.'hosturl', [
'{DAV:}href' => $writer->contextUri.$this->hostUrl,
]);
if ($this->summary) {
$writer->writeElement($cs.'summary', $this->summary);
}
$writer->startElement($cs.'access');
if ($this->readOnly) {
$writer->writeElement($cs.'read');
} else {
$writer->writeElement($cs.'read-write');
}
$writer->endElement(); // access
$writer->startElement($cs.'organizer');
// If the organizer contains a 'mailto:' part, it means it should be
// treated as absolute.
if ('mailto:' === strtolower(substr($this->organizer, 0, 7))) {
$writer->writeElement('{DAV:}href', $this->organizer);
} else {
$writer->writeElement('{DAV:}href', $writer->contextUri.$this->organizer);
}
if ($this->commonName) {
$writer->writeElement($cs.'common-name', $this->commonName);
}
if ($this->firstName) {
$writer->writeElement($cs.'first-name', $this->firstName);
}
if ($this->lastName) {
$writer->writeElement($cs.'last-name', $this->lastName);
}
$writer->endElement(); // organizer
if ($this->commonName) {
$writer->writeElement($cs.'organizer-cn', $this->commonName);
}
if ($this->firstName) {
$writer->writeElement($cs.'organizer-first', $this->firstName);
}
if ($this->lastName) {
$writer->writeElement($cs.'organizer-last', $this->lastName);
}
if ($this->supportedComponents) {
$writer->writeElement('{'.CalDAV\Plugin::NS_CALDAV.'}supported-calendar-component-set', $this->supportedComponents);
}
$writer->endElement(); // invite-notification
}
/**
* Returns a unique id for this notification.
*
* This is just the base url. This should generally be some kind of unique
* id.
*
* @return string
*/
public function getId()
{
return $this->id;
}
/**
* Returns the ETag for this notification.
*
* The ETag must be surrounded by literal double-quotes.
*
* @return string
*/
public function getETag()
{
return $this->etag;
}
}

View file

@ -0,0 +1,199 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Xml\Notification;
use Sabre\CalDAV;
use Sabre\CalDAV\SharingPlugin;
use Sabre\DAV;
use Sabre\Xml\Writer;
/**
* This class represents the cs:invite-reply notification element.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class InviteReply implements NotificationInterface
{
/**
* A unique id for the message.
*
* @var string
*/
protected $id;
/**
* Timestamp of the notification.
*
* @var \DateTime
*/
protected $dtStamp;
/**
* The unique id of the notification this was a reply to.
*
* @var string
*/
protected $inReplyTo;
/**
* A url to the recipient of the original (!) notification.
*
* @var string
*/
protected $href;
/**
* The type of message, see the SharingPlugin::STATUS_ constants.
*
* @var int
*/
protected $type;
/**
* A url to the shared calendar.
*
* @var string
*/
protected $hostUrl;
/**
* A description of the share request.
*
* @var string
*/
protected $summary;
/**
* Notification Etag.
*
* @var string
*/
protected $etag;
/**
* Creates the Invite Reply Notification.
*
* This constructor receives an array with the following elements:
*
* * id - A unique id
* * etag - The etag
* * dtStamp - A DateTime object with a timestamp for the notification.
* * inReplyTo - This should refer to the 'id' of the notification
* this is a reply to.
* * type - The type of notification, see SharingPlugin::STATUS_*
* constants for details.
* * hostUrl - A url to the shared calendar.
* * summary - Description of the share, can be the same as the
* calendar, but may also be modified (optional).
*/
public function __construct(array $values)
{
$required = [
'id',
'etag',
'href',
'dtStamp',
'inReplyTo',
'type',
'hostUrl',
];
foreach ($required as $item) {
if (!isset($values[$item])) {
throw new \InvalidArgumentException($item.' is a required constructor option');
}
}
foreach ($values as $key => $value) {
if (!property_exists($this, $key)) {
throw new \InvalidArgumentException('Unknown option: '.$key);
}
$this->$key = $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 XmlSerializable 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.
*/
public function xmlSerialize(Writer $writer)
{
$writer->writeElement('{'.CalDAV\Plugin::NS_CALENDARSERVER.'}invite-reply');
}
/**
* This method serializes the entire notification, as it is used in the
* response body.
*/
public function xmlSerializeFull(Writer $writer)
{
$cs = '{'.CalDAV\Plugin::NS_CALENDARSERVER.'}';
$this->dtStamp->setTimezone(new \DateTimeZone('GMT'));
$writer->writeElement($cs.'dtstamp', $this->dtStamp->format('Ymd\\THis\\Z'));
$writer->startElement($cs.'invite-reply');
$writer->writeElement($cs.'uid', $this->id);
$writer->writeElement($cs.'in-reply-to', $this->inReplyTo);
$writer->writeElement('{DAV:}href', $this->href);
switch ($this->type) {
case DAV\Sharing\Plugin::INVITE_ACCEPTED:
$writer->writeElement($cs.'invite-accepted');
break;
case DAV\Sharing\Plugin::INVITE_DECLINED:
$writer->writeElement($cs.'invite-declined');
break;
}
$writer->writeElement($cs.'hosturl', [
'{DAV:}href' => $writer->contextUri.$this->hostUrl,
]);
if ($this->summary) {
$writer->writeElement($cs.'summary', $this->summary);
}
$writer->endElement(); // invite-reply
}
/**
* Returns a unique id for this notification.
*
* This is just the base url. This should generally be some kind of unique
* id.
*
* @return string
*/
public function getId()
{
return $this->id;
}
/**
* Returns the ETag for this notification.
*
* The ETag must be surrounded by literal double-quotes.
*
* @return string
*/
public function getETag()
{
return $this->etag;
}
}

View file

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Xml\Notification;
use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;
/**
* This interface reflects a single notification type.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface NotificationInterface extends XmlSerializable
{
/**
* This method serializes the entire notification, as it is used in the
* response body.
*/
public function xmlSerializeFull(Writer $writer);
/**
* Returns a unique id for this notification.
*
* This is just the base url. This should generally be some kind of unique
* id.
*
* @return string
*/
public function getId();
/**
* Returns the ETag for this notification.
*
* The ETag must be surrounded by literal double-quotes.
*
* @return string
*/
public function getETag();
}

View file

@ -0,0 +1,171 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Xml\Notification;
use Sabre\CalDAV\Plugin;
use Sabre\Xml\Writer;
/**
* SystemStatus notification.
*
* This notification can be used to indicate to the user that the system is
* down.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class SystemStatus implements NotificationInterface
{
const TYPE_LOW = 1;
const TYPE_MEDIUM = 2;
const TYPE_HIGH = 3;
/**
* A unique id.
*
* @var string
*/
protected $id;
/**
* The type of alert. This should be one of the TYPE_ constants.
*
* @var int
*/
protected $type;
/**
* A human-readable description of the problem.
*
* @var string
*/
protected $description;
/**
* A url to a website with more information for the user.
*
* @var string
*/
protected $href;
/**
* Notification Etag.
*
* @var string
*/
protected $etag;
/**
* Creates the notification.
*
* Some kind of unique id should be provided. This is used to generate a
* url.
*
* @param string $id
* @param string $etag
* @param int $type
* @param string $description
* @param string $href
*/
public function __construct($id, $etag, $type = self::TYPE_HIGH, $description = null, $href = null)
{
$this->id = $id;
$this->type = $type;
$this->description = $description;
$this->href = $href;
$this->etag = $etag;
}
/**
* The serialize method is called during xml writing.
*
* It should use the $writer argument to encode this object into XML.
*
* Important note: it is not needed to create the parent element. The
* parent element is already created, and we only have to worry about
* attributes, child elements and text (if any).
*
* Important note 2: If you are writing any new elements, you are also
* responsible for closing them.
*/
public function xmlSerialize(Writer $writer)
{
switch ($this->type) {
case self::TYPE_LOW:
$type = 'low';
break;
case self::TYPE_MEDIUM:
$type = 'medium';
break;
default:
case self::TYPE_HIGH:
$type = 'high';
break;
}
$writer->startElement('{'.Plugin::NS_CALENDARSERVER.'}systemstatus');
$writer->writeAttribute('type', $type);
$writer->endElement();
}
/**
* This method serializes the entire notification, as it is used in the
* response body.
*/
public function xmlSerializeFull(Writer $writer)
{
$cs = '{'.Plugin::NS_CALENDARSERVER.'}';
switch ($this->type) {
case self::TYPE_LOW:
$type = 'low';
break;
case self::TYPE_MEDIUM:
$type = 'medium';
break;
default:
case self::TYPE_HIGH:
$type = 'high';
break;
}
$writer->startElement($cs.'systemstatus');
$writer->writeAttribute('type', $type);
if ($this->description) {
$writer->writeElement($cs.'description', $this->description);
}
if ($this->href) {
$writer->writeElement('{DAV:}href', $this->href);
}
$writer->endElement(); // systemstatus
}
/**
* Returns a unique id for this notification.
*
* This is just the base url. This should generally be some kind of unique
* id.
*
* @return string
*/
public function getId()
{
return $this->id;
}
/*
* Returns the ETag for this notification.
*
* The ETag must be surrounded by literal double-quotes.
*
* @return string
*/
public function getETag()
{
return $this->etag;
}
}

View file

@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Xml\Property;
use Sabre\CalDAV\Plugin;
use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;
/**
* AllowedSharingModes.
*
* This property encodes the 'allowed-sharing-modes' property, as defined by
* the 'caldav-sharing-02' spec, in the http://calendarserver.org/ns/
* namespace.
*
* This property is a representation of the supported-calendar_component-set
* property in the CalDAV namespace. It simply requires an array of components,
* such as VEVENT, VTODO
*
* @see https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://sabre.io/license/ Modified BSD License
*/
class AllowedSharingModes implements XmlSerializable
{
/**
* Whether or not a calendar can be shared with another user.
*
* @var bool
*/
protected $canBeShared;
/**
* Whether or not the calendar can be placed on a public url.
*
* @var bool
*/
protected $canBePublished;
/**
* Constructor.
*
* @param bool $canBeShared
* @param bool $canBePublished
*/
public function __construct($canBeShared, $canBePublished)
{
$this->canBeShared = $canBeShared;
$this->canBePublished = $canBePublished;
}
/**
* 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 XmlSerializable 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.
*/
public function xmlSerialize(Writer $writer)
{
if ($this->canBeShared) {
$writer->writeElement('{'.Plugin::NS_CALENDARSERVER.'}can-be-shared');
}
if ($this->canBePublished) {
$writer->writeElement('{'.Plugin::NS_CALENDARSERVER.'}can-be-published');
}
}
}

View file

@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Xml\Property;
use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;
/**
* email-address-set property.
*
* This property represents the email-address-set property in the
* http://calendarserver.org/ns/ namespace.
*
* It's a list of email addresses associated with a user.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class EmailAddressSet implements XmlSerializable
{
/**
* emails.
*
* @var array
*/
private $emails;
/**
* __construct.
*/
public function __construct(array $emails)
{
$this->emails = $emails;
}
/**
* Returns the email addresses.
*
* @return array
*/
public function getValue()
{
return $this->emails;
}
/**
* 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 XmlSerializable 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.
*/
public function xmlSerialize(Writer $writer)
{
foreach ($this->emails as $email) {
$writer->writeElement('{http://calendarserver.org/ns/}email-address', $email);
}
}
}

View file

@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace Sabre\CalDAV\Xml\Property;
use Sabre\CalDAV\Plugin;
use Sabre\DAV;
use Sabre\DAV\Xml\Element\Sharee;
use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;
/**
* Invite property.
*
* This property encodes the 'invite' property, as defined by
* the 'caldav-sharing-02' spec, in the http://calendarserver.org/ns/
* namespace.
*
* @see https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt
*
* @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
{
/**
* The list of users a calendar has been shared to.
*
* @var Sharee[]
*/
protected $sharees;
/**
* Creates the property.
*
* @param Sharee[] $sharees
*/
public function __construct(array $sharees)
{
$this->sharees = $sharees;
}
/**
* Returns the list of users, as it was passed to the constructor.
*
* @return array
*/
public function getValue()
{
return $this->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 XmlSerializable 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.
*/
public function xmlSerialize(Writer $writer)
{
$cs = '{'.Plugin::NS_CALENDARSERVER.'}';
foreach ($this->sharees as $sharee) {
if (DAV\Sharing\Plugin::ACCESS_SHAREDOWNER === $sharee->access) {
$writer->startElement($cs.'organizer');
} else {
$writer->startElement($cs.'user');
switch ($sharee->inviteStatus) {
case DAV\Sharing\Plugin::INVITE_ACCEPTED:
$writer->writeElement($cs.'invite-accepted');
break;
case DAV\Sharing\Plugin::INVITE_DECLINED:
$writer->writeElement($cs.'invite-declined');
break;
case DAV\Sharing\Plugin::INVITE_NORESPONSE:
$writer->writeElement($cs.'invite-noresponse');
break;
case DAV\Sharing\Plugin::INVITE_INVALID:
$writer->writeElement($cs.'invite-invalid');
break;
}
$writer->startElement($cs.'access');
switch ($sharee->access) {
case DAV\Sharing\Plugin::ACCESS_READWRITE:
$writer->writeElement($cs.'read-write');
break;
case DAV\Sharing\Plugin::ACCESS_READ:
$writer->writeElement($cs.'read');
break;
}
$writer->endElement(); // access
}
$href = new 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
}
}
}

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