+
diff --git a/plugins/panakour/backup/controllers/backups/index.htm b/plugins/panakour/backup/controllers/backups/index.htm
new file mode 100644
index 000000000..e91d61c8e
--- /dev/null
+++ b/plugins/panakour/backup/controllers/backups/index.htm
@@ -0,0 +1,2 @@
+= $this->makePartial('toolbar') ?>
+= $this->makePartial('list') ?>
\ No newline at end of file
diff --git a/plugins/panakour/backup/docs/images/oc_backup_config.png b/plugins/panakour/backup/docs/images/oc_backup_config.png
new file mode 100644
index 000000000..38ef9f262
Binary files /dev/null and b/plugins/panakour/backup/docs/images/oc_backup_config.png differ
diff --git a/plugins/panakour/backup/docs/images/oc_backup_config_1.png b/plugins/panakour/backup/docs/images/oc_backup_config_1.png
new file mode 100644
index 000000000..acea17df0
Binary files /dev/null and b/plugins/panakour/backup/docs/images/oc_backup_config_1.png differ
diff --git a/plugins/panakour/backup/docs/images/oc_backups.png b/plugins/panakour/backup/docs/images/oc_backups.png
new file mode 100644
index 000000000..b89f0389a
Binary files /dev/null and b/plugins/panakour/backup/docs/images/oc_backups.png differ
diff --git a/plugins/panakour/backup/models/Settings.php b/plugins/panakour/backup/models/Settings.php
new file mode 100644
index 000000000..531ea415d
--- /dev/null
+++ b/plugins/panakour/backup/models/Settings.php
@@ -0,0 +1,97 @@
+ 'themes'],
+ ['path' => 'plugins'],
+ ];
+ $pathsToExclude = [
+ ['path' => 'vendor'],
+ ['path' => 'plugins/rainlab'],
+ ];
+ Settings::set('include_files', $pathsToInclude);
+ Settings::set('exclude_files', $pathsToExclude);
+ Settings::set('maximum_execution_time', 30);
+ }
+}
diff --git a/plugins/panakour/backup/updates/version.yaml b/plugins/panakour/backup/updates/version.yaml
new file mode 100644
index 000000000..79a69285f
--- /dev/null
+++ b/plugins/panakour/backup/updates/version.yaml
@@ -0,0 +1,13 @@
+1.0.1:
+ - First version of backup plugin.
+ - seed_default_settings.php
+1.0.2: Fixes bug when no backup folder exist
+1.0.3: Settings button directly in toolbar of backup list. Fixed monitored backups name
+1.0.4: Support Dropbox storage driver
+1.0.5: Support Dropbox storage driver (Fix if not configured yet)
+1.0.6: Change the backups path to fix 404 error on apache server because of the default .htaccess
+1.0.7: Display backup list even if exist only in the old path
+1.0.8: Add permissions.
+1.0.9: Add option to create backup of whole project independently of settings
+1.0.10: Added webdav driver (thanks to @AdrienAdB )
+1.0.11: Fix symfony/process dependency
diff --git a/plugins/panakour/backup/vendor/autoload.php b/plugins/panakour/backup/vendor/autoload.php
new file mode 100644
index 000000000..892d6a459
--- /dev/null
+++ b/plugins/panakour/backup/vendor/autoload.php
@@ -0,0 +1,7 @@
+ testdata.vcf
+
+HI;
+
+ fwrite(STDERR, $help);
+ exit(2);
+}
+
+$count = (int)$argv[1];
+if ($count < 1) {
+ fwrite(STDERR, "Count must be at least 1\n");
+ exit(2);
+}
+
+fwrite(STDERR, "sabre/vobject " . Version::VERSION . "\n");
+fwrite(STDERR, "Generating " . $count . " vcards in vCard 4.0 format\n");
+
+/**
+ * The following list is just some random data we compiled from various
+ * sources online.
+ *
+ * Very little thought went into compiling this list, and certainly nothing
+ * political or ethical.
+ *
+ * We would _love_ more additions to this to add more variation to this list.
+ *
+ * Send us PR's and don't be shy adding your own first and last name for fun.
+ */
+
+$sets = array(
+ "nl" => array(
+ "country" => "Netherlands",
+ "boys" => array(
+ "Anno",
+ "Bram",
+ "Daan",
+ "Evert",
+ "Finn",
+ "Jayden",
+ "Jens",
+ "Jesse",
+ "Levi",
+ "Lucas",
+ "Luuk",
+ "Milan",
+ "René",
+ "Sem",
+ "Sibrand",
+ "Willem",
+ ),
+ "girls" => array(
+ "Celia",
+ "Emma",
+ "Fenna",
+ "Geke",
+ "Inge",
+ "Julia",
+ "Lisa",
+ "Lotte",
+ "Mila",
+ "Sara",
+ "Sophie",
+ "Tess",
+ "Zoë",
+ ),
+ "last" => array(
+ "Bakker",
+ "Bos",
+ "De Boer",
+ "De Groot",
+ "De Jong",
+ "De Vries",
+ "Jansen",
+ "Janssen",
+ "Meyer",
+ "Mulder",
+ "Peters",
+ "Smit",
+ "Van Dijk",
+ "Van den Berg",
+ "Visser",
+ "Vos",
+ ),
+ ),
+ "us" => array(
+ "country" => "United States",
+ "boys" => array(
+ "Aiden",
+ "Alexander",
+ "Charles",
+ "David",
+ "Ethan",
+ "Jacob",
+ "James",
+ "Jayden",
+ "John",
+ "Joseph",
+ "Liam",
+ "Mason",
+ "Michael",
+ "Noah",
+ "Richard",
+ "Robert",
+ "Thomas",
+ "William",
+ ),
+ "girls" => array(
+ "Ava",
+ "Barbara",
+ "Chloe",
+ "Dorothy",
+ "Elizabeth",
+ "Emily",
+ "Emma",
+ "Isabella",
+ "Jennifer",
+ "Lily",
+ "Linda",
+ "Margaret",
+ "Maria",
+ "Mary",
+ "Mia",
+ "Olivia",
+ "Patricia",
+ "Roxy",
+ "Sophia",
+ "Susan",
+ "Zoe",
+ ),
+ "last" => array(
+ "Smith",
+ "Johnson",
+ "Williams",
+ "Jones",
+ "Brown",
+ "Davis",
+ "Miller",
+ "Wilson",
+ "Moore",
+ "Taylor",
+ "Anderson",
+ "Thomas",
+ "Jackson",
+ "White",
+ "Harris",
+ "Martin",
+ "Thompson",
+ "Garcia",
+ "Martinez",
+ "Robinson",
+ ),
+ ),
+);
+
+$current = 0;
+
+$r = function($arr) {
+
+ return $arr[mt_rand(0,count($arr)-1)];
+
+};
+
+$bdayStart = strtotime('-85 years');
+$bdayEnd = strtotime('-20 years');
+
+while($current < $count) {
+
+ $current++;
+ fwrite(STDERR, "\033[100D$current/$count");
+
+ $country = array_rand($sets);
+ $gender = mt_rand(0,1)?'girls':'boys';
+
+ $vcard = new Component\VCard(array(
+ 'VERSION' => '4.0',
+ 'FN' => $r($sets[$country][$gender]) . ' ' . $r($sets[$country]['last']),
+ 'UID' => UUIDUtil::getUUID(),
+ ));
+
+ $bdayRatio = mt_rand(0,9);
+
+ if($bdayRatio < 2) {
+ // 20% has a birthday property with a full date
+ $dt = new \DateTime('@' . mt_rand($bdayStart, $bdayEnd));
+ $vcard->add('BDAY', $dt->format('Ymd'));
+
+ } elseif ($bdayRatio < 3) {
+ // 10% we only know the month and date of
+ $dt = new \DateTime('@' . mt_rand($bdayStart, $bdayEnd));
+ $vcard->add('BDAY', '--' . $dt->format('md'));
+ }
+ if ($result = $vcard->validate()) {
+ ob_start();
+ echo "\nWe produced an invalid vcard somehow!\n";
+ foreach($result as $message) {
+ echo " " . $message['message'] . "\n";
+ }
+ fwrite(STDERR, ob_get_clean());
+ }
+ echo $vcard->serialize();
+
+}
+
+fwrite(STDERR,"\nDone.\n");
diff --git a/plugins/panakour/backup/vendor/bin/naturalselection b/plugins/panakour/backup/vendor/bin/naturalselection
new file mode 100644
index 000000000..7e20439c1
--- /dev/null
+++ b/plugins/panakour/backup/vendor/bin/naturalselection
@@ -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()
diff --git a/plugins/panakour/backup/vendor/bin/sabredav b/plugins/panakour/backup/vendor/bin/sabredav
new file mode 100644
index 000000000..032371ba8
--- /dev/null
+++ b/plugins/panakour/backup/vendor/bin/sabredav
@@ -0,0 +1,2 @@
+#!/bin/sh
+php -S 0.0.0.0:8080 `dirname $0`/sabredav.php
diff --git a/plugins/panakour/backup/vendor/bin/vobject b/plugins/panakour/backup/vendor/bin/vobject
new file mode 100644
index 000000000..2aca7e729
--- /dev/null
+++ b/plugins/panakour/backup/vendor/bin/vobject
@@ -0,0 +1,27 @@
+#!/usr/bin/env php
+main($argv));
+
diff --git a/plugins/panakour/backup/vendor/composer/ClassLoader.php b/plugins/panakour/backup/vendor/composer/ClassLoader.php
new file mode 100644
index 000000000..fce8549f0
--- /dev/null
+++ b/plugins/panakour/backup/vendor/composer/ClassLoader.php
@@ -0,0 +1,445 @@
+
+ * Jordi Boggiano
+ *
+ * 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
+ * @author Jordi Boggiano
+ * @see http://www.php-fig.org/psr/psr-0/
+ * @see http://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ // PSR-4
+ private $prefixLengthsPsr4 = array();
+ private $prefixDirsPsr4 = array();
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ private $prefixesPsr0 = array();
+ private $fallbackDirsPsr0 = array();
+
+ private $useIncludePath = false;
+ private $classMap = array();
+ private $classMapAuthoritative = false;
+ private $missingClasses = array();
+ private $apcuPrefix;
+
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', $this->prefixesPsr0);
+ }
+
+ return array();
+ }
+
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param array $classMap Class to filename map
+ */
+ 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 array|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ (array) $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ (array) $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 array|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ (array) $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] = (array) $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ (array) $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 array|string $paths The PSR-0 base directories
+ */
+ 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 array|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ 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
+ */
+ 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
+ */
+ 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
+ */
+ 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
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return bool|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ includeFile($file);
+
+ return true;
+ }
+ }
+
+ /**
+ * 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;
+ }
+
+ 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;
+ }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+ include $file;
+}
diff --git a/plugins/panakour/backup/vendor/composer/LICENSE b/plugins/panakour/backup/vendor/composer/LICENSE
new file mode 100644
index 000000000..f27399a04
--- /dev/null
+++ b/plugins/panakour/backup/vendor/composer/LICENSE
@@ -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.
+
diff --git a/plugins/panakour/backup/vendor/composer/autoload_classmap.php b/plugins/panakour/backup/vendor/composer/autoload_classmap.php
new file mode 100644
index 000000000..7a91153b0
--- /dev/null
+++ b/plugins/panakour/backup/vendor/composer/autoload_classmap.php
@@ -0,0 +1,9 @@
+ $vendorDir . '/sabre/uri/lib/functions.php',
+ '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
+ '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
+ '25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
+ 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
+ 'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
+ 'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',
+ '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.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',
+ '0c3c22e27afa83be19b4c938f4c6e9ea' => $vendorDir . '/spatie/laravel-backup/src/Helpers/functions.php',
+);
diff --git a/plugins/panakour/backup/vendor/composer/autoload_namespaces.php b/plugins/panakour/backup/vendor/composer/autoload_namespaces.php
new file mode 100644
index 000000000..b7fc0125d
--- /dev/null
+++ b/plugins/panakour/backup/vendor/composer/autoload_namespaces.php
@@ -0,0 +1,9 @@
+ array($vendorDir . '/symfony/polyfill-php72'),
+ 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
+ 'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'),
+ 'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
+ 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'),
+ 'Spatie\\TemporaryDirectory\\' => array($vendorDir . '/spatie/temporary-directory/src'),
+ 'Spatie\\FlysystemDropbox\\' => array($vendorDir . '/spatie/flysystem-dropbox/src'),
+ 'Spatie\\Dropbox\\' => array($vendorDir . '/spatie/dropbox-api/src'),
+ 'Spatie\\DbDumper\\' => array($vendorDir . '/spatie/db-dumper/src'),
+ 'Spatie\\Backup\\' => array($vendorDir . '/spatie/laravel-backup/src'),
+ 'Sabre\\Xml\\' => array($vendorDir . '/sabre/xml/lib'),
+ 'Sabre\\VObject\\' => array($vendorDir . '/sabre/vobject/lib'),
+ 'Sabre\\Uri\\' => array($vendorDir . '/sabre/uri/lib'),
+ 'Sabre\\HTTP\\' => array($vendorDir . '/sabre/http/lib'),
+ 'Sabre\\Event\\' => array($vendorDir . '/sabre/event/lib'),
+ 'Sabre\\DAV\\' => array($vendorDir . '/sabre/dav/lib/DAV'),
+ 'Sabre\\DAVACL\\' => array($vendorDir . '/sabre/dav/lib/DAVACL'),
+ 'Sabre\\CardDAV\\' => array($vendorDir . '/sabre/dav/lib/CardDAV'),
+ 'Sabre\\CalDAV\\' => array($vendorDir . '/sabre/dav/lib/CalDAV'),
+ 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
+ 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
+ 'League\\Flysystem\\WebDAV\\' => array($vendorDir . '/league/flysystem-webdav/src'),
+ 'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'),
+ 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
+ 'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
+ 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
+ 'GrahamCampbell\\GuzzleFactory\\' => array($vendorDir . '/graham-campbell/guzzle-factory/src'),
+);
diff --git a/plugins/panakour/backup/vendor/composer/autoload_real.php b/plugins/panakour/backup/vendor/composer/autoload_real.php
new file mode 100644
index 000000000..d733de89d
--- /dev/null
+++ b/plugins/panakour/backup/vendor/composer/autoload_real.php
@@ -0,0 +1,70 @@
+= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
+ if ($useStaticLoader) {
+ require_once __DIR__ . '/autoload_static.php';
+
+ call_user_func(\Composer\Autoload\ComposerStaticInitbe9348664e724b21a091b7dc2c806f56::getInitializer($loader));
+ } else {
+ $map = require __DIR__ . '/autoload_namespaces.php';
+ foreach ($map as $namespace => $path) {
+ $loader->set($namespace, $path);
+ }
+
+ $map = require __DIR__ . '/autoload_psr4.php';
+ foreach ($map as $namespace => $path) {
+ $loader->setPsr4($namespace, $path);
+ }
+
+ $classMap = require __DIR__ . '/autoload_classmap.php';
+ if ($classMap) {
+ $loader->addClassMap($classMap);
+ }
+ }
+
+ $loader->register(true);
+
+ if ($useStaticLoader) {
+ $includeFiles = Composer\Autoload\ComposerStaticInitbe9348664e724b21a091b7dc2c806f56::$files;
+ } else {
+ $includeFiles = require __DIR__ . '/autoload_files.php';
+ }
+ foreach ($includeFiles as $fileIdentifier => $file) {
+ composerRequirebe9348664e724b21a091b7dc2c806f56($fileIdentifier, $file);
+ }
+
+ return $loader;
+ }
+}
+
+function composerRequirebe9348664e724b21a091b7dc2c806f56($fileIdentifier, $file)
+{
+ if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+ require $file;
+
+ $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/composer/autoload_static.php b/plugins/panakour/backup/vendor/composer/autoload_static.php
new file mode 100644
index 000000000..56c7c0133
--- /dev/null
+++ b/plugins/panakour/backup/vendor/composer/autoload_static.php
@@ -0,0 +1,188 @@
+ __DIR__ . '/..' . '/sabre/uri/lib/functions.php',
+ '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
+ '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
+ '25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
+ 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
+ 'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php',
+ 'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php',
+ '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.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',
+ '0c3c22e27afa83be19b4c938f4c6e9ea' => __DIR__ . '/..' . '/spatie/laravel-backup/src/Helpers/functions.php',
+ );
+
+ public static $prefixLengthsPsr4 = array (
+ 'S' =>
+ array (
+ 'Symfony\\Polyfill\\Php72\\' => 23,
+ 'Symfony\\Polyfill\\Mbstring\\' => 26,
+ 'Symfony\\Polyfill\\Intl\\Idn\\' => 26,
+ 'Symfony\\Component\\Process\\' => 26,
+ 'Symfony\\Component\\Finder\\' => 25,
+ 'Spatie\\TemporaryDirectory\\' => 26,
+ 'Spatie\\FlysystemDropbox\\' => 24,
+ 'Spatie\\Dropbox\\' => 15,
+ 'Spatie\\DbDumper\\' => 16,
+ 'Spatie\\Backup\\' => 14,
+ 'Sabre\\Xml\\' => 10,
+ 'Sabre\\VObject\\' => 14,
+ 'Sabre\\Uri\\' => 10,
+ 'Sabre\\HTTP\\' => 11,
+ 'Sabre\\Event\\' => 12,
+ 'Sabre\\DAV\\' => 10,
+ 'Sabre\\DAVACL\\' => 13,
+ 'Sabre\\CardDAV\\' => 14,
+ 'Sabre\\CalDAV\\' => 13,
+ ),
+ 'P' =>
+ array (
+ 'Psr\\Log\\' => 8,
+ 'Psr\\Http\\Message\\' => 17,
+ ),
+ 'L' =>
+ array (
+ 'League\\Flysystem\\WebDAV\\' => 24,
+ 'League\\Flysystem\\' => 17,
+ ),
+ 'G' =>
+ array (
+ 'GuzzleHttp\\Psr7\\' => 16,
+ 'GuzzleHttp\\Promise\\' => 19,
+ 'GuzzleHttp\\' => 11,
+ 'GrahamCampbell\\GuzzleFactory\\' => 29,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'Symfony\\Polyfill\\Php72\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/symfony/polyfill-php72',
+ ),
+ 'Symfony\\Polyfill\\Mbstring\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
+ ),
+ 'Symfony\\Polyfill\\Intl\\Idn\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-idn',
+ ),
+ 'Symfony\\Component\\Process\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/symfony/process',
+ ),
+ 'Symfony\\Component\\Finder\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/symfony/finder',
+ ),
+ 'Spatie\\TemporaryDirectory\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/spatie/temporary-directory/src',
+ ),
+ 'Spatie\\FlysystemDropbox\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/spatie/flysystem-dropbox/src',
+ ),
+ 'Spatie\\Dropbox\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/spatie/dropbox-api/src',
+ ),
+ 'Spatie\\DbDumper\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/spatie/db-dumper/src',
+ ),
+ 'Spatie\\Backup\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/spatie/laravel-backup/src',
+ ),
+ 'Sabre\\Xml\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/sabre/xml/lib',
+ ),
+ 'Sabre\\VObject\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/sabre/vobject/lib',
+ ),
+ 'Sabre\\Uri\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/sabre/uri/lib',
+ ),
+ 'Sabre\\HTTP\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/sabre/http/lib',
+ ),
+ 'Sabre\\Event\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/sabre/event/lib',
+ ),
+ 'Sabre\\DAV\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/sabre/dav/lib/DAV',
+ ),
+ 'Sabre\\DAVACL\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL',
+ ),
+ 'Sabre\\CardDAV\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV',
+ ),
+ 'Sabre\\CalDAV\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV',
+ ),
+ 'Psr\\Log\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
+ ),
+ 'Psr\\Http\\Message\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/psr/http-message/src',
+ ),
+ 'League\\Flysystem\\WebDAV\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/league/flysystem-webdav/src',
+ ),
+ 'League\\Flysystem\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/league/flysystem/src',
+ ),
+ 'GuzzleHttp\\Psr7\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
+ ),
+ 'GuzzleHttp\\Promise\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/guzzlehttp/promises/src',
+ ),
+ 'GuzzleHttp\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src',
+ ),
+ 'GrahamCampbell\\GuzzleFactory\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/graham-campbell/guzzle-factory/src',
+ ),
+ );
+
+ public static function getInitializer(ClassLoader $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInitbe9348664e724b21a091b7dc2c806f56::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInitbe9348664e724b21a091b7dc2c806f56::$prefixDirsPsr4;
+
+ }, null, ClassLoader::class);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/composer/installed.json b/plugins/panakour/backup/vendor/composer/installed.json
new file mode 100644
index 000000000..e362f900b
--- /dev/null
+++ b/plugins/panakour/backup/vendor/composer/installed.json
@@ -0,0 +1,1524 @@
+[
+ {
+ "name": "graham-campbell/guzzle-factory",
+ "version": "v3.0.3",
+ "version_normalized": "3.0.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/GrahamCampbell/Guzzle-Factory.git",
+ "reference": "de49ec514107b43f3a770743421c9bb78d01d3e2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/GrahamCampbell/Guzzle-Factory/zipball/de49ec514107b43f3a770743421c9bb78d01d3e2",
+ "reference": "de49ec514107b43f3a770743421c9bb78d01d3e2",
+ "shasum": ""
+ },
+ "require": {
+ "guzzlehttp/guzzle": "^6.2",
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "graham-campbell/analyzer": "^2.4",
+ "phpunit/phpunit": "^6.5|^7.0|^8.0"
+ },
+ "time": "2020-04-13T13:12:06+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "GrahamCampbell\\GuzzleFactory\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "graham@alt-three.com"
+ }
+ ],
+ "description": "Provides A Simple Guzzle Factory With Good Defaults",
+ "keywords": [
+ "Graham Campbell",
+ "GrahamCampbell",
+ "Guzzle",
+ "Guzzle Factory",
+ "Guzzle-Factory",
+ "http"
+ ]
+ },
+ {
+ "name": "guzzlehttp/guzzle",
+ "version": "6.5.3",
+ "version_normalized": "6.5.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/guzzle.git",
+ "reference": "aab4ebd862aa7d04f01a4b51849d657db56d882e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/aab4ebd862aa7d04f01a4b51849d657db56d882e",
+ "reference": "aab4ebd862aa7d04f01a4b51849d657db56d882e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "guzzlehttp/promises": "^1.0",
+ "guzzlehttp/psr7": "^1.6.1",
+ "php": ">=5.5",
+ "symfony/polyfill-intl-idn": "^1.11"
+ },
+ "require-dev": {
+ "ext-curl": "*",
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
+ "psr/log": "^1.1"
+ },
+ "suggest": {
+ "psr/log": "Required for using the Log middleware"
+ },
+ "time": "2020-04-18T10:38:46+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.5-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Guzzle is a PHP HTTP client library",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": [
+ "client",
+ "curl",
+ "framework",
+ "http",
+ "http client",
+ "rest",
+ "web service"
+ ]
+ },
+ {
+ "name": "guzzlehttp/promises",
+ "version": "v1.3.1",
+ "version_normalized": "1.3.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/promises.git",
+ "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+ "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.0"
+ },
+ "time": "2016-12-20T10:07:11+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Promise\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Guzzle promises library",
+ "keywords": [
+ "promise"
+ ]
+ },
+ {
+ "name": "guzzlehttp/psr7",
+ "version": "1.6.1",
+ "version_normalized": "1.6.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/psr7.git",
+ "reference": "239400de7a173fe9901b9ac7c06497751f00727a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a",
+ "reference": "239400de7a173fe9901b9ac7c06497751f00727a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0",
+ "psr/http-message": "~1.0",
+ "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
+ },
+ "provide": {
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "ext-zlib": "*",
+ "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
+ },
+ "suggest": {
+ "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses"
+ },
+ "time": "2019-07-01T23:21:34+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.6-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Tobias Schultze",
+ "homepage": "https://github.com/Tobion"
+ }
+ ],
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": [
+ "http",
+ "message",
+ "psr-7",
+ "request",
+ "response",
+ "stream",
+ "uri",
+ "url"
+ ]
+ },
+ {
+ "name": "league/flysystem",
+ "version": "1.0.67",
+ "version_normalized": "1.0.67.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/flysystem.git",
+ "reference": "5b1f36c75c4bdde981294c2a0ebdb437ee6f275e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/5b1f36c75c4bdde981294c2a0ebdb437ee6f275e",
+ "reference": "5b1f36c75c4bdde981294c2a0ebdb437ee6f275e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-fileinfo": "*",
+ "php": ">=5.5.9"
+ },
+ "conflict": {
+ "league/flysystem-sftp": "<1.0.6"
+ },
+ "require-dev": {
+ "phpspec/phpspec": "^3.4",
+ "phpunit/phpunit": "^5.7.26"
+ },
+ "suggest": {
+ "ext-fileinfo": "Required for MimeType",
+ "ext-ftp": "Allows you to use FTP server storage",
+ "ext-openssl": "Allows you to use FTPS server storage",
+ "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2",
+ "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3",
+ "league/flysystem-azure": "Allows you to use Windows Azure Blob storage",
+ "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching",
+ "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem",
+ "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files",
+ "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib",
+ "league/flysystem-webdav": "Allows you to use WebDAV storage",
+ "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter",
+ "spatie/flysystem-dropbox": "Allows you to use Dropbox storage",
+ "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications"
+ },
+ "time": "2020-04-16T13:21:26+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "League\\Flysystem\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Frank de Jonge",
+ "email": "info@frenky.net"
+ }
+ ],
+ "description": "Filesystem abstraction: Many filesystems, one API.",
+ "keywords": [
+ "Cloud Files",
+ "WebDAV",
+ "abstraction",
+ "aws",
+ "cloud",
+ "copy.com",
+ "dropbox",
+ "file systems",
+ "files",
+ "filesystem",
+ "filesystems",
+ "ftp",
+ "rackspace",
+ "remote",
+ "s3",
+ "sftp",
+ "storage"
+ ]
+ },
+ {
+ "name": "league/flysystem-webdav",
+ "version": "1.0.9",
+ "version_normalized": "1.0.9.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/flysystem-webdav.git",
+ "reference": "b5c3c756e60cbd495173ce09c0a68a858803f4ce"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/flysystem-webdav/zipball/b5c3c756e60cbd495173ce09c0a68a858803f4ce",
+ "reference": "b5c3c756e60cbd495173ce09c0a68a858803f4ce",
+ "shasum": ""
+ },
+ "require": {
+ "league/flysystem": "~1.0",
+ "php": ">=5.6",
+ "sabre/dav": "~4.0|~3.1"
+ },
+ "require-dev": {
+ "mockery/mockery": "~1.2",
+ "phpunit/phpunit": "~4.8"
+ },
+ "time": "2019-12-13T22:44:03+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "League\\Flysystem\\WebDAV\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Frank de Jonge",
+ "email": "info@frenky.net"
+ }
+ ],
+ "description": "Flysystem adapter for WebDAV"
+ },
+ {
+ "name": "psr/http-message",
+ "version": "1.0.1",
+ "version_normalized": "1.0.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-message.git",
+ "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "time": "2016-08-06T14:39:51+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP messages",
+ "homepage": "https://github.com/php-fig/http-message",
+ "keywords": [
+ "http",
+ "http-message",
+ "psr",
+ "psr-7",
+ "request",
+ "response"
+ ]
+ },
+ {
+ "name": "psr/log",
+ "version": "1.1.3",
+ "version_normalized": "1.1.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc",
+ "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "time": "2020-03-23T09:12:05+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ]
+ },
+ {
+ "name": "ralouphie/getallheaders",
+ "version": "3.0.3",
+ "version_normalized": "3.0.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ralouphie/getallheaders.git",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.1",
+ "phpunit/phpunit": "^5 || ^6.5"
+ },
+ "time": "2019-03-08T08:55:37+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "src/getallheaders.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ralph Khattar",
+ "email": "ralph.khattar@gmail.com"
+ }
+ ],
+ "description": "A polyfill for getallheaders."
+ },
+ {
+ "name": "sabre/dav",
+ "version": "4.1.0",
+ "version_normalized": "4.1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sabre-io/dav.git",
+ "reference": "8f6f4d272504ee8424e1d0a47d6efc7772de2270"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sabre-io/dav/zipball/8f6f4d272504ee8424e1d0a47d6efc7772de2270",
+ "reference": "8f6f4d272504ee8424e1d0a47d6efc7772de2270",
+ "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",
+ "psr/log": "^1.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": {
+ "evert/phpdoc-md": "~0.1.0",
+ "friendsofphp/php-cs-fixer": "~2.16.1",
+ "monolog/monolog": "^1.18",
+ "phpstan/phpstan": "^0.12",
+ "phpunit/phpunit": "^7.5 || ^8.5 || ^9.0"
+ },
+ "suggest": {
+ "ext-curl": "*",
+ "ext-imap": "*",
+ "ext-pdo": "*"
+ },
+ "time": "2020-03-20T08:55:46+00:00",
+ "bin": [
+ "bin/sabredav",
+ "bin/naturalselection"
+ ],
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Sabre\\DAV\\": "lib/DAV/",
+ "Sabre\\DAVACL\\": "lib/DAVACL/",
+ "Sabre\\CalDAV\\": "lib/CalDAV/",
+ "Sabre\\CardDAV\\": "lib/CardDAV/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Evert Pot",
+ "email": "me@evertpot.com",
+ "homepage": "http://evertpot.com/",
+ "role": "Developer"
+ }
+ ],
+ "description": "WebDAV Framework for PHP",
+ "homepage": "http://sabre.io/",
+ "keywords": [
+ "CalDAV",
+ "CardDAV",
+ "WebDAV",
+ "framework",
+ "iCalendar"
+ ]
+ },
+ {
+ "name": "sabre/event",
+ "version": "5.1.0",
+ "version_normalized": "5.1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sabre-io/event.git",
+ "reference": "d00a17507af0e7544cfe17096372f5d733e3b276"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sabre-io/event/zipball/d00a17507af0e7544cfe17096372f5d733e3b276",
+ "reference": "d00a17507af0e7544cfe17096372f5d733e3b276",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~2.16.1",
+ "phpunit/phpunit": "^7 || ^8"
+ },
+ "time": "2020-01-31T18:52:29+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Sabre\\Event\\": "lib/"
+ },
+ "files": [
+ "lib/coroutine.php",
+ "lib/Loop/functions.php",
+ "lib/Promise/functions.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Evert Pot",
+ "email": "me@evertpot.com",
+ "homepage": "http://evertpot.com/",
+ "role": "Developer"
+ }
+ ],
+ "description": "sabre/event is a library for lightweight event-based programming",
+ "homepage": "http://sabre.io/event/",
+ "keywords": [
+ "EventEmitter",
+ "async",
+ "coroutine",
+ "eventloop",
+ "events",
+ "hooks",
+ "plugin",
+ "promise",
+ "reactor",
+ "signal"
+ ]
+ },
+ {
+ "name": "sabre/http",
+ "version": "5.1.0",
+ "version_normalized": "5.1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sabre-io/http.git",
+ "reference": "23446999f1f6e62892bbd89745070aa902dd3539"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sabre-io/http/zipball/23446999f1f6e62892bbd89745070aa902dd3539",
+ "reference": "23446999f1f6e62892bbd89745070aa902dd3539",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-curl": "*",
+ "ext-mbstring": "*",
+ "php": "^7.1",
+ "sabre/event": ">=4.0 <6.0",
+ "sabre/uri": "^2.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~2.16.1",
+ "phpunit/phpunit": "^7.0 || ^8.0"
+ },
+ "suggest": {
+ "ext-curl": " to make http requests with the Client class"
+ },
+ "time": "2020-01-31T20:07:09+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"
+ ]
+ },
+ {
+ "name": "sabre/uri",
+ "version": "2.2.0",
+ "version_normalized": "2.2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sabre-io/uri.git",
+ "reference": "059d11012603be2e32ddb7543602965563ddbb09"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sabre-io/uri/zipball/059d11012603be2e32ddb7543602965563ddbb09",
+ "reference": "059d11012603be2e32ddb7543602965563ddbb09",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~2.16.1",
+ "phpunit/phpunit": "^7 || ^8"
+ },
+ "time": "2020-01-31T18:53:43+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"
+ ]
+ },
+ {
+ "name": "sabre/vobject",
+ "version": "4.3.0",
+ "version_normalized": "4.3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sabre-io/vobject.git",
+ "reference": "5b2248d965160f93053f3a24704794a13a22a1bb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sabre-io/vobject/zipball/5b2248d965160f93053f3a24704794a13a22a1bb",
+ "reference": "5b2248d965160f93053f3a24704794a13a22a1bb",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "php": "^7.1",
+ "sabre/xml": "^2.1"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~2.16.1",
+ "phpunit/phpunit": "^7 || ^8"
+ },
+ "suggest": {
+ "hoa/bench": "If you would like to run the benchmark scripts"
+ },
+ "time": "2020-01-31T18:50:58+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"
+ ]
+ },
+ {
+ "name": "sabre/xml",
+ "version": "2.2.0",
+ "version_normalized": "2.2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sabre-io/xml.git",
+ "reference": "705f5cbf7f4fb1e3dd47173e3f026892818c8d46"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sabre-io/xml/zipball/705f5cbf7f4fb1e3dd47173e3f026892818c8d46",
+ "reference": "705f5cbf7f4fb1e3dd47173e3f026892818c8d46",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-xmlreader": "*",
+ "ext-xmlwriter": "*",
+ "lib-libxml": ">=2.6.20",
+ "php": "^7.1",
+ "sabre/uri": ">=1.0,<3.0.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~2.16.1",
+ "phpunit/phpunit": "^7 || ^8"
+ },
+ "time": "2020-01-31T18:52:58+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Sabre\\Xml\\": "lib/"
+ },
+ "files": [
+ "lib/Deserializer/functions.php",
+ "lib/Serializer/functions.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Evert Pot",
+ "email": "me@evertpot.com",
+ "homepage": "http://evertpot.com/",
+ "role": "Developer"
+ },
+ {
+ "name": "Markus Staab",
+ "email": "markus.staab@redaxo.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "sabre/xml is an XML library that you may not hate.",
+ "homepage": "https://sabre.io/xml/",
+ "keywords": [
+ "XMLReader",
+ "XMLWriter",
+ "dom",
+ "xml"
+ ]
+ },
+ {
+ "name": "spatie/db-dumper",
+ "version": "2.13.1",
+ "version_normalized": "2.13.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/db-dumper.git",
+ "reference": "8f88e6f772ddf6c5a71ec9c0b5682ebca3323377"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/db-dumper/zipball/8f88e6f772ddf6c5a71ec9c0b5682ebca3323377",
+ "reference": "8f88e6f772ddf6c5a71ec9c0b5682ebca3323377",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0",
+ "symfony/process": "^3.0|^4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "time": "2019-03-01T15:46:17+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\DbDumper\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Dump databases",
+ "homepage": "https://github.com/spatie/db-dumper",
+ "keywords": [
+ "database",
+ "db-dumper",
+ "dump",
+ "mysqldump",
+ "spatie"
+ ]
+ },
+ {
+ "name": "spatie/dropbox-api",
+ "version": "1.12.0",
+ "version_normalized": "1.12.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/dropbox-api.git",
+ "reference": "f599374697e7fefa2b0d64ca25ad7079abf91970"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/dropbox-api/zipball/f599374697e7fefa2b0d64ca25ad7079abf91970",
+ "reference": "f599374697e7fefa2b0d64ca25ad7079abf91970",
+ "shasum": ""
+ },
+ "require": {
+ "graham-campbell/guzzle-factory": "^3.0",
+ "guzzlehttp/guzzle": "^6.2",
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.5.15|^8.5"
+ },
+ "time": "2020-02-04T07:32:05+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\Dropbox\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Alex Vanderbist",
+ "email": "alex.vanderbist@gmail.com",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ },
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "A minimal implementation of Dropbox API v2",
+ "homepage": "https://github.com/spatie/dropbox-api",
+ "keywords": [
+ "Dropbox-API",
+ "api",
+ "dropbox",
+ "spatie",
+ "v2"
+ ]
+ },
+ {
+ "name": "spatie/flysystem-dropbox",
+ "version": "1.2.2",
+ "version_normalized": "1.2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/flysystem-dropbox.git",
+ "reference": "512e8d59b3f9b8a6710f932c421032cb490e9869"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/flysystem-dropbox/zipball/512e8d59b3f9b8a6710f932c421032cb490e9869",
+ "reference": "512e8d59b3f9b8a6710f932c421032cb490e9869",
+ "shasum": ""
+ },
+ "require": {
+ "league/flysystem": "^1.0.20",
+ "php": "^7.0",
+ "spatie/dropbox-api": "^1.1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "time": "2019-12-04T08:18:17+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\FlysystemDropbox\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Alex Vanderbist",
+ "email": "alex.vanderbist@gmail.com",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Flysystem Adapter for the Dropbox v2 API",
+ "homepage": "https://github.com/spatie/flysystem-dropbox",
+ "keywords": [
+ "Flysystem",
+ "api",
+ "dropbox",
+ "flysystem-dropbox",
+ "spatie",
+ "v2"
+ ]
+ },
+ {
+ "name": "spatie/laravel-backup",
+ "version": "5.12.1",
+ "version_normalized": "5.12.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/laravel-backup.git",
+ "reference": "553562557ef13fda0e823cc609cd7d4f2c4f2552"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/laravel-backup/zipball/553562557ef13fda0e823cc609cd7d4f2c4f2552",
+ "reference": "553562557ef13fda0e823cc609cd7d4f2c4f2552",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/console": "~5.5.0|~5.6.0|~5.7.0|~5.8.0",
+ "illuminate/contracts": "~5.5.0|~5.6.0|~5.7.0|~5.8.0",
+ "illuminate/events": "~5.5.0|~5.6.0|~5.7.0|~5.8.0",
+ "illuminate/filesystem": "~5.5.0|~5.6.0|~5.7.0|~5.8.0",
+ "illuminate/notifications": "~5.5.0|~5.6.0|~5.7.0|~5.8.0",
+ "illuminate/support": "~5.5.0|~5.6.0|~5.7.0|~5.8.0",
+ "league/flysystem": "^1.0.27",
+ "php": "^7.1",
+ "spatie/db-dumper": "^2.11.1",
+ "spatie/temporary-directory": "^1.1",
+ "symfony/finder": "^3.3|^4.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.0",
+ "orchestra/testbench": "~3.5.0|~3.6.0|~3.7.0|~3.8.0",
+ "phpunit/phpunit": "^7.3"
+ },
+ "suggest": {
+ "guzzlehttp/guzzle": "Allows notifications to be sent via Slack"
+ },
+ "time": "2019-04-04T12:00:30+00:00",
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Spatie\\Backup\\BackupServiceProvider"
+ ]
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\Backup\\": "src"
+ },
+ "files": [
+ "src/Helpers/functions.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "A Laravel 5 package to backup your application",
+ "homepage": "https://github.com/spatie/laravel-backup",
+ "keywords": [
+ "backup",
+ "database",
+ "laravel-backup",
+ "spatie"
+ ]
+ },
+ {
+ "name": "spatie/temporary-directory",
+ "version": "1.2.2",
+ "version_normalized": "1.2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/temporary-directory.git",
+ "reference": "fcb127e615700751dac2aefee0ea2808ff3f5bb1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/fcb127e615700751dac2aefee0ea2808ff3f5bb1",
+ "reference": "fcb127e615700751dac2aefee0ea2808ff3f5bb1",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.0"
+ },
+ "time": "2019-12-15T18:52:09+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\TemporaryDirectory\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Alex Vanderbist",
+ "email": "alex@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Easily create, use and destroy temporary directories",
+ "homepage": "https://github.com/spatie/temporary-directory",
+ "keywords": [
+ "spatie",
+ "temporary-directory"
+ ]
+ },
+ {
+ "name": "symfony/finder",
+ "version": "v4.4.7",
+ "version_normalized": "4.4.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/finder.git",
+ "reference": "5729f943f9854c5781984ed4907bbb817735776b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/5729f943f9854c5781984ed4907bbb817735776b",
+ "reference": "5729f943f9854c5781984ed4907bbb817735776b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3"
+ },
+ "time": "2020-03-27T16:54:36+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.4-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Finder\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Finder Component",
+ "homepage": "https://symfony.com"
+ },
+ {
+ "name": "symfony/polyfill-intl-idn",
+ "version": "v1.15.0",
+ "version_normalized": "1.15.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-idn.git",
+ "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf",
+ "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "symfony/polyfill-mbstring": "^1.3",
+ "symfony/polyfill-php72": "^1.10"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "time": "2020-03-09T19:04:49+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.15-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Idn\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Laurent Bassin",
+ "email": "laurent@bassin.info"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "idn",
+ "intl",
+ "polyfill",
+ "portable",
+ "shim"
+ ]
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.15.0",
+ "version_normalized": "1.15.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/81ffd3a9c6d707be22e3012b827de1c9775fc5ac",
+ "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "time": "2020-03-09T19:04:49+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.15-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ]
+ },
+ {
+ "name": "symfony/polyfill-php72",
+ "version": "v1.15.0",
+ "version_normalized": "1.15.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php72.git",
+ "reference": "37b0976c78b94856543260ce09b460a7bc852747"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/37b0976c78b94856543260ce09b460a7bc852747",
+ "reference": "37b0976c78b94856543260ce09b460a7bc852747",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "time": "2020-02-27T09:26:54+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.15-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Php72\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ]
+ },
+ {
+ "name": "symfony/process",
+ "version": "v3.4.39",
+ "version_normalized": "3.4.39.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "1dbc09f6e14703ae2398efc86b02ae2bcd9a9931"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/1dbc09f6e14703ae2398efc86b02ae2bcd9a9931",
+ "reference": "1dbc09f6e14703ae2398efc86b02ae2bcd9a9931",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5.9|>=7.0.8"
+ },
+ "time": "2020-03-20T06:07:50+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Process Component",
+ "homepage": "https://symfony.com"
+ }
+]
diff --git a/plugins/panakour/backup/vendor/graham-campbell/guzzle-factory/LICENSE b/plugins/panakour/backup/vendor/graham-campbell/guzzle-factory/LICENSE
new file mode 100644
index 000000000..f158c8ae9
--- /dev/null
+++ b/plugins/panakour/backup/vendor/graham-campbell/guzzle-factory/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2017-2020 Graham Campbell
+
+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.
diff --git a/plugins/panakour/backup/vendor/graham-campbell/guzzle-factory/composer.json b/plugins/panakour/backup/vendor/graham-campbell/guzzle-factory/composer.json
new file mode 100644
index 000000000..a0d461e58
--- /dev/null
+++ b/plugins/panakour/backup/vendor/graham-campbell/guzzle-factory/composer.json
@@ -0,0 +1,40 @@
+{
+ "name": "graham-campbell/guzzle-factory",
+ "description": "Provides A Simple Guzzle Factory With Good Defaults",
+ "keywords": ["guzzle", "http", "guzzle factory", "guzzle-factory", "Guzzle", "Guzzle Factory", "Guzzle-Factory", "Graham Campbell", "GrahamCampbell"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "graham@alt-three.com"
+ }
+ ],
+ "require": {
+ "php": "^7.0",
+ "guzzlehttp/guzzle": "^6.2"
+ },
+ "require-dev": {
+ "graham-campbell/analyzer": "^2.4",
+ "phpunit/phpunit": "^6.5|^7.0|^8.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "GrahamCampbell\\GuzzleFactory\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "GrahamCampbell\\Tests\\GuzzleFactory\\": "tests/"
+ }
+ },
+ "config": {
+ "preferred-install": "dist"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true
+}
diff --git a/plugins/panakour/backup/vendor/graham-campbell/guzzle-factory/src/GuzzleFactory.php b/plugins/panakour/backup/vendor/graham-campbell/guzzle-factory/src/GuzzleFactory.php
new file mode 100644
index 000000000..42971b907
--- /dev/null
+++ b/plugins/panakour/backup/vendor/graham-campbell/guzzle-factory/src/GuzzleFactory.php
@@ -0,0 +1,97 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace GrahamCampbell\GuzzleFactory;
+
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\ConnectException;
+use GuzzleHttp\Exception\TransferException;
+use GuzzleHttp\HandlerStack;
+use GuzzleHttp\Middleware;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * This is the guzzle factory class.
+ *
+ * @author Graham Campbell
+ */
+final class GuzzleFactory
+{
+ /**
+ * The default connect timeout.
+ *
+ * @var int
+ */
+ const CONNECT_TIMEOUT = 10;
+
+ /**
+ * The default transport timeout.
+ *
+ * @var int
+ */
+ const TIMEOUT = 15;
+
+ /**
+ * The default backoff multiplier.
+ *
+ * @var int
+ */
+ const BACKOFF = 1000;
+
+ /**
+ * The default 4xx retry codes.
+ *
+ * @var int[]
+ */
+ const CODES = [429];
+
+ /**
+ * Create a new guzzle client.
+ *
+ * @param array $options
+ * @param int|null $backoff
+ * @param int[]|null $codes
+ *
+ * @return \GuzzleHttp\Client
+ */
+ public static function make(array $options = [], int $backoff = null, array $codes = null)
+ {
+ $config = array_merge(['connect_timeout' => self::CONNECT_TIMEOUT, 'timeout' => self::TIMEOUT], $options);
+ $config['handler'] = self::handler($backoff, $codes, $options['handler'] ?? null);
+
+ return new Client($config);
+ }
+
+ /**
+ * Create a new guzzle handler stack.
+ *
+ * @param int|null $backoff
+ * @param int[]|null $codes
+ * @param \GuzzleHttp\HandlerStack|null $stack
+ *
+ * @return \GuzzleHttp\HandlerStack
+ */
+ public static function handler(int $backoff = null, array $codes = null, HandlerStack $stack = null)
+ {
+ $stack = $stack ?: HandlerStack::create();
+
+ $stack->push(Middleware::retry(function ($retries, RequestInterface $request, ResponseInterface $response = null, TransferException $exception = null) use ($codes) {
+ return $retries < 3 && ($exception instanceof ConnectException || ($response && ($response->getStatusCode() >= 500 || in_array($response->getStatusCode(), $codes === null ? self::CODES : $codes, true))));
+ }, function ($retries) use ($backoff) {
+ return (int) pow(2, $retries) * ($backoff === null ? self::BACKOFF : $backoff);
+ }));
+
+ return $stack;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/CHANGELOG.md b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/CHANGELOG.md
new file mode 100644
index 000000000..a5cb9c1a8
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/CHANGELOG.md
@@ -0,0 +1,1327 @@
+# Change Log
+
+## 6.5.3 - 2020-04-18
+
+* Use Symfony intl-idn polyfill [#2550](https://github.com/guzzle/guzzle/pull/2550)
+* Remove use of internal functions [#2548](https://github.com/guzzle/guzzle/pull/2548)
+
+## 6.5.2 - 2019-12-23
+
+* idn_to_ascii() fix for old PHP versions [#2489](https://github.com/guzzle/guzzle/pull/2489)
+
+## 6.5.1 - 2019-12-21
+
+* Better defaults for PHP installations with old ICU lib [#2454](https://github.com/guzzle/guzzle/pull/2454)
+* IDN support for redirects [#2424](https://github.com/guzzle/guzzle/pull/2424)
+
+## 6.5.0 - 2019-12-07
+
+* Improvement: Added support for reset internal queue in MockHandler. [#2143](https://github.com/guzzle/guzzle/pull/2143)
+* Improvement: Added support to pass arbitrary options to `curl_multi_init`. [#2287](https://github.com/guzzle/guzzle/pull/2287)
+* Fix: Gracefully handle passing `null` to the `header` option. [#2132](https://github.com/guzzle/guzzle/pull/2132)
+* Fix: `RetryMiddleware` did not do exponential delay between retires due unit mismatch. [#2132](https://github.com/guzzle/guzzle/pull/2132)
+* Fix: Prevent undefined offset when using array for ssl_key options. [#2348](https://github.com/guzzle/guzzle/pull/2348)
+* Deprecated `ClientInterface::VERSION`
+
+## 6.4.1 - 2019-10-23
+
+* No `guzzle.phar` was created in 6.4.0 due expired API token. This release will fix that
+* Added `parent::__construct()` to `FileCookieJar` and `SessionCookieJar`
+
+## 6.4.0 - 2019-10-23
+
+* Improvement: Improved error messages when using curl < 7.21.2 [#2108](https://github.com/guzzle/guzzle/pull/2108)
+* Fix: Test if response is readable before returning a summary in `RequestException::getResponseBodySummary()` [#2081](https://github.com/guzzle/guzzle/pull/2081)
+* Fix: Add support for GUZZLE_CURL_SELECT_TIMEOUT environment variable [#2161](https://github.com/guzzle/guzzle/pull/2161)
+* Improvement: Added `GuzzleHttp\Exception\InvalidArgumentException` [#2163](https://github.com/guzzle/guzzle/pull/2163)
+* Improvement: Added `GuzzleHttp\_current_time()` to use `hrtime()` if that function exists. [#2242](https://github.com/guzzle/guzzle/pull/2242)
+* Improvement: Added curl's `appconnect_time` in `TransferStats` [#2284](https://github.com/guzzle/guzzle/pull/2284)
+* Improvement: Make GuzzleException extend Throwable wherever it's available [#2273](https://github.com/guzzle/guzzle/pull/2273)
+* Fix: Prevent concurrent writes to file when saving `CookieJar` [#2335](https://github.com/guzzle/guzzle/pull/2335)
+* Improvement: Update `MockHandler` so we can test transfer time [#2362](https://github.com/guzzle/guzzle/pull/2362)
+
+## 6.3.3 - 2018-04-22
+
+* Fix: Default headers when decode_content is specified
+
+
+## 6.3.2 - 2018-03-26
+
+* Fix: Release process
+
+
+## 6.3.1 - 2018-03-26
+
+* Bug fix: Parsing 0 epoch expiry times in cookies [#2014](https://github.com/guzzle/guzzle/pull/2014)
+* Improvement: Better ConnectException detection [#2012](https://github.com/guzzle/guzzle/pull/2012)
+* Bug fix: Malformed domain that contains a "/" [#1999](https://github.com/guzzle/guzzle/pull/1999)
+* Bug fix: Undefined offset when a cookie has no first key-value pair [#1998](https://github.com/guzzle/guzzle/pull/1998)
+* Improvement: Support PHPUnit 6 [#1953](https://github.com/guzzle/guzzle/pull/1953)
+* Bug fix: Support empty headers [#1915](https://github.com/guzzle/guzzle/pull/1915)
+* Bug fix: Ignore case during header modifications [#1916](https://github.com/guzzle/guzzle/pull/1916)
+
++ Minor code cleanups, documentation fixes and clarifications.
+
+
+## 6.3.0 - 2017-06-22
+
+* Feature: force IP resolution (ipv4 or ipv6) [#1608](https://github.com/guzzle/guzzle/pull/1608), [#1659](https://github.com/guzzle/guzzle/pull/1659)
+* Improvement: Don't include summary in exception message when body is empty [#1621](https://github.com/guzzle/guzzle/pull/1621)
+* Improvement: Handle `on_headers` option in MockHandler [#1580](https://github.com/guzzle/guzzle/pull/1580)
+* Improvement: Added SUSE Linux CA path [#1609](https://github.com/guzzle/guzzle/issues/1609)
+* Improvement: Use class reference for getting the name of the class instead of using hardcoded strings [#1641](https://github.com/guzzle/guzzle/pull/1641)
+* Feature: Added `read_timeout` option [#1611](https://github.com/guzzle/guzzle/pull/1611)
+* Bug fix: PHP 7.x fixes [#1685](https://github.com/guzzle/guzzle/pull/1685), [#1686](https://github.com/guzzle/guzzle/pull/1686), [#1811](https://github.com/guzzle/guzzle/pull/1811)
+* Deprecation: BadResponseException instantiation without a response [#1642](https://github.com/guzzle/guzzle/pull/1642)
+* Feature: Added NTLM auth [#1569](https://github.com/guzzle/guzzle/pull/1569)
+* Feature: Track redirect HTTP status codes [#1711](https://github.com/guzzle/guzzle/pull/1711)
+* Improvement: Check handler type during construction [#1745](https://github.com/guzzle/guzzle/pull/1745)
+* Improvement: Always include the Content-Length if there's a body [#1721](https://github.com/guzzle/guzzle/pull/1721)
+* Feature: Added convenience method to access a cookie by name [#1318](https://github.com/guzzle/guzzle/pull/1318)
+* Bug fix: Fill `CURLOPT_CAPATH` and `CURLOPT_CAINFO` properly [#1684](https://github.com/guzzle/guzzle/pull/1684)
+* Improvement: Use `\GuzzleHttp\Promise\rejection_for` function instead of object init [#1827](https://github.com/guzzle/guzzle/pull/1827)
+
+
++ Minor code cleanups, documentation fixes and clarifications.
+
+## 6.2.3 - 2017-02-28
+
+* Fix deprecations with guzzle/psr7 version 1.4
+
+## 6.2.2 - 2016-10-08
+
+* Allow to pass nullable Response to delay callable
+* Only add scheme when host is present
+* Fix drain case where content-length is the literal string zero
+* Obfuscate in-URL credentials in exceptions
+
+## 6.2.1 - 2016-07-18
+
+* Address HTTP_PROXY security vulnerability, CVE-2016-5385:
+ https://httpoxy.org/
+* Fixing timeout bug with StreamHandler:
+ https://github.com/guzzle/guzzle/pull/1488
+* Only read up to `Content-Length` in PHP StreamHandler to avoid timeouts when
+ a server does not honor `Connection: close`.
+* Ignore URI fragment when sending requests.
+
+## 6.2.0 - 2016-03-21
+
+* Feature: added `GuzzleHttp\json_encode` and `GuzzleHttp\json_decode`.
+ https://github.com/guzzle/guzzle/pull/1389
+* Bug fix: Fix sleep calculation when waiting for delayed requests.
+ https://github.com/guzzle/guzzle/pull/1324
+* Feature: More flexible history containers.
+ https://github.com/guzzle/guzzle/pull/1373
+* Bug fix: defer sink stream opening in StreamHandler.
+ https://github.com/guzzle/guzzle/pull/1377
+* Bug fix: do not attempt to escape cookie values.
+ https://github.com/guzzle/guzzle/pull/1406
+* Feature: report original content encoding and length on decoded responses.
+ https://github.com/guzzle/guzzle/pull/1409
+* Bug fix: rewind seekable request bodies before dispatching to cURL.
+ https://github.com/guzzle/guzzle/pull/1422
+* Bug fix: provide an empty string to `http_build_query` for HHVM workaround.
+ https://github.com/guzzle/guzzle/pull/1367
+
+## 6.1.1 - 2015-11-22
+
+* Bug fix: Proxy::wrapSync() now correctly proxies to the appropriate handler
+ https://github.com/guzzle/guzzle/commit/911bcbc8b434adce64e223a6d1d14e9a8f63e4e4
+* Feature: HandlerStack is now more generic.
+ https://github.com/guzzle/guzzle/commit/f2102941331cda544745eedd97fc8fd46e1ee33e
+* Bug fix: setting verify to false in the StreamHandler now disables peer
+ verification. https://github.com/guzzle/guzzle/issues/1256
+* Feature: Middleware now uses an exception factory, including more error
+ context. https://github.com/guzzle/guzzle/pull/1282
+* Feature: better support for disabled functions.
+ https://github.com/guzzle/guzzle/pull/1287
+* Bug fix: fixed regression where MockHandler was not using `sink`.
+ https://github.com/guzzle/guzzle/pull/1292
+
+## 6.1.0 - 2015-09-08
+
+* Feature: Added the `on_stats` request option to provide access to transfer
+ statistics for requests. https://github.com/guzzle/guzzle/pull/1202
+* Feature: Added the ability to persist session cookies in CookieJars.
+ https://github.com/guzzle/guzzle/pull/1195
+* Feature: Some compatibility updates for Google APP Engine
+ https://github.com/guzzle/guzzle/pull/1216
+* Feature: Added support for NO_PROXY to prevent the use of a proxy based on
+ a simple set of rules. https://github.com/guzzle/guzzle/pull/1197
+* Feature: Cookies can now contain square brackets.
+ https://github.com/guzzle/guzzle/pull/1237
+* Bug fix: Now correctly parsing `=` inside of quotes in Cookies.
+ https://github.com/guzzle/guzzle/pull/1232
+* Bug fix: Cusotm cURL options now correctly override curl options of the
+ same name. https://github.com/guzzle/guzzle/pull/1221
+* Bug fix: Content-Type header is now added when using an explicitly provided
+ multipart body. https://github.com/guzzle/guzzle/pull/1218
+* Bug fix: Now ignoring Set-Cookie headers that have no name.
+* Bug fix: Reason phrase is no longer cast to an int in some cases in the
+ cURL handler. https://github.com/guzzle/guzzle/pull/1187
+* Bug fix: Remove the Authorization header when redirecting if the Host
+ header changes. https://github.com/guzzle/guzzle/pull/1207
+* Bug fix: Cookie path matching fixes
+ https://github.com/guzzle/guzzle/issues/1129
+* Bug fix: Fixing the cURL `body_as_string` setting
+ https://github.com/guzzle/guzzle/pull/1201
+* Bug fix: quotes are no longer stripped when parsing cookies.
+ https://github.com/guzzle/guzzle/issues/1172
+* Bug fix: `form_params` and `query` now always uses the `&` separator.
+ https://github.com/guzzle/guzzle/pull/1163
+* Bug fix: Adding a Content-Length to PHP stream wrapper requests if not set.
+ https://github.com/guzzle/guzzle/pull/1189
+
+## 6.0.2 - 2015-07-04
+
+* Fixed a memory leak in the curl handlers in which references to callbacks
+ were not being removed by `curl_reset`.
+* Cookies are now extracted properly before redirects.
+* Cookies now allow more character ranges.
+* Decoded Content-Encoding responses are now modified to correctly reflect
+ their state if the encoding was automatically removed by a handler. This
+ means that the `Content-Encoding` header may be removed an the
+ `Content-Length` modified to reflect the message size after removing the
+ encoding.
+* Added a more explicit error message when trying to use `form_params` and
+ `multipart` in the same request.
+* Several fixes for HHVM support.
+* Functions are now conditionally required using an additional level of
+ indirection to help with global Composer installations.
+
+## 6.0.1 - 2015-05-27
+
+* Fixed a bug with serializing the `query` request option where the `&`
+ separator was missing.
+* Added a better error message for when `body` is provided as an array. Please
+ use `form_params` or `multipart` instead.
+* Various doc fixes.
+
+## 6.0.0 - 2015-05-26
+
+* See the UPGRADING.md document for more information.
+* Added `multipart` and `form_params` request options.
+* Added `synchronous` request option.
+* Added the `on_headers` request option.
+* Fixed `expect` handling.
+* No longer adding default middlewares in the client ctor. These need to be
+ present on the provided handler in order to work.
+* Requests are no longer initiated when sending async requests with the
+ CurlMultiHandler. This prevents unexpected recursion from requests completing
+ while ticking the cURL loop.
+* Removed the semantics of setting `default` to `true`. This is no longer
+ required now that the cURL loop is not ticked for async requests.
+* Added request and response logging middleware.
+* No longer allowing self signed certificates when using the StreamHandler.
+* Ensuring that `sink` is valid if saving to a file.
+* Request exceptions now include a "handler context" which provides handler
+ specific contextual information.
+* Added `GuzzleHttp\RequestOptions` to allow request options to be applied
+ using constants.
+* `$maxHandles` has been removed from CurlMultiHandler.
+* `MultipartPostBody` is now part of the `guzzlehttp/psr7` package.
+
+## 5.3.0 - 2015-05-19
+
+* Mock now supports `save_to`
+* Marked `AbstractRequestEvent::getTransaction()` as public.
+* Fixed a bug in which multiple headers using different casing would overwrite
+ previous headers in the associative array.
+* Added `Utils::getDefaultHandler()`
+* Marked `GuzzleHttp\Client::getDefaultUserAgent` as deprecated.
+* URL scheme is now always lowercased.
+
+## 6.0.0-beta.1
+
+* Requires PHP >= 5.5
+* Updated to use PSR-7
+ * Requires immutable messages, which basically means an event based system
+ owned by a request instance is no longer possible.
+ * Utilizing the [Guzzle PSR-7 package](https://github.com/guzzle/psr7).
+ * Removed the dependency on `guzzlehttp/streams`. These stream abstractions
+ are available in the `guzzlehttp/psr7` package under the `GuzzleHttp\Psr7`
+ namespace.
+* Added middleware and handler system
+ * Replaced the Guzzle event and subscriber system with a middleware system.
+ * No longer depends on RingPHP, but rather places the HTTP handlers directly
+ in Guzzle, operating on PSR-7 messages.
+ * Retry logic is now encapsulated in `GuzzleHttp\Middleware::retry`, which
+ means the `guzzlehttp/retry-subscriber` is now obsolete.
+ * Mocking responses is now handled using `GuzzleHttp\Handler\MockHandler`.
+* Asynchronous responses
+ * No longer supports the `future` request option to send an async request.
+ Instead, use one of the `*Async` methods of a client (e.g., `requestAsync`,
+ `getAsync`, etc.).
+ * Utilizing `GuzzleHttp\Promise` instead of React's promise library to avoid
+ recursion required by chaining and forwarding react promises. See
+ https://github.com/guzzle/promises
+ * Added `requestAsync` and `sendAsync` to send request asynchronously.
+ * Added magic methods for `getAsync()`, `postAsync()`, etc. to send requests
+ asynchronously.
+* Request options
+ * POST and form updates
+ * Added the `form_fields` and `form_files` request options.
+ * Removed the `GuzzleHttp\Post` namespace.
+ * The `body` request option no longer accepts an array for POST requests.
+ * The `exceptions` request option has been deprecated in favor of the
+ `http_errors` request options.
+ * The `save_to` request option has been deprecated in favor of `sink` request
+ option.
+* Clients no longer accept an array of URI template string and variables for
+ URI variables. You will need to expand URI templates before passing them
+ into a client constructor or request method.
+* Client methods `get()`, `post()`, `put()`, `patch()`, `options()`, etc. are
+ now magic methods that will send synchronous requests.
+* Replaced `Utils.php` with plain functions in `functions.php`.
+* Removed `GuzzleHttp\Collection`.
+* Removed `GuzzleHttp\BatchResults`. Batched pool results are now returned as
+ an array.
+* Removed `GuzzleHttp\Query`. Query string handling is now handled using an
+ associative array passed into the `query` request option. The query string
+ is serialized using PHP's `http_build_query`. If you need more control, you
+ can pass the query string in as a string.
+* `GuzzleHttp\QueryParser` has been replaced with the
+ `GuzzleHttp\Psr7\parse_query`.
+
+## 5.2.0 - 2015-01-27
+
+* Added `AppliesHeadersInterface` to make applying headers to a request based
+ on the body more generic and not specific to `PostBodyInterface`.
+* Reduced the number of stack frames needed to send requests.
+* Nested futures are now resolved in the client rather than the RequestFsm
+* Finishing state transitions is now handled in the RequestFsm rather than the
+ RingBridge.
+* Added a guard in the Pool class to not use recursion for request retries.
+
+## 5.1.0 - 2014-12-19
+
+* Pool class no longer uses recursion when a request is intercepted.
+* The size of a Pool can now be dynamically adjusted using a callback.
+ See https://github.com/guzzle/guzzle/pull/943.
+* Setting a request option to `null` when creating a request with a client will
+ ensure that the option is not set. This allows you to overwrite default
+ request options on a per-request basis.
+ See https://github.com/guzzle/guzzle/pull/937.
+* Added the ability to limit which protocols are allowed for redirects by
+ specifying a `protocols` array in the `allow_redirects` request option.
+* Nested futures due to retries are now resolved when waiting for synchronous
+ responses. See https://github.com/guzzle/guzzle/pull/947.
+* `"0"` is now an allowed URI path. See
+ https://github.com/guzzle/guzzle/pull/935.
+* `Query` no longer typehints on the `$query` argument in the constructor,
+ allowing for strings and arrays.
+* Exceptions thrown in the `end` event are now correctly wrapped with Guzzle
+ specific exceptions if necessary.
+
+## 5.0.3 - 2014-11-03
+
+This change updates query strings so that they are treated as un-encoded values
+by default where the value represents an un-encoded value to send over the
+wire. A Query object then encodes the value before sending over the wire. This
+means that even value query string values (e.g., ":") are url encoded. This
+makes the Query class match PHP's http_build_query function. However, if you
+want to send requests over the wire using valid query string characters that do
+not need to be encoded, then you can provide a string to Url::setQuery() and
+pass true as the second argument to specify that the query string is a raw
+string that should not be parsed or encoded (unless a call to getQuery() is
+subsequently made, forcing the query-string to be converted into a Query
+object).
+
+## 5.0.2 - 2014-10-30
+
+* Added a trailing `\r\n` to multipart/form-data payloads. See
+ https://github.com/guzzle/guzzle/pull/871
+* Added a `GuzzleHttp\Pool::send()` convenience method to match the docs.
+* Status codes are now returned as integers. See
+ https://github.com/guzzle/guzzle/issues/881
+* No longer overwriting an existing `application/x-www-form-urlencoded` header
+ when sending POST requests, allowing for customized headers. See
+ https://github.com/guzzle/guzzle/issues/877
+* Improved path URL serialization.
+
+ * No longer double percent-encoding characters in the path or query string if
+ they are already encoded.
+ * Now properly encoding the supplied path to a URL object, instead of only
+ encoding ' ' and '?'.
+ * Note: This has been changed in 5.0.3 to now encode query string values by
+ default unless the `rawString` argument is provided when setting the query
+ string on a URL: Now allowing many more characters to be present in the
+ query string without being percent encoded. See http://tools.ietf.org/html/rfc3986#appendix-A
+
+## 5.0.1 - 2014-10-16
+
+Bugfix release.
+
+* Fixed an issue where connection errors still returned response object in
+ error and end events event though the response is unusable. This has been
+ corrected so that a response is not returned in the `getResponse` method of
+ these events if the response did not complete. https://github.com/guzzle/guzzle/issues/867
+* Fixed an issue where transfer statistics were not being populated in the
+ RingBridge. https://github.com/guzzle/guzzle/issues/866
+
+## 5.0.0 - 2014-10-12
+
+Adding support for non-blocking responses and some minor API cleanup.
+
+### New Features
+
+* Added support for non-blocking responses based on `guzzlehttp/guzzle-ring`.
+* Added a public API for creating a default HTTP adapter.
+* Updated the redirect plugin to be non-blocking so that redirects are sent
+ concurrently. Other plugins like this can now be updated to be non-blocking.
+* Added a "progress" event so that you can get upload and download progress
+ events.
+* Added `GuzzleHttp\Pool` which implements FutureInterface and transfers
+ requests concurrently using a capped pool size as efficiently as possible.
+* Added `hasListeners()` to EmitterInterface.
+* Removed `GuzzleHttp\ClientInterface::sendAll` and marked
+ `GuzzleHttp\Client::sendAll` as deprecated (it's still there, just not the
+ recommended way).
+
+### Breaking changes
+
+The breaking changes in this release are relatively minor. The biggest thing to
+look out for is that request and response objects no longer implement fluent
+interfaces.
+
+* Removed the fluent interfaces (i.e., `return $this`) from requests,
+ responses, `GuzzleHttp\Collection`, `GuzzleHttp\Url`,
+ `GuzzleHttp\Query`, `GuzzleHttp\Post\PostBody`, and
+ `GuzzleHttp\Cookie\SetCookie`. This blog post provides a good outline of
+ why I did this: http://ocramius.github.io/blog/fluent-interfaces-are-evil/.
+ This also makes the Guzzle message interfaces compatible with the current
+ PSR-7 message proposal.
+* Removed "functions.php", so that Guzzle is truly PSR-4 compliant. Except
+ for the HTTP request functions from function.php, these functions are now
+ implemented in `GuzzleHttp\Utils` using camelCase. `GuzzleHttp\json_decode`
+ moved to `GuzzleHttp\Utils::jsonDecode`. `GuzzleHttp\get_path` moved to
+ `GuzzleHttp\Utils::getPath`. `GuzzleHttp\set_path` moved to
+ `GuzzleHttp\Utils::setPath`. `GuzzleHttp\batch` should now be
+ `GuzzleHttp\Pool::batch`, which returns an `objectStorage`. Using functions.php
+ caused problems for many users: they aren't PSR-4 compliant, require an
+ explicit include, and needed an if-guard to ensure that the functions are not
+ declared multiple times.
+* Rewrote adapter layer.
+ * Removing all classes from `GuzzleHttp\Adapter`, these are now
+ implemented as callables that are stored in `GuzzleHttp\Ring\Client`.
+ * Removed the concept of "parallel adapters". Sending requests serially or
+ concurrently is now handled using a single adapter.
+ * Moved `GuzzleHttp\Adapter\Transaction` to `GuzzleHttp\Transaction`. The
+ Transaction object now exposes the request, response, and client as public
+ properties. The getters and setters have been removed.
+* Removed the "headers" event. This event was only useful for changing the
+ body a response once the headers of the response were known. You can implement
+ a similar behavior in a number of ways. One example might be to use a
+ FnStream that has access to the transaction being sent. For example, when the
+ first byte is written, you could check if the response headers match your
+ expectations, and if so, change the actual stream body that is being
+ written to.
+* Removed the `asArray` parameter from
+ `GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header
+ value as an array, then use the newly added `getHeaderAsArray()` method of
+ `MessageInterface`. This change makes the Guzzle interfaces compatible with
+ the PSR-7 interfaces.
+* `GuzzleHttp\Message\MessageFactory` no longer allows subclasses to add
+ custom request options using double-dispatch (this was an implementation
+ detail). Instead, you should now provide an associative array to the
+ constructor which is a mapping of the request option name mapping to a
+ function that applies the option value to a request.
+* Removed the concept of "throwImmediately" from exceptions and error events.
+ This control mechanism was used to stop a transfer of concurrent requests
+ from completing. This can now be handled by throwing the exception or by
+ cancelling a pool of requests or each outstanding future request individually.
+* Updated to "GuzzleHttp\Streams" 3.0.
+ * `GuzzleHttp\Stream\StreamInterface::getContents()` no longer accepts a
+ `maxLen` parameter. This update makes the Guzzle streams project
+ compatible with the current PSR-7 proposal.
+ * `GuzzleHttp\Stream\Stream::__construct`,
+ `GuzzleHttp\Stream\Stream::factory`, and
+ `GuzzleHttp\Stream\Utils::create` no longer accept a size in the second
+ argument. They now accept an associative array of options, including the
+ "size" key and "metadata" key which can be used to provide custom metadata.
+
+## 4.2.2 - 2014-09-08
+
+* Fixed a memory leak in the CurlAdapter when reusing cURL handles.
+* No longer using `request_fulluri` in stream adapter proxies.
+* Relative redirects are now based on the last response, not the first response.
+
+## 4.2.1 - 2014-08-19
+
+* Ensuring that the StreamAdapter does not always add a Content-Type header
+* Adding automated github releases with a phar and zip
+
+## 4.2.0 - 2014-08-17
+
+* Now merging in default options using a case-insensitive comparison.
+ Closes https://github.com/guzzle/guzzle/issues/767
+* Added the ability to automatically decode `Content-Encoding` response bodies
+ using the `decode_content` request option. This is set to `true` by default
+ to decode the response body if it comes over the wire with a
+ `Content-Encoding`. Set this value to `false` to disable decoding the
+ response content, and pass a string to provide a request `Accept-Encoding`
+ header and turn on automatic response decoding. This feature now allows you
+ to pass an `Accept-Encoding` header in the headers of a request but still
+ disable automatic response decoding.
+ Closes https://github.com/guzzle/guzzle/issues/764
+* Added the ability to throw an exception immediately when transferring
+ requests in parallel. Closes https://github.com/guzzle/guzzle/issues/760
+* Updating guzzlehttp/streams dependency to ~2.1
+* No longer utilizing the now deprecated namespaced methods from the stream
+ package.
+
+## 4.1.8 - 2014-08-14
+
+* Fixed an issue in the CurlFactory that caused setting the `stream=false`
+ request option to throw an exception.
+ See: https://github.com/guzzle/guzzle/issues/769
+* TransactionIterator now calls rewind on the inner iterator.
+ See: https://github.com/guzzle/guzzle/pull/765
+* You can now set the `Content-Type` header to `multipart/form-data`
+ when creating POST requests to force multipart bodies.
+ See https://github.com/guzzle/guzzle/issues/768
+
+## 4.1.7 - 2014-08-07
+
+* Fixed an error in the HistoryPlugin that caused the same request and response
+ to be logged multiple times when an HTTP protocol error occurs.
+* Ensuring that cURL does not add a default Content-Type when no Content-Type
+ has been supplied by the user. This prevents the adapter layer from modifying
+ the request that is sent over the wire after any listeners may have already
+ put the request in a desired state (e.g., signed the request).
+* Throwing an exception when you attempt to send requests that have the
+ "stream" set to true in parallel using the MultiAdapter.
+* Only calling curl_multi_select when there are active cURL handles. This was
+ previously changed and caused performance problems on some systems due to PHP
+ always selecting until the maximum select timeout.
+* Fixed a bug where multipart/form-data POST fields were not correctly
+ aggregated (e.g., values with "&").
+
+## 4.1.6 - 2014-08-03
+
+* Added helper methods to make it easier to represent messages as strings,
+ including getting the start line and getting headers as a string.
+
+## 4.1.5 - 2014-08-02
+
+* Automatically retrying cURL "Connection died, retrying a fresh connect"
+ errors when possible.
+* cURL implementation cleanup
+* Allowing multiple event subscriber listeners to be registered per event by
+ passing an array of arrays of listener configuration.
+
+## 4.1.4 - 2014-07-22
+
+* Fixed a bug that caused multi-part POST requests with more than one field to
+ serialize incorrectly.
+* Paths can now be set to "0"
+* `ResponseInterface::xml` now accepts a `libxml_options` option and added a
+ missing default argument that was required when parsing XML response bodies.
+* A `save_to` stream is now created lazily, which means that files are not
+ created on disk unless a request succeeds.
+
+## 4.1.3 - 2014-07-15
+
+* Various fixes to multipart/form-data POST uploads
+* Wrapping function.php in an if-statement to ensure Guzzle can be used
+ globally and in a Composer install
+* Fixed an issue with generating and merging in events to an event array
+* POST headers are only applied before sending a request to allow you to change
+ the query aggregator used before uploading
+* Added much more robust query string parsing
+* Fixed various parsing and normalization issues with URLs
+* Fixing an issue where multi-valued headers were not being utilized correctly
+ in the StreamAdapter
+
+## 4.1.2 - 2014-06-18
+
+* Added support for sending payloads with GET requests
+
+## 4.1.1 - 2014-06-08
+
+* Fixed an issue related to using custom message factory options in subclasses
+* Fixed an issue with nested form fields in a multi-part POST
+* Fixed an issue with using the `json` request option for POST requests
+* Added `ToArrayInterface` to `GuzzleHttp\Cookie\CookieJar`
+
+## 4.1.0 - 2014-05-27
+
+* Added a `json` request option to easily serialize JSON payloads.
+* Added a `GuzzleHttp\json_decode()` wrapper to safely parse JSON.
+* Added `setPort()` and `getPort()` to `GuzzleHttp\Message\RequestInterface`.
+* Added the ability to provide an emitter to a client in the client constructor.
+* Added the ability to persist a cookie session using $_SESSION.
+* Added a trait that can be used to add event listeners to an iterator.
+* Removed request method constants from RequestInterface.
+* Fixed warning when invalid request start-lines are received.
+* Updated MessageFactory to work with custom request option methods.
+* Updated cacert bundle to latest build.
+
+4.0.2 (2014-04-16)
+------------------
+
+* Proxy requests using the StreamAdapter now properly use request_fulluri (#632)
+* Added the ability to set scalars as POST fields (#628)
+
+## 4.0.1 - 2014-04-04
+
+* The HTTP status code of a response is now set as the exception code of
+ RequestException objects.
+* 303 redirects will now correctly switch from POST to GET requests.
+* The default parallel adapter of a client now correctly uses the MultiAdapter.
+* HasDataTrait now initializes the internal data array as an empty array so
+ that the toArray() method always returns an array.
+
+## 4.0.0 - 2014-03-29
+
+* For more information on the 4.0 transition, see:
+ http://mtdowling.com/blog/2014/03/15/guzzle-4-rc/
+* For information on changes and upgrading, see:
+ https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40
+* Added `GuzzleHttp\batch()` as a convenience function for sending requests in
+ parallel without needing to write asynchronous code.
+* Restructured how events are added to `GuzzleHttp\ClientInterface::sendAll()`.
+ You can now pass a callable or an array of associative arrays where each
+ associative array contains the "fn", "priority", and "once" keys.
+
+## 4.0.0.rc-2 - 2014-03-25
+
+* Removed `getConfig()` and `setConfig()` from clients to avoid confusion
+ around whether things like base_url, message_factory, etc. should be able to
+ be retrieved or modified.
+* Added `getDefaultOption()` and `setDefaultOption()` to ClientInterface
+* functions.php functions were renamed using snake_case to match PHP idioms
+* Added support for `HTTP_PROXY`, `HTTPS_PROXY`, and
+ `GUZZLE_CURL_SELECT_TIMEOUT` environment variables
+* Added the ability to specify custom `sendAll()` event priorities
+* Added the ability to specify custom stream context options to the stream
+ adapter.
+* Added a functions.php function for `get_path()` and `set_path()`
+* CurlAdapter and MultiAdapter now use a callable to generate curl resources
+* MockAdapter now properly reads a body and emits a `headers` event
+* Updated Url class to check if a scheme and host are set before adding ":"
+ and "//". This allows empty Url (e.g., "") to be serialized as "".
+* Parsing invalid XML no longer emits warnings
+* Curl classes now properly throw AdapterExceptions
+* Various performance optimizations
+* Streams are created with the faster `Stream\create()` function
+* Marked deprecation_proxy() as internal
+* Test server is now a collection of static methods on a class
+
+## 4.0.0-rc.1 - 2014-03-15
+
+* See https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40
+
+## 3.8.1 - 2014-01-28
+
+* Bug: Always using GET requests when redirecting from a 303 response
+* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in
+ `Guzzle\Http\ClientInterface::setSslVerification()`
+* Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL
+* Bug: The body of a request can now be set to `"0"`
+* Sending PHP stream requests no longer forces `HTTP/1.0`
+* Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of
+ each sub-exception
+* Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than
+ clobbering everything).
+* Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators)
+* Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`.
+ For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`.
+* Now properly escaping the regular expression delimiter when matching Cookie domains.
+* Network access is now disabled when loading XML documents
+
+## 3.8.0 - 2013-12-05
+
+* Added the ability to define a POST name for a file
+* JSON response parsing now properly walks additionalProperties
+* cURL error code 18 is now retried automatically in the BackoffPlugin
+* Fixed a cURL error when URLs contain fragments
+* Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were
+ CurlExceptions
+* CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e)
+* Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS`
+* Fixed a bug that was encountered when parsing empty header parameters
+* UriTemplate now has a `setRegex()` method to match the docs
+* The `debug` request parameter now checks if it is truthy rather than if it exists
+* Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin
+* Added the ability to combine URLs using strict RFC 3986 compliance
+* Command objects can now return the validation errors encountered by the command
+* Various fixes to cache revalidation (#437 and 29797e5)
+* Various fixes to the AsyncPlugin
+* Cleaned up build scripts
+
+## 3.7.4 - 2013-10-02
+
+* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430)
+* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp
+ (see https://github.com/aws/aws-sdk-php/issues/147)
+* Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots
+* Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420)
+* Updated the bundled cacert.pem (#419)
+* OauthPlugin now supports adding authentication to headers or query string (#425)
+
+## 3.7.3 - 2013-09-08
+
+* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and
+ `CommandTransferException`.
+* Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description
+* Schemas are only injected into response models when explicitly configured.
+* No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of
+ an EntityBody.
+* Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator.
+* Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`.
+* Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody()
+* Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin
+* Bug fix: Visiting XML attributes first before visiting XML children when serializing requests
+* Bug fix: Properly parsing headers that contain commas contained in quotes
+* Bug fix: mimetype guessing based on a filename is now case-insensitive
+
+## 3.7.2 - 2013-08-02
+
+* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander
+ See https://github.com/guzzle/guzzle/issues/371
+* Bug fix: Cookie domains are now matched correctly according to RFC 6265
+ See https://github.com/guzzle/guzzle/issues/377
+* Bug fix: GET parameters are now used when calculating an OAuth signature
+* Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted
+* `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched
+* `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input.
+ See https://github.com/guzzle/guzzle/issues/379
+* Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See
+ https://github.com/guzzle/guzzle/pull/380
+* cURL multi cleanup and optimizations
+
+## 3.7.1 - 2013-07-05
+
+* Bug fix: Setting default options on a client now works
+* Bug fix: Setting options on HEAD requests now works. See #352
+* Bug fix: Moving stream factory before send event to before building the stream. See #353
+* Bug fix: Cookies no longer match on IP addresses per RFC 6265
+* Bug fix: Correctly parsing header parameters that are in `<>` and quotes
+* Added `cert` and `ssl_key` as request options
+* `Host` header can now diverge from the host part of a URL if the header is set manually
+* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter
+* OAuth parameters are only added via the plugin if they aren't already set
+* Exceptions are now thrown when a URL cannot be parsed
+* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails
+* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin
+
+## 3.7.0 - 2013-06-10
+
+* See UPGRADING.md for more information on how to upgrade.
+* Requests now support the ability to specify an array of $options when creating a request to more easily modify a
+ request. You can pass a 'request.options' configuration setting to a client to apply default request options to
+ every request created by a client (e.g. default query string variables, headers, curl options, etc.).
+* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`.
+ See `Guzzle\Http\StaticClient::mount`.
+* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests
+ created by a command (e.g. custom headers, query string variables, timeout settings, etc.).
+* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the
+ headers of a response
+* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key
+ (e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`)
+* ServiceBuilders now support storing and retrieving arbitrary data
+* CachePlugin can now purge all resources for a given URI
+* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource
+* CachePlugin now uses the Vary header to determine if a resource is a cache hit
+* `Guzzle\Http\Message\Response` now implements `\Serializable`
+* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters
+* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable
+* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()`
+* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size
+* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message
+* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older
+ Symfony users can still use the old version of Monolog.
+* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`.
+ Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`.
+* Several performance improvements to `Guzzle\Common\Collection`
+* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
+ createRequest, head, delete, put, patch, post, options, prepareRequest
+* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
+* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
+* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
+ `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
+ resource, string, or EntityBody into the $options parameter to specify the download location of the response.
+* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
+ default `array()`
+* Added `Guzzle\Stream\StreamInterface::isRepeatable`
+* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
+ $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
+ $client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`.
+* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`.
+* Removed `Guzzle\Http\ClientInterface::expandTemplate()`
+* Removed `Guzzle\Http\ClientInterface::setRequestFactory()`
+* Removed `Guzzle\Http\ClientInterface::getCurlMulti()`
+* Removed `Guzzle\Http\Message\RequestInterface::canCache`
+* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`
+* Removed `Guzzle\Http\Message\RequestInterface::isRedirect`
+* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
+* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting
+ `Guzzle\Common\Version::$emitWarnings` to true.
+* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use
+ `$request->getResponseBody()->isRepeatable()` instead.
+* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
+ `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
+ `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
+* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
+* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
+* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand.
+ These will work through Guzzle 4.0
+* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params].
+* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
+* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`.
+* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`.
+* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
+* Marked `Guzzle\Common\Collection::inject()` as deprecated.
+* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');`
+* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
+ CacheStorageInterface. These two objects and interface will be removed in a future version.
+* Always setting X-cache headers on cached responses
+* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
+* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
+ $request, Response $response);`
+* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
+* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
+* Added `CacheStorageInterface::purge($url)`
+* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
+ $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
+ CanCacheStrategyInterface $canCache = null)`
+* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`
+
+## 3.6.0 - 2013-05-29
+
+* ServiceDescription now implements ToArrayInterface
+* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters
+* Guzzle can now correctly parse incomplete URLs
+* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
+* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
+* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
+* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
+ HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
+ CacheControl header implementation.
+* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
+* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
+* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
+ Guzzle\Http\Curl\RequestMediator
+* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
+* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
+* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
+* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
+* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
+* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
+* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle
+ directly via interfaces
+* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
+ but are a no-op until removed.
+* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a
+ `Guzzle\Service\Command\ArrayCommandInterface`.
+* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
+ on a request while the request is still being transferred
+* The ability to case-insensitively search for header values
+* Guzzle\Http\Message\Header::hasExactHeader
+* Guzzle\Http\Message\Header::raw. Use getAll()
+* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
+ instead.
+* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
+* Added the ability to cast Model objects to a string to view debug information.
+
+## 3.5.0 - 2013-05-13
+
+* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times
+* Bug: Better cleanup of one-time events across the board (when an event is meant to fire once, it will now remove
+ itself from the EventDispatcher)
+* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values
+* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too
+* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a
+ non-existent key
+* Bug: All __call() method arguments are now required (helps with mocking frameworks)
+* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference
+ to help with refcount based garbage collection of resources created by sending a request
+* Deprecating ZF1 cache and log adapters. These will be removed in the next major version.
+* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it's deprecated). Use the
+ HistoryPlugin for a history.
+* Added a `responseBody` alias for the `response_body` location
+* Refactored internals to no longer rely on Response::getRequest()
+* HistoryPlugin can now be cast to a string
+* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests
+ and responses that are sent over the wire
+* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects
+
+## 3.4.3 - 2013-04-30
+
+* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response
+* Added a check to re-extract the temp cacert bundle from the phar before sending each request
+
+## 3.4.2 - 2013-04-29
+
+* Bug fix: Stream objects now work correctly with "a" and "a+" modes
+* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present
+* Bug fix: AsyncPlugin no longer forces HEAD requests
+* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter
+* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails
+* Setting a response on a request will write to the custom request body from the response body if one is specified
+* LogPlugin now writes to php://output when STDERR is undefined
+* Added the ability to set multiple POST files for the same key in a single call
+* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default
+* Added the ability to queue CurlExceptions to the MockPlugin
+* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send)
+* Configuration loading now allows remote files
+
+## 3.4.1 - 2013-04-16
+
+* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti
+ handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost.
+* Exceptions are now properly grouped when sending requests in parallel
+* Redirects are now properly aggregated when a multi transaction fails
+* Redirects now set the response on the original object even in the event of a failure
+* Bug fix: Model names are now properly set even when using $refs
+* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax
+* Added support for oauth_callback in OAuth signatures
+* Added support for oauth_verifier in OAuth signatures
+* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection
+
+## 3.4.0 - 2013-04-11
+
+* Bug fix: URLs are now resolved correctly based on http://tools.ietf.org/html/rfc3986#section-5.2. #289
+* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289
+* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263
+* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264.
+* Bug fix: Added `number` type to service descriptions.
+* Bug fix: empty parameters are removed from an OAuth signature
+* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header
+* Bug fix: Fixed "array to string" error when validating a union of types in a service description
+* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream
+* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin.
+* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs.
+* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections.
+* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if
+ the Content-Type can be determined based on the entity body or the path of the request.
+* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder.
+* Added support for a PSR-3 LogAdapter.
+* Added a `command.after_prepare` event
+* Added `oauth_callback` parameter to the OauthPlugin
+* Added the ability to create a custom stream class when using a stream factory
+* Added a CachingEntityBody decorator
+* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized.
+* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar.
+* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies
+* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This
+ means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use
+ POST fields or files (the latter is only used when emulating a form POST in the browser).
+* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest
+
+## 3.3.1 - 2013-03-10
+
+* Added the ability to create PHP streaming responses from HTTP requests
+* Bug fix: Running any filters when parsing response headers with service descriptions
+* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing
+* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across
+ response location visitors.
+* Bug fix: Removed the possibility of creating configuration files with circular dependencies
+* RequestFactory::create() now uses the key of a POST file when setting the POST file name
+* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set
+
+## 3.3.0 - 2013-03-03
+
+* A large number of performance optimizations have been made
+* Bug fix: Added 'wb' as a valid write mode for streams
+* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned
+* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()`
+* BC: Removed `Guzzle\Http\Utils` class
+* BC: Setting a service description on a client will no longer modify the client's command factories.
+* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using
+ the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
+* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to
+ lowercase
+* Operation parameter objects are now lazy loaded internally
+* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses
+* Added support for instantiating responseType=class responseClass classes. Classes must implement
+ `Guzzle\Service\Command\ResponseClassInterface`
+* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These
+ additional properties also support locations and can be used to parse JSON responses where the outermost part of the
+ JSON is an array
+* Added support for nested renaming of JSON models (rename sentAs to name)
+* CachePlugin
+ * Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error
+ * Debug headers can now added to cached response in the CachePlugin
+
+## 3.2.0 - 2013-02-14
+
+* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients.
+* URLs with no path no longer contain a "/" by default
+* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url.
+* BadResponseException no longer includes the full request and response message
+* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface
+* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface
+* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription
+* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list
+* xmlEncoding can now be customized for the XML declaration of a XML service description operation
+* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value
+ aggregation and no longer uses callbacks
+* The URL encoding implementation of Guzzle\Http\QueryString can now be customized
+* Bug fix: Filters were not always invoked for array service description parameters
+* Bug fix: Redirects now use a target response body rather than a temporary response body
+* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded
+* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives
+
+## 3.1.2 - 2013-01-27
+
+* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the
+ response body. For example, the XmlVisitor now parses the XML response into an array in the before() method.
+* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent
+* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444)
+* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse()
+* Setting default headers on a client after setting the user-agent will not erase the user-agent setting
+
+## 3.1.1 - 2013-01-20
+
+* Adding wildcard support to Guzzle\Common\Collection::getPath()
+* Adding alias support to ServiceBuilder configs
+* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface
+
+## 3.1.0 - 2013-01-12
+
+* BC: CurlException now extends from RequestException rather than BadResponseException
+* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse()
+* Added getData to ServiceDescriptionInterface
+* Added context array to RequestInterface::setState()
+* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http
+* Bug: Adding required content-type when JSON request visitor adds JSON to a command
+* Bug: Fixing the serialization of a service description with custom data
+* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing
+ an array of successful and failed responses
+* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection
+* Added Guzzle\Http\IoEmittingEntityBody
+* Moved command filtration from validators to location visitors
+* Added `extends` attributes to service description parameters
+* Added getModels to ServiceDescriptionInterface
+
+## 3.0.7 - 2012-12-19
+
+* Fixing phar detection when forcing a cacert to system if null or true
+* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()`
+* Cleaning up `Guzzle\Common\Collection::inject` method
+* Adding a response_body location to service descriptions
+
+## 3.0.6 - 2012-12-09
+
+* CurlMulti performance improvements
+* Adding setErrorResponses() to Operation
+* composer.json tweaks
+
+## 3.0.5 - 2012-11-18
+
+* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin
+* Bug: Response body can now be a string containing "0"
+* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert
+* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs
+* Added support for XML attributes in service description responses
+* DefaultRequestSerializer now supports array URI parameter values for URI template expansion
+* Added better mimetype guessing to requests and post files
+
+## 3.0.4 - 2012-11-11
+
+* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value
+* Bug: Cookies can now be added that have a name, domain, or value set to "0"
+* Bug: Using the system cacert bundle when using the Phar
+* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures
+* Enhanced cookie jar de-duplication
+* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added
+* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies
+* Added the ability to create any sort of hash for a stream rather than just an MD5 hash
+
+## 3.0.3 - 2012-11-04
+
+* Implementing redirects in PHP rather than cURL
+* Added PECL URI template extension and using as default parser if available
+* Bug: Fixed Content-Length parsing of Response factory
+* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams.
+* Adding ToArrayInterface throughout library
+* Fixing OauthPlugin to create unique nonce values per request
+
+## 3.0.2 - 2012-10-25
+
+* Magic methods are enabled by default on clients
+* Magic methods return the result of a command
+* Service clients no longer require a base_url option in the factory
+* Bug: Fixed an issue with URI templates where null template variables were being expanded
+
+## 3.0.1 - 2012-10-22
+
+* Models can now be used like regular collection objects by calling filter, map, etc.
+* Models no longer require a Parameter structure or initial data in the constructor
+* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator`
+
+## 3.0.0 - 2012-10-15
+
+* Rewrote service description format to be based on Swagger
+ * Now based on JSON schema
+ * Added nested input structures and nested response models
+ * Support for JSON and XML input and output models
+ * Renamed `commands` to `operations`
+ * Removed dot class notation
+ * Removed custom types
+* Broke the project into smaller top-level namespaces to be more component friendly
+* Removed support for XML configs and descriptions. Use arrays or JSON files.
+* Removed the Validation component and Inspector
+* Moved all cookie code to Guzzle\Plugin\Cookie
+* Magic methods on a Guzzle\Service\Client now return the command un-executed.
+* Calling getResult() or getResponse() on a command will lazily execute the command if needed.
+* Now shipping with cURL's CA certs and using it by default
+* Added previousResponse() method to response objects
+* No longer sending Accept and Accept-Encoding headers on every request
+* Only sending an Expect header by default when a payload is greater than 1MB
+* Added/moved client options:
+ * curl.blacklist to curl.option.blacklist
+ * Added ssl.certificate_authority
+* Added a Guzzle\Iterator component
+* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin
+* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin)
+* Added a more robust caching plugin
+* Added setBody to response objects
+* Updating LogPlugin to use a more flexible MessageFormatter
+* Added a completely revamped build process
+* Cleaning up Collection class and removing default values from the get method
+* Fixed ZF2 cache adapters
+
+## 2.8.8 - 2012-10-15
+
+* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did
+
+## 2.8.7 - 2012-09-30
+
+* Bug: Fixed config file aliases for JSON includes
+* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests
+* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload
+* Bug: Hardening request and response parsing to account for missing parts
+* Bug: Fixed PEAR packaging
+* Bug: Fixed Request::getInfo
+* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail
+* Adding the ability for the namespace Iterator factory to look in multiple directories
+* Added more getters/setters/removers from service descriptions
+* Added the ability to remove POST fields from OAuth signatures
+* OAuth plugin now supports 2-legged OAuth
+
+## 2.8.6 - 2012-09-05
+
+* Added the ability to modify and build service descriptions
+* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command
+* Added a `json` parameter location
+* Now allowing dot notation for classes in the CacheAdapterFactory
+* Using the union of two arrays rather than an array_merge when extending service builder services and service params
+* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references
+ in service builder config files.
+* Services defined in two different config files that include one another will by default replace the previously
+ defined service, but you can now create services that extend themselves and merge their settings over the previous
+* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like
+ '_default' with a default JSON configuration file.
+
+## 2.8.5 - 2012-08-29
+
+* Bug: Suppressed empty arrays from URI templates
+* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching
+* Added support for HTTP responses that do not contain a reason phrase in the start-line
+* AbstractCommand commands are now invokable
+* Added a way to get the data used when signing an Oauth request before a request is sent
+
+## 2.8.4 - 2012-08-15
+
+* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin
+* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable.
+* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream
+* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream
+* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5())
+* Added additional response status codes
+* Removed SSL information from the default User-Agent header
+* DELETE requests can now send an entity body
+* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries
+* Added the ability of the MockPlugin to consume mocked request bodies
+* LogPlugin now exposes request and response objects in the extras array
+
+## 2.8.3 - 2012-07-30
+
+* Bug: Fixed a case where empty POST requests were sent as GET requests
+* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body
+* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new
+* Added multiple inheritance to service description commands
+* Added an ApiCommandInterface and added `getParamNames()` and `hasParam()`
+* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything
+* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles
+
+## 2.8.2 - 2012-07-24
+
+* Bug: Query string values set to 0 are no longer dropped from the query string
+* Bug: A Collection object is no longer created each time a call is made to `Guzzle\Service\Command\AbstractCommand::getRequestHeaders()`
+* Bug: `+` is now treated as an encoded space when parsing query strings
+* QueryString and Collection performance improvements
+* Allowing dot notation for class paths in filters attribute of a service descriptions
+
+## 2.8.1 - 2012-07-16
+
+* Loosening Event Dispatcher dependency
+* POST redirects can now be customized using CURLOPT_POSTREDIR
+
+## 2.8.0 - 2012-07-15
+
+* BC: Guzzle\Http\Query
+ * Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl)
+ * Changed isEncodingValues() and isEncodingFields() to isUrlEncoding()
+ * Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool)
+ * Changed the aggregation functions of QueryString to be static methods
+ * Can now use fromString() with querystrings that have a leading ?
+* cURL configuration values can be specified in service descriptions using `curl.` prefixed parameters
+* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body
+* Cookies are no longer URL decoded by default
+* Bug: URI template variables set to null are no longer expanded
+
+## 2.7.2 - 2012-07-02
+
+* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser.
+* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty()
+* CachePlugin now allows for a custom request parameter function to check if a request can be cached
+* Bug fix: CachePlugin now only caches GET and HEAD requests by default
+* Bug fix: Using header glue when transferring headers over the wire
+* Allowing deeply nested arrays for composite variables in URI templates
+* Batch divisors can now return iterators or arrays
+
+## 2.7.1 - 2012-06-26
+
+* Minor patch to update version number in UA string
+* Updating build process
+
+## 2.7.0 - 2012-06-25
+
+* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes.
+* BC: Removed magic setX methods from commands
+* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method
+* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable.
+* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity)
+* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace
+* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin
+* Added the ability to set POST fields and files in a service description
+* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method
+* Adding a command.before_prepare event to clients
+* Added BatchClosureTransfer and BatchClosureDivisor
+* BatchTransferException now includes references to the batch divisor and transfer strategies
+* Fixed some tests so that they pass more reliably
+* Added Guzzle\Common\Log\ArrayLogAdapter
+
+## 2.6.6 - 2012-06-10
+
+* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin
+* BC: Removing Guzzle\Service\Command\CommandSet
+* Adding generic batching system (replaces the batch queue plugin and command set)
+* Updating ZF cache and log adapters and now using ZF's composer repository
+* Bug: Setting the name of each ApiParam when creating through an ApiCommand
+* Adding result_type, result_doc, deprecated, and doc_url to service descriptions
+* Bug: Changed the default cookie header casing back to 'Cookie'
+
+## 2.6.5 - 2012-06-03
+
+* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource()
+* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from
+* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data
+* BC: Renaming methods in the CookieJarInterface
+* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations
+* Making the default glue for HTTP headers ';' instead of ','
+* Adding a removeValue to Guzzle\Http\Message\Header
+* Adding getCookies() to request interface.
+* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber()
+
+## 2.6.4 - 2012-05-30
+
+* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class.
+* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand
+* Bug: Fixing magic method command calls on clients
+* Bug: Email constraint only validates strings
+* Bug: Aggregate POST fields when POST files are present in curl handle
+* Bug: Fixing default User-Agent header
+* Bug: Only appending or prepending parameters in commands if they are specified
+* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes
+* Allowing the use of dot notation for class namespaces when using instance_of constraint
+* Added any_match validation constraint
+* Added an AsyncPlugin
+* Passing request object to the calculateWait method of the ExponentialBackoffPlugin
+* Allowing the result of a command object to be changed
+* Parsing location and type sub values when instantiating a service description rather than over and over at runtime
+
+## 2.6.3 - 2012-05-23
+
+* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options.
+* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields.
+* You can now use an array of data when creating PUT request bodies in the request factory.
+* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable.
+* [Http] Adding support for Content-Type in multipart POST uploads per upload
+* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1])
+* Adding more POST data operations for easier manipulation of POST data.
+* You can now set empty POST fields.
+* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files.
+* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate.
+* CS updates
+
+## 2.6.2 - 2012-05-19
+
+* [Http] Better handling of nested scope requests in CurlMulti. Requests are now always prepares in the send() method rather than the addRequest() method.
+
+## 2.6.1 - 2012-05-19
+
+* [BC] Removing 'path' support in service descriptions. Use 'uri'.
+* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache.
+* [BC] Removing Guzzle\Common\NullObject. Use https://github.com/mtdowling/NullObject if you need it.
+* [BC] Removing Guzzle\Common\XmlElement.
+* All commands, both dynamic and concrete, have ApiCommand objects.
+* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits.
+* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored.
+* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible.
+
+## 2.6.0 - 2012-05-15
+
+* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder
+* [BC] Executing a Command returns the result of the command rather than the command
+* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed.
+* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args.
+* [BC] Moving ResourceIterator* to Guzzle\Service\Resource
+* [BC] Completely refactored ResourceIterators to iterate over a cloned command object
+* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate
+* [BC] Guzzle\Guzzle is now deprecated
+* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject
+* Adding Guzzle\Version class to give version information about Guzzle
+* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate()
+* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data
+* ServiceDescription and ServiceBuilder are now cacheable using similar configs
+* Changing the format of XML and JSON service builder configs. Backwards compatible.
+* Cleaned up Cookie parsing
+* Trimming the default Guzzle User-Agent header
+* Adding a setOnComplete() method to Commands that is called when a command completes
+* Keeping track of requests that were mocked in the MockPlugin
+* Fixed a caching bug in the CacheAdapterFactory
+* Inspector objects can be injected into a Command object
+* Refactoring a lot of code and tests to be case insensitive when dealing with headers
+* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL
+* Adding the ability to set global option overrides to service builder configs
+* Adding the ability to include other service builder config files from within XML and JSON files
+* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method.
+
+## 2.5.0 - 2012-05-08
+
+* Major performance improvements
+* [BC] Simplifying Guzzle\Common\Collection. Please check to see if you are using features that are now deprecated.
+* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component.
+* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates. Use "{}"
+* Added the ability to passed parameters to all requests created by a client
+* Added callback functionality to the ExponentialBackoffPlugin
+* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies.
+* Rewinding request stream bodies when retrying requests
+* Exception is thrown when JSON response body cannot be decoded
+* Added configurable magic method calls to clients and commands. This is off by default.
+* Fixed a defect that added a hash to every parsed URL part
+* Fixed duplicate none generation for OauthPlugin.
+* Emitting an event each time a client is generated by a ServiceBuilder
+* Using an ApiParams object instead of a Collection for parameters of an ApiCommand
+* cache.* request parameters should be renamed to params.cache.*
+* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc.). See CurlHandle.
+* Added the ability to disable type validation of service descriptions
+* ServiceDescriptions and ServiceBuilders are now Serializable
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/Dockerfile b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/Dockerfile
new file mode 100644
index 000000000..f6a095230
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/Dockerfile
@@ -0,0 +1,18 @@
+FROM composer:latest as setup
+
+RUN mkdir /guzzle
+
+WORKDIR /guzzle
+
+RUN set -xe \
+ && composer init --name=guzzlehttp/test --description="Simple project for testing Guzzle scripts" --author="Márk Sági-Kazár " --no-interaction \
+ && composer require guzzlehttp/guzzle
+
+
+FROM php:7.3
+
+RUN mkdir /guzzle
+
+WORKDIR /guzzle
+
+COPY --from=setup /guzzle /guzzle
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/LICENSE b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/LICENSE
new file mode 100644
index 000000000..50a177b03
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2011-2018 Michael Dowling, https://github.com/mtdowling
+
+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.
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/README.md b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/README.md
new file mode 100644
index 000000000..5fdb6c5f4
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/README.md
@@ -0,0 +1,90 @@
+Guzzle, PHP HTTP client
+=======================
+
+[](https://github.com/guzzle/guzzle/releases)
+[](https://travis-ci.org/guzzle/guzzle)
+[](https://packagist.org/packages/guzzlehttp/guzzle)
+
+Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and
+trivial to integrate with web services.
+
+- Simple interface for building query strings, POST requests, streaming large
+ uploads, streaming large downloads, using HTTP cookies, uploading JSON data,
+ etc...
+- Can send both synchronous and asynchronous requests using the same interface.
+- Uses PSR-7 interfaces for requests, responses, and streams. This allows you
+ to utilize other PSR-7 compatible libraries with Guzzle.
+- Abstracts away the underlying HTTP transport, allowing you to write
+ environment and transport agnostic code; i.e., no hard dependency on cURL,
+ PHP streams, sockets, or non-blocking event loops.
+- Middleware system allows you to augment and compose client behavior.
+
+```php
+$client = new \GuzzleHttp\Client();
+$response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle');
+
+echo $response->getStatusCode(); # 200
+echo $response->getHeaderLine('content-type'); # 'application/json; charset=utf8'
+echo $response->getBody(); # '{"id": 1420053, "name": "guzzle", ...}'
+
+# Send an asynchronous request.
+$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org');
+$promise = $client->sendAsync($request)->then(function ($response) {
+ echo 'I completed! ' . $response->getBody();
+});
+
+$promise->wait();
+```
+
+## Help and docs
+
+- [Documentation](http://guzzlephp.org/)
+- [Stack Overflow](http://stackoverflow.com/questions/tagged/guzzle)
+- [Gitter](https://gitter.im/guzzle/guzzle)
+
+
+## Installing Guzzle
+
+The recommended way to install Guzzle is through
+[Composer](http://getcomposer.org).
+
+```bash
+# Install Composer
+curl -sS https://getcomposer.org/installer | php
+```
+
+Next, run the Composer command to install the latest stable version of Guzzle:
+
+```bash
+composer require guzzlehttp/guzzle
+```
+
+After installing, you need to require Composer's autoloader:
+
+```php
+require 'vendor/autoload.php';
+```
+
+You can then later update Guzzle using composer:
+
+ ```bash
+composer update
+ ```
+
+
+## Version Guidance
+
+| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version |
+|---------|------------|---------------------|--------------|---------------------|---------------------|-------|-------------|
+| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >= 5.3.3 |
+| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >= 5.4 |
+| 5.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >= 5.4 |
+| 6.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >= 5.5 |
+
+[guzzle-3-repo]: https://github.com/guzzle/guzzle3
+[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x
+[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3
+[guzzle-6-repo]: https://github.com/guzzle/guzzle
+[guzzle-3-docs]: http://guzzle3.readthedocs.org
+[guzzle-5-docs]: http://guzzle.readthedocs.org/en/5.3/
+[guzzle-6-docs]: http://guzzle.readthedocs.org/en/latest/
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/UPGRADING.md b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/UPGRADING.md
new file mode 100644
index 000000000..91d1dcc99
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/UPGRADING.md
@@ -0,0 +1,1203 @@
+Guzzle Upgrade Guide
+====================
+
+5.0 to 6.0
+----------
+
+Guzzle now uses [PSR-7](http://www.php-fig.org/psr/psr-7/) for HTTP messages.
+Due to the fact that these messages are immutable, this prompted a refactoring
+of Guzzle to use a middleware based system rather than an event system. Any
+HTTP message interaction (e.g., `GuzzleHttp\Message\Request`) need to be
+updated to work with the new immutable PSR-7 request and response objects. Any
+event listeners or subscribers need to be updated to become middleware
+functions that wrap handlers (or are injected into a
+`GuzzleHttp\HandlerStack`).
+
+- Removed `GuzzleHttp\BatchResults`
+- Removed `GuzzleHttp\Collection`
+- Removed `GuzzleHttp\HasDataTrait`
+- Removed `GuzzleHttp\ToArrayInterface`
+- The `guzzlehttp/streams` dependency has been removed. Stream functionality
+ is now present in the `GuzzleHttp\Psr7` namespace provided by the
+ `guzzlehttp/psr7` package.
+- Guzzle no longer uses ReactPHP promises and now uses the
+ `guzzlehttp/promises` library. We use a custom promise library for three
+ significant reasons:
+ 1. React promises (at the time of writing this) are recursive. Promise
+ chaining and promise resolution will eventually blow the stack. Guzzle
+ promises are not recursive as they use a sort of trampolining technique.
+ Note: there has been movement in the React project to modify promises to
+ no longer utilize recursion.
+ 2. Guzzle needs to have the ability to synchronously block on a promise to
+ wait for a result. Guzzle promises allows this functionality (and does
+ not require the use of recursion).
+ 3. Because we need to be able to wait on a result, doing so using React
+ promises requires wrapping react promises with RingPHP futures. This
+ overhead is no longer needed, reducing stack sizes, reducing complexity,
+ and improving performance.
+- `GuzzleHttp\Mimetypes` has been moved to a function in
+ `GuzzleHttp\Psr7\mimetype_from_extension` and
+ `GuzzleHttp\Psr7\mimetype_from_filename`.
+- `GuzzleHttp\Query` and `GuzzleHttp\QueryParser` have been removed. Query
+ strings must now be passed into request objects as strings, or provided to
+ the `query` request option when creating requests with clients. The `query`
+ option uses PHP's `http_build_query` to convert an array to a string. If you
+ need a different serialization technique, you will need to pass the query
+ string in as a string. There are a couple helper functions that will make
+ working with query strings easier: `GuzzleHttp\Psr7\parse_query` and
+ `GuzzleHttp\Psr7\build_query`.
+- Guzzle no longer has a dependency on RingPHP. Due to the use of a middleware
+ system based on PSR-7, using RingPHP and it's middleware system as well adds
+ more complexity than the benefits it provides. All HTTP handlers that were
+ present in RingPHP have been modified to work directly with PSR-7 messages
+ and placed in the `GuzzleHttp\Handler` namespace. This significantly reduces
+ complexity in Guzzle, removes a dependency, and improves performance. RingPHP
+ will be maintained for Guzzle 5 support, but will no longer be a part of
+ Guzzle 6.
+- As Guzzle now uses a middleware based systems the event system and RingPHP
+ integration has been removed. Note: while the event system has been removed,
+ it is possible to add your own type of event system that is powered by the
+ middleware system.
+ - Removed the `Event` namespace.
+ - Removed the `Subscriber` namespace.
+ - Removed `Transaction` class
+ - Removed `RequestFsm`
+ - Removed `RingBridge`
+ - `GuzzleHttp\Subscriber\Cookie` is now provided by
+ `GuzzleHttp\Middleware::cookies`
+ - `GuzzleHttp\Subscriber\HttpError` is now provided by
+ `GuzzleHttp\Middleware::httpError`
+ - `GuzzleHttp\Subscriber\History` is now provided by
+ `GuzzleHttp\Middleware::history`
+ - `GuzzleHttp\Subscriber\Mock` is now provided by
+ `GuzzleHttp\Handler\MockHandler`
+ - `GuzzleHttp\Subscriber\Prepare` is now provided by
+ `GuzzleHttp\PrepareBodyMiddleware`
+ - `GuzzleHttp\Subscriber\Redirect` is now provided by
+ `GuzzleHttp\RedirectMiddleware`
+- Guzzle now uses `Psr\Http\Message\UriInterface` (implements in
+ `GuzzleHttp\Psr7\Uri`) for URI support. `GuzzleHttp\Url` is now gone.
+- Static functions in `GuzzleHttp\Utils` have been moved to namespaced
+ functions under the `GuzzleHttp` namespace. This requires either a Composer
+ based autoloader or you to include functions.php.
+- `GuzzleHttp\ClientInterface::getDefaultOption` has been renamed to
+ `GuzzleHttp\ClientInterface::getConfig`.
+- `GuzzleHttp\ClientInterface::setDefaultOption` has been removed.
+- The `json` and `xml` methods of response objects has been removed. With the
+ migration to strictly adhering to PSR-7 as the interface for Guzzle messages,
+ adding methods to message interfaces would actually require Guzzle messages
+ to extend from PSR-7 messages rather then work with them directly.
+
+## Migrating to middleware
+
+The change to PSR-7 unfortunately required significant refactoring to Guzzle
+due to the fact that PSR-7 messages are immutable. Guzzle 5 relied on an event
+system from plugins. The event system relied on mutability of HTTP messages and
+side effects in order to work. With immutable messages, you have to change your
+workflow to become more about either returning a value (e.g., functional
+middlewares) or setting a value on an object. Guzzle v6 has chosen the
+functional middleware approach.
+
+Instead of using the event system to listen for things like the `before` event,
+you now create a stack based middleware function that intercepts a request on
+the way in and the promise of the response on the way out. This is a much
+simpler and more predictable approach than the event system and works nicely
+with PSR-7 middleware. Due to the use of promises, the middleware system is
+also asynchronous.
+
+v5:
+
+```php
+use GuzzleHttp\Event\BeforeEvent;
+$client = new GuzzleHttp\Client();
+// Get the emitter and listen to the before event.
+$client->getEmitter()->on('before', function (BeforeEvent $e) {
+ // Guzzle v5 events relied on mutation
+ $e->getRequest()->setHeader('X-Foo', 'Bar');
+});
+```
+
+v6:
+
+In v6, you can modify the request before it is sent using the `mapRequest`
+middleware. The idiomatic way in v6 to modify the request/response lifecycle is
+to setup a handler middleware stack up front and inject the handler into a
+client.
+
+```php
+use GuzzleHttp\Middleware;
+// Create a handler stack that has all of the default middlewares attached
+$handler = GuzzleHttp\HandlerStack::create();
+// Push the handler onto the handler stack
+$handler->push(Middleware::mapRequest(function (RequestInterface $request) {
+ // Notice that we have to return a request object
+ return $request->withHeader('X-Foo', 'Bar');
+}));
+// Inject the handler into the client
+$client = new GuzzleHttp\Client(['handler' => $handler]);
+```
+
+## POST Requests
+
+This version added the [`form_params`](http://guzzle.readthedocs.org/en/latest/request-options.html#form_params)
+and `multipart` request options. `form_params` is an associative array of
+strings or array of strings and is used to serialize an
+`application/x-www-form-urlencoded` POST request. The
+[`multipart`](http://guzzle.readthedocs.org/en/latest/request-options.html#multipart)
+option is now used to send a multipart/form-data POST request.
+
+`GuzzleHttp\Post\PostFile` has been removed. Use the `multipart` option to add
+POST files to a multipart/form-data request.
+
+The `body` option no longer accepts an array to send POST requests. Please use
+`multipart` or `form_params` instead.
+
+The `base_url` option has been renamed to `base_uri`.
+
+4.x to 5.0
+----------
+
+## Rewritten Adapter Layer
+
+Guzzle now uses [RingPHP](http://ringphp.readthedocs.org/en/latest) to send
+HTTP requests. The `adapter` option in a `GuzzleHttp\Client` constructor
+is still supported, but it has now been renamed to `handler`. Instead of
+passing a `GuzzleHttp\Adapter\AdapterInterface`, you must now pass a PHP
+`callable` that follows the RingPHP specification.
+
+## Removed Fluent Interfaces
+
+[Fluent interfaces were removed](http://ocramius.github.io/blog/fluent-interfaces-are-evil)
+from the following classes:
+
+- `GuzzleHttp\Collection`
+- `GuzzleHttp\Url`
+- `GuzzleHttp\Query`
+- `GuzzleHttp\Post\PostBody`
+- `GuzzleHttp\Cookie\SetCookie`
+
+## Removed functions.php
+
+Removed "functions.php", so that Guzzle is truly PSR-4 compliant. The following
+functions can be used as replacements.
+
+- `GuzzleHttp\json_decode` -> `GuzzleHttp\Utils::jsonDecode`
+- `GuzzleHttp\get_path` -> `GuzzleHttp\Utils::getPath`
+- `GuzzleHttp\Utils::setPath` -> `GuzzleHttp\set_path`
+- `GuzzleHttp\Pool::batch` -> `GuzzleHttp\batch`. This function is, however,
+ deprecated in favor of using `GuzzleHttp\Pool::batch()`.
+
+The "procedural" global client has been removed with no replacement (e.g.,
+`GuzzleHttp\get()`, `GuzzleHttp\post()`, etc.). Use a `GuzzleHttp\Client`
+object as a replacement.
+
+## `throwImmediately` has been removed
+
+The concept of "throwImmediately" has been removed from exceptions and error
+events. This control mechanism was used to stop a transfer of concurrent
+requests from completing. This can now be handled by throwing the exception or
+by cancelling a pool of requests or each outstanding future request
+individually.
+
+## headers event has been removed
+
+Removed the "headers" event. This event was only useful for changing the
+body a response once the headers of the response were known. You can implement
+a similar behavior in a number of ways. One example might be to use a
+FnStream that has access to the transaction being sent. For example, when the
+first byte is written, you could check if the response headers match your
+expectations, and if so, change the actual stream body that is being
+written to.
+
+## Updates to HTTP Messages
+
+Removed the `asArray` parameter from
+`GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header
+value as an array, then use the newly added `getHeaderAsArray()` method of
+`MessageInterface`. This change makes the Guzzle interfaces compatible with
+the PSR-7 interfaces.
+
+3.x to 4.0
+----------
+
+## Overarching changes:
+
+- Now requires PHP 5.4 or greater.
+- No longer requires cURL to send requests.
+- Guzzle no longer wraps every exception it throws. Only exceptions that are
+ recoverable are now wrapped by Guzzle.
+- Various namespaces have been removed or renamed.
+- No longer requiring the Symfony EventDispatcher. A custom event dispatcher
+ based on the Symfony EventDispatcher is
+ now utilized in `GuzzleHttp\Event\EmitterInterface` (resulting in significant
+ speed and functionality improvements).
+
+Changes per Guzzle 3.x namespace are described below.
+
+## Batch
+
+The `Guzzle\Batch` namespace has been removed. This is best left to
+third-parties to implement on top of Guzzle's core HTTP library.
+
+## Cache
+
+The `Guzzle\Cache` namespace has been removed. (Todo: No suitable replacement
+has been implemented yet, but hoping to utilize a PSR cache interface).
+
+## Common
+
+- Removed all of the wrapped exceptions. It's better to use the standard PHP
+ library for unrecoverable exceptions.
+- `FromConfigInterface` has been removed.
+- `Guzzle\Common\Version` has been removed. The VERSION constant can be found
+ at `GuzzleHttp\ClientInterface::VERSION`.
+
+### Collection
+
+- `getAll` has been removed. Use `toArray` to convert a collection to an array.
+- `inject` has been removed.
+- `keySearch` has been removed.
+- `getPath` no longer supports wildcard expressions. Use something better like
+ JMESPath for this.
+- `setPath` now supports appending to an existing array via the `[]` notation.
+
+### Events
+
+Guzzle no longer requires Symfony's EventDispatcher component. Guzzle now uses
+`GuzzleHttp\Event\Emitter`.
+
+- `Symfony\Component\EventDispatcher\EventDispatcherInterface` is replaced by
+ `GuzzleHttp\Event\EmitterInterface`.
+- `Symfony\Component\EventDispatcher\EventDispatcher` is replaced by
+ `GuzzleHttp\Event\Emitter`.
+- `Symfony\Component\EventDispatcher\Event` is replaced by
+ `GuzzleHttp\Event\Event`, and Guzzle now has an EventInterface in
+ `GuzzleHttp\Event\EventInterface`.
+- `AbstractHasDispatcher` has moved to a trait, `HasEmitterTrait`, and
+ `HasDispatcherInterface` has moved to `HasEmitterInterface`. Retrieving the
+ event emitter of a request, client, etc. now uses the `getEmitter` method
+ rather than the `getDispatcher` method.
+
+#### Emitter
+
+- Use the `once()` method to add a listener that automatically removes itself
+ the first time it is invoked.
+- Use the `listeners()` method to retrieve a list of event listeners rather than
+ the `getListeners()` method.
+- Use `emit()` instead of `dispatch()` to emit an event from an emitter.
+- Use `attach()` instead of `addSubscriber()` and `detach()` instead of
+ `removeSubscriber()`.
+
+```php
+$mock = new Mock();
+// 3.x
+$request->getEventDispatcher()->addSubscriber($mock);
+$request->getEventDispatcher()->removeSubscriber($mock);
+// 4.x
+$request->getEmitter()->attach($mock);
+$request->getEmitter()->detach($mock);
+```
+
+Use the `on()` method to add a listener rather than the `addListener()` method.
+
+```php
+// 3.x
+$request->getEventDispatcher()->addListener('foo', function (Event $event) { /* ... */ } );
+// 4.x
+$request->getEmitter()->on('foo', function (Event $event, $name) { /* ... */ } );
+```
+
+## Http
+
+### General changes
+
+- The cacert.pem certificate has been moved to `src/cacert.pem`.
+- Added the concept of adapters that are used to transfer requests over the
+ wire.
+- Simplified the event system.
+- Sending requests in parallel is still possible, but batching is no longer a
+ concept of the HTTP layer. Instead, you must use the `complete` and `error`
+ events to asynchronously manage parallel request transfers.
+- `Guzzle\Http\Url` has moved to `GuzzleHttp\Url`.
+- `Guzzle\Http\QueryString` has moved to `GuzzleHttp\Query`.
+- QueryAggregators have been rewritten so that they are simply callable
+ functions.
+- `GuzzleHttp\StaticClient` has been removed. Use the functions provided in
+ `functions.php` for an easy to use static client instance.
+- Exceptions in `GuzzleHttp\Exception` have been updated to all extend from
+ `GuzzleHttp\Exception\TransferException`.
+
+### Client
+
+Calling methods like `get()`, `post()`, `head()`, etc. no longer create and
+return a request, but rather creates a request, sends the request, and returns
+the response.
+
+```php
+// 3.0
+$request = $client->get('/');
+$response = $request->send();
+
+// 4.0
+$response = $client->get('/');
+
+// or, to mirror the previous behavior
+$request = $client->createRequest('GET', '/');
+$response = $client->send($request);
+```
+
+`GuzzleHttp\ClientInterface` has changed.
+
+- The `send` method no longer accepts more than one request. Use `sendAll` to
+ send multiple requests in parallel.
+- `setUserAgent()` has been removed. Use a default request option instead. You
+ could, for example, do something like:
+ `$client->setConfig('defaults/headers/User-Agent', 'Foo/Bar ' . $client::getDefaultUserAgent())`.
+- `setSslVerification()` has been removed. Use default request options instead,
+ like `$client->setConfig('defaults/verify', true)`.
+
+`GuzzleHttp\Client` has changed.
+
+- The constructor now accepts only an associative array. You can include a
+ `base_url` string or array to use a URI template as the base URL of a client.
+ You can also specify a `defaults` key that is an associative array of default
+ request options. You can pass an `adapter` to use a custom adapter,
+ `batch_adapter` to use a custom adapter for sending requests in parallel, or
+ a `message_factory` to change the factory used to create HTTP requests and
+ responses.
+- The client no longer emits a `client.create_request` event.
+- Creating requests with a client no longer automatically utilize a URI
+ template. You must pass an array into a creational method (e.g.,
+ `createRequest`, `get`, `put`, etc.) in order to expand a URI template.
+
+### Messages
+
+Messages no longer have references to their counterparts (i.e., a request no
+longer has a reference to it's response, and a response no loger has a
+reference to its request). This association is now managed through a
+`GuzzleHttp\Adapter\TransactionInterface` object. You can get references to
+these transaction objects using request events that are emitted over the
+lifecycle of a request.
+
+#### Requests with a body
+
+- `GuzzleHttp\Message\EntityEnclosingRequest` and
+ `GuzzleHttp\Message\EntityEnclosingRequestInterface` have been removed. The
+ separation between requests that contain a body and requests that do not
+ contain a body has been removed, and now `GuzzleHttp\Message\RequestInterface`
+ handles both use cases.
+- Any method that previously accepts a `GuzzleHttp\Response` object now accept a
+ `GuzzleHttp\Message\ResponseInterface`.
+- `GuzzleHttp\Message\RequestFactoryInterface` has been renamed to
+ `GuzzleHttp\Message\MessageFactoryInterface`. This interface is used to create
+ both requests and responses and is implemented in
+ `GuzzleHttp\Message\MessageFactory`.
+- POST field and file methods have been removed from the request object. You
+ must now use the methods made available to `GuzzleHttp\Post\PostBodyInterface`
+ to control the format of a POST body. Requests that are created using a
+ standard `GuzzleHttp\Message\MessageFactoryInterface` will automatically use
+ a `GuzzleHttp\Post\PostBody` body if the body was passed as an array or if
+ the method is POST and no body is provided.
+
+```php
+$request = $client->createRequest('POST', '/');
+$request->getBody()->setField('foo', 'bar');
+$request->getBody()->addFile(new PostFile('file_key', fopen('/path/to/content', 'r')));
+```
+
+#### Headers
+
+- `GuzzleHttp\Message\Header` has been removed. Header values are now simply
+ represented by an array of values or as a string. Header values are returned
+ as a string by default when retrieving a header value from a message. You can
+ pass an optional argument of `true` to retrieve a header value as an array
+ of strings instead of a single concatenated string.
+- `GuzzleHttp\PostFile` and `GuzzleHttp\PostFileInterface` have been moved to
+ `GuzzleHttp\Post`. This interface has been simplified and now allows the
+ addition of arbitrary headers.
+- Custom headers like `GuzzleHttp\Message\Header\Link` have been removed. Most
+ of the custom headers are now handled separately in specific
+ subscribers/plugins, and `GuzzleHttp\Message\HeaderValues::parseParams()` has
+ been updated to properly handle headers that contain parameters (like the
+ `Link` header).
+
+#### Responses
+
+- `GuzzleHttp\Message\Response::getInfo()` and
+ `GuzzleHttp\Message\Response::setInfo()` have been removed. Use the event
+ system to retrieve this type of information.
+- `GuzzleHttp\Message\Response::getRawHeaders()` has been removed.
+- `GuzzleHttp\Message\Response::getMessage()` has been removed.
+- `GuzzleHttp\Message\Response::calculateAge()` and other cache specific
+ methods have moved to the CacheSubscriber.
+- Header specific helper functions like `getContentMd5()` have been removed.
+ Just use `getHeader('Content-MD5')` instead.
+- `GuzzleHttp\Message\Response::setRequest()` and
+ `GuzzleHttp\Message\Response::getRequest()` have been removed. Use the event
+ system to work with request and response objects as a transaction.
+- `GuzzleHttp\Message\Response::getRedirectCount()` has been removed. Use the
+ Redirect subscriber instead.
+- `GuzzleHttp\Message\Response::isSuccessful()` and other related methods have
+ been removed. Use `getStatusCode()` instead.
+
+#### Streaming responses
+
+Streaming requests can now be created by a client directly, returning a
+`GuzzleHttp\Message\ResponseInterface` object that contains a body stream
+referencing an open PHP HTTP stream.
+
+```php
+// 3.0
+use Guzzle\Stream\PhpStreamRequestFactory;
+$request = $client->get('/');
+$factory = new PhpStreamRequestFactory();
+$stream = $factory->fromRequest($request);
+$data = $stream->read(1024);
+
+// 4.0
+$response = $client->get('/', ['stream' => true]);
+// Read some data off of the stream in the response body
+$data = $response->getBody()->read(1024);
+```
+
+#### Redirects
+
+The `configureRedirects()` method has been removed in favor of a
+`allow_redirects` request option.
+
+```php
+// Standard redirects with a default of a max of 5 redirects
+$request = $client->createRequest('GET', '/', ['allow_redirects' => true]);
+
+// Strict redirects with a custom number of redirects
+$request = $client->createRequest('GET', '/', [
+ 'allow_redirects' => ['max' => 5, 'strict' => true]
+]);
+```
+
+#### EntityBody
+
+EntityBody interfaces and classes have been removed or moved to
+`GuzzleHttp\Stream`. All classes and interfaces that once required
+`GuzzleHttp\EntityBodyInterface` now require
+`GuzzleHttp\Stream\StreamInterface`. Creating a new body for a request no
+longer uses `GuzzleHttp\EntityBody::factory` but now uses
+`GuzzleHttp\Stream\Stream::factory` or even better:
+`GuzzleHttp\Stream\create()`.
+
+- `Guzzle\Http\EntityBodyInterface` is now `GuzzleHttp\Stream\StreamInterface`
+- `Guzzle\Http\EntityBody` is now `GuzzleHttp\Stream\Stream`
+- `Guzzle\Http\CachingEntityBody` is now `GuzzleHttp\Stream\CachingStream`
+- `Guzzle\Http\ReadLimitEntityBody` is now `GuzzleHttp\Stream\LimitStream`
+- `Guzzle\Http\IoEmittyinEntityBody` has been removed.
+
+#### Request lifecycle events
+
+Requests previously submitted a large number of requests. The number of events
+emitted over the lifecycle of a request has been significantly reduced to make
+it easier to understand how to extend the behavior of a request. All events
+emitted during the lifecycle of a request now emit a custom
+`GuzzleHttp\Event\EventInterface` object that contains context providing
+methods and a way in which to modify the transaction at that specific point in
+time (e.g., intercept the request and set a response on the transaction).
+
+- `request.before_send` has been renamed to `before` and now emits a
+ `GuzzleHttp\Event\BeforeEvent`
+- `request.complete` has been renamed to `complete` and now emits a
+ `GuzzleHttp\Event\CompleteEvent`.
+- `request.sent` has been removed. Use `complete`.
+- `request.success` has been removed. Use `complete`.
+- `error` is now an event that emits a `GuzzleHttp\Event\ErrorEvent`.
+- `request.exception` has been removed. Use `error`.
+- `request.receive.status_line` has been removed.
+- `curl.callback.progress` has been removed. Use a custom `StreamInterface` to
+ maintain a status update.
+- `curl.callback.write` has been removed. Use a custom `StreamInterface` to
+ intercept writes.
+- `curl.callback.read` has been removed. Use a custom `StreamInterface` to
+ intercept reads.
+
+`headers` is a new event that is emitted after the response headers of a
+request have been received before the body of the response is downloaded. This
+event emits a `GuzzleHttp\Event\HeadersEvent`.
+
+You can intercept a request and inject a response using the `intercept()` event
+of a `GuzzleHttp\Event\BeforeEvent`, `GuzzleHttp\Event\CompleteEvent`, and
+`GuzzleHttp\Event\ErrorEvent` event.
+
+See: http://docs.guzzlephp.org/en/latest/events.html
+
+## Inflection
+
+The `Guzzle\Inflection` namespace has been removed. This is not a core concern
+of Guzzle.
+
+## Iterator
+
+The `Guzzle\Iterator` namespace has been removed.
+
+- `Guzzle\Iterator\AppendIterator`, `Guzzle\Iterator\ChunkedIterator`, and
+ `Guzzle\Iterator\MethodProxyIterator` are nice, but not a core requirement of
+ Guzzle itself.
+- `Guzzle\Iterator\FilterIterator` is no longer needed because an equivalent
+ class is shipped with PHP 5.4.
+- `Guzzle\Iterator\MapIterator` is not really needed when using PHP 5.5 because
+ it's easier to just wrap an iterator in a generator that maps values.
+
+For a replacement of these iterators, see https://github.com/nikic/iter
+
+## Log
+
+The LogPlugin has moved to https://github.com/guzzle/log-subscriber. The
+`Guzzle\Log` namespace has been removed. Guzzle now relies on
+`Psr\Log\LoggerInterface` for all logging. The MessageFormatter class has been
+moved to `GuzzleHttp\Subscriber\Log\Formatter`.
+
+## Parser
+
+The `Guzzle\Parser` namespace has been removed. This was previously used to
+make it possible to plug in custom parsers for cookies, messages, URI
+templates, and URLs; however, this level of complexity is not needed in Guzzle
+so it has been removed.
+
+- Cookie: Cookie parsing logic has been moved to
+ `GuzzleHttp\Cookie\SetCookie::fromString`.
+- Message: Message parsing logic for both requests and responses has been moved
+ to `GuzzleHttp\Message\MessageFactory::fromMessage`. Message parsing is only
+ used in debugging or deserializing messages, so it doesn't make sense for
+ Guzzle as a library to add this level of complexity to parsing messages.
+- UriTemplate: URI template parsing has been moved to
+ `GuzzleHttp\UriTemplate`. The Guzzle library will automatically use the PECL
+ URI template library if it is installed.
+- Url: URL parsing is now performed in `GuzzleHttp\Url::fromString` (previously
+ it was `Guzzle\Http\Url::factory()`). If custom URL parsing is necessary,
+ then developers are free to subclass `GuzzleHttp\Url`.
+
+## Plugin
+
+The `Guzzle\Plugin` namespace has been renamed to `GuzzleHttp\Subscriber`.
+Several plugins are shipping with the core Guzzle library under this namespace.
+
+- `GuzzleHttp\Subscriber\Cookie`: Replaces the old CookiePlugin. Cookie jar
+ code has moved to `GuzzleHttp\Cookie`.
+- `GuzzleHttp\Subscriber\History`: Replaces the old HistoryPlugin.
+- `GuzzleHttp\Subscriber\HttpError`: Throws errors when a bad HTTP response is
+ received.
+- `GuzzleHttp\Subscriber\Mock`: Replaces the old MockPlugin.
+- `GuzzleHttp\Subscriber\Prepare`: Prepares the body of a request just before
+ sending. This subscriber is attached to all requests by default.
+- `GuzzleHttp\Subscriber\Redirect`: Replaces the RedirectPlugin.
+
+The following plugins have been removed (third-parties are free to re-implement
+these if needed):
+
+- `GuzzleHttp\Plugin\Async` has been removed.
+- `GuzzleHttp\Plugin\CurlAuth` has been removed.
+- `GuzzleHttp\Plugin\ErrorResponse\ErrorResponsePlugin` has been removed. This
+ functionality should instead be implemented with event listeners that occur
+ after normal response parsing occurs in the guzzle/command package.
+
+The following plugins are not part of the core Guzzle package, but are provided
+in separate repositories:
+
+- `Guzzle\Http\Plugin\BackoffPlugin` has been rewritten to be much simpler
+ to build custom retry policies using simple functions rather than various
+ chained classes. See: https://github.com/guzzle/retry-subscriber
+- `Guzzle\Http\Plugin\Cache\CachePlugin` has moved to
+ https://github.com/guzzle/cache-subscriber
+- `Guzzle\Http\Plugin\Log\LogPlugin` has moved to
+ https://github.com/guzzle/log-subscriber
+- `Guzzle\Http\Plugin\Md5\Md5Plugin` has moved to
+ https://github.com/guzzle/message-integrity-subscriber
+- `Guzzle\Http\Plugin\Mock\MockPlugin` has moved to
+ `GuzzleHttp\Subscriber\MockSubscriber`.
+- `Guzzle\Http\Plugin\Oauth\OauthPlugin` has moved to
+ https://github.com/guzzle/oauth-subscriber
+
+## Service
+
+The service description layer of Guzzle has moved into two separate packages:
+
+- http://github.com/guzzle/command Provides a high level abstraction over web
+ services by representing web service operations using commands.
+- http://github.com/guzzle/guzzle-services Provides an implementation of
+ guzzle/command that provides request serialization and response parsing using
+ Guzzle service descriptions.
+
+## Stream
+
+Stream have moved to a separate package available at
+https://github.com/guzzle/streams.
+
+`Guzzle\Stream\StreamInterface` has been given a large update to cleanly take
+on the responsibilities of `Guzzle\Http\EntityBody` and
+`Guzzle\Http\EntityBodyInterface` now that they have been removed. The number
+of methods implemented by the `StreamInterface` has been drastically reduced to
+allow developers to more easily extend and decorate stream behavior.
+
+## Removed methods from StreamInterface
+
+- `getStream` and `setStream` have been removed to better encapsulate streams.
+- `getMetadata` and `setMetadata` have been removed in favor of
+ `GuzzleHttp\Stream\MetadataStreamInterface`.
+- `getWrapper`, `getWrapperData`, `getStreamType`, and `getUri` have all been
+ removed. This data is accessible when
+ using streams that implement `GuzzleHttp\Stream\MetadataStreamInterface`.
+- `rewind` has been removed. Use `seek(0)` for a similar behavior.
+
+## Renamed methods
+
+- `detachStream` has been renamed to `detach`.
+- `feof` has been renamed to `eof`.
+- `ftell` has been renamed to `tell`.
+- `readLine` has moved from an instance method to a static class method of
+ `GuzzleHttp\Stream\Stream`.
+
+## Metadata streams
+
+`GuzzleHttp\Stream\MetadataStreamInterface` has been added to denote streams
+that contain additional metadata accessible via `getMetadata()`.
+`GuzzleHttp\Stream\StreamInterface::getMetadata` and
+`GuzzleHttp\Stream\StreamInterface::setMetadata` have been removed.
+
+## StreamRequestFactory
+
+The entire concept of the StreamRequestFactory has been removed. The way this
+was used in Guzzle 3 broke the actual interface of sending streaming requests
+(instead of getting back a Response, you got a StreamInterface). Streaming
+PHP requests are now implemented through the `GuzzleHttp\Adapter\StreamAdapter`.
+
+3.6 to 3.7
+----------
+
+### Deprecations
+
+- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.:
+
+```php
+\Guzzle\Common\Version::$emitWarnings = true;
+```
+
+The following APIs and options have been marked as deprecated:
+
+- Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead.
+- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+- Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
+- Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
+- Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
+- Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
+- Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
+- Marked `Guzzle\Common\Collection::inject()` as deprecated.
+- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use
+ `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or
+ `$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));`
+
+3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational
+request methods. When paired with a client's configuration settings, these options allow you to specify default settings
+for various aspects of a request. Because these options make other previous configuration options redundant, several
+configuration options and methods of a client and AbstractCommand have been deprecated.
+
+- Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`.
+- Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`.
+- Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')`
+- Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0
+
+ $command = $client->getCommand('foo', array(
+ 'command.headers' => array('Test' => '123'),
+ 'command.response_body' => '/path/to/file'
+ ));
+
+ // Should be changed to:
+
+ $command = $client->getCommand('foo', array(
+ 'command.request_options' => array(
+ 'headers' => array('Test' => '123'),
+ 'save_as' => '/path/to/file'
+ )
+ ));
+
+### Interface changes
+
+Additions and changes (you will need to update any implementations or subclasses you may have created):
+
+- Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
+ createRequest, head, delete, put, patch, post, options, prepareRequest
+- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
+- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
+- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
+ `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
+ resource, string, or EntityBody into the $options parameter to specify the download location of the response.
+- Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
+ default `array()`
+- Added `Guzzle\Stream\StreamInterface::isRepeatable`
+- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
+
+The following methods were removed from interfaces. All of these methods are still available in the concrete classes
+that implement them, but you should update your code to use alternative methods:
+
+- Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
+ `$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
+ `$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or
+ `$client->setDefaultOption('headers/{header_name}', 'value')`. or
+ `$client->setDefaultOption('headers', array('header_name' => 'value'))`.
+- Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`.
+- Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail.
+- Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail.
+- Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail.
+- Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin.
+- Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin.
+- Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin.
+
+### Cache plugin breaking changes
+
+- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
+ CacheStorageInterface. These two objects and interface will be removed in a future version.
+- Always setting X-cache headers on cached responses
+- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
+- `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
+ $request, Response $response);`
+- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
+- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
+- Added `CacheStorageInterface::purge($url)`
+- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
+ $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
+ CanCacheStrategyInterface $canCache = null)`
+- Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`
+
+3.5 to 3.6
+----------
+
+* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
+* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
+* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
+ For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader().
+ Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request.
+* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
+ HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
+ CacheControl header implementation.
+* Moved getLinks() from Response to just be used on a Link header object.
+
+If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the
+HeaderInterface (e.g. toArray(), getAll(), etc.).
+
+### Interface changes
+
+* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
+* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
+* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
+ Guzzle\Http\Curl\RequestMediator
+* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
+* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
+* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
+
+### Removed deprecated functions
+
+* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
+* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
+
+### Deprecations
+
+* The ability to case-insensitively search for header values
+* Guzzle\Http\Message\Header::hasExactHeader
+* Guzzle\Http\Message\Header::raw. Use getAll()
+* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
+ instead.
+
+### Other changes
+
+* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
+* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle
+ directly via interfaces
+* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
+ but are a no-op until removed.
+* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a
+ `Guzzle\Service\Command\ArrayCommandInterface`.
+* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
+ on a request while the request is still being transferred
+* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
+
+3.3 to 3.4
+----------
+
+Base URLs of a client now follow the rules of http://tools.ietf.org/html/rfc3986#section-5.2.2 when merging URLs.
+
+3.2 to 3.3
+----------
+
+### Response::getEtag() quote stripping removed
+
+`Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header
+
+### Removed `Guzzle\Http\Utils`
+
+The `Guzzle\Http\Utils` class was removed. This class was only used for testing.
+
+### Stream wrapper and type
+
+`Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getStreamType()` are no longer converted to lowercase.
+
+### curl.emit_io became emit_io
+
+Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the
+'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
+
+3.1 to 3.2
+----------
+
+### CurlMulti is no longer reused globally
+
+Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added
+to a single client can pollute requests dispatched from other clients.
+
+If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the
+ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is
+created.
+
+```php
+$multi = new Guzzle\Http\Curl\CurlMulti();
+$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json');
+$builder->addListener('service_builder.create_client', function ($event) use ($multi) {
+ $event['client']->setCurlMulti($multi);
+}
+});
+```
+
+### No default path
+
+URLs no longer have a default path value of '/' if no path was specified.
+
+Before:
+
+```php
+$request = $client->get('http://www.foo.com');
+echo $request->getUrl();
+// >> http://www.foo.com/
+```
+
+After:
+
+```php
+$request = $client->get('http://www.foo.com');
+echo $request->getUrl();
+// >> http://www.foo.com
+```
+
+### Less verbose BadResponseException
+
+The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and
+response information. You can, however, get access to the request and response object by calling `getRequest()` or
+`getResponse()` on the exception object.
+
+### Query parameter aggregation
+
+Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a
+setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is
+responsible for handling the aggregation of multi-valued query string variables into a flattened hash.
+
+2.8 to 3.x
+----------
+
+### Guzzle\Service\Inspector
+
+Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig`
+
+**Before**
+
+```php
+use Guzzle\Service\Inspector;
+
+class YourClient extends \Guzzle\Service\Client
+{
+ public static function factory($config = array())
+ {
+ $default = array();
+ $required = array('base_url', 'username', 'api_key');
+ $config = Inspector::fromConfig($config, $default, $required);
+
+ $client = new self(
+ $config->get('base_url'),
+ $config->get('username'),
+ $config->get('api_key')
+ );
+ $client->setConfig($config);
+
+ $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));
+
+ return $client;
+ }
+```
+
+**After**
+
+```php
+use Guzzle\Common\Collection;
+
+class YourClient extends \Guzzle\Service\Client
+{
+ public static function factory($config = array())
+ {
+ $default = array();
+ $required = array('base_url', 'username', 'api_key');
+ $config = Collection::fromConfig($config, $default, $required);
+
+ $client = new self(
+ $config->get('base_url'),
+ $config->get('username'),
+ $config->get('api_key')
+ );
+ $client->setConfig($config);
+
+ $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));
+
+ return $client;
+ }
+```
+
+### Convert XML Service Descriptions to JSON
+
+**Before**
+
+```xml
+
+
+
+
+
+ Get a list of groups
+
+
+ Uses a search query to get a list of groups
+
+
+
+ Create a group
+
+
+
+
+ Delete a group by ID
+
+
+
+
+
+
+ Update a group
+
+
+
+
+
+
+```
+
+**After**
+
+```json
+{
+ "name": "Zendesk REST API v2",
+ "apiVersion": "2012-12-31",
+ "description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users",
+ "operations": {
+ "list_groups": {
+ "httpMethod":"GET",
+ "uri": "groups.json",
+ "summary": "Get a list of groups"
+ },
+ "search_groups":{
+ "httpMethod":"GET",
+ "uri": "search.json?query=\"{query} type:group\"",
+ "summary": "Uses a search query to get a list of groups",
+ "parameters":{
+ "query":{
+ "location": "uri",
+ "description":"Zendesk Search Query",
+ "type": "string",
+ "required": true
+ }
+ }
+ },
+ "create_group": {
+ "httpMethod":"POST",
+ "uri": "groups.json",
+ "summary": "Create a group",
+ "parameters":{
+ "data": {
+ "type": "array",
+ "location": "body",
+ "description":"Group JSON",
+ "filters": "json_encode",
+ "required": true
+ },
+ "Content-Type":{
+ "type": "string",
+ "location":"header",
+ "static": "application/json"
+ }
+ }
+ },
+ "delete_group": {
+ "httpMethod":"DELETE",
+ "uri": "groups/{id}.json",
+ "summary": "Delete a group",
+ "parameters":{
+ "id":{
+ "location": "uri",
+ "description":"Group to delete by ID",
+ "type": "integer",
+ "required": true
+ }
+ }
+ },
+ "get_group": {
+ "httpMethod":"GET",
+ "uri": "groups/{id}.json",
+ "summary": "Get a ticket",
+ "parameters":{
+ "id":{
+ "location": "uri",
+ "description":"Group to get by ID",
+ "type": "integer",
+ "required": true
+ }
+ }
+ },
+ "update_group": {
+ "httpMethod":"PUT",
+ "uri": "groups/{id}.json",
+ "summary": "Update a group",
+ "parameters":{
+ "id": {
+ "location": "uri",
+ "description":"Group to update by ID",
+ "type": "integer",
+ "required": true
+ },
+ "data": {
+ "type": "array",
+ "location": "body",
+ "description":"Group JSON",
+ "filters": "json_encode",
+ "required": true
+ },
+ "Content-Type":{
+ "type": "string",
+ "location":"header",
+ "static": "application/json"
+ }
+ }
+ }
+}
+```
+
+### Guzzle\Service\Description\ServiceDescription
+
+Commands are now called Operations
+
+**Before**
+
+```php
+use Guzzle\Service\Description\ServiceDescription;
+
+$sd = new ServiceDescription();
+$sd->getCommands(); // @returns ApiCommandInterface[]
+$sd->hasCommand($name);
+$sd->getCommand($name); // @returns ApiCommandInterface|null
+$sd->addCommand($command); // @param ApiCommandInterface $command
+```
+
+**After**
+
+```php
+use Guzzle\Service\Description\ServiceDescription;
+
+$sd = new ServiceDescription();
+$sd->getOperations(); // @returns OperationInterface[]
+$sd->hasOperation($name);
+$sd->getOperation($name); // @returns OperationInterface|null
+$sd->addOperation($operation); // @param OperationInterface $operation
+```
+
+### Guzzle\Common\Inflection\Inflector
+
+Namespace is now `Guzzle\Inflection\Inflector`
+
+### Guzzle\Http\Plugin
+
+Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below.
+
+### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log
+
+Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively.
+
+**Before**
+
+```php
+use Guzzle\Common\Log\ClosureLogAdapter;
+use Guzzle\Http\Plugin\LogPlugin;
+
+/** @var \Guzzle\Http\Client */
+$client;
+
+// $verbosity is an integer indicating desired message verbosity level
+$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE);
+```
+
+**After**
+
+```php
+use Guzzle\Log\ClosureLogAdapter;
+use Guzzle\Log\MessageFormatter;
+use Guzzle\Plugin\Log\LogPlugin;
+
+/** @var \Guzzle\Http\Client */
+$client;
+
+// $format is a string indicating desired message format -- @see MessageFormatter
+$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT);
+```
+
+### Guzzle\Http\Plugin\CurlAuthPlugin
+
+Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`.
+
+### Guzzle\Http\Plugin\ExponentialBackoffPlugin
+
+Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes.
+
+**Before**
+
+```php
+use Guzzle\Http\Plugin\ExponentialBackoffPlugin;
+
+$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge(
+ ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429)
+ ));
+
+$client->addSubscriber($backoffPlugin);
+```
+
+**After**
+
+```php
+use Guzzle\Plugin\Backoff\BackoffPlugin;
+use Guzzle\Plugin\Backoff\HttpBackoffStrategy;
+
+// Use convenient factory method instead -- see implementation for ideas of what
+// you can do with chaining backoff strategies
+$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge(
+ HttpBackoffStrategy::getDefaultFailureCodes(), array(429)
+ ));
+$client->addSubscriber($backoffPlugin);
+```
+
+### Known Issues
+
+#### [BUG] Accept-Encoding header behavior changed unintentionally.
+
+(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e)
+
+In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to
+properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen.
+See issue #217 for a workaround, or use a version containing the fix.
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/composer.json b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/composer.json
new file mode 100644
index 000000000..02ab73c0f
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/composer.json
@@ -0,0 +1,59 @@
+{
+ "name": "guzzlehttp/guzzle",
+ "type": "library",
+ "description": "Guzzle is a PHP HTTP client library",
+ "keywords": [
+ "framework",
+ "http",
+ "rest",
+ "web service",
+ "curl",
+ "client",
+ "HTTP client"
+ ],
+ "homepage": "http://guzzlephp.org/",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.5",
+ "ext-json": "*",
+ "symfony/polyfill-intl-idn": "^1.11",
+ "guzzlehttp/promises": "^1.0",
+ "guzzlehttp/psr7": "^1.6.1"
+ },
+ "require-dev": {
+ "ext-curl": "*",
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
+ "psr/log": "^1.1"
+ },
+ "suggest": {
+ "psr/log": "Required for using the Log middleware"
+ },
+ "config": {
+ "sort-packages": true
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.5-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "GuzzleHttp\\Tests\\": "tests/"
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Client.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Client.php
new file mode 100644
index 000000000..cd9a63574
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Client.php
@@ -0,0 +1,502 @@
+ 'http://www.foo.com/1.0/',
+ * 'timeout' => 0,
+ * 'allow_redirects' => false,
+ * 'proxy' => '192.168.16.1:10'
+ * ]);
+ *
+ * Client configuration settings include the following options:
+ *
+ * - handler: (callable) Function that transfers HTTP requests over the
+ * wire. The function is called with a Psr7\Http\Message\RequestInterface
+ * and array of transfer options, and must return a
+ * GuzzleHttp\Promise\PromiseInterface that is fulfilled with a
+ * Psr7\Http\Message\ResponseInterface on success. "handler" is a
+ * constructor only option that cannot be overridden in per/request
+ * options. If no handler is provided, a default handler will be created
+ * that enables all of the request options below by attaching all of the
+ * default middleware to the handler.
+ * - base_uri: (string|UriInterface) Base URI of the client that is merged
+ * into relative URIs. Can be a string or instance of UriInterface.
+ * - **: any request option
+ *
+ * @param array $config Client configuration settings.
+ *
+ * @see \GuzzleHttp\RequestOptions for a list of available request options.
+ */
+ public function __construct(array $config = [])
+ {
+ if (!isset($config['handler'])) {
+ $config['handler'] = HandlerStack::create();
+ } elseif (!is_callable($config['handler'])) {
+ throw new \InvalidArgumentException('handler must be a callable');
+ }
+
+ // Convert the base_uri to a UriInterface
+ if (isset($config['base_uri'])) {
+ $config['base_uri'] = Psr7\uri_for($config['base_uri']);
+ }
+
+ $this->configureDefaults($config);
+ }
+
+ /**
+ * @param string $method
+ * @param array $args
+ *
+ * @return Promise\PromiseInterface
+ */
+ public function __call($method, $args)
+ {
+ if (count($args) < 1) {
+ throw new \InvalidArgumentException('Magic request methods require a URI and optional options array');
+ }
+
+ $uri = $args[0];
+ $opts = isset($args[1]) ? $args[1] : [];
+
+ return substr($method, -5) === 'Async'
+ ? $this->requestAsync(substr($method, 0, -5), $uri, $opts)
+ : $this->request($method, $uri, $opts);
+ }
+
+ /**
+ * Asynchronously send an HTTP request.
+ *
+ * @param array $options Request options to apply to the given
+ * request and to the transfer. See \GuzzleHttp\RequestOptions.
+ *
+ * @return Promise\PromiseInterface
+ */
+ public function sendAsync(RequestInterface $request, array $options = [])
+ {
+ // Merge the base URI into the request URI if needed.
+ $options = $this->prepareDefaults($options);
+
+ return $this->transfer(
+ $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')),
+ $options
+ );
+ }
+
+ /**
+ * Send an HTTP request.
+ *
+ * @param array $options Request options to apply to the given
+ * request and to the transfer. See \GuzzleHttp\RequestOptions.
+ *
+ * @return ResponseInterface
+ * @throws GuzzleException
+ */
+ public function send(RequestInterface $request, array $options = [])
+ {
+ $options[RequestOptions::SYNCHRONOUS] = true;
+ return $this->sendAsync($request, $options)->wait();
+ }
+
+ /**
+ * Create and send an asynchronous HTTP request.
+ *
+ * Use an absolute path to override the base path of the client, or a
+ * relative path to append to the base path of the client. The URL can
+ * contain the query string as well. Use an array to provide a URL
+ * template and additional variables to use in the URL template expansion.
+ *
+ * @param string $method HTTP method
+ * @param string|UriInterface $uri URI object or string.
+ * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions.
+ *
+ * @return Promise\PromiseInterface
+ */
+ public function requestAsync($method, $uri = '', array $options = [])
+ {
+ $options = $this->prepareDefaults($options);
+ // Remove request modifying parameter because it can be done up-front.
+ $headers = isset($options['headers']) ? $options['headers'] : [];
+ $body = isset($options['body']) ? $options['body'] : null;
+ $version = isset($options['version']) ? $options['version'] : '1.1';
+ // Merge the URI into the base URI.
+ $uri = $this->buildUri($uri, $options);
+ if (is_array($body)) {
+ $this->invalidBody();
+ }
+ $request = new Psr7\Request($method, $uri, $headers, $body, $version);
+ // Remove the option so that they are not doubly-applied.
+ unset($options['headers'], $options['body'], $options['version']);
+
+ return $this->transfer($request, $options);
+ }
+
+ /**
+ * Create and send an HTTP request.
+ *
+ * Use an absolute path to override the base path of the client, or a
+ * relative path to append to the base path of the client. The URL can
+ * contain the query string as well.
+ *
+ * @param string $method HTTP method.
+ * @param string|UriInterface $uri URI object or string.
+ * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions.
+ *
+ * @return ResponseInterface
+ * @throws GuzzleException
+ */
+ public function request($method, $uri = '', array $options = [])
+ {
+ $options[RequestOptions::SYNCHRONOUS] = true;
+ return $this->requestAsync($method, $uri, $options)->wait();
+ }
+
+ /**
+ * Get a client configuration option.
+ *
+ * These options include default request options of the client, a "handler"
+ * (if utilized by the concrete client), and a "base_uri" if utilized by
+ * the concrete client.
+ *
+ * @param string|null $option The config option to retrieve.
+ *
+ * @return mixed
+ */
+ public function getConfig($option = null)
+ {
+ return $option === null
+ ? $this->config
+ : (isset($this->config[$option]) ? $this->config[$option] : null);
+ }
+
+ /**
+ * @param string|null $uri
+ *
+ * @return UriInterface
+ */
+ private function buildUri($uri, array $config)
+ {
+ // for BC we accept null which would otherwise fail in uri_for
+ $uri = Psr7\uri_for($uri === null ? '' : $uri);
+
+ if (isset($config['base_uri'])) {
+ $uri = Psr7\UriResolver::resolve(Psr7\uri_for($config['base_uri']), $uri);
+ }
+
+ if (isset($config['idn_conversion']) && ($config['idn_conversion'] !== false)) {
+ $idnOptions = ($config['idn_conversion'] === true) ? IDNA_DEFAULT : $config['idn_conversion'];
+ $uri = Utils::idnUriConvert($uri, $idnOptions);
+ }
+
+ return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;
+ }
+
+ /**
+ * Configures the default options for a client.
+ *
+ * @param array $config
+ * @return void
+ */
+ private function configureDefaults(array $config)
+ {
+ $defaults = [
+ 'allow_redirects' => RedirectMiddleware::$defaultSettings,
+ 'http_errors' => true,
+ 'decode_content' => true,
+ 'verify' => true,
+ 'cookies' => false,
+ 'idn_conversion' => true,
+ ];
+
+ // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set.
+
+ // We can only trust the HTTP_PROXY environment variable in a CLI
+ // process due to the fact that PHP has no reliable mechanism to
+ // get environment variables that start with "HTTP_".
+ if (php_sapi_name() === 'cli' && getenv('HTTP_PROXY')) {
+ $defaults['proxy']['http'] = getenv('HTTP_PROXY');
+ }
+
+ if ($proxy = getenv('HTTPS_PROXY')) {
+ $defaults['proxy']['https'] = $proxy;
+ }
+
+ if ($noProxy = getenv('NO_PROXY')) {
+ $cleanedNoProxy = str_replace(' ', '', $noProxy);
+ $defaults['proxy']['no'] = explode(',', $cleanedNoProxy);
+ }
+
+ $this->config = $config + $defaults;
+
+ if (!empty($config['cookies']) && $config['cookies'] === true) {
+ $this->config['cookies'] = new CookieJar();
+ }
+
+ // Add the default user-agent header.
+ if (!isset($this->config['headers'])) {
+ $this->config['headers'] = ['User-Agent' => default_user_agent()];
+ } else {
+ // Add the User-Agent header if one was not already set.
+ foreach (array_keys($this->config['headers']) as $name) {
+ if (strtolower($name) === 'user-agent') {
+ return;
+ }
+ }
+ $this->config['headers']['User-Agent'] = default_user_agent();
+ }
+ }
+
+ /**
+ * Merges default options into the array.
+ *
+ * @param array $options Options to modify by reference
+ *
+ * @return array
+ */
+ private function prepareDefaults(array $options)
+ {
+ $defaults = $this->config;
+
+ if (!empty($defaults['headers'])) {
+ // Default headers are only added if they are not present.
+ $defaults['_conditional'] = $defaults['headers'];
+ unset($defaults['headers']);
+ }
+
+ // Special handling for headers is required as they are added as
+ // conditional headers and as headers passed to a request ctor.
+ if (array_key_exists('headers', $options)) {
+ // Allows default headers to be unset.
+ if ($options['headers'] === null) {
+ $defaults['_conditional'] = [];
+ unset($options['headers']);
+ } elseif (!is_array($options['headers'])) {
+ throw new \InvalidArgumentException('headers must be an array');
+ }
+ }
+
+ // Shallow merge defaults underneath options.
+ $result = $options + $defaults;
+
+ // Remove null values.
+ foreach ($result as $k => $v) {
+ if ($v === null) {
+ unset($result[$k]);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Transfers the given request and applies request options.
+ *
+ * The URI of the request is not modified and the request options are used
+ * as-is without merging in default options.
+ *
+ * @param array $options See \GuzzleHttp\RequestOptions.
+ *
+ * @return Promise\PromiseInterface
+ */
+ private function transfer(RequestInterface $request, array $options)
+ {
+ // save_to -> sink
+ if (isset($options['save_to'])) {
+ $options['sink'] = $options['save_to'];
+ unset($options['save_to']);
+ }
+
+ // exceptions -> http_errors
+ if (isset($options['exceptions'])) {
+ $options['http_errors'] = $options['exceptions'];
+ unset($options['exceptions']);
+ }
+
+ $request = $this->applyOptions($request, $options);
+ /** @var HandlerStack $handler */
+ $handler = $options['handler'];
+
+ try {
+ return Promise\promise_for($handler($request, $options));
+ } catch (\Exception $e) {
+ return Promise\rejection_for($e);
+ }
+ }
+
+ /**
+ * Applies the array of request options to a request.
+ *
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return RequestInterface
+ */
+ private function applyOptions(RequestInterface $request, array &$options)
+ {
+ $modify = [
+ 'set_headers' => [],
+ ];
+
+ if (isset($options['headers'])) {
+ $modify['set_headers'] = $options['headers'];
+ unset($options['headers']);
+ }
+
+ if (isset($options['form_params'])) {
+ if (isset($options['multipart'])) {
+ throw new \InvalidArgumentException('You cannot use '
+ . 'form_params and multipart at the same time. Use the '
+ . 'form_params option if you want to send application/'
+ . 'x-www-form-urlencoded requests, and the multipart '
+ . 'option to send multipart/form-data requests.');
+ }
+ $options['body'] = http_build_query($options['form_params'], '', '&');
+ unset($options['form_params']);
+ // Ensure that we don't have the header in different case and set the new value.
+ $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
+ $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded';
+ }
+
+ if (isset($options['multipart'])) {
+ $options['body'] = new Psr7\MultipartStream($options['multipart']);
+ unset($options['multipart']);
+ }
+
+ if (isset($options['json'])) {
+ $options['body'] = \GuzzleHttp\json_encode($options['json']);
+ unset($options['json']);
+ // Ensure that we don't have the header in different case and set the new value.
+ $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
+ $options['_conditional']['Content-Type'] = 'application/json';
+ }
+
+ if (!empty($options['decode_content'])
+ && $options['decode_content'] !== true
+ ) {
+ // Ensure that we don't have the header in different case and set the new value.
+ $options['_conditional'] = Psr7\_caseless_remove(['Accept-Encoding'], $options['_conditional']);
+ $modify['set_headers']['Accept-Encoding'] = $options['decode_content'];
+ }
+
+ if (isset($options['body'])) {
+ if (is_array($options['body'])) {
+ $this->invalidBody();
+ }
+ $modify['body'] = Psr7\stream_for($options['body']);
+ unset($options['body']);
+ }
+
+ if (!empty($options['auth']) && is_array($options['auth'])) {
+ $value = $options['auth'];
+ $type = isset($value[2]) ? strtolower($value[2]) : 'basic';
+ switch ($type) {
+ case 'basic':
+ // Ensure that we don't have the header in different case and set the new value.
+ $modify['set_headers'] = Psr7\_caseless_remove(['Authorization'], $modify['set_headers']);
+ $modify['set_headers']['Authorization'] = 'Basic '
+ . base64_encode("$value[0]:$value[1]");
+ break;
+ case 'digest':
+ // @todo: Do not rely on curl
+ $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST;
+ $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]";
+ break;
+ case 'ntlm':
+ $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_NTLM;
+ $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]";
+ break;
+ }
+ }
+
+ if (isset($options['query'])) {
+ $value = $options['query'];
+ if (is_array($value)) {
+ $value = http_build_query($value, null, '&', PHP_QUERY_RFC3986);
+ }
+ if (!is_string($value)) {
+ throw new \InvalidArgumentException('query must be a string or array');
+ }
+ $modify['query'] = $value;
+ unset($options['query']);
+ }
+
+ // Ensure that sink is not an invalid value.
+ if (isset($options['sink'])) {
+ // TODO: Add more sink validation?
+ if (is_bool($options['sink'])) {
+ throw new \InvalidArgumentException('sink must not be a boolean');
+ }
+ }
+
+ $request = Psr7\modify_request($request, $modify);
+ if ($request->getBody() instanceof Psr7\MultipartStream) {
+ // Use a multipart/form-data POST if a Content-Type is not set.
+ // Ensure that we don't have the header in different case and set the new value.
+ $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
+ $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary='
+ . $request->getBody()->getBoundary();
+ }
+
+ // Merge in conditional headers if they are not present.
+ if (isset($options['_conditional'])) {
+ // Build up the changes so it's in a single clone of the message.
+ $modify = [];
+ foreach ($options['_conditional'] as $k => $v) {
+ if (!$request->hasHeader($k)) {
+ $modify['set_headers'][$k] = $v;
+ }
+ }
+ $request = Psr7\modify_request($request, $modify);
+ // Don't pass this internal value along to middleware/handlers.
+ unset($options['_conditional']);
+ }
+
+ return $request;
+ }
+
+ /**
+ * Throw Exception with pre-set message.
+ * @return void
+ * @throws InvalidArgumentException Invalid body.
+ */
+ private function invalidBody()
+ {
+ throw new \InvalidArgumentException('Passing in the "body" request '
+ . 'option as an array to send a POST request has been deprecated. '
+ . 'Please use the "form_params" request option to send a '
+ . 'application/x-www-form-urlencoded request, or the "multipart" '
+ . 'request option to send a multipart/form-data request.');
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/ClientInterface.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/ClientInterface.php
new file mode 100644
index 000000000..76872dd3a
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/ClientInterface.php
@@ -0,0 +1,87 @@
+strictMode = $strictMode;
+
+ foreach ($cookieArray as $cookie) {
+ if (!($cookie instanceof SetCookie)) {
+ $cookie = new SetCookie($cookie);
+ }
+ $this->setCookie($cookie);
+ }
+ }
+
+ /**
+ * Create a new Cookie jar from an associative array and domain.
+ *
+ * @param array $cookies Cookies to create the jar from
+ * @param string $domain Domain to set the cookies to
+ *
+ * @return self
+ */
+ public static function fromArray(array $cookies, $domain)
+ {
+ $cookieJar = new self();
+ foreach ($cookies as $name => $value) {
+ $cookieJar->setCookie(new SetCookie([
+ 'Domain' => $domain,
+ 'Name' => $name,
+ 'Value' => $value,
+ 'Discard' => true
+ ]));
+ }
+
+ return $cookieJar;
+ }
+
+ /**
+ * @deprecated
+ */
+ public static function getCookieValue($value)
+ {
+ return $value;
+ }
+
+ /**
+ * Evaluate if this cookie should be persisted to storage
+ * that survives between requests.
+ *
+ * @param SetCookie $cookie Being evaluated.
+ * @param bool $allowSessionCookies If we should persist session cookies
+ * @return bool
+ */
+ public static function shouldPersist(
+ SetCookie $cookie,
+ $allowSessionCookies = false
+ ) {
+ if ($cookie->getExpires() || $allowSessionCookies) {
+ if (!$cookie->getDiscard()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Finds and returns the cookie based on the name
+ *
+ * @param string $name cookie name to search for
+ * @return SetCookie|null cookie that was found or null if not found
+ */
+ public function getCookieByName($name)
+ {
+ // don't allow a non string name
+ if ($name === null || !is_scalar($name)) {
+ return null;
+ }
+ foreach ($this->cookies as $cookie) {
+ if ($cookie->getName() !== null && strcasecmp($cookie->getName(), $name) === 0) {
+ return $cookie;
+ }
+ }
+
+ return null;
+ }
+
+ public function toArray()
+ {
+ return array_map(function (SetCookie $cookie) {
+ return $cookie->toArray();
+ }, $this->getIterator()->getArrayCopy());
+ }
+
+ public function clear($domain = null, $path = null, $name = null)
+ {
+ if (!$domain) {
+ $this->cookies = [];
+ return;
+ } elseif (!$path) {
+ $this->cookies = array_filter(
+ $this->cookies,
+ function (SetCookie $cookie) use ($domain) {
+ return !$cookie->matchesDomain($domain);
+ }
+ );
+ } elseif (!$name) {
+ $this->cookies = array_filter(
+ $this->cookies,
+ function (SetCookie $cookie) use ($path, $domain) {
+ return !($cookie->matchesPath($path) &&
+ $cookie->matchesDomain($domain));
+ }
+ );
+ } else {
+ $this->cookies = array_filter(
+ $this->cookies,
+ function (SetCookie $cookie) use ($path, $domain, $name) {
+ return !($cookie->getName() == $name &&
+ $cookie->matchesPath($path) &&
+ $cookie->matchesDomain($domain));
+ }
+ );
+ }
+ }
+
+ public function clearSessionCookies()
+ {
+ $this->cookies = array_filter(
+ $this->cookies,
+ function (SetCookie $cookie) {
+ return !$cookie->getDiscard() && $cookie->getExpires();
+ }
+ );
+ }
+
+ public function setCookie(SetCookie $cookie)
+ {
+ // If the name string is empty (but not 0), ignore the set-cookie
+ // string entirely.
+ $name = $cookie->getName();
+ if (!$name && $name !== '0') {
+ return false;
+ }
+
+ // Only allow cookies with set and valid domain, name, value
+ $result = $cookie->validate();
+ if ($result !== true) {
+ if ($this->strictMode) {
+ throw new \RuntimeException('Invalid cookie: ' . $result);
+ } else {
+ $this->removeCookieIfEmpty($cookie);
+ return false;
+ }
+ }
+
+ // Resolve conflicts with previously set cookies
+ foreach ($this->cookies as $i => $c) {
+
+ // Two cookies are identical, when their path, and domain are
+ // identical.
+ if ($c->getPath() != $cookie->getPath() ||
+ $c->getDomain() != $cookie->getDomain() ||
+ $c->getName() != $cookie->getName()
+ ) {
+ continue;
+ }
+
+ // The previously set cookie is a discard cookie and this one is
+ // not so allow the new cookie to be set
+ if (!$cookie->getDiscard() && $c->getDiscard()) {
+ unset($this->cookies[$i]);
+ continue;
+ }
+
+ // If the new cookie's expiration is further into the future, then
+ // replace the old cookie
+ if ($cookie->getExpires() > $c->getExpires()) {
+ unset($this->cookies[$i]);
+ continue;
+ }
+
+ // If the value has changed, we better change it
+ if ($cookie->getValue() !== $c->getValue()) {
+ unset($this->cookies[$i]);
+ continue;
+ }
+
+ // The cookie exists, so no need to continue
+ return false;
+ }
+
+ $this->cookies[] = $cookie;
+
+ return true;
+ }
+
+ public function count()
+ {
+ return count($this->cookies);
+ }
+
+ public function getIterator()
+ {
+ return new \ArrayIterator(array_values($this->cookies));
+ }
+
+ public function extractCookies(
+ RequestInterface $request,
+ ResponseInterface $response
+ ) {
+ if ($cookieHeader = $response->getHeader('Set-Cookie')) {
+ foreach ($cookieHeader as $cookie) {
+ $sc = SetCookie::fromString($cookie);
+ if (!$sc->getDomain()) {
+ $sc->setDomain($request->getUri()->getHost());
+ }
+ if (0 !== strpos($sc->getPath(), '/')) {
+ $sc->setPath($this->getCookiePathFromRequest($request));
+ }
+ $this->setCookie($sc);
+ }
+ }
+ }
+
+ /**
+ * Computes cookie path following RFC 6265 section 5.1.4
+ *
+ * @link https://tools.ietf.org/html/rfc6265#section-5.1.4
+ *
+ * @param RequestInterface $request
+ * @return string
+ */
+ private function getCookiePathFromRequest(RequestInterface $request)
+ {
+ $uriPath = $request->getUri()->getPath();
+ if ('' === $uriPath) {
+ return '/';
+ }
+ if (0 !== strpos($uriPath, '/')) {
+ return '/';
+ }
+ if ('/' === $uriPath) {
+ return '/';
+ }
+ if (0 === $lastSlashPos = strrpos($uriPath, '/')) {
+ return '/';
+ }
+
+ return substr($uriPath, 0, $lastSlashPos);
+ }
+
+ public function withCookieHeader(RequestInterface $request)
+ {
+ $values = [];
+ $uri = $request->getUri();
+ $scheme = $uri->getScheme();
+ $host = $uri->getHost();
+ $path = $uri->getPath() ?: '/';
+
+ foreach ($this->cookies as $cookie) {
+ if ($cookie->matchesPath($path) &&
+ $cookie->matchesDomain($host) &&
+ !$cookie->isExpired() &&
+ (!$cookie->getSecure() || $scheme === 'https')
+ ) {
+ $values[] = $cookie->getName() . '='
+ . $cookie->getValue();
+ }
+ }
+
+ return $values
+ ? $request->withHeader('Cookie', implode('; ', $values))
+ : $request;
+ }
+
+ /**
+ * If a cookie already exists and the server asks to set it again with a
+ * null value, the cookie must be deleted.
+ *
+ * @param SetCookie $cookie
+ */
+ private function removeCookieIfEmpty(SetCookie $cookie)
+ {
+ $cookieValue = $cookie->getValue();
+ if ($cookieValue === null || $cookieValue === '') {
+ $this->clear(
+ $cookie->getDomain(),
+ $cookie->getPath(),
+ $cookie->getName()
+ );
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php
new file mode 100644
index 000000000..6ee11885e
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php
@@ -0,0 +1,84 @@
+filename = $cookieFile;
+ $this->storeSessionCookies = $storeSessionCookies;
+
+ if (file_exists($cookieFile)) {
+ $this->load($cookieFile);
+ }
+ }
+
+ /**
+ * Saves the file when shutting down
+ */
+ public function __destruct()
+ {
+ $this->save($this->filename);
+ }
+
+ /**
+ * Saves the cookies to a file.
+ *
+ * @param string $filename File to save
+ * @throws \RuntimeException if the file cannot be found or created
+ */
+ public function save($filename)
+ {
+ $json = [];
+ foreach ($this as $cookie) {
+ /** @var SetCookie $cookie */
+ if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
+ $json[] = $cookie->toArray();
+ }
+ }
+
+ $jsonStr = \GuzzleHttp\json_encode($json);
+ if (false === file_put_contents($filename, $jsonStr, LOCK_EX)) {
+ throw new \RuntimeException("Unable to save file {$filename}");
+ }
+ }
+
+ /**
+ * Load cookies from a JSON formatted file.
+ *
+ * Old cookies are kept unless overwritten by newly loaded ones.
+ *
+ * @param string $filename Cookie file to load.
+ * @throws \RuntimeException if the file cannot be loaded.
+ */
+ public function load($filename)
+ {
+ $json = file_get_contents($filename);
+ if (false === $json) {
+ throw new \RuntimeException("Unable to load file {$filename}");
+ } elseif ($json === '') {
+ return;
+ }
+
+ $data = \GuzzleHttp\json_decode($json, true);
+ if (is_array($data)) {
+ foreach (json_decode($json, true) as $cookie) {
+ $this->setCookie(new SetCookie($cookie));
+ }
+ } elseif (strlen($data)) {
+ throw new \RuntimeException("Invalid cookie file: {$filename}");
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php
new file mode 100644
index 000000000..0224a2447
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php
@@ -0,0 +1,72 @@
+sessionKey = $sessionKey;
+ $this->storeSessionCookies = $storeSessionCookies;
+ $this->load();
+ }
+
+ /**
+ * Saves cookies to session when shutting down
+ */
+ public function __destruct()
+ {
+ $this->save();
+ }
+
+ /**
+ * Save cookies to the client session
+ */
+ public function save()
+ {
+ $json = [];
+ foreach ($this as $cookie) {
+ /** @var SetCookie $cookie */
+ if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
+ $json[] = $cookie->toArray();
+ }
+ }
+
+ $_SESSION[$this->sessionKey] = json_encode($json);
+ }
+
+ /**
+ * Load the contents of the client session into the data array
+ */
+ protected function load()
+ {
+ if (!isset($_SESSION[$this->sessionKey])) {
+ return;
+ }
+ $data = json_decode($_SESSION[$this->sessionKey], true);
+ if (is_array($data)) {
+ foreach ($data as $cookie) {
+ $this->setCookie(new SetCookie($cookie));
+ }
+ } elseif (strlen($data)) {
+ throw new \RuntimeException("Invalid cookie data");
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php
new file mode 100644
index 000000000..3d776a70b
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php
@@ -0,0 +1,403 @@
+ null,
+ 'Value' => null,
+ 'Domain' => null,
+ 'Path' => '/',
+ 'Max-Age' => null,
+ 'Expires' => null,
+ 'Secure' => false,
+ 'Discard' => false,
+ 'HttpOnly' => false
+ ];
+
+ /** @var array Cookie data */
+ private $data;
+
+ /**
+ * Create a new SetCookie object from a string
+ *
+ * @param string $cookie Set-Cookie header string
+ *
+ * @return self
+ */
+ public static function fromString($cookie)
+ {
+ // Create the default return array
+ $data = self::$defaults;
+ // Explode the cookie string using a series of semicolons
+ $pieces = array_filter(array_map('trim', explode(';', $cookie)));
+ // The name of the cookie (first kvp) must exist and include an equal sign.
+ if (empty($pieces[0]) || !strpos($pieces[0], '=')) {
+ return new self($data);
+ }
+
+ // Add the cookie pieces into the parsed data array
+ foreach ($pieces as $part) {
+ $cookieParts = explode('=', $part, 2);
+ $key = trim($cookieParts[0]);
+ $value = isset($cookieParts[1])
+ ? trim($cookieParts[1], " \n\r\t\0\x0B")
+ : true;
+
+ // Only check for non-cookies when cookies have been found
+ if (empty($data['Name'])) {
+ $data['Name'] = $key;
+ $data['Value'] = $value;
+ } else {
+ foreach (array_keys(self::$defaults) as $search) {
+ if (!strcasecmp($search, $key)) {
+ $data[$search] = $value;
+ continue 2;
+ }
+ }
+ $data[$key] = $value;
+ }
+ }
+
+ return new self($data);
+ }
+
+ /**
+ * @param array $data Array of cookie data provided by a Cookie parser
+ */
+ public function __construct(array $data = [])
+ {
+ $this->data = array_replace(self::$defaults, $data);
+ // Extract the Expires value and turn it into a UNIX timestamp if needed
+ if (!$this->getExpires() && $this->getMaxAge()) {
+ // Calculate the Expires date
+ $this->setExpires(time() + $this->getMaxAge());
+ } elseif ($this->getExpires() && !is_numeric($this->getExpires())) {
+ $this->setExpires($this->getExpires());
+ }
+ }
+
+ public function __toString()
+ {
+ $str = $this->data['Name'] . '=' . $this->data['Value'] . '; ';
+ foreach ($this->data as $k => $v) {
+ if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) {
+ if ($k === 'Expires') {
+ $str .= 'Expires=' . gmdate('D, d M Y H:i:s \G\M\T', $v) . '; ';
+ } else {
+ $str .= ($v === true ? $k : "{$k}={$v}") . '; ';
+ }
+ }
+ }
+
+ return rtrim($str, '; ');
+ }
+
+ public function toArray()
+ {
+ return $this->data;
+ }
+
+ /**
+ * Get the cookie name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->data['Name'];
+ }
+
+ /**
+ * Set the cookie name
+ *
+ * @param string $name Cookie name
+ */
+ public function setName($name)
+ {
+ $this->data['Name'] = $name;
+ }
+
+ /**
+ * Get the cookie value
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->data['Value'];
+ }
+
+ /**
+ * Set the cookie value
+ *
+ * @param string $value Cookie value
+ */
+ public function setValue($value)
+ {
+ $this->data['Value'] = $value;
+ }
+
+ /**
+ * Get the domain
+ *
+ * @return string|null
+ */
+ public function getDomain()
+ {
+ return $this->data['Domain'];
+ }
+
+ /**
+ * Set the domain of the cookie
+ *
+ * @param string $domain
+ */
+ public function setDomain($domain)
+ {
+ $this->data['Domain'] = $domain;
+ }
+
+ /**
+ * Get the path
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->data['Path'];
+ }
+
+ /**
+ * Set the path of the cookie
+ *
+ * @param string $path Path of the cookie
+ */
+ public function setPath($path)
+ {
+ $this->data['Path'] = $path;
+ }
+
+ /**
+ * Maximum lifetime of the cookie in seconds
+ *
+ * @return int|null
+ */
+ public function getMaxAge()
+ {
+ return $this->data['Max-Age'];
+ }
+
+ /**
+ * Set the max-age of the cookie
+ *
+ * @param int $maxAge Max age of the cookie in seconds
+ */
+ public function setMaxAge($maxAge)
+ {
+ $this->data['Max-Age'] = $maxAge;
+ }
+
+ /**
+ * The UNIX timestamp when the cookie Expires
+ *
+ * @return mixed
+ */
+ public function getExpires()
+ {
+ return $this->data['Expires'];
+ }
+
+ /**
+ * Set the unix timestamp for which the cookie will expire
+ *
+ * @param int $timestamp Unix timestamp
+ */
+ public function setExpires($timestamp)
+ {
+ $this->data['Expires'] = is_numeric($timestamp)
+ ? (int) $timestamp
+ : strtotime($timestamp);
+ }
+
+ /**
+ * Get whether or not this is a secure cookie
+ *
+ * @return bool|null
+ */
+ public function getSecure()
+ {
+ return $this->data['Secure'];
+ }
+
+ /**
+ * Set whether or not the cookie is secure
+ *
+ * @param bool $secure Set to true or false if secure
+ */
+ public function setSecure($secure)
+ {
+ $this->data['Secure'] = $secure;
+ }
+
+ /**
+ * Get whether or not this is a session cookie
+ *
+ * @return bool|null
+ */
+ public function getDiscard()
+ {
+ return $this->data['Discard'];
+ }
+
+ /**
+ * Set whether or not this is a session cookie
+ *
+ * @param bool $discard Set to true or false if this is a session cookie
+ */
+ public function setDiscard($discard)
+ {
+ $this->data['Discard'] = $discard;
+ }
+
+ /**
+ * Get whether or not this is an HTTP only cookie
+ *
+ * @return bool
+ */
+ public function getHttpOnly()
+ {
+ return $this->data['HttpOnly'];
+ }
+
+ /**
+ * Set whether or not this is an HTTP only cookie
+ *
+ * @param bool $httpOnly Set to true or false if this is HTTP only
+ */
+ public function setHttpOnly($httpOnly)
+ {
+ $this->data['HttpOnly'] = $httpOnly;
+ }
+
+ /**
+ * Check if the cookie matches a path value.
+ *
+ * A request-path path-matches a given cookie-path if at least one of
+ * the following conditions holds:
+ *
+ * - The cookie-path and the request-path are identical.
+ * - The cookie-path is a prefix of the request-path, and the last
+ * character of the cookie-path is %x2F ("/").
+ * - The cookie-path is a prefix of the request-path, and the first
+ * character of the request-path that is not included in the cookie-
+ * path is a %x2F ("/") character.
+ *
+ * @param string $requestPath Path to check against
+ *
+ * @return bool
+ */
+ public function matchesPath($requestPath)
+ {
+ $cookiePath = $this->getPath();
+
+ // Match on exact matches or when path is the default empty "/"
+ if ($cookiePath === '/' || $cookiePath == $requestPath) {
+ return true;
+ }
+
+ // Ensure that the cookie-path is a prefix of the request path.
+ if (0 !== strpos($requestPath, $cookiePath)) {
+ return false;
+ }
+
+ // Match if the last character of the cookie-path is "/"
+ if (substr($cookiePath, -1, 1) === '/') {
+ return true;
+ }
+
+ // Match if the first character not included in cookie path is "/"
+ return substr($requestPath, strlen($cookiePath), 1) === '/';
+ }
+
+ /**
+ * Check if the cookie matches a domain value
+ *
+ * @param string $domain Domain to check against
+ *
+ * @return bool
+ */
+ public function matchesDomain($domain)
+ {
+ // Remove the leading '.' as per spec in RFC 6265.
+ // http://tools.ietf.org/html/rfc6265#section-5.2.3
+ $cookieDomain = ltrim($this->getDomain(), '.');
+
+ // Domain not set or exact match.
+ if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) {
+ return true;
+ }
+
+ // Matching the subdomain according to RFC 6265.
+ // http://tools.ietf.org/html/rfc6265#section-5.1.3
+ if (filter_var($domain, FILTER_VALIDATE_IP)) {
+ return false;
+ }
+
+ return (bool) preg_match('/\.' . preg_quote($cookieDomain, '/') . '$/', $domain);
+ }
+
+ /**
+ * Check if the cookie is expired
+ *
+ * @return bool
+ */
+ public function isExpired()
+ {
+ return $this->getExpires() !== null && time() > $this->getExpires();
+ }
+
+ /**
+ * Check if the cookie is valid according to RFC 6265
+ *
+ * @return bool|string Returns true if valid or an error message if invalid
+ */
+ public function validate()
+ {
+ // Names must not be empty, but can be 0
+ $name = $this->getName();
+ if (empty($name) && !is_numeric($name)) {
+ return 'The cookie name must not be empty';
+ }
+
+ // Check if any of the invalid characters are present in the cookie name
+ if (preg_match(
+ '/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/',
+ $name
+ )) {
+ return 'Cookie name must not contain invalid characters: ASCII '
+ . 'Control characters (0-31;127), space, tab and the '
+ . 'following characters: ()<>@,;:\"/?={}';
+ }
+
+ // Value must not be empty, but can be 0
+ $value = $this->getValue();
+ if (empty($value) && !is_numeric($value)) {
+ return 'The cookie value must not be empty';
+ }
+
+ // Domains must not be empty, but can be 0
+ // A "0" is not a valid internet domain, but may be used as server name
+ // in a private network.
+ $domain = $this->getDomain();
+ if (empty($domain) && !is_numeric($domain)) {
+ return 'The cookie domain must not be empty';
+ }
+
+ return true;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php
new file mode 100644
index 000000000..427d896fb
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php
@@ -0,0 +1,27 @@
+getStatusCode()
+ : 0;
+ parent::__construct($message, $code, $previous);
+ $this->request = $request;
+ $this->response = $response;
+ $this->handlerContext = $handlerContext;
+ }
+
+ /**
+ * Wrap non-RequestExceptions with a RequestException
+ *
+ * @param RequestInterface $request
+ * @param \Exception $e
+ *
+ * @return RequestException
+ */
+ public static function wrapException(RequestInterface $request, \Exception $e)
+ {
+ return $e instanceof RequestException
+ ? $e
+ : new RequestException($e->getMessage(), $request, null, $e);
+ }
+
+ /**
+ * Factory method to create a new exception with a normalized error message
+ *
+ * @param RequestInterface $request Request
+ * @param ResponseInterface $response Response received
+ * @param \Exception $previous Previous exception
+ * @param array $ctx Optional handler context.
+ *
+ * @return self
+ */
+ public static function create(
+ RequestInterface $request,
+ ResponseInterface $response = null,
+ \Exception $previous = null,
+ array $ctx = []
+ ) {
+ if (!$response) {
+ return new self(
+ 'Error completing request',
+ $request,
+ null,
+ $previous,
+ $ctx
+ );
+ }
+
+ $level = (int) floor($response->getStatusCode() / 100);
+ if ($level === 4) {
+ $label = 'Client error';
+ $className = ClientException::class;
+ } elseif ($level === 5) {
+ $label = 'Server error';
+ $className = ServerException::class;
+ } else {
+ $label = 'Unsuccessful request';
+ $className = __CLASS__;
+ }
+
+ $uri = $request->getUri();
+ $uri = static::obfuscateUri($uri);
+
+ // Client Error: `GET /` resulted in a `404 Not Found` response:
+ // ... (truncated)
+ $message = sprintf(
+ '%s: `%s %s` resulted in a `%s %s` response',
+ $label,
+ $request->getMethod(),
+ $uri,
+ $response->getStatusCode(),
+ $response->getReasonPhrase()
+ );
+
+ $summary = static::getResponseBodySummary($response);
+
+ if ($summary !== null) {
+ $message .= ":\n{$summary}\n";
+ }
+
+ return new $className($message, $request, $response, $previous, $ctx);
+ }
+
+ /**
+ * Get a short summary of the response
+ *
+ * Will return `null` if the response is not printable.
+ *
+ * @param ResponseInterface $response
+ *
+ * @return string|null
+ */
+ public static function getResponseBodySummary(ResponseInterface $response)
+ {
+ return \GuzzleHttp\Psr7\get_message_body_summary($response);
+ }
+
+ /**
+ * Obfuscates URI if there is a username and a password present
+ *
+ * @param UriInterface $uri
+ *
+ * @return UriInterface
+ */
+ private static function obfuscateUri(UriInterface $uri)
+ {
+ $userInfo = $uri->getUserInfo();
+
+ if (false !== ($pos = strpos($userInfo, ':'))) {
+ return $uri->withUserInfo(substr($userInfo, 0, $pos), '***');
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Get the request that caused the exception
+ *
+ * @return RequestInterface
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ /**
+ * Get the associated response
+ *
+ * @return ResponseInterface|null
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+ /**
+ * Check if a response was received
+ *
+ * @return bool
+ */
+ public function hasResponse()
+ {
+ return $this->response !== null;
+ }
+
+ /**
+ * Get contextual information about the error from the underlying handler.
+ *
+ * The contents of this array will vary depending on which handler you are
+ * using. It may also be just an empty array. Relying on this data will
+ * couple you to a specific handler, but can give more debug information
+ * when needed.
+ *
+ * @return array
+ */
+ public function getHandlerContext()
+ {
+ return $this->handlerContext;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php
new file mode 100644
index 000000000..a77c28926
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php
@@ -0,0 +1,27 @@
+stream = $stream;
+ $msg = $msg ?: 'Could not seek the stream to position ' . $pos;
+ parent::__construct($msg);
+ }
+
+ /**
+ * @return StreamInterface
+ */
+ public function getStream()
+ {
+ return $this->stream;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php
new file mode 100644
index 000000000..127094c14
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php
@@ -0,0 +1,9 @@
+maxHandles = $maxHandles;
+ }
+
+ public function create(RequestInterface $request, array $options)
+ {
+ if (isset($options['curl']['body_as_string'])) {
+ $options['_body_as_string'] = $options['curl']['body_as_string'];
+ unset($options['curl']['body_as_string']);
+ }
+
+ $easy = new EasyHandle;
+ $easy->request = $request;
+ $easy->options = $options;
+ $conf = $this->getDefaultConf($easy);
+ $this->applyMethod($easy, $conf);
+ $this->applyHandlerOptions($easy, $conf);
+ $this->applyHeaders($easy, $conf);
+ unset($conf['_headers']);
+
+ // Add handler options from the request configuration options
+ if (isset($options['curl'])) {
+ $conf = array_replace($conf, $options['curl']);
+ }
+
+ $conf[CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy);
+ $easy->handle = $this->handles
+ ? array_pop($this->handles)
+ : curl_init();
+ curl_setopt_array($easy->handle, $conf);
+
+ return $easy;
+ }
+
+ public function release(EasyHandle $easy)
+ {
+ $resource = $easy->handle;
+ unset($easy->handle);
+
+ if (count($this->handles) >= $this->maxHandles) {
+ curl_close($resource);
+ } else {
+ // Remove all callback functions as they can hold onto references
+ // and are not cleaned up by curl_reset. Using curl_setopt_array
+ // does not work for some reason, so removing each one
+ // individually.
+ curl_setopt($resource, CURLOPT_HEADERFUNCTION, null);
+ curl_setopt($resource, CURLOPT_READFUNCTION, null);
+ curl_setopt($resource, CURLOPT_WRITEFUNCTION, null);
+ curl_setopt($resource, CURLOPT_PROGRESSFUNCTION, null);
+ curl_reset($resource);
+ $this->handles[] = $resource;
+ }
+ }
+
+ /**
+ * Completes a cURL transaction, either returning a response promise or a
+ * rejected promise.
+ *
+ * @param callable $handler
+ * @param EasyHandle $easy
+ * @param CurlFactoryInterface $factory Dictates how the handle is released
+ *
+ * @return \GuzzleHttp\Promise\PromiseInterface
+ */
+ public static function finish(
+ callable $handler,
+ EasyHandle $easy,
+ CurlFactoryInterface $factory
+ ) {
+ if (isset($easy->options['on_stats'])) {
+ self::invokeStats($easy);
+ }
+
+ if (!$easy->response || $easy->errno) {
+ return self::finishError($handler, $easy, $factory);
+ }
+
+ // Return the response if it is present and there is no error.
+ $factory->release($easy);
+
+ // Rewind the body of the response if possible.
+ $body = $easy->response->getBody();
+ if ($body->isSeekable()) {
+ $body->rewind();
+ }
+
+ return new FulfilledPromise($easy->response);
+ }
+
+ private static function invokeStats(EasyHandle $easy)
+ {
+ $curlStats = curl_getinfo($easy->handle);
+ $curlStats['appconnect_time'] = curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME);
+ $stats = new TransferStats(
+ $easy->request,
+ $easy->response,
+ $curlStats['total_time'],
+ $easy->errno,
+ $curlStats
+ );
+ call_user_func($easy->options['on_stats'], $stats);
+ }
+
+ private static function finishError(
+ callable $handler,
+ EasyHandle $easy,
+ CurlFactoryInterface $factory
+ ) {
+ // Get error information and release the handle to the factory.
+ $ctx = [
+ 'errno' => $easy->errno,
+ 'error' => curl_error($easy->handle),
+ 'appconnect_time' => curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME),
+ ] + curl_getinfo($easy->handle);
+ $ctx[self::CURL_VERSION_STR] = curl_version()['version'];
+ $factory->release($easy);
+
+ // Retry when nothing is present or when curl failed to rewind.
+ if (empty($easy->options['_err_message'])
+ && (!$easy->errno || $easy->errno == 65)
+ ) {
+ return self::retryFailedRewind($handler, $easy, $ctx);
+ }
+
+ return self::createRejection($easy, $ctx);
+ }
+
+ private static function createRejection(EasyHandle $easy, array $ctx)
+ {
+ static $connectionErrors = [
+ CURLE_OPERATION_TIMEOUTED => true,
+ CURLE_COULDNT_RESOLVE_HOST => true,
+ CURLE_COULDNT_CONNECT => true,
+ CURLE_SSL_CONNECT_ERROR => true,
+ CURLE_GOT_NOTHING => true,
+ ];
+
+ // If an exception was encountered during the onHeaders event, then
+ // return a rejected promise that wraps that exception.
+ if ($easy->onHeadersException) {
+ return \GuzzleHttp\Promise\rejection_for(
+ new RequestException(
+ 'An error was encountered during the on_headers event',
+ $easy->request,
+ $easy->response,
+ $easy->onHeadersException,
+ $ctx
+ )
+ );
+ }
+ if (version_compare($ctx[self::CURL_VERSION_STR], self::LOW_CURL_VERSION_NUMBER)) {
+ $message = sprintf(
+ 'cURL error %s: %s (%s)',
+ $ctx['errno'],
+ $ctx['error'],
+ 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html'
+ );
+ } else {
+ $message = sprintf(
+ 'cURL error %s: %s (%s) for %s',
+ $ctx['errno'],
+ $ctx['error'],
+ 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html',
+ $easy->request->getUri()
+ );
+ }
+
+ // Create a connection exception if it was a specific error code.
+ $error = isset($connectionErrors[$easy->errno])
+ ? new ConnectException($message, $easy->request, null, $ctx)
+ : new RequestException($message, $easy->request, $easy->response, null, $ctx);
+
+ return \GuzzleHttp\Promise\rejection_for($error);
+ }
+
+ private function getDefaultConf(EasyHandle $easy)
+ {
+ $conf = [
+ '_headers' => $easy->request->getHeaders(),
+ CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(),
+ CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''),
+ CURLOPT_RETURNTRANSFER => false,
+ CURLOPT_HEADER => false,
+ CURLOPT_CONNECTTIMEOUT => 150,
+ ];
+
+ if (defined('CURLOPT_PROTOCOLS')) {
+ $conf[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
+ }
+
+ $version = $easy->request->getProtocolVersion();
+ if ($version == 1.1) {
+ $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
+ } elseif ($version == 2.0) {
+ $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
+ } else {
+ $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
+ }
+
+ return $conf;
+ }
+
+ private function applyMethod(EasyHandle $easy, array &$conf)
+ {
+ $body = $easy->request->getBody();
+ $size = $body->getSize();
+
+ if ($size === null || $size > 0) {
+ $this->applyBody($easy->request, $easy->options, $conf);
+ return;
+ }
+
+ $method = $easy->request->getMethod();
+ if ($method === 'PUT' || $method === 'POST') {
+ // See http://tools.ietf.org/html/rfc7230#section-3.3.2
+ if (!$easy->request->hasHeader('Content-Length')) {
+ $conf[CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
+ }
+ } elseif ($method === 'HEAD') {
+ $conf[CURLOPT_NOBODY] = true;
+ unset(
+ $conf[CURLOPT_WRITEFUNCTION],
+ $conf[CURLOPT_READFUNCTION],
+ $conf[CURLOPT_FILE],
+ $conf[CURLOPT_INFILE]
+ );
+ }
+ }
+
+ private function applyBody(RequestInterface $request, array $options, array &$conf)
+ {
+ $size = $request->hasHeader('Content-Length')
+ ? (int) $request->getHeaderLine('Content-Length')
+ : null;
+
+ // Send the body as a string if the size is less than 1MB OR if the
+ // [curl][body_as_string] request value is set.
+ if (($size !== null && $size < 1000000) ||
+ !empty($options['_body_as_string'])
+ ) {
+ $conf[CURLOPT_POSTFIELDS] = (string) $request->getBody();
+ // Don't duplicate the Content-Length header
+ $this->removeHeader('Content-Length', $conf);
+ $this->removeHeader('Transfer-Encoding', $conf);
+ } else {
+ $conf[CURLOPT_UPLOAD] = true;
+ if ($size !== null) {
+ $conf[CURLOPT_INFILESIZE] = $size;
+ $this->removeHeader('Content-Length', $conf);
+ }
+ $body = $request->getBody();
+ if ($body->isSeekable()) {
+ $body->rewind();
+ }
+ $conf[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) {
+ return $body->read($length);
+ };
+ }
+
+ // If the Expect header is not present, prevent curl from adding it
+ if (!$request->hasHeader('Expect')) {
+ $conf[CURLOPT_HTTPHEADER][] = 'Expect:';
+ }
+
+ // cURL sometimes adds a content-type by default. Prevent this.
+ if (!$request->hasHeader('Content-Type')) {
+ $conf[CURLOPT_HTTPHEADER][] = 'Content-Type:';
+ }
+ }
+
+ private function applyHeaders(EasyHandle $easy, array &$conf)
+ {
+ foreach ($conf['_headers'] as $name => $values) {
+ foreach ($values as $value) {
+ $value = (string) $value;
+ if ($value === '') {
+ // cURL requires a special format for empty headers.
+ // See https://github.com/guzzle/guzzle/issues/1882 for more details.
+ $conf[CURLOPT_HTTPHEADER][] = "$name;";
+ } else {
+ $conf[CURLOPT_HTTPHEADER][] = "$name: $value";
+ }
+ }
+ }
+
+ // Remove the Accept header if one was not set
+ if (!$easy->request->hasHeader('Accept')) {
+ $conf[CURLOPT_HTTPHEADER][] = 'Accept:';
+ }
+ }
+
+ /**
+ * Remove a header from the options array.
+ *
+ * @param string $name Case-insensitive header to remove
+ * @param array $options Array of options to modify
+ */
+ private function removeHeader($name, array &$options)
+ {
+ foreach (array_keys($options['_headers']) as $key) {
+ if (!strcasecmp($key, $name)) {
+ unset($options['_headers'][$key]);
+ return;
+ }
+ }
+ }
+
+ private function applyHandlerOptions(EasyHandle $easy, array &$conf)
+ {
+ $options = $easy->options;
+ if (isset($options['verify'])) {
+ if ($options['verify'] === false) {
+ unset($conf[CURLOPT_CAINFO]);
+ $conf[CURLOPT_SSL_VERIFYHOST] = 0;
+ $conf[CURLOPT_SSL_VERIFYPEER] = false;
+ } else {
+ $conf[CURLOPT_SSL_VERIFYHOST] = 2;
+ $conf[CURLOPT_SSL_VERIFYPEER] = true;
+ if (is_string($options['verify'])) {
+ // Throw an error if the file/folder/link path is not valid or doesn't exist.
+ if (!file_exists($options['verify'])) {
+ throw new \InvalidArgumentException(
+ "SSL CA bundle not found: {$options['verify']}"
+ );
+ }
+ // If it's a directory or a link to a directory use CURLOPT_CAPATH.
+ // If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO.
+ if (is_dir($options['verify']) ||
+ (is_link($options['verify']) && is_dir(readlink($options['verify'])))) {
+ $conf[CURLOPT_CAPATH] = $options['verify'];
+ } else {
+ $conf[CURLOPT_CAINFO] = $options['verify'];
+ }
+ }
+ }
+ }
+
+ if (!empty($options['decode_content'])) {
+ $accept = $easy->request->getHeaderLine('Accept-Encoding');
+ if ($accept) {
+ $conf[CURLOPT_ENCODING] = $accept;
+ } else {
+ $conf[CURLOPT_ENCODING] = '';
+ // Don't let curl send the header over the wire
+ $conf[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
+ }
+ }
+
+ if (isset($options['sink'])) {
+ $sink = $options['sink'];
+ if (!is_string($sink)) {
+ $sink = \GuzzleHttp\Psr7\stream_for($sink);
+ } elseif (!is_dir(dirname($sink))) {
+ // Ensure that the directory exists before failing in curl.
+ throw new \RuntimeException(sprintf(
+ 'Directory %s does not exist for sink value of %s',
+ dirname($sink),
+ $sink
+ ));
+ } else {
+ $sink = new LazyOpenStream($sink, 'w+');
+ }
+ $easy->sink = $sink;
+ $conf[CURLOPT_WRITEFUNCTION] = function ($ch, $write) use ($sink) {
+ return $sink->write($write);
+ };
+ } else {
+ // Use a default temp stream if no sink was set.
+ $conf[CURLOPT_FILE] = fopen('php://temp', 'w+');
+ $easy->sink = Psr7\stream_for($conf[CURLOPT_FILE]);
+ }
+ $timeoutRequiresNoSignal = false;
+ if (isset($options['timeout'])) {
+ $timeoutRequiresNoSignal |= $options['timeout'] < 1;
+ $conf[CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000;
+ }
+
+ // CURL default value is CURL_IPRESOLVE_WHATEVER
+ if (isset($options['force_ip_resolve'])) {
+ if ('v4' === $options['force_ip_resolve']) {
+ $conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
+ } elseif ('v6' === $options['force_ip_resolve']) {
+ $conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V6;
+ }
+ }
+
+ if (isset($options['connect_timeout'])) {
+ $timeoutRequiresNoSignal |= $options['connect_timeout'] < 1;
+ $conf[CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000;
+ }
+
+ if ($timeoutRequiresNoSignal && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
+ $conf[CURLOPT_NOSIGNAL] = true;
+ }
+
+ if (isset($options['proxy'])) {
+ if (!is_array($options['proxy'])) {
+ $conf[CURLOPT_PROXY] = $options['proxy'];
+ } else {
+ $scheme = $easy->request->getUri()->getScheme();
+ if (isset($options['proxy'][$scheme])) {
+ $host = $easy->request->getUri()->getHost();
+ if (!isset($options['proxy']['no']) ||
+ !\GuzzleHttp\is_host_in_noproxy($host, $options['proxy']['no'])
+ ) {
+ $conf[CURLOPT_PROXY] = $options['proxy'][$scheme];
+ }
+ }
+ }
+ }
+
+ if (isset($options['cert'])) {
+ $cert = $options['cert'];
+ if (is_array($cert)) {
+ $conf[CURLOPT_SSLCERTPASSWD] = $cert[1];
+ $cert = $cert[0];
+ }
+ if (!file_exists($cert)) {
+ throw new \InvalidArgumentException(
+ "SSL certificate not found: {$cert}"
+ );
+ }
+ $conf[CURLOPT_SSLCERT] = $cert;
+ }
+
+ if (isset($options['ssl_key'])) {
+ if (is_array($options['ssl_key'])) {
+ if (count($options['ssl_key']) === 2) {
+ list($sslKey, $conf[CURLOPT_SSLKEYPASSWD]) = $options['ssl_key'];
+ } else {
+ list($sslKey) = $options['ssl_key'];
+ }
+ }
+
+ $sslKey = isset($sslKey) ? $sslKey: $options['ssl_key'];
+
+ if (!file_exists($sslKey)) {
+ throw new \InvalidArgumentException(
+ "SSL private key not found: {$sslKey}"
+ );
+ }
+ $conf[CURLOPT_SSLKEY] = $sslKey;
+ }
+
+ if (isset($options['progress'])) {
+ $progress = $options['progress'];
+ if (!is_callable($progress)) {
+ throw new \InvalidArgumentException(
+ 'progress client option must be callable'
+ );
+ }
+ $conf[CURLOPT_NOPROGRESS] = false;
+ $conf[CURLOPT_PROGRESSFUNCTION] = function () use ($progress) {
+ $args = func_get_args();
+ // PHP 5.5 pushed the handle onto the start of the args
+ if (is_resource($args[0])) {
+ array_shift($args);
+ }
+ call_user_func_array($progress, $args);
+ };
+ }
+
+ if (!empty($options['debug'])) {
+ $conf[CURLOPT_STDERR] = \GuzzleHttp\debug_resource($options['debug']);
+ $conf[CURLOPT_VERBOSE] = true;
+ }
+ }
+
+ /**
+ * This function ensures that a response was set on a transaction. If one
+ * was not set, then the request is retried if possible. This error
+ * typically means you are sending a payload, curl encountered a
+ * "Connection died, retrying a fresh connect" error, tried to rewind the
+ * stream, and then encountered a "necessary data rewind wasn't possible"
+ * error, causing the request to be sent through curl_multi_info_read()
+ * without an error status.
+ */
+ private static function retryFailedRewind(
+ callable $handler,
+ EasyHandle $easy,
+ array $ctx
+ ) {
+ try {
+ // Only rewind if the body has been read from.
+ $body = $easy->request->getBody();
+ if ($body->tell() > 0) {
+ $body->rewind();
+ }
+ } catch (\RuntimeException $e) {
+ $ctx['error'] = 'The connection unexpectedly failed without '
+ . 'providing an error. The request would have been retried, '
+ . 'but attempting to rewind the request body failed. '
+ . 'Exception: ' . $e;
+ return self::createRejection($easy, $ctx);
+ }
+
+ // Retry no more than 3 times before giving up.
+ if (!isset($easy->options['_curl_retries'])) {
+ $easy->options['_curl_retries'] = 1;
+ } elseif ($easy->options['_curl_retries'] == 2) {
+ $ctx['error'] = 'The cURL request was retried 3 times '
+ . 'and did not succeed. The most likely reason for the failure '
+ . 'is that cURL was unable to rewind the body of the request '
+ . 'and subsequent retries resulted in the same error. Turn on '
+ . 'the debug option to see what went wrong. See '
+ . 'https://bugs.php.net/bug.php?id=47204 for more information.';
+ return self::createRejection($easy, $ctx);
+ } else {
+ $easy->options['_curl_retries']++;
+ }
+
+ return $handler($easy->request, $easy->options);
+ }
+
+ private function createHeaderFn(EasyHandle $easy)
+ {
+ if (isset($easy->options['on_headers'])) {
+ $onHeaders = $easy->options['on_headers'];
+
+ if (!is_callable($onHeaders)) {
+ throw new \InvalidArgumentException('on_headers must be callable');
+ }
+ } else {
+ $onHeaders = null;
+ }
+
+ return function ($ch, $h) use (
+ $onHeaders,
+ $easy,
+ &$startingResponse
+ ) {
+ $value = trim($h);
+ if ($value === '') {
+ $startingResponse = true;
+ $easy->createResponse();
+ if ($onHeaders !== null) {
+ try {
+ $onHeaders($easy->response);
+ } catch (\Exception $e) {
+ // Associate the exception with the handle and trigger
+ // a curl header write error by returning 0.
+ $easy->onHeadersException = $e;
+ return -1;
+ }
+ }
+ } elseif ($startingResponse) {
+ $startingResponse = false;
+ $easy->headers = [$value];
+ } else {
+ $easy->headers[] = $value;
+ }
+ return strlen($h);
+ };
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php
new file mode 100644
index 000000000..b0fc23685
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php
@@ -0,0 +1,27 @@
+factory = isset($options['handle_factory'])
+ ? $options['handle_factory']
+ : new CurlFactory(3);
+ }
+
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ if (isset($options['delay'])) {
+ usleep($options['delay'] * 1000);
+ }
+
+ $easy = $this->factory->create($request, $options);
+ curl_exec($easy->handle);
+ $easy->errno = curl_errno($easy->handle);
+
+ return CurlFactory::finish($this, $easy, $this->factory);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php
new file mode 100644
index 000000000..8eaa34f35
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php
@@ -0,0 +1,220 @@
+factory = isset($options['handle_factory'])
+ ? $options['handle_factory'] : new CurlFactory(50);
+
+ if (isset($options['select_timeout'])) {
+ $this->selectTimeout = $options['select_timeout'];
+ } elseif ($selectTimeout = getenv('GUZZLE_CURL_SELECT_TIMEOUT')) {
+ $this->selectTimeout = $selectTimeout;
+ } else {
+ $this->selectTimeout = 1;
+ }
+
+ $this->options = isset($options['options']) ? $options['options'] : [];
+ }
+
+ public function __get($name)
+ {
+ if ($name === '_mh') {
+ $this->_mh = curl_multi_init();
+
+ foreach ($this->options as $option => $value) {
+ // A warning is raised in case of a wrong option.
+ curl_multi_setopt($this->_mh, $option, $value);
+ }
+
+ // Further calls to _mh will return the value directly, without entering the
+ // __get() method at all.
+ return $this->_mh;
+ }
+
+ throw new \BadMethodCallException();
+ }
+
+ public function __destruct()
+ {
+ if (isset($this->_mh)) {
+ curl_multi_close($this->_mh);
+ unset($this->_mh);
+ }
+ }
+
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ $easy = $this->factory->create($request, $options);
+ $id = (int) $easy->handle;
+
+ $promise = new Promise(
+ [$this, 'execute'],
+ function () use ($id) {
+ return $this->cancel($id);
+ }
+ );
+
+ $this->addRequest(['easy' => $easy, 'deferred' => $promise]);
+
+ return $promise;
+ }
+
+ /**
+ * Ticks the curl event loop.
+ */
+ public function tick()
+ {
+ // Add any delayed handles if needed.
+ if ($this->delays) {
+ $currentTime = Utils::currentTime();
+ foreach ($this->delays as $id => $delay) {
+ if ($currentTime >= $delay) {
+ unset($this->delays[$id]);
+ curl_multi_add_handle(
+ $this->_mh,
+ $this->handles[$id]['easy']->handle
+ );
+ }
+ }
+ }
+
+ // Step through the task queue which may add additional requests.
+ P\queue()->run();
+
+ if ($this->active &&
+ curl_multi_select($this->_mh, $this->selectTimeout) === -1
+ ) {
+ // Perform a usleep if a select returns -1.
+ // See: https://bugs.php.net/bug.php?id=61141
+ usleep(250);
+ }
+
+ while (curl_multi_exec($this->_mh, $this->active) === CURLM_CALL_MULTI_PERFORM);
+
+ $this->processMessages();
+ }
+
+ /**
+ * Runs until all outstanding connections have completed.
+ */
+ public function execute()
+ {
+ $queue = P\queue();
+
+ while ($this->handles || !$queue->isEmpty()) {
+ // If there are no transfers, then sleep for the next delay
+ if (!$this->active && $this->delays) {
+ usleep($this->timeToNext());
+ }
+ $this->tick();
+ }
+ }
+
+ private function addRequest(array $entry)
+ {
+ $easy = $entry['easy'];
+ $id = (int) $easy->handle;
+ $this->handles[$id] = $entry;
+ if (empty($easy->options['delay'])) {
+ curl_multi_add_handle($this->_mh, $easy->handle);
+ } else {
+ $this->delays[$id] = Utils::currentTime() + ($easy->options['delay'] / 1000);
+ }
+ }
+
+ /**
+ * Cancels a handle from sending and removes references to it.
+ *
+ * @param int $id Handle ID to cancel and remove.
+ *
+ * @return bool True on success, false on failure.
+ */
+ private function cancel($id)
+ {
+ // Cannot cancel if it has been processed.
+ if (!isset($this->handles[$id])) {
+ return false;
+ }
+
+ $handle = $this->handles[$id]['easy']->handle;
+ unset($this->delays[$id], $this->handles[$id]);
+ curl_multi_remove_handle($this->_mh, $handle);
+ curl_close($handle);
+
+ return true;
+ }
+
+ private function processMessages()
+ {
+ while ($done = curl_multi_info_read($this->_mh)) {
+ $id = (int) $done['handle'];
+ curl_multi_remove_handle($this->_mh, $done['handle']);
+
+ if (!isset($this->handles[$id])) {
+ // Probably was cancelled.
+ continue;
+ }
+
+ $entry = $this->handles[$id];
+ unset($this->handles[$id], $this->delays[$id]);
+ $entry['easy']->errno = $done['result'];
+ $entry['deferred']->resolve(
+ CurlFactory::finish(
+ $this,
+ $entry['easy'],
+ $this->factory
+ )
+ );
+ }
+ }
+
+ private function timeToNext()
+ {
+ $currentTime = Utils::currentTime();
+ $nextTime = PHP_INT_MAX;
+ foreach ($this->delays as $time) {
+ if ($time < $nextTime) {
+ $nextTime = $time;
+ }
+ }
+
+ return max(0, $nextTime - $currentTime) * 1000000;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php
new file mode 100644
index 000000000..7754e9111
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php
@@ -0,0 +1,92 @@
+headers)) {
+ throw new \RuntimeException('No headers have been received');
+ }
+
+ // HTTP-version SP status-code SP reason-phrase
+ $startLine = explode(' ', array_shift($this->headers), 3);
+ $headers = \GuzzleHttp\headers_from_lines($this->headers);
+ $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers);
+
+ if (!empty($this->options['decode_content'])
+ && isset($normalizedKeys['content-encoding'])
+ ) {
+ $headers['x-encoded-content-encoding']
+ = $headers[$normalizedKeys['content-encoding']];
+ unset($headers[$normalizedKeys['content-encoding']]);
+ if (isset($normalizedKeys['content-length'])) {
+ $headers['x-encoded-content-length']
+ = $headers[$normalizedKeys['content-length']];
+
+ $bodyLength = (int) $this->sink->getSize();
+ if ($bodyLength) {
+ $headers[$normalizedKeys['content-length']] = $bodyLength;
+ } else {
+ unset($headers[$normalizedKeys['content-length']]);
+ }
+ }
+ }
+
+ // Attach a response to the easy handle with the parsed headers.
+ $this->response = new Response(
+ $startLine[1],
+ $headers,
+ $this->sink,
+ substr($startLine[0], 5),
+ isset($startLine[2]) ? (string) $startLine[2] : null
+ );
+ }
+
+ public function __get($name)
+ {
+ $msg = $name === 'handle'
+ ? 'The EasyHandle has been released'
+ : 'Invalid property: ' . $name;
+ throw new \BadMethodCallException($msg);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php
new file mode 100644
index 000000000..5b312bc04
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php
@@ -0,0 +1,195 @@
+onFulfilled = $onFulfilled;
+ $this->onRejected = $onRejected;
+
+ if ($queue) {
+ call_user_func_array([$this, 'append'], $queue);
+ }
+ }
+
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ if (!$this->queue) {
+ throw new \OutOfBoundsException('Mock queue is empty');
+ }
+
+ if (isset($options['delay']) && is_numeric($options['delay'])) {
+ usleep($options['delay'] * 1000);
+ }
+
+ $this->lastRequest = $request;
+ $this->lastOptions = $options;
+ $response = array_shift($this->queue);
+
+ if (isset($options['on_headers'])) {
+ if (!is_callable($options['on_headers'])) {
+ throw new \InvalidArgumentException('on_headers must be callable');
+ }
+ try {
+ $options['on_headers']($response);
+ } catch (\Exception $e) {
+ $msg = 'An error was encountered during the on_headers event';
+ $response = new RequestException($msg, $request, $response, $e);
+ }
+ }
+
+ if (is_callable($response)) {
+ $response = call_user_func($response, $request, $options);
+ }
+
+ $response = $response instanceof \Exception
+ ? \GuzzleHttp\Promise\rejection_for($response)
+ : \GuzzleHttp\Promise\promise_for($response);
+
+ return $response->then(
+ function ($value) use ($request, $options) {
+ $this->invokeStats($request, $options, $value);
+ if ($this->onFulfilled) {
+ call_user_func($this->onFulfilled, $value);
+ }
+ if (isset($options['sink'])) {
+ $contents = (string) $value->getBody();
+ $sink = $options['sink'];
+
+ if (is_resource($sink)) {
+ fwrite($sink, $contents);
+ } elseif (is_string($sink)) {
+ file_put_contents($sink, $contents);
+ } elseif ($sink instanceof \Psr\Http\Message\StreamInterface) {
+ $sink->write($contents);
+ }
+ }
+
+ return $value;
+ },
+ function ($reason) use ($request, $options) {
+ $this->invokeStats($request, $options, null, $reason);
+ if ($this->onRejected) {
+ call_user_func($this->onRejected, $reason);
+ }
+ return \GuzzleHttp\Promise\rejection_for($reason);
+ }
+ );
+ }
+
+ /**
+ * Adds one or more variadic requests, exceptions, callables, or promises
+ * to the queue.
+ */
+ public function append()
+ {
+ foreach (func_get_args() as $value) {
+ if ($value instanceof ResponseInterface
+ || $value instanceof \Exception
+ || $value instanceof PromiseInterface
+ || is_callable($value)
+ ) {
+ $this->queue[] = $value;
+ } else {
+ throw new \InvalidArgumentException('Expected a response or '
+ . 'exception. Found ' . \GuzzleHttp\describe_type($value));
+ }
+ }
+ }
+
+ /**
+ * Get the last received request.
+ *
+ * @return RequestInterface
+ */
+ public function getLastRequest()
+ {
+ return $this->lastRequest;
+ }
+
+ /**
+ * Get the last received request options.
+ *
+ * @return array
+ */
+ public function getLastOptions()
+ {
+ return $this->lastOptions;
+ }
+
+ /**
+ * Returns the number of remaining items in the queue.
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->queue);
+ }
+
+ public function reset()
+ {
+ $this->queue = [];
+ }
+
+ private function invokeStats(
+ RequestInterface $request,
+ array $options,
+ ResponseInterface $response = null,
+ $reason = null
+ ) {
+ if (isset($options['on_stats'])) {
+ $transferTime = isset($options['transfer_time']) ? $options['transfer_time'] : 0;
+ $stats = new TransferStats($request, $response, $transferTime, $reason);
+ call_user_func($options['on_stats'], $stats);
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php
new file mode 100644
index 000000000..f8b00be0b
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php
@@ -0,0 +1,55 @@
+withoutHeader('Expect');
+
+ // Append a content-length header if body size is zero to match
+ // cURL's behavior.
+ if (0 === $request->getBody()->getSize()) {
+ $request = $request->withHeader('Content-Length', '0');
+ }
+
+ return $this->createResponse(
+ $request,
+ $options,
+ $this->createStream($request, $options),
+ $startTime
+ );
+ } catch (\InvalidArgumentException $e) {
+ throw $e;
+ } catch (\Exception $e) {
+ // Determine if the error was a networking error.
+ $message = $e->getMessage();
+ // This list can probably get more comprehensive.
+ if (strpos($message, 'getaddrinfo') // DNS lookup failed
+ || strpos($message, 'Connection refused')
+ || strpos($message, "couldn't connect to host") // error on HHVM
+ || strpos($message, "connection attempt failed")
+ ) {
+ $e = new ConnectException($e->getMessage(), $request, $e);
+ }
+ $e = RequestException::wrapException($request, $e);
+ $this->invokeStats($options, $request, $startTime, null, $e);
+
+ return \GuzzleHttp\Promise\rejection_for($e);
+ }
+ }
+
+ private function invokeStats(
+ array $options,
+ RequestInterface $request,
+ $startTime,
+ ResponseInterface $response = null,
+ $error = null
+ ) {
+ if (isset($options['on_stats'])) {
+ $stats = new TransferStats(
+ $request,
+ $response,
+ Utils::currentTime() - $startTime,
+ $error,
+ []
+ );
+ call_user_func($options['on_stats'], $stats);
+ }
+ }
+
+ private function createResponse(
+ RequestInterface $request,
+ array $options,
+ $stream,
+ $startTime
+ ) {
+ $hdrs = $this->lastHeaders;
+ $this->lastHeaders = [];
+ $parts = explode(' ', array_shift($hdrs), 3);
+ $ver = explode('/', $parts[0])[1];
+ $status = $parts[1];
+ $reason = isset($parts[2]) ? $parts[2] : null;
+ $headers = \GuzzleHttp\headers_from_lines($hdrs);
+ list($stream, $headers) = $this->checkDecode($options, $headers, $stream);
+ $stream = Psr7\stream_for($stream);
+ $sink = $stream;
+
+ if (strcasecmp('HEAD', $request->getMethod())) {
+ $sink = $this->createSink($stream, $options);
+ }
+
+ $response = new Psr7\Response($status, $headers, $sink, $ver, $reason);
+
+ if (isset($options['on_headers'])) {
+ try {
+ $options['on_headers']($response);
+ } catch (\Exception $e) {
+ $msg = 'An error was encountered during the on_headers event';
+ $ex = new RequestException($msg, $request, $response, $e);
+ return \GuzzleHttp\Promise\rejection_for($ex);
+ }
+ }
+
+ // Do not drain when the request is a HEAD request because they have
+ // no body.
+ if ($sink !== $stream) {
+ $this->drain(
+ $stream,
+ $sink,
+ $response->getHeaderLine('Content-Length')
+ );
+ }
+
+ $this->invokeStats($options, $request, $startTime, $response, null);
+
+ return new FulfilledPromise($response);
+ }
+
+ private function createSink(StreamInterface $stream, array $options)
+ {
+ if (!empty($options['stream'])) {
+ return $stream;
+ }
+
+ $sink = isset($options['sink'])
+ ? $options['sink']
+ : fopen('php://temp', 'r+');
+
+ return is_string($sink)
+ ? new Psr7\LazyOpenStream($sink, 'w+')
+ : Psr7\stream_for($sink);
+ }
+
+ private function checkDecode(array $options, array $headers, $stream)
+ {
+ // Automatically decode responses when instructed.
+ if (!empty($options['decode_content'])) {
+ $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers);
+ if (isset($normalizedKeys['content-encoding'])) {
+ $encoding = $headers[$normalizedKeys['content-encoding']];
+ if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') {
+ $stream = new Psr7\InflateStream(
+ Psr7\stream_for($stream)
+ );
+ $headers['x-encoded-content-encoding']
+ = $headers[$normalizedKeys['content-encoding']];
+ // Remove content-encoding header
+ unset($headers[$normalizedKeys['content-encoding']]);
+ // Fix content-length header
+ if (isset($normalizedKeys['content-length'])) {
+ $headers['x-encoded-content-length']
+ = $headers[$normalizedKeys['content-length']];
+
+ $length = (int) $stream->getSize();
+ if ($length === 0) {
+ unset($headers[$normalizedKeys['content-length']]);
+ } else {
+ $headers[$normalizedKeys['content-length']] = [$length];
+ }
+ }
+ }
+ }
+ }
+
+ return [$stream, $headers];
+ }
+
+ /**
+ * Drains the source stream into the "sink" client option.
+ *
+ * @param StreamInterface $source
+ * @param StreamInterface $sink
+ * @param string $contentLength Header specifying the amount of
+ * data to read.
+ *
+ * @return StreamInterface
+ * @throws \RuntimeException when the sink option is invalid.
+ */
+ private function drain(
+ StreamInterface $source,
+ StreamInterface $sink,
+ $contentLength
+ ) {
+ // If a content-length header is provided, then stop reading once
+ // that number of bytes has been read. This can prevent infinitely
+ // reading from a stream when dealing with servers that do not honor
+ // Connection: Close headers.
+ Psr7\copy_to_stream(
+ $source,
+ $sink,
+ (strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1
+ );
+
+ $sink->seek(0);
+ $source->close();
+
+ return $sink;
+ }
+
+ /**
+ * Create a resource and check to ensure it was created successfully
+ *
+ * @param callable $callback Callable that returns stream resource
+ *
+ * @return resource
+ * @throws \RuntimeException on error
+ */
+ private function createResource(callable $callback)
+ {
+ $errors = null;
+ set_error_handler(function ($_, $msg, $file, $line) use (&$errors) {
+ $errors[] = [
+ 'message' => $msg,
+ 'file' => $file,
+ 'line' => $line
+ ];
+ return true;
+ });
+
+ $resource = $callback();
+ restore_error_handler();
+
+ if (!$resource) {
+ $message = 'Error creating resource: ';
+ foreach ($errors as $err) {
+ foreach ($err as $key => $value) {
+ $message .= "[$key] $value" . PHP_EOL;
+ }
+ }
+ throw new \RuntimeException(trim($message));
+ }
+
+ return $resource;
+ }
+
+ private function createStream(RequestInterface $request, array $options)
+ {
+ static $methods;
+ if (!$methods) {
+ $methods = array_flip(get_class_methods(__CLASS__));
+ }
+
+ // HTTP/1.1 streams using the PHP stream wrapper require a
+ // Connection: close header
+ if ($request->getProtocolVersion() == '1.1'
+ && !$request->hasHeader('Connection')
+ ) {
+ $request = $request->withHeader('Connection', 'close');
+ }
+
+ // Ensure SSL is verified by default
+ if (!isset($options['verify'])) {
+ $options['verify'] = true;
+ }
+
+ $params = [];
+ $context = $this->getDefaultContext($request);
+
+ if (isset($options['on_headers']) && !is_callable($options['on_headers'])) {
+ throw new \InvalidArgumentException('on_headers must be callable');
+ }
+
+ if (!empty($options)) {
+ foreach ($options as $key => $value) {
+ $method = "add_{$key}";
+ if (isset($methods[$method])) {
+ $this->{$method}($request, $context, $value, $params);
+ }
+ }
+ }
+
+ if (isset($options['stream_context'])) {
+ if (!is_array($options['stream_context'])) {
+ throw new \InvalidArgumentException('stream_context must be an array');
+ }
+ $context = array_replace_recursive(
+ $context,
+ $options['stream_context']
+ );
+ }
+
+ // Microsoft NTLM authentication only supported with curl handler
+ if (isset($options['auth'])
+ && is_array($options['auth'])
+ && isset($options['auth'][2])
+ && 'ntlm' == $options['auth'][2]
+ ) {
+ throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler');
+ }
+
+ $uri = $this->resolveHost($request, $options);
+
+ $context = $this->createResource(
+ function () use ($context, $params) {
+ return stream_context_create($context, $params);
+ }
+ );
+
+ return $this->createResource(
+ function () use ($uri, &$http_response_header, $context, $options) {
+ $resource = fopen((string) $uri, 'r', null, $context);
+ $this->lastHeaders = $http_response_header;
+
+ if (isset($options['read_timeout'])) {
+ $readTimeout = $options['read_timeout'];
+ $sec = (int) $readTimeout;
+ $usec = ($readTimeout - $sec) * 100000;
+ stream_set_timeout($resource, $sec, $usec);
+ }
+
+ return $resource;
+ }
+ );
+ }
+
+ private function resolveHost(RequestInterface $request, array $options)
+ {
+ $uri = $request->getUri();
+
+ if (isset($options['force_ip_resolve']) && !filter_var($uri->getHost(), FILTER_VALIDATE_IP)) {
+ if ('v4' === $options['force_ip_resolve']) {
+ $records = dns_get_record($uri->getHost(), DNS_A);
+ if (!isset($records[0]['ip'])) {
+ throw new ConnectException(
+ sprintf(
+ "Could not resolve IPv4 address for host '%s'",
+ $uri->getHost()
+ ),
+ $request
+ );
+ }
+ $uri = $uri->withHost($records[0]['ip']);
+ } elseif ('v6' === $options['force_ip_resolve']) {
+ $records = dns_get_record($uri->getHost(), DNS_AAAA);
+ if (!isset($records[0]['ipv6'])) {
+ throw new ConnectException(
+ sprintf(
+ "Could not resolve IPv6 address for host '%s'",
+ $uri->getHost()
+ ),
+ $request
+ );
+ }
+ $uri = $uri->withHost('[' . $records[0]['ipv6'] . ']');
+ }
+ }
+
+ return $uri;
+ }
+
+ private function getDefaultContext(RequestInterface $request)
+ {
+ $headers = '';
+ foreach ($request->getHeaders() as $name => $value) {
+ foreach ($value as $val) {
+ $headers .= "$name: $val\r\n";
+ }
+ }
+
+ $context = [
+ 'http' => [
+ 'method' => $request->getMethod(),
+ 'header' => $headers,
+ 'protocol_version' => $request->getProtocolVersion(),
+ 'ignore_errors' => true,
+ 'follow_location' => 0,
+ ],
+ ];
+
+ $body = (string) $request->getBody();
+
+ if (!empty($body)) {
+ $context['http']['content'] = $body;
+ // Prevent the HTTP handler from adding a Content-Type header.
+ if (!$request->hasHeader('Content-Type')) {
+ $context['http']['header'] .= "Content-Type:\r\n";
+ }
+ }
+
+ $context['http']['header'] = rtrim($context['http']['header']);
+
+ return $context;
+ }
+
+ private function add_proxy(RequestInterface $request, &$options, $value, &$params)
+ {
+ if (!is_array($value)) {
+ $options['http']['proxy'] = $value;
+ } else {
+ $scheme = $request->getUri()->getScheme();
+ if (isset($value[$scheme])) {
+ if (!isset($value['no'])
+ || !\GuzzleHttp\is_host_in_noproxy(
+ $request->getUri()->getHost(),
+ $value['no']
+ )
+ ) {
+ $options['http']['proxy'] = $value[$scheme];
+ }
+ }
+ }
+ }
+
+ private function add_timeout(RequestInterface $request, &$options, $value, &$params)
+ {
+ if ($value > 0) {
+ $options['http']['timeout'] = $value;
+ }
+ }
+
+ private function add_verify(RequestInterface $request, &$options, $value, &$params)
+ {
+ if ($value === true) {
+ // PHP 5.6 or greater will find the system cert by default. When
+ // < 5.6, use the Guzzle bundled cacert.
+ if (PHP_VERSION_ID < 50600) {
+ $options['ssl']['cafile'] = \GuzzleHttp\default_ca_bundle();
+ }
+ } elseif (is_string($value)) {
+ $options['ssl']['cafile'] = $value;
+ if (!file_exists($value)) {
+ throw new \RuntimeException("SSL CA bundle not found: $value");
+ }
+ } elseif ($value === false) {
+ $options['ssl']['verify_peer'] = false;
+ $options['ssl']['verify_peer_name'] = false;
+ return;
+ } else {
+ throw new \InvalidArgumentException('Invalid verify request option');
+ }
+
+ $options['ssl']['verify_peer'] = true;
+ $options['ssl']['verify_peer_name'] = true;
+ $options['ssl']['allow_self_signed'] = false;
+ }
+
+ private function add_cert(RequestInterface $request, &$options, $value, &$params)
+ {
+ if (is_array($value)) {
+ $options['ssl']['passphrase'] = $value[1];
+ $value = $value[0];
+ }
+
+ if (!file_exists($value)) {
+ throw new \RuntimeException("SSL certificate not found: {$value}");
+ }
+
+ $options['ssl']['local_cert'] = $value;
+ }
+
+ private function add_progress(RequestInterface $request, &$options, $value, &$params)
+ {
+ $this->addNotification(
+ $params,
+ function ($code, $a, $b, $c, $transferred, $total) use ($value) {
+ if ($code == STREAM_NOTIFY_PROGRESS) {
+ $value($total, $transferred, null, null);
+ }
+ }
+ );
+ }
+
+ private function add_debug(RequestInterface $request, &$options, $value, &$params)
+ {
+ if ($value === false) {
+ return;
+ }
+
+ static $map = [
+ STREAM_NOTIFY_CONNECT => 'CONNECT',
+ STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED',
+ STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT',
+ STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS',
+ STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS',
+ STREAM_NOTIFY_REDIRECTED => 'REDIRECTED',
+ STREAM_NOTIFY_PROGRESS => 'PROGRESS',
+ STREAM_NOTIFY_FAILURE => 'FAILURE',
+ STREAM_NOTIFY_COMPLETED => 'COMPLETED',
+ STREAM_NOTIFY_RESOLVE => 'RESOLVE',
+ ];
+ static $args = ['severity', 'message', 'message_code',
+ 'bytes_transferred', 'bytes_max'];
+
+ $value = \GuzzleHttp\debug_resource($value);
+ $ident = $request->getMethod() . ' ' . $request->getUri()->withFragment('');
+ $this->addNotification(
+ $params,
+ function () use ($ident, $value, $map, $args) {
+ $passed = func_get_args();
+ $code = array_shift($passed);
+ fprintf($value, '<%s> [%s] ', $ident, $map[$code]);
+ foreach (array_filter($passed) as $i => $v) {
+ fwrite($value, $args[$i] . ': "' . $v . '" ');
+ }
+ fwrite($value, "\n");
+ }
+ );
+ }
+
+ private function addNotification(array &$params, callable $notify)
+ {
+ // Wrap the existing function if needed.
+ if (!isset($params['notification'])) {
+ $params['notification'] = $notify;
+ } else {
+ $params['notification'] = $this->callArray([
+ $params['notification'],
+ $notify
+ ]);
+ }
+ }
+
+ private function callArray(array $functions)
+ {
+ return function () use ($functions) {
+ $args = func_get_args();
+ foreach ($functions as $fn) {
+ call_user_func_array($fn, $args);
+ }
+ };
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/HandlerStack.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/HandlerStack.php
new file mode 100644
index 000000000..6a49cc069
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/HandlerStack.php
@@ -0,0 +1,277 @@
+push(Middleware::httpErrors(), 'http_errors');
+ $stack->push(Middleware::redirect(), 'allow_redirects');
+ $stack->push(Middleware::cookies(), 'cookies');
+ $stack->push(Middleware::prepareBody(), 'prepare_body');
+
+ return $stack;
+ }
+
+ /**
+ * @param callable $handler Underlying HTTP handler.
+ */
+ public function __construct(callable $handler = null)
+ {
+ $this->handler = $handler;
+ }
+
+ /**
+ * Invokes the handler stack as a composed handler
+ *
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return ResponseInterface|PromiseInterface
+ */
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ $handler = $this->resolve();
+
+ return $handler($request, $options);
+ }
+
+ /**
+ * Dumps a string representation of the stack.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ $depth = 0;
+ $stack = [];
+ if ($this->handler) {
+ $stack[] = "0) Handler: " . $this->debugCallable($this->handler);
+ }
+
+ $result = '';
+ foreach (array_reverse($this->stack) as $tuple) {
+ $depth++;
+ $str = "{$depth}) Name: '{$tuple[1]}', ";
+ $str .= "Function: " . $this->debugCallable($tuple[0]);
+ $result = "> {$str}\n{$result}";
+ $stack[] = $str;
+ }
+
+ foreach (array_keys($stack) as $k) {
+ $result .= "< {$stack[$k]}\n";
+ }
+
+ return $result;
+ }
+
+ /**
+ * Set the HTTP handler that actually returns a promise.
+ *
+ * @param callable $handler Accepts a request and array of options and
+ * returns a Promise.
+ */
+ public function setHandler(callable $handler)
+ {
+ $this->handler = $handler;
+ $this->cached = null;
+ }
+
+ /**
+ * Returns true if the builder has a handler.
+ *
+ * @return bool
+ */
+ public function hasHandler()
+ {
+ return (bool) $this->handler;
+ }
+
+ /**
+ * Unshift a middleware to the bottom of the stack.
+ *
+ * @param callable $middleware Middleware function
+ * @param string $name Name to register for this middleware.
+ */
+ public function unshift(callable $middleware, $name = null)
+ {
+ array_unshift($this->stack, [$middleware, $name]);
+ $this->cached = null;
+ }
+
+ /**
+ * Push a middleware to the top of the stack.
+ *
+ * @param callable $middleware Middleware function
+ * @param string $name Name to register for this middleware.
+ */
+ public function push(callable $middleware, $name = '')
+ {
+ $this->stack[] = [$middleware, $name];
+ $this->cached = null;
+ }
+
+ /**
+ * Add a middleware before another middleware by name.
+ *
+ * @param string $findName Middleware to find
+ * @param callable $middleware Middleware function
+ * @param string $withName Name to register for this middleware.
+ */
+ public function before($findName, callable $middleware, $withName = '')
+ {
+ $this->splice($findName, $withName, $middleware, true);
+ }
+
+ /**
+ * Add a middleware after another middleware by name.
+ *
+ * @param string $findName Middleware to find
+ * @param callable $middleware Middleware function
+ * @param string $withName Name to register for this middleware.
+ */
+ public function after($findName, callable $middleware, $withName = '')
+ {
+ $this->splice($findName, $withName, $middleware, false);
+ }
+
+ /**
+ * Remove a middleware by instance or name from the stack.
+ *
+ * @param callable|string $remove Middleware to remove by instance or name.
+ */
+ public function remove($remove)
+ {
+ $this->cached = null;
+ $idx = is_callable($remove) ? 0 : 1;
+ $this->stack = array_values(array_filter(
+ $this->stack,
+ function ($tuple) use ($idx, $remove) {
+ return $tuple[$idx] !== $remove;
+ }
+ ));
+ }
+
+ /**
+ * Compose the middleware and handler into a single callable function.
+ *
+ * @return callable
+ */
+ public function resolve()
+ {
+ if (!$this->cached) {
+ if (!($prev = $this->handler)) {
+ throw new \LogicException('No handler has been specified');
+ }
+
+ foreach (array_reverse($this->stack) as $fn) {
+ $prev = $fn[0]($prev);
+ }
+
+ $this->cached = $prev;
+ }
+
+ return $this->cached;
+ }
+
+ /**
+ * @param string $name
+ * @return int
+ */
+ private function findByName($name)
+ {
+ foreach ($this->stack as $k => $v) {
+ if ($v[1] === $name) {
+ return $k;
+ }
+ }
+
+ throw new \InvalidArgumentException("Middleware not found: $name");
+ }
+
+ /**
+ * Splices a function into the middleware list at a specific position.
+ *
+ * @param string $findName
+ * @param string $withName
+ * @param callable $middleware
+ * @param bool $before
+ */
+ private function splice($findName, $withName, callable $middleware, $before)
+ {
+ $this->cached = null;
+ $idx = $this->findByName($findName);
+ $tuple = [$middleware, $withName];
+
+ if ($before) {
+ if ($idx === 0) {
+ array_unshift($this->stack, $tuple);
+ } else {
+ $replacement = [$tuple, $this->stack[$idx]];
+ array_splice($this->stack, $idx, 1, $replacement);
+ }
+ } elseif ($idx === count($this->stack) - 1) {
+ $this->stack[] = $tuple;
+ } else {
+ $replacement = [$this->stack[$idx], $tuple];
+ array_splice($this->stack, $idx, 1, $replacement);
+ }
+ }
+
+ /**
+ * Provides a debug string for a given callable.
+ *
+ * @param array|callable $fn Function to write as a string.
+ *
+ * @return string
+ */
+ private function debugCallable($fn)
+ {
+ if (is_string($fn)) {
+ return "callable({$fn})";
+ }
+
+ if (is_array($fn)) {
+ return is_string($fn[0])
+ ? "callable({$fn[0]}::{$fn[1]})"
+ : "callable(['" . get_class($fn[0]) . "', '{$fn[1]}'])";
+ }
+
+ return 'callable(' . spl_object_hash($fn) . ')';
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/MessageFormatter.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/MessageFormatter.php
new file mode 100644
index 000000000..dc36bb524
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/MessageFormatter.php
@@ -0,0 +1,185 @@
+>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}";
+ const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}';
+
+ /** @var string Template used to format log messages */
+ private $template;
+
+ /**
+ * @param string $template Log message template
+ */
+ public function __construct($template = self::CLF)
+ {
+ $this->template = $template ?: self::CLF;
+ }
+
+ /**
+ * Returns a formatted message string.
+ *
+ * @param RequestInterface $request Request that was sent
+ * @param ResponseInterface $response Response that was received
+ * @param \Exception $error Exception that was received
+ *
+ * @return string
+ */
+ public function format(
+ RequestInterface $request,
+ ResponseInterface $response = null,
+ \Exception $error = null
+ ) {
+ $cache = [];
+
+ return preg_replace_callback(
+ '/{\s*([A-Za-z_\-\.0-9]+)\s*}/',
+ function (array $matches) use ($request, $response, $error, &$cache) {
+ if (isset($cache[$matches[1]])) {
+ return $cache[$matches[1]];
+ }
+
+ $result = '';
+ switch ($matches[1]) {
+ case 'request':
+ $result = Psr7\str($request);
+ break;
+ case 'response':
+ $result = $response ? Psr7\str($response) : '';
+ break;
+ case 'req_headers':
+ $result = trim($request->getMethod()
+ . ' ' . $request->getRequestTarget())
+ . ' HTTP/' . $request->getProtocolVersion() . "\r\n"
+ . $this->headers($request);
+ break;
+ case 'res_headers':
+ $result = $response ?
+ sprintf(
+ 'HTTP/%s %d %s',
+ $response->getProtocolVersion(),
+ $response->getStatusCode(),
+ $response->getReasonPhrase()
+ ) . "\r\n" . $this->headers($response)
+ : 'NULL';
+ break;
+ case 'req_body':
+ $result = $request->getBody();
+ break;
+ case 'res_body':
+ $result = $response ? $response->getBody() : 'NULL';
+ break;
+ case 'ts':
+ case 'date_iso_8601':
+ $result = gmdate('c');
+ break;
+ case 'date_common_log':
+ $result = date('d/M/Y:H:i:s O');
+ break;
+ case 'method':
+ $result = $request->getMethod();
+ break;
+ case 'version':
+ $result = $request->getProtocolVersion();
+ break;
+ case 'uri':
+ case 'url':
+ $result = $request->getUri();
+ break;
+ case 'target':
+ $result = $request->getRequestTarget();
+ break;
+ case 'req_version':
+ $result = $request->getProtocolVersion();
+ break;
+ case 'res_version':
+ $result = $response
+ ? $response->getProtocolVersion()
+ : 'NULL';
+ break;
+ case 'host':
+ $result = $request->getHeaderLine('Host');
+ break;
+ case 'hostname':
+ $result = gethostname();
+ break;
+ case 'code':
+ $result = $response ? $response->getStatusCode() : 'NULL';
+ break;
+ case 'phrase':
+ $result = $response ? $response->getReasonPhrase() : 'NULL';
+ break;
+ case 'error':
+ $result = $error ? $error->getMessage() : 'NULL';
+ break;
+ default:
+ // handle prefixed dynamic headers
+ if (strpos($matches[1], 'req_header_') === 0) {
+ $result = $request->getHeaderLine(substr($matches[1], 11));
+ } elseif (strpos($matches[1], 'res_header_') === 0) {
+ $result = $response
+ ? $response->getHeaderLine(substr($matches[1], 11))
+ : 'NULL';
+ }
+ }
+
+ $cache[$matches[1]] = $result;
+ return $result;
+ },
+ $this->template
+ );
+ }
+
+ /**
+ * Get headers from message as string
+ *
+ * @return string
+ */
+ private function headers(MessageInterface $message)
+ {
+ $result = '';
+ foreach ($message->getHeaders() as $name => $values) {
+ $result .= $name . ': ' . implode(', ', $values) . "\r\n";
+ }
+
+ return trim($result);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Middleware.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Middleware.php
new file mode 100644
index 000000000..bffc1974b
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Middleware.php
@@ -0,0 +1,254 @@
+withCookieHeader($request);
+ return $handler($request, $options)
+ ->then(
+ function ($response) use ($cookieJar, $request) {
+ $cookieJar->extractCookies($request, $response);
+ return $response;
+ }
+ );
+ };
+ };
+ }
+
+ /**
+ * Middleware that throws exceptions for 4xx or 5xx responses when the
+ * "http_error" request option is set to true.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function httpErrors()
+ {
+ return function (callable $handler) {
+ return function ($request, array $options) use ($handler) {
+ if (empty($options['http_errors'])) {
+ return $handler($request, $options);
+ }
+ return $handler($request, $options)->then(
+ function (ResponseInterface $response) use ($request) {
+ $code = $response->getStatusCode();
+ if ($code < 400) {
+ return $response;
+ }
+ throw RequestException::create($request, $response);
+ }
+ );
+ };
+ };
+ }
+
+ /**
+ * Middleware that pushes history data to an ArrayAccess container.
+ *
+ * @param array|\ArrayAccess $container Container to hold the history (by reference).
+ *
+ * @return callable Returns a function that accepts the next handler.
+ * @throws \InvalidArgumentException if container is not an array or ArrayAccess.
+ */
+ public static function history(&$container)
+ {
+ if (!is_array($container) && !$container instanceof \ArrayAccess) {
+ throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess');
+ }
+
+ return function (callable $handler) use (&$container) {
+ return function ($request, array $options) use ($handler, &$container) {
+ return $handler($request, $options)->then(
+ function ($value) use ($request, &$container, $options) {
+ $container[] = [
+ 'request' => $request,
+ 'response' => $value,
+ 'error' => null,
+ 'options' => $options
+ ];
+ return $value;
+ },
+ function ($reason) use ($request, &$container, $options) {
+ $container[] = [
+ 'request' => $request,
+ 'response' => null,
+ 'error' => $reason,
+ 'options' => $options
+ ];
+ return \GuzzleHttp\Promise\rejection_for($reason);
+ }
+ );
+ };
+ };
+ }
+
+ /**
+ * Middleware that invokes a callback before and after sending a request.
+ *
+ * The provided listener cannot modify or alter the response. It simply
+ * "taps" into the chain to be notified before returning the promise. The
+ * before listener accepts a request and options array, and the after
+ * listener accepts a request, options array, and response promise.
+ *
+ * @param callable $before Function to invoke before forwarding the request.
+ * @param callable $after Function invoked after forwarding.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function tap(callable $before = null, callable $after = null)
+ {
+ return function (callable $handler) use ($before, $after) {
+ return function ($request, array $options) use ($handler, $before, $after) {
+ if ($before) {
+ $before($request, $options);
+ }
+ $response = $handler($request, $options);
+ if ($after) {
+ $after($request, $options, $response);
+ }
+ return $response;
+ };
+ };
+ }
+
+ /**
+ * Middleware that handles request redirects.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function redirect()
+ {
+ return function (callable $handler) {
+ return new RedirectMiddleware($handler);
+ };
+ }
+
+ /**
+ * Middleware that retries requests based on the boolean result of
+ * invoking the provided "decider" function.
+ *
+ * If no delay function is provided, a simple implementation of exponential
+ * backoff will be utilized.
+ *
+ * @param callable $decider Function that accepts the number of retries,
+ * a request, [response], and [exception] and
+ * returns true if the request is to be retried.
+ * @param callable $delay Function that accepts the number of retries and
+ * returns the number of milliseconds to delay.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function retry(callable $decider, callable $delay = null)
+ {
+ return function (callable $handler) use ($decider, $delay) {
+ return new RetryMiddleware($decider, $handler, $delay);
+ };
+ }
+
+ /**
+ * Middleware that logs requests, responses, and errors using a message
+ * formatter.
+ *
+ * @param LoggerInterface $logger Logs messages.
+ * @param MessageFormatter $formatter Formatter used to create message strings.
+ * @param string $logLevel Level at which to log requests.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function log(LoggerInterface $logger, MessageFormatter $formatter, $logLevel = 'info' /* \Psr\Log\LogLevel::INFO */)
+ {
+ return function (callable $handler) use ($logger, $formatter, $logLevel) {
+ return function ($request, array $options) use ($handler, $logger, $formatter, $logLevel) {
+ return $handler($request, $options)->then(
+ function ($response) use ($logger, $request, $formatter, $logLevel) {
+ $message = $formatter->format($request, $response);
+ $logger->log($logLevel, $message);
+ return $response;
+ },
+ function ($reason) use ($logger, $request, $formatter) {
+ $response = $reason instanceof RequestException
+ ? $reason->getResponse()
+ : null;
+ $message = $formatter->format($request, $response, $reason);
+ $logger->notice($message);
+ return \GuzzleHttp\Promise\rejection_for($reason);
+ }
+ );
+ };
+ };
+ }
+
+ /**
+ * This middleware adds a default content-type if possible, a default
+ * content-length or transfer-encoding header, and the expect header.
+ *
+ * @return callable
+ */
+ public static function prepareBody()
+ {
+ return function (callable $handler) {
+ return new PrepareBodyMiddleware($handler);
+ };
+ }
+
+ /**
+ * Middleware that applies a map function to the request before passing to
+ * the next handler.
+ *
+ * @param callable $fn Function that accepts a RequestInterface and returns
+ * a RequestInterface.
+ * @return callable
+ */
+ public static function mapRequest(callable $fn)
+ {
+ return function (callable $handler) use ($fn) {
+ return function ($request, array $options) use ($handler, $fn) {
+ return $handler($fn($request), $options);
+ };
+ };
+ }
+
+ /**
+ * Middleware that applies a map function to the resolved promise's
+ * response.
+ *
+ * @param callable $fn Function that accepts a ResponseInterface and
+ * returns a ResponseInterface.
+ * @return callable
+ */
+ public static function mapResponse(callable $fn)
+ {
+ return function (callable $handler) use ($fn) {
+ return function ($request, array $options) use ($handler, $fn) {
+ return $handler($request, $options)->then($fn);
+ };
+ };
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Pool.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Pool.php
new file mode 100644
index 000000000..5838db4f4
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Pool.php
@@ -0,0 +1,134 @@
+ $rfn) {
+ if ($rfn instanceof RequestInterface) {
+ yield $key => $client->sendAsync($rfn, $opts);
+ } elseif (is_callable($rfn)) {
+ yield $key => $rfn($opts);
+ } else {
+ throw new \InvalidArgumentException('Each value yielded by '
+ . 'the iterator must be a Psr7\Http\Message\RequestInterface '
+ . 'or a callable that returns a promise that fulfills '
+ . 'with a Psr7\Message\Http\ResponseInterface object.');
+ }
+ }
+ };
+
+ $this->each = new EachPromise($requests(), $config);
+ }
+
+ /**
+ * Get promise
+ *
+ * @return PromiseInterface
+ */
+ public function promise()
+ {
+ return $this->each->promise();
+ }
+
+ /**
+ * Sends multiple requests concurrently and returns an array of responses
+ * and exceptions that uses the same ordering as the provided requests.
+ *
+ * IMPORTANT: This method keeps every request and response in memory, and
+ * as such, is NOT recommended when sending a large number or an
+ * indeterminate number of requests concurrently.
+ *
+ * @param ClientInterface $client Client used to send the requests
+ * @param array|\Iterator $requests Requests to send concurrently.
+ * @param array $options Passes through the options available in
+ * {@see GuzzleHttp\Pool::__construct}
+ *
+ * @return array Returns an array containing the response or an exception
+ * in the same order that the requests were sent.
+ * @throws \InvalidArgumentException if the event format is incorrect.
+ */
+ public static function batch(
+ ClientInterface $client,
+ $requests,
+ array $options = []
+ ) {
+ $res = [];
+ self::cmpCallback($options, 'fulfilled', $res);
+ self::cmpCallback($options, 'rejected', $res);
+ $pool = new static($client, $requests, $options);
+ $pool->promise()->wait();
+ ksort($res);
+
+ return $res;
+ }
+
+ /**
+ * Execute callback(s)
+ *
+ * @return void
+ */
+ private static function cmpCallback(array &$options, $name, array &$results)
+ {
+ if (!isset($options[$name])) {
+ $options[$name] = function ($v, $k) use (&$results) {
+ $results[$k] = $v;
+ };
+ } else {
+ $currentFn = $options[$name];
+ $options[$name] = function ($v, $k) use (&$results, $currentFn) {
+ $currentFn($v, $k);
+ $results[$k] = $v;
+ };
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php
new file mode 100644
index 000000000..568a1e906
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php
@@ -0,0 +1,111 @@
+nextHandler = $nextHandler;
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return PromiseInterface
+ */
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ $fn = $this->nextHandler;
+
+ // Don't do anything if the request has no body.
+ if ($request->getBody()->getSize() === 0) {
+ return $fn($request, $options);
+ }
+
+ $modify = [];
+
+ // Add a default content-type if possible.
+ if (!$request->hasHeader('Content-Type')) {
+ if ($uri = $request->getBody()->getMetadata('uri')) {
+ if ($type = Psr7\mimetype_from_filename($uri)) {
+ $modify['set_headers']['Content-Type'] = $type;
+ }
+ }
+ }
+
+ // Add a default content-length or transfer-encoding header.
+ if (!$request->hasHeader('Content-Length')
+ && !$request->hasHeader('Transfer-Encoding')
+ ) {
+ $size = $request->getBody()->getSize();
+ if ($size !== null) {
+ $modify['set_headers']['Content-Length'] = $size;
+ } else {
+ $modify['set_headers']['Transfer-Encoding'] = 'chunked';
+ }
+ }
+
+ // Add the expect header if needed.
+ $this->addExpectHeader($request, $options, $modify);
+
+ return $fn(Psr7\modify_request($request, $modify), $options);
+ }
+
+ /**
+ * Add expect header
+ *
+ * @return void
+ */
+ private function addExpectHeader(
+ RequestInterface $request,
+ array $options,
+ array &$modify
+ ) {
+ // Determine if the Expect header should be used
+ if ($request->hasHeader('Expect')) {
+ return;
+ }
+
+ $expect = isset($options['expect']) ? $options['expect'] : null;
+
+ // Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0
+ if ($expect === false || $request->getProtocolVersion() < 1.1) {
+ return;
+ }
+
+ // The expect header is unconditionally enabled
+ if ($expect === true) {
+ $modify['set_headers']['Expect'] = '100-Continue';
+ return;
+ }
+
+ // By default, send the expect header when the payload is > 1mb
+ if ($expect === null) {
+ $expect = 1048576;
+ }
+
+ // Always add if the body cannot be rewound, the size cannot be
+ // determined, or the size is greater than the cutoff threshold
+ $body = $request->getBody();
+ $size = $body->getSize();
+
+ if ($size === null || $size >= (int) $expect || !$body->isSeekable()) {
+ $modify['set_headers']['Expect'] = '100-Continue';
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php
new file mode 100644
index 000000000..e4644b7ac
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php
@@ -0,0 +1,255 @@
+ 5,
+ 'protocols' => ['http', 'https'],
+ 'strict' => false,
+ 'referer' => false,
+ 'track_redirects' => false,
+ ];
+
+ /** @var callable */
+ private $nextHandler;
+
+ /**
+ * @param callable $nextHandler Next handler to invoke.
+ */
+ public function __construct(callable $nextHandler)
+ {
+ $this->nextHandler = $nextHandler;
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return PromiseInterface
+ */
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ $fn = $this->nextHandler;
+
+ if (empty($options['allow_redirects'])) {
+ return $fn($request, $options);
+ }
+
+ if ($options['allow_redirects'] === true) {
+ $options['allow_redirects'] = self::$defaultSettings;
+ } elseif (!is_array($options['allow_redirects'])) {
+ throw new \InvalidArgumentException('allow_redirects must be true, false, or array');
+ } else {
+ // Merge the default settings with the provided settings
+ $options['allow_redirects'] += self::$defaultSettings;
+ }
+
+ if (empty($options['allow_redirects']['max'])) {
+ return $fn($request, $options);
+ }
+
+ return $fn($request, $options)
+ ->then(function (ResponseInterface $response) use ($request, $options) {
+ return $this->checkRedirect($request, $options, $response);
+ });
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ * @param ResponseInterface $response
+ *
+ * @return ResponseInterface|PromiseInterface
+ */
+ public function checkRedirect(
+ RequestInterface $request,
+ array $options,
+ ResponseInterface $response
+ ) {
+ if (substr($response->getStatusCode(), 0, 1) != '3'
+ || !$response->hasHeader('Location')
+ ) {
+ return $response;
+ }
+
+ $this->guardMax($request, $options);
+ $nextRequest = $this->modifyRequest($request, $options, $response);
+
+ if (isset($options['allow_redirects']['on_redirect'])) {
+ call_user_func(
+ $options['allow_redirects']['on_redirect'],
+ $request,
+ $response,
+ $nextRequest->getUri()
+ );
+ }
+
+ /** @var PromiseInterface|ResponseInterface $promise */
+ $promise = $this($nextRequest, $options);
+
+ // Add headers to be able to track history of redirects.
+ if (!empty($options['allow_redirects']['track_redirects'])) {
+ return $this->withTracking(
+ $promise,
+ (string) $nextRequest->getUri(),
+ $response->getStatusCode()
+ );
+ }
+
+ return $promise;
+ }
+
+ /**
+ * Enable tracking on promise.
+ *
+ * @return PromiseInterface
+ */
+ private function withTracking(PromiseInterface $promise, $uri, $statusCode)
+ {
+ return $promise->then(
+ function (ResponseInterface $response) use ($uri, $statusCode) {
+ // Note that we are pushing to the front of the list as this
+ // would be an earlier response than what is currently present
+ // in the history header.
+ $historyHeader = $response->getHeader(self::HISTORY_HEADER);
+ $statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER);
+ array_unshift($historyHeader, $uri);
+ array_unshift($statusHeader, $statusCode);
+ return $response->withHeader(self::HISTORY_HEADER, $historyHeader)
+ ->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader);
+ }
+ );
+ }
+
+ /**
+ * Check for too many redirects
+ *
+ * @return void
+ *
+ * @throws TooManyRedirectsException Too many redirects.
+ */
+ private function guardMax(RequestInterface $request, array &$options)
+ {
+ $current = isset($options['__redirect_count'])
+ ? $options['__redirect_count']
+ : 0;
+ $options['__redirect_count'] = $current + 1;
+ $max = $options['allow_redirects']['max'];
+
+ if ($options['__redirect_count'] > $max) {
+ throw new TooManyRedirectsException(
+ "Will not follow more than {$max} redirects",
+ $request
+ );
+ }
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ * @param ResponseInterface $response
+ *
+ * @return RequestInterface
+ */
+ public function modifyRequest(
+ RequestInterface $request,
+ array $options,
+ ResponseInterface $response
+ ) {
+ // Request modifications to apply.
+ $modify = [];
+ $protocols = $options['allow_redirects']['protocols'];
+
+ // Use a GET request if this is an entity enclosing request and we are
+ // not forcing RFC compliance, but rather emulating what all browsers
+ // would do.
+ $statusCode = $response->getStatusCode();
+ if ($statusCode == 303 ||
+ ($statusCode <= 302 && !$options['allow_redirects']['strict'])
+ ) {
+ $modify['method'] = 'GET';
+ $modify['body'] = '';
+ }
+
+ $uri = $this->redirectUri($request, $response, $protocols);
+ if (isset($options['idn_conversion']) && ($options['idn_conversion'] !== false)) {
+ $idnOptions = ($options['idn_conversion'] === true) ? IDNA_DEFAULT : $options['idn_conversion'];
+ $uri = Utils::idnUriConvert($uri, $idnOptions);
+ }
+
+ $modify['uri'] = $uri;
+ Psr7\rewind_body($request);
+
+ // Add the Referer header if it is told to do so and only
+ // add the header if we are not redirecting from https to http.
+ if ($options['allow_redirects']['referer']
+ && $modify['uri']->getScheme() === $request->getUri()->getScheme()
+ ) {
+ $uri = $request->getUri()->withUserInfo('');
+ $modify['set_headers']['Referer'] = (string) $uri;
+ } else {
+ $modify['remove_headers'][] = 'Referer';
+ }
+
+ // Remove Authorization header if host is different.
+ if ($request->getUri()->getHost() !== $modify['uri']->getHost()) {
+ $modify['remove_headers'][] = 'Authorization';
+ }
+
+ return Psr7\modify_request($request, $modify);
+ }
+
+ /**
+ * Set the appropriate URL on the request based on the location header
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @param array $protocols
+ *
+ * @return UriInterface
+ */
+ private function redirectUri(
+ RequestInterface $request,
+ ResponseInterface $response,
+ array $protocols
+ ) {
+ $location = Psr7\UriResolver::resolve(
+ $request->getUri(),
+ new Psr7\Uri($response->getHeaderLine('Location'))
+ );
+
+ // Ensure that the redirect URI is allowed based on the protocols.
+ if (!in_array($location->getScheme(), $protocols)) {
+ throw new BadResponseException(
+ sprintf(
+ 'Redirect URI, %s, does not use one of the allowed redirect protocols: %s',
+ $location,
+ implode(', ', $protocols)
+ ),
+ $request,
+ $response
+ );
+ }
+
+ return $location;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/RequestOptions.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/RequestOptions.php
new file mode 100644
index 000000000..355f658f0
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/RequestOptions.php
@@ -0,0 +1,263 @@
+decider = $decider;
+ $this->nextHandler = $nextHandler;
+ $this->delay = $delay ?: __CLASS__ . '::exponentialDelay';
+ }
+
+ /**
+ * Default exponential backoff delay function.
+ *
+ * @param int $retries
+ *
+ * @return int milliseconds.
+ */
+ public static function exponentialDelay($retries)
+ {
+ return (int) pow(2, $retries - 1) * 1000;
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return PromiseInterface
+ */
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ if (!isset($options['retries'])) {
+ $options['retries'] = 0;
+ }
+
+ $fn = $this->nextHandler;
+ return $fn($request, $options)
+ ->then(
+ $this->onFulfilled($request, $options),
+ $this->onRejected($request, $options)
+ );
+ }
+
+ /**
+ * Execute fulfilled closure
+ *
+ * @return mixed
+ */
+ private function onFulfilled(RequestInterface $req, array $options)
+ {
+ return function ($value) use ($req, $options) {
+ if (!call_user_func(
+ $this->decider,
+ $options['retries'],
+ $req,
+ $value,
+ null
+ )) {
+ return $value;
+ }
+ return $this->doRetry($req, $options, $value);
+ };
+ }
+
+ /**
+ * Execute rejected closure
+ *
+ * @return callable
+ */
+ private function onRejected(RequestInterface $req, array $options)
+ {
+ return function ($reason) use ($req, $options) {
+ if (!call_user_func(
+ $this->decider,
+ $options['retries'],
+ $req,
+ null,
+ $reason
+ )) {
+ return \GuzzleHttp\Promise\rejection_for($reason);
+ }
+ return $this->doRetry($req, $options);
+ };
+ }
+
+ /**
+ * @return self
+ */
+ private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null)
+ {
+ $options['delay'] = call_user_func($this->delay, ++$options['retries'], $response);
+
+ return $this($request, $options);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/TransferStats.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/TransferStats.php
new file mode 100644
index 000000000..87fb3c001
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/TransferStats.php
@@ -0,0 +1,126 @@
+request = $request;
+ $this->response = $response;
+ $this->transferTime = $transferTime;
+ $this->handlerErrorData = $handlerErrorData;
+ $this->handlerStats = $handlerStats;
+ }
+
+ /**
+ * @return RequestInterface
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ /**
+ * Returns the response that was received (if any).
+ *
+ * @return ResponseInterface|null
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+ /**
+ * Returns true if a response was received.
+ *
+ * @return bool
+ */
+ public function hasResponse()
+ {
+ return $this->response !== null;
+ }
+
+ /**
+ * Gets handler specific error data.
+ *
+ * This might be an exception, a integer representing an error code, or
+ * anything else. Relying on this value assumes that you know what handler
+ * you are using.
+ *
+ * @return mixed
+ */
+ public function getHandlerErrorData()
+ {
+ return $this->handlerErrorData;
+ }
+
+ /**
+ * Get the effective URI the request was sent to.
+ *
+ * @return UriInterface
+ */
+ public function getEffectiveUri()
+ {
+ return $this->request->getUri();
+ }
+
+ /**
+ * Get the estimated time the request was being transferred by the handler.
+ *
+ * @return float|null Time in seconds.
+ */
+ public function getTransferTime()
+ {
+ return $this->transferTime;
+ }
+
+ /**
+ * Gets an array of all of the handler specific transfer data.
+ *
+ * @return array
+ */
+ public function getHandlerStats()
+ {
+ return $this->handlerStats;
+ }
+
+ /**
+ * Get a specific handler statistic from the handler by name.
+ *
+ * @param string $stat Handler specific transfer stat to retrieve.
+ *
+ * @return mixed|null
+ */
+ public function getHandlerStat($stat)
+ {
+ return isset($this->handlerStats[$stat])
+ ? $this->handlerStats[$stat]
+ : null;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/UriTemplate.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/UriTemplate.php
new file mode 100644
index 000000000..96dcfd09c
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/UriTemplate.php
@@ -0,0 +1,237 @@
+ ['prefix' => '', 'joiner' => ',', 'query' => false],
+ '+' => ['prefix' => '', 'joiner' => ',', 'query' => false],
+ '#' => ['prefix' => '#', 'joiner' => ',', 'query' => false],
+ '.' => ['prefix' => '.', 'joiner' => '.', 'query' => false],
+ '/' => ['prefix' => '/', 'joiner' => '/', 'query' => false],
+ ';' => ['prefix' => ';', 'joiner' => ';', 'query' => true],
+ '?' => ['prefix' => '?', 'joiner' => '&', 'query' => true],
+ '&' => ['prefix' => '&', 'joiner' => '&', 'query' => true]
+ ];
+
+ /** @var array Delimiters */
+ private static $delims = [':', '/', '?', '#', '[', ']', '@', '!', '$',
+ '&', '\'', '(', ')', '*', '+', ',', ';', '='];
+
+ /** @var array Percent encoded delimiters */
+ private static $delimsPct = ['%3A', '%2F', '%3F', '%23', '%5B', '%5D',
+ '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C',
+ '%3B', '%3D'];
+
+ public function expand($template, array $variables)
+ {
+ if (false === strpos($template, '{')) {
+ return $template;
+ }
+
+ $this->template = $template;
+ $this->variables = $variables;
+
+ return preg_replace_callback(
+ '/\{([^\}]+)\}/',
+ [$this, 'expandMatch'],
+ $this->template
+ );
+ }
+
+ /**
+ * Parse an expression into parts
+ *
+ * @param string $expression Expression to parse
+ *
+ * @return array Returns an associative array of parts
+ */
+ private function parseExpression($expression)
+ {
+ $result = [];
+
+ if (isset(self::$operatorHash[$expression[0]])) {
+ $result['operator'] = $expression[0];
+ $expression = substr($expression, 1);
+ } else {
+ $result['operator'] = '';
+ }
+
+ foreach (explode(',', $expression) as $value) {
+ $value = trim($value);
+ $varspec = [];
+ if ($colonPos = strpos($value, ':')) {
+ $varspec['value'] = substr($value, 0, $colonPos);
+ $varspec['modifier'] = ':';
+ $varspec['position'] = (int) substr($value, $colonPos + 1);
+ } elseif (substr($value, -1) === '*') {
+ $varspec['modifier'] = '*';
+ $varspec['value'] = substr($value, 0, -1);
+ } else {
+ $varspec['value'] = (string) $value;
+ $varspec['modifier'] = '';
+ }
+ $result['values'][] = $varspec;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Process an expansion
+ *
+ * @param array $matches Matches met in the preg_replace_callback
+ *
+ * @return string Returns the replacement string
+ */
+ private function expandMatch(array $matches)
+ {
+ static $rfc1738to3986 = ['+' => '%20', '%7e' => '~'];
+
+ $replacements = [];
+ $parsed = self::parseExpression($matches[1]);
+ $prefix = self::$operatorHash[$parsed['operator']]['prefix'];
+ $joiner = self::$operatorHash[$parsed['operator']]['joiner'];
+ $useQuery = self::$operatorHash[$parsed['operator']]['query'];
+
+ foreach ($parsed['values'] as $value) {
+ if (!isset($this->variables[$value['value']])) {
+ continue;
+ }
+
+ $variable = $this->variables[$value['value']];
+ $actuallyUseQuery = $useQuery;
+ $expanded = '';
+
+ if (is_array($variable)) {
+ $isAssoc = $this->isAssoc($variable);
+ $kvp = [];
+ foreach ($variable as $key => $var) {
+ if ($isAssoc) {
+ $key = rawurlencode($key);
+ $isNestedArray = is_array($var);
+ } else {
+ $isNestedArray = false;
+ }
+
+ if (!$isNestedArray) {
+ $var = rawurlencode($var);
+ if ($parsed['operator'] === '+' ||
+ $parsed['operator'] === '#'
+ ) {
+ $var = $this->decodeReserved($var);
+ }
+ }
+
+ if ($value['modifier'] === '*') {
+ if ($isAssoc) {
+ if ($isNestedArray) {
+ // Nested arrays must allow for deeply nested
+ // structures.
+ $var = strtr(
+ http_build_query([$key => $var]),
+ $rfc1738to3986
+ );
+ } else {
+ $var = $key . '=' . $var;
+ }
+ } elseif ($key > 0 && $actuallyUseQuery) {
+ $var = $value['value'] . '=' . $var;
+ }
+ }
+
+ $kvp[$key] = $var;
+ }
+
+ if (empty($variable)) {
+ $actuallyUseQuery = false;
+ } elseif ($value['modifier'] === '*') {
+ $expanded = implode($joiner, $kvp);
+ if ($isAssoc) {
+ // Don't prepend the value name when using the explode
+ // modifier with an associative array.
+ $actuallyUseQuery = false;
+ }
+ } else {
+ if ($isAssoc) {
+ // When an associative array is encountered and the
+ // explode modifier is not set, then the result must be
+ // a comma separated list of keys followed by their
+ // respective values.
+ foreach ($kvp as $k => &$v) {
+ $v = $k . ',' . $v;
+ }
+ }
+ $expanded = implode(',', $kvp);
+ }
+ } else {
+ if ($value['modifier'] === ':') {
+ $variable = substr($variable, 0, $value['position']);
+ }
+ $expanded = rawurlencode($variable);
+ if ($parsed['operator'] === '+' || $parsed['operator'] === '#') {
+ $expanded = $this->decodeReserved($expanded);
+ }
+ }
+
+ if ($actuallyUseQuery) {
+ if (!$expanded && $joiner !== '&') {
+ $expanded = $value['value'];
+ } else {
+ $expanded = $value['value'] . '=' . $expanded;
+ }
+ }
+
+ $replacements[] = $expanded;
+ }
+
+ $ret = implode($joiner, $replacements);
+ if ($ret && $prefix) {
+ return $prefix . $ret;
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Determines if an array is associative.
+ *
+ * This makes the assumption that input arrays are sequences or hashes.
+ * This assumption is a tradeoff for accuracy in favor of speed, but it
+ * should work in almost every case where input is supplied for a URI
+ * template.
+ *
+ * @param array $array Array to check
+ *
+ * @return bool
+ */
+ private function isAssoc(array $array)
+ {
+ return $array && array_keys($array)[0] !== 0;
+ }
+
+ /**
+ * Removes percent encoding on reserved characters (used with + and #
+ * modifiers).
+ *
+ * @param string $string String to fix
+ *
+ * @return string
+ */
+ private function decodeReserved($string)
+ {
+ return str_replace(self::$delimsPct, self::$delims, $string);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Utils.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Utils.php
new file mode 100644
index 000000000..c8fc1aec6
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/Utils.php
@@ -0,0 +1,67 @@
+getHost()) {
+ $idnaVariant = defined('INTL_IDNA_VARIANT_UTS46') ? INTL_IDNA_VARIANT_UTS46 : 0;
+ $asciiHost = $idnaVariant === 0
+ ? idn_to_ascii($uri->getHost(), $options)
+ : idn_to_ascii($uri->getHost(), $options, $idnaVariant, $info);
+ if ($asciiHost === false) {
+ $errorBitSet = isset($info['errors']) ? $info['errors'] : 0;
+
+ $errorConstants = array_filter(array_keys(get_defined_constants()), function ($name) {
+ return substr($name, 0, 11) === 'IDNA_ERROR_';
+ });
+
+ $errors = [];
+ foreach ($errorConstants as $errorConstant) {
+ if ($errorBitSet & constant($errorConstant)) {
+ $errors[] = $errorConstant;
+ }
+ }
+
+ $errorMessage = 'IDN conversion failed';
+ if ($errors) {
+ $errorMessage .= ' (errors: ' . implode(', ', $errors) . ')';
+ }
+
+ throw new InvalidArgumentException($errorMessage);
+ } else {
+ if ($uri->getHost() !== $asciiHost) {
+ // Replace URI only if the ASCII version is different
+ $uri = $uri->withHost($asciiHost);
+ }
+ }
+ }
+
+ return $uri;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/functions.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/functions.php
new file mode 100644
index 000000000..c2afd8c7b
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/functions.php
@@ -0,0 +1,334 @@
+expand($template, $variables);
+}
+
+/**
+ * Debug function used to describe the provided value type and class.
+ *
+ * @param mixed $input
+ *
+ * @return string Returns a string containing the type of the variable and
+ * if a class is provided, the class name.
+ */
+function describe_type($input)
+{
+ switch (gettype($input)) {
+ case 'object':
+ return 'object(' . get_class($input) . ')';
+ case 'array':
+ return 'array(' . count($input) . ')';
+ default:
+ ob_start();
+ var_dump($input);
+ // normalize float vs double
+ return str_replace('double(', 'float(', rtrim(ob_get_clean()));
+ }
+}
+
+/**
+ * Parses an array of header lines into an associative array of headers.
+ *
+ * @param iterable $lines Header lines array of strings in the following
+ * format: "Name: Value"
+ * @return array
+ */
+function headers_from_lines($lines)
+{
+ $headers = [];
+
+ foreach ($lines as $line) {
+ $parts = explode(':', $line, 2);
+ $headers[trim($parts[0])][] = isset($parts[1])
+ ? trim($parts[1])
+ : null;
+ }
+
+ return $headers;
+}
+
+/**
+ * Returns a debug stream based on the provided variable.
+ *
+ * @param mixed $value Optional value
+ *
+ * @return resource
+ */
+function debug_resource($value = null)
+{
+ if (is_resource($value)) {
+ return $value;
+ } elseif (defined('STDOUT')) {
+ return STDOUT;
+ }
+
+ return fopen('php://output', 'w');
+}
+
+/**
+ * Chooses and creates a default handler to use based on the environment.
+ *
+ * The returned handler is not wrapped by any default middlewares.
+ *
+ * @return callable Returns the best handler for the given system.
+ * @throws \RuntimeException if no viable Handler is available.
+ */
+function choose_handler()
+{
+ $handler = null;
+ if (function_exists('curl_multi_exec') && function_exists('curl_exec')) {
+ $handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler());
+ } elseif (function_exists('curl_exec')) {
+ $handler = new CurlHandler();
+ } elseif (function_exists('curl_multi_exec')) {
+ $handler = new CurlMultiHandler();
+ }
+
+ if (ini_get('allow_url_fopen')) {
+ $handler = $handler
+ ? Proxy::wrapStreaming($handler, new StreamHandler())
+ : new StreamHandler();
+ } elseif (!$handler) {
+ throw new \RuntimeException('GuzzleHttp requires cURL, the '
+ . 'allow_url_fopen ini setting, or a custom HTTP handler.');
+ }
+
+ return $handler;
+}
+
+/**
+ * Get the default User-Agent string to use with Guzzle
+ *
+ * @return string
+ */
+function default_user_agent()
+{
+ static $defaultAgent = '';
+
+ if (!$defaultAgent) {
+ $defaultAgent = 'GuzzleHttp/' . Client::VERSION;
+ if (extension_loaded('curl') && function_exists('curl_version')) {
+ $defaultAgent .= ' curl/' . \curl_version()['version'];
+ }
+ $defaultAgent .= ' PHP/' . PHP_VERSION;
+ }
+
+ return $defaultAgent;
+}
+
+/**
+ * Returns the default cacert bundle for the current system.
+ *
+ * First, the openssl.cafile and curl.cainfo php.ini settings are checked.
+ * If those settings are not configured, then the common locations for
+ * bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X
+ * and Windows are checked. If any of these file locations are found on
+ * disk, they will be utilized.
+ *
+ * Note: the result of this function is cached for subsequent calls.
+ *
+ * @return string
+ * @throws \RuntimeException if no bundle can be found.
+ */
+function default_ca_bundle()
+{
+ static $cached = null;
+ static $cafiles = [
+ // Red Hat, CentOS, Fedora (provided by the ca-certificates package)
+ '/etc/pki/tls/certs/ca-bundle.crt',
+ // Ubuntu, Debian (provided by the ca-certificates package)
+ '/etc/ssl/certs/ca-certificates.crt',
+ // FreeBSD (provided by the ca_root_nss package)
+ '/usr/local/share/certs/ca-root-nss.crt',
+ // SLES 12 (provided by the ca-certificates package)
+ '/var/lib/ca-certificates/ca-bundle.pem',
+ // OS X provided by homebrew (using the default path)
+ '/usr/local/etc/openssl/cert.pem',
+ // Google app engine
+ '/etc/ca-certificates.crt',
+ // Windows?
+ 'C:\\windows\\system32\\curl-ca-bundle.crt',
+ 'C:\\windows\\curl-ca-bundle.crt',
+ ];
+
+ if ($cached) {
+ return $cached;
+ }
+
+ if ($ca = ini_get('openssl.cafile')) {
+ return $cached = $ca;
+ }
+
+ if ($ca = ini_get('curl.cainfo')) {
+ return $cached = $ca;
+ }
+
+ foreach ($cafiles as $filename) {
+ if (file_exists($filename)) {
+ return $cached = $filename;
+ }
+ }
+
+ throw new \RuntimeException(
+ <<< EOT
+No system CA bundle could be found in any of the the common system locations.
+PHP versions earlier than 5.6 are not properly configured to use the system's
+CA bundle by default. In order to verify peer certificates, you will need to
+supply the path on disk to a certificate bundle to the 'verify' request
+option: http://docs.guzzlephp.org/en/latest/clients.html#verify. If you do not
+need a specific certificate bundle, then Mozilla provides a commonly used CA
+bundle which can be downloaded here (provided by the maintainer of cURL):
+https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt. Once
+you have a CA bundle available on disk, you can set the 'openssl.cafile' PHP
+ini setting to point to the path to the file, allowing you to omit the 'verify'
+request option. See http://curl.haxx.se/docs/sslcerts.html for more
+information.
+EOT
+ );
+}
+
+/**
+ * Creates an associative array of lowercase header names to the actual
+ * header casing.
+ *
+ * @param array $headers
+ *
+ * @return array
+ */
+function normalize_header_keys(array $headers)
+{
+ $result = [];
+ foreach (array_keys($headers) as $key) {
+ $result[strtolower($key)] = $key;
+ }
+
+ return $result;
+}
+
+/**
+ * Returns true if the provided host matches any of the no proxy areas.
+ *
+ * This method will strip a port from the host if it is present. Each pattern
+ * can be matched with an exact match (e.g., "foo.com" == "foo.com") or a
+ * partial match: (e.g., "foo.com" == "baz.foo.com" and ".foo.com" ==
+ * "baz.foo.com", but ".foo.com" != "foo.com").
+ *
+ * Areas are matched in the following cases:
+ * 1. "*" (without quotes) always matches any hosts.
+ * 2. An exact match.
+ * 3. The area starts with "." and the area is the last part of the host. e.g.
+ * '.mit.edu' will match any host that ends with '.mit.edu'.
+ *
+ * @param string $host Host to check against the patterns.
+ * @param array $noProxyArray An array of host patterns.
+ *
+ * @return bool
+ */
+function is_host_in_noproxy($host, array $noProxyArray)
+{
+ if (strlen($host) === 0) {
+ throw new \InvalidArgumentException('Empty host provided');
+ }
+
+ // Strip port if present.
+ if (strpos($host, ':')) {
+ $host = explode($host, ':', 2)[0];
+ }
+
+ foreach ($noProxyArray as $area) {
+ // Always match on wildcards.
+ if ($area === '*') {
+ return true;
+ } elseif (empty($area)) {
+ // Don't match on empty values.
+ continue;
+ } elseif ($area === $host) {
+ // Exact matches.
+ return true;
+ } else {
+ // Special match if the area when prefixed with ".". Remove any
+ // existing leading "." and add a new leading ".".
+ $area = '.' . ltrim($area, '.');
+ if (substr($host, -(strlen($area))) === $area) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Wrapper for json_decode that throws when an error occurs.
+ *
+ * @param string $json JSON data to parse
+ * @param bool $assoc When true, returned objects will be converted
+ * into associative arrays.
+ * @param int $depth User specified recursion depth.
+ * @param int $options Bitmask of JSON decode options.
+ *
+ * @return mixed
+ * @throws Exception\InvalidArgumentException if the JSON cannot be decoded.
+ * @link http://www.php.net/manual/en/function.json-decode.php
+ */
+function json_decode($json, $assoc = false, $depth = 512, $options = 0)
+{
+ $data = \json_decode($json, $assoc, $depth, $options);
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ throw new Exception\InvalidArgumentException(
+ 'json_decode error: ' . json_last_error_msg()
+ );
+ }
+
+ return $data;
+}
+
+/**
+ * Wrapper for JSON encoding that throws when an error occurs.
+ *
+ * @param mixed $value The value being encoded
+ * @param int $options JSON encode option bitmask
+ * @param int $depth Set the maximum depth. Must be greater than zero.
+ *
+ * @return string
+ * @throws Exception\InvalidArgumentException if the JSON cannot be encoded.
+ * @link http://www.php.net/manual/en/function.json-encode.php
+ */
+function json_encode($value, $options = 0, $depth = 512)
+{
+ $json = \json_encode($value, $options, $depth);
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ throw new Exception\InvalidArgumentException(
+ 'json_encode error: ' . json_last_error_msg()
+ );
+ }
+
+ return $json;
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/functions_include.php b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/functions_include.php
new file mode 100644
index 000000000..a93393acc
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/guzzle/src/functions_include.php
@@ -0,0 +1,6 @@
+
+
+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.
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/promises/Makefile b/plugins/panakour/backup/vendor/guzzlehttp/promises/Makefile
new file mode 100644
index 000000000..8d5b3ef95
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/promises/Makefile
@@ -0,0 +1,13 @@
+all: clean test
+
+test:
+ vendor/bin/phpunit
+
+coverage:
+ vendor/bin/phpunit --coverage-html=artifacts/coverage
+
+view-coverage:
+ open artifacts/coverage/index.html
+
+clean:
+ rm -rf artifacts/*
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/promises/README.md b/plugins/panakour/backup/vendor/guzzlehttp/promises/README.md
new file mode 100644
index 000000000..7b607e28b
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/promises/README.md
@@ -0,0 +1,504 @@
+# Guzzle Promises
+
+[Promises/A+](https://promisesaplus.com/) implementation that handles promise
+chaining and resolution iteratively, allowing for "infinite" promise chaining
+while keeping the stack size constant. Read [this blog post](https://blog.domenic.me/youre-missing-the-point-of-promises/)
+for a general introduction to promises.
+
+- [Features](#features)
+- [Quick start](#quick-start)
+- [Synchronous wait](#synchronous-wait)
+- [Cancellation](#cancellation)
+- [API](#api)
+ - [Promise](#promise)
+ - [FulfilledPromise](#fulfilledpromise)
+ - [RejectedPromise](#rejectedpromise)
+- [Promise interop](#promise-interop)
+- [Implementation notes](#implementation-notes)
+
+
+# Features
+
+- [Promises/A+](https://promisesaplus.com/) implementation.
+- Promise resolution and chaining is handled iteratively, allowing for
+ "infinite" promise chaining.
+- Promises have a synchronous `wait` method.
+- Promises can be cancelled.
+- Works with any object that has a `then` function.
+- C# style async/await coroutine promises using
+ `GuzzleHttp\Promise\coroutine()`.
+
+
+# Quick start
+
+A *promise* represents the eventual result of an asynchronous operation. The
+primary way of interacting with a promise is through its `then` method, which
+registers callbacks to receive either a promise's eventual value or the reason
+why the promise cannot be fulfilled.
+
+
+## Callbacks
+
+Callbacks are registered with the `then` method by providing an optional
+`$onFulfilled` followed by an optional `$onRejected` function.
+
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise();
+$promise->then(
+ // $onFulfilled
+ function ($value) {
+ echo 'The promise was fulfilled.';
+ },
+ // $onRejected
+ function ($reason) {
+ echo 'The promise was rejected.';
+ }
+);
+```
+
+*Resolving* a promise means that you either fulfill a promise with a *value* or
+reject a promise with a *reason*. Resolving a promises triggers callbacks
+registered with the promises's `then` method. These callbacks are triggered
+only once and in the order in which they were added.
+
+
+## Resolving a promise
+
+Promises are fulfilled using the `resolve($value)` method. Resolving a promise
+with any value other than a `GuzzleHttp\Promise\RejectedPromise` will trigger
+all of the onFulfilled callbacks (resolving a promise with a rejected promise
+will reject the promise and trigger the `$onRejected` callbacks).
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise();
+$promise
+ ->then(function ($value) {
+ // Return a value and don't break the chain
+ return "Hello, " . $value;
+ })
+ // This then is executed after the first then and receives the value
+ // returned from the first then.
+ ->then(function ($value) {
+ echo $value;
+ });
+
+// Resolving the promise triggers the $onFulfilled callbacks and outputs
+// "Hello, reader".
+$promise->resolve('reader.');
+```
+
+
+## Promise forwarding
+
+Promises can be chained one after the other. Each then in the chain is a new
+promise. The return value of a promise is what's forwarded to the next
+promise in the chain. Returning a promise in a `then` callback will cause the
+subsequent promises in the chain to only be fulfilled when the returned promise
+has been fulfilled. The next promise in the chain will be invoked with the
+resolved value of the promise.
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise();
+$nextPromise = new Promise();
+
+$promise
+ ->then(function ($value) use ($nextPromise) {
+ echo $value;
+ return $nextPromise;
+ })
+ ->then(function ($value) {
+ echo $value;
+ });
+
+// Triggers the first callback and outputs "A"
+$promise->resolve('A');
+// Triggers the second callback and outputs "B"
+$nextPromise->resolve('B');
+```
+
+## Promise rejection
+
+When a promise is rejected, the `$onRejected` callbacks are invoked with the
+rejection reason.
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise();
+$promise->then(null, function ($reason) {
+ echo $reason;
+});
+
+$promise->reject('Error!');
+// Outputs "Error!"
+```
+
+## Rejection forwarding
+
+If an exception is thrown in an `$onRejected` callback, subsequent
+`$onRejected` callbacks are invoked with the thrown exception as the reason.
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise();
+$promise->then(null, function ($reason) {
+ throw new \Exception($reason);
+})->then(null, function ($reason) {
+ assert($reason->getMessage() === 'Error!');
+});
+
+$promise->reject('Error!');
+```
+
+You can also forward a rejection down the promise chain by returning a
+`GuzzleHttp\Promise\RejectedPromise` in either an `$onFulfilled` or
+`$onRejected` callback.
+
+```php
+use GuzzleHttp\Promise\Promise;
+use GuzzleHttp\Promise\RejectedPromise;
+
+$promise = new Promise();
+$promise->then(null, function ($reason) {
+ return new RejectedPromise($reason);
+})->then(null, function ($reason) {
+ assert($reason === 'Error!');
+});
+
+$promise->reject('Error!');
+```
+
+If an exception is not thrown in a `$onRejected` callback and the callback
+does not return a rejected promise, downstream `$onFulfilled` callbacks are
+invoked using the value returned from the `$onRejected` callback.
+
+```php
+use GuzzleHttp\Promise\Promise;
+use GuzzleHttp\Promise\RejectedPromise;
+
+$promise = new Promise();
+$promise
+ ->then(null, function ($reason) {
+ return "It's ok";
+ })
+ ->then(function ($value) {
+ assert($value === "It's ok");
+ });
+
+$promise->reject('Error!');
+```
+
+# Synchronous wait
+
+You can synchronously force promises to complete using a promise's `wait`
+method. When creating a promise, you can provide a wait function that is used
+to synchronously force a promise to complete. When a wait function is invoked
+it is expected to deliver a value to the promise or reject the promise. If the
+wait function does not deliver a value, then an exception is thrown. The wait
+function provided to a promise constructor is invoked when the `wait` function
+of the promise is called.
+
+```php
+$promise = new Promise(function () use (&$promise) {
+ $promise->resolve('foo');
+});
+
+// Calling wait will return the value of the promise.
+echo $promise->wait(); // outputs "foo"
+```
+
+If an exception is encountered while invoking the wait function of a promise,
+the promise is rejected with the exception and the exception is thrown.
+
+```php
+$promise = new Promise(function () use (&$promise) {
+ throw new \Exception('foo');
+});
+
+$promise->wait(); // throws the exception.
+```
+
+Calling `wait` on a promise that has been fulfilled will not trigger the wait
+function. It will simply return the previously resolved value.
+
+```php
+$promise = new Promise(function () { die('this is not called!'); });
+$promise->resolve('foo');
+echo $promise->wait(); // outputs "foo"
+```
+
+Calling `wait` on a promise that has been rejected will throw an exception. If
+the rejection reason is an instance of `\Exception` the reason is thrown.
+Otherwise, a `GuzzleHttp\Promise\RejectionException` is thrown and the reason
+can be obtained by calling the `getReason` method of the exception.
+
+```php
+$promise = new Promise();
+$promise->reject('foo');
+$promise->wait();
+```
+
+> PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo'
+
+
+## Unwrapping a promise
+
+When synchronously waiting on a promise, you are joining the state of the
+promise into the current state of execution (i.e., return the value of the
+promise if it was fulfilled or throw an exception if it was rejected). This is
+called "unwrapping" the promise. Waiting on a promise will by default unwrap
+the promise state.
+
+You can force a promise to resolve and *not* unwrap the state of the promise
+by passing `false` to the first argument of the `wait` function:
+
+```php
+$promise = new Promise();
+$promise->reject('foo');
+// This will not throw an exception. It simply ensures the promise has
+// been resolved.
+$promise->wait(false);
+```
+
+When unwrapping a promise, the resolved value of the promise will be waited
+upon until the unwrapped value is not a promise. This means that if you resolve
+promise A with a promise B and unwrap promise A, the value returned by the
+wait function will be the value delivered to promise B.
+
+**Note**: when you do not unwrap the promise, no value is returned.
+
+
+# Cancellation
+
+You can cancel a promise that has not yet been fulfilled using the `cancel()`
+method of a promise. When creating a promise you can provide an optional
+cancel function that when invoked cancels the action of computing a resolution
+of the promise.
+
+
+# API
+
+
+## Promise
+
+When creating a promise object, you can provide an optional `$waitFn` and
+`$cancelFn`. `$waitFn` is a function that is invoked with no arguments and is
+expected to resolve the promise. `$cancelFn` is a function with no arguments
+that is expected to cancel the computation of a promise. It is invoked when the
+`cancel()` method of a promise is called.
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise(
+ function () use (&$promise) {
+ $promise->resolve('waited');
+ },
+ function () {
+ // do something that will cancel the promise computation (e.g., close
+ // a socket, cancel a database query, etc...)
+ }
+);
+
+assert('waited' === $promise->wait());
+```
+
+A promise has the following methods:
+
+- `then(callable $onFulfilled, callable $onRejected) : PromiseInterface`
+
+ Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler.
+
+- `otherwise(callable $onRejected) : PromiseInterface`
+
+ Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled.
+
+- `wait($unwrap = true) : mixed`
+
+ Synchronously waits on the promise to complete.
+
+ `$unwrap` controls whether or not the value of the promise is returned for a
+ fulfilled promise or if an exception is thrown if the promise is rejected.
+ This is set to `true` by default.
+
+- `cancel()`
+
+ Attempts to cancel the promise if possible. The promise being cancelled and
+ the parent most ancestor that has not yet been resolved will also be
+ cancelled. Any promises waiting on the cancelled promise to resolve will also
+ be cancelled.
+
+- `getState() : string`
+
+ Returns the state of the promise. One of `pending`, `fulfilled`, or
+ `rejected`.
+
+- `resolve($value)`
+
+ Fulfills the promise with the given `$value`.
+
+- `reject($reason)`
+
+ Rejects the promise with the given `$reason`.
+
+
+## FulfilledPromise
+
+A fulfilled promise can be created to represent a promise that has been
+fulfilled.
+
+```php
+use GuzzleHttp\Promise\FulfilledPromise;
+
+$promise = new FulfilledPromise('value');
+
+// Fulfilled callbacks are immediately invoked.
+$promise->then(function ($value) {
+ echo $value;
+});
+```
+
+
+## RejectedPromise
+
+A rejected promise can be created to represent a promise that has been
+rejected.
+
+```php
+use GuzzleHttp\Promise\RejectedPromise;
+
+$promise = new RejectedPromise('Error');
+
+// Rejected callbacks are immediately invoked.
+$promise->then(null, function ($reason) {
+ echo $reason;
+});
+```
+
+
+# Promise interop
+
+This library works with foreign promises that have a `then` method. This means
+you can use Guzzle promises with [React promises](https://github.com/reactphp/promise)
+for example. When a foreign promise is returned inside of a then method
+callback, promise resolution will occur recursively.
+
+```php
+// Create a React promise
+$deferred = new React\Promise\Deferred();
+$reactPromise = $deferred->promise();
+
+// Create a Guzzle promise that is fulfilled with a React promise.
+$guzzlePromise = new \GuzzleHttp\Promise\Promise();
+$guzzlePromise->then(function ($value) use ($reactPromise) {
+ // Do something something with the value...
+ // Return the React promise
+ return $reactPromise;
+});
+```
+
+Please note that wait and cancel chaining is no longer possible when forwarding
+a foreign promise. You will need to wrap a third-party promise with a Guzzle
+promise in order to utilize wait and cancel functions with foreign promises.
+
+
+## Event Loop Integration
+
+In order to keep the stack size constant, Guzzle promises are resolved
+asynchronously using a task queue. When waiting on promises synchronously, the
+task queue will be automatically run to ensure that the blocking promise and
+any forwarded promises are resolved. When using promises asynchronously in an
+event loop, you will need to run the task queue on each tick of the loop. If
+you do not run the task queue, then promises will not be resolved.
+
+You can run the task queue using the `run()` method of the global task queue
+instance.
+
+```php
+// Get the global task queue
+$queue = \GuzzleHttp\Promise\queue();
+$queue->run();
+```
+
+For example, you could use Guzzle promises with React using a periodic timer:
+
+```php
+$loop = React\EventLoop\Factory::create();
+$loop->addPeriodicTimer(0, [$queue, 'run']);
+```
+
+*TODO*: Perhaps adding a `futureTick()` on each tick would be faster?
+
+
+# Implementation notes
+
+
+## Promise resolution and chaining is handled iteratively
+
+By shuffling pending handlers from one owner to another, promises are
+resolved iteratively, allowing for "infinite" then chaining.
+
+```php
+then(function ($v) {
+ // The stack size remains constant (a good thing)
+ echo xdebug_get_stack_depth() . ', ';
+ return $v + 1;
+ });
+}
+
+$parent->resolve(0);
+var_dump($p->wait()); // int(1000)
+
+```
+
+When a promise is fulfilled or rejected with a non-promise value, the promise
+then takes ownership of the handlers of each child promise and delivers values
+down the chain without using recursion.
+
+When a promise is resolved with another promise, the original promise transfers
+all of its pending handlers to the new promise. When the new promise is
+eventually resolved, all of the pending handlers are delivered the forwarded
+value.
+
+
+## A promise is the deferred.
+
+Some promise libraries implement promises using a deferred object to represent
+a computation and a promise object to represent the delivery of the result of
+the computation. This is a nice separation of computation and delivery because
+consumers of the promise cannot modify the value that will be eventually
+delivered.
+
+One side effect of being able to implement promise resolution and chaining
+iteratively is that you need to be able for one promise to reach into the state
+of another promise to shuffle around ownership of handlers. In order to achieve
+this without making the handlers of a promise publicly mutable, a promise is
+also the deferred value, allowing promises of the same parent class to reach
+into and modify the private properties of promises of the same type. While this
+does allow consumers of the value to modify the resolution or rejection of the
+deferred, it is a small price to pay for keeping the stack size constant.
+
+```php
+$promise = new Promise();
+$promise->then(function ($value) { echo $value; });
+// The promise is the deferred value, so you can deliver a value to it.
+$promise->resolve('foo');
+// prints "foo"
+```
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/promises/composer.json b/plugins/panakour/backup/vendor/guzzlehttp/promises/composer.json
new file mode 100644
index 000000000..ec41a61e6
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/promises/composer.json
@@ -0,0 +1,34 @@
+{
+ "name": "guzzlehttp/promises",
+ "description": "Guzzle promises library",
+ "keywords": ["promise"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Promise\\": "src/"
+ },
+ "files": ["src/functions_include.php"]
+ },
+ "scripts": {
+ "test": "vendor/bin/phpunit",
+ "test-ci": "vendor/bin/phpunit --coverage-text"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/promises/src/AggregateException.php b/plugins/panakour/backup/vendor/guzzlehttp/promises/src/AggregateException.php
new file mode 100644
index 000000000..6a5690c37
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/promises/src/AggregateException.php
@@ -0,0 +1,16 @@
+then(function ($v) { echo $v; });
+ *
+ * @param callable $generatorFn Generator function to wrap into a promise.
+ *
+ * @return Promise
+ * @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration
+ */
+final class Coroutine implements PromiseInterface
+{
+ /**
+ * @var PromiseInterface|null
+ */
+ private $currentPromise;
+
+ /**
+ * @var Generator
+ */
+ private $generator;
+
+ /**
+ * @var Promise
+ */
+ private $result;
+
+ public function __construct(callable $generatorFn)
+ {
+ $this->generator = $generatorFn();
+ $this->result = new Promise(function () {
+ while (isset($this->currentPromise)) {
+ $this->currentPromise->wait();
+ }
+ });
+ $this->nextCoroutine($this->generator->current());
+ }
+
+ public function then(
+ callable $onFulfilled = null,
+ callable $onRejected = null
+ ) {
+ return $this->result->then($onFulfilled, $onRejected);
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->result->otherwise($onRejected);
+ }
+
+ public function wait($unwrap = true)
+ {
+ return $this->result->wait($unwrap);
+ }
+
+ public function getState()
+ {
+ return $this->result->getState();
+ }
+
+ public function resolve($value)
+ {
+ $this->result->resolve($value);
+ }
+
+ public function reject($reason)
+ {
+ $this->result->reject($reason);
+ }
+
+ public function cancel()
+ {
+ $this->currentPromise->cancel();
+ $this->result->cancel();
+ }
+
+ private function nextCoroutine($yielded)
+ {
+ $this->currentPromise = promise_for($yielded)
+ ->then([$this, '_handleSuccess'], [$this, '_handleFailure']);
+ }
+
+ /**
+ * @internal
+ */
+ public function _handleSuccess($value)
+ {
+ unset($this->currentPromise);
+ try {
+ $next = $this->generator->send($value);
+ if ($this->generator->valid()) {
+ $this->nextCoroutine($next);
+ } else {
+ $this->result->resolve($value);
+ }
+ } catch (Exception $exception) {
+ $this->result->reject($exception);
+ } catch (Throwable $throwable) {
+ $this->result->reject($throwable);
+ }
+ }
+
+ /**
+ * @internal
+ */
+ public function _handleFailure($reason)
+ {
+ unset($this->currentPromise);
+ try {
+ $nextYield = $this->generator->throw(exception_for($reason));
+ // The throw was caught, so keep iterating on the coroutine
+ $this->nextCoroutine($nextYield);
+ } catch (Exception $exception) {
+ $this->result->reject($exception);
+ } catch (Throwable $throwable) {
+ $this->result->reject($throwable);
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/promises/src/EachPromise.php b/plugins/panakour/backup/vendor/guzzlehttp/promises/src/EachPromise.php
new file mode 100644
index 000000000..d0ddf603f
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/promises/src/EachPromise.php
@@ -0,0 +1,229 @@
+iterable = iter_for($iterable);
+
+ if (isset($config['concurrency'])) {
+ $this->concurrency = $config['concurrency'];
+ }
+
+ if (isset($config['fulfilled'])) {
+ $this->onFulfilled = $config['fulfilled'];
+ }
+
+ if (isset($config['rejected'])) {
+ $this->onRejected = $config['rejected'];
+ }
+ }
+
+ public function promise()
+ {
+ if ($this->aggregate) {
+ return $this->aggregate;
+ }
+
+ try {
+ $this->createPromise();
+ $this->iterable->rewind();
+ $this->refillPending();
+ } catch (\Throwable $e) {
+ $this->aggregate->reject($e);
+ } catch (\Exception $e) {
+ $this->aggregate->reject($e);
+ }
+
+ return $this->aggregate;
+ }
+
+ private function createPromise()
+ {
+ $this->mutex = false;
+ $this->aggregate = new Promise(function () {
+ reset($this->pending);
+ if (empty($this->pending) && !$this->iterable->valid()) {
+ $this->aggregate->resolve(null);
+ return;
+ }
+
+ // Consume a potentially fluctuating list of promises while
+ // ensuring that indexes are maintained (precluding array_shift).
+ while ($promise = current($this->pending)) {
+ next($this->pending);
+ $promise->wait();
+ if ($this->aggregate->getState() !== PromiseInterface::PENDING) {
+ return;
+ }
+ }
+ });
+
+ // Clear the references when the promise is resolved.
+ $clearFn = function () {
+ $this->iterable = $this->concurrency = $this->pending = null;
+ $this->onFulfilled = $this->onRejected = null;
+ };
+
+ $this->aggregate->then($clearFn, $clearFn);
+ }
+
+ private function refillPending()
+ {
+ if (!$this->concurrency) {
+ // Add all pending promises.
+ while ($this->addPending() && $this->advanceIterator());
+ return;
+ }
+
+ // Add only up to N pending promises.
+ $concurrency = is_callable($this->concurrency)
+ ? call_user_func($this->concurrency, count($this->pending))
+ : $this->concurrency;
+ $concurrency = max($concurrency - count($this->pending), 0);
+ // Concurrency may be set to 0 to disallow new promises.
+ if (!$concurrency) {
+ return;
+ }
+ // Add the first pending promise.
+ $this->addPending();
+ // Note this is special handling for concurrency=1 so that we do
+ // not advance the iterator after adding the first promise. This
+ // helps work around issues with generators that might not have the
+ // next value to yield until promise callbacks are called.
+ while (--$concurrency
+ && $this->advanceIterator()
+ && $this->addPending());
+ }
+
+ private function addPending()
+ {
+ if (!$this->iterable || !$this->iterable->valid()) {
+ return false;
+ }
+
+ $promise = promise_for($this->iterable->current());
+ $idx = $this->iterable->key();
+
+ $this->pending[$idx] = $promise->then(
+ function ($value) use ($idx) {
+ if ($this->onFulfilled) {
+ call_user_func(
+ $this->onFulfilled, $value, $idx, $this->aggregate
+ );
+ }
+ $this->step($idx);
+ },
+ function ($reason) use ($idx) {
+ if ($this->onRejected) {
+ call_user_func(
+ $this->onRejected, $reason, $idx, $this->aggregate
+ );
+ }
+ $this->step($idx);
+ }
+ );
+
+ return true;
+ }
+
+ private function advanceIterator()
+ {
+ // Place a lock on the iterator so that we ensure to not recurse,
+ // preventing fatal generator errors.
+ if ($this->mutex) {
+ return false;
+ }
+
+ $this->mutex = true;
+
+ try {
+ $this->iterable->next();
+ $this->mutex = false;
+ return true;
+ } catch (\Throwable $e) {
+ $this->aggregate->reject($e);
+ $this->mutex = false;
+ return false;
+ } catch (\Exception $e) {
+ $this->aggregate->reject($e);
+ $this->mutex = false;
+ return false;
+ }
+ }
+
+ private function step($idx)
+ {
+ // If the promise was already resolved, then ignore this step.
+ if ($this->aggregate->getState() !== PromiseInterface::PENDING) {
+ return;
+ }
+
+ unset($this->pending[$idx]);
+
+ // Only refill pending promises if we are not locked, preventing the
+ // EachPromise to recursively invoke the provided iterator, which
+ // cause a fatal error: "Cannot resume an already running generator"
+ if ($this->advanceIterator() && !$this->checkIfFinished()) {
+ // Add more pending promises if possible.
+ $this->refillPending();
+ }
+ }
+
+ private function checkIfFinished()
+ {
+ if (!$this->pending && !$this->iterable->valid()) {
+ // Resolve the promise if there's nothing left to do.
+ $this->aggregate->resolve(null);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/promises/src/FulfilledPromise.php b/plugins/panakour/backup/vendor/guzzlehttp/promises/src/FulfilledPromise.php
new file mode 100644
index 000000000..dbbeeb9f7
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/promises/src/FulfilledPromise.php
@@ -0,0 +1,82 @@
+value = $value;
+ }
+
+ public function then(
+ callable $onFulfilled = null,
+ callable $onRejected = null
+ ) {
+ // Return itself if there is no onFulfilled function.
+ if (!$onFulfilled) {
+ return $this;
+ }
+
+ $queue = queue();
+ $p = new Promise([$queue, 'run']);
+ $value = $this->value;
+ $queue->add(static function () use ($p, $value, $onFulfilled) {
+ if ($p->getState() === self::PENDING) {
+ try {
+ $p->resolve($onFulfilled($value));
+ } catch (\Throwable $e) {
+ $p->reject($e);
+ } catch (\Exception $e) {
+ $p->reject($e);
+ }
+ }
+ });
+
+ return $p;
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->then(null, $onRejected);
+ }
+
+ public function wait($unwrap = true, $defaultDelivery = null)
+ {
+ return $unwrap ? $this->value : null;
+ }
+
+ public function getState()
+ {
+ return self::FULFILLED;
+ }
+
+ public function resolve($value)
+ {
+ if ($value !== $this->value) {
+ throw new \LogicException("Cannot resolve a fulfilled promise");
+ }
+ }
+
+ public function reject($reason)
+ {
+ throw new \LogicException("Cannot reject a fulfilled promise");
+ }
+
+ public function cancel()
+ {
+ // pass
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/promises/src/Promise.php b/plugins/panakour/backup/vendor/guzzlehttp/promises/src/Promise.php
new file mode 100644
index 000000000..844ada073
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/promises/src/Promise.php
@@ -0,0 +1,280 @@
+waitFn = $waitFn;
+ $this->cancelFn = $cancelFn;
+ }
+
+ public function then(
+ callable $onFulfilled = null,
+ callable $onRejected = null
+ ) {
+ if ($this->state === self::PENDING) {
+ $p = new Promise(null, [$this, 'cancel']);
+ $this->handlers[] = [$p, $onFulfilled, $onRejected];
+ $p->waitList = $this->waitList;
+ $p->waitList[] = $this;
+ return $p;
+ }
+
+ // Return a fulfilled promise and immediately invoke any callbacks.
+ if ($this->state === self::FULFILLED) {
+ return $onFulfilled
+ ? promise_for($this->result)->then($onFulfilled)
+ : promise_for($this->result);
+ }
+
+ // It's either cancelled or rejected, so return a rejected promise
+ // and immediately invoke any callbacks.
+ $rejection = rejection_for($this->result);
+ return $onRejected ? $rejection->then(null, $onRejected) : $rejection;
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->then(null, $onRejected);
+ }
+
+ public function wait($unwrap = true)
+ {
+ $this->waitIfPending();
+
+ $inner = $this->result instanceof PromiseInterface
+ ? $this->result->wait($unwrap)
+ : $this->result;
+
+ if ($unwrap) {
+ if ($this->result instanceof PromiseInterface
+ || $this->state === self::FULFILLED
+ ) {
+ return $inner;
+ } else {
+ // It's rejected so "unwrap" and throw an exception.
+ throw exception_for($inner);
+ }
+ }
+ }
+
+ public function getState()
+ {
+ return $this->state;
+ }
+
+ public function cancel()
+ {
+ if ($this->state !== self::PENDING) {
+ return;
+ }
+
+ $this->waitFn = $this->waitList = null;
+
+ if ($this->cancelFn) {
+ $fn = $this->cancelFn;
+ $this->cancelFn = null;
+ try {
+ $fn();
+ } catch (\Throwable $e) {
+ $this->reject($e);
+ } catch (\Exception $e) {
+ $this->reject($e);
+ }
+ }
+
+ // Reject the promise only if it wasn't rejected in a then callback.
+ if ($this->state === self::PENDING) {
+ $this->reject(new CancellationException('Promise has been cancelled'));
+ }
+ }
+
+ public function resolve($value)
+ {
+ $this->settle(self::FULFILLED, $value);
+ }
+
+ public function reject($reason)
+ {
+ $this->settle(self::REJECTED, $reason);
+ }
+
+ private function settle($state, $value)
+ {
+ if ($this->state !== self::PENDING) {
+ // Ignore calls with the same resolution.
+ if ($state === $this->state && $value === $this->result) {
+ return;
+ }
+ throw $this->state === $state
+ ? new \LogicException("The promise is already {$state}.")
+ : new \LogicException("Cannot change a {$this->state} promise to {$state}");
+ }
+
+ if ($value === $this) {
+ throw new \LogicException('Cannot fulfill or reject a promise with itself');
+ }
+
+ // Clear out the state of the promise but stash the handlers.
+ $this->state = $state;
+ $this->result = $value;
+ $handlers = $this->handlers;
+ $this->handlers = null;
+ $this->waitList = $this->waitFn = null;
+ $this->cancelFn = null;
+
+ if (!$handlers) {
+ return;
+ }
+
+ // If the value was not a settled promise or a thenable, then resolve
+ // it in the task queue using the correct ID.
+ if (!method_exists($value, 'then')) {
+ $id = $state === self::FULFILLED ? 1 : 2;
+ // It's a success, so resolve the handlers in the queue.
+ queue()->add(static function () use ($id, $value, $handlers) {
+ foreach ($handlers as $handler) {
+ self::callHandler($id, $value, $handler);
+ }
+ });
+ } elseif ($value instanceof Promise
+ && $value->getState() === self::PENDING
+ ) {
+ // We can just merge our handlers onto the next promise.
+ $value->handlers = array_merge($value->handlers, $handlers);
+ } else {
+ // Resolve the handlers when the forwarded promise is resolved.
+ $value->then(
+ static function ($value) use ($handlers) {
+ foreach ($handlers as $handler) {
+ self::callHandler(1, $value, $handler);
+ }
+ },
+ static function ($reason) use ($handlers) {
+ foreach ($handlers as $handler) {
+ self::callHandler(2, $reason, $handler);
+ }
+ }
+ );
+ }
+ }
+
+ /**
+ * Call a stack of handlers using a specific callback index and value.
+ *
+ * @param int $index 1 (resolve) or 2 (reject).
+ * @param mixed $value Value to pass to the callback.
+ * @param array $handler Array of handler data (promise and callbacks).
+ *
+ * @return array Returns the next group to resolve.
+ */
+ private static function callHandler($index, $value, array $handler)
+ {
+ /** @var PromiseInterface $promise */
+ $promise = $handler[0];
+
+ // The promise may have been cancelled or resolved before placing
+ // this thunk in the queue.
+ if ($promise->getState() !== self::PENDING) {
+ return;
+ }
+
+ try {
+ if (isset($handler[$index])) {
+ $promise->resolve($handler[$index]($value));
+ } elseif ($index === 1) {
+ // Forward resolution values as-is.
+ $promise->resolve($value);
+ } else {
+ // Forward rejections down the chain.
+ $promise->reject($value);
+ }
+ } catch (\Throwable $reason) {
+ $promise->reject($reason);
+ } catch (\Exception $reason) {
+ $promise->reject($reason);
+ }
+ }
+
+ private function waitIfPending()
+ {
+ if ($this->state !== self::PENDING) {
+ return;
+ } elseif ($this->waitFn) {
+ $this->invokeWaitFn();
+ } elseif ($this->waitList) {
+ $this->invokeWaitList();
+ } else {
+ // If there's not wait function, then reject the promise.
+ $this->reject('Cannot wait on a promise that has '
+ . 'no internal wait function. You must provide a wait '
+ . 'function when constructing the promise to be able to '
+ . 'wait on a promise.');
+ }
+
+ queue()->run();
+
+ if ($this->state === self::PENDING) {
+ $this->reject('Invoking the wait callback did not resolve the promise');
+ }
+ }
+
+ private function invokeWaitFn()
+ {
+ try {
+ $wfn = $this->waitFn;
+ $this->waitFn = null;
+ $wfn(true);
+ } catch (\Exception $reason) {
+ if ($this->state === self::PENDING) {
+ // The promise has not been resolved yet, so reject the promise
+ // with the exception.
+ $this->reject($reason);
+ } else {
+ // The promise was already resolved, so there's a problem in
+ // the application.
+ throw $reason;
+ }
+ }
+ }
+
+ private function invokeWaitList()
+ {
+ $waitList = $this->waitList;
+ $this->waitList = null;
+
+ foreach ($waitList as $result) {
+ while (true) {
+ $result->waitIfPending();
+
+ if ($result->result instanceof Promise) {
+ $result = $result->result;
+ } else {
+ if ($result->result instanceof PromiseInterface) {
+ $result->result->wait(false);
+ }
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/promises/src/PromiseInterface.php b/plugins/panakour/backup/vendor/guzzlehttp/promises/src/PromiseInterface.php
new file mode 100644
index 000000000..8f5f4b99b
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/promises/src/PromiseInterface.php
@@ -0,0 +1,93 @@
+reason = $reason;
+ }
+
+ public function then(
+ callable $onFulfilled = null,
+ callable $onRejected = null
+ ) {
+ // If there's no onRejected callback then just return self.
+ if (!$onRejected) {
+ return $this;
+ }
+
+ $queue = queue();
+ $reason = $this->reason;
+ $p = new Promise([$queue, 'run']);
+ $queue->add(static function () use ($p, $reason, $onRejected) {
+ if ($p->getState() === self::PENDING) {
+ try {
+ // Return a resolved promise if onRejected does not throw.
+ $p->resolve($onRejected($reason));
+ } catch (\Throwable $e) {
+ // onRejected threw, so return a rejected promise.
+ $p->reject($e);
+ } catch (\Exception $e) {
+ // onRejected threw, so return a rejected promise.
+ $p->reject($e);
+ }
+ }
+ });
+
+ return $p;
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->then(null, $onRejected);
+ }
+
+ public function wait($unwrap = true, $defaultDelivery = null)
+ {
+ if ($unwrap) {
+ throw exception_for($this->reason);
+ }
+ }
+
+ public function getState()
+ {
+ return self::REJECTED;
+ }
+
+ public function resolve($value)
+ {
+ throw new \LogicException("Cannot resolve a rejected promise");
+ }
+
+ public function reject($reason)
+ {
+ if ($reason !== $this->reason) {
+ throw new \LogicException("Cannot reject a rejected promise");
+ }
+ }
+
+ public function cancel()
+ {
+ // pass
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/promises/src/RejectionException.php b/plugins/panakour/backup/vendor/guzzlehttp/promises/src/RejectionException.php
new file mode 100644
index 000000000..07c1136da
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/promises/src/RejectionException.php
@@ -0,0 +1,47 @@
+reason = $reason;
+
+ $message = 'The promise was rejected';
+
+ if ($description) {
+ $message .= ' with reason: ' . $description;
+ } elseif (is_string($reason)
+ || (is_object($reason) && method_exists($reason, '__toString'))
+ ) {
+ $message .= ' with reason: ' . $this->reason;
+ } elseif ($reason instanceof \JsonSerializable) {
+ $message .= ' with reason: '
+ . json_encode($this->reason, JSON_PRETTY_PRINT);
+ }
+
+ parent::__construct($message);
+ }
+
+ /**
+ * Returns the rejection reason.
+ *
+ * @return mixed
+ */
+ public function getReason()
+ {
+ return $this->reason;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/promises/src/TaskQueue.php b/plugins/panakour/backup/vendor/guzzlehttp/promises/src/TaskQueue.php
new file mode 100644
index 000000000..6e8a2a083
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/promises/src/TaskQueue.php
@@ -0,0 +1,66 @@
+run();
+ */
+class TaskQueue implements TaskQueueInterface
+{
+ private $enableShutdown = true;
+ private $queue = [];
+
+ public function __construct($withShutdown = true)
+ {
+ if ($withShutdown) {
+ register_shutdown_function(function () {
+ if ($this->enableShutdown) {
+ // Only run the tasks if an E_ERROR didn't occur.
+ $err = error_get_last();
+ if (!$err || ($err['type'] ^ E_ERROR)) {
+ $this->run();
+ }
+ }
+ });
+ }
+ }
+
+ public function isEmpty()
+ {
+ return !$this->queue;
+ }
+
+ public function add(callable $task)
+ {
+ $this->queue[] = $task;
+ }
+
+ public function run()
+ {
+ /** @var callable $task */
+ while ($task = array_shift($this->queue)) {
+ $task();
+ }
+ }
+
+ /**
+ * The task queue will be run and exhausted by default when the process
+ * exits IFF the exit is not the result of a PHP E_ERROR error.
+ *
+ * You can disable running the automatic shutdown of the queue by calling
+ * this function. If you disable the task queue shutdown process, then you
+ * MUST either run the task queue (as a result of running your event loop
+ * or manually using the run() method) or wait on each outstanding promise.
+ *
+ * Note: This shutdown will occur before any destructors are triggered.
+ */
+ public function disableShutdown()
+ {
+ $this->enableShutdown = false;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/promises/src/TaskQueueInterface.php b/plugins/panakour/backup/vendor/guzzlehttp/promises/src/TaskQueueInterface.php
new file mode 100644
index 000000000..ac8306e19
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/promises/src/TaskQueueInterface.php
@@ -0,0 +1,25 @@
+
+ * while ($eventLoop->isRunning()) {
+ * GuzzleHttp\Promise\queue()->run();
+ * }
+ *
+ *
+ * @param TaskQueueInterface $assign Optionally specify a new queue instance.
+ *
+ * @return TaskQueueInterface
+ */
+function queue(TaskQueueInterface $assign = null)
+{
+ static $queue;
+
+ if ($assign) {
+ $queue = $assign;
+ } elseif (!$queue) {
+ $queue = new TaskQueue();
+ }
+
+ return $queue;
+}
+
+/**
+ * Adds a function to run in the task queue when it is next `run()` and returns
+ * a promise that is fulfilled or rejected with the result.
+ *
+ * @param callable $task Task function to run.
+ *
+ * @return PromiseInterface
+ */
+function task(callable $task)
+{
+ $queue = queue();
+ $promise = new Promise([$queue, 'run']);
+ $queue->add(function () use ($task, $promise) {
+ try {
+ $promise->resolve($task());
+ } catch (\Throwable $e) {
+ $promise->reject($e);
+ } catch (\Exception $e) {
+ $promise->reject($e);
+ }
+ });
+
+ return $promise;
+}
+
+/**
+ * Creates a promise for a value if the value is not a promise.
+ *
+ * @param mixed $value Promise or value.
+ *
+ * @return PromiseInterface
+ */
+function promise_for($value)
+{
+ if ($value instanceof PromiseInterface) {
+ return $value;
+ }
+
+ // Return a Guzzle promise that shadows the given promise.
+ if (method_exists($value, 'then')) {
+ $wfn = method_exists($value, 'wait') ? [$value, 'wait'] : null;
+ $cfn = method_exists($value, 'cancel') ? [$value, 'cancel'] : null;
+ $promise = new Promise($wfn, $cfn);
+ $value->then([$promise, 'resolve'], [$promise, 'reject']);
+ return $promise;
+ }
+
+ return new FulfilledPromise($value);
+}
+
+/**
+ * Creates a rejected promise for a reason if the reason is not a promise. If
+ * the provided reason is a promise, then it is returned as-is.
+ *
+ * @param mixed $reason Promise or reason.
+ *
+ * @return PromiseInterface
+ */
+function rejection_for($reason)
+{
+ if ($reason instanceof PromiseInterface) {
+ return $reason;
+ }
+
+ return new RejectedPromise($reason);
+}
+
+/**
+ * Create an exception for a rejected promise value.
+ *
+ * @param mixed $reason
+ *
+ * @return \Exception|\Throwable
+ */
+function exception_for($reason)
+{
+ return $reason instanceof \Exception || $reason instanceof \Throwable
+ ? $reason
+ : new RejectionException($reason);
+}
+
+/**
+ * Returns an iterator for the given value.
+ *
+ * @param mixed $value
+ *
+ * @return \Iterator
+ */
+function iter_for($value)
+{
+ if ($value instanceof \Iterator) {
+ return $value;
+ } elseif (is_array($value)) {
+ return new \ArrayIterator($value);
+ } else {
+ return new \ArrayIterator([$value]);
+ }
+}
+
+/**
+ * Synchronously waits on a promise to resolve and returns an inspection state
+ * array.
+ *
+ * Returns a state associative array containing a "state" key mapping to a
+ * valid promise state. If the state of the promise is "fulfilled", the array
+ * will contain a "value" key mapping to the fulfilled value of the promise. If
+ * the promise is rejected, the array will contain a "reason" key mapping to
+ * the rejection reason of the promise.
+ *
+ * @param PromiseInterface $promise Promise or value.
+ *
+ * @return array
+ */
+function inspect(PromiseInterface $promise)
+{
+ try {
+ return [
+ 'state' => PromiseInterface::FULFILLED,
+ 'value' => $promise->wait()
+ ];
+ } catch (RejectionException $e) {
+ return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()];
+ } catch (\Throwable $e) {
+ return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
+ } catch (\Exception $e) {
+ return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
+ }
+}
+
+/**
+ * Waits on all of the provided promises, but does not unwrap rejected promises
+ * as thrown exception.
+ *
+ * Returns an array of inspection state arrays.
+ *
+ * @param PromiseInterface[] $promises Traversable of promises to wait upon.
+ *
+ * @return array
+ * @see GuzzleHttp\Promise\inspect for the inspection state array format.
+ */
+function inspect_all($promises)
+{
+ $results = [];
+ foreach ($promises as $key => $promise) {
+ $results[$key] = inspect($promise);
+ }
+
+ return $results;
+}
+
+/**
+ * Waits on all of the provided promises and returns the fulfilled values.
+ *
+ * Returns an array that contains the value of each promise (in the same order
+ * the promises were provided). An exception is thrown if any of the promises
+ * are rejected.
+ *
+ * @param mixed $promises Iterable of PromiseInterface objects to wait on.
+ *
+ * @return array
+ * @throws \Exception on error
+ * @throws \Throwable on error in PHP >=7
+ */
+function unwrap($promises)
+{
+ $results = [];
+ foreach ($promises as $key => $promise) {
+ $results[$key] = $promise->wait();
+ }
+
+ return $results;
+}
+
+/**
+ * Given an array of promises, return a promise that is fulfilled when all the
+ * items in the array are fulfilled.
+ *
+ * The promise's fulfillment value is an array with fulfillment values at
+ * respective positions to the original array. If any promise in the array
+ * rejects, the returned promise is rejected with the rejection reason.
+ *
+ * @param mixed $promises Promises or values.
+ *
+ * @return PromiseInterface
+ */
+function all($promises)
+{
+ $results = [];
+ return each(
+ $promises,
+ function ($value, $idx) use (&$results) {
+ $results[$idx] = $value;
+ },
+ function ($reason, $idx, Promise $aggregate) {
+ $aggregate->reject($reason);
+ }
+ )->then(function () use (&$results) {
+ ksort($results);
+ return $results;
+ });
+}
+
+/**
+ * Initiate a competitive race between multiple promises or values (values will
+ * become immediately fulfilled promises).
+ *
+ * When count amount of promises have been fulfilled, the returned promise is
+ * fulfilled with an array that contains the fulfillment values of the winners
+ * in order of resolution.
+ *
+ * This prommise is rejected with a {@see GuzzleHttp\Promise\AggregateException}
+ * if the number of fulfilled promises is less than the desired $count.
+ *
+ * @param int $count Total number of promises.
+ * @param mixed $promises Promises or values.
+ *
+ * @return PromiseInterface
+ */
+function some($count, $promises)
+{
+ $results = [];
+ $rejections = [];
+
+ return each(
+ $promises,
+ function ($value, $idx, PromiseInterface $p) use (&$results, $count) {
+ if ($p->getState() !== PromiseInterface::PENDING) {
+ return;
+ }
+ $results[$idx] = $value;
+ if (count($results) >= $count) {
+ $p->resolve(null);
+ }
+ },
+ function ($reason) use (&$rejections) {
+ $rejections[] = $reason;
+ }
+ )->then(
+ function () use (&$results, &$rejections, $count) {
+ if (count($results) !== $count) {
+ throw new AggregateException(
+ 'Not enough promises to fulfill count',
+ $rejections
+ );
+ }
+ ksort($results);
+ return array_values($results);
+ }
+ );
+}
+
+/**
+ * Like some(), with 1 as count. However, if the promise fulfills, the
+ * fulfillment value is not an array of 1 but the value directly.
+ *
+ * @param mixed $promises Promises or values.
+ *
+ * @return PromiseInterface
+ */
+function any($promises)
+{
+ return some(1, $promises)->then(function ($values) { return $values[0]; });
+}
+
+/**
+ * Returns a promise that is fulfilled when all of the provided promises have
+ * been fulfilled or rejected.
+ *
+ * The returned promise is fulfilled with an array of inspection state arrays.
+ *
+ * @param mixed $promises Promises or values.
+ *
+ * @return PromiseInterface
+ * @see GuzzleHttp\Promise\inspect for the inspection state array format.
+ */
+function settle($promises)
+{
+ $results = [];
+
+ return each(
+ $promises,
+ function ($value, $idx) use (&$results) {
+ $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value];
+ },
+ function ($reason, $idx) use (&$results) {
+ $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason];
+ }
+ )->then(function () use (&$results) {
+ ksort($results);
+ return $results;
+ });
+}
+
+/**
+ * Given an iterator that yields promises or values, returns a promise that is
+ * fulfilled with a null value when the iterator has been consumed or the
+ * aggregate promise has been fulfilled or rejected.
+ *
+ * $onFulfilled is a function that accepts the fulfilled value, iterator
+ * index, and the aggregate promise. The callback can invoke any necessary side
+ * effects and choose to resolve or reject the aggregate promise if needed.
+ *
+ * $onRejected is a function that accepts the rejection reason, iterator
+ * index, and the aggregate promise. The callback can invoke any necessary side
+ * effects and choose to resolve or reject the aggregate promise if needed.
+ *
+ * @param mixed $iterable Iterator or array to iterate over.
+ * @param callable $onFulfilled
+ * @param callable $onRejected
+ *
+ * @return PromiseInterface
+ */
+function each(
+ $iterable,
+ callable $onFulfilled = null,
+ callable $onRejected = null
+) {
+ return (new EachPromise($iterable, [
+ 'fulfilled' => $onFulfilled,
+ 'rejected' => $onRejected
+ ]))->promise();
+}
+
+/**
+ * Like each, but only allows a certain number of outstanding promises at any
+ * given time.
+ *
+ * $concurrency may be an integer or a function that accepts the number of
+ * pending promises and returns a numeric concurrency limit value to allow for
+ * dynamic a concurrency size.
+ *
+ * @param mixed $iterable
+ * @param int|callable $concurrency
+ * @param callable $onFulfilled
+ * @param callable $onRejected
+ *
+ * @return PromiseInterface
+ */
+function each_limit(
+ $iterable,
+ $concurrency,
+ callable $onFulfilled = null,
+ callable $onRejected = null
+) {
+ return (new EachPromise($iterable, [
+ 'fulfilled' => $onFulfilled,
+ 'rejected' => $onRejected,
+ 'concurrency' => $concurrency
+ ]))->promise();
+}
+
+/**
+ * Like each_limit, but ensures that no promise in the given $iterable argument
+ * is rejected. If any promise is rejected, then the aggregate promise is
+ * rejected with the encountered rejection.
+ *
+ * @param mixed $iterable
+ * @param int|callable $concurrency
+ * @param callable $onFulfilled
+ *
+ * @return PromiseInterface
+ */
+function each_limit_all(
+ $iterable,
+ $concurrency,
+ callable $onFulfilled = null
+) {
+ return each_limit(
+ $iterable,
+ $concurrency,
+ $onFulfilled,
+ function ($reason, $idx, PromiseInterface $aggregate) {
+ $aggregate->reject($reason);
+ }
+ );
+}
+
+/**
+ * Returns true if a promise is fulfilled.
+ *
+ * @param PromiseInterface $promise
+ *
+ * @return bool
+ */
+function is_fulfilled(PromiseInterface $promise)
+{
+ return $promise->getState() === PromiseInterface::FULFILLED;
+}
+
+/**
+ * Returns true if a promise is rejected.
+ *
+ * @param PromiseInterface $promise
+ *
+ * @return bool
+ */
+function is_rejected(PromiseInterface $promise)
+{
+ return $promise->getState() === PromiseInterface::REJECTED;
+}
+
+/**
+ * Returns true if a promise is fulfilled or rejected.
+ *
+ * @param PromiseInterface $promise
+ *
+ * @return bool
+ */
+function is_settled(PromiseInterface $promise)
+{
+ return $promise->getState() !== PromiseInterface::PENDING;
+}
+
+/**
+ * @see Coroutine
+ *
+ * @param callable $generatorFn
+ *
+ * @return PromiseInterface
+ */
+function coroutine(callable $generatorFn)
+{
+ return new Coroutine($generatorFn);
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/promises/src/functions_include.php b/plugins/panakour/backup/vendor/guzzlehttp/promises/src/functions_include.php
new file mode 100644
index 000000000..34cd1710a
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/promises/src/functions_include.php
@@ -0,0 +1,6 @@
+withPath('foo')->withHost('example.com')` will throw an exception
+ because the path of a URI with an authority must start with a slash "/" or be empty
+ - `(new Uri())->withScheme('http')` will return `'http://localhost'`
+
+### Deprecated
+
+- `Uri::resolve` in favor of `UriResolver::resolve`
+- `Uri::removeDotSegments` in favor of `UriResolver::removeDotSegments`
+
+### Fixed
+
+- `Stream::read` when length parameter <= 0.
+- `copy_to_stream` reads bytes in chunks instead of `maxLen` into memory.
+- `ServerRequest::getUriFromGlobals` when `Host` header contains port.
+- Compatibility of URIs with `file` scheme and empty host.
+
+
+## [1.3.1] - 2016-06-25
+
+### Fixed
+
+- `Uri::__toString` for network path references, e.g. `//example.org`.
+- Missing lowercase normalization for host.
+- Handling of URI components in case they are `'0'` in a lot of places,
+ e.g. as a user info password.
+- `Uri::withAddedHeader` to correctly merge headers with different case.
+- Trimming of header values in `Uri::withAddedHeader`. Header values may
+ be surrounded by whitespace which should be ignored according to RFC 7230
+ Section 3.2.4. This does not apply to header names.
+- `Uri::withAddedHeader` with an array of header values.
+- `Uri::resolve` when base path has no slash and handling of fragment.
+- Handling of encoding in `Uri::with(out)QueryValue` so one can pass the
+ key/value both in encoded as well as decoded form to those methods. This is
+ consistent with withPath, withQuery etc.
+- `ServerRequest::withoutAttribute` when attribute value is null.
+
+
+## [1.3.0] - 2016-04-13
+
+### Added
+
+- Remaining interfaces needed for full PSR7 compatibility
+ (ServerRequestInterface, UploadedFileInterface, etc.).
+- Support for stream_for from scalars.
+
+### Changed
+
+- Can now extend Uri.
+
+### Fixed
+- A bug in validating request methods by making it more permissive.
+
+
+## [1.2.3] - 2016-02-18
+
+### Fixed
+
+- Support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote
+ streams, which can sometimes return fewer bytes than requested with `fread`.
+- Handling of gzipped responses with FNAME headers.
+
+
+## [1.2.2] - 2016-01-22
+
+### Added
+
+- Support for URIs without any authority.
+- Support for HTTP 451 'Unavailable For Legal Reasons.'
+- Support for using '0' as a filename.
+- Support for including non-standard ports in Host headers.
+
+
+## [1.2.1] - 2015-11-02
+
+### Changes
+
+- Now supporting negative offsets when seeking to SEEK_END.
+
+
+## [1.2.0] - 2015-08-15
+
+### Changed
+
+- Body as `"0"` is now properly added to a response.
+- Now allowing forward seeking in CachingStream.
+- Now properly parsing HTTP requests that contain proxy targets in
+ `parse_request`.
+- functions.php is now conditionally required.
+- user-info is no longer dropped when resolving URIs.
+
+
+## [1.1.0] - 2015-06-24
+
+### Changed
+
+- URIs can now be relative.
+- `multipart/form-data` headers are now overridden case-insensitively.
+- URI paths no longer encode the following characters because they are allowed
+ in URIs: "(", ")", "*", "!", "'"
+- A port is no longer added to a URI when the scheme is missing and no port is
+ present.
+
+
+## 1.0.0 - 2015-05-19
+
+Initial release.
+
+Currently unsupported:
+
+- `Psr\Http\Message\ServerRequestInterface`
+- `Psr\Http\Message\UploadedFileInterface`
+
+
+
+[Unreleased]: https://github.com/guzzle/psr7/compare/1.6.0...HEAD
+[1.6.0]: https://github.com/guzzle/psr7/compare/1.5.2...1.6.0
+[1.5.2]: https://github.com/guzzle/psr7/compare/1.5.1...1.5.2
+[1.5.1]: https://github.com/guzzle/psr7/compare/1.5.0...1.5.1
+[1.5.0]: https://github.com/guzzle/psr7/compare/1.4.2...1.5.0
+[1.4.2]: https://github.com/guzzle/psr7/compare/1.4.1...1.4.2
+[1.4.1]: https://github.com/guzzle/psr7/compare/1.4.0...1.4.1
+[1.4.0]: https://github.com/guzzle/psr7/compare/1.3.1...1.4.0
+[1.3.1]: https://github.com/guzzle/psr7/compare/1.3.0...1.3.1
+[1.3.0]: https://github.com/guzzle/psr7/compare/1.2.3...1.3.0
+[1.2.3]: https://github.com/guzzle/psr7/compare/1.2.2...1.2.3
+[1.2.2]: https://github.com/guzzle/psr7/compare/1.2.1...1.2.2
+[1.2.1]: https://github.com/guzzle/psr7/compare/1.2.0...1.2.1
+[1.2.0]: https://github.com/guzzle/psr7/compare/1.1.0...1.2.0
+[1.1.0]: https://github.com/guzzle/psr7/compare/1.0.0...1.1.0
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/LICENSE b/plugins/panakour/backup/vendor/guzzlehttp/psr7/LICENSE
new file mode 100644
index 000000000..581d95f92
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015 Michael Dowling, https://github.com/mtdowling
+
+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.
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/README.md b/plugins/panakour/backup/vendor/guzzlehttp/psr7/README.md
new file mode 100644
index 000000000..c60a6a38d
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/README.md
@@ -0,0 +1,745 @@
+# PSR-7 Message Implementation
+
+This repository contains a full [PSR-7](http://www.php-fig.org/psr/psr-7/)
+message implementation, several stream decorators, and some helpful
+functionality like query string parsing.
+
+
+[](https://travis-ci.org/guzzle/psr7)
+
+
+# Stream implementation
+
+This package comes with a number of stream implementations and stream
+decorators.
+
+
+## AppendStream
+
+`GuzzleHttp\Psr7\AppendStream`
+
+Reads from multiple streams, one after the other.
+
+```php
+use GuzzleHttp\Psr7;
+
+$a = Psr7\stream_for('abc, ');
+$b = Psr7\stream_for('123.');
+$composed = new Psr7\AppendStream([$a, $b]);
+
+$composed->addStream(Psr7\stream_for(' Above all listen to me'));
+
+echo $composed; // abc, 123. Above all listen to me.
+```
+
+
+## BufferStream
+
+`GuzzleHttp\Psr7\BufferStream`
+
+Provides a buffer stream that can be written to fill a buffer, and read
+from to remove bytes from the buffer.
+
+This stream returns a "hwm" metadata value that tells upstream consumers
+what the configured high water mark of the stream is, or the maximum
+preferred size of the buffer.
+
+```php
+use GuzzleHttp\Psr7;
+
+// When more than 1024 bytes are in the buffer, it will begin returning
+// false to writes. This is an indication that writers should slow down.
+$buffer = new Psr7\BufferStream(1024);
+```
+
+
+## CachingStream
+
+The CachingStream is used to allow seeking over previously read bytes on
+non-seekable streams. This can be useful when transferring a non-seekable
+entity body fails due to needing to rewind the stream (for example, resulting
+from a redirect). Data that is read from the remote stream will be buffered in
+a PHP temp stream so that previously read bytes are cached first in memory,
+then on disk.
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\stream_for(fopen('http://www.google.com', 'r'));
+$stream = new Psr7\CachingStream($original);
+
+$stream->read(1024);
+echo $stream->tell();
+// 1024
+
+$stream->seek(0);
+echo $stream->tell();
+// 0
+```
+
+
+## DroppingStream
+
+`GuzzleHttp\Psr7\DroppingStream`
+
+Stream decorator that begins dropping data once the size of the underlying
+stream becomes too full.
+
+```php
+use GuzzleHttp\Psr7;
+
+// Create an empty stream
+$stream = Psr7\stream_for();
+
+// Start dropping data when the stream has more than 10 bytes
+$dropping = new Psr7\DroppingStream($stream, 10);
+
+$dropping->write('01234567890123456789');
+echo $stream; // 0123456789
+```
+
+
+## FnStream
+
+`GuzzleHttp\Psr7\FnStream`
+
+Compose stream implementations based on a hash of functions.
+
+Allows for easy testing and extension of a provided stream without needing
+to create a concrete class for a simple extension point.
+
+```php
+
+use GuzzleHttp\Psr7;
+
+$stream = Psr7\stream_for('hi');
+$fnStream = Psr7\FnStream::decorate($stream, [
+ 'rewind' => function () use ($stream) {
+ echo 'About to rewind - ';
+ $stream->rewind();
+ echo 'rewound!';
+ }
+]);
+
+$fnStream->rewind();
+// Outputs: About to rewind - rewound!
+```
+
+
+## InflateStream
+
+`GuzzleHttp\Psr7\InflateStream`
+
+Uses PHP's zlib.inflate filter to inflate deflate or gzipped content.
+
+This stream decorator skips the first 10 bytes of the given stream to remove
+the gzip header, converts the provided stream to a PHP stream resource,
+then appends the zlib.inflate filter. The stream is then converted back
+to a Guzzle stream resource to be used as a Guzzle stream.
+
+
+## LazyOpenStream
+
+`GuzzleHttp\Psr7\LazyOpenStream`
+
+Lazily reads or writes to a file that is opened only after an IO operation
+take place on the stream.
+
+```php
+use GuzzleHttp\Psr7;
+
+$stream = new Psr7\LazyOpenStream('/path/to/file', 'r');
+// The file has not yet been opened...
+
+echo $stream->read(10);
+// The file is opened and read from only when needed.
+```
+
+
+## LimitStream
+
+`GuzzleHttp\Psr7\LimitStream`
+
+LimitStream can be used to read a subset or slice of an existing stream object.
+This can be useful for breaking a large file into smaller pieces to be sent in
+chunks (e.g. Amazon S3's multipart upload API).
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\stream_for(fopen('/tmp/test.txt', 'r+'));
+echo $original->getSize();
+// >>> 1048576
+
+// Limit the size of the body to 1024 bytes and start reading from byte 2048
+$stream = new Psr7\LimitStream($original, 1024, 2048);
+echo $stream->getSize();
+// >>> 1024
+echo $stream->tell();
+// >>> 0
+```
+
+
+## MultipartStream
+
+`GuzzleHttp\Psr7\MultipartStream`
+
+Stream that when read returns bytes for a streaming multipart or
+multipart/form-data stream.
+
+
+## NoSeekStream
+
+`GuzzleHttp\Psr7\NoSeekStream`
+
+NoSeekStream wraps a stream and does not allow seeking.
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\stream_for('foo');
+$noSeek = new Psr7\NoSeekStream($original);
+
+echo $noSeek->read(3);
+// foo
+var_export($noSeek->isSeekable());
+// false
+$noSeek->seek(0);
+var_export($noSeek->read(3));
+// NULL
+```
+
+
+## PumpStream
+
+`GuzzleHttp\Psr7\PumpStream`
+
+Provides a read only stream that pumps data from a PHP callable.
+
+When invoking the provided callable, the PumpStream will pass the amount of
+data requested to read to the callable. The callable can choose to ignore
+this value and return fewer or more bytes than requested. Any extra data
+returned by the provided callable is buffered internally until drained using
+the read() function of the PumpStream. The provided callable MUST return
+false when there is no more data to read.
+
+
+## Implementing stream decorators
+
+Creating a stream decorator is very easy thanks to the
+`GuzzleHttp\Psr7\StreamDecoratorTrait`. This trait provides methods that
+implement `Psr\Http\Message\StreamInterface` by proxying to an underlying
+stream. Just `use` the `StreamDecoratorTrait` and implement your custom
+methods.
+
+For example, let's say we wanted to call a specific function each time the last
+byte is read from a stream. This could be implemented by overriding the
+`read()` method.
+
+```php
+use Psr\Http\Message\StreamInterface;
+use GuzzleHttp\Psr7\StreamDecoratorTrait;
+
+class EofCallbackStream implements StreamInterface
+{
+ use StreamDecoratorTrait;
+
+ private $callback;
+
+ public function __construct(StreamInterface $stream, callable $cb)
+ {
+ $this->stream = $stream;
+ $this->callback = $cb;
+ }
+
+ public function read($length)
+ {
+ $result = $this->stream->read($length);
+
+ // Invoke the callback when EOF is hit.
+ if ($this->eof()) {
+ call_user_func($this->callback);
+ }
+
+ return $result;
+ }
+}
+```
+
+This decorator could be added to any existing stream and used like so:
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\stream_for('foo');
+
+$eofStream = new EofCallbackStream($original, function () {
+ echo 'EOF!';
+});
+
+$eofStream->read(2);
+$eofStream->read(1);
+// echoes "EOF!"
+$eofStream->seek(0);
+$eofStream->read(3);
+// echoes "EOF!"
+```
+
+
+## PHP StreamWrapper
+
+You can use the `GuzzleHttp\Psr7\StreamWrapper` class if you need to use a
+PSR-7 stream as a PHP stream resource.
+
+Use the `GuzzleHttp\Psr7\StreamWrapper::getResource()` method to create a PHP
+stream from a PSR-7 stream.
+
+```php
+use GuzzleHttp\Psr7\StreamWrapper;
+
+$stream = GuzzleHttp\Psr7\stream_for('hello!');
+$resource = StreamWrapper::getResource($stream);
+echo fread($resource, 6); // outputs hello!
+```
+
+
+# Function API
+
+There are various functions available under the `GuzzleHttp\Psr7` namespace.
+
+
+## `function str`
+
+`function str(MessageInterface $message)`
+
+Returns the string representation of an HTTP message.
+
+```php
+$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com');
+echo GuzzleHttp\Psr7\str($request);
+```
+
+
+## `function uri_for`
+
+`function uri_for($uri)`
+
+This function accepts a string or `Psr\Http\Message\UriInterface` and returns a
+UriInterface for the given value. If the value is already a `UriInterface`, it
+is returned as-is.
+
+```php
+$uri = GuzzleHttp\Psr7\uri_for('http://example.com');
+assert($uri === GuzzleHttp\Psr7\uri_for($uri));
+```
+
+
+## `function stream_for`
+
+`function stream_for($resource = '', array $options = [])`
+
+Create a new stream based on the input type.
+
+Options is an associative array that can contain the following keys:
+
+* - metadata: Array of custom metadata.
+* - size: Size of the stream.
+
+This method accepts the following `$resource` types:
+
+- `Psr\Http\Message\StreamInterface`: Returns the value as-is.
+- `string`: Creates a stream object that uses the given string as the contents.
+- `resource`: Creates a stream object that wraps the given PHP stream resource.
+- `Iterator`: If the provided value implements `Iterator`, then a read-only
+ stream object will be created that wraps the given iterable. Each time the
+ stream is read from, data from the iterator will fill a buffer and will be
+ continuously called until the buffer is equal to the requested read size.
+ Subsequent read calls will first read from the buffer and then call `next`
+ on the underlying iterator until it is exhausted.
+- `object` with `__toString()`: If the object has the `__toString()` method,
+ the object will be cast to a string and then a stream will be returned that
+ uses the string value.
+- `NULL`: When `null` is passed, an empty stream object is returned.
+- `callable` When a callable is passed, a read-only stream object will be
+ created that invokes the given callable. The callable is invoked with the
+ number of suggested bytes to read. The callable can return any number of
+ bytes, but MUST return `false` when there is no more data to return. The
+ stream object that wraps the callable will invoke the callable until the
+ number of requested bytes are available. Any additional bytes will be
+ buffered and used in subsequent reads.
+
+```php
+$stream = GuzzleHttp\Psr7\stream_for('foo');
+$stream = GuzzleHttp\Psr7\stream_for(fopen('/path/to/file', 'r'));
+
+$generator = function ($bytes) {
+ for ($i = 0; $i < $bytes; $i++) {
+ yield ' ';
+ }
+}
+
+$stream = GuzzleHttp\Psr7\stream_for($generator(100));
+```
+
+
+## `function parse_header`
+
+`function parse_header($header)`
+
+Parse an array of header values containing ";" separated data into an array of
+associative arrays representing the header key value pair data of the header.
+When a parameter does not contain a value, but just contains a key, this
+function will inject a key with a '' string value.
+
+
+## `function normalize_header`
+
+`function normalize_header($header)`
+
+Converts an array of header values that may contain comma separated headers
+into an array of headers with no comma separated values.
+
+
+## `function modify_request`
+
+`function modify_request(RequestInterface $request, array $changes)`
+
+Clone and modify a request with the given changes. This method is useful for
+reducing the number of clones needed to mutate a message.
+
+The changes can be one of:
+
+- method: (string) Changes the HTTP method.
+- set_headers: (array) Sets the given headers.
+- remove_headers: (array) Remove the given headers.
+- body: (mixed) Sets the given body.
+- uri: (UriInterface) Set the URI.
+- query: (string) Set the query string value of the URI.
+- version: (string) Set the protocol version.
+
+
+## `function rewind_body`
+
+`function rewind_body(MessageInterface $message)`
+
+Attempts to rewind a message body and throws an exception on failure. The body
+of the message will only be rewound if a call to `tell()` returns a value other
+than `0`.
+
+
+## `function try_fopen`
+
+`function try_fopen($filename, $mode)`
+
+Safely opens a PHP stream resource using a filename.
+
+When fopen fails, PHP normally raises a warning. This function adds an error
+handler that checks for errors and throws an exception instead.
+
+
+## `function copy_to_string`
+
+`function copy_to_string(StreamInterface $stream, $maxLen = -1)`
+
+Copy the contents of a stream into a string until the given number of bytes
+have been read.
+
+
+## `function copy_to_stream`
+
+`function copy_to_stream(StreamInterface $source, StreamInterface $dest, $maxLen = -1)`
+
+Copy the contents of a stream into another stream until the given number of
+bytes have been read.
+
+
+## `function hash`
+
+`function hash(StreamInterface $stream, $algo, $rawOutput = false)`
+
+Calculate a hash of a Stream. This method reads the entire stream to calculate
+a rolling hash (based on PHP's hash_init functions).
+
+
+## `function readline`
+
+`function readline(StreamInterface $stream, $maxLength = null)`
+
+Read a line from the stream up to the maximum allowed buffer length.
+
+
+## `function parse_request`
+
+`function parse_request($message)`
+
+Parses a request message string into a request object.
+
+
+## `function parse_response`
+
+`function parse_response($message)`
+
+Parses a response message string into a response object.
+
+
+## `function parse_query`
+
+`function parse_query($str, $urlEncoding = true)`
+
+Parse a query string into an associative array.
+
+If multiple values are found for the same key, the value of that key value pair
+will become an array. This function does not parse nested PHP style arrays into
+an associative array (e.g., `foo[a]=1&foo[b]=2` will be parsed into
+`['foo[a]' => '1', 'foo[b]' => '2']`).
+
+
+## `function build_query`
+
+`function build_query(array $params, $encoding = PHP_QUERY_RFC3986)`
+
+Build a query string from an array of key value pairs.
+
+This function can use the return value of parse_query() to build a query string.
+This function does not modify the provided keys when an array is encountered
+(like http_build_query would).
+
+
+## `function mimetype_from_filename`
+
+`function mimetype_from_filename($filename)`
+
+Determines the mimetype of a file by looking at its extension.
+
+
+## `function mimetype_from_extension`
+
+`function mimetype_from_extension($extension)`
+
+Maps a file extensions to a mimetype.
+
+
+# Additional URI Methods
+
+Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class,
+this library also provides additional functionality when working with URIs as static methods.
+
+## URI Types
+
+An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference.
+An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI,
+the base URI. Relative references can be divided into several forms according to
+[RFC 3986 Section 4.2](https://tools.ietf.org/html/rfc3986#section-4.2):
+
+- network-path references, e.g. `//example.com/path`
+- absolute-path references, e.g. `/path`
+- relative-path references, e.g. `subpath`
+
+The following methods can be used to identify the type of the URI.
+
+### `GuzzleHttp\Psr7\Uri::isAbsolute`
+
+`public static function isAbsolute(UriInterface $uri): bool`
+
+Whether the URI is absolute, i.e. it has a scheme.
+
+### `GuzzleHttp\Psr7\Uri::isNetworkPathReference`
+
+`public static function isNetworkPathReference(UriInterface $uri): bool`
+
+Whether the URI is a network-path reference. A relative reference that begins with two slash characters is
+termed an network-path reference.
+
+### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference`
+
+`public static function isAbsolutePathReference(UriInterface $uri): bool`
+
+Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is
+termed an absolute-path reference.
+
+### `GuzzleHttp\Psr7\Uri::isRelativePathReference`
+
+`public static function isRelativePathReference(UriInterface $uri): bool`
+
+Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is
+termed a relative-path reference.
+
+### `GuzzleHttp\Psr7\Uri::isSameDocumentReference`
+
+`public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool`
+
+Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its
+fragment component, identical to the base URI. When no base URI is given, only an empty URI reference
+(apart from its fragment) is considered a same-document reference.
+
+## URI Components
+
+Additional methods to work with URI components.
+
+### `GuzzleHttp\Psr7\Uri::isDefaultPort`
+
+`public static function isDefaultPort(UriInterface $uri): bool`
+
+Whether the URI has the default port of the current scheme. `Psr\Http\Message\UriInterface::getPort` may return null
+or the standard port. This method can be used independently of the implementation.
+
+### `GuzzleHttp\Psr7\Uri::composeComponents`
+
+`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string`
+
+Composes a URI reference string from its various components according to
+[RFC 3986 Section 5.3](https://tools.ietf.org/html/rfc3986#section-5.3). Usually this method does not need to be called
+manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`.
+
+### `GuzzleHttp\Psr7\Uri::fromParts`
+
+`public static function fromParts(array $parts): UriInterface`
+
+Creates a URI from a hash of [`parse_url`](http://php.net/manual/en/function.parse-url.php) components.
+
+
+### `GuzzleHttp\Psr7\Uri::withQueryValue`
+
+`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface`
+
+Creates a new URI with a specific query string value. Any existing query string values that exactly match the
+provided key are removed and replaced with the given key value pair. A value of null will set the query string
+key without a value, e.g. "key" instead of "key=value".
+
+### `GuzzleHttp\Psr7\Uri::withQueryValues`
+
+`public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface`
+
+Creates a new URI with multiple query string values. It has the same behavior as `withQueryValue()` but for an
+associative array of key => value.
+
+### `GuzzleHttp\Psr7\Uri::withoutQueryValue`
+
+`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface`
+
+Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the
+provided key are removed.
+
+## Reference Resolution
+
+`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according
+to [RFC 3986 Section 5](https://tools.ietf.org/html/rfc3986#section-5). This is for example also what web browsers
+do when resolving a link in a website based on the current request URI.
+
+### `GuzzleHttp\Psr7\UriResolver::resolve`
+
+`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface`
+
+Converts the relative URI into a new URI that is resolved against the base URI.
+
+### `GuzzleHttp\Psr7\UriResolver::removeDotSegments`
+
+`public static function removeDotSegments(string $path): string`
+
+Removes dot segments from a path and returns the new path according to
+[RFC 3986 Section 5.2.4](https://tools.ietf.org/html/rfc3986#section-5.2.4).
+
+### `GuzzleHttp\Psr7\UriResolver::relativize`
+
+`public static function relativize(UriInterface $base, UriInterface $target): UriInterface`
+
+Returns the target URI as a relative reference from the base URI. This method is the counterpart to resolve():
+
+```php
+(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
+```
+
+One use-case is to use the current request URI as base URI and then generate relative links in your documents
+to reduce the document size or offer self-contained downloadable document archives.
+
+```php
+$base = new Uri('http://example.com/a/b/');
+echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'.
+echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'.
+echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
+echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
+```
+
+## Normalization and Comparison
+
+`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to
+[RFC 3986 Section 6](https://tools.ietf.org/html/rfc3986#section-6).
+
+### `GuzzleHttp\Psr7\UriNormalizer::normalize`
+
+`public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface`
+
+Returns a normalized URI. The scheme and host component are already normalized to lowercase per PSR-7 UriInterface.
+This methods adds additional normalizations that can be configured with the `$flags` parameter which is a bitmask
+of normalizations to apply. The following normalizations are available:
+
+- `UriNormalizer::PRESERVING_NORMALIZATIONS`
+
+ Default normalizations which only include the ones that preserve semantics.
+
+- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING`
+
+ All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized.
+
+ Example: `http://example.org/a%c2%b1b` → `http://example.org/a%C2%B1b`
+
+- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS`
+
+ Decodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of
+ ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should
+ not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved
+ characters by URI normalizers.
+
+ Example: `http://example.org/%7Eusern%61me/` → `http://example.org/~username/`
+
+- `UriNormalizer::CONVERT_EMPTY_PATH`
+
+ Converts the empty path to "/" for http and https URIs.
+
+ Example: `http://example.org` → `http://example.org/`
+
+- `UriNormalizer::REMOVE_DEFAULT_HOST`
+
+ Removes the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host
+ "localhost". All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are equivalent according to
+ RFC 3986.
+
+ Example: `file://localhost/myfile` → `file:///myfile`
+
+- `UriNormalizer::REMOVE_DEFAULT_PORT`
+
+ Removes the default port of the given URI scheme from the URI.
+
+ Example: `http://example.org:80/` → `http://example.org/`
+
+- `UriNormalizer::REMOVE_DOT_SEGMENTS`
+
+ Removes unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would
+ change the semantics of the URI reference.
+
+ Example: `http://example.org/../a/b/../c/./d.html` → `http://example.org/a/c/d.html`
+
+- `UriNormalizer::REMOVE_DUPLICATE_SLASHES`
+
+ Paths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes
+ and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization
+ may change the semantics. Encoded slashes (%2F) are not removed.
+
+ Example: `http://example.org//foo///bar.html` → `http://example.org/foo/bar.html`
+
+- `UriNormalizer::SORT_QUERY_PARAMETERS`
+
+ Sort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be
+ significant (this is not defined by the standard). So this normalization is not safe and may change the semantics
+ of the URI.
+
+ Example: `?lang=en&article=fred` → `?article=fred&lang=en`
+
+### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent`
+
+`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool`
+
+Whether two URIs can be considered equivalent. Both URIs are normalized automatically before comparison with the given
+`$normalizations` bitmask. The method also accepts relative URI references and returns true when they are equivalent.
+This of course assumes they will be resolved against the same base URI. If this is not the case, determination of
+equivalence or difference of relative references does not mean anything.
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/composer.json b/plugins/panakour/backup/vendor/guzzlehttp/psr7/composer.json
new file mode 100644
index 000000000..168a055b0
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/composer.json
@@ -0,0 +1,49 @@
+{
+ "name": "guzzlehttp/psr7",
+ "type": "library",
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": ["request", "response", "message", "stream", "http", "uri", "url", "psr-7"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Tobias Schultze",
+ "homepage": "https://github.com/Tobion"
+ }
+ ],
+ "require": {
+ "php": ">=5.4.0",
+ "psr/http-message": "~1.0",
+ "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8",
+ "ext-zlib": "*"
+ },
+ "provide": {
+ "psr/http-message-implementation": "1.0"
+ },
+ "suggest": {
+ "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses"
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ },
+ "files": ["src/functions_include.php"]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "GuzzleHttp\\Tests\\Psr7\\": "tests/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.6-dev"
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/AppendStream.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/AppendStream.php
new file mode 100644
index 000000000..472a0d61b
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/AppendStream.php
@@ -0,0 +1,241 @@
+addStream($stream);
+ }
+ }
+
+ public function __toString()
+ {
+ try {
+ $this->rewind();
+ return $this->getContents();
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ /**
+ * Add a stream to the AppendStream
+ *
+ * @param StreamInterface $stream Stream to append. Must be readable.
+ *
+ * @throws \InvalidArgumentException if the stream is not readable
+ */
+ public function addStream(StreamInterface $stream)
+ {
+ if (!$stream->isReadable()) {
+ throw new \InvalidArgumentException('Each stream must be readable');
+ }
+
+ // The stream is only seekable if all streams are seekable
+ if (!$stream->isSeekable()) {
+ $this->seekable = false;
+ }
+
+ $this->streams[] = $stream;
+ }
+
+ public function getContents()
+ {
+ return copy_to_string($this);
+ }
+
+ /**
+ * Closes each attached stream.
+ *
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ $this->pos = $this->current = 0;
+ $this->seekable = true;
+
+ foreach ($this->streams as $stream) {
+ $stream->close();
+ }
+
+ $this->streams = [];
+ }
+
+ /**
+ * Detaches each attached stream.
+ *
+ * Returns null as it's not clear which underlying stream resource to return.
+ *
+ * {@inheritdoc}
+ */
+ public function detach()
+ {
+ $this->pos = $this->current = 0;
+ $this->seekable = true;
+
+ foreach ($this->streams as $stream) {
+ $stream->detach();
+ }
+
+ $this->streams = [];
+ }
+
+ public function tell()
+ {
+ return $this->pos;
+ }
+
+ /**
+ * Tries to calculate the size by adding the size of each stream.
+ *
+ * If any of the streams do not return a valid number, then the size of the
+ * append stream cannot be determined and null is returned.
+ *
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ $size = 0;
+
+ foreach ($this->streams as $stream) {
+ $s = $stream->getSize();
+ if ($s === null) {
+ return null;
+ }
+ $size += $s;
+ }
+
+ return $size;
+ }
+
+ public function eof()
+ {
+ return !$this->streams ||
+ ($this->current >= count($this->streams) - 1 &&
+ $this->streams[$this->current]->eof());
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ /**
+ * Attempts to seek to the given position. Only supports SEEK_SET.
+ *
+ * {@inheritdoc}
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if (!$this->seekable) {
+ throw new \RuntimeException('This AppendStream is not seekable');
+ } elseif ($whence !== SEEK_SET) {
+ throw new \RuntimeException('The AppendStream can only seek with SEEK_SET');
+ }
+
+ $this->pos = $this->current = 0;
+
+ // Rewind each stream
+ foreach ($this->streams as $i => $stream) {
+ try {
+ $stream->rewind();
+ } catch (\Exception $e) {
+ throw new \RuntimeException('Unable to seek stream '
+ . $i . ' of the AppendStream', 0, $e);
+ }
+ }
+
+ // Seek to the actual position by reading from each stream
+ while ($this->pos < $offset && !$this->eof()) {
+ $result = $this->read(min(8096, $offset - $this->pos));
+ if ($result === '') {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Reads from all of the appended streams until the length is met or EOF.
+ *
+ * {@inheritdoc}
+ */
+ public function read($length)
+ {
+ $buffer = '';
+ $total = count($this->streams) - 1;
+ $remaining = $length;
+ $progressToNext = false;
+
+ while ($remaining > 0) {
+
+ // Progress to the next stream if needed.
+ if ($progressToNext || $this->streams[$this->current]->eof()) {
+ $progressToNext = false;
+ if ($this->current === $total) {
+ break;
+ }
+ $this->current++;
+ }
+
+ $result = $this->streams[$this->current]->read($remaining);
+
+ // Using a loose comparison here to match on '', false, and null
+ if ($result == null) {
+ $progressToNext = true;
+ continue;
+ }
+
+ $buffer .= $result;
+ $remaining = $length - strlen($buffer);
+ }
+
+ $this->pos += strlen($buffer);
+
+ return $buffer;
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ public function isSeekable()
+ {
+ return $this->seekable;
+ }
+
+ public function write($string)
+ {
+ throw new \RuntimeException('Cannot write to an AppendStream');
+ }
+
+ public function getMetadata($key = null)
+ {
+ return $key ? null : [];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/BufferStream.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/BufferStream.php
new file mode 100644
index 000000000..af4d4c227
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/BufferStream.php
@@ -0,0 +1,137 @@
+hwm = $hwm;
+ }
+
+ public function __toString()
+ {
+ return $this->getContents();
+ }
+
+ public function getContents()
+ {
+ $buffer = $this->buffer;
+ $this->buffer = '';
+
+ return $buffer;
+ }
+
+ public function close()
+ {
+ $this->buffer = '';
+ }
+
+ public function detach()
+ {
+ $this->close();
+ }
+
+ public function getSize()
+ {
+ return strlen($this->buffer);
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function isWritable()
+ {
+ return true;
+ }
+
+ public function isSeekable()
+ {
+ return false;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ throw new \RuntimeException('Cannot seek a BufferStream');
+ }
+
+ public function eof()
+ {
+ return strlen($this->buffer) === 0;
+ }
+
+ public function tell()
+ {
+ throw new \RuntimeException('Cannot determine the position of a BufferStream');
+ }
+
+ /**
+ * Reads data from the buffer.
+ */
+ public function read($length)
+ {
+ $currentLength = strlen($this->buffer);
+
+ if ($length >= $currentLength) {
+ // No need to slice the buffer because we don't have enough data.
+ $result = $this->buffer;
+ $this->buffer = '';
+ } else {
+ // Slice up the result to provide a subset of the buffer.
+ $result = substr($this->buffer, 0, $length);
+ $this->buffer = substr($this->buffer, $length);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Writes data to the buffer.
+ */
+ public function write($string)
+ {
+ $this->buffer .= $string;
+
+ // TODO: What should happen here?
+ if (strlen($this->buffer) >= $this->hwm) {
+ return false;
+ }
+
+ return strlen($string);
+ }
+
+ public function getMetadata($key = null)
+ {
+ if ($key == 'hwm') {
+ return $this->hwm;
+ }
+
+ return $key ? null : [];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/CachingStream.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/CachingStream.php
new file mode 100644
index 000000000..ed68f0861
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/CachingStream.php
@@ -0,0 +1,138 @@
+remoteStream = $stream;
+ $this->stream = $target ?: new Stream(fopen('php://temp', 'r+'));
+ }
+
+ public function getSize()
+ {
+ return max($this->stream->getSize(), $this->remoteStream->getSize());
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if ($whence == SEEK_SET) {
+ $byte = $offset;
+ } elseif ($whence == SEEK_CUR) {
+ $byte = $offset + $this->tell();
+ } elseif ($whence == SEEK_END) {
+ $size = $this->remoteStream->getSize();
+ if ($size === null) {
+ $size = $this->cacheEntireStream();
+ }
+ $byte = $size + $offset;
+ } else {
+ throw new \InvalidArgumentException('Invalid whence');
+ }
+
+ $diff = $byte - $this->stream->getSize();
+
+ if ($diff > 0) {
+ // Read the remoteStream until we have read in at least the amount
+ // of bytes requested, or we reach the end of the file.
+ while ($diff > 0 && !$this->remoteStream->eof()) {
+ $this->read($diff);
+ $diff = $byte - $this->stream->getSize();
+ }
+ } else {
+ // We can just do a normal seek since we've already seen this byte.
+ $this->stream->seek($byte);
+ }
+ }
+
+ public function read($length)
+ {
+ // Perform a regular read on any previously read data from the buffer
+ $data = $this->stream->read($length);
+ $remaining = $length - strlen($data);
+
+ // More data was requested so read from the remote stream
+ if ($remaining) {
+ // If data was written to the buffer in a position that would have
+ // been filled from the remote stream, then we must skip bytes on
+ // the remote stream to emulate overwriting bytes from that
+ // position. This mimics the behavior of other PHP stream wrappers.
+ $remoteData = $this->remoteStream->read(
+ $remaining + $this->skipReadBytes
+ );
+
+ if ($this->skipReadBytes) {
+ $len = strlen($remoteData);
+ $remoteData = substr($remoteData, $this->skipReadBytes);
+ $this->skipReadBytes = max(0, $this->skipReadBytes - $len);
+ }
+
+ $data .= $remoteData;
+ $this->stream->write($remoteData);
+ }
+
+ return $data;
+ }
+
+ public function write($string)
+ {
+ // When appending to the end of the currently read stream, you'll want
+ // to skip bytes from being read from the remote stream to emulate
+ // other stream wrappers. Basically replacing bytes of data of a fixed
+ // length.
+ $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell();
+ if ($overflow > 0) {
+ $this->skipReadBytes += $overflow;
+ }
+
+ return $this->stream->write($string);
+ }
+
+ public function eof()
+ {
+ return $this->stream->eof() && $this->remoteStream->eof();
+ }
+
+ /**
+ * Close both the remote stream and buffer stream
+ */
+ public function close()
+ {
+ $this->remoteStream->close() && $this->stream->close();
+ }
+
+ private function cacheEntireStream()
+ {
+ $target = new FnStream(['write' => 'strlen']);
+ copy_to_stream($this, $target);
+
+ return $this->tell();
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/DroppingStream.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/DroppingStream.php
new file mode 100644
index 000000000..8935c80d7
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/DroppingStream.php
@@ -0,0 +1,42 @@
+stream = $stream;
+ $this->maxLength = $maxLength;
+ }
+
+ public function write($string)
+ {
+ $diff = $this->maxLength - $this->stream->getSize();
+
+ // Begin returning 0 when the underlying stream is too large.
+ if ($diff <= 0) {
+ return 0;
+ }
+
+ // Write the stream or a subset of the stream if needed.
+ if (strlen($string) < $diff) {
+ return $this->stream->write($string);
+ }
+
+ return $this->stream->write(substr($string, 0, $diff));
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/FnStream.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/FnStream.php
new file mode 100644
index 000000000..73daea6f3
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/FnStream.php
@@ -0,0 +1,158 @@
+methods = $methods;
+
+ // Create the functions on the class
+ foreach ($methods as $name => $fn) {
+ $this->{'_fn_' . $name} = $fn;
+ }
+ }
+
+ /**
+ * Lazily determine which methods are not implemented.
+ * @throws \BadMethodCallException
+ */
+ public function __get($name)
+ {
+ throw new \BadMethodCallException(str_replace('_fn_', '', $name)
+ . '() is not implemented in the FnStream');
+ }
+
+ /**
+ * The close method is called on the underlying stream only if possible.
+ */
+ public function __destruct()
+ {
+ if (isset($this->_fn_close)) {
+ call_user_func($this->_fn_close);
+ }
+ }
+
+ /**
+ * An unserialize would allow the __destruct to run when the unserialized value goes out of scope.
+ * @throws \LogicException
+ */
+ public function __wakeup()
+ {
+ throw new \LogicException('FnStream should never be unserialized');
+ }
+
+ /**
+ * Adds custom functionality to an underlying stream by intercepting
+ * specific method calls.
+ *
+ * @param StreamInterface $stream Stream to decorate
+ * @param array $methods Hash of method name to a closure
+ *
+ * @return FnStream
+ */
+ public static function decorate(StreamInterface $stream, array $methods)
+ {
+ // If any of the required methods were not provided, then simply
+ // proxy to the decorated stream.
+ foreach (array_diff(self::$slots, array_keys($methods)) as $diff) {
+ $methods[$diff] = [$stream, $diff];
+ }
+
+ return new self($methods);
+ }
+
+ public function __toString()
+ {
+ return call_user_func($this->_fn___toString);
+ }
+
+ public function close()
+ {
+ return call_user_func($this->_fn_close);
+ }
+
+ public function detach()
+ {
+ return call_user_func($this->_fn_detach);
+ }
+
+ public function getSize()
+ {
+ return call_user_func($this->_fn_getSize);
+ }
+
+ public function tell()
+ {
+ return call_user_func($this->_fn_tell);
+ }
+
+ public function eof()
+ {
+ return call_user_func($this->_fn_eof);
+ }
+
+ public function isSeekable()
+ {
+ return call_user_func($this->_fn_isSeekable);
+ }
+
+ public function rewind()
+ {
+ call_user_func($this->_fn_rewind);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ call_user_func($this->_fn_seek, $offset, $whence);
+ }
+
+ public function isWritable()
+ {
+ return call_user_func($this->_fn_isWritable);
+ }
+
+ public function write($string)
+ {
+ return call_user_func($this->_fn_write, $string);
+ }
+
+ public function isReadable()
+ {
+ return call_user_func($this->_fn_isReadable);
+ }
+
+ public function read($length)
+ {
+ return call_user_func($this->_fn_read, $length);
+ }
+
+ public function getContents()
+ {
+ return call_user_func($this->_fn_getContents);
+ }
+
+ public function getMetadata($key = null)
+ {
+ return call_user_func($this->_fn_getMetadata, $key);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/InflateStream.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/InflateStream.php
new file mode 100644
index 000000000..5e4f6028c
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/InflateStream.php
@@ -0,0 +1,52 @@
+read(10);
+ $filenameHeaderLength = $this->getLengthOfPossibleFilenameHeader($stream, $header);
+ // Skip the header, that is 10 + length of filename + 1 (nil) bytes
+ $stream = new LimitStream($stream, -1, 10 + $filenameHeaderLength);
+ $resource = StreamWrapper::getResource($stream);
+ stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ);
+ $this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource));
+ }
+
+ /**
+ * @param StreamInterface $stream
+ * @param $header
+ * @return int
+ */
+ private function getLengthOfPossibleFilenameHeader(StreamInterface $stream, $header)
+ {
+ $filename_header_length = 0;
+
+ if (substr(bin2hex($header), 6, 2) === '08') {
+ // we have a filename, read until nil
+ $filename_header_length = 1;
+ while ($stream->read(1) !== chr(0)) {
+ $filename_header_length++;
+ }
+ }
+
+ return $filename_header_length;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/LazyOpenStream.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/LazyOpenStream.php
new file mode 100644
index 000000000..02cec3af4
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/LazyOpenStream.php
@@ -0,0 +1,39 @@
+filename = $filename;
+ $this->mode = $mode;
+ }
+
+ /**
+ * Creates the underlying stream lazily when required.
+ *
+ * @return StreamInterface
+ */
+ protected function createStream()
+ {
+ return stream_for(try_fopen($this->filename, $this->mode));
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/LimitStream.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/LimitStream.php
new file mode 100644
index 000000000..e4f239e30
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/LimitStream.php
@@ -0,0 +1,155 @@
+stream = $stream;
+ $this->setLimit($limit);
+ $this->setOffset($offset);
+ }
+
+ public function eof()
+ {
+ // Always return true if the underlying stream is EOF
+ if ($this->stream->eof()) {
+ return true;
+ }
+
+ // No limit and the underlying stream is not at EOF
+ if ($this->limit == -1) {
+ return false;
+ }
+
+ return $this->stream->tell() >= $this->offset + $this->limit;
+ }
+
+ /**
+ * Returns the size of the limited subset of data
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ if (null === ($length = $this->stream->getSize())) {
+ return null;
+ } elseif ($this->limit == -1) {
+ return $length - $this->offset;
+ } else {
+ return min($this->limit, $length - $this->offset);
+ }
+ }
+
+ /**
+ * Allow for a bounded seek on the read limited stream
+ * {@inheritdoc}
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if ($whence !== SEEK_SET || $offset < 0) {
+ throw new \RuntimeException(sprintf(
+ 'Cannot seek to offset %s with whence %s',
+ $offset,
+ $whence
+ ));
+ }
+
+ $offset += $this->offset;
+
+ if ($this->limit !== -1) {
+ if ($offset > $this->offset + $this->limit) {
+ $offset = $this->offset + $this->limit;
+ }
+ }
+
+ $this->stream->seek($offset);
+ }
+
+ /**
+ * Give a relative tell()
+ * {@inheritdoc}
+ */
+ public function tell()
+ {
+ return $this->stream->tell() - $this->offset;
+ }
+
+ /**
+ * Set the offset to start limiting from
+ *
+ * @param int $offset Offset to seek to and begin byte limiting from
+ *
+ * @throws \RuntimeException if the stream cannot be seeked.
+ */
+ public function setOffset($offset)
+ {
+ $current = $this->stream->tell();
+
+ if ($current !== $offset) {
+ // If the stream cannot seek to the offset position, then read to it
+ if ($this->stream->isSeekable()) {
+ $this->stream->seek($offset);
+ } elseif ($current > $offset) {
+ throw new \RuntimeException("Could not seek to stream offset $offset");
+ } else {
+ $this->stream->read($offset - $current);
+ }
+ }
+
+ $this->offset = $offset;
+ }
+
+ /**
+ * Set the limit of bytes that the decorator allows to be read from the
+ * stream.
+ *
+ * @param int $limit Number of bytes to allow to be read from the stream.
+ * Use -1 for no limit.
+ */
+ public function setLimit($limit)
+ {
+ $this->limit = $limit;
+ }
+
+ public function read($length)
+ {
+ if ($this->limit == -1) {
+ return $this->stream->read($length);
+ }
+
+ // Check if the current position is less than the total allowed
+ // bytes + original offset
+ $remaining = ($this->offset + $this->limit) - $this->stream->tell();
+ if ($remaining > 0) {
+ // Only return the amount of requested data, ensuring that the byte
+ // limit is not exceeded
+ return $this->stream->read(min($remaining, $length));
+ }
+
+ return '';
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/MessageTrait.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/MessageTrait.php
new file mode 100644
index 000000000..a7966d10c
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/MessageTrait.php
@@ -0,0 +1,213 @@
+ array of values */
+ private $headers = [];
+
+ /** @var array Map of lowercase header name => original name at registration */
+ private $headerNames = [];
+
+ /** @var string */
+ private $protocol = '1.1';
+
+ /** @var StreamInterface */
+ private $stream;
+
+ public function getProtocolVersion()
+ {
+ return $this->protocol;
+ }
+
+ public function withProtocolVersion($version)
+ {
+ if ($this->protocol === $version) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->protocol = $version;
+ return $new;
+ }
+
+ public function getHeaders()
+ {
+ return $this->headers;
+ }
+
+ public function hasHeader($header)
+ {
+ return isset($this->headerNames[strtolower($header)]);
+ }
+
+ public function getHeader($header)
+ {
+ $header = strtolower($header);
+
+ if (!isset($this->headerNames[$header])) {
+ return [];
+ }
+
+ $header = $this->headerNames[$header];
+
+ return $this->headers[$header];
+ }
+
+ public function getHeaderLine($header)
+ {
+ return implode(', ', $this->getHeader($header));
+ }
+
+ public function withHeader($header, $value)
+ {
+ $this->assertHeader($header);
+ $value = $this->normalizeHeaderValue($value);
+ $normalized = strtolower($header);
+
+ $new = clone $this;
+ if (isset($new->headerNames[$normalized])) {
+ unset($new->headers[$new->headerNames[$normalized]]);
+ }
+ $new->headerNames[$normalized] = $header;
+ $new->headers[$header] = $value;
+
+ return $new;
+ }
+
+ public function withAddedHeader($header, $value)
+ {
+ $this->assertHeader($header);
+ $value = $this->normalizeHeaderValue($value);
+ $normalized = strtolower($header);
+
+ $new = clone $this;
+ if (isset($new->headerNames[$normalized])) {
+ $header = $this->headerNames[$normalized];
+ $new->headers[$header] = array_merge($this->headers[$header], $value);
+ } else {
+ $new->headerNames[$normalized] = $header;
+ $new->headers[$header] = $value;
+ }
+
+ return $new;
+ }
+
+ public function withoutHeader($header)
+ {
+ $normalized = strtolower($header);
+
+ if (!isset($this->headerNames[$normalized])) {
+ return $this;
+ }
+
+ $header = $this->headerNames[$normalized];
+
+ $new = clone $this;
+ unset($new->headers[$header], $new->headerNames[$normalized]);
+
+ return $new;
+ }
+
+ public function getBody()
+ {
+ if (!$this->stream) {
+ $this->stream = stream_for('');
+ }
+
+ return $this->stream;
+ }
+
+ public function withBody(StreamInterface $body)
+ {
+ if ($body === $this->stream) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->stream = $body;
+ return $new;
+ }
+
+ private function setHeaders(array $headers)
+ {
+ $this->headerNames = $this->headers = [];
+ foreach ($headers as $header => $value) {
+ if (is_int($header)) {
+ // Numeric array keys are converted to int by PHP but having a header name '123' is not forbidden by the spec
+ // and also allowed in withHeader(). So we need to cast it to string again for the following assertion to pass.
+ $header = (string) $header;
+ }
+ $this->assertHeader($header);
+ $value = $this->normalizeHeaderValue($value);
+ $normalized = strtolower($header);
+ if (isset($this->headerNames[$normalized])) {
+ $header = $this->headerNames[$normalized];
+ $this->headers[$header] = array_merge($this->headers[$header], $value);
+ } else {
+ $this->headerNames[$normalized] = $header;
+ $this->headers[$header] = $value;
+ }
+ }
+ }
+
+ private function normalizeHeaderValue($value)
+ {
+ if (!is_array($value)) {
+ return $this->trimHeaderValues([$value]);
+ }
+
+ if (count($value) === 0) {
+ throw new \InvalidArgumentException('Header value can not be an empty array.');
+ }
+
+ return $this->trimHeaderValues($value);
+ }
+
+ /**
+ * Trims whitespace from the header values.
+ *
+ * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field.
+ *
+ * header-field = field-name ":" OWS field-value OWS
+ * OWS = *( SP / HTAB )
+ *
+ * @param string[] $values Header values
+ *
+ * @return string[] Trimmed header values
+ *
+ * @see https://tools.ietf.org/html/rfc7230#section-3.2.4
+ */
+ private function trimHeaderValues(array $values)
+ {
+ return array_map(function ($value) {
+ if (!is_scalar($value) && null !== $value) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Header value must be scalar or null but %s provided.',
+ is_object($value) ? get_class($value) : gettype($value)
+ ));
+ }
+
+ return trim((string) $value, " \t");
+ }, $values);
+ }
+
+ private function assertHeader($header)
+ {
+ if (!is_string($header)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Header name must be a string but %s provided.',
+ is_object($header) ? get_class($header) : gettype($header)
+ ));
+ }
+
+ if ($header === '') {
+ throw new \InvalidArgumentException('Header name can not be empty.');
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/MultipartStream.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/MultipartStream.php
new file mode 100644
index 000000000..c0fd584f7
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/MultipartStream.php
@@ -0,0 +1,153 @@
+boundary = $boundary ?: sha1(uniqid('', true));
+ $this->stream = $this->createStream($elements);
+ }
+
+ /**
+ * Get the boundary
+ *
+ * @return string
+ */
+ public function getBoundary()
+ {
+ return $this->boundary;
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ /**
+ * Get the headers needed before transferring the content of a POST file
+ */
+ private function getHeaders(array $headers)
+ {
+ $str = '';
+ foreach ($headers as $key => $value) {
+ $str .= "{$key}: {$value}\r\n";
+ }
+
+ return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n";
+ }
+
+ /**
+ * Create the aggregate stream that will be used to upload the POST data
+ */
+ protected function createStream(array $elements)
+ {
+ $stream = new AppendStream();
+
+ foreach ($elements as $element) {
+ $this->addElement($stream, $element);
+ }
+
+ // Add the trailing boundary with CRLF
+ $stream->addStream(stream_for("--{$this->boundary}--\r\n"));
+
+ return $stream;
+ }
+
+ private function addElement(AppendStream $stream, array $element)
+ {
+ foreach (['contents', 'name'] as $key) {
+ if (!array_key_exists($key, $element)) {
+ throw new \InvalidArgumentException("A '{$key}' key is required");
+ }
+ }
+
+ $element['contents'] = stream_for($element['contents']);
+
+ if (empty($element['filename'])) {
+ $uri = $element['contents']->getMetadata('uri');
+ if (substr($uri, 0, 6) !== 'php://') {
+ $element['filename'] = $uri;
+ }
+ }
+
+ list($body, $headers) = $this->createElement(
+ $element['name'],
+ $element['contents'],
+ isset($element['filename']) ? $element['filename'] : null,
+ isset($element['headers']) ? $element['headers'] : []
+ );
+
+ $stream->addStream(stream_for($this->getHeaders($headers)));
+ $stream->addStream($body);
+ $stream->addStream(stream_for("\r\n"));
+ }
+
+ /**
+ * @return array
+ */
+ private function createElement($name, StreamInterface $stream, $filename, array $headers)
+ {
+ // Set a default content-disposition header if one was no provided
+ $disposition = $this->getHeader($headers, 'content-disposition');
+ if (!$disposition) {
+ $headers['Content-Disposition'] = ($filename === '0' || $filename)
+ ? sprintf('form-data; name="%s"; filename="%s"',
+ $name,
+ basename($filename))
+ : "form-data; name=\"{$name}\"";
+ }
+
+ // Set a default content-length header if one was no provided
+ $length = $this->getHeader($headers, 'content-length');
+ if (!$length) {
+ if ($length = $stream->getSize()) {
+ $headers['Content-Length'] = (string) $length;
+ }
+ }
+
+ // Set a default Content-Type if one was not supplied
+ $type = $this->getHeader($headers, 'content-type');
+ if (!$type && ($filename === '0' || $filename)) {
+ if ($type = mimetype_from_filename($filename)) {
+ $headers['Content-Type'] = $type;
+ }
+ }
+
+ return [$stream, $headers];
+ }
+
+ private function getHeader(array $headers, $key)
+ {
+ $lowercaseHeader = strtolower($key);
+ foreach ($headers as $k => $v) {
+ if (strtolower($k) === $lowercaseHeader) {
+ return $v;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/NoSeekStream.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/NoSeekStream.php
new file mode 100644
index 000000000..233221805
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/NoSeekStream.php
@@ -0,0 +1,22 @@
+source = $source;
+ $this->size = isset($options['size']) ? $options['size'] : null;
+ $this->metadata = isset($options['metadata']) ? $options['metadata'] : [];
+ $this->buffer = new BufferStream();
+ }
+
+ public function __toString()
+ {
+ try {
+ return copy_to_string($this);
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ public function close()
+ {
+ $this->detach();
+ }
+
+ public function detach()
+ {
+ $this->tellPos = false;
+ $this->source = null;
+ }
+
+ public function getSize()
+ {
+ return $this->size;
+ }
+
+ public function tell()
+ {
+ return $this->tellPos;
+ }
+
+ public function eof()
+ {
+ return !$this->source;
+ }
+
+ public function isSeekable()
+ {
+ return false;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ throw new \RuntimeException('Cannot seek a PumpStream');
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ public function write($string)
+ {
+ throw new \RuntimeException('Cannot write to a PumpStream');
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function read($length)
+ {
+ $data = $this->buffer->read($length);
+ $readLen = strlen($data);
+ $this->tellPos += $readLen;
+ $remaining = $length - $readLen;
+
+ if ($remaining) {
+ $this->pump($remaining);
+ $data .= $this->buffer->read($remaining);
+ $this->tellPos += strlen($data) - $readLen;
+ }
+
+ return $data;
+ }
+
+ public function getContents()
+ {
+ $result = '';
+ while (!$this->eof()) {
+ $result .= $this->read(1000000);
+ }
+
+ return $result;
+ }
+
+ public function getMetadata($key = null)
+ {
+ if (!$key) {
+ return $this->metadata;
+ }
+
+ return isset($this->metadata[$key]) ? $this->metadata[$key] : null;
+ }
+
+ private function pump($length)
+ {
+ if ($this->source) {
+ do {
+ $data = call_user_func($this->source, $length);
+ if ($data === false || $data === null) {
+ $this->source = null;
+ return;
+ }
+ $this->buffer->write($data);
+ $length -= strlen($data);
+ } while ($length > 0);
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/Request.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/Request.php
new file mode 100644
index 000000000..59f337db1
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/Request.php
@@ -0,0 +1,151 @@
+assertMethod($method);
+ if (!($uri instanceof UriInterface)) {
+ $uri = new Uri($uri);
+ }
+
+ $this->method = strtoupper($method);
+ $this->uri = $uri;
+ $this->setHeaders($headers);
+ $this->protocol = $version;
+
+ if (!isset($this->headerNames['host'])) {
+ $this->updateHostFromUri();
+ }
+
+ if ($body !== '' && $body !== null) {
+ $this->stream = stream_for($body);
+ }
+ }
+
+ public function getRequestTarget()
+ {
+ if ($this->requestTarget !== null) {
+ return $this->requestTarget;
+ }
+
+ $target = $this->uri->getPath();
+ if ($target == '') {
+ $target = '/';
+ }
+ if ($this->uri->getQuery() != '') {
+ $target .= '?' . $this->uri->getQuery();
+ }
+
+ return $target;
+ }
+
+ public function withRequestTarget($requestTarget)
+ {
+ if (preg_match('#\s#', $requestTarget)) {
+ throw new InvalidArgumentException(
+ 'Invalid request target provided; cannot contain whitespace'
+ );
+ }
+
+ $new = clone $this;
+ $new->requestTarget = $requestTarget;
+ return $new;
+ }
+
+ public function getMethod()
+ {
+ return $this->method;
+ }
+
+ public function withMethod($method)
+ {
+ $this->assertMethod($method);
+ $new = clone $this;
+ $new->method = strtoupper($method);
+ return $new;
+ }
+
+ public function getUri()
+ {
+ return $this->uri;
+ }
+
+ public function withUri(UriInterface $uri, $preserveHost = false)
+ {
+ if ($uri === $this->uri) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->uri = $uri;
+
+ if (!$preserveHost || !isset($this->headerNames['host'])) {
+ $new->updateHostFromUri();
+ }
+
+ return $new;
+ }
+
+ private function updateHostFromUri()
+ {
+ $host = $this->uri->getHost();
+
+ if ($host == '') {
+ return;
+ }
+
+ if (($port = $this->uri->getPort()) !== null) {
+ $host .= ':' . $port;
+ }
+
+ if (isset($this->headerNames['host'])) {
+ $header = $this->headerNames['host'];
+ } else {
+ $header = 'Host';
+ $this->headerNames['host'] = 'Host';
+ }
+ // Ensure Host is the first header.
+ // See: http://tools.ietf.org/html/rfc7230#section-5.4
+ $this->headers = [$header => [$host]] + $this->headers;
+ }
+
+ private function assertMethod($method)
+ {
+ if (!is_string($method) || $method === '') {
+ throw new \InvalidArgumentException('Method must be a non-empty string.');
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/Response.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/Response.php
new file mode 100644
index 000000000..e7e04d86a
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/Response.php
@@ -0,0 +1,154 @@
+ 'Continue',
+ 101 => 'Switching Protocols',
+ 102 => 'Processing',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-status',
+ 208 => 'Already Reported',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 306 => 'Switch Proxy',
+ 307 => 'Temporary Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Time-out',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Large',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested range not satisfiable',
+ 417 => 'Expectation Failed',
+ 418 => 'I\'m a teapot',
+ 422 => 'Unprocessable Entity',
+ 423 => 'Locked',
+ 424 => 'Failed Dependency',
+ 425 => 'Unordered Collection',
+ 426 => 'Upgrade Required',
+ 428 => 'Precondition Required',
+ 429 => 'Too Many Requests',
+ 431 => 'Request Header Fields Too Large',
+ 451 => 'Unavailable For Legal Reasons',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Time-out',
+ 505 => 'HTTP Version not supported',
+ 506 => 'Variant Also Negotiates',
+ 507 => 'Insufficient Storage',
+ 508 => 'Loop Detected',
+ 511 => 'Network Authentication Required',
+ ];
+
+ /** @var string */
+ private $reasonPhrase = '';
+
+ /** @var int */
+ private $statusCode = 200;
+
+ /**
+ * @param int $status Status code
+ * @param array $headers Response headers
+ * @param string|null|resource|StreamInterface $body Response body
+ * @param string $version Protocol version
+ * @param string|null $reason Reason phrase (when empty a default will be used based on the status code)
+ */
+ public function __construct(
+ $status = 200,
+ array $headers = [],
+ $body = null,
+ $version = '1.1',
+ $reason = null
+ ) {
+ $this->assertStatusCodeIsInteger($status);
+ $status = (int) $status;
+ $this->assertStatusCodeRange($status);
+
+ $this->statusCode = $status;
+
+ if ($body !== '' && $body !== null) {
+ $this->stream = stream_for($body);
+ }
+
+ $this->setHeaders($headers);
+ if ($reason == '' && isset(self::$phrases[$this->statusCode])) {
+ $this->reasonPhrase = self::$phrases[$this->statusCode];
+ } else {
+ $this->reasonPhrase = (string) $reason;
+ }
+
+ $this->protocol = $version;
+ }
+
+ public function getStatusCode()
+ {
+ return $this->statusCode;
+ }
+
+ public function getReasonPhrase()
+ {
+ return $this->reasonPhrase;
+ }
+
+ public function withStatus($code, $reasonPhrase = '')
+ {
+ $this->assertStatusCodeIsInteger($code);
+ $code = (int) $code;
+ $this->assertStatusCodeRange($code);
+
+ $new = clone $this;
+ $new->statusCode = $code;
+ if ($reasonPhrase == '' && isset(self::$phrases[$new->statusCode])) {
+ $reasonPhrase = self::$phrases[$new->statusCode];
+ }
+ $new->reasonPhrase = $reasonPhrase;
+ return $new;
+ }
+
+ private function assertStatusCodeIsInteger($statusCode)
+ {
+ if (filter_var($statusCode, FILTER_VALIDATE_INT) === false) {
+ throw new \InvalidArgumentException('Status code must be an integer value.');
+ }
+ }
+
+ private function assertStatusCodeRange($statusCode)
+ {
+ if ($statusCode < 100 || $statusCode >= 600) {
+ throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.');
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/Rfc7230.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/Rfc7230.php
new file mode 100644
index 000000000..505e4742b
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/Rfc7230.php
@@ -0,0 +1,18 @@
+@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m";
+ const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)";
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/ServerRequest.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/ServerRequest.php
new file mode 100644
index 000000000..1a09a6c87
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/ServerRequest.php
@@ -0,0 +1,376 @@
+serverParams = $serverParams;
+
+ parent::__construct($method, $uri, $headers, $body, $version);
+ }
+
+ /**
+ * Return an UploadedFile instance array.
+ *
+ * @param array $files A array which respect $_FILES structure
+ * @throws InvalidArgumentException for unrecognized values
+ * @return array
+ */
+ public static function normalizeFiles(array $files)
+ {
+ $normalized = [];
+
+ foreach ($files as $key => $value) {
+ if ($value instanceof UploadedFileInterface) {
+ $normalized[$key] = $value;
+ } elseif (is_array($value) && isset($value['tmp_name'])) {
+ $normalized[$key] = self::createUploadedFileFromSpec($value);
+ } elseif (is_array($value)) {
+ $normalized[$key] = self::normalizeFiles($value);
+ continue;
+ } else {
+ throw new InvalidArgumentException('Invalid value in files specification');
+ }
+ }
+
+ return $normalized;
+ }
+
+ /**
+ * Create and return an UploadedFile instance from a $_FILES specification.
+ *
+ * If the specification represents an array of values, this method will
+ * delegate to normalizeNestedFileSpec() and return that return value.
+ *
+ * @param array $value $_FILES struct
+ * @return array|UploadedFileInterface
+ */
+ private static function createUploadedFileFromSpec(array $value)
+ {
+ if (is_array($value['tmp_name'])) {
+ return self::normalizeNestedFileSpec($value);
+ }
+
+ return new UploadedFile(
+ $value['tmp_name'],
+ (int) $value['size'],
+ (int) $value['error'],
+ $value['name'],
+ $value['type']
+ );
+ }
+
+ /**
+ * Normalize an array of file specifications.
+ *
+ * Loops through all nested files and returns a normalized array of
+ * UploadedFileInterface instances.
+ *
+ * @param array $files
+ * @return UploadedFileInterface[]
+ */
+ private static function normalizeNestedFileSpec(array $files = [])
+ {
+ $normalizedFiles = [];
+
+ foreach (array_keys($files['tmp_name']) as $key) {
+ $spec = [
+ 'tmp_name' => $files['tmp_name'][$key],
+ 'size' => $files['size'][$key],
+ 'error' => $files['error'][$key],
+ 'name' => $files['name'][$key],
+ 'type' => $files['type'][$key],
+ ];
+ $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec);
+ }
+
+ return $normalizedFiles;
+ }
+
+ /**
+ * Return a ServerRequest populated with superglobals:
+ * $_GET
+ * $_POST
+ * $_COOKIE
+ * $_FILES
+ * $_SERVER
+ *
+ * @return ServerRequestInterface
+ */
+ public static function fromGlobals()
+ {
+ $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
+ $headers = getallheaders();
+ $uri = self::getUriFromGlobals();
+ $body = new CachingStream(new LazyOpenStream('php://input', 'r+'));
+ $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1';
+
+ $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER);
+
+ return $serverRequest
+ ->withCookieParams($_COOKIE)
+ ->withQueryParams($_GET)
+ ->withParsedBody($_POST)
+ ->withUploadedFiles(self::normalizeFiles($_FILES));
+ }
+
+ private static function extractHostAndPortFromAuthority($authority)
+ {
+ $uri = 'http://'.$authority;
+ $parts = parse_url($uri);
+ if (false === $parts) {
+ return [null, null];
+ }
+
+ $host = isset($parts['host']) ? $parts['host'] : null;
+ $port = isset($parts['port']) ? $parts['port'] : null;
+
+ return [$host, $port];
+ }
+
+ /**
+ * Get a Uri populated with values from $_SERVER.
+ *
+ * @return UriInterface
+ */
+ public static function getUriFromGlobals()
+ {
+ $uri = new Uri('');
+
+ $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http');
+
+ $hasPort = false;
+ if (isset($_SERVER['HTTP_HOST'])) {
+ list($host, $port) = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']);
+ if ($host !== null) {
+ $uri = $uri->withHost($host);
+ }
+
+ if ($port !== null) {
+ $hasPort = true;
+ $uri = $uri->withPort($port);
+ }
+ } elseif (isset($_SERVER['SERVER_NAME'])) {
+ $uri = $uri->withHost($_SERVER['SERVER_NAME']);
+ } elseif (isset($_SERVER['SERVER_ADDR'])) {
+ $uri = $uri->withHost($_SERVER['SERVER_ADDR']);
+ }
+
+ if (!$hasPort && isset($_SERVER['SERVER_PORT'])) {
+ $uri = $uri->withPort($_SERVER['SERVER_PORT']);
+ }
+
+ $hasQuery = false;
+ if (isset($_SERVER['REQUEST_URI'])) {
+ $requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2);
+ $uri = $uri->withPath($requestUriParts[0]);
+ if (isset($requestUriParts[1])) {
+ $hasQuery = true;
+ $uri = $uri->withQuery($requestUriParts[1]);
+ }
+ }
+
+ if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) {
+ $uri = $uri->withQuery($_SERVER['QUERY_STRING']);
+ }
+
+ return $uri;
+ }
+
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getServerParams()
+ {
+ return $this->serverParams;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getUploadedFiles()
+ {
+ return $this->uploadedFiles;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withUploadedFiles(array $uploadedFiles)
+ {
+ $new = clone $this;
+ $new->uploadedFiles = $uploadedFiles;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCookieParams()
+ {
+ return $this->cookieParams;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withCookieParams(array $cookies)
+ {
+ $new = clone $this;
+ $new->cookieParams = $cookies;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getQueryParams()
+ {
+ return $this->queryParams;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withQueryParams(array $query)
+ {
+ $new = clone $this;
+ $new->queryParams = $query;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getParsedBody()
+ {
+ return $this->parsedBody;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withParsedBody($data)
+ {
+ $new = clone $this;
+ $new->parsedBody = $data;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAttribute($attribute, $default = null)
+ {
+ if (false === array_key_exists($attribute, $this->attributes)) {
+ return $default;
+ }
+
+ return $this->attributes[$attribute];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withAttribute($attribute, $value)
+ {
+ $new = clone $this;
+ $new->attributes[$attribute] = $value;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withoutAttribute($attribute)
+ {
+ if (false === array_key_exists($attribute, $this->attributes)) {
+ return $this;
+ }
+
+ $new = clone $this;
+ unset($new->attributes[$attribute]);
+
+ return $new;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/Stream.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/Stream.php
new file mode 100644
index 000000000..d9e7409c7
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/Stream.php
@@ -0,0 +1,267 @@
+size = $options['size'];
+ }
+
+ $this->customMetadata = isset($options['metadata'])
+ ? $options['metadata']
+ : [];
+
+ $this->stream = $stream;
+ $meta = stream_get_meta_data($this->stream);
+ $this->seekable = $meta['seekable'];
+ $this->readable = (bool)preg_match(self::READABLE_MODES, $meta['mode']);
+ $this->writable = (bool)preg_match(self::WRITABLE_MODES, $meta['mode']);
+ $this->uri = $this->getMetadata('uri');
+ }
+
+ /**
+ * Closes the stream when the destructed
+ */
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ public function __toString()
+ {
+ try {
+ $this->seek(0);
+ return (string) stream_get_contents($this->stream);
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ public function getContents()
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+
+ $contents = stream_get_contents($this->stream);
+
+ if ($contents === false) {
+ throw new \RuntimeException('Unable to read stream contents');
+ }
+
+ return $contents;
+ }
+
+ public function close()
+ {
+ if (isset($this->stream)) {
+ if (is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ $this->detach();
+ }
+ }
+
+ public function detach()
+ {
+ if (!isset($this->stream)) {
+ return null;
+ }
+
+ $result = $this->stream;
+ unset($this->stream);
+ $this->size = $this->uri = null;
+ $this->readable = $this->writable = $this->seekable = false;
+
+ return $result;
+ }
+
+ public function getSize()
+ {
+ if ($this->size !== null) {
+ return $this->size;
+ }
+
+ if (!isset($this->stream)) {
+ return null;
+ }
+
+ // Clear the stat cache if the stream has a URI
+ if ($this->uri) {
+ clearstatcache(true, $this->uri);
+ }
+
+ $stats = fstat($this->stream);
+ if (isset($stats['size'])) {
+ $this->size = $stats['size'];
+ return $this->size;
+ }
+
+ return null;
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function isSeekable()
+ {
+ return $this->seekable;
+ }
+
+ public function eof()
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+
+ return feof($this->stream);
+ }
+
+ public function tell()
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+
+ $result = ftell($this->stream);
+
+ if ($result === false) {
+ throw new \RuntimeException('Unable to determine stream position');
+ }
+
+ return $result;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ $whence = (int) $whence;
+
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+ if (!$this->seekable) {
+ throw new \RuntimeException('Stream is not seekable');
+ }
+ if (fseek($this->stream, $offset, $whence) === -1) {
+ throw new \RuntimeException('Unable to seek to stream position '
+ . $offset . ' with whence ' . var_export($whence, true));
+ }
+ }
+
+ public function read($length)
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+ if (!$this->readable) {
+ throw new \RuntimeException('Cannot read from non-readable stream');
+ }
+ if ($length < 0) {
+ throw new \RuntimeException('Length parameter cannot be negative');
+ }
+
+ if (0 === $length) {
+ return '';
+ }
+
+ $string = fread($this->stream, $length);
+ if (false === $string) {
+ throw new \RuntimeException('Unable to read from stream');
+ }
+
+ return $string;
+ }
+
+ public function write($string)
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+ if (!$this->writable) {
+ throw new \RuntimeException('Cannot write to a non-writable stream');
+ }
+
+ // We can't know the size after writing anything
+ $this->size = null;
+ $result = fwrite($this->stream, $string);
+
+ if ($result === false) {
+ throw new \RuntimeException('Unable to write to stream');
+ }
+
+ return $result;
+ }
+
+ public function getMetadata($key = null)
+ {
+ if (!isset($this->stream)) {
+ return $key ? null : [];
+ } elseif (!$key) {
+ return $this->customMetadata + stream_get_meta_data($this->stream);
+ } elseif (isset($this->customMetadata[$key])) {
+ return $this->customMetadata[$key];
+ }
+
+ $meta = stream_get_meta_data($this->stream);
+
+ return isset($meta[$key]) ? $meta[$key] : null;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php
new file mode 100644
index 000000000..daec6f52e
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php
@@ -0,0 +1,149 @@
+stream = $stream;
+ }
+
+ /**
+ * Magic method used to create a new stream if streams are not added in
+ * the constructor of a decorator (e.g., LazyOpenStream).
+ *
+ * @param string $name Name of the property (allows "stream" only).
+ *
+ * @return StreamInterface
+ */
+ public function __get($name)
+ {
+ if ($name == 'stream') {
+ $this->stream = $this->createStream();
+ return $this->stream;
+ }
+
+ throw new \UnexpectedValueException("$name not found on class");
+ }
+
+ public function __toString()
+ {
+ try {
+ if ($this->isSeekable()) {
+ $this->seek(0);
+ }
+ return $this->getContents();
+ } catch (\Exception $e) {
+ // Really, PHP? https://bugs.php.net/bug.php?id=53648
+ trigger_error('StreamDecorator::__toString exception: '
+ . (string) $e, E_USER_ERROR);
+ return '';
+ }
+ }
+
+ public function getContents()
+ {
+ return copy_to_string($this);
+ }
+
+ /**
+ * Allow decorators to implement custom methods
+ *
+ * @param string $method Missing method name
+ * @param array $args Method arguments
+ *
+ * @return mixed
+ */
+ public function __call($method, array $args)
+ {
+ $result = call_user_func_array([$this->stream, $method], $args);
+
+ // Always return the wrapped object if the result is a return $this
+ return $result === $this->stream ? $this : $result;
+ }
+
+ public function close()
+ {
+ $this->stream->close();
+ }
+
+ public function getMetadata($key = null)
+ {
+ return $this->stream->getMetadata($key);
+ }
+
+ public function detach()
+ {
+ return $this->stream->detach();
+ }
+
+ public function getSize()
+ {
+ return $this->stream->getSize();
+ }
+
+ public function eof()
+ {
+ return $this->stream->eof();
+ }
+
+ public function tell()
+ {
+ return $this->stream->tell();
+ }
+
+ public function isReadable()
+ {
+ return $this->stream->isReadable();
+ }
+
+ public function isWritable()
+ {
+ return $this->stream->isWritable();
+ }
+
+ public function isSeekable()
+ {
+ return $this->stream->isSeekable();
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ $this->stream->seek($offset, $whence);
+ }
+
+ public function read($length)
+ {
+ return $this->stream->read($length);
+ }
+
+ public function write($string)
+ {
+ return $this->stream->write($string);
+ }
+
+ /**
+ * Implement in subclasses to dynamically create streams when requested.
+ *
+ * @return StreamInterface
+ * @throws \BadMethodCallException
+ */
+ protected function createStream()
+ {
+ throw new \BadMethodCallException('Not implemented');
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/StreamWrapper.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/StreamWrapper.php
new file mode 100644
index 000000000..0f3a2856a
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/StreamWrapper.php
@@ -0,0 +1,161 @@
+isReadable()) {
+ $mode = $stream->isWritable() ? 'r+' : 'r';
+ } elseif ($stream->isWritable()) {
+ $mode = 'w';
+ } else {
+ throw new \InvalidArgumentException('The stream must be readable, '
+ . 'writable, or both.');
+ }
+
+ return fopen('guzzle://stream', $mode, null, self::createStreamContext($stream));
+ }
+
+ /**
+ * Creates a stream context that can be used to open a stream as a php stream resource.
+ *
+ * @param StreamInterface $stream
+ *
+ * @return resource
+ */
+ public static function createStreamContext(StreamInterface $stream)
+ {
+ return stream_context_create([
+ 'guzzle' => ['stream' => $stream]
+ ]);
+ }
+
+ /**
+ * Registers the stream wrapper if needed
+ */
+ public static function register()
+ {
+ if (!in_array('guzzle', stream_get_wrappers())) {
+ stream_wrapper_register('guzzle', __CLASS__);
+ }
+ }
+
+ public function stream_open($path, $mode, $options, &$opened_path)
+ {
+ $options = stream_context_get_options($this->context);
+
+ if (!isset($options['guzzle']['stream'])) {
+ return false;
+ }
+
+ $this->mode = $mode;
+ $this->stream = $options['guzzle']['stream'];
+
+ return true;
+ }
+
+ public function stream_read($count)
+ {
+ return $this->stream->read($count);
+ }
+
+ public function stream_write($data)
+ {
+ return (int) $this->stream->write($data);
+ }
+
+ public function stream_tell()
+ {
+ return $this->stream->tell();
+ }
+
+ public function stream_eof()
+ {
+ return $this->stream->eof();
+ }
+
+ public function stream_seek($offset, $whence)
+ {
+ $this->stream->seek($offset, $whence);
+
+ return true;
+ }
+
+ public function stream_cast($cast_as)
+ {
+ $stream = clone($this->stream);
+
+ return $stream->detach();
+ }
+
+ public function stream_stat()
+ {
+ static $modeMap = [
+ 'r' => 33060,
+ 'rb' => 33060,
+ 'r+' => 33206,
+ 'w' => 33188,
+ 'wb' => 33188
+ ];
+
+ return [
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => $modeMap[$this->mode],
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => $this->stream->getSize() ?: 0,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0
+ ];
+ }
+
+ public function url_stat($path, $flags)
+ {
+ return [
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => 0,
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => 0,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/UploadedFile.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/UploadedFile.php
new file mode 100644
index 000000000..e62bd5c80
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/UploadedFile.php
@@ -0,0 +1,316 @@
+setError($errorStatus);
+ $this->setSize($size);
+ $this->setClientFilename($clientFilename);
+ $this->setClientMediaType($clientMediaType);
+
+ if ($this->isOk()) {
+ $this->setStreamOrFile($streamOrFile);
+ }
+ }
+
+ /**
+ * Depending on the value set file or stream variable
+ *
+ * @param mixed $streamOrFile
+ * @throws InvalidArgumentException
+ */
+ private function setStreamOrFile($streamOrFile)
+ {
+ if (is_string($streamOrFile)) {
+ $this->file = $streamOrFile;
+ } elseif (is_resource($streamOrFile)) {
+ $this->stream = new Stream($streamOrFile);
+ } elseif ($streamOrFile instanceof StreamInterface) {
+ $this->stream = $streamOrFile;
+ } else {
+ throw new InvalidArgumentException(
+ 'Invalid stream or file provided for UploadedFile'
+ );
+ }
+ }
+
+ /**
+ * @param int $error
+ * @throws InvalidArgumentException
+ */
+ private function setError($error)
+ {
+ if (false === is_int($error)) {
+ throw new InvalidArgumentException(
+ 'Upload file error status must be an integer'
+ );
+ }
+
+ if (false === in_array($error, UploadedFile::$errors)) {
+ throw new InvalidArgumentException(
+ 'Invalid error status for UploadedFile'
+ );
+ }
+
+ $this->error = $error;
+ }
+
+ /**
+ * @param int $size
+ * @throws InvalidArgumentException
+ */
+ private function setSize($size)
+ {
+ if (false === is_int($size)) {
+ throw new InvalidArgumentException(
+ 'Upload file size must be an integer'
+ );
+ }
+
+ $this->size = $size;
+ }
+
+ /**
+ * @param mixed $param
+ * @return boolean
+ */
+ private function isStringOrNull($param)
+ {
+ return in_array(gettype($param), ['string', 'NULL']);
+ }
+
+ /**
+ * @param mixed $param
+ * @return boolean
+ */
+ private function isStringNotEmpty($param)
+ {
+ return is_string($param) && false === empty($param);
+ }
+
+ /**
+ * @param string|null $clientFilename
+ * @throws InvalidArgumentException
+ */
+ private function setClientFilename($clientFilename)
+ {
+ if (false === $this->isStringOrNull($clientFilename)) {
+ throw new InvalidArgumentException(
+ 'Upload file client filename must be a string or null'
+ );
+ }
+
+ $this->clientFilename = $clientFilename;
+ }
+
+ /**
+ * @param string|null $clientMediaType
+ * @throws InvalidArgumentException
+ */
+ private function setClientMediaType($clientMediaType)
+ {
+ if (false === $this->isStringOrNull($clientMediaType)) {
+ throw new InvalidArgumentException(
+ 'Upload file client media type must be a string or null'
+ );
+ }
+
+ $this->clientMediaType = $clientMediaType;
+ }
+
+ /**
+ * Return true if there is no upload error
+ *
+ * @return boolean
+ */
+ private function isOk()
+ {
+ return $this->error === UPLOAD_ERR_OK;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isMoved()
+ {
+ return $this->moved;
+ }
+
+ /**
+ * @throws RuntimeException if is moved or not ok
+ */
+ private function validateActive()
+ {
+ if (false === $this->isOk()) {
+ throw new RuntimeException('Cannot retrieve stream due to upload error');
+ }
+
+ if ($this->isMoved()) {
+ throw new RuntimeException('Cannot retrieve stream after it has already been moved');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ * @throws RuntimeException if the upload was not successful.
+ */
+ public function getStream()
+ {
+ $this->validateActive();
+
+ if ($this->stream instanceof StreamInterface) {
+ return $this->stream;
+ }
+
+ return new LazyOpenStream($this->file, 'r+');
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see http://php.net/is_uploaded_file
+ * @see http://php.net/move_uploaded_file
+ * @param string $targetPath Path to which to move the uploaded file.
+ * @throws RuntimeException if the upload was not successful.
+ * @throws InvalidArgumentException if the $path specified is invalid.
+ * @throws RuntimeException on any error during the move operation, or on
+ * the second or subsequent call to the method.
+ */
+ public function moveTo($targetPath)
+ {
+ $this->validateActive();
+
+ if (false === $this->isStringNotEmpty($targetPath)) {
+ throw new InvalidArgumentException(
+ 'Invalid path provided for move operation; must be a non-empty string'
+ );
+ }
+
+ if ($this->file) {
+ $this->moved = php_sapi_name() == 'cli'
+ ? rename($this->file, $targetPath)
+ : move_uploaded_file($this->file, $targetPath);
+ } else {
+ copy_to_stream(
+ $this->getStream(),
+ new LazyOpenStream($targetPath, 'w')
+ );
+
+ $this->moved = true;
+ }
+
+ if (false === $this->moved) {
+ throw new RuntimeException(
+ sprintf('Uploaded file could not be moved to %s', $targetPath)
+ );
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return int|null The file size in bytes or null if unknown.
+ */
+ public function getSize()
+ {
+ return $this->size;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see http://php.net/manual/en/features.file-upload.errors.php
+ * @return int One of PHP's UPLOAD_ERR_XXX constants.
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return string|null The filename sent by the client or null if none
+ * was provided.
+ */
+ public function getClientFilename()
+ {
+ return $this->clientFilename;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getClientMediaType()
+ {
+ return $this->clientMediaType;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/Uri.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/Uri.php
new file mode 100644
index 000000000..825a25eed
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/Uri.php
@@ -0,0 +1,760 @@
+ 80,
+ 'https' => 443,
+ 'ftp' => 21,
+ 'gopher' => 70,
+ 'nntp' => 119,
+ 'news' => 119,
+ 'telnet' => 23,
+ 'tn3270' => 23,
+ 'imap' => 143,
+ 'pop' => 110,
+ 'ldap' => 389,
+ ];
+
+ private static $charUnreserved = 'a-zA-Z0-9_\-\.~';
+ private static $charSubDelims = '!\$&\'\(\)\*\+,;=';
+ private static $replaceQuery = ['=' => '%3D', '&' => '%26'];
+
+ /** @var string Uri scheme. */
+ private $scheme = '';
+
+ /** @var string Uri user info. */
+ private $userInfo = '';
+
+ /** @var string Uri host. */
+ private $host = '';
+
+ /** @var int|null Uri port. */
+ private $port;
+
+ /** @var string Uri path. */
+ private $path = '';
+
+ /** @var string Uri query string. */
+ private $query = '';
+
+ /** @var string Uri fragment. */
+ private $fragment = '';
+
+ /**
+ * @param string $uri URI to parse
+ */
+ public function __construct($uri = '')
+ {
+ // weak type check to also accept null until we can add scalar type hints
+ if ($uri != '') {
+ $parts = parse_url($uri);
+ if ($parts === false) {
+ throw new \InvalidArgumentException("Unable to parse URI: $uri");
+ }
+ $this->applyParts($parts);
+ }
+ }
+
+ public function __toString()
+ {
+ return self::composeComponents(
+ $this->scheme,
+ $this->getAuthority(),
+ $this->path,
+ $this->query,
+ $this->fragment
+ );
+ }
+
+ /**
+ * Composes a URI reference string from its various components.
+ *
+ * Usually this method does not need to be called manually but instead is used indirectly via
+ * `Psr\Http\Message\UriInterface::__toString`.
+ *
+ * PSR-7 UriInterface treats an empty component the same as a missing component as
+ * getQuery(), getFragment() etc. always return a string. This explains the slight
+ * difference to RFC 3986 Section 5.3.
+ *
+ * Another adjustment is that the authority separator is added even when the authority is missing/empty
+ * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with
+ * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But
+ * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to
+ * that format).
+ *
+ * @param string $scheme
+ * @param string $authority
+ * @param string $path
+ * @param string $query
+ * @param string $fragment
+ *
+ * @return string
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-5.3
+ */
+ public static function composeComponents($scheme, $authority, $path, $query, $fragment)
+ {
+ $uri = '';
+
+ // weak type checks to also accept null until we can add scalar type hints
+ if ($scheme != '') {
+ $uri .= $scheme . ':';
+ }
+
+ if ($authority != ''|| $scheme === 'file') {
+ $uri .= '//' . $authority;
+ }
+
+ $uri .= $path;
+
+ if ($query != '') {
+ $uri .= '?' . $query;
+ }
+
+ if ($fragment != '') {
+ $uri .= '#' . $fragment;
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Whether the URI has the default port of the current scheme.
+ *
+ * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used
+ * independently of the implementation.
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ */
+ public static function isDefaultPort(UriInterface $uri)
+ {
+ return $uri->getPort() === null
+ || (isset(self::$defaultPorts[$uri->getScheme()]) && $uri->getPort() === self::$defaultPorts[$uri->getScheme()]);
+ }
+
+ /**
+ * Whether the URI is absolute, i.e. it has a scheme.
+ *
+ * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true
+ * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative
+ * to another URI, the base URI. Relative references can be divided into several forms:
+ * - network-path references, e.g. '//example.com/path'
+ * - absolute-path references, e.g. '/path'
+ * - relative-path references, e.g. 'subpath'
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ * @see Uri::isNetworkPathReference
+ * @see Uri::isAbsolutePathReference
+ * @see Uri::isRelativePathReference
+ * @link https://tools.ietf.org/html/rfc3986#section-4
+ */
+ public static function isAbsolute(UriInterface $uri)
+ {
+ return $uri->getScheme() !== '';
+ }
+
+ /**
+ * Whether the URI is a network-path reference.
+ *
+ * A relative reference that begins with two slash characters is termed an network-path reference.
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.2
+ */
+ public static function isNetworkPathReference(UriInterface $uri)
+ {
+ return $uri->getScheme() === '' && $uri->getAuthority() !== '';
+ }
+
+ /**
+ * Whether the URI is a absolute-path reference.
+ *
+ * A relative reference that begins with a single slash character is termed an absolute-path reference.
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.2
+ */
+ public static function isAbsolutePathReference(UriInterface $uri)
+ {
+ return $uri->getScheme() === ''
+ && $uri->getAuthority() === ''
+ && isset($uri->getPath()[0])
+ && $uri->getPath()[0] === '/';
+ }
+
+ /**
+ * Whether the URI is a relative-path reference.
+ *
+ * A relative reference that does not begin with a slash character is termed a relative-path reference.
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.2
+ */
+ public static function isRelativePathReference(UriInterface $uri)
+ {
+ return $uri->getScheme() === ''
+ && $uri->getAuthority() === ''
+ && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/');
+ }
+
+ /**
+ * Whether the URI is a same-document reference.
+ *
+ * A same-document reference refers to a URI that is, aside from its fragment
+ * component, identical to the base URI. When no base URI is given, only an empty
+ * URI reference (apart from its fragment) is considered a same-document reference.
+ *
+ * @param UriInterface $uri The URI to check
+ * @param UriInterface|null $base An optional base URI to compare against
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.4
+ */
+ public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null)
+ {
+ if ($base !== null) {
+ $uri = UriResolver::resolve($base, $uri);
+
+ return ($uri->getScheme() === $base->getScheme())
+ && ($uri->getAuthority() === $base->getAuthority())
+ && ($uri->getPath() === $base->getPath())
+ && ($uri->getQuery() === $base->getQuery());
+ }
+
+ return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === '';
+ }
+
+ /**
+ * Removes dot segments from a path and returns the new path.
+ *
+ * @param string $path
+ *
+ * @return string
+ *
+ * @deprecated since version 1.4. Use UriResolver::removeDotSegments instead.
+ * @see UriResolver::removeDotSegments
+ */
+ public static function removeDotSegments($path)
+ {
+ return UriResolver::removeDotSegments($path);
+ }
+
+ /**
+ * Converts the relative URI into a new URI that is resolved against the base URI.
+ *
+ * @param UriInterface $base Base URI
+ * @param string|UriInterface $rel Relative URI
+ *
+ * @return UriInterface
+ *
+ * @deprecated since version 1.4. Use UriResolver::resolve instead.
+ * @see UriResolver::resolve
+ */
+ public static function resolve(UriInterface $base, $rel)
+ {
+ if (!($rel instanceof UriInterface)) {
+ $rel = new self($rel);
+ }
+
+ return UriResolver::resolve($base, $rel);
+ }
+
+ /**
+ * Creates a new URI with a specific query string value removed.
+ *
+ * Any existing query string values that exactly match the provided key are
+ * removed.
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param string $key Query string key to remove.
+ *
+ * @return UriInterface
+ */
+ public static function withoutQueryValue(UriInterface $uri, $key)
+ {
+ $result = self::getFilteredQueryString($uri, [$key]);
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Creates a new URI with a specific query string value.
+ *
+ * Any existing query string values that exactly match the provided key are
+ * removed and replaced with the given key value pair.
+ *
+ * A value of null will set the query string key without a value, e.g. "key"
+ * instead of "key=value".
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param string $key Key to set.
+ * @param string|null $value Value to set
+ *
+ * @return UriInterface
+ */
+ public static function withQueryValue(UriInterface $uri, $key, $value)
+ {
+ $result = self::getFilteredQueryString($uri, [$key]);
+
+ $result[] = self::generateQueryString($key, $value);
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Creates a new URI with multiple specific query string values.
+ *
+ * It has the same behavior as withQueryValue() but for an associative array of key => value.
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param array $keyValueArray Associative array of key and values
+ *
+ * @return UriInterface
+ */
+ public static function withQueryValues(UriInterface $uri, array $keyValueArray)
+ {
+ $result = self::getFilteredQueryString($uri, array_keys($keyValueArray));
+
+ foreach ($keyValueArray as $key => $value) {
+ $result[] = self::generateQueryString($key, $value);
+ }
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Creates a URI from a hash of `parse_url` components.
+ *
+ * @param array $parts
+ *
+ * @return UriInterface
+ * @link http://php.net/manual/en/function.parse-url.php
+ *
+ * @throws \InvalidArgumentException If the components do not form a valid URI.
+ */
+ public static function fromParts(array $parts)
+ {
+ $uri = new self();
+ $uri->applyParts($parts);
+ $uri->validateState();
+
+ return $uri;
+ }
+
+ public function getScheme()
+ {
+ return $this->scheme;
+ }
+
+ public function getAuthority()
+ {
+ $authority = $this->host;
+ if ($this->userInfo !== '') {
+ $authority = $this->userInfo . '@' . $authority;
+ }
+
+ if ($this->port !== null) {
+ $authority .= ':' . $this->port;
+ }
+
+ return $authority;
+ }
+
+ public function getUserInfo()
+ {
+ return $this->userInfo;
+ }
+
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ public function getPort()
+ {
+ return $this->port;
+ }
+
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ public function getQuery()
+ {
+ return $this->query;
+ }
+
+ public function getFragment()
+ {
+ return $this->fragment;
+ }
+
+ public function withScheme($scheme)
+ {
+ $scheme = $this->filterScheme($scheme);
+
+ if ($this->scheme === $scheme) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->scheme = $scheme;
+ $new->removeDefaultPort();
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withUserInfo($user, $password = null)
+ {
+ $info = $this->filterUserInfoComponent($user);
+ if ($password !== null) {
+ $info .= ':' . $this->filterUserInfoComponent($password);
+ }
+
+ if ($this->userInfo === $info) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->userInfo = $info;
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withHost($host)
+ {
+ $host = $this->filterHost($host);
+
+ if ($this->host === $host) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->host = $host;
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withPort($port)
+ {
+ $port = $this->filterPort($port);
+
+ if ($this->port === $port) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->port = $port;
+ $new->removeDefaultPort();
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withPath($path)
+ {
+ $path = $this->filterPath($path);
+
+ if ($this->path === $path) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->path = $path;
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withQuery($query)
+ {
+ $query = $this->filterQueryAndFragment($query);
+
+ if ($this->query === $query) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->query = $query;
+
+ return $new;
+ }
+
+ public function withFragment($fragment)
+ {
+ $fragment = $this->filterQueryAndFragment($fragment);
+
+ if ($this->fragment === $fragment) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->fragment = $fragment;
+
+ return $new;
+ }
+
+ /**
+ * Apply parse_url parts to a URI.
+ *
+ * @param array $parts Array of parse_url parts to apply.
+ */
+ private function applyParts(array $parts)
+ {
+ $this->scheme = isset($parts['scheme'])
+ ? $this->filterScheme($parts['scheme'])
+ : '';
+ $this->userInfo = isset($parts['user'])
+ ? $this->filterUserInfoComponent($parts['user'])
+ : '';
+ $this->host = isset($parts['host'])
+ ? $this->filterHost($parts['host'])
+ : '';
+ $this->port = isset($parts['port'])
+ ? $this->filterPort($parts['port'])
+ : null;
+ $this->path = isset($parts['path'])
+ ? $this->filterPath($parts['path'])
+ : '';
+ $this->query = isset($parts['query'])
+ ? $this->filterQueryAndFragment($parts['query'])
+ : '';
+ $this->fragment = isset($parts['fragment'])
+ ? $this->filterQueryAndFragment($parts['fragment'])
+ : '';
+ if (isset($parts['pass'])) {
+ $this->userInfo .= ':' . $this->filterUserInfoComponent($parts['pass']);
+ }
+
+ $this->removeDefaultPort();
+ }
+
+ /**
+ * @param string $scheme
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the scheme is invalid.
+ */
+ private function filterScheme($scheme)
+ {
+ if (!is_string($scheme)) {
+ throw new \InvalidArgumentException('Scheme must be a string');
+ }
+
+ return strtolower($scheme);
+ }
+
+ /**
+ * @param string $component
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the user info is invalid.
+ */
+ private function filterUserInfoComponent($component)
+ {
+ if (!is_string($component)) {
+ throw new \InvalidArgumentException('User info must be a string');
+ }
+
+ return preg_replace_callback(
+ '/(?:[^%' . self::$charUnreserved . self::$charSubDelims . ']+|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $component
+ );
+ }
+
+ /**
+ * @param string $host
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the host is invalid.
+ */
+ private function filterHost($host)
+ {
+ if (!is_string($host)) {
+ throw new \InvalidArgumentException('Host must be a string');
+ }
+
+ return strtolower($host);
+ }
+
+ /**
+ * @param int|null $port
+ *
+ * @return int|null
+ *
+ * @throws \InvalidArgumentException If the port is invalid.
+ */
+ private function filterPort($port)
+ {
+ if ($port === null) {
+ return null;
+ }
+
+ $port = (int) $port;
+ if (0 > $port || 0xffff < $port) {
+ throw new \InvalidArgumentException(
+ sprintf('Invalid port: %d. Must be between 0 and 65535', $port)
+ );
+ }
+
+ return $port;
+ }
+
+ /**
+ * @param UriInterface $uri
+ * @param array $keys
+ *
+ * @return array
+ */
+ private static function getFilteredQueryString(UriInterface $uri, array $keys)
+ {
+ $current = $uri->getQuery();
+
+ if ($current === '') {
+ return [];
+ }
+
+ $decodedKeys = array_map('rawurldecode', $keys);
+
+ return array_filter(explode('&', $current), function ($part) use ($decodedKeys) {
+ return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true);
+ });
+ }
+
+ /**
+ * @param string $key
+ * @param string|null $value
+ *
+ * @return string
+ */
+ private static function generateQueryString($key, $value)
+ {
+ // Query string separators ("=", "&") within the key or value need to be encoded
+ // (while preventing double-encoding) before setting the query string. All other
+ // chars that need percent-encoding will be encoded by withQuery().
+ $queryString = strtr($key, self::$replaceQuery);
+
+ if ($value !== null) {
+ $queryString .= '=' . strtr($value, self::$replaceQuery);
+ }
+
+ return $queryString;
+ }
+
+ private function removeDefaultPort()
+ {
+ if ($this->port !== null && self::isDefaultPort($this)) {
+ $this->port = null;
+ }
+ }
+
+ /**
+ * Filters the path of a URI
+ *
+ * @param string $path
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the path is invalid.
+ */
+ private function filterPath($path)
+ {
+ if (!is_string($path)) {
+ throw new \InvalidArgumentException('Path must be a string');
+ }
+
+ return preg_replace_callback(
+ '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $path
+ );
+ }
+
+ /**
+ * Filters the query string or fragment of a URI.
+ *
+ * @param string $str
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the query or fragment is invalid.
+ */
+ private function filterQueryAndFragment($str)
+ {
+ if (!is_string($str)) {
+ throw new \InvalidArgumentException('Query and fragment must be a string');
+ }
+
+ return preg_replace_callback(
+ '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $str
+ );
+ }
+
+ private function rawurlencodeMatchZero(array $match)
+ {
+ return rawurlencode($match[0]);
+ }
+
+ private function validateState()
+ {
+ if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) {
+ $this->host = self::HTTP_DEFAULT_HOST;
+ }
+
+ if ($this->getAuthority() === '') {
+ if (0 === strpos($this->path, '//')) {
+ throw new \InvalidArgumentException('The path of a URI without an authority must not start with two slashes "//"');
+ }
+ if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) {
+ throw new \InvalidArgumentException('A relative URI must not have a path beginning with a segment containing a colon');
+ }
+ } elseif (isset($this->path[0]) && $this->path[0] !== '/') {
+ @trigger_error(
+ 'The path of a URI with an authority must start with a slash "/" or be empty. Automagically fixing the URI ' .
+ 'by adding a leading slash to the path is deprecated since version 1.4 and will throw an exception instead.',
+ E_USER_DEPRECATED
+ );
+ $this->path = '/'. $this->path;
+ //throw new \InvalidArgumentException('The path of a URI with an authority must start with a slash "/" or be empty');
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/UriNormalizer.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/UriNormalizer.php
new file mode 100644
index 000000000..384c29e50
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/UriNormalizer.php
@@ -0,0 +1,216 @@
+getPath() === '' &&
+ ($uri->getScheme() === 'http' || $uri->getScheme() === 'https')
+ ) {
+ $uri = $uri->withPath('/');
+ }
+
+ if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') {
+ $uri = $uri->withHost('');
+ }
+
+ if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) {
+ $uri = $uri->withPort(null);
+ }
+
+ if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) {
+ $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath()));
+ }
+
+ if ($flags & self::REMOVE_DUPLICATE_SLASHES) {
+ $uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath()));
+ }
+
+ if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') {
+ $queryKeyValues = explode('&', $uri->getQuery());
+ sort($queryKeyValues);
+ $uri = $uri->withQuery(implode('&', $queryKeyValues));
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Whether two URIs can be considered equivalent.
+ *
+ * Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also
+ * accepts relative URI references and returns true when they are equivalent. This of course assumes they will be
+ * resolved against the same base URI. If this is not the case, determination of equivalence or difference of
+ * relative references does not mean anything.
+ *
+ * @param UriInterface $uri1 An URI to compare
+ * @param UriInterface $uri2 An URI to compare
+ * @param int $normalizations A bitmask of normalizations to apply, see constants
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-6.1
+ */
+ public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS)
+ {
+ return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations);
+ }
+
+ private static function capitalizePercentEncoding(UriInterface $uri)
+ {
+ $regex = '/(?:%[A-Fa-f0-9]{2})++/';
+
+ $callback = function (array $match) {
+ return strtoupper($match[0]);
+ };
+
+ return
+ $uri->withPath(
+ preg_replace_callback($regex, $callback, $uri->getPath())
+ )->withQuery(
+ preg_replace_callback($regex, $callback, $uri->getQuery())
+ );
+ }
+
+ private static function decodeUnreservedCharacters(UriInterface $uri)
+ {
+ $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i';
+
+ $callback = function (array $match) {
+ return rawurldecode($match[0]);
+ };
+
+ return
+ $uri->withPath(
+ preg_replace_callback($regex, $callback, $uri->getPath())
+ )->withQuery(
+ preg_replace_callback($regex, $callback, $uri->getQuery())
+ );
+ }
+
+ private function __construct()
+ {
+ // cannot be instantiated
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/UriResolver.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/UriResolver.php
new file mode 100644
index 000000000..c1cb8a275
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/UriResolver.php
@@ -0,0 +1,219 @@
+getScheme() != '') {
+ return $rel->withPath(self::removeDotSegments($rel->getPath()));
+ }
+
+ if ($rel->getAuthority() != '') {
+ $targetAuthority = $rel->getAuthority();
+ $targetPath = self::removeDotSegments($rel->getPath());
+ $targetQuery = $rel->getQuery();
+ } else {
+ $targetAuthority = $base->getAuthority();
+ if ($rel->getPath() === '') {
+ $targetPath = $base->getPath();
+ $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery();
+ } else {
+ if ($rel->getPath()[0] === '/') {
+ $targetPath = $rel->getPath();
+ } else {
+ if ($targetAuthority != '' && $base->getPath() === '') {
+ $targetPath = '/' . $rel->getPath();
+ } else {
+ $lastSlashPos = strrpos($base->getPath(), '/');
+ if ($lastSlashPos === false) {
+ $targetPath = $rel->getPath();
+ } else {
+ $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath();
+ }
+ }
+ }
+ $targetPath = self::removeDotSegments($targetPath);
+ $targetQuery = $rel->getQuery();
+ }
+ }
+
+ return new Uri(Uri::composeComponents(
+ $base->getScheme(),
+ $targetAuthority,
+ $targetPath,
+ $targetQuery,
+ $rel->getFragment()
+ ));
+ }
+
+ /**
+ * Returns the target URI as a relative reference from the base URI.
+ *
+ * This method is the counterpart to resolve():
+ *
+ * (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
+ *
+ * One use-case is to use the current request URI as base URI and then generate relative links in your documents
+ * to reduce the document size or offer self-contained downloadable document archives.
+ *
+ * $base = new Uri('http://example.com/a/b/');
+ * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'.
+ * echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'.
+ * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
+ * echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
+ *
+ * This method also accepts a target that is already relative and will try to relativize it further. Only a
+ * relative-path reference will be returned as-is.
+ *
+ * echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well
+ *
+ * @param UriInterface $base Base URI
+ * @param UriInterface $target Target URI
+ *
+ * @return UriInterface The relative URI reference
+ */
+ public static function relativize(UriInterface $base, UriInterface $target)
+ {
+ if ($target->getScheme() !== '' &&
+ ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '')
+ ) {
+ return $target;
+ }
+
+ if (Uri::isRelativePathReference($target)) {
+ // As the target is already highly relative we return it as-is. It would be possible to resolve
+ // the target with `$target = self::resolve($base, $target);` and then try make it more relative
+ // by removing a duplicate query. But let's not do that automatically.
+ return $target;
+ }
+
+ if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) {
+ return $target->withScheme('');
+ }
+
+ // We must remove the path before removing the authority because if the path starts with two slashes, the URI
+ // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also
+ // invalid.
+ $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost('');
+
+ if ($base->getPath() !== $target->getPath()) {
+ return $emptyPathUri->withPath(self::getRelativePath($base, $target));
+ }
+
+ if ($base->getQuery() === $target->getQuery()) {
+ // Only the target fragment is left. And it must be returned even if base and target fragment are the same.
+ return $emptyPathUri->withQuery('');
+ }
+
+ // If the base URI has a query but the target has none, we cannot return an empty path reference as it would
+ // inherit the base query component when resolving.
+ if ($target->getQuery() === '') {
+ $segments = explode('/', $target->getPath());
+ $lastSegment = end($segments);
+
+ return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment);
+ }
+
+ return $emptyPathUri;
+ }
+
+ private static function getRelativePath(UriInterface $base, UriInterface $target)
+ {
+ $sourceSegments = explode('/', $base->getPath());
+ $targetSegments = explode('/', $target->getPath());
+ array_pop($sourceSegments);
+ $targetLastSegment = array_pop($targetSegments);
+ foreach ($sourceSegments as $i => $segment) {
+ if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) {
+ unset($sourceSegments[$i], $targetSegments[$i]);
+ } else {
+ break;
+ }
+ }
+ $targetSegments[] = $targetLastSegment;
+ $relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments);
+
+ // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./".
+ // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
+ // as the first segment of a relative-path reference, as it would be mistaken for a scheme name.
+ if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) {
+ $relativePath = "./$relativePath";
+ } elseif ('/' === $relativePath[0]) {
+ if ($base->getAuthority() != '' && $base->getPath() === '') {
+ // In this case an extra slash is added by resolve() automatically. So we must not add one here.
+ $relativePath = ".$relativePath";
+ } else {
+ $relativePath = "./$relativePath";
+ }
+ }
+
+ return $relativePath;
+ }
+
+ private function __construct()
+ {
+ // cannot be instantiated
+ }
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/functions.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/functions.php
new file mode 100644
index 000000000..8e6dafe68
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/functions.php
@@ -0,0 +1,899 @@
+getMethod() . ' '
+ . $message->getRequestTarget())
+ . ' HTTP/' . $message->getProtocolVersion();
+ if (!$message->hasHeader('host')) {
+ $msg .= "\r\nHost: " . $message->getUri()->getHost();
+ }
+ } elseif ($message instanceof ResponseInterface) {
+ $msg = 'HTTP/' . $message->getProtocolVersion() . ' '
+ . $message->getStatusCode() . ' '
+ . $message->getReasonPhrase();
+ } else {
+ throw new \InvalidArgumentException('Unknown message type');
+ }
+
+ foreach ($message->getHeaders() as $name => $values) {
+ $msg .= "\r\n{$name}: " . implode(', ', $values);
+ }
+
+ return "{$msg}\r\n\r\n" . $message->getBody();
+}
+
+/**
+ * Returns a UriInterface for the given value.
+ *
+ * This function accepts a string or {@see Psr\Http\Message\UriInterface} and
+ * returns a UriInterface for the given value. If the value is already a
+ * `UriInterface`, it is returned as-is.
+ *
+ * @param string|UriInterface $uri
+ *
+ * @return UriInterface
+ * @throws \InvalidArgumentException
+ */
+function uri_for($uri)
+{
+ if ($uri instanceof UriInterface) {
+ return $uri;
+ } elseif (is_string($uri)) {
+ return new Uri($uri);
+ }
+
+ throw new \InvalidArgumentException('URI must be a string or UriInterface');
+}
+
+/**
+ * Create a new stream based on the input type.
+ *
+ * Options is an associative array that can contain the following keys:
+ * - metadata: Array of custom metadata.
+ * - size: Size of the stream.
+ *
+ * @param resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource Entity body data
+ * @param array $options Additional options
+ *
+ * @return StreamInterface
+ * @throws \InvalidArgumentException if the $resource arg is not valid.
+ */
+function stream_for($resource = '', array $options = [])
+{
+ if (is_scalar($resource)) {
+ $stream = fopen('php://temp', 'r+');
+ if ($resource !== '') {
+ fwrite($stream, $resource);
+ fseek($stream, 0);
+ }
+ return new Stream($stream, $options);
+ }
+
+ switch (gettype($resource)) {
+ case 'resource':
+ return new Stream($resource, $options);
+ case 'object':
+ if ($resource instanceof StreamInterface) {
+ return $resource;
+ } elseif ($resource instanceof \Iterator) {
+ return new PumpStream(function () use ($resource) {
+ if (!$resource->valid()) {
+ return false;
+ }
+ $result = $resource->current();
+ $resource->next();
+ return $result;
+ }, $options);
+ } elseif (method_exists($resource, '__toString')) {
+ return stream_for((string) $resource, $options);
+ }
+ break;
+ case 'NULL':
+ return new Stream(fopen('php://temp', 'r+'), $options);
+ }
+
+ if (is_callable($resource)) {
+ return new PumpStream($resource, $options);
+ }
+
+ throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource));
+}
+
+/**
+ * Parse an array of header values containing ";" separated data into an
+ * array of associative arrays representing the header key value pair
+ * data of the header. When a parameter does not contain a value, but just
+ * contains a key, this function will inject a key with a '' string value.
+ *
+ * @param string|array $header Header to parse into components.
+ *
+ * @return array Returns the parsed header values.
+ */
+function parse_header($header)
+{
+ static $trimmed = "\"' \n\t\r";
+ $params = $matches = [];
+
+ foreach (normalize_header($header) as $val) {
+ $part = [];
+ foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) {
+ if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
+ $m = $matches[0];
+ if (isset($m[1])) {
+ $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed);
+ } else {
+ $part[] = trim($m[0], $trimmed);
+ }
+ }
+ }
+ if ($part) {
+ $params[] = $part;
+ }
+ }
+
+ return $params;
+}
+
+/**
+ * Converts an array of header values that may contain comma separated
+ * headers into an array of headers with no comma separated values.
+ *
+ * @param string|array $header Header to normalize.
+ *
+ * @return array Returns the normalized header field values.
+ */
+function normalize_header($header)
+{
+ if (!is_array($header)) {
+ return array_map('trim', explode(',', $header));
+ }
+
+ $result = [];
+ foreach ($header as $value) {
+ foreach ((array) $value as $v) {
+ if (strpos($v, ',') === false) {
+ $result[] = $v;
+ continue;
+ }
+ foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) {
+ $result[] = trim($vv);
+ }
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Clone and modify a request with the given changes.
+ *
+ * The changes can be one of:
+ * - method: (string) Changes the HTTP method.
+ * - set_headers: (array) Sets the given headers.
+ * - remove_headers: (array) Remove the given headers.
+ * - body: (mixed) Sets the given body.
+ * - uri: (UriInterface) Set the URI.
+ * - query: (string) Set the query string value of the URI.
+ * - version: (string) Set the protocol version.
+ *
+ * @param RequestInterface $request Request to clone and modify.
+ * @param array $changes Changes to apply.
+ *
+ * @return RequestInterface
+ */
+function modify_request(RequestInterface $request, array $changes)
+{
+ if (!$changes) {
+ return $request;
+ }
+
+ $headers = $request->getHeaders();
+
+ if (!isset($changes['uri'])) {
+ $uri = $request->getUri();
+ } else {
+ // Remove the host header if one is on the URI
+ if ($host = $changes['uri']->getHost()) {
+ $changes['set_headers']['Host'] = $host;
+
+ if ($port = $changes['uri']->getPort()) {
+ $standardPorts = ['http' => 80, 'https' => 443];
+ $scheme = $changes['uri']->getScheme();
+ if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) {
+ $changes['set_headers']['Host'] .= ':'.$port;
+ }
+ }
+ }
+ $uri = $changes['uri'];
+ }
+
+ if (!empty($changes['remove_headers'])) {
+ $headers = _caseless_remove($changes['remove_headers'], $headers);
+ }
+
+ if (!empty($changes['set_headers'])) {
+ $headers = _caseless_remove(array_keys($changes['set_headers']), $headers);
+ $headers = $changes['set_headers'] + $headers;
+ }
+
+ if (isset($changes['query'])) {
+ $uri = $uri->withQuery($changes['query']);
+ }
+
+ if ($request instanceof ServerRequestInterface) {
+ return (new ServerRequest(
+ isset($changes['method']) ? $changes['method'] : $request->getMethod(),
+ $uri,
+ $headers,
+ isset($changes['body']) ? $changes['body'] : $request->getBody(),
+ isset($changes['version'])
+ ? $changes['version']
+ : $request->getProtocolVersion(),
+ $request->getServerParams()
+ ))
+ ->withParsedBody($request->getParsedBody())
+ ->withQueryParams($request->getQueryParams())
+ ->withCookieParams($request->getCookieParams())
+ ->withUploadedFiles($request->getUploadedFiles());
+ }
+
+ return new Request(
+ isset($changes['method']) ? $changes['method'] : $request->getMethod(),
+ $uri,
+ $headers,
+ isset($changes['body']) ? $changes['body'] : $request->getBody(),
+ isset($changes['version'])
+ ? $changes['version']
+ : $request->getProtocolVersion()
+ );
+}
+
+/**
+ * Attempts to rewind a message body and throws an exception on failure.
+ *
+ * The body of the message will only be rewound if a call to `tell()` returns a
+ * value other than `0`.
+ *
+ * @param MessageInterface $message Message to rewind
+ *
+ * @throws \RuntimeException
+ */
+function rewind_body(MessageInterface $message)
+{
+ $body = $message->getBody();
+
+ if ($body->tell()) {
+ $body->rewind();
+ }
+}
+
+/**
+ * Safely opens a PHP stream resource using a filename.
+ *
+ * When fopen fails, PHP normally raises a warning. This function adds an
+ * error handler that checks for errors and throws an exception instead.
+ *
+ * @param string $filename File to open
+ * @param string $mode Mode used to open the file
+ *
+ * @return resource
+ * @throws \RuntimeException if the file cannot be opened
+ */
+function try_fopen($filename, $mode)
+{
+ $ex = null;
+ set_error_handler(function () use ($filename, $mode, &$ex) {
+ $ex = new \RuntimeException(sprintf(
+ 'Unable to open %s using mode %s: %s',
+ $filename,
+ $mode,
+ func_get_args()[1]
+ ));
+ });
+
+ $handle = fopen($filename, $mode);
+ restore_error_handler();
+
+ if ($ex) {
+ /** @var $ex \RuntimeException */
+ throw $ex;
+ }
+
+ return $handle;
+}
+
+/**
+ * Copy the contents of a stream into a string until the given number of
+ * bytes have been read.
+ *
+ * @param StreamInterface $stream Stream to read
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ * @return string
+ * @throws \RuntimeException on error.
+ */
+function copy_to_string(StreamInterface $stream, $maxLen = -1)
+{
+ $buffer = '';
+
+ if ($maxLen === -1) {
+ while (!$stream->eof()) {
+ $buf = $stream->read(1048576);
+ // Using a loose equality here to match on '' and false.
+ if ($buf == null) {
+ break;
+ }
+ $buffer .= $buf;
+ }
+ return $buffer;
+ }
+
+ $len = 0;
+ while (!$stream->eof() && $len < $maxLen) {
+ $buf = $stream->read($maxLen - $len);
+ // Using a loose equality here to match on '' and false.
+ if ($buf == null) {
+ break;
+ }
+ $buffer .= $buf;
+ $len = strlen($buffer);
+ }
+
+ return $buffer;
+}
+
+/**
+ * Copy the contents of a stream into another stream until the given number
+ * of bytes have been read.
+ *
+ * @param StreamInterface $source Stream to read from
+ * @param StreamInterface $dest Stream to write to
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ *
+ * @throws \RuntimeException on error.
+ */
+function copy_to_stream(
+ StreamInterface $source,
+ StreamInterface $dest,
+ $maxLen = -1
+) {
+ $bufferSize = 8192;
+
+ if ($maxLen === -1) {
+ while (!$source->eof()) {
+ if (!$dest->write($source->read($bufferSize))) {
+ break;
+ }
+ }
+ } else {
+ $remaining = $maxLen;
+ while ($remaining > 0 && !$source->eof()) {
+ $buf = $source->read(min($bufferSize, $remaining));
+ $len = strlen($buf);
+ if (!$len) {
+ break;
+ }
+ $remaining -= $len;
+ $dest->write($buf);
+ }
+ }
+}
+
+/**
+ * Calculate a hash of a Stream
+ *
+ * @param StreamInterface $stream Stream to calculate the hash for
+ * @param string $algo Hash algorithm (e.g. md5, crc32, etc)
+ * @param bool $rawOutput Whether or not to use raw output
+ *
+ * @return string Returns the hash of the stream
+ * @throws \RuntimeException on error.
+ */
+function hash(
+ StreamInterface $stream,
+ $algo,
+ $rawOutput = false
+) {
+ $pos = $stream->tell();
+
+ if ($pos > 0) {
+ $stream->rewind();
+ }
+
+ $ctx = hash_init($algo);
+ while (!$stream->eof()) {
+ hash_update($ctx, $stream->read(1048576));
+ }
+
+ $out = hash_final($ctx, (bool) $rawOutput);
+ $stream->seek($pos);
+
+ return $out;
+}
+
+/**
+ * Read a line from the stream up to the maximum allowed buffer length
+ *
+ * @param StreamInterface $stream Stream to read from
+ * @param int $maxLength Maximum buffer length
+ *
+ * @return string
+ */
+function readline(StreamInterface $stream, $maxLength = null)
+{
+ $buffer = '';
+ $size = 0;
+
+ while (!$stream->eof()) {
+ // Using a loose equality here to match on '' and false.
+ if (null == ($byte = $stream->read(1))) {
+ return $buffer;
+ }
+ $buffer .= $byte;
+ // Break when a new line is found or the max length - 1 is reached
+ if ($byte === "\n" || ++$size === $maxLength - 1) {
+ break;
+ }
+ }
+
+ return $buffer;
+}
+
+/**
+ * Parses a request message string into a request object.
+ *
+ * @param string $message Request message string.
+ *
+ * @return Request
+ */
+function parse_request($message)
+{
+ $data = _parse_message($message);
+ $matches = [];
+ if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
+ throw new \InvalidArgumentException('Invalid request string');
+ }
+ $parts = explode(' ', $data['start-line'], 3);
+ $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1';
+
+ $request = new Request(
+ $parts[0],
+ $matches[1] === '/' ? _parse_request_uri($parts[1], $data['headers']) : $parts[1],
+ $data['headers'],
+ $data['body'],
+ $version
+ );
+
+ return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]);
+}
+
+/**
+ * Parses a response message string into a response object.
+ *
+ * @param string $message Response message string.
+ *
+ * @return Response
+ */
+function parse_response($message)
+{
+ $data = _parse_message($message);
+ // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space
+ // between status-code and reason-phrase is required. But browsers accept
+ // responses without space and reason as well.
+ if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) {
+ throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']);
+ }
+ $parts = explode(' ', $data['start-line'], 3);
+
+ return new Response(
+ $parts[1],
+ $data['headers'],
+ $data['body'],
+ explode('/', $parts[0])[1],
+ isset($parts[2]) ? $parts[2] : null
+ );
+}
+
+/**
+ * Parse a query string into an associative array.
+ *
+ * If multiple values are found for the same key, the value of that key
+ * value pair will become an array. This function does not parse nested
+ * PHP style arrays into an associative array (e.g., foo[a]=1&foo[b]=2 will
+ * be parsed into ['foo[a]' => '1', 'foo[b]' => '2']).
+ *
+ * @param string $str Query string to parse
+ * @param int|bool $urlEncoding How the query string is encoded
+ *
+ * @return array
+ */
+function parse_query($str, $urlEncoding = true)
+{
+ $result = [];
+
+ if ($str === '') {
+ return $result;
+ }
+
+ if ($urlEncoding === true) {
+ $decoder = function ($value) {
+ return rawurldecode(str_replace('+', ' ', $value));
+ };
+ } elseif ($urlEncoding === PHP_QUERY_RFC3986) {
+ $decoder = 'rawurldecode';
+ } elseif ($urlEncoding === PHP_QUERY_RFC1738) {
+ $decoder = 'urldecode';
+ } else {
+ $decoder = function ($str) { return $str; };
+ }
+
+ foreach (explode('&', $str) as $kvp) {
+ $parts = explode('=', $kvp, 2);
+ $key = $decoder($parts[0]);
+ $value = isset($parts[1]) ? $decoder($parts[1]) : null;
+ if (!isset($result[$key])) {
+ $result[$key] = $value;
+ } else {
+ if (!is_array($result[$key])) {
+ $result[$key] = [$result[$key]];
+ }
+ $result[$key][] = $value;
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Build a query string from an array of key value pairs.
+ *
+ * This function can use the return value of parse_query() to build a query
+ * string. This function does not modify the provided keys when an array is
+ * encountered (like http_build_query would).
+ *
+ * @param array $params Query string parameters.
+ * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986
+ * to encode using RFC3986, or PHP_QUERY_RFC1738
+ * to encode using RFC1738.
+ * @return string
+ */
+function build_query(array $params, $encoding = PHP_QUERY_RFC3986)
+{
+ if (!$params) {
+ return '';
+ }
+
+ if ($encoding === false) {
+ $encoder = function ($str) { return $str; };
+ } elseif ($encoding === PHP_QUERY_RFC3986) {
+ $encoder = 'rawurlencode';
+ } elseif ($encoding === PHP_QUERY_RFC1738) {
+ $encoder = 'urlencode';
+ } else {
+ throw new \InvalidArgumentException('Invalid type');
+ }
+
+ $qs = '';
+ foreach ($params as $k => $v) {
+ $k = $encoder($k);
+ if (!is_array($v)) {
+ $qs .= $k;
+ if ($v !== null) {
+ $qs .= '=' . $encoder($v);
+ }
+ $qs .= '&';
+ } else {
+ foreach ($v as $vv) {
+ $qs .= $k;
+ if ($vv !== null) {
+ $qs .= '=' . $encoder($vv);
+ }
+ $qs .= '&';
+ }
+ }
+ }
+
+ return $qs ? (string) substr($qs, 0, -1) : '';
+}
+
+/**
+ * Determines the mimetype of a file by looking at its extension.
+ *
+ * @param $filename
+ *
+ * @return null|string
+ */
+function mimetype_from_filename($filename)
+{
+ return mimetype_from_extension(pathinfo($filename, PATHINFO_EXTENSION));
+}
+
+/**
+ * Maps a file extensions to a mimetype.
+ *
+ * @param $extension string The file extension.
+ *
+ * @return string|null
+ * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types
+ */
+function mimetype_from_extension($extension)
+{
+ static $mimetypes = [
+ '3gp' => 'video/3gpp',
+ '7z' => 'application/x-7z-compressed',
+ 'aac' => 'audio/x-aac',
+ 'ai' => 'application/postscript',
+ 'aif' => 'audio/x-aiff',
+ 'asc' => 'text/plain',
+ 'asf' => 'video/x-ms-asf',
+ 'atom' => 'application/atom+xml',
+ 'avi' => 'video/x-msvideo',
+ 'bmp' => 'image/bmp',
+ 'bz2' => 'application/x-bzip2',
+ 'cer' => 'application/pkix-cert',
+ 'crl' => 'application/pkix-crl',
+ 'crt' => 'application/x-x509-ca-cert',
+ 'css' => 'text/css',
+ 'csv' => 'text/csv',
+ 'cu' => 'application/cu-seeme',
+ 'deb' => 'application/x-debian-package',
+ 'doc' => 'application/msword',
+ 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'dvi' => 'application/x-dvi',
+ 'eot' => 'application/vnd.ms-fontobject',
+ 'eps' => 'application/postscript',
+ 'epub' => 'application/epub+zip',
+ 'etx' => 'text/x-setext',
+ 'flac' => 'audio/flac',
+ 'flv' => 'video/x-flv',
+ 'gif' => 'image/gif',
+ 'gz' => 'application/gzip',
+ 'htm' => 'text/html',
+ 'html' => 'text/html',
+ 'ico' => 'image/x-icon',
+ 'ics' => 'text/calendar',
+ 'ini' => 'text/plain',
+ 'iso' => 'application/x-iso9660-image',
+ 'jar' => 'application/java-archive',
+ 'jpe' => 'image/jpeg',
+ 'jpeg' => 'image/jpeg',
+ 'jpg' => 'image/jpeg',
+ 'js' => 'text/javascript',
+ 'json' => 'application/json',
+ 'latex' => 'application/x-latex',
+ 'log' => 'text/plain',
+ 'm4a' => 'audio/mp4',
+ 'm4v' => 'video/mp4',
+ 'mid' => 'audio/midi',
+ 'midi' => 'audio/midi',
+ 'mov' => 'video/quicktime',
+ 'mkv' => 'video/x-matroska',
+ 'mp3' => 'audio/mpeg',
+ 'mp4' => 'video/mp4',
+ 'mp4a' => 'audio/mp4',
+ 'mp4v' => 'video/mp4',
+ 'mpe' => 'video/mpeg',
+ 'mpeg' => 'video/mpeg',
+ 'mpg' => 'video/mpeg',
+ 'mpg4' => 'video/mp4',
+ 'oga' => 'audio/ogg',
+ 'ogg' => 'audio/ogg',
+ 'ogv' => 'video/ogg',
+ 'ogx' => 'application/ogg',
+ 'pbm' => 'image/x-portable-bitmap',
+ 'pdf' => 'application/pdf',
+ 'pgm' => 'image/x-portable-graymap',
+ 'png' => 'image/png',
+ 'pnm' => 'image/x-portable-anymap',
+ 'ppm' => 'image/x-portable-pixmap',
+ 'ppt' => 'application/vnd.ms-powerpoint',
+ 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'ps' => 'application/postscript',
+ 'qt' => 'video/quicktime',
+ 'rar' => 'application/x-rar-compressed',
+ 'ras' => 'image/x-cmu-raster',
+ 'rss' => 'application/rss+xml',
+ 'rtf' => 'application/rtf',
+ 'sgm' => 'text/sgml',
+ 'sgml' => 'text/sgml',
+ 'svg' => 'image/svg+xml',
+ 'swf' => 'application/x-shockwave-flash',
+ 'tar' => 'application/x-tar',
+ 'tif' => 'image/tiff',
+ 'tiff' => 'image/tiff',
+ 'torrent' => 'application/x-bittorrent',
+ 'ttf' => 'application/x-font-ttf',
+ 'txt' => 'text/plain',
+ 'wav' => 'audio/x-wav',
+ 'webm' => 'video/webm',
+ 'webp' => 'image/webp',
+ 'wma' => 'audio/x-ms-wma',
+ 'wmv' => 'video/x-ms-wmv',
+ 'woff' => 'application/x-font-woff',
+ 'wsdl' => 'application/wsdl+xml',
+ 'xbm' => 'image/x-xbitmap',
+ 'xls' => 'application/vnd.ms-excel',
+ 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'xml' => 'application/xml',
+ 'xpm' => 'image/x-xpixmap',
+ 'xwd' => 'image/x-xwindowdump',
+ 'yaml' => 'text/yaml',
+ 'yml' => 'text/yaml',
+ 'zip' => 'application/zip',
+ ];
+
+ $extension = strtolower($extension);
+
+ return isset($mimetypes[$extension])
+ ? $mimetypes[$extension]
+ : null;
+}
+
+/**
+ * Parses an HTTP message into an associative array.
+ *
+ * The array contains the "start-line" key containing the start line of
+ * the message, "headers" key containing an associative array of header
+ * array values, and a "body" key containing the body of the message.
+ *
+ * @param string $message HTTP request or response to parse.
+ *
+ * @return array
+ * @internal
+ */
+function _parse_message($message)
+{
+ if (!$message) {
+ throw new \InvalidArgumentException('Invalid message');
+ }
+
+ $message = ltrim($message, "\r\n");
+
+ $messageParts = preg_split("/\r?\n\r?\n/", $message, 2);
+
+ if ($messageParts === false || count($messageParts) !== 2) {
+ throw new \InvalidArgumentException('Invalid message: Missing header delimiter');
+ }
+
+ list($rawHeaders, $body) = $messageParts;
+ $rawHeaders .= "\r\n"; // Put back the delimiter we split previously
+ $headerParts = preg_split("/\r?\n/", $rawHeaders, 2);
+
+ if ($headerParts === false || count($headerParts) !== 2) {
+ throw new \InvalidArgumentException('Invalid message: Missing status line');
+ }
+
+ list($startLine, $rawHeaders) = $headerParts;
+
+ if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') {
+ // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0
+ $rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders);
+ }
+
+ /** @var array[] $headerLines */
+ $count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER);
+
+ // If these aren't the same, then one line didn't match and there's an invalid header.
+ if ($count !== substr_count($rawHeaders, "\n")) {
+ // Folding is deprecated, see https://tools.ietf.org/html/rfc7230#section-3.2.4
+ if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) {
+ throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding');
+ }
+
+ throw new \InvalidArgumentException('Invalid header syntax');
+ }
+
+ $headers = [];
+
+ foreach ($headerLines as $headerLine) {
+ $headers[$headerLine[1]][] = $headerLine[2];
+ }
+
+ return [
+ 'start-line' => $startLine,
+ 'headers' => $headers,
+ 'body' => $body,
+ ];
+}
+
+/**
+ * Constructs a URI for an HTTP request message.
+ *
+ * @param string $path Path from the start-line
+ * @param array $headers Array of headers (each value an array).
+ *
+ * @return string
+ * @internal
+ */
+function _parse_request_uri($path, array $headers)
+{
+ $hostKey = array_filter(array_keys($headers), function ($k) {
+ return strtolower($k) === 'host';
+ });
+
+ // If no host is found, then a full URI cannot be constructed.
+ if (!$hostKey) {
+ return $path;
+ }
+
+ $host = $headers[reset($hostKey)][0];
+ $scheme = substr($host, -4) === ':443' ? 'https' : 'http';
+
+ return $scheme . '://' . $host . '/' . ltrim($path, '/');
+}
+
+/**
+ * Get a short summary of the message body
+ *
+ * Will return `null` if the response is not printable.
+ *
+ * @param MessageInterface $message The message to get the body summary
+ * @param int $truncateAt The maximum allowed size of the summary
+ *
+ * @return null|string
+ */
+function get_message_body_summary(MessageInterface $message, $truncateAt = 120)
+{
+ $body = $message->getBody();
+
+ if (!$body->isSeekable() || !$body->isReadable()) {
+ return null;
+ }
+
+ $size = $body->getSize();
+
+ if ($size === 0) {
+ return null;
+ }
+
+ $summary = $body->read($truncateAt);
+ $body->rewind();
+
+ if ($size > $truncateAt) {
+ $summary .= ' (truncated...)';
+ }
+
+ // Matches any printable character, including unicode characters:
+ // letters, marks, numbers, punctuation, spacing, and separators.
+ if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/', $summary)) {
+ return null;
+ }
+
+ return $summary;
+}
+
+/** @internal */
+function _caseless_remove($keys, array $data)
+{
+ $result = [];
+
+ foreach ($keys as &$key) {
+ $key = strtolower($key);
+ }
+
+ foreach ($data as $k => $v) {
+ if (!in_array(strtolower($k), $keys)) {
+ $result[$k] = $v;
+ }
+ }
+
+ return $result;
+}
diff --git a/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/functions_include.php b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/functions_include.php
new file mode 100644
index 000000000..96a4a83a0
--- /dev/null
+++ b/plugins/panakour/backup/vendor/guzzlehttp/psr7/src/functions_include.php
@@ -0,0 +1,6 @@
+ [int])
+
+
+## 1.0.0
+
+Initial release
diff --git a/plugins/panakour/backup/vendor/league/flysystem-webdav/composer.json b/plugins/panakour/backup/vendor/league/flysystem-webdav/composer.json
new file mode 100644
index 000000000..64d4c38a5
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem-webdav/composer.json
@@ -0,0 +1,33 @@
+{
+ "name": "league/flysystem-webdav",
+ "description": "Flysystem adapter for WebDAV",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Frank de Jonge",
+ "email": "info@frenky.net"
+ }
+ ],
+ "require": {
+ "php": ">=5.6",
+ "league/flysystem": "~1.0",
+ "sabre/dav": "~4.0|~3.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8",
+ "mockery/mockery": "~1.2"
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\Flysystem\\WebDAV\\": "src"
+ }
+ },
+ "config": {
+ "bin-dir": "bin"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem-webdav/src/WebDAVAdapter.php b/plugins/panakour/backup/vendor/league/flysystem-webdav/src/WebDAVAdapter.php
new file mode 100644
index 000000000..c6a8eeddd
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem-webdav/src/WebDAVAdapter.php
@@ -0,0 +1,419 @@
+ 'size',
+ '{DAV:}getcontenttype' => 'mimetype',
+ 'content-length' => 'size',
+ 'content-type' => 'mimetype',
+ ];
+
+ /**
+ * @var Client
+ */
+ protected $client;
+
+ /**
+ * @var bool
+ */
+ protected $useStreamedCopy = true;
+
+ /**
+ * Constructor.
+ *
+ * @param Client $client
+ * @param string $prefix
+ * @param bool $useStreamedCopy
+ */
+ public function __construct(Client $client, $prefix = null, $useStreamedCopy = true)
+ {
+ $this->client = $client;
+ $this->setPathPrefix($prefix);
+ $this->setUseStreamedCopy($useStreamedCopy);
+ }
+
+ /**
+ * url encode a path
+ *
+ * @param string $path
+ *
+ * @return string
+ */
+ protected function encodePath($path)
+ {
+ $a = explode('/', $path);
+ for ($i=0; $iapplyPathPrefix($this->encodePath($path));
+
+ try {
+ $result = $this->client->propFind($location, static::$metadataFields);
+
+ if (empty($result)) {
+ return false;
+ }
+
+ return $this->normalizeObject($result, $path);
+ } catch (Exception $e) {
+ return false;
+ } catch (HttpException $e) {
+ return false;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($path)
+ {
+ return $this->getMetadata($path);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function read($path)
+ {
+ $location = $this->applyPathPrefix($this->encodePath($path));
+
+ try {
+ $response = $this->client->request('GET', $location);
+
+ if ($response['statusCode'] !== 200) {
+ return false;
+ }
+
+ return array_merge([
+ 'contents' => $response['body'],
+ 'timestamp' => strtotime(is_array($response['headers']['last-modified'])
+ ? current($response['headers']['last-modified'])
+ : $response['headers']['last-modified']),
+ 'path' => $path,
+ ], Util::map($response['headers'], static::$resultMap));
+ } catch (Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function write($path, $contents, Config $config)
+ {
+ if (!$this->createDir(Util::dirname($path), $config)) {
+ return false;
+ }
+
+ $location = $this->applyPathPrefix($this->encodePath($path));
+ $response = $this->client->request('PUT', $location, $contents);
+
+ if ($response['statusCode'] >= 400) {
+ return false;
+ }
+
+ $result = compact('path', 'contents');
+
+ if ($config->get('visibility')) {
+ throw new LogicException(__CLASS__.' does not support visibility settings.');
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function writeStream($path, $resource, Config $config)
+ {
+ return $this->write($path, $resource, $config);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function update($path, $contents, Config $config)
+ {
+ return $this->write($path, $contents, $config);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function updateStream($path, $resource, Config $config)
+ {
+ return $this->update($path, $resource, $config);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function rename($path, $newpath)
+ {
+ $location = $this->applyPathPrefix($this->encodePath($path));
+ $newLocation = $this->applyPathPrefix($this->encodePath($newpath));
+
+ try {
+ $response = $this->client->request('MOVE', '/'.ltrim($location, '/'), null, [
+ 'Destination' => '/'.ltrim($newLocation, '/'),
+ ]);
+
+ if ($response['statusCode'] >= 200 && $response['statusCode'] < 300) {
+ return true;
+ }
+ } catch (NotFound $e) {
+ // Would have returned false here, but would be redundant
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function copy($path, $newpath)
+ {
+ if ($this->useStreamedCopy === true) {
+ return $this->streamedCopy($path, $newpath);
+ } else {
+ return $this->nativeCopy($path, $newpath);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function delete($path)
+ {
+ $location = $this->applyPathPrefix($this->encodePath($path));
+
+ try {
+ $response = $this->client->request('DELETE', $location)['statusCode'];
+
+
+ return $response >= 200 && $response < 300;
+ } catch (NotFound $e) {
+ return false;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createDir($path, Config $config)
+ {
+ $encodedPath = $this->encodePath($path);
+ $path = trim($path, '/');
+
+ $result = compact('path') + ['type' => 'dir'];
+
+ if (Util::normalizeDirname($path) === '' || $this->has($path)) {
+ return $result;
+ }
+
+ $directories = explode('/', $path);
+ if (count($directories) > 1) {
+ $parentDirectories = array_splice($directories, 0, count($directories) - 1);
+ if (!$this->createDir(implode('/', $parentDirectories), $config)) {
+ return false;
+ }
+ }
+
+ $location = $this->applyPathPrefix($encodedPath);
+ $response = $this->client->request('MKCOL', $location . $this->pathSeparator);
+
+ if ($response['statusCode'] !== 201) {
+ return false;
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function deleteDir($dirname)
+ {
+ return $this->delete($dirname);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function listContents($directory = '', $recursive = false)
+ {
+ $location = $this->applyPathPrefix($this->encodePath($directory));
+ $response = $this->client->propFind($location . '/', static::$metadataFields, 1);
+
+ array_shift($response);
+ $result = [];
+
+ foreach ($response as $path => $object) {
+ $path = $this->removePathPrefix(rawurldecode($path));
+ $object = $this->normalizeObject($object, $path);
+ $result[] = $object;
+
+ if ($recursive && $object['type'] === 'dir') {
+ $result = array_merge($result, $this->listContents($object['path'], true));
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSize($path)
+ {
+ return $this->getMetadata($path);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTimestamp($path)
+ {
+ return $this->getMetadata($path);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMimetype($path)
+ {
+ return $this->getMetadata($path);
+ }
+
+ /**
+ * @return boolean
+ */
+ public function getUseStreamedCopy()
+ {
+ return $this->useStreamedCopy;
+ }
+
+ /**
+ * @param boolean $useStreamedCopy
+ */
+ public function setUseStreamedCopy($useStreamedCopy)
+ {
+ $this->useStreamedCopy = (bool)$useStreamedCopy;
+ }
+
+ /**
+ * Copy a file through WebDav COPY method.
+ *
+ * @param string $path
+ * @param string $newPath
+ *
+ * @return bool
+ */
+ protected function nativeCopy($path, $newPath)
+ {
+ if (!$this->createDir(Util::dirname($newPath), new Config())) {
+ return false;
+ }
+
+ $location = $this->applyPathPrefix($this->encodePath($path));
+ $newLocation = $this->applyPathPrefix($this->encodePath($newPath));
+
+ try {
+ $destination = $this->client->getAbsoluteUrl($newLocation);
+ $response = $this->client->request('COPY', '/'.ltrim($location, '/'), null, [
+ 'Destination' => $destination,
+ ]);
+
+ if ($response['statusCode'] >= 200 && $response['statusCode'] < 300) {
+ return true;
+ }
+ } catch (NotFound $e) {
+ // Would have returned false here, but would be redundant
+ }
+
+ return false;
+ }
+
+ /**
+ * Normalise a WebDAV repsonse object.
+ *
+ * @param array $object
+ * @param string $path
+ *
+ * @return array
+ */
+ protected function normalizeObject(array $object, $path)
+ {
+ if ($this->isDirectory($object)) {
+ return ['type' => 'dir', 'path' => trim($path, '/')];
+ }
+
+ $result = Util::map($object, static::$resultMap);
+
+ if (isset($object['{DAV:}getlastmodified'])) {
+ $result['timestamp'] = strtotime($object['{DAV:}getlastmodified']);
+ }
+
+ $result['type'] = 'file';
+ $result['path'] = trim($path, '/');
+
+ return $result;
+ }
+
+ /**
+ * @param array $object
+ * @return bool
+ */
+ protected function isDirectory(array $object)
+ {
+ if (isset($object['{DAV:}resourcetype'])) {
+ /** @var ResourceType $resourceType */
+ $resourceType = $object['{DAV:}resourcetype'];
+ return $resourceType->is('{DAV:}collection');
+ }
+
+ return isset($object['{DAV:}iscollection']) && $object['{DAV:}iscollection'] === '1';
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/LICENSE b/plugins/panakour/backup/vendor/league/flysystem/LICENSE
new file mode 100644
index 000000000..f2684c841
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2013-2019 Frank de Jonge
+
+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.
diff --git a/plugins/panakour/backup/vendor/league/flysystem/SECURITY.md b/plugins/panakour/backup/vendor/league/flysystem/SECURITY.md
new file mode 100644
index 000000000..f5b205ed0
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/SECURITY.md
@@ -0,0 +1,16 @@
+# Security Policy
+
+## Supported Versions
+
+| Version | Supported |
+| ------- | ------------------ |
+| 1.0.x | :white_check_mark: |
+| 2.0.x | :x: |
+
+## Reporting a Vulnerability
+
+When you've encountered a security vulnerability, please disclose it securely.
+
+The security process is described at:
+[https://flysystem.thephpleague.com/docs/security/](https://flysystem.thephpleague.com/docs/security/)
+
diff --git a/plugins/panakour/backup/vendor/league/flysystem/composer.json b/plugins/panakour/backup/vendor/league/flysystem/composer.json
new file mode 100644
index 000000000..51169de0e
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/composer.json
@@ -0,0 +1,71 @@
+{
+ "name": "league/flysystem",
+ "type": "library",
+ "description": "Filesystem abstraction: Many filesystems, one API.",
+ "keywords": [
+ "filesystem", "filesystems", "files", "storage", "dropbox", "aws",
+ "abstraction", "s3", "ftp", "sftp", "remote", "webdav",
+ "file systems", "cloud", "cloud files", "rackspace", "copy.com"
+ ],
+ "funding": [
+ {
+ "type": "other",
+ "url": "https://offset.earth/frankdejonge"
+ }
+ ],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Frank de Jonge",
+ "email": "info@frenky.net"
+ }
+ ],
+ "require": {
+ "php": ">=5.5.9",
+ "ext-fileinfo": "*"
+ },
+ "require-dev": {
+ "phpspec/phpspec": "^3.4",
+ "phpunit/phpunit": "^5.7.26"
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\Flysystem\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "League\\Flysystem\\Stub\\": "stub/"
+ },
+ "files": [
+ "tests/PHPUnitHacks.php"
+ ]
+ },
+ "suggest": {
+ "ext-fileinfo": "Required for MimeType",
+ "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem",
+ "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files",
+ "league/flysystem-azure": "Allows you to use Windows Azure Blob storage",
+ "league/flysystem-webdav": "Allows you to use WebDAV storage",
+ "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2",
+ "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3",
+ "spatie/flysystem-dropbox": "Allows you to use Dropbox storage",
+ "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications",
+ "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching",
+ "ext-ftp": "Allows you to use FTP server storage",
+ "ext-openssl": "Allows you to use FTPS server storage",
+ "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib",
+ "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter"
+ },
+ "conflict": {
+ "league/flysystem-sftp": "<1.0.6"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "scripts": {
+ "phpstan": "php phpstan.php"
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/deprecations.md b/plugins/panakour/backup/vendor/league/flysystem/deprecations.md
new file mode 100644
index 000000000..c336a425d
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/deprecations.md
@@ -0,0 +1,19 @@
+# Deprecations
+
+This document lists all the planned deprecations.
+
+## Handlers will be removed in 2.0
+
+The `Handler` type and associated calls will be removed in version 2.0.
+
+### Upgrade path
+
+You should create your own implementation for handling OOP usage,
+but it's recommended to move away from using an OOP-style wrapper entirely.
+
+The reason for this is that it's too easy for implementation details (for
+your application this is Flysystem) to leak into the application. The most
+important part for Flysystem is that it improves portability and creates a
+solid boundary between your application core and the infrastructure you use.
+The OOP-style handling breaks this principle, therefore I want to stop
+promoting it.
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/AbstractAdapter.php b/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/AbstractAdapter.php
new file mode 100644
index 000000000..e577ac4a7
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/AbstractAdapter.php
@@ -0,0 +1,72 @@
+pathPrefix = null;
+
+ return;
+ }
+
+ $this->pathPrefix = rtrim($prefix, '\\/') . $this->pathSeparator;
+ }
+
+ /**
+ * Get the path prefix.
+ *
+ * @return string|null path prefix or null if pathPrefix is empty
+ */
+ public function getPathPrefix()
+ {
+ return $this->pathPrefix;
+ }
+
+ /**
+ * Prefix a path.
+ *
+ * @param string $path
+ *
+ * @return string prefixed path
+ */
+ public function applyPathPrefix($path)
+ {
+ return $this->getPathPrefix() . ltrim($path, '\\/');
+ }
+
+ /**
+ * Remove a path prefix.
+ *
+ * @param string $path
+ *
+ * @return string path without the prefix
+ */
+ public function removePathPrefix($path)
+ {
+ return substr($path, strlen($this->getPathPrefix()));
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php b/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php
new file mode 100644
index 000000000..95a6b4d09
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php
@@ -0,0 +1,697 @@
+safeStorage = new SafeStorage();
+ $this->setConfig($config);
+ }
+
+ /**
+ * Set the config.
+ *
+ * @param array $config
+ *
+ * @return $this
+ */
+ public function setConfig(array $config)
+ {
+ foreach ($this->configurable as $setting) {
+ if ( ! isset($config[$setting])) {
+ continue;
+ }
+
+ $method = 'set' . ucfirst($setting);
+
+ if (method_exists($this, $method)) {
+ $this->$method($config[$setting]);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the host.
+ *
+ * @return string
+ */
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ /**
+ * Set the host.
+ *
+ * @param string $host
+ *
+ * @return $this
+ */
+ public function setHost($host)
+ {
+ $this->host = $host;
+
+ return $this;
+ }
+
+ /**
+ * Set the public permission value.
+ *
+ * @param int $permPublic
+ *
+ * @return $this
+ */
+ public function setPermPublic($permPublic)
+ {
+ $this->permPublic = $permPublic;
+
+ return $this;
+ }
+
+ /**
+ * Set the private permission value.
+ *
+ * @param int $permPrivate
+ *
+ * @return $this
+ */
+ public function setPermPrivate($permPrivate)
+ {
+ $this->permPrivate = $permPrivate;
+
+ return $this;
+ }
+
+ /**
+ * Returns the ftp port.
+ *
+ * @return int
+ */
+ public function getPort()
+ {
+ return $this->port;
+ }
+
+ /**
+ * Returns the root folder to work from.
+ *
+ * @return string
+ */
+ public function getRoot()
+ {
+ return $this->root;
+ }
+
+ /**
+ * Set the ftp port.
+ *
+ * @param int|string $port
+ *
+ * @return $this
+ */
+ public function setPort($port)
+ {
+ $this->port = (int) $port;
+
+ return $this;
+ }
+
+ /**
+ * Set the root folder to work from.
+ *
+ * @param string $root
+ *
+ * @return $this
+ */
+ public function setRoot($root)
+ {
+ $this->root = rtrim($root, '\\/') . $this->separator;
+
+ return $this;
+ }
+
+ /**
+ * Returns the ftp username.
+ *
+ * @return string username
+ */
+ public function getUsername()
+ {
+ $username = $this->safeStorage->retrieveSafely('username');
+
+ return $username !== null ? $username : 'anonymous';
+ }
+
+ /**
+ * Set ftp username.
+ *
+ * @param string $username
+ *
+ * @return $this
+ */
+ public function setUsername($username)
+ {
+ $this->safeStorage->storeSafely('username', $username);
+
+ return $this;
+ }
+
+ /**
+ * Returns the password.
+ *
+ * @return string password
+ */
+ public function getPassword()
+ {
+ return $this->safeStorage->retrieveSafely('password');
+ }
+
+ /**
+ * Set the ftp password.
+ *
+ * @param string $password
+ *
+ * @return $this
+ */
+ public function setPassword($password)
+ {
+ $this->safeStorage->storeSafely('password', $password);
+
+ return $this;
+ }
+
+ /**
+ * Returns the amount of seconds before the connection will timeout.
+ *
+ * @return int
+ */
+ public function getTimeout()
+ {
+ return $this->timeout;
+ }
+
+ /**
+ * Set the amount of seconds before the connection should timeout.
+ *
+ * @param int $timeout
+ *
+ * @return $this
+ */
+ public function setTimeout($timeout)
+ {
+ $this->timeout = (int) $timeout;
+
+ return $this;
+ }
+
+ /**
+ * Return the FTP system type.
+ *
+ * @return string
+ */
+ public function getSystemType()
+ {
+ return $this->systemType;
+ }
+
+ /**
+ * Set the FTP system type (windows or unix).
+ *
+ * @param string $systemType
+ *
+ * @return $this
+ */
+ public function setSystemType($systemType)
+ {
+ $this->systemType = strtolower($systemType);
+
+ return $this;
+ }
+
+ /**
+ * True to enable timestamps for FTP servers that return unix-style listings.
+ *
+ * @param bool $bool
+ *
+ * @return $this
+ */
+ public function setEnableTimestampsOnUnixListings($bool = false)
+ {
+ $this->enableTimestampsOnUnixListings = $bool;
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function listContents($directory = '', $recursive = false)
+ {
+ return $this->listDirectoryContents($directory, $recursive);
+ }
+
+ abstract protected function listDirectoryContents($directory, $recursive = false);
+
+ /**
+ * Normalize a directory listing.
+ *
+ * @param array $listing
+ * @param string $prefix
+ *
+ * @return array directory listing
+ */
+ protected function normalizeListing(array $listing, $prefix = '')
+ {
+ $base = $prefix;
+ $result = [];
+ $listing = $this->removeDotDirectories($listing);
+
+ while ($item = array_shift($listing)) {
+ if (preg_match('#^.*:$#', $item)) {
+ $base = preg_replace('~^\./*|:$~', '', $item);
+ continue;
+ }
+
+ $result[] = $this->normalizeObject($item, $base);
+ }
+
+ return $this->sortListing($result);
+ }
+
+ /**
+ * Sort a directory listing.
+ *
+ * @param array $result
+ *
+ * @return array sorted listing
+ */
+ protected function sortListing(array $result)
+ {
+ $compare = function ($one, $two) {
+ return strnatcmp($one['path'], $two['path']);
+ };
+
+ usort($result, $compare);
+
+ return $result;
+ }
+
+ /**
+ * Normalize a file entry.
+ *
+ * @param string $item
+ * @param string $base
+ *
+ * @return array normalized file array
+ *
+ * @throws NotSupportedException
+ */
+ protected function normalizeObject($item, $base)
+ {
+ $systemType = $this->systemType ?: $this->detectSystemType($item);
+
+ if ($systemType === 'unix') {
+ return $this->normalizeUnixObject($item, $base);
+ } elseif ($systemType === 'windows') {
+ return $this->normalizeWindowsObject($item, $base);
+ }
+
+ throw NotSupportedException::forFtpSystemType($systemType);
+ }
+
+ /**
+ * Normalize a Unix file entry.
+ *
+ * Given $item contains:
+ * '-rw-r--r-- 1 ftp ftp 409 Aug 19 09:01 file1.txt'
+ *
+ * This function will return:
+ * [
+ * 'type' => 'file',
+ * 'path' => 'file1.txt',
+ * 'visibility' => 'public',
+ * 'size' => 409,
+ * 'timestamp' => 1566205260
+ * ]
+ *
+ * @param string $item
+ * @param string $base
+ *
+ * @return array normalized file array
+ */
+ protected function normalizeUnixObject($item, $base)
+ {
+ $item = preg_replace('#\s+#', ' ', trim($item), 7);
+
+ if (count(explode(' ', $item, 9)) !== 9) {
+ throw new RuntimeException("Metadata can't be parsed from item '$item' , not enough parts.");
+ }
+
+ list($permissions, /* $number */, /* $owner */, /* $group */, $size, $month, $day, $timeOrYear, $name) = explode(' ', $item, 9);
+ $type = $this->detectType($permissions);
+ $path = $base === '' ? $name : $base . $this->separator . $name;
+
+ if ($type === 'dir') {
+ return compact('type', 'path');
+ }
+
+ $permissions = $this->normalizePermissions($permissions);
+ $visibility = $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE;
+ $size = (int) $size;
+
+ $result = compact('type', 'path', 'visibility', 'size');
+ if ($this->enableTimestampsOnUnixListings) {
+ $timestamp = $this->normalizeUnixTimestamp($month, $day, $timeOrYear);
+ $result += compact('timestamp');
+ }
+
+ return $result;
+ }
+
+ /**
+ * Only accurate to the minute (current year), or to the day.
+ *
+ * Inadequacies in timestamp accuracy are due to limitations of the FTP 'LIST' command
+ *
+ * Note: The 'MLSD' command is a machine-readable replacement for 'LIST'
+ * but many FTP servers do not support it :(
+ *
+ * @param string $month e.g. 'Aug'
+ * @param string $day e.g. '19'
+ * @param string $timeOrYear e.g. '09:01' OR '2015'
+ *
+ * @return int
+ */
+ protected function normalizeUnixTimestamp($month, $day, $timeOrYear)
+ {
+ if (is_numeric($timeOrYear)) {
+ $year = $timeOrYear;
+ $hour = '00';
+ $minute = '00';
+ $seconds = '00';
+ } else {
+ $year = date('Y');
+ list($hour, $minute) = explode(':', $timeOrYear);
+ $seconds = '00';
+ }
+ $dateTime = DateTime::createFromFormat('Y-M-j-G:i:s', "{$year}-{$month}-{$day}-{$hour}:{$minute}:{$seconds}");
+
+ return $dateTime->getTimestamp();
+ }
+
+ /**
+ * Normalize a Windows/DOS file entry.
+ *
+ * @param string $item
+ * @param string $base
+ *
+ * @return array normalized file array
+ */
+ protected function normalizeWindowsObject($item, $base)
+ {
+ $item = preg_replace('#\s+#', ' ', trim($item), 3);
+
+ if (count(explode(' ', $item, 4)) !== 4) {
+ throw new RuntimeException("Metadata can't be parsed from item '$item' , not enough parts.");
+ }
+
+ list($date, $time, $size, $name) = explode(' ', $item, 4);
+ $path = $base === '' ? $name : $base . $this->separator . $name;
+
+ // Check for the correct date/time format
+ $format = strlen($date) === 8 ? 'm-d-yH:iA' : 'Y-m-dH:i';
+ $dt = DateTime::createFromFormat($format, $date . $time);
+ $timestamp = $dt ? $dt->getTimestamp() : (int) strtotime("$date $time");
+
+ if ($size === '') {
+ $type = 'dir';
+
+ return compact('type', 'path', 'timestamp');
+ }
+
+ $type = 'file';
+ $visibility = AdapterInterface::VISIBILITY_PUBLIC;
+ $size = (int) $size;
+
+ return compact('type', 'path', 'visibility', 'size', 'timestamp');
+ }
+
+ /**
+ * Get the system type from a listing item.
+ *
+ * @param string $item
+ *
+ * @return string the system type
+ */
+ protected function detectSystemType($item)
+ {
+ return preg_match('/^[0-9]{2,4}-[0-9]{2}-[0-9]{2}/', $item) ? 'windows' : 'unix';
+ }
+
+ /**
+ * Get the file type from the permissions.
+ *
+ * @param string $permissions
+ *
+ * @return string file type
+ */
+ protected function detectType($permissions)
+ {
+ return substr($permissions, 0, 1) === 'd' ? 'dir' : 'file';
+ }
+
+ /**
+ * Normalize a permissions string.
+ *
+ * @param string $permissions
+ *
+ * @return int
+ */
+ protected function normalizePermissions($permissions)
+ {
+ if (is_numeric($permissions)) {
+ return ((int) $permissions) & 0777;
+ }
+
+ // remove the type identifier
+ $permissions = substr($permissions, 1);
+
+ // map the string rights to the numeric counterparts
+ $map = ['-' => '0', 'r' => '4', 'w' => '2', 'x' => '1'];
+ $permissions = strtr($permissions, $map);
+
+ // split up the permission groups
+ $parts = str_split($permissions, 3);
+
+ // convert the groups
+ $mapper = function ($part) {
+ return array_sum(str_split($part));
+ };
+
+ // converts to decimal number
+ return octdec(implode('', array_map($mapper, $parts)));
+ }
+
+ /**
+ * Filter out dot-directories.
+ *
+ * @param array $list
+ *
+ * @return array
+ */
+ public function removeDotDirectories(array $list)
+ {
+ $filter = function ($line) {
+ return $line !== '' && ! preg_match('#.* \.(\.)?$|^total#', $line);
+ };
+
+ return array_filter($list, $filter);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function has($path)
+ {
+ return $this->getMetadata($path);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getSize($path)
+ {
+ return $this->getMetadata($path);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getVisibility($path)
+ {
+ return $this->getMetadata($path);
+ }
+
+ /**
+ * Ensure a directory exists.
+ *
+ * @param string $dirname
+ */
+ public function ensureDirectory($dirname)
+ {
+ $dirname = (string) $dirname;
+
+ if ($dirname !== '' && ! $this->has($dirname)) {
+ $this->createDir($dirname, new Config());
+ }
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getConnection()
+ {
+ $tries = 0;
+
+ while ( ! $this->isConnected() && $tries < 3) {
+ $tries++;
+ $this->disconnect();
+ $this->connect();
+ }
+
+ return $this->connection;
+ }
+
+ /**
+ * Get the public permission value.
+ *
+ * @return int
+ */
+ public function getPermPublic()
+ {
+ return $this->permPublic;
+ }
+
+ /**
+ * Get the private permission value.
+ *
+ * @return int
+ */
+ public function getPermPrivate()
+ {
+ return $this->permPrivate;
+ }
+
+ /**
+ * Disconnect on destruction.
+ */
+ public function __destruct()
+ {
+ $this->disconnect();
+ }
+
+ /**
+ * Establish a connection.
+ */
+ abstract public function connect();
+
+ /**
+ * Close the connection.
+ */
+ abstract public function disconnect();
+
+ /**
+ * Check if a connection is active.
+ *
+ * @return bool
+ */
+ abstract public function isConnected();
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/CanOverwriteFiles.php b/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/CanOverwriteFiles.php
new file mode 100644
index 000000000..fd8d2161e
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/CanOverwriteFiles.php
@@ -0,0 +1,12 @@
+transferMode = $mode;
+
+ return $this;
+ }
+
+ /**
+ * Set if Ssl is enabled.
+ *
+ * @param bool $ssl
+ *
+ * @return $this
+ */
+ public function setSsl($ssl)
+ {
+ $this->ssl = (bool) $ssl;
+
+ return $this;
+ }
+
+ /**
+ * Set if passive mode should be used.
+ *
+ * @param bool $passive
+ */
+ public function setPassive($passive = true)
+ {
+ $this->passive = $passive;
+ }
+
+ /**
+ * @param bool $ignorePassiveAddress
+ */
+ public function setIgnorePassiveAddress($ignorePassiveAddress)
+ {
+ $this->ignorePassiveAddress = $ignorePassiveAddress;
+ }
+
+ /**
+ * @param bool $recurseManually
+ */
+ public function setRecurseManually($recurseManually)
+ {
+ $this->recurseManually = $recurseManually;
+ }
+
+ /**
+ * @param bool $utf8
+ */
+ public function setUtf8($utf8)
+ {
+ $this->utf8 = (bool) $utf8;
+ }
+
+ /**
+ * Connect to the FTP server.
+ */
+ public function connect()
+ {
+ if ($this->ssl) {
+ $this->connection = @ftp_ssl_connect($this->getHost(), $this->getPort(), $this->getTimeout());
+ } else {
+ $this->connection = @ftp_connect($this->getHost(), $this->getPort(), $this->getTimeout());
+ }
+
+ if ( ! $this->connection) {
+ throw new ConnectionRuntimeException('Could not connect to host: ' . $this->getHost() . ', port:' . $this->getPort());
+ }
+
+ $this->login();
+ $this->setUtf8Mode();
+ $this->setConnectionPassiveMode();
+ $this->setConnectionRoot();
+ $this->isPureFtpd = $this->isPureFtpdServer();
+ }
+
+ /**
+ * Set the connection to UTF-8 mode.
+ */
+ protected function setUtf8Mode()
+ {
+ if ($this->utf8) {
+ $response = ftp_raw($this->connection, "OPTS UTF8 ON");
+ if (substr($response[0], 0, 3) !== '200') {
+ throw new ConnectionRuntimeException(
+ 'Could not set UTF-8 mode for connection: ' . $this->getHost() . '::' . $this->getPort()
+ );
+ }
+ }
+ }
+
+ /**
+ * Set the connections to passive mode.
+ *
+ * @throws ConnectionRuntimeException
+ */
+ protected function setConnectionPassiveMode()
+ {
+ if (is_bool($this->ignorePassiveAddress) && defined('FTP_USEPASVADDRESS')) {
+ ftp_set_option($this->connection, FTP_USEPASVADDRESS, ! $this->ignorePassiveAddress);
+ }
+
+ if ( ! ftp_pasv($this->connection, $this->passive)) {
+ throw new ConnectionRuntimeException(
+ 'Could not set passive mode for connection: ' . $this->getHost() . '::' . $this->getPort()
+ );
+ }
+ }
+
+ /**
+ * Set the connection root.
+ */
+ protected function setConnectionRoot()
+ {
+ $root = $this->getRoot();
+ $connection = $this->connection;
+
+ if ($root && ! ftp_chdir($connection, $root)) {
+ throw new InvalidRootException('Root is invalid or does not exist: ' . $this->getRoot());
+ }
+
+ // Store absolute path for further reference.
+ // This is needed when creating directories and
+ // initial root was a relative path, else the root
+ // would be relative to the chdir'd path.
+ $this->root = ftp_pwd($connection);
+ }
+
+ /**
+ * Login.
+ *
+ * @throws ConnectionRuntimeException
+ */
+ protected function login()
+ {
+ set_error_handler(function () {
+ });
+ $isLoggedIn = ftp_login(
+ $this->connection,
+ $this->getUsername(),
+ $this->getPassword()
+ );
+ restore_error_handler();
+
+ if ( ! $isLoggedIn) {
+ $this->disconnect();
+ throw new ConnectionRuntimeException(
+ 'Could not login with connection: ' . $this->getHost() . '::' . $this->getPort(
+ ) . ', username: ' . $this->getUsername()
+ );
+ }
+ }
+
+ /**
+ * Disconnect from the FTP server.
+ */
+ public function disconnect()
+ {
+ if (is_resource($this->connection)) {
+ @ftp_close($this->connection);
+ }
+
+ $this->connection = null;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function write($path, $contents, Config $config)
+ {
+ $stream = fopen('php://temp', 'w+b');
+ fwrite($stream, $contents);
+ rewind($stream);
+ $result = $this->writeStream($path, $stream, $config);
+ fclose($stream);
+
+ if ($result === false) {
+ return false;
+ }
+
+ $result['contents'] = $contents;
+ $result['mimetype'] = $config->get('mimetype') ?: Util::guessMimeType($path, $contents);
+
+ return $result;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function writeStream($path, $resource, Config $config)
+ {
+ $this->ensureDirectory(Util::dirname($path));
+
+ if ( ! ftp_fput($this->getConnection(), $path, $resource, $this->transferMode)) {
+ return false;
+ }
+
+ if ($visibility = $config->get('visibility')) {
+ $this->setVisibility($path, $visibility);
+ }
+
+ $type = 'file';
+
+ return compact('type', 'path', 'visibility');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function update($path, $contents, Config $config)
+ {
+ return $this->write($path, $contents, $config);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function updateStream($path, $resource, Config $config)
+ {
+ return $this->writeStream($path, $resource, $config);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function rename($path, $newpath)
+ {
+ return ftp_rename($this->getConnection(), $path, $newpath);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function delete($path)
+ {
+ return ftp_delete($this->getConnection(), $path);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function deleteDir($dirname)
+ {
+ $connection = $this->getConnection();
+ $contents = array_reverse($this->listDirectoryContents($dirname, false));
+
+ foreach ($contents as $object) {
+ if ($object['type'] === 'file') {
+ if ( ! ftp_delete($connection, $object['path'])) {
+ return false;
+ }
+ } elseif ( ! $this->deleteDir($object['path'])) {
+ return false;
+ }
+ }
+
+ return ftp_rmdir($connection, $dirname);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function createDir($dirname, Config $config)
+ {
+ $connection = $this->getConnection();
+ $directories = explode('/', $dirname);
+
+ foreach ($directories as $directory) {
+ if (false === $this->createActualDirectory($directory, $connection)) {
+ $this->setConnectionRoot();
+
+ return false;
+ }
+
+ ftp_chdir($connection, $directory);
+ }
+
+ $this->setConnectionRoot();
+
+ return ['type' => 'dir', 'path' => $dirname];
+ }
+
+ /**
+ * Create a directory.
+ *
+ * @param string $directory
+ * @param resource $connection
+ *
+ * @return bool
+ */
+ protected function createActualDirectory($directory, $connection)
+ {
+ // List the current directory
+ $listing = ftp_nlist($connection, '.') ?: [];
+
+ foreach ($listing as $key => $item) {
+ if (preg_match('~^\./.*~', $item)) {
+ $listing[$key] = substr($item, 2);
+ }
+ }
+
+ if (in_array($directory, $listing, true)) {
+ return true;
+ }
+
+ return (boolean) ftp_mkdir($connection, $directory);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getMetadata($path)
+ {
+ if ($path === '') {
+ return ['type' => 'dir', 'path' => ''];
+ }
+
+ if (@ftp_chdir($this->getConnection(), $path) === true) {
+ $this->setConnectionRoot();
+
+ return ['type' => 'dir', 'path' => $path];
+ }
+
+ $listing = $this->ftpRawlist('-A', str_replace('*', '\\*', $path));
+
+ if (empty($listing) || in_array('total 0', $listing, true)) {
+ return false;
+ }
+
+ if (preg_match('/.* not found/', $listing[0])) {
+ return false;
+ }
+
+ if (preg_match('/^total [0-9]*$/', $listing[0])) {
+ array_shift($listing);
+ }
+
+ return $this->normalizeObject($listing[0], '');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getMimetype($path)
+ {
+ if ( ! $metadata = $this->getMetadata($path)) {
+ return false;
+ }
+
+ $metadata['mimetype'] = MimeType::detectByFilename($path);
+
+ return $metadata;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getTimestamp($path)
+ {
+ $timestamp = ftp_mdtm($this->getConnection(), $path);
+
+ return ($timestamp !== -1) ? ['path' => $path, 'timestamp' => $timestamp] : false;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function read($path)
+ {
+ if ( ! $object = $this->readStream($path)) {
+ return false;
+ }
+
+ $object['contents'] = stream_get_contents($object['stream']);
+ fclose($object['stream']);
+ unset($object['stream']);
+
+ return $object;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function readStream($path)
+ {
+ $stream = fopen('php://temp', 'w+b');
+ $result = ftp_fget($this->getConnection(), $stream, $path, $this->transferMode);
+ rewind($stream);
+
+ if ( ! $result) {
+ fclose($stream);
+
+ return false;
+ }
+
+ return ['type' => 'file', 'path' => $path, 'stream' => $stream];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setVisibility($path, $visibility)
+ {
+ $mode = $visibility === AdapterInterface::VISIBILITY_PUBLIC ? $this->getPermPublic() : $this->getPermPrivate();
+
+ if ( ! ftp_chmod($this->getConnection(), $mode, $path)) {
+ return false;
+ }
+
+ return compact('path', 'visibility');
+ }
+
+ /**
+ * @inheritdoc
+ *
+ * @param string $directory
+ */
+ protected function listDirectoryContents($directory, $recursive = true)
+ {
+ $directory = str_replace('*', '\\*', $directory);
+
+ if ($recursive && $this->recurseManually) {
+ return $this->listDirectoryContentsRecursive($directory);
+ }
+
+ $options = $recursive ? '-alnR' : '-aln';
+ $listing = $this->ftpRawlist($options, $directory);
+
+ return $listing ? $this->normalizeListing($listing, $directory) : [];
+ }
+
+ /**
+ * @inheritdoc
+ *
+ * @param string $directory
+ */
+ protected function listDirectoryContentsRecursive($directory)
+ {
+ $listing = $this->normalizeListing($this->ftpRawlist('-aln', $directory) ?: [], $directory);
+ $output = [];
+
+ foreach ($listing as $item) {
+ $output[] = $item;
+ if ($item['type'] !== 'dir') {
+ continue;
+ }
+ $output = array_merge($output, $this->listDirectoryContentsRecursive($item['path']));
+ }
+
+ return $output;
+ }
+
+ /**
+ * Check if the connection is open.
+ *
+ * @return bool
+ *
+ * @throws ConnectionErrorException
+ */
+ public function isConnected()
+ {
+ return is_resource($this->connection)
+ && $this->getRawExecResponseCode('NOOP') === 200;
+ }
+
+ /**
+ * @return bool
+ */
+ protected function isPureFtpdServer()
+ {
+ $response = ftp_raw($this->connection, 'HELP');
+
+ return stripos(implode(' ', $response), 'Pure-FTPd') !== false;
+ }
+
+ /**
+ * The ftp_rawlist function with optional escaping.
+ *
+ * @param string $options
+ * @param string $path
+ *
+ * @return array
+ */
+ protected function ftpRawlist($options, $path)
+ {
+ $connection = $this->getConnection();
+
+ if ($this->isPureFtpd) {
+ $path = str_replace(' ', '\ ', $path);
+ }
+
+ return ftp_rawlist($connection, $options . ' ' . $path);
+ }
+
+ private function getRawExecResponseCode($command)
+ {
+ $response = @ftp_raw($this->connection, trim($command));
+
+ return (int) preg_replace('/\D/', '', implode(' ', $response));
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/Ftpd.php b/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/Ftpd.php
new file mode 100644
index 000000000..d5349e475
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/Ftpd.php
@@ -0,0 +1,45 @@
+ 'dir', 'path' => ''];
+ }
+ if (@ftp_chdir($this->getConnection(), $path) === true) {
+ $this->setConnectionRoot();
+
+ return ['type' => 'dir', 'path' => $path];
+ }
+
+ if ( ! ($object = ftp_raw($this->getConnection(), 'STAT ' . $path)) || count($object) < 3) {
+ return false;
+ }
+
+ if (substr($object[1], 0, 5) === "ftpd:") {
+ return false;
+ }
+
+ return $this->normalizeObject($object[1], '');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function listDirectoryContents($directory, $recursive = true)
+ {
+ $listing = ftp_rawlist($this->getConnection(), $directory, $recursive);
+
+ if ($listing === false || ( ! empty($listing) && substr($listing[0], 0, 5) === "ftpd:")) {
+ return [];
+ }
+
+ return $this->normalizeListing($listing, $directory);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/Local.php b/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/Local.php
new file mode 100644
index 000000000..2b892ab74
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/Local.php
@@ -0,0 +1,532 @@
+ [
+ 'public' => 0644,
+ 'private' => 0600,
+ ],
+ 'dir' => [
+ 'public' => 0755,
+ 'private' => 0700,
+ ],
+ ];
+
+ /**
+ * @var string
+ */
+ protected $pathSeparator = DIRECTORY_SEPARATOR;
+
+ /**
+ * @var array
+ */
+ protected $permissionMap;
+
+ /**
+ * @var int
+ */
+ protected $writeFlags;
+
+ /**
+ * @var int
+ */
+ private $linkHandling;
+
+ /**
+ * Constructor.
+ *
+ * @param string $root
+ * @param int $writeFlags
+ * @param int $linkHandling
+ * @param array $permissions
+ *
+ * @throws LogicException
+ */
+ public function __construct($root, $writeFlags = LOCK_EX, $linkHandling = self::DISALLOW_LINKS, array $permissions = [])
+ {
+ $root = is_link($root) ? realpath($root) : $root;
+ $this->permissionMap = array_replace_recursive(static::$permissions, $permissions);
+ $this->ensureDirectory($root);
+
+ if ( ! is_dir($root) || ! is_readable($root)) {
+ throw new LogicException('The root path ' . $root . ' is not readable.');
+ }
+
+ $this->setPathPrefix($root);
+ $this->writeFlags = $writeFlags;
+ $this->linkHandling = $linkHandling;
+ }
+
+ /**
+ * Ensure the root directory exists.
+ *
+ * @param string $root root directory path
+ *
+ * @return void
+ *
+ * @throws Exception in case the root directory can not be created
+ */
+ protected function ensureDirectory($root)
+ {
+ if ( ! is_dir($root)) {
+ $umask = umask(0);
+
+ if ( ! @mkdir($root, $this->permissionMap['dir']['public'], true)) {
+ $mkdirError = error_get_last();
+ }
+
+ umask($umask);
+ clearstatcache(false, $root);
+
+ if ( ! is_dir($root)) {
+ $errorMessage = isset($mkdirError['message']) ? $mkdirError['message'] : '';
+ throw new Exception(sprintf('Impossible to create the root directory "%s". %s', $root, $errorMessage));
+ }
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function has($path)
+ {
+ $location = $this->applyPathPrefix($path);
+
+ return file_exists($location);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function write($path, $contents, Config $config)
+ {
+ $location = $this->applyPathPrefix($path);
+ $this->ensureDirectory(dirname($location));
+
+ if (($size = file_put_contents($location, $contents, $this->writeFlags)) === false) {
+ return false;
+ }
+
+ $type = 'file';
+ $result = compact('contents', 'type', 'size', 'path');
+
+ if ($visibility = $config->get('visibility')) {
+ $result['visibility'] = $visibility;
+ $this->setVisibility($path, $visibility);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function writeStream($path, $resource, Config $config)
+ {
+ $location = $this->applyPathPrefix($path);
+ $this->ensureDirectory(dirname($location));
+ $stream = fopen($location, 'w+b');
+
+ if ( ! $stream || stream_copy_to_stream($resource, $stream) === false || ! fclose($stream)) {
+ return false;
+ }
+
+ $type = 'file';
+ $result = compact('type', 'path');
+
+ if ($visibility = $config->get('visibility')) {
+ $this->setVisibility($path, $visibility);
+ $result['visibility'] = $visibility;
+ }
+
+ return $result;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function readStream($path)
+ {
+ $location = $this->applyPathPrefix($path);
+ $stream = fopen($location, 'rb');
+
+ return ['type' => 'file', 'path' => $path, 'stream' => $stream];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function updateStream($path, $resource, Config $config)
+ {
+ return $this->writeStream($path, $resource, $config);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function update($path, $contents, Config $config)
+ {
+ $location = $this->applyPathPrefix($path);
+ $size = file_put_contents($location, $contents, $this->writeFlags);
+
+ if ($size === false) {
+ return false;
+ }
+
+ $type = 'file';
+
+ $result = compact('type', 'path', 'size', 'contents');
+
+ if ($mimetype = $config->get('mimetype') ?: Util::guessMimeType($path, $contents)) {
+ $result['mimetype'] = $mimetype;
+ }
+
+ return $result;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function read($path)
+ {
+ $location = $this->applyPathPrefix($path);
+ $contents = @file_get_contents($location);
+
+ if ($contents === false) {
+ return false;
+ }
+
+ return ['type' => 'file', 'path' => $path, 'contents' => $contents];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function rename($path, $newpath)
+ {
+ $location = $this->applyPathPrefix($path);
+ $destination = $this->applyPathPrefix($newpath);
+ $parentDirectory = $this->applyPathPrefix(Util::dirname($newpath));
+ $this->ensureDirectory($parentDirectory);
+
+ return rename($location, $destination);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function copy($path, $newpath)
+ {
+ $location = $this->applyPathPrefix($path);
+ $destination = $this->applyPathPrefix($newpath);
+ $this->ensureDirectory(dirname($destination));
+
+ return copy($location, $destination);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function delete($path)
+ {
+ $location = $this->applyPathPrefix($path);
+
+ return @unlink($location);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function listContents($directory = '', $recursive = false)
+ {
+ $result = [];
+ $location = $this->applyPathPrefix($directory);
+
+ if ( ! is_dir($location)) {
+ return [];
+ }
+
+ $iterator = $recursive ? $this->getRecursiveDirectoryIterator($location) : $this->getDirectoryIterator($location);
+
+ foreach ($iterator as $file) {
+ $path = $this->getFilePath($file);
+
+ if (preg_match('#(^|/|\\\\)\.{1,2}$#', $path)) {
+ continue;
+ }
+
+ $result[] = $this->normalizeFileInfo($file);
+ }
+
+ unset($iterator);
+
+ return array_filter($result);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getMetadata($path)
+ {
+ $location = $this->applyPathPrefix($path);
+ clearstatcache(false, $location);
+ $info = new SplFileInfo($location);
+
+ return $this->normalizeFileInfo($info);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getSize($path)
+ {
+ return $this->getMetadata($path);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getMimetype($path)
+ {
+ $location = $this->applyPathPrefix($path);
+ $finfo = new Finfo(FILEINFO_MIME_TYPE);
+ $mimetype = $finfo->file($location);
+
+ if (in_array($mimetype, ['application/octet-stream', 'inode/x-empty', 'application/x-empty'])) {
+ $mimetype = Util\MimeType::detectByFilename($location);
+ }
+
+ return ['path' => $path, 'type' => 'file', 'mimetype' => $mimetype];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getTimestamp($path)
+ {
+ return $this->getMetadata($path);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getVisibility($path)
+ {
+ $location = $this->applyPathPrefix($path);
+ clearstatcache(false, $location);
+ $permissions = octdec(substr(sprintf('%o', fileperms($location)), -4));
+ $type = is_dir($location) ? 'dir' : 'file';
+
+ foreach ($this->permissionMap[$type] as $visibility => $visibilityPermissions) {
+ if ($visibilityPermissions == $permissions) {
+ return compact('path', 'visibility');
+ }
+ }
+
+ $visibility = substr(sprintf('%o', fileperms($location)), -4);
+
+ return compact('path', 'visibility');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setVisibility($path, $visibility)
+ {
+ $location = $this->applyPathPrefix($path);
+ $type = is_dir($location) ? 'dir' : 'file';
+ $success = chmod($location, $this->permissionMap[$type][$visibility]);
+
+ if ($success === false) {
+ return false;
+ }
+
+ return compact('path', 'visibility');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function createDir($dirname, Config $config)
+ {
+ $location = $this->applyPathPrefix($dirname);
+ $umask = umask(0);
+ $visibility = $config->get('visibility', 'public');
+ $return = ['path' => $dirname, 'type' => 'dir'];
+
+ if ( ! is_dir($location)) {
+ if (false === @mkdir($location, $this->permissionMap['dir'][$visibility], true)
+ || false === is_dir($location)) {
+ $return = false;
+ }
+ }
+
+ umask($umask);
+
+ return $return;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function deleteDir($dirname)
+ {
+ $location = $this->applyPathPrefix($dirname);
+
+ if ( ! is_dir($location)) {
+ return false;
+ }
+
+ $contents = $this->getRecursiveDirectoryIterator($location, RecursiveIteratorIterator::CHILD_FIRST);
+
+ /** @var SplFileInfo $file */
+ foreach ($contents as $file) {
+ $this->guardAgainstUnreadableFileInfo($file);
+ $this->deleteFileInfoObject($file);
+ }
+
+ unset($contents);
+
+ return rmdir($location);
+ }
+
+ /**
+ * @param SplFileInfo $file
+ */
+ protected function deleteFileInfoObject(SplFileInfo $file)
+ {
+ switch ($file->getType()) {
+ case 'dir':
+ rmdir($file->getRealPath());
+ break;
+ case 'link':
+ unlink($file->getPathname());
+ break;
+ default:
+ unlink($file->getRealPath());
+ }
+ }
+
+ /**
+ * Normalize the file info.
+ *
+ * @param SplFileInfo $file
+ *
+ * @return array|void
+ *
+ * @throws NotSupportedException
+ */
+ protected function normalizeFileInfo(SplFileInfo $file)
+ {
+ if ( ! $file->isLink()) {
+ return $this->mapFileInfo($file);
+ }
+
+ if ($this->linkHandling & self::DISALLOW_LINKS) {
+ throw NotSupportedException::forLink($file);
+ }
+ }
+
+ /**
+ * Get the normalized path from a SplFileInfo object.
+ *
+ * @param SplFileInfo $file
+ *
+ * @return string
+ */
+ protected function getFilePath(SplFileInfo $file)
+ {
+ $location = $file->getPathname();
+ $path = $this->removePathPrefix($location);
+
+ return trim(str_replace('\\', '/', $path), '/');
+ }
+
+ /**
+ * @param string $path
+ * @param int $mode
+ *
+ * @return RecursiveIteratorIterator
+ */
+ protected function getRecursiveDirectoryIterator($path, $mode = RecursiveIteratorIterator::SELF_FIRST)
+ {
+ return new RecursiveIteratorIterator(
+ new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
+ $mode
+ );
+ }
+
+ /**
+ * @param string $path
+ *
+ * @return DirectoryIterator
+ */
+ protected function getDirectoryIterator($path)
+ {
+ $iterator = new DirectoryIterator($path);
+
+ return $iterator;
+ }
+
+ /**
+ * @param SplFileInfo $file
+ *
+ * @return array
+ */
+ protected function mapFileInfo(SplFileInfo $file)
+ {
+ $normalized = [
+ 'type' => $file->getType(),
+ 'path' => $this->getFilePath($file),
+ ];
+
+ $normalized['timestamp'] = $file->getMTime();
+
+ if ($normalized['type'] === 'file') {
+ $normalized['size'] = $file->getSize();
+ }
+
+ return $normalized;
+ }
+
+ /**
+ * @param SplFileInfo $file
+ *
+ * @throws UnreadableFileException
+ */
+ protected function guardAgainstUnreadableFileInfo(SplFileInfo $file)
+ {
+ if ( ! $file->isReadable()) {
+ throw UnreadableFileException::forFileInfo($file);
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/NullAdapter.php b/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/NullAdapter.php
new file mode 100644
index 000000000..2527087f7
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/NullAdapter.php
@@ -0,0 +1,144 @@
+get('visibility')) {
+ $result['visibility'] = $visibility;
+ }
+
+ return $result;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function update($path, $contents, Config $config)
+ {
+ return false;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function read($path)
+ {
+ return false;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function rename($path, $newpath)
+ {
+ return false;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function delete($path)
+ {
+ return false;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function listContents($directory = '', $recursive = false)
+ {
+ return [];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getMetadata($path)
+ {
+ return false;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getSize($path)
+ {
+ return false;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getMimetype($path)
+ {
+ return false;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getTimestamp($path)
+ {
+ return false;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getVisibility($path)
+ {
+ return false;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setVisibility($path, $visibility)
+ {
+ return compact('visibility');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function createDir($dirname, Config $config)
+ {
+ return ['path' => $dirname, 'type' => 'dir'];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function deleteDir($dirname)
+ {
+ return false;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php b/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php
new file mode 100644
index 000000000..fc0a747ac
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php
@@ -0,0 +1,33 @@
+readStream($path);
+
+ if ($response === false || ! is_resource($response['stream'])) {
+ return false;
+ }
+
+ $result = $this->writeStream($newpath, $response['stream'], new Config());
+
+ if ($result !== false && is_resource($response['stream'])) {
+ fclose($response['stream']);
+ }
+
+ return $result !== false;
+ }
+
+ // Required abstract method
+
+ /**
+ * @param string $path
+ *
+ * @return resource
+ */
+ abstract public function readStream($path);
+
+ /**
+ * @param string $path
+ * @param resource $resource
+ * @param Config $config
+ *
+ * @return resource
+ */
+ abstract public function writeStream($path, $resource, Config $config);
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php b/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php
new file mode 100644
index 000000000..2b31c01d6
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php
@@ -0,0 +1,44 @@
+read($path)) {
+ return false;
+ }
+
+ $stream = fopen('php://temp', 'w+b');
+ fwrite($stream, $data['contents']);
+ rewind($stream);
+ $data['stream'] = $stream;
+ unset($data['contents']);
+
+ return $data;
+ }
+
+ /**
+ * Reads a file.
+ *
+ * @param string $path
+ *
+ * @return array|false
+ *
+ * @see League\Flysystem\ReadInterface::read()
+ */
+ abstract public function read($path);
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php b/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php
new file mode 100644
index 000000000..80424960c
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php
@@ -0,0 +1,9 @@
+stream($path, $resource, $config, 'write');
+ }
+
+ /**
+ * Update a file using a stream.
+ *
+ * @param string $path
+ * @param resource $resource
+ * @param Config $config Config object or visibility setting
+ *
+ * @return mixed false of file metadata
+ */
+ public function updateStream($path, $resource, Config $config)
+ {
+ return $this->stream($path, $resource, $config, 'update');
+ }
+
+ // Required abstract methods
+ abstract public function write($pash, $contents, Config $config);
+ abstract public function update($pash, $contents, Config $config);
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/SynologyFtp.php b/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/SynologyFtp.php
new file mode 100644
index 000000000..fe0d344cf
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Adapter/SynologyFtp.php
@@ -0,0 +1,8 @@
+settings = $settings;
+ }
+
+ /**
+ * Get a setting.
+ *
+ * @param string $key
+ * @param mixed $default
+ *
+ * @return mixed config setting or default when not found
+ */
+ public function get($key, $default = null)
+ {
+ if ( ! array_key_exists($key, $this->settings)) {
+ return $this->getDefault($key, $default);
+ }
+
+ return $this->settings[$key];
+ }
+
+ /**
+ * Check if an item exists by key.
+ *
+ * @param string $key
+ *
+ * @return bool
+ */
+ public function has($key)
+ {
+ if (array_key_exists($key, $this->settings)) {
+ return true;
+ }
+
+ return $this->fallback instanceof Config
+ ? $this->fallback->has($key)
+ : false;
+ }
+
+ /**
+ * Try to retrieve a default setting from a config fallback.
+ *
+ * @param string $key
+ * @param mixed $default
+ *
+ * @return mixed config setting or default when not found
+ */
+ protected function getDefault($key, $default)
+ {
+ if ( ! $this->fallback) {
+ return $default;
+ }
+
+ return $this->fallback->get($key, $default);
+ }
+
+ /**
+ * Set a setting.
+ *
+ * @param string $key
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function set($key, $value)
+ {
+ $this->settings[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Set the fallback.
+ *
+ * @param Config $fallback
+ *
+ * @return $this
+ */
+ public function setFallback(Config $fallback)
+ {
+ $this->fallback = $fallback;
+
+ return $this;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/ConfigAwareTrait.php b/plugins/panakour/backup/vendor/league/flysystem/src/ConfigAwareTrait.php
new file mode 100644
index 000000000..202d605da
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/ConfigAwareTrait.php
@@ -0,0 +1,49 @@
+config = $config ? Util::ensureConfig($config) : new Config;
+ }
+
+ /**
+ * Get the Config.
+ *
+ * @return Config config object
+ */
+ public function getConfig()
+ {
+ return $this->config;
+ }
+
+ /**
+ * Convert a config array to a Config object with the correct fallback.
+ *
+ * @param array $config
+ *
+ * @return Config
+ */
+ protected function prepareConfig(array $config)
+ {
+ $config = new Config($config);
+ $config->setFallback($this->getConfig());
+
+ return $config;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/ConnectionErrorException.php b/plugins/panakour/backup/vendor/league/flysystem/src/ConnectionErrorException.php
new file mode 100644
index 000000000..adb651d3d
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/ConnectionErrorException.php
@@ -0,0 +1,9 @@
+filesystem->deleteDir($this->path);
+ }
+
+ /**
+ * List the directory contents.
+ *
+ * @param bool $recursive
+ *
+ * @return array|bool directory contents or false
+ */
+ public function getContents($recursive = false)
+ {
+ return $this->filesystem->listContents($this->path, $recursive);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Exception.php b/plugins/panakour/backup/vendor/league/flysystem/src/Exception.php
new file mode 100644
index 000000000..4596c0a9a
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Exception.php
@@ -0,0 +1,8 @@
+filesystem->has($this->path);
+ }
+
+ /**
+ * Read the file.
+ *
+ * @return string|false file contents
+ */
+ public function read()
+ {
+ return $this->filesystem->read($this->path);
+ }
+
+ /**
+ * Read the file as a stream.
+ *
+ * @return resource|false file stream
+ */
+ public function readStream()
+ {
+ return $this->filesystem->readStream($this->path);
+ }
+
+ /**
+ * Write the new file.
+ *
+ * @param string $content
+ *
+ * @return bool success boolean
+ */
+ public function write($content)
+ {
+ return $this->filesystem->write($this->path, $content);
+ }
+
+ /**
+ * Write the new file using a stream.
+ *
+ * @param resource $resource
+ *
+ * @return bool success boolean
+ */
+ public function writeStream($resource)
+ {
+ return $this->filesystem->writeStream($this->path, $resource);
+ }
+
+ /**
+ * Update the file contents.
+ *
+ * @param string $content
+ *
+ * @return bool success boolean
+ */
+ public function update($content)
+ {
+ return $this->filesystem->update($this->path, $content);
+ }
+
+ /**
+ * Update the file contents with a stream.
+ *
+ * @param resource $resource
+ *
+ * @return bool success boolean
+ */
+ public function updateStream($resource)
+ {
+ return $this->filesystem->updateStream($this->path, $resource);
+ }
+
+ /**
+ * Create the file or update if exists.
+ *
+ * @param string $content
+ *
+ * @return bool success boolean
+ */
+ public function put($content)
+ {
+ return $this->filesystem->put($this->path, $content);
+ }
+
+ /**
+ * Create the file or update if exists using a stream.
+ *
+ * @param resource $resource
+ *
+ * @return bool success boolean
+ */
+ public function putStream($resource)
+ {
+ return $this->filesystem->putStream($this->path, $resource);
+ }
+
+ /**
+ * Rename the file.
+ *
+ * @param string $newpath
+ *
+ * @return bool success boolean
+ */
+ public function rename($newpath)
+ {
+ if ($this->filesystem->rename($this->path, $newpath)) {
+ $this->path = $newpath;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Copy the file.
+ *
+ * @param string $newpath
+ *
+ * @return File|false new file or false
+ */
+ public function copy($newpath)
+ {
+ if ($this->filesystem->copy($this->path, $newpath)) {
+ return new File($this->filesystem, $newpath);
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the file's timestamp.
+ *
+ * @return string|false The timestamp or false on failure.
+ */
+ public function getTimestamp()
+ {
+ return $this->filesystem->getTimestamp($this->path);
+ }
+
+ /**
+ * Get the file's mimetype.
+ *
+ * @return string|false The file mime-type or false on failure.
+ */
+ public function getMimetype()
+ {
+ return $this->filesystem->getMimetype($this->path);
+ }
+
+ /**
+ * Get the file's visibility.
+ *
+ * @return string|false The visibility (public|private) or false on failure.
+ */
+ public function getVisibility()
+ {
+ return $this->filesystem->getVisibility($this->path);
+ }
+
+ /**
+ * Get the file's metadata.
+ *
+ * @return array|false The file metadata or false on failure.
+ */
+ public function getMetadata()
+ {
+ return $this->filesystem->getMetadata($this->path);
+ }
+
+ /**
+ * Get the file size.
+ *
+ * @return int|false The file size or false on failure.
+ */
+ public function getSize()
+ {
+ return $this->filesystem->getSize($this->path);
+ }
+
+ /**
+ * Delete the file.
+ *
+ * @return bool success boolean
+ */
+ public function delete()
+ {
+ return $this->filesystem->delete($this->path);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/FileExistsException.php b/plugins/panakour/backup/vendor/league/flysystem/src/FileExistsException.php
new file mode 100644
index 000000000..c82e20c16
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/FileExistsException.php
@@ -0,0 +1,37 @@
+path = $path;
+
+ parent::__construct('File already exists at path: ' . $this->getPath(), $code, $previous);
+ }
+
+ /**
+ * Get the path which was found.
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/FileNotFoundException.php b/plugins/panakour/backup/vendor/league/flysystem/src/FileNotFoundException.php
new file mode 100644
index 000000000..989df69bb
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/FileNotFoundException.php
@@ -0,0 +1,37 @@
+path = $path;
+
+ parent::__construct('File not found at path: ' . $this->getPath(), $code, $previous);
+ }
+
+ /**
+ * Get the path which was not found.
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Filesystem.php b/plugins/panakour/backup/vendor/league/flysystem/src/Filesystem.php
new file mode 100644
index 000000000..45095263c
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Filesystem.php
@@ -0,0 +1,408 @@
+adapter = $adapter;
+ $this->setConfig($config);
+ }
+
+ /**
+ * Get the Adapter.
+ *
+ * @return AdapterInterface adapter
+ */
+ public function getAdapter()
+ {
+ return $this->adapter;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function has($path)
+ {
+ $path = Util::normalizePath($path);
+
+ return strlen($path) === 0 ? false : (bool) $this->getAdapter()->has($path);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function write($path, $contents, array $config = [])
+ {
+ $path = Util::normalizePath($path);
+ $this->assertAbsent($path);
+ $config = $this->prepareConfig($config);
+
+ return (bool) $this->getAdapter()->write($path, $contents, $config);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function writeStream($path, $resource, array $config = [])
+ {
+ if ( ! is_resource($resource)) {
+ throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.');
+ }
+
+ $path = Util::normalizePath($path);
+ $this->assertAbsent($path);
+ $config = $this->prepareConfig($config);
+
+ Util::rewindStream($resource);
+
+ return (bool) $this->getAdapter()->writeStream($path, $resource, $config);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function put($path, $contents, array $config = [])
+ {
+ $path = Util::normalizePath($path);
+ $config = $this->prepareConfig($config);
+
+ if ( ! $this->getAdapter() instanceof CanOverwriteFiles && $this->has($path)) {
+ return (bool) $this->getAdapter()->update($path, $contents, $config);
+ }
+
+ return (bool) $this->getAdapter()->write($path, $contents, $config);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function putStream($path, $resource, array $config = [])
+ {
+ if ( ! is_resource($resource)) {
+ throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.');
+ }
+
+ $path = Util::normalizePath($path);
+ $config = $this->prepareConfig($config);
+ Util::rewindStream($resource);
+
+ if ( ! $this->getAdapter() instanceof CanOverwriteFiles && $this->has($path)) {
+ return (bool) $this->getAdapter()->updateStream($path, $resource, $config);
+ }
+
+ return (bool) $this->getAdapter()->writeStream($path, $resource, $config);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function readAndDelete($path)
+ {
+ $path = Util::normalizePath($path);
+ $this->assertPresent($path);
+ $contents = $this->read($path);
+
+ if ($contents === false) {
+ return false;
+ }
+
+ $this->delete($path);
+
+ return $contents;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function update($path, $contents, array $config = [])
+ {
+ $path = Util::normalizePath($path);
+ $config = $this->prepareConfig($config);
+
+ $this->assertPresent($path);
+
+ return (bool) $this->getAdapter()->update($path, $contents, $config);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function updateStream($path, $resource, array $config = [])
+ {
+ if ( ! is_resource($resource)) {
+ throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.');
+ }
+
+ $path = Util::normalizePath($path);
+ $config = $this->prepareConfig($config);
+ $this->assertPresent($path);
+ Util::rewindStream($resource);
+
+ return (bool) $this->getAdapter()->updateStream($path, $resource, $config);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function read($path)
+ {
+ $path = Util::normalizePath($path);
+ $this->assertPresent($path);
+
+ if ( ! ($object = $this->getAdapter()->read($path))) {
+ return false;
+ }
+
+ return $object['contents'];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function readStream($path)
+ {
+ $path = Util::normalizePath($path);
+ $this->assertPresent($path);
+
+ if ( ! $object = $this->getAdapter()->readStream($path)) {
+ return false;
+ }
+
+ return $object['stream'];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function rename($path, $newpath)
+ {
+ $path = Util::normalizePath($path);
+ $newpath = Util::normalizePath($newpath);
+ $this->assertPresent($path);
+ $this->assertAbsent($newpath);
+
+ return (bool) $this->getAdapter()->rename($path, $newpath);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function copy($path, $newpath)
+ {
+ $path = Util::normalizePath($path);
+ $newpath = Util::normalizePath($newpath);
+ $this->assertPresent($path);
+ $this->assertAbsent($newpath);
+
+ return $this->getAdapter()->copy($path, $newpath);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function delete($path)
+ {
+ $path = Util::normalizePath($path);
+ $this->assertPresent($path);
+
+ return $this->getAdapter()->delete($path);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function deleteDir($dirname)
+ {
+ $dirname = Util::normalizePath($dirname);
+
+ if ($dirname === '') {
+ throw new RootViolationException('Root directories can not be deleted.');
+ }
+
+ return (bool) $this->getAdapter()->deleteDir($dirname);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function createDir($dirname, array $config = [])
+ {
+ $dirname = Util::normalizePath($dirname);
+ $config = $this->prepareConfig($config);
+
+ return (bool) $this->getAdapter()->createDir($dirname, $config);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function listContents($directory = '', $recursive = false)
+ {
+ $directory = Util::normalizePath($directory);
+ $contents = $this->getAdapter()->listContents($directory, $recursive);
+
+ return (new ContentListingFormatter($directory, $recursive, $this->config->get('case_sensitive', true)))
+ ->formatListing($contents);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getMimetype($path)
+ {
+ $path = Util::normalizePath($path);
+ $this->assertPresent($path);
+
+ if (( ! $object = $this->getAdapter()->getMimetype($path)) || ! array_key_exists('mimetype', $object)) {
+ return false;
+ }
+
+ return $object['mimetype'];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getTimestamp($path)
+ {
+ $path = Util::normalizePath($path);
+ $this->assertPresent($path);
+
+ if (( ! $object = $this->getAdapter()->getTimestamp($path)) || ! array_key_exists('timestamp', $object)) {
+ return false;
+ }
+
+ return $object['timestamp'];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getVisibility($path)
+ {
+ $path = Util::normalizePath($path);
+ $this->assertPresent($path);
+
+ if (( ! $object = $this->getAdapter()->getVisibility($path)) || ! array_key_exists('visibility', $object)) {
+ return false;
+ }
+
+ return $object['visibility'];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getSize($path)
+ {
+ $path = Util::normalizePath($path);
+ $this->assertPresent($path);
+
+ if (( ! $object = $this->getAdapter()->getSize($path)) || ! array_key_exists('size', $object)) {
+ return false;
+ }
+
+ return (int) $object['size'];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setVisibility($path, $visibility)
+ {
+ $path = Util::normalizePath($path);
+ $this->assertPresent($path);
+
+ return (bool) $this->getAdapter()->setVisibility($path, $visibility);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getMetadata($path)
+ {
+ $path = Util::normalizePath($path);
+ $this->assertPresent($path);
+
+ return $this->getAdapter()->getMetadata($path);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function get($path, Handler $handler = null)
+ {
+ $path = Util::normalizePath($path);
+
+ if ( ! $handler) {
+ $metadata = $this->getMetadata($path);
+ $handler = ($metadata && $metadata['type'] === 'file') ? new File($this, $path) : new Directory($this, $path);
+ }
+
+ $handler->setPath($path);
+ $handler->setFilesystem($this);
+
+ return $handler;
+ }
+
+ /**
+ * Assert a file is present.
+ *
+ * @param string $path path to file
+ *
+ * @throws FileNotFoundException
+ *
+ * @return void
+ */
+ public function assertPresent($path)
+ {
+ if ($this->config->get('disable_asserts', false) === false && ! $this->has($path)) {
+ throw new FileNotFoundException($path);
+ }
+ }
+
+ /**
+ * Assert a file is absent.
+ *
+ * @param string $path path to file
+ *
+ * @throws FileExistsException
+ *
+ * @return void
+ */
+ public function assertAbsent($path)
+ {
+ if ($this->config->get('disable_asserts', false) === false && $this->has($path)) {
+ throw new FileExistsException($path);
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/FilesystemException.php b/plugins/panakour/backup/vendor/league/flysystem/src/FilesystemException.php
new file mode 100644
index 000000000..3121e533d
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/FilesystemException.php
@@ -0,0 +1,7 @@
+path = $path;
+ $this->filesystem = $filesystem;
+ }
+
+ /**
+ * Check whether the entree is a directory.
+ *
+ * @return bool
+ */
+ public function isDir()
+ {
+ return $this->getType() === 'dir';
+ }
+
+ /**
+ * Check whether the entree is a file.
+ *
+ * @return bool
+ */
+ public function isFile()
+ {
+ return $this->getType() === 'file';
+ }
+
+ /**
+ * Retrieve the entree type (file|dir).
+ *
+ * @return string file or dir
+ */
+ public function getType()
+ {
+ $metadata = $this->filesystem->getMetadata($this->path);
+
+ return $metadata ? $metadata['type'] : 'dir';
+ }
+
+ /**
+ * Set the Filesystem object.
+ *
+ * @param FilesystemInterface $filesystem
+ *
+ * @return $this
+ */
+ public function setFilesystem(FilesystemInterface $filesystem)
+ {
+ $this->filesystem = $filesystem;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve the Filesystem object.
+ *
+ * @return FilesystemInterface
+ */
+ public function getFilesystem()
+ {
+ return $this->filesystem;
+ }
+
+ /**
+ * Set the entree path.
+ *
+ * @param string $path
+ *
+ * @return $this
+ */
+ public function setPath($path)
+ {
+ $this->path = $path;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve the entree path.
+ *
+ * @return string path
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * Plugins pass-through.
+ *
+ * @param string $method
+ * @param array $arguments
+ *
+ * @return mixed
+ */
+ public function __call($method, array $arguments)
+ {
+ array_unshift($arguments, $this->path);
+ $callback = [$this->filesystem, $method];
+
+ try {
+ return call_user_func_array($callback, $arguments);
+ } catch (BadMethodCallException $e) {
+ throw new BadMethodCallException(
+ 'Call to undefined method '
+ . get_called_class()
+ . '::' . $method
+ );
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/InvalidRootException.php b/plugins/panakour/backup/vendor/league/flysystem/src/InvalidRootException.php
new file mode 100644
index 000000000..468d1d58c
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/InvalidRootException.php
@@ -0,0 +1,9 @@
+ Filesystem,]
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct(array $filesystems = [])
+ {
+ $this->mountFilesystems($filesystems);
+ }
+
+ /**
+ * Mount filesystems.
+ *
+ * @param FilesystemInterface[] $filesystems [:prefix => Filesystem,]
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return $this
+ */
+ public function mountFilesystems(array $filesystems)
+ {
+ foreach ($filesystems as $prefix => $filesystem) {
+ $this->mountFilesystem($prefix, $filesystem);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Mount filesystems.
+ *
+ * @param string $prefix
+ * @param FilesystemInterface $filesystem
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return $this
+ */
+ public function mountFilesystem($prefix, FilesystemInterface $filesystem)
+ {
+ if ( ! is_string($prefix)) {
+ throw new InvalidArgumentException(__METHOD__ . ' expects argument #1 to be a string.');
+ }
+
+ $this->filesystems[$prefix] = $filesystem;
+
+ return $this;
+ }
+
+ /**
+ * Get the filesystem with the corresponding prefix.
+ *
+ * @param string $prefix
+ *
+ * @throws FilesystemNotFoundException
+ *
+ * @return FilesystemInterface
+ */
+ public function getFilesystem($prefix)
+ {
+ if ( ! isset($this->filesystems[$prefix])) {
+ throw new FilesystemNotFoundException('No filesystem mounted with prefix ' . $prefix);
+ }
+
+ return $this->filesystems[$prefix];
+ }
+
+ /**
+ * Retrieve the prefix from an arguments array.
+ *
+ * @param array $arguments
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return array [:prefix, :arguments]
+ */
+ public function filterPrefix(array $arguments)
+ {
+ if (empty($arguments)) {
+ throw new InvalidArgumentException('At least one argument needed');
+ }
+
+ $path = array_shift($arguments);
+
+ if ( ! is_string($path)) {
+ throw new InvalidArgumentException('First argument should be a string');
+ }
+
+ list($prefix, $path) = $this->getPrefixAndPath($path);
+ array_unshift($arguments, $path);
+
+ return [$prefix, $arguments];
+ }
+
+ /**
+ * @param string $directory
+ * @param bool $recursive
+ *
+ * @throws InvalidArgumentException
+ * @throws FilesystemNotFoundException
+ *
+ * @return array
+ */
+ public function listContents($directory = '', $recursive = false)
+ {
+ list($prefix, $directory) = $this->getPrefixAndPath($directory);
+ $filesystem = $this->getFilesystem($prefix);
+ $result = $filesystem->listContents($directory, $recursive);
+
+ foreach ($result as &$file) {
+ $file['filesystem'] = $prefix;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Call forwarder.
+ *
+ * @param string $method
+ * @param array $arguments
+ *
+ * @throws InvalidArgumentException
+ * @throws FilesystemNotFoundException
+ *
+ * @return mixed
+ */
+ public function __call($method, $arguments)
+ {
+ list($prefix, $arguments) = $this->filterPrefix($arguments);
+
+ return $this->invokePluginOnFilesystem($method, $arguments, $prefix);
+ }
+
+ /**
+ * @param string $from
+ * @param string $to
+ * @param array $config
+ *
+ * @throws InvalidArgumentException
+ * @throws FilesystemNotFoundException
+ * @throws FileExistsException
+ *
+ * @return bool
+ */
+ public function copy($from, $to, array $config = [])
+ {
+ list($prefixFrom, $from) = $this->getPrefixAndPath($from);
+
+ $buffer = $this->getFilesystem($prefixFrom)->readStream($from);
+
+ if ($buffer === false) {
+ return false;
+ }
+
+ list($prefixTo, $to) = $this->getPrefixAndPath($to);
+
+ $result = $this->getFilesystem($prefixTo)->writeStream($to, $buffer, $config);
+
+ if (is_resource($buffer)) {
+ fclose($buffer);
+ }
+
+ return $result;
+ }
+
+ /**
+ * List with plugin adapter.
+ *
+ * @param array $keys
+ * @param string $directory
+ * @param bool $recursive
+ *
+ * @throws InvalidArgumentException
+ * @throws FilesystemNotFoundException
+ *
+ * @return array
+ */
+ public function listWith(array $keys = [], $directory = '', $recursive = false)
+ {
+ list($prefix, $directory) = $this->getPrefixAndPath($directory);
+ $arguments = [$keys, $directory, $recursive];
+
+ return $this->invokePluginOnFilesystem('listWith', $arguments, $prefix);
+ }
+
+ /**
+ * Move a file.
+ *
+ * @param string $from
+ * @param string $to
+ * @param array $config
+ *
+ * @throws InvalidArgumentException
+ * @throws FilesystemNotFoundException
+ *
+ * @return bool
+ */
+ public function move($from, $to, array $config = [])
+ {
+ list($prefixFrom, $pathFrom) = $this->getPrefixAndPath($from);
+ list($prefixTo, $pathTo) = $this->getPrefixAndPath($to);
+
+ if ($prefixFrom === $prefixTo) {
+ $filesystem = $this->getFilesystem($prefixFrom);
+ $renamed = $filesystem->rename($pathFrom, $pathTo);
+
+ if ($renamed && isset($config['visibility'])) {
+ return $filesystem->setVisibility($pathTo, $config['visibility']);
+ }
+
+ return $renamed;
+ }
+
+ $copied = $this->copy($from, $to, $config);
+
+ if ($copied) {
+ return $this->delete($from);
+ }
+
+ return false;
+ }
+
+ /**
+ * Invoke a plugin on a filesystem mounted on a given prefix.
+ *
+ * @param string $method
+ * @param array $arguments
+ * @param string $prefix
+ *
+ * @throws FilesystemNotFoundException
+ *
+ * @return mixed
+ */
+ public function invokePluginOnFilesystem($method, $arguments, $prefix)
+ {
+ $filesystem = $this->getFilesystem($prefix);
+
+ try {
+ return $this->invokePlugin($method, $arguments, $filesystem);
+ } catch (PluginNotFoundException $e) {
+ // Let it pass, it's ok, don't panic.
+ }
+
+ $callback = [$filesystem, $method];
+
+ return call_user_func_array($callback, $arguments);
+ }
+
+ /**
+ * @param string $path
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return string[] [:prefix, :path]
+ */
+ protected function getPrefixAndPath($path)
+ {
+ if (strpos($path, '://') < 1) {
+ throw new InvalidArgumentException('No prefix detected in path: ' . $path);
+ }
+
+ return explode('://', $path, 2);
+ }
+
+ /**
+ * Check whether a file exists.
+ *
+ * @param string $path
+ *
+ * @return bool
+ */
+ public function has($path)
+ {
+ list($prefix, $path) = $this->getPrefixAndPath($path);
+
+ return $this->getFilesystem($prefix)->has($path);
+ }
+
+ /**
+ * Read a file.
+ *
+ * @param string $path The path to the file.
+ *
+ * @throws FileNotFoundException
+ *
+ * @return string|false The file contents or false on failure.
+ */
+ public function read($path)
+ {
+ list($prefix, $path) = $this->getPrefixAndPath($path);
+
+ return $this->getFilesystem($prefix)->read($path);
+ }
+
+ /**
+ * Retrieves a read-stream for a path.
+ *
+ * @param string $path The path to the file.
+ *
+ * @throws FileNotFoundException
+ *
+ * @return resource|false The path resource or false on failure.
+ */
+ public function readStream($path)
+ {
+ list($prefix, $path) = $this->getPrefixAndPath($path);
+
+ return $this->getFilesystem($prefix)->readStream($path);
+ }
+
+ /**
+ * Get a file's metadata.
+ *
+ * @param string $path The path to the file.
+ *
+ * @throws FileNotFoundException
+ *
+ * @return array|false The file metadata or false on failure.
+ */
+ public function getMetadata($path)
+ {
+ list($prefix, $path) = $this->getPrefixAndPath($path);
+
+ return $this->getFilesystem($prefix)->getMetadata($path);
+ }
+
+ /**
+ * Get a file's size.
+ *
+ * @param string $path The path to the file.
+ *
+ * @throws FileNotFoundException
+ *
+ * @return int|false The file size or false on failure.
+ */
+ public function getSize($path)
+ {
+ list($prefix, $path) = $this->getPrefixAndPath($path);
+
+ return $this->getFilesystem($prefix)->getSize($path);
+ }
+
+ /**
+ * Get a file's mime-type.
+ *
+ * @param string $path The path to the file.
+ *
+ * @throws FileNotFoundException
+ *
+ * @return string|false The file mime-type or false on failure.
+ */
+ public function getMimetype($path)
+ {
+ list($prefix, $path) = $this->getPrefixAndPath($path);
+
+ return $this->getFilesystem($prefix)->getMimetype($path);
+ }
+
+ /**
+ * Get a file's timestamp.
+ *
+ * @param string $path The path to the file.
+ *
+ * @throws FileNotFoundException
+ *
+ * @return string|false The timestamp or false on failure.
+ */
+ public function getTimestamp($path)
+ {
+ list($prefix, $path) = $this->getPrefixAndPath($path);
+
+ return $this->getFilesystem($prefix)->getTimestamp($path);
+ }
+
+ /**
+ * Get a file's visibility.
+ *
+ * @param string $path The path to the file.
+ *
+ * @throws FileNotFoundException
+ *
+ * @return string|false The visibility (public|private) or false on failure.
+ */
+ public function getVisibility($path)
+ {
+ list($prefix, $path) = $this->getPrefixAndPath($path);
+
+ return $this->getFilesystem($prefix)->getVisibility($path);
+ }
+
+ /**
+ * Write a new file.
+ *
+ * @param string $path The path of the new file.
+ * @param string $contents The file contents.
+ * @param array $config An optional configuration array.
+ *
+ * @throws FileExistsException
+ *
+ * @return bool True on success, false on failure.
+ */
+ public function write($path, $contents, array $config = [])
+ {
+ list($prefix, $path) = $this->getPrefixAndPath($path);
+
+ return $this->getFilesystem($prefix)->write($path, $contents, $config);
+ }
+
+ /**
+ * Write a new file using a stream.
+ *
+ * @param string $path The path of the new file.
+ * @param resource $resource The file handle.
+ * @param array $config An optional configuration array.
+ *
+ * @throws InvalidArgumentException If $resource is not a file handle.
+ * @throws FileExistsException
+ *
+ * @return bool True on success, false on failure.
+ */
+ public function writeStream($path, $resource, array $config = [])
+ {
+ list($prefix, $path) = $this->getPrefixAndPath($path);
+
+ return $this->getFilesystem($prefix)->writeStream($path, $resource, $config);
+ }
+
+ /**
+ * Update an existing file.
+ *
+ * @param string $path The path of the existing file.
+ * @param string $contents The file contents.
+ * @param array $config An optional configuration array.
+ *
+ * @throws FileNotFoundException
+ *
+ * @return bool True on success, false on failure.
+ */
+ public function update($path, $contents, array $config = [])
+ {
+ list($prefix, $path) = $this->getPrefixAndPath($path);
+
+ return $this->getFilesystem($prefix)->update($path, $contents, $config);
+ }
+
+ /**
+ * Update an existing file using a stream.
+ *
+ * @param string $path The path of the existing file.
+ * @param resource $resource The file handle.
+ * @param array $config An optional configuration array.
+ *
+ * @throws InvalidArgumentException If $resource is not a file handle.
+ * @throws FileNotFoundException
+ *
+ * @return bool True on success, false on failure.
+ */
+ public function updateStream($path, $resource, array $config = [])
+ {
+ list($prefix, $path) = $this->getPrefixAndPath($path);
+
+ return $this->getFilesystem($prefix)->updateStream($path, $resource, $config);
+ }
+
+ /**
+ * Rename a file.
+ *
+ * @param string $path Path to the existing file.
+ * @param string $newpath The new path of the file.
+ *
+ * @throws FileExistsException Thrown if $newpath exists.
+ * @throws FileNotFoundException Thrown if $path does not exist.
+ *
+ * @return bool True on success, false on failure.
+ */
+ public function rename($path, $newpath)
+ {
+ list($prefix, $path) = $this->getPrefixAndPath($path);
+
+ return $this->getFilesystem($prefix)->rename($path, $newpath);
+ }
+
+ /**
+ * Delete a file.
+ *
+ * @param string $path
+ *
+ * @throws FileNotFoundException
+ *
+ * @return bool True on success, false on failure.
+ */
+ public function delete($path)
+ {
+ list($prefix, $path) = $this->getPrefixAndPath($path);
+
+ return $this->getFilesystem($prefix)->delete($path);
+ }
+
+ /**
+ * Delete a directory.
+ *
+ * @param string $dirname
+ *
+ * @throws RootViolationException Thrown if $dirname is empty.
+ *
+ * @return bool True on success, false on failure.
+ */
+ public function deleteDir($dirname)
+ {
+ list($prefix, $dirname) = $this->getPrefixAndPath($dirname);
+
+ return $this->getFilesystem($prefix)->deleteDir($dirname);
+ }
+
+ /**
+ * Create a directory.
+ *
+ * @param string $dirname The name of the new directory.
+ * @param array $config An optional configuration array.
+ *
+ * @return bool True on success, false on failure.
+ */
+ public function createDir($dirname, array $config = [])
+ {
+ list($prefix, $dirname) = $this->getPrefixAndPath($dirname);
+
+ return $this->getFilesystem($prefix)->createDir($dirname);
+ }
+
+ /**
+ * Set the visibility for a file.
+ *
+ * @param string $path The path to the file.
+ * @param string $visibility One of 'public' or 'private'.
+ *
+ * @throws FileNotFoundException
+ *
+ * @return bool True on success, false on failure.
+ */
+ public function setVisibility($path, $visibility)
+ {
+ list($prefix, $path) = $this->getPrefixAndPath($path);
+
+ return $this->getFilesystem($prefix)->setVisibility($path, $visibility);
+ }
+
+ /**
+ * Create a file or update if exists.
+ *
+ * @param string $path The path to the file.
+ * @param string $contents The file contents.
+ * @param array $config An optional configuration array.
+ *
+ * @return bool True on success, false on failure.
+ */
+ public function put($path, $contents, array $config = [])
+ {
+ list($prefix, $path) = $this->getPrefixAndPath($path);
+
+ return $this->getFilesystem($prefix)->put($path, $contents, $config);
+ }
+
+ /**
+ * Create a file or update if exists.
+ *
+ * @param string $path The path to the file.
+ * @param resource $resource The file handle.
+ * @param array $config An optional configuration array.
+ *
+ * @throws InvalidArgumentException Thrown if $resource is not a resource.
+ *
+ * @return bool True on success, false on failure.
+ */
+ public function putStream($path, $resource, array $config = [])
+ {
+ list($prefix, $path) = $this->getPrefixAndPath($path);
+
+ return $this->getFilesystem($prefix)->putStream($path, $resource, $config);
+ }
+
+ /**
+ * Read and delete a file.
+ *
+ * @param string $path The path to the file.
+ *
+ * @throws FileNotFoundException
+ *
+ * @return string|false The file contents, or false on failure.
+ */
+ public function readAndDelete($path)
+ {
+ list($prefix, $path) = $this->getPrefixAndPath($path);
+
+ return $this->getFilesystem($prefix)->readAndDelete($path);
+ }
+
+ /**
+ * Get a file/directory handler.
+ *
+ * @deprecated
+ *
+ * @param string $path The path to the file.
+ * @param Handler $handler An optional existing handler to populate.
+ *
+ * @return Handler Either a file or directory handler.
+ */
+ public function get($path, Handler $handler = null)
+ {
+ list($prefix, $path) = $this->getPrefixAndPath($path);
+
+ return $this->getFilesystem($prefix)->get($path);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/NotSupportedException.php b/plugins/panakour/backup/vendor/league/flysystem/src/NotSupportedException.php
new file mode 100644
index 000000000..e0a989b22
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/NotSupportedException.php
@@ -0,0 +1,37 @@
+getPathname());
+ }
+
+ /**
+ * Create a new exception for a link.
+ *
+ * @param string $systemType
+ *
+ * @return static
+ */
+ public static function forFtpSystemType($systemType)
+ {
+ $message = "The FTP system type '$systemType' is currently not supported.";
+
+ return new static($message);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/AbstractPlugin.php b/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/AbstractPlugin.php
new file mode 100644
index 000000000..0d5678976
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/AbstractPlugin.php
@@ -0,0 +1,24 @@
+filesystem = $filesystem;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/EmptyDir.php b/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/EmptyDir.php
new file mode 100644
index 000000000..b5ae7f582
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/EmptyDir.php
@@ -0,0 +1,34 @@
+filesystem->listContents($dirname, false);
+
+ foreach ($listing as $item) {
+ if ($item['type'] === 'dir') {
+ $this->filesystem->deleteDir($item['path']);
+ } else {
+ $this->filesystem->delete($item['path']);
+ }
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/ForcedCopy.php b/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/ForcedCopy.php
new file mode 100644
index 000000000..a41e9f3ae
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/ForcedCopy.php
@@ -0,0 +1,44 @@
+filesystem->delete($newpath);
+ } catch (FileNotFoundException $e) {
+ // The destination path does not exist. That's ok.
+ $deleted = true;
+ }
+
+ if ($deleted) {
+ return $this->filesystem->copy($path, $newpath);
+ }
+
+ return false;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/ForcedRename.php b/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/ForcedRename.php
new file mode 100644
index 000000000..3f51cd607
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/ForcedRename.php
@@ -0,0 +1,44 @@
+filesystem->delete($newpath);
+ } catch (FileNotFoundException $e) {
+ // The destination path does not exist. That's ok.
+ $deleted = true;
+ }
+
+ if ($deleted) {
+ return $this->filesystem->rename($path, $newpath);
+ }
+
+ return false;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/GetWithMetadata.php b/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/GetWithMetadata.php
new file mode 100644
index 000000000..6fe4f0562
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/GetWithMetadata.php
@@ -0,0 +1,51 @@
+filesystem->getMetadata($path);
+
+ if ( ! $object) {
+ return false;
+ }
+
+ $keys = array_diff($metadata, array_keys($object));
+
+ foreach ($keys as $key) {
+ if ( ! method_exists($this->filesystem, $method = 'get' . ucfirst($key))) {
+ throw new InvalidArgumentException('Could not fetch metadata: ' . $key);
+ }
+
+ $object[$key] = $this->filesystem->{$method}($path);
+ }
+
+ return $object;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/ListFiles.php b/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/ListFiles.php
new file mode 100644
index 000000000..9669fe7e7
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/ListFiles.php
@@ -0,0 +1,35 @@
+filesystem->listContents($directory, $recursive);
+
+ $filter = function ($object) {
+ return $object['type'] === 'file';
+ };
+
+ return array_values(array_filter($contents, $filter));
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/ListPaths.php b/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/ListPaths.php
new file mode 100644
index 000000000..514bdf0b3
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/ListPaths.php
@@ -0,0 +1,36 @@
+filesystem->listContents($directory, $recursive);
+
+ foreach ($contents as $object) {
+ $result[] = $object['path'];
+ }
+
+ return $result;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/ListWith.php b/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/ListWith.php
new file mode 100644
index 000000000..d90464e52
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/ListWith.php
@@ -0,0 +1,60 @@
+filesystem->listContents($directory, $recursive);
+
+ foreach ($contents as $index => $object) {
+ if ($object['type'] === 'file') {
+ $missingKeys = array_diff($keys, array_keys($object));
+ $contents[$index] = array_reduce($missingKeys, [$this, 'getMetadataByName'], $object);
+ }
+ }
+
+ return $contents;
+ }
+
+ /**
+ * Get a meta-data value by key name.
+ *
+ * @param array $object
+ * @param string $key
+ *
+ * @return array
+ */
+ protected function getMetadataByName(array $object, $key)
+ {
+ $method = 'get' . ucfirst($key);
+
+ if ( ! method_exists($this->filesystem, $method)) {
+ throw new \InvalidArgumentException('Could not get meta-data for key: ' . $key);
+ }
+
+ $object[$key] = $this->filesystem->{$method}($object['path']);
+
+ return $object;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/PluggableTrait.php b/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/PluggableTrait.php
new file mode 100644
index 000000000..922edfe52
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/PluggableTrait.php
@@ -0,0 +1,97 @@
+plugins[$plugin->getMethod()] = $plugin;
+
+ return $this;
+ }
+
+ /**
+ * Find a specific plugin.
+ *
+ * @param string $method
+ *
+ * @throws PluginNotFoundException
+ *
+ * @return PluginInterface
+ */
+ protected function findPlugin($method)
+ {
+ if ( ! isset($this->plugins[$method])) {
+ throw new PluginNotFoundException('Plugin not found for method: ' . $method);
+ }
+
+ return $this->plugins[$method];
+ }
+
+ /**
+ * Invoke a plugin by method name.
+ *
+ * @param string $method
+ * @param array $arguments
+ * @param FilesystemInterface $filesystem
+ *
+ * @throws PluginNotFoundException
+ *
+ * @return mixed
+ */
+ protected function invokePlugin($method, array $arguments, FilesystemInterface $filesystem)
+ {
+ $plugin = $this->findPlugin($method);
+ $plugin->setFilesystem($filesystem);
+ $callback = [$plugin, 'handle'];
+
+ return call_user_func_array($callback, $arguments);
+ }
+
+ /**
+ * Plugins pass-through.
+ *
+ * @param string $method
+ * @param array $arguments
+ *
+ * @throws BadMethodCallException
+ *
+ * @return mixed
+ */
+ public function __call($method, array $arguments)
+ {
+ try {
+ return $this->invokePlugin($method, $arguments, $this);
+ } catch (PluginNotFoundException $e) {
+ throw new BadMethodCallException(
+ 'Call to undefined method '
+ . get_class($this)
+ . '::' . $method
+ );
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/PluginNotFoundException.php b/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/PluginNotFoundException.php
new file mode 100644
index 000000000..fd1d7e7e3
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Plugin/PluginNotFoundException.php
@@ -0,0 +1,10 @@
+hash = spl_object_hash($this);
+ static::$safeStorage[$this->hash] = [];
+ }
+
+ public function storeSafely($key, $value)
+ {
+ static::$safeStorage[$this->hash][$key] = $value;
+ }
+
+ public function retrieveSafely($key)
+ {
+ if (array_key_exists($key, static::$safeStorage[$this->hash])) {
+ return static::$safeStorage[$this->hash][$key];
+ }
+ }
+
+ public function __destruct()
+ {
+ unset(static::$safeStorage[$this->hash]);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/UnreadableFileException.php b/plugins/panakour/backup/vendor/league/flysystem/src/UnreadableFileException.php
new file mode 100644
index 000000000..e66803383
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/UnreadableFileException.php
@@ -0,0 +1,18 @@
+getRealPath()
+ )
+ );
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Util.php b/plugins/panakour/backup/vendor/league/flysystem/src/Util.php
new file mode 100644
index 000000000..76454a05e
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Util.php
@@ -0,0 +1,353 @@
+ ''];
+ }
+
+ /**
+ * Normalize a dirname return value.
+ *
+ * @param string $dirname
+ *
+ * @return string normalized dirname
+ */
+ public static function normalizeDirname($dirname)
+ {
+ return $dirname === '.' ? '' : $dirname;
+ }
+
+ /**
+ * Get a normalized dirname from a path.
+ *
+ * @param string $path
+ *
+ * @return string dirname
+ */
+ public static function dirname($path)
+ {
+ return static::normalizeDirname(dirname($path));
+ }
+
+ /**
+ * Map result arrays.
+ *
+ * @param array $object
+ * @param array $map
+ *
+ * @return array mapped result
+ */
+ public static function map(array $object, array $map)
+ {
+ $result = [];
+
+ foreach ($map as $from => $to) {
+ if ( ! isset($object[$from])) {
+ continue;
+ }
+
+ $result[$to] = $object[$from];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Normalize path.
+ *
+ * @param string $path
+ *
+ * @throws LogicException
+ *
+ * @return string
+ */
+ public static function normalizePath($path)
+ {
+ return static::normalizeRelativePath($path);
+ }
+
+ /**
+ * Normalize relative directories in a path.
+ *
+ * @param string $path
+ *
+ * @throws LogicException
+ *
+ * @return string
+ */
+ public static function normalizeRelativePath($path)
+ {
+ $path = str_replace('\\', '/', $path);
+ $path = static::removeFunkyWhiteSpace($path);
+
+ $parts = [];
+
+ foreach (explode('/', $path) as $part) {
+ switch ($part) {
+ case '':
+ case '.':
+ break;
+
+ case '..':
+ if (empty($parts)) {
+ throw new LogicException(
+ 'Path is outside of the defined root, path: [' . $path . ']'
+ );
+ }
+ array_pop($parts);
+ break;
+
+ default:
+ $parts[] = $part;
+ break;
+ }
+ }
+
+ return implode('/', $parts);
+ }
+
+ /**
+ * Removes unprintable characters and invalid unicode characters.
+ *
+ * @param string $path
+ *
+ * @return string $path
+ */
+ protected static function removeFunkyWhiteSpace($path)
+ {
+ // We do this check in a loop, since removing invalid unicode characters
+ // can lead to new characters being created.
+ while (preg_match('#\p{C}+|^\./#u', $path)) {
+ $path = preg_replace('#\p{C}+|^\./#u', '', $path);
+ }
+
+ return $path;
+ }
+
+ /**
+ * Normalize prefix.
+ *
+ * @param string $prefix
+ * @param string $separator
+ *
+ * @return string normalized path
+ */
+ public static function normalizePrefix($prefix, $separator)
+ {
+ return rtrim($prefix, $separator) . $separator;
+ }
+
+ /**
+ * Get content size.
+ *
+ * @param string $contents
+ *
+ * @return int content size
+ */
+ public static function contentSize($contents)
+ {
+ return defined('MB_OVERLOAD_STRING') ? mb_strlen($contents, '8bit') : strlen($contents);
+ }
+
+ /**
+ * Guess MIME Type based on the path of the file and it's content.
+ *
+ * @param string $path
+ * @param string|resource $content
+ *
+ * @return string|null MIME Type or NULL if no extension detected
+ */
+ public static function guessMimeType($path, $content)
+ {
+ $mimeType = MimeType::detectByContent($content);
+
+ if ( ! (empty($mimeType) || in_array($mimeType, ['application/x-empty', 'text/plain', 'text/x-asm']))) {
+ return $mimeType;
+ }
+
+ return MimeType::detectByFilename($path);
+ }
+
+ /**
+ * Emulate directories.
+ *
+ * @param array $listing
+ *
+ * @return array listing with emulated directories
+ */
+ public static function emulateDirectories(array $listing)
+ {
+ $directories = [];
+ $listedDirectories = [];
+
+ foreach ($listing as $object) {
+ list($directories, $listedDirectories) = static::emulateObjectDirectories($object, $directories, $listedDirectories);
+ }
+
+ $directories = array_diff(array_unique($directories), array_unique($listedDirectories));
+
+ foreach ($directories as $directory) {
+ $listing[] = static::pathinfo($directory) + ['type' => 'dir'];
+ }
+
+ return $listing;
+ }
+
+ /**
+ * Ensure a Config instance.
+ *
+ * @param null|array|Config $config
+ *
+ * @return Config config instance
+ *
+ * @throw LogicException
+ */
+ public static function ensureConfig($config)
+ {
+ if ($config === null) {
+ return new Config();
+ }
+
+ if ($config instanceof Config) {
+ return $config;
+ }
+
+ if (is_array($config)) {
+ return new Config($config);
+ }
+
+ throw new LogicException('A config should either be an array or a Flysystem\Config object.');
+ }
+
+ /**
+ * Rewind a stream.
+ *
+ * @param resource $resource
+ */
+ public static function rewindStream($resource)
+ {
+ if (ftell($resource) !== 0 && static::isSeekableStream($resource)) {
+ rewind($resource);
+ }
+ }
+
+ public static function isSeekableStream($resource)
+ {
+ $metadata = stream_get_meta_data($resource);
+
+ return $metadata['seekable'];
+ }
+
+ /**
+ * Get the size of a stream.
+ *
+ * @param resource $resource
+ *
+ * @return int|null stream size
+ */
+ public static function getStreamSize($resource)
+ {
+ $stat = fstat($resource);
+
+ if ( ! is_array($stat) || ! isset($stat['size'])) {
+ return null;
+ }
+
+ return $stat['size'];
+ }
+
+ /**
+ * Emulate the directories of a single object.
+ *
+ * @param array $object
+ * @param array $directories
+ * @param array $listedDirectories
+ *
+ * @return array
+ */
+ protected static function emulateObjectDirectories(array $object, array $directories, array $listedDirectories)
+ {
+ if ($object['type'] === 'dir') {
+ $listedDirectories[] = $object['path'];
+ }
+
+ if ( ! isset($object['dirname']) || trim($object['dirname']) === '') {
+ return [$directories, $listedDirectories];
+ }
+
+ $parent = $object['dirname'];
+
+ while (isset($parent) && trim($parent) !== '' && ! in_array($parent, $directories)) {
+ $directories[] = $parent;
+ $parent = static::dirname($parent);
+ }
+
+ if (isset($object['type']) && $object['type'] === 'dir') {
+ $listedDirectories[] = $object['path'];
+
+ return [$directories, $listedDirectories];
+ }
+
+ return [$directories, $listedDirectories];
+ }
+
+ /**
+ * Returns the trailing name component of the path.
+ *
+ * @param string $path
+ *
+ * @return string
+ */
+ private static function basename($path)
+ {
+ $separators = DIRECTORY_SEPARATOR === '/' ? '/' : '\/';
+
+ $path = rtrim($path, $separators);
+
+ $basename = preg_replace('#.*?([^' . preg_quote($separators, '#') . ']+$)#', '$1', $path);
+
+ if (DIRECTORY_SEPARATOR === '/') {
+ return $basename;
+ }
+ // @codeCoverageIgnoreStart
+ // Extra Windows path munging. This is tested via AppVeyor, but code
+ // coverage is not reported.
+
+ // Handle relative paths with drive letters. c:file.txt.
+ while (preg_match('#^[a-zA-Z]{1}:[^\\\/]#', $basename)) {
+ $basename = substr($basename, 2);
+ }
+
+ // Remove colon for standalone drive letter names.
+ if (preg_match('#^[a-zA-Z]{1}:$#', $basename)) {
+ $basename = rtrim($basename, ':');
+ }
+
+ return $basename;
+ // @codeCoverageIgnoreEnd
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Util/ContentListingFormatter.php b/plugins/panakour/backup/vendor/league/flysystem/src/Util/ContentListingFormatter.php
new file mode 100644
index 000000000..ae0d3b91d
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Util/ContentListingFormatter.php
@@ -0,0 +1,122 @@
+directory = rtrim($directory, '/');
+ $this->recursive = $recursive;
+ $this->caseSensitive = $caseSensitive;
+ }
+
+ /**
+ * Format contents listing.
+ *
+ * @param array $listing
+ *
+ * @return array
+ */
+ public function formatListing(array $listing)
+ {
+ $listing = array_filter(array_map([$this, 'addPathInfo'], $listing), [$this, 'isEntryOutOfScope']);
+
+ return $this->sortListing(array_values($listing));
+ }
+
+ private function addPathInfo(array $entry)
+ {
+ return $entry + Util::pathinfo($entry['path']);
+ }
+
+ /**
+ * Determine if the entry is out of scope.
+ *
+ * @param array $entry
+ *
+ * @return bool
+ */
+ private function isEntryOutOfScope(array $entry)
+ {
+ if (empty($entry['path']) && $entry['path'] !== '0') {
+ return false;
+ }
+
+ if ($this->recursive) {
+ return $this->residesInDirectory($entry);
+ }
+
+ return $this->isDirectChild($entry);
+ }
+
+ /**
+ * Check if the entry resides within the parent directory.
+ *
+ * @param array $entry
+ *
+ * @return bool
+ */
+ private function residesInDirectory(array $entry)
+ {
+ if ($this->directory === '') {
+ return true;
+ }
+
+ return $this->caseSensitive
+ ? strpos($entry['path'], $this->directory . '/') === 0
+ : stripos($entry['path'], $this->directory . '/') === 0;
+ }
+
+ /**
+ * Check if the entry is a direct child of the directory.
+ *
+ * @param array $entry
+ *
+ * @return bool
+ */
+ private function isDirectChild(array $entry)
+ {
+ return $this->caseSensitive
+ ? $entry['dirname'] === $this->directory
+ : strcasecmp($this->directory, $entry['dirname']) === 0;
+ }
+
+ /**
+ * @param array $listing
+ *
+ * @return array
+ */
+ private function sortListing(array $listing)
+ {
+ usort($listing, function ($a, $b) {
+ return strcasecmp($a['path'], $b['path']);
+ });
+
+ return $listing;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Util/MimeType.php b/plugins/panakour/backup/vendor/league/flysystem/src/Util/MimeType.php
new file mode 100644
index 000000000..84792c5b4
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Util/MimeType.php
@@ -0,0 +1,248 @@
+ 'application/mac-binhex40',
+ 'cpt' => 'application/mac-compactpro',
+ 'csv' => 'text/csv',
+ 'bin' => 'application/octet-stream',
+ 'dms' => 'application/octet-stream',
+ 'lha' => 'application/octet-stream',
+ 'lzh' => 'application/octet-stream',
+ 'exe' => 'application/octet-stream',
+ 'class' => 'application/octet-stream',
+ 'psd' => 'application/x-photoshop',
+ 'so' => 'application/octet-stream',
+ 'sea' => 'application/octet-stream',
+ 'dll' => 'application/octet-stream',
+ 'oda' => 'application/oda',
+ 'pdf' => 'application/pdf',
+ 'ai' => 'application/pdf',
+ 'eps' => 'application/postscript',
+ 'epub' => 'application/epub+zip',
+ 'ps' => 'application/postscript',
+ 'smi' => 'application/smil',
+ 'smil' => 'application/smil',
+ 'mif' => 'application/vnd.mif',
+ 'xls' => 'application/vnd.ms-excel',
+ 'xlt' => 'application/vnd.ms-excel',
+ 'xla' => 'application/vnd.ms-excel',
+ 'ppt' => 'application/powerpoint',
+ 'pot' => 'application/vnd.ms-powerpoint',
+ 'pps' => 'application/vnd.ms-powerpoint',
+ 'ppa' => 'application/vnd.ms-powerpoint',
+ 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
+ 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
+ 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
+ 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
+ 'potm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
+ 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
+ 'wbxml' => 'application/wbxml',
+ 'wmlc' => 'application/wmlc',
+ 'dcr' => 'application/x-director',
+ 'dir' => 'application/x-director',
+ 'dxr' => 'application/x-director',
+ 'dvi' => 'application/x-dvi',
+ 'gtar' => 'application/x-gtar',
+ 'gz' => 'application/x-gzip',
+ 'gzip' => 'application/x-gzip',
+ 'php' => 'application/x-httpd-php',
+ 'php4' => 'application/x-httpd-php',
+ 'php3' => 'application/x-httpd-php',
+ 'phtml' => 'application/x-httpd-php',
+ 'phps' => 'application/x-httpd-php-source',
+ 'js' => 'application/javascript',
+ 'swf' => 'application/x-shockwave-flash',
+ 'sit' => 'application/x-stuffit',
+ 'tar' => 'application/x-tar',
+ 'tgz' => 'application/x-tar',
+ 'z' => 'application/x-compress',
+ 'xhtml' => 'application/xhtml+xml',
+ 'xht' => 'application/xhtml+xml',
+ 'rdf' => 'application/rdf+xml',
+ 'zip' => 'application/x-zip',
+ 'rar' => 'application/x-rar',
+ 'mid' => 'audio/midi',
+ 'midi' => 'audio/midi',
+ 'mpga' => 'audio/mpeg',
+ 'mp2' => 'audio/mpeg',
+ 'mp3' => 'audio/mpeg',
+ 'aif' => 'audio/x-aiff',
+ 'aiff' => 'audio/x-aiff',
+ 'aifc' => 'audio/x-aiff',
+ 'ram' => 'audio/x-pn-realaudio',
+ 'rm' => 'audio/x-pn-realaudio',
+ 'rpm' => 'audio/x-pn-realaudio-plugin',
+ 'ra' => 'audio/x-realaudio',
+ 'rv' => 'video/vnd.rn-realvideo',
+ 'wav' => 'audio/x-wav',
+ 'jpg' => 'image/jpeg',
+ 'jpeg' => 'image/jpeg',
+ 'jpe' => 'image/jpeg',
+ 'png' => 'image/png',
+ 'gif' => 'image/gif',
+ 'bmp' => 'image/bmp',
+ 'tiff' => 'image/tiff',
+ 'tif' => 'image/tiff',
+ 'svg' => 'image/svg+xml',
+ 'css' => 'text/css',
+ 'html' => 'text/html',
+ 'htm' => 'text/html',
+ 'shtml' => 'text/html',
+ 'txt' => 'text/plain',
+ 'text' => 'text/plain',
+ 'log' => 'text/plain',
+ 'markdown' => 'text/markdown',
+ 'md' => 'text/markdown',
+ 'rtx' => 'text/richtext',
+ 'rtf' => 'text/rtf',
+ 'xml' => 'application/xml',
+ 'xsl' => 'application/xml',
+ 'dmn' => 'application/octet-stream',
+ 'bpmn' => 'application/octet-stream',
+ 'mpeg' => 'video/mpeg',
+ 'mpg' => 'video/mpeg',
+ 'mpe' => 'video/mpeg',
+ 'qt' => 'video/quicktime',
+ 'mov' => 'video/quicktime',
+ 'avi' => 'video/x-msvideo',
+ 'movie' => 'video/x-sgi-movie',
+ 'doc' => 'application/msword',
+ 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'docm' => 'application/vnd.ms-word.template.macroEnabled.12',
+ 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12',
+ 'dot' => 'application/msword',
+ 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
+ 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
+ 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12',
+ 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12',
+ 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
+ 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
+ 'word' => 'application/msword',
+ 'xl' => 'application/excel',
+ 'eml' => 'message/rfc822',
+ 'json' => 'application/json',
+ 'pem' => 'application/x-x509-user-cert',
+ 'p10' => 'application/x-pkcs10',
+ 'p12' => 'application/x-pkcs12',
+ 'p7a' => 'application/x-pkcs7-signature',
+ 'p7c' => 'application/pkcs7-mime',
+ 'p7m' => 'application/pkcs7-mime',
+ 'p7r' => 'application/x-pkcs7-certreqresp',
+ 'p7s' => 'application/pkcs7-signature',
+ 'crt' => 'application/x-x509-ca-cert',
+ 'crl' => 'application/pkix-crl',
+ 'der' => 'application/x-x509-ca-cert',
+ 'kdb' => 'application/octet-stream',
+ 'pgp' => 'application/pgp',
+ 'gpg' => 'application/gpg-keys',
+ 'sst' => 'application/octet-stream',
+ 'csr' => 'application/octet-stream',
+ 'rsa' => 'application/x-pkcs7',
+ 'cer' => 'application/pkix-cert',
+ '3g2' => 'video/3gpp2',
+ '3gp' => 'video/3gp',
+ 'mp4' => 'video/mp4',
+ 'm4a' => 'audio/x-m4a',
+ 'f4v' => 'video/mp4',
+ 'webm' => 'video/webm',
+ 'aac' => 'audio/x-acc',
+ 'm4u' => 'application/vnd.mpegurl',
+ 'm3u' => 'text/plain',
+ 'xspf' => 'application/xspf+xml',
+ 'vlc' => 'application/videolan',
+ 'wmv' => 'video/x-ms-wmv',
+ 'au' => 'audio/x-au',
+ 'ac3' => 'audio/ac3',
+ 'flac' => 'audio/x-flac',
+ 'ogg' => 'audio/ogg',
+ 'kmz' => 'application/vnd.google-earth.kmz',
+ 'kml' => 'application/vnd.google-earth.kml+xml',
+ 'ics' => 'text/calendar',
+ 'zsh' => 'text/x-scriptzsh',
+ '7zip' => 'application/x-7z-compressed',
+ 'cdr' => 'application/cdr',
+ 'wma' => 'audio/x-ms-wma',
+ 'jar' => 'application/java-archive',
+ 'tex' => 'application/x-tex',
+ 'latex' => 'application/x-latex',
+ 'odt' => 'application/vnd.oasis.opendocument.text',
+ 'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
+ 'odp' => 'application/vnd.oasis.opendocument.presentation',
+ 'odg' => 'application/vnd.oasis.opendocument.graphics',
+ 'odc' => 'application/vnd.oasis.opendocument.chart',
+ 'odf' => 'application/vnd.oasis.opendocument.formula',
+ 'odi' => 'application/vnd.oasis.opendocument.image',
+ 'odm' => 'application/vnd.oasis.opendocument.text-master',
+ 'odb' => 'application/vnd.oasis.opendocument.database',
+ 'ott' => 'application/vnd.oasis.opendocument.text-template',
+ 'webp' => 'image/webp',
+ ];
+
+ /**
+ * Detects MIME Type based on given content.
+ *
+ * @param mixed $content
+ *
+ * @return string|null MIME Type or NULL if no mime type detected
+ */
+ public static function detectByContent($content)
+ {
+ if ( ! class_exists('finfo') || ! is_string($content)) {
+ return null;
+ }
+ try {
+ $finfo = new finfo(FILEINFO_MIME_TYPE);
+
+ return $finfo->buffer($content) ?: null;
+ // @codeCoverageIgnoreStart
+ } catch (ErrorException $e) {
+ // This is caused by an array to string conversion error.
+ }
+ } // @codeCoverageIgnoreEnd
+
+ /**
+ * Detects MIME Type based on file extension.
+ *
+ * @param string $extension
+ *
+ * @return string|null MIME Type or NULL if no extension detected
+ */
+ public static function detectByFileExtension($extension)
+ {
+ return isset(static::$extensionToMimeTypeMap[$extension])
+ ? static::$extensionToMimeTypeMap[$extension]
+ : 'text/plain';
+ }
+
+ /**
+ * @param string $filename
+ *
+ * @return string|null MIME Type or NULL if no extension detected
+ */
+ public static function detectByFilename($filename)
+ {
+ $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
+
+ return empty($extension) ? 'text/plain' : static::detectByFileExtension($extension);
+ }
+
+ /**
+ * @return array Map of file extension to MIME Type
+ */
+ public static function getExtensionToMimeTypeMap()
+ {
+ return static::$extensionToMimeTypeMap;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/league/flysystem/src/Util/StreamHasher.php b/plugins/panakour/backup/vendor/league/flysystem/src/Util/StreamHasher.php
new file mode 100644
index 000000000..938ec5db7
--- /dev/null
+++ b/plugins/panakour/backup/vendor/league/flysystem/src/Util/StreamHasher.php
@@ -0,0 +1,36 @@
+algo = $algo;
+ }
+
+ /**
+ * @param resource $resource
+ *
+ * @return string
+ */
+ public function hash($resource)
+ {
+ rewind($resource);
+ $context = hash_init($this->algo);
+ hash_update_stream($context, $resource);
+ fclose($resource);
+
+ return hash_final($context);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/psr/http-message/CHANGELOG.md b/plugins/panakour/backup/vendor/psr/http-message/CHANGELOG.md
new file mode 100644
index 000000000..74b1ef923
--- /dev/null
+++ b/plugins/panakour/backup/vendor/psr/http-message/CHANGELOG.md
@@ -0,0 +1,36 @@
+# Changelog
+
+All notable changes to this project will be documented in this file, in reverse chronological order by release.
+
+## 1.0.1 - 2016-08-06
+
+### Added
+
+- Nothing.
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- Updated all `@return self` annotation references in interfaces to use
+ `@return static`, which more closelly follows the semantics of the
+ specification.
+- Updated the `MessageInterface::getHeaders()` return annotation to use the
+ value `string[][]`, indicating the format is a nested array of strings.
+- Updated the `@link` annotation for `RequestInterface::withRequestTarget()`
+ to point to the correct section of RFC 7230.
+- Updated the `ServerRequestInterface::withUploadedFiles()` parameter annotation
+ to add the parameter name (`$uploadedFiles`).
+- Updated a `@throws` annotation for the `UploadedFileInterface::moveTo()`
+ method to correctly reference the method parameter (it was referencing an
+ incorrect parameter name previously).
+
+## 1.0.0 - 2016-05-18
+
+Initial stable release; reflects accepted PSR-7 specification.
diff --git a/plugins/panakour/backup/vendor/psr/http-message/LICENSE b/plugins/panakour/backup/vendor/psr/http-message/LICENSE
new file mode 100644
index 000000000..c2d8e452d
--- /dev/null
+++ b/plugins/panakour/backup/vendor/psr/http-message/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 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.
diff --git a/plugins/panakour/backup/vendor/psr/http-message/README.md b/plugins/panakour/backup/vendor/psr/http-message/README.md
new file mode 100644
index 000000000..28185338f
--- /dev/null
+++ b/plugins/panakour/backup/vendor/psr/http-message/README.md
@@ -0,0 +1,13 @@
+PSR Http Message
+================
+
+This repository holds all interfaces/classes/traits related to
+[PSR-7](http://www.php-fig.org/psr/psr-7/).
+
+Note that this is not a HTTP message implementation of its own. It is merely an
+interface that describes a HTTP message. See the specification for more details.
+
+Usage
+-----
+
+We'll certainly need some stuff in here.
\ No newline at end of file
diff --git a/plugins/panakour/backup/vendor/psr/http-message/composer.json b/plugins/panakour/backup/vendor/psr/http-message/composer.json
new file mode 100644
index 000000000..b0d2937a0
--- /dev/null
+++ b/plugins/panakour/backup/vendor/psr/http-message/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "psr/http-message",
+ "description": "Common interface for HTTP messages",
+ "keywords": ["psr", "psr-7", "http", "http-message", "request", "response"],
+ "homepage": "https://github.com/php-fig/http-message",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/psr/http-message/src/MessageInterface.php b/plugins/panakour/backup/vendor/psr/http-message/src/MessageInterface.php
new file mode 100644
index 000000000..dd46e5ec8
--- /dev/null
+++ b/plugins/panakour/backup/vendor/psr/http-message/src/MessageInterface.php
@@ -0,0 +1,187 @@
+getHeaders() as $name => $values) {
+ * echo $name . ": " . implode(", ", $values);
+ * }
+ *
+ * // Emit headers iteratively:
+ * foreach ($message->getHeaders() as $name => $values) {
+ * foreach ($values as $value) {
+ * header(sprintf('%s: %s', $name, $value), false);
+ * }
+ * }
+ *
+ * While header names are not case-sensitive, getHeaders() will preserve the
+ * exact case in which headers were originally specified.
+ *
+ * @return string[][] Returns an associative array of the message's headers. Each
+ * key MUST be a header name, and each value MUST be an array of strings
+ * for that header.
+ */
+ public function getHeaders();
+
+ /**
+ * Checks if a header exists by the given case-insensitive name.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return bool Returns true if any header names match the given header
+ * name using a case-insensitive string comparison. Returns false if
+ * no matching header name is found in the message.
+ */
+ public function hasHeader($name);
+
+ /**
+ * Retrieves a message header value by the given case-insensitive name.
+ *
+ * This method returns an array of all the header values of the given
+ * case-insensitive header name.
+ *
+ * If the header does not appear in the message, this method MUST return an
+ * empty array.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return string[] An array of string values as provided for the given
+ * header. If the header does not appear in the message, this method MUST
+ * return an empty array.
+ */
+ public function getHeader($name);
+
+ /**
+ * Retrieves a comma-separated string of the values for a single header.
+ *
+ * This method returns all of the header values of the given
+ * case-insensitive header name as a string concatenated together using
+ * a comma.
+ *
+ * NOTE: Not all header values may be appropriately represented using
+ * comma concatenation. For such headers, use getHeader() instead
+ * and supply your own delimiter when concatenating.
+ *
+ * If the header does not appear in the message, this method MUST return
+ * an empty string.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return string A string of values as provided for the given header
+ * concatenated together using a comma. If the header does not appear in
+ * the message, this method MUST return an empty string.
+ */
+ public function getHeaderLine($name);
+
+ /**
+ * Return an instance with the provided value replacing the specified header.
+ *
+ * While header names are case-insensitive, the casing of the header will
+ * be preserved by this function, and returned from getHeaders().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * new and/or updated header and value.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @param string|string[] $value Header value(s).
+ * @return static
+ * @throws \InvalidArgumentException for invalid header names or values.
+ */
+ public function withHeader($name, $value);
+
+ /**
+ * Return an instance with the specified header appended with the given value.
+ *
+ * Existing values for the specified header will be maintained. The new
+ * value(s) will be appended to the existing list. If the header did not
+ * exist previously, it will be added.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * new header and/or value.
+ *
+ * @param string $name Case-insensitive header field name to add.
+ * @param string|string[] $value Header value(s).
+ * @return static
+ * @throws \InvalidArgumentException for invalid header names or values.
+ */
+ public function withAddedHeader($name, $value);
+
+ /**
+ * Return an instance without the specified header.
+ *
+ * Header resolution MUST be done without case-sensitivity.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that removes
+ * the named header.
+ *
+ * @param string $name Case-insensitive header field name to remove.
+ * @return static
+ */
+ public function withoutHeader($name);
+
+ /**
+ * Gets the body of the message.
+ *
+ * @return StreamInterface Returns the body as a stream.
+ */
+ public function getBody();
+
+ /**
+ * Return an instance with the specified message body.
+ *
+ * The body MUST be a StreamInterface object.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return a new instance that has the
+ * new body stream.
+ *
+ * @param StreamInterface $body Body.
+ * @return static
+ * @throws \InvalidArgumentException When the body is not valid.
+ */
+ public function withBody(StreamInterface $body);
+}
diff --git a/plugins/panakour/backup/vendor/psr/http-message/src/RequestInterface.php b/plugins/panakour/backup/vendor/psr/http-message/src/RequestInterface.php
new file mode 100644
index 000000000..a96d4fd63
--- /dev/null
+++ b/plugins/panakour/backup/vendor/psr/http-message/src/RequestInterface.php
@@ -0,0 +1,129 @@
+getQuery()`
+ * or from the `QUERY_STRING` server param.
+ *
+ * @return array
+ */
+ public function getQueryParams();
+
+ /**
+ * Return an instance with the specified query string arguments.
+ *
+ * These values SHOULD remain immutable over the course of the incoming
+ * request. They MAY be injected during instantiation, such as from PHP's
+ * $_GET superglobal, or MAY be derived from some other value such as the
+ * URI. In cases where the arguments are parsed from the URI, the data
+ * MUST be compatible with what PHP's parse_str() would return for
+ * purposes of how duplicate query parameters are handled, and how nested
+ * sets are handled.
+ *
+ * Setting query string arguments MUST NOT change the URI stored by the
+ * request, nor the values in the server params.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated query string arguments.
+ *
+ * @param array $query Array of query string arguments, typically from
+ * $_GET.
+ * @return static
+ */
+ public function withQueryParams(array $query);
+
+ /**
+ * Retrieve normalized file upload data.
+ *
+ * This method returns upload metadata in a normalized tree, with each leaf
+ * an instance of Psr\Http\Message\UploadedFileInterface.
+ *
+ * These values MAY be prepared from $_FILES or the message body during
+ * instantiation, or MAY be injected via withUploadedFiles().
+ *
+ * @return array An array tree of UploadedFileInterface instances; an empty
+ * array MUST be returned if no data is present.
+ */
+ public function getUploadedFiles();
+
+ /**
+ * Create a new instance with the specified uploaded files.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated body parameters.
+ *
+ * @param array $uploadedFiles An array tree of UploadedFileInterface instances.
+ * @return static
+ * @throws \InvalidArgumentException if an invalid structure is provided.
+ */
+ public function withUploadedFiles(array $uploadedFiles);
+
+ /**
+ * Retrieve any parameters provided in the request body.
+ *
+ * If the request Content-Type is either application/x-www-form-urlencoded
+ * or multipart/form-data, and the request method is POST, this method MUST
+ * return the contents of $_POST.
+ *
+ * Otherwise, this method may return any results of deserializing
+ * the request body content; as parsing returns structured content, the
+ * potential types MUST be arrays or objects only. A null value indicates
+ * the absence of body content.
+ *
+ * @return null|array|object The deserialized body parameters, if any.
+ * These will typically be an array or object.
+ */
+ public function getParsedBody();
+
+ /**
+ * Return an instance with the specified body parameters.
+ *
+ * These MAY be injected during instantiation.
+ *
+ * If the request Content-Type is either application/x-www-form-urlencoded
+ * or multipart/form-data, and the request method is POST, use this method
+ * ONLY to inject the contents of $_POST.
+ *
+ * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of
+ * deserializing the request body content. Deserialization/parsing returns
+ * structured data, and, as such, this method ONLY accepts arrays or objects,
+ * or a null value if nothing was available to parse.
+ *
+ * As an example, if content negotiation determines that the request data
+ * is a JSON payload, this method could be used to create a request
+ * instance with the deserialized parameters.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated body parameters.
+ *
+ * @param null|array|object $data The deserialized body data. This will
+ * typically be in an array or object.
+ * @return static
+ * @throws \InvalidArgumentException if an unsupported argument type is
+ * provided.
+ */
+ public function withParsedBody($data);
+
+ /**
+ * Retrieve attributes derived from the request.
+ *
+ * The request "attributes" may be used to allow injection of any
+ * parameters derived from the request: e.g., the results of path
+ * match operations; the results of decrypting cookies; the results of
+ * deserializing non-form-encoded message bodies; etc. Attributes
+ * will be application and request specific, and CAN be mutable.
+ *
+ * @return array Attributes derived from the request.
+ */
+ public function getAttributes();
+
+ /**
+ * Retrieve a single derived request attribute.
+ *
+ * Retrieves a single derived request attribute as described in
+ * getAttributes(). If the attribute has not been previously set, returns
+ * the default value as provided.
+ *
+ * This method obviates the need for a hasAttribute() method, as it allows
+ * specifying a default value to return if the attribute is not found.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @param mixed $default Default value to return if the attribute does not exist.
+ * @return mixed
+ */
+ public function getAttribute($name, $default = null);
+
+ /**
+ * Return an instance with the specified derived request attribute.
+ *
+ * This method allows setting a single derived request attribute as
+ * described in getAttributes().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated attribute.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @param mixed $value The value of the attribute.
+ * @return static
+ */
+ public function withAttribute($name, $value);
+
+ /**
+ * Return an instance that removes the specified derived request attribute.
+ *
+ * This method allows removing a single derived request attribute as
+ * described in getAttributes().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that removes
+ * the attribute.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @return static
+ */
+ public function withoutAttribute($name);
+}
diff --git a/plugins/panakour/backup/vendor/psr/http-message/src/StreamInterface.php b/plugins/panakour/backup/vendor/psr/http-message/src/StreamInterface.php
new file mode 100644
index 000000000..f68f39126
--- /dev/null
+++ b/plugins/panakour/backup/vendor/psr/http-message/src/StreamInterface.php
@@ -0,0 +1,158 @@
+
+ * [user-info@]host[:port]
+ *
+ *
+ * If the port component is not set or is the standard port for the current
+ * scheme, it SHOULD NOT be included.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-3.2
+ * @return string The URI authority, in "[user-info@]host[:port]" format.
+ */
+ public function getAuthority();
+
+ /**
+ * Retrieve the user information component of the URI.
+ *
+ * If no user information is present, this method MUST return an empty
+ * string.
+ *
+ * If a user is present in the URI, this will return that value;
+ * additionally, if the password is also present, it will be appended to the
+ * user value, with a colon (":") separating the values.
+ *
+ * The trailing "@" character is not part of the user information and MUST
+ * NOT be added.
+ *
+ * @return string The URI user information, in "username[:password]" format.
+ */
+ public function getUserInfo();
+
+ /**
+ * Retrieve the host component of the URI.
+ *
+ * If no host is present, this method MUST return an empty string.
+ *
+ * The value returned MUST be normalized to lowercase, per RFC 3986
+ * Section 3.2.2.
+ *
+ * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
+ * @return string The URI host.
+ */
+ public function getHost();
+
+ /**
+ * Retrieve the port component of the URI.
+ *
+ * If a port is present, and it is non-standard for the current scheme,
+ * this method MUST return it as an integer. If the port is the standard port
+ * used with the current scheme, this method SHOULD return null.
+ *
+ * If no port is present, and no scheme is present, this method MUST return
+ * a null value.
+ *
+ * If no port is present, but a scheme is present, this method MAY return
+ * the standard port for that scheme, but SHOULD return null.
+ *
+ * @return null|int The URI port.
+ */
+ public function getPort();
+
+ /**
+ * Retrieve the path component of the URI.
+ *
+ * The path can either be empty or absolute (starting with a slash) or
+ * rootless (not starting with a slash). Implementations MUST support all
+ * three syntaxes.
+ *
+ * Normally, the empty path "" and absolute path "/" are considered equal as
+ * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
+ * do this normalization because in contexts with a trimmed base path, e.g.
+ * the front controller, this difference becomes significant. It's the task
+ * of the user to handle both "" and "/".
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.3.
+ *
+ * As an example, if the value should include a slash ("/") not intended as
+ * delimiter between path segments, that value MUST be passed in encoded
+ * form (e.g., "%2F") to the instance.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.3
+ * @return string The URI path.
+ */
+ public function getPath();
+
+ /**
+ * Retrieve the query string of the URI.
+ *
+ * If no query string is present, this method MUST return an empty string.
+ *
+ * The leading "?" character is not part of the query and MUST NOT be
+ * added.
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.4.
+ *
+ * As an example, if a value in a key/value pair of the query string should
+ * include an ampersand ("&") not intended as a delimiter between values,
+ * that value MUST be passed in encoded form (e.g., "%26") to the instance.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.4
+ * @return string The URI query string.
+ */
+ public function getQuery();
+
+ /**
+ * Retrieve the fragment component of the URI.
+ *
+ * If no fragment is present, this method MUST return an empty string.
+ *
+ * The leading "#" character is not part of the fragment and MUST NOT be
+ * added.
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.5.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.5
+ * @return string The URI fragment.
+ */
+ public function getFragment();
+
+ /**
+ * Return an instance with the specified scheme.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified scheme.
+ *
+ * Implementations MUST support the schemes "http" and "https" case
+ * insensitively, and MAY accommodate other schemes if required.
+ *
+ * An empty scheme is equivalent to removing the scheme.
+ *
+ * @param string $scheme The scheme to use with the new instance.
+ * @return static A new instance with the specified scheme.
+ * @throws \InvalidArgumentException for invalid or unsupported schemes.
+ */
+ public function withScheme($scheme);
+
+ /**
+ * Return an instance with the specified user information.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified user information.
+ *
+ * Password is optional, but the user information MUST include the
+ * user; an empty string for the user is equivalent to removing user
+ * information.
+ *
+ * @param string $user The user name to use for authority.
+ * @param null|string $password The password associated with $user.
+ * @return static A new instance with the specified user information.
+ */
+ public function withUserInfo($user, $password = null);
+
+ /**
+ * Return an instance with the specified host.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified host.
+ *
+ * An empty host value is equivalent to removing the host.
+ *
+ * @param string $host The hostname to use with the new instance.
+ * @return static A new instance with the specified host.
+ * @throws \InvalidArgumentException for invalid hostnames.
+ */
+ public function withHost($host);
+
+ /**
+ * Return an instance with the specified port.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified port.
+ *
+ * Implementations MUST raise an exception for ports outside the
+ * established TCP and UDP port ranges.
+ *
+ * A null value provided for the port is equivalent to removing the port
+ * information.
+ *
+ * @param null|int $port The port to use with the new instance; a null value
+ * removes the port information.
+ * @return static A new instance with the specified port.
+ * @throws \InvalidArgumentException for invalid ports.
+ */
+ public function withPort($port);
+
+ /**
+ * Return an instance with the specified path.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified path.
+ *
+ * The path can either be empty or absolute (starting with a slash) or
+ * rootless (not starting with a slash). Implementations MUST support all
+ * three syntaxes.
+ *
+ * If the path is intended to be domain-relative rather than path relative then
+ * it must begin with a slash ("/"). Paths not starting with a slash ("/")
+ * are assumed to be relative to some base path known to the application or
+ * consumer.
+ *
+ * Users can provide both encoded and decoded path characters.
+ * Implementations ensure the correct encoding as outlined in getPath().
+ *
+ * @param string $path The path to use with the new instance.
+ * @return static A new instance with the specified path.
+ * @throws \InvalidArgumentException for invalid paths.
+ */
+ public function withPath($path);
+
+ /**
+ * Return an instance with the specified query string.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified query string.
+ *
+ * Users can provide both encoded and decoded query characters.
+ * Implementations ensure the correct encoding as outlined in getQuery().
+ *
+ * An empty query string value is equivalent to removing the query string.
+ *
+ * @param string $query The query string to use with the new instance.
+ * @return static A new instance with the specified query string.
+ * @throws \InvalidArgumentException for invalid query strings.
+ */
+ public function withQuery($query);
+
+ /**
+ * Return an instance with the specified URI fragment.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified URI fragment.
+ *
+ * Users can provide both encoded and decoded fragment characters.
+ * Implementations ensure the correct encoding as outlined in getFragment().
+ *
+ * An empty fragment value is equivalent to removing the fragment.
+ *
+ * @param string $fragment The fragment to use with the new instance.
+ * @return static A new instance with the specified fragment.
+ */
+ public function withFragment($fragment);
+
+ /**
+ * Return the string representation as a URI reference.
+ *
+ * Depending on which components of the URI are present, the resulting
+ * string is either a full URI or relative reference according to RFC 3986,
+ * Section 4.1. The method concatenates the various components of the URI,
+ * using the appropriate delimiters:
+ *
+ * - If a scheme is present, it MUST be suffixed by ":".
+ * - If an authority is present, it MUST be prefixed by "//".
+ * - The path can be concatenated without delimiters. But there are two
+ * cases where the path has to be adjusted to make the URI reference
+ * valid as PHP does not allow to throw an exception in __toString():
+ * - If the path is rootless and an authority is present, the path MUST
+ * be prefixed by "/".
+ * - If the path is starting with more than one "/" and no authority is
+ * present, the starting slashes MUST be reduced to one.
+ * - If a query is present, it MUST be prefixed by "?".
+ * - If a fragment is present, it MUST be prefixed by "#".
+ *
+ * @see http://tools.ietf.org/html/rfc3986#section-4.1
+ * @return string
+ */
+ public function __toString();
+}
diff --git a/plugins/panakour/backup/vendor/psr/log/LICENSE b/plugins/panakour/backup/vendor/psr/log/LICENSE
new file mode 100644
index 000000000..474c952b4
--- /dev/null
+++ b/plugins/panakour/backup/vendor/psr/log/LICENSE
@@ -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.
diff --git a/plugins/panakour/backup/vendor/psr/log/Psr/Log/AbstractLogger.php b/plugins/panakour/backup/vendor/psr/log/Psr/Log/AbstractLogger.php
new file mode 100644
index 000000000..90e721af2
--- /dev/null
+++ b/plugins/panakour/backup/vendor/psr/log/Psr/Log/AbstractLogger.php
@@ -0,0 +1,128 @@
+log(LogLevel::EMERGENCY, $message, $context);
+ }
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function alert($message, array $context = array())
+ {
+ $this->log(LogLevel::ALERT, $message, $context);
+ }
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function critical($message, array $context = array())
+ {
+ $this->log(LogLevel::CRITICAL, $message, $context);
+ }
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function error($message, array $context = array())
+ {
+ $this->log(LogLevel::ERROR, $message, $context);
+ }
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function warning($message, array $context = array())
+ {
+ $this->log(LogLevel::WARNING, $message, $context);
+ }
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function notice($message, array $context = array())
+ {
+ $this->log(LogLevel::NOTICE, $message, $context);
+ }
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function info($message, array $context = array())
+ {
+ $this->log(LogLevel::INFO, $message, $context);
+ }
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function debug($message, array $context = array())
+ {
+ $this->log(LogLevel::DEBUG, $message, $context);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/plugins/panakour/backup/vendor/psr/log/Psr/Log/InvalidArgumentException.php
new file mode 100644
index 000000000..67f852d1d
--- /dev/null
+++ b/plugins/panakour/backup/vendor/psr/log/Psr/Log/InvalidArgumentException.php
@@ -0,0 +1,7 @@
+logger = $logger;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/psr/log/Psr/Log/LoggerInterface.php b/plugins/panakour/backup/vendor/psr/log/Psr/Log/LoggerInterface.php
new file mode 100644
index 000000000..2206cfde4
--- /dev/null
+++ b/plugins/panakour/backup/vendor/psr/log/Psr/Log/LoggerInterface.php
@@ -0,0 +1,125 @@
+log(LogLevel::EMERGENCY, $message, $context);
+ }
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function alert($message, array $context = array())
+ {
+ $this->log(LogLevel::ALERT, $message, $context);
+ }
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function critical($message, array $context = array())
+ {
+ $this->log(LogLevel::CRITICAL, $message, $context);
+ }
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function error($message, array $context = array())
+ {
+ $this->log(LogLevel::ERROR, $message, $context);
+ }
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function warning($message, array $context = array())
+ {
+ $this->log(LogLevel::WARNING, $message, $context);
+ }
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function notice($message, array $context = array())
+ {
+ $this->log(LogLevel::NOTICE, $message, $context);
+ }
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function info($message, array $context = array())
+ {
+ $this->log(LogLevel::INFO, $message, $context);
+ }
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function debug($message, array $context = array())
+ {
+ $this->log(LogLevel::DEBUG, $message, $context);
+ }
+
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ *
+ * @throws \Psr\Log\InvalidArgumentException
+ */
+ abstract public function log($level, $message, array $context = array());
+}
diff --git a/plugins/panakour/backup/vendor/psr/log/Psr/Log/NullLogger.php b/plugins/panakour/backup/vendor/psr/log/Psr/Log/NullLogger.php
new file mode 100644
index 000000000..c8f7293b1
--- /dev/null
+++ b/plugins/panakour/backup/vendor/psr/log/Psr/Log/NullLogger.php
@@ -0,0 +1,30 @@
+logger) { }`
+ * blocks.
+ */
+class NullLogger extends AbstractLogger
+{
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ *
+ * @throws \Psr\Log\InvalidArgumentException
+ */
+ public function log($level, $message, array $context = array())
+ {
+ // noop
+ }
+}
diff --git a/plugins/panakour/backup/vendor/psr/log/Psr/Log/Test/DummyTest.php b/plugins/panakour/backup/vendor/psr/log/Psr/Log/Test/DummyTest.php
new file mode 100644
index 000000000..9638c1101
--- /dev/null
+++ b/plugins/panakour/backup/vendor/psr/log/Psr/Log/Test/DummyTest.php
@@ -0,0 +1,18 @@
+ ".
+ *
+ * Example ->error('Foo') would yield "error Foo".
+ *
+ * @return string[]
+ */
+ abstract public function getLogs();
+
+ public function testImplements()
+ {
+ $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger());
+ }
+
+ /**
+ * @dataProvider provideLevelsAndMessages
+ */
+ public function testLogsAtAllLevels($level, $message)
+ {
+ $logger = $this->getLogger();
+ $logger->{$level}($message, array('user' => 'Bob'));
+ $logger->log($level, $message, array('user' => 'Bob'));
+
+ $expected = array(
+ $level.' message of level '.$level.' with context: Bob',
+ $level.' message of level '.$level.' with context: Bob',
+ );
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function provideLevelsAndMessages()
+ {
+ return array(
+ LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'),
+ LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'),
+ LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'),
+ LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'),
+ LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'),
+ LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'),
+ LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'),
+ LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'),
+ );
+ }
+
+ /**
+ * @expectedException \Psr\Log\InvalidArgumentException
+ */
+ public function testThrowsOnInvalidLevel()
+ {
+ $logger = $this->getLogger();
+ $logger->log('invalid level', 'Foo');
+ }
+
+ public function testContextReplacement()
+ {
+ $logger = $this->getLogger();
+ $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar'));
+
+ $expected = array('info {Message {nothing} Bob Bar a}');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testObjectCastToString()
+ {
+ if (method_exists($this, 'createPartialMock')) {
+ $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString'));
+ } else {
+ $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString'));
+ }
+ $dummy->expects($this->once())
+ ->method('__toString')
+ ->will($this->returnValue('DUMMY'));
+
+ $this->getLogger()->warning($dummy);
+
+ $expected = array('warning DUMMY');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testContextCanContainAnything()
+ {
+ $closed = fopen('php://memory', 'r');
+ fclose($closed);
+
+ $context = array(
+ 'bool' => true,
+ 'null' => null,
+ 'string' => 'Foo',
+ 'int' => 0,
+ 'float' => 0.5,
+ 'nested' => array('with object' => new DummyTest),
+ 'object' => new \DateTime,
+ 'resource' => fopen('php://memory', 'r'),
+ 'closed' => $closed,
+ );
+
+ $this->getLogger()->warning('Crazy context data', $context);
+
+ $expected = array('warning Crazy context data');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testContextExceptionKeyCanBeExceptionOrOtherValues()
+ {
+ $logger = $this->getLogger();
+ $logger->warning('Random message', array('exception' => 'oops'));
+ $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail')));
+
+ $expected = array(
+ 'warning Random message',
+ 'critical Uncaught Exception!'
+ );
+ $this->assertEquals($expected, $this->getLogs());
+ }
+}
diff --git a/plugins/panakour/backup/vendor/psr/log/Psr/Log/Test/TestLogger.php b/plugins/panakour/backup/vendor/psr/log/Psr/Log/Test/TestLogger.php
new file mode 100644
index 000000000..1be323049
--- /dev/null
+++ b/plugins/panakour/backup/vendor/psr/log/Psr/Log/Test/TestLogger.php
@@ -0,0 +1,147 @@
+ $level,
+ 'message' => $message,
+ 'context' => $context,
+ ];
+
+ $this->recordsByLevel[$record['level']][] = $record;
+ $this->records[] = $record;
+ }
+
+ public function hasRecords($level)
+ {
+ return isset($this->recordsByLevel[$level]);
+ }
+
+ public function hasRecord($record, $level)
+ {
+ if (is_string($record)) {
+ $record = ['message' => $record];
+ }
+ return $this->hasRecordThatPasses(function ($rec) use ($record) {
+ if ($rec['message'] !== $record['message']) {
+ return false;
+ }
+ if (isset($record['context']) && $rec['context'] !== $record['context']) {
+ return false;
+ }
+ return true;
+ }, $level);
+ }
+
+ public function hasRecordThatContains($message, $level)
+ {
+ return $this->hasRecordThatPasses(function ($rec) use ($message) {
+ return strpos($rec['message'], $message) !== false;
+ }, $level);
+ }
+
+ public function hasRecordThatMatches($regex, $level)
+ {
+ return $this->hasRecordThatPasses(function ($rec) use ($regex) {
+ return preg_match($regex, $rec['message']) > 0;
+ }, $level);
+ }
+
+ public function hasRecordThatPasses(callable $predicate, $level)
+ {
+ if (!isset($this->recordsByLevel[$level])) {
+ return false;
+ }
+ foreach ($this->recordsByLevel[$level] as $i => $rec) {
+ if (call_user_func($predicate, $rec, $i)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public function __call($method, $args)
+ {
+ if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) {
+ $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3];
+ $level = strtolower($matches[2]);
+ if (method_exists($this, $genericMethod)) {
+ $args[] = $level;
+ return call_user_func_array([$this, $genericMethod], $args);
+ }
+ }
+ throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()');
+ }
+
+ public function reset()
+ {
+ $this->records = [];
+ $this->recordsByLevel = [];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/psr/log/README.md b/plugins/panakour/backup/vendor/psr/log/README.md
new file mode 100644
index 000000000..a9f20c437
--- /dev/null
+++ b/plugins/panakour/backup/vendor/psr/log/README.md
@@ -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
+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.
diff --git a/plugins/panakour/backup/vendor/psr/log/composer.json b/plugins/panakour/backup/vendor/psr/log/composer.json
new file mode 100644
index 000000000..3f6d4eea4
--- /dev/null
+++ b/plugins/panakour/backup/vendor/psr/log/composer.json
@@ -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": "http://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/ralouphie/getallheaders/LICENSE b/plugins/panakour/backup/vendor/ralouphie/getallheaders/LICENSE
new file mode 100644
index 000000000..be5540c2a
--- /dev/null
+++ b/plugins/panakour/backup/vendor/ralouphie/getallheaders/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Ralph Khattar
+
+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.
diff --git a/plugins/panakour/backup/vendor/ralouphie/getallheaders/README.md b/plugins/panakour/backup/vendor/ralouphie/getallheaders/README.md
new file mode 100644
index 000000000..9430d76bb
--- /dev/null
+++ b/plugins/panakour/backup/vendor/ralouphie/getallheaders/README.md
@@ -0,0 +1,27 @@
+getallheaders
+=============
+
+PHP `getallheaders()` polyfill. Compatible with PHP >= 5.3.
+
+[](https://travis-ci.org/ralouphie/getallheaders)
+[](https://coveralls.io/r/ralouphie/getallheaders?branch=master)
+[](https://packagist.org/packages/ralouphie/getallheaders)
+[](https://packagist.org/packages/ralouphie/getallheaders)
+[](https://packagist.org/packages/ralouphie/getallheaders)
+
+
+This is a simple polyfill for [`getallheaders()`](http://www.php.net/manual/en/function.getallheaders.php).
+
+## Install
+
+For PHP version **`>= 5.6`**:
+
+```
+composer require ralouphie/getallheaders
+```
+
+For PHP version **`< 5.6`**:
+
+```
+composer require ralouphie/getallheaders "^2"
+```
diff --git a/plugins/panakour/backup/vendor/ralouphie/getallheaders/composer.json b/plugins/panakour/backup/vendor/ralouphie/getallheaders/composer.json
new file mode 100644
index 000000000..de8ce62e4
--- /dev/null
+++ b/plugins/panakour/backup/vendor/ralouphie/getallheaders/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "ralouphie/getallheaders",
+ "description": "A polyfill for getallheaders.",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Ralph Khattar",
+ "email": "ralph.khattar@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5 || ^6.5",
+ "php-coveralls/php-coveralls": "^2.1"
+ },
+ "autoload": {
+ "files": ["src/getallheaders.php"]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "getallheaders\\Tests\\": "tests/"
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/ralouphie/getallheaders/src/getallheaders.php b/plugins/panakour/backup/vendor/ralouphie/getallheaders/src/getallheaders.php
new file mode 100644
index 000000000..c7285a5ba
--- /dev/null
+++ b/plugins/panakour/backup/vendor/ralouphie/getallheaders/src/getallheaders.php
@@ -0,0 +1,46 @@
+ 'Content-Type',
+ 'CONTENT_LENGTH' => 'Content-Length',
+ 'CONTENT_MD5' => 'Content-Md5',
+ );
+
+ foreach ($_SERVER as $key => $value) {
+ if (substr($key, 0, 5) === 'HTTP_') {
+ $key = substr($key, 5);
+ if (!isset($copy_server[$key]) || !isset($_SERVER[$key])) {
+ $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key))));
+ $headers[$key] = $value;
+ }
+ } elseif (isset($copy_server[$key])) {
+ $headers[$copy_server[$key]] = $value;
+ }
+ }
+
+ if (!isset($headers['Authorization'])) {
+ if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
+ $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
+ } elseif (isset($_SERVER['PHP_AUTH_USER'])) {
+ $basic_pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : '';
+ $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass);
+ } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) {
+ $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST'];
+ }
+ }
+
+ return $headers;
+ }
+
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/CHANGELOG.md b/plugins/panakour/backup/vendor/sabre/dav/CHANGELOG.md
new file mode 100644
index 000000000..932827a72
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/CHANGELOG.md
@@ -0,0 +1,2512 @@
+ChangeLog
+=========
+
+4.1.0 (2020-03-20)
+-------------------------
+* Support PHP 7.4
+* Drop support for PHP 7.0
+* CalDAV: send MIME-Version header in scheduling emails
+
+4.0.3 (2020-01-10)
+-------------------------
+* DAV: Streaming PROPFIND server implementation
+* DAVACL: Fix uppercase of NotAuthenticated class
+* CalDAV: Return only calendar objects owned by principal itself
+* CalDAV: Convert scheduling object data from resource to string
+* Browser Plugin: Fix content type guessing if setBaseUri is set to a folder
+
+
+4.0.2 (2019-10-18)
+-------------------------
+* Fix error with PHP 7.4
+* CardDAV: Fix content-type for Thunderbird
+
+
+4.0.1 (2019-08-20)
+-------------------------
+* TemporaryFileFilterPlugin: Fix Strict Error
+* CalDAV\Plugin: Fix null path
+
+
+4.0.0 (2019-07-01)
+-------------------------
+* Lock: Support lock timeout value Infinity
+* Lock: Hide lock token in lock discovery when not set
+* BrowserPlugin: Show display name of nodes
+* FSExt: Fix folder (file) move issue if rename fails
+* IMipPlugin: Add sender name in invite mail headers
+* IMipPlugin: Fix email subject and recipient
+* Fix issues with empty content-type header
+* Apply new code style
+* Fix for litmus test suite - test case: props propfind_invalid2
+* Depend on sabre/xml 2.0.1
+* Depend on sabre/http 5.0
+* Now supports PHP 7.3
+* Now requires PHP 7.
+* Using `strict_types` in every php file.
+* #896: Using the [sabre/event][evnt] `WildcardEmitter`. This allows event
+ handlers to listen to events using a wildcard.
+* #896: Event listeners that in the past listened to `beforeMethod` or `method`
+ no longer get called. They must listen to `beforeMethod:*` and `method:*` now.
+* #322: Imap authentication backend. (@c0d3z3r0).
+* #889: Support for selective property querying in CardDAV's addressbook-query.
+ (@DeepDiver1975).
+* #982: Make sure that files that are siblings of directories, are reported
+ as files (@nickvergessen)
+
+
+4.0.0-beta1 (2019-05-08)
+-------------------------
+* Lock: Support lock timeout value Infinity
+* Lock: Hide lock token in lock discovery when not set
+* BrowserPlugin: Show display name of nodes
+* FSExt: Fix folder (file) move issue if rename fails
+* IMipPlugin: Add sender name in invite mail headers
+* IMipPlugin: Fix email subject and recipient
+
+
+4.0.0-alpha5 (2018-10-15)
+-------------------------
+* Fix issues with empty content-type header
+
+
+4.0.0-alpha4 (2018-10-12)
+-------------------------
+* Apply new code style
+* Fix for litmus test suite - test case: props propfind_invalid2
+* Depend on sabre/xml 2.0.1
+
+
+4.0.0-alpha3 (2018-10-05)
+-------------------------
+* Fixes for PHP 7.3
+* Depend on sabre/http 5.0
+
+
+4.0.0-alpha2 (2018-09-27)
+-------------------------
+* Now supports PHP 7.3
+
+
+4.0.0-alpha1 (2018-06-05)
+-------------------------
+
+* Now requires PHP 7.
+* Using `strict_types` in every php file.
+* #896: Using the [sabre/event][evnt] `WildcardEmitter`. This allows event
+ handlers to listen to events using a wildcard.
+* #896: Event listeners that in the past listened to `beforeMethod` or `method`
+ no longer get called. They must listen to `beforeMethod:*` and `method:*` now.
+* #322: Imap authentication backend. (@c0d3z3r0).
+* #889: Support for selective property querying in CardDAV's addressbook-query.
+ (@DeepDiver1975).
+* #982: Make sure that files that are siblings of directories, are reported
+ as files (@nickvergessen)
+
+
+3.3.0-alpha1 (2018-06-04)
+-------------------------
+
+* SimpleCollection can now take arrays and strings as argument for super
+ simple tree creation.
+* Added `Sabre\DAV\Server::start()`. This replaces `::exec()`. `::exec()`
+ is now deprecated, but we're keeping it around for a year or two to make
+ the transition easier.
+* `getChildren()` function in any collection may now return an iterator
+ instead of an array. This can result in memory savings for large
+ collections.
+* `Tree::getChildren()` now returns an Iterator instead of an array.
+* Added `$overrideName` to all `Sabre\DAV\FS` and `Sabre\DAV\FSExt` classes,
+ so users can specify under what name these nodes show up in the tree.
+* #889 Added support for filtering vCard properties in the addressbook-query
+ REPORT (@DeepDiver1975).
+* #918: Add a lot of sqlite indexes. This should speed up sqlite-based
+ installations quite a bit.
+* #982: Make sure that files that are siblings of directories, are reported
+ as files (@nickvergessen)
+* #1058: Don't open file resource on HEAD request (@icewind1991)
+* #1031: Fix copyNode for case of file named 0 (@phil-davis)
+
+
+3.2.3 (????-??-??)
+------------------
+
+* #982: Make sure that files that are siblings of directories, are reported
+ as files (@nickvergessen)
+
+
+3.2.2 (2017-02-14)
+------------------
+
+* #943: Fix CardDAV XML reporting bug, which was affecting several CardDAV
+ clients. Bug was introduced in 3.2.1.
+* The zip release ships with [sabre/vobject 4.1.2][vobj],
+ [sabre/http 4.2.2][http], [sabre/event 3.0.0][evnt],
+ [sabre/uri 1.2.0][uri] and [sabre/xml 1.5.0][xml].
+
+
+3.2.1 (2017-01-28)
+------------------
+
+* #877: Fix for syncing large calendars when using the Sqlite PDO backend.
+ (@theseer).
+* #889 Added support for filtering vCard properties in the addressbook-query
+ REPORT (@DeepDiver1975).
+* The zip release ships with [sabre/vobject 4.1.2][vobj],
+ [sabre/http 4.2.2][http], [sabre/event 3.0.0][evnt],
+ [sabre/uri 1.2.0][uri] and [sabre/xml 1.5.0][xml].
+
+
+3.2.0 (2016-06-27)
+------------------
+
+* The default ACL rules allow an unauthenticated user to read information
+ about nodes that don't have their own ACL defined. This was a security
+ problem.
+* The zip release ships with [sabre/vobject 4.1.0][vobj],
+ [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
+ [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.2][xml].
+
+
+3.2.0-beta1 (2016-05-20)
+------------------------
+
+* #833: Calendars throw exceptions when the sharing plugin is not enabled.
+* #834: Return vCards exactly as they were stored if we don't need to convert
+ in between versions.
+* The zip release ships with [sabre/vobject 4.1.0][vobj],
+ [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
+ [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml].
+
+
+3.2.0-alpha1 (2016-05-09)
+-------------------------
+
+* Database changes for CalDAV. If you are using the CalDAV PDO backends, you
+ must migrate. Run `./bin/migrateto32.php` for more info.
+* Support for WebDAV Resource Sharing, an upcoming standard.
+* Added support for sharing in the CalDAV PDO backend! Users can now invite
+ others to their calendar and give them read/read-write access!
+* #397: Support for PSR-3. You can now log exceptions with your favourite
+ psr3-compatible logging tool.
+* #825: Actual proper, tested support for PostgreSQL. We require version 9.5.
+* Removed database migration script for sabre/dav 1.7. To update from that
+ version you now first need to update to sabre/dav 3.1.
+* Removed deprecated function: `Sabre\DAV\Auth\Plugin::getCurrentUser()`.
+* #774: Fixes for getting free disk space on Windows.
+* #803: Major changes in the sharing API. If you were using an old sabre/dav
+ sharing api, head to the website for more detailed migration notes.
+* #657: Support for optional auth using `{DAV:}unauthorized` and `{DAV:}all`
+ privileges. This allows you to assign a privilege to a resource, allowing
+ non-authenticated users to access it. For instance, this could allow you
+ to create a public read-only collection.
+* #812 #814: ICS/VCF exporter now includes a more useful filename in its
+ `Content-Disposition` header. (@Xenopathic).
+* #801: BC break: If you were using the `Href` object before, it's behavior
+ now changed a bit, and `LocalHref` was added to replace the old, default
+ behavior of `Href`. See the migration doc for more info.
+* Removed `Sabre\DAVACL\Plugin::$allowAccessToNodesWithoutACL` setting.
+ Instead, you can provide a set of default ACL rules with
+ `Sabre\DAVACL\Plugin::setDefaultAcl()`.
+* Introduced `Sabre\DAVACL\ACLTrait` which contains a default implementation
+ of `Sabre\DAV\IACL` with some sane defaults. We're using this trait all over
+ the place now, reducing the amount of boilerplate.
+* Plugins can now control the "Supported Privilege Set".
+* Added Sharing, ICSExport and VCFExport plugins to `groupwareserver.php`
+ example.
+* The `{DAV:}all` privilege is now no longer abstract, so it can be assigned
+ directly. We're using the `{DAV:}all` privilege now in a lot of cases where
+ we before assigned both `{DAV:}read` and `{DAV:}write`.
+* Resources that are not collections no longer support the `{DAV:}bind` and
+ `{DAV:}unbind` privileges.
+* Corrected the CalDAV-scheduling related privileges.
+* Doing an `UNLOCK` no longer requires the `{DAV:}write-content` privilege.
+* Added a new `getPrincipalByUri` plugin event. Allowing plugins to request
+ quickly where a principal lives on a server.
+* Renamed `phpunit.xml` to `phpunit.xml.dist` to make local modifications easy.
+* Functionality from `IShareableCalendar` is merged into `ISharedCalendar`.
+* #751: Fixed XML responses from failing `MKCOL` requests.
+* #600: Support for `principal-match` ACL `REPORT`.
+* #599: Support for `acl-principal-prop-set` ACL `REPORT`.
+* #798: Added an index on `firstoccurence` field in MySQL CalDAV backend. This
+ should speed up common calendar-query requests.
+* #759: DAV\Client is now able to actually correctly resolve relative urls.
+* #671: We are no longer checking the `read-free-busy` privilege on individual
+ calendars during freebusy operations in the scheduling plugin. Instead, we
+ check the `schedule-query-freebusy` privilege on the target users' inbox,
+ which validates access for the entire account, per the spec.
+* The zip release ships with [sabre/vobject 4.1.0][vobj],
+ [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
+ [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml].
+
+
+3.1.5 (????-??-??)
+------------------
+
+* Fixed: Creating a new calendar on some MySQL configurations caused an error.
+* #889 Added support for filtering vCard properties in the addressbook-query
+ REPORT (@DeepDiver1975).
+
+
+
+3.1.4 (2016-05-28)
+------------------
+
+* #834: Backport from `master`: Return vCards exactly as they were stored if
+ we don't need to convert in between versions. This should speed up many
+ large addressbook syncs sometimes up to 50%.
+* The zip release ships with [sabre/vobject 4.1.0][vobj],
+ [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
+ [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.2][xml].
+
+
+3.1.3 (2016-04-06)
+------------------
+
+* Set minimum libxml version to 2.7.0 in `composer.json`.
+* #805: It wasn't possible to create calendars that hold events, journals and
+ todos using MySQL, because the `components` column was 1 byte too small.
+* The zip release ships with [sabre/vobject 4.1.0][vobj],
+ [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
+ [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml].
+
+
+3.1.2 (2016-03-12)
+------------------
+
+* #784: Sync logs for address books were not correctly cleaned up after
+ deleting them.
+* #787: Cannot use non-seekable stream-wrappers with range requests.
+* Faster XML parsing and generating due to sabre/xml update.
+* #793: The Sqlite schema is now more strict and more similar to the MySQL
+ schema. This solves a problem within Baikal.
+* The zip release ships with [sabre/vobject 4.0.3][vobj],
+ [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
+ [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml].
+
+
+3.1.1 (2016-01-25)
+------------------
+
+* #755: The brower plugin and some operations would break when scheduling and
+ delegation would both be enabled.
+* #757: A bunch of unittest improvements (@jakobsack).
+* The zip release ships with [sabre/vobject 4.0.2][vobj],
+ [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.3.0][xml].
+
+
+3.1.0 (2016-01-06)
+------------------
+
+* Better error message when the browser plugin is not enabled.
+* Added a super minimal server example.
+* #730: Switched all mysql tables to `utf8mb4` character set, allowing you to
+ use emoji in some tables where you couldn't before.
+* #710: Provide an Auth backend that acts as a helper for people implementing
+ OAuth2 Bearer token. (@fkooman).
+* #729: Not all calls to `Sabre\DAV\Tree::getChildren()` were properly cached.
+* #727: Added another workaround to make CalDAV work for Windows 10 clients.
+* #742: Fixes to make sure that vobject 4 is correctly supported.
+* #726: Better error reporting in `Client::propPatch`. We're now throwing
+ exceptions.
+* #608: When a HTTP error is triggered during `Client:propFind`, we're now
+ throwing `Sabre\HTTP\ClientHttpException` instead of `Sabre\DAV\Exception`.
+ This new exception contains a LOT more information about the problem.
+* #721: Events are now handled in the correct order for `COPY` requests.
+ Before this subtle bugs could appear that could cause data-loss.
+* #747: Now throwing exceptions and setting the HTTP status to 500 in subtle
+ cases where no other plugin set a correct HTTP status.
+* #686: Corrected PDO principal backend's findByURI for email addresses that
+ don't match the exact capitalization.
+* #512: The client now has it's own `User-Agent`.
+* #720: Some browser improvements.
+* The zip release ships with [sabre/vobject 4.0.1][vobj],
+ [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.3.0][xml].
+
+
+3.1.0-alpha2 (2015-09-05)
+-------------------------
+
+* Massive calendars and addressbooks should see a big drop in peak memory
+ usage.
+* Fixed a privilege bug in the availability system.
+* #697: Added a "tableName" member to the PropertyStorage PDO backend. (@Frzk).
+* #699: PostgreSQL fix for the Locks PDO backend. (@TCKnet)
+* Removed the `simplefsserver.php` example file. It's not simple enough.
+* #703: PropPatch in client is not correctly encoded.
+* #709: Throw exception when running into empty
+ `supported-calendar-component-set`.
+* #711: Don't trigger deserializers for empty elements in `{DAV:}prop`. This
+ fixes issues when using sabre/dav as a client.
+* The zip release ships with [sabre/vobject 4.0.0-alpha2][vobj],
+ [sabre/http 4.1.0][http], [sabre/event 2.0.2][evnt],
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.2.0][xml].
+
+
+3.1.0-alpha1 (2015-07-19)
+-------------------------
+
+* Now requires PHP 5.5
+* Upgraded to vobject 4, which is a lot faster.
+* Support for PHP 7.
+* #690: Support for `calendar-availability`, draft 05.
+ [reference][calendar-availability].
+* #691: Workaround for broken Windows Phone client.
+* The zip release ships with [sabre/vobject 4.0.0-alpha1][vobj],
+ [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.1.0][xml].
+
+
+3.0.10 (2016-??-??)
+------------------
+
+* #889 Added support for filtering vCard properties in the addressbook-query
+ REPORT (@DeepDiver1975).
+
+
+3.0.9 (2016-04-06)
+------------------
+
+* Set minimum libxml version to 2.7.0 in `composer.json`.
+* #727: Added another workaround to make CalDAV work for Windows 10 clients.
+* #805: It wasn't possible to create calendars that hold events, journals and
+ todos using MySQL, because the `components` column was 1 byte too small.
+* The zip release ships with [sabre/vobject 3.5.1][vobj],
+ [sabre/http 4.2.1][http], [sabre/event 2.0.2][evnt],
+ [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml].
+
+
+3.0.8 (2016-03-12)
+------------------
+
+* #784: Sync logs for address books were not correctly cleaned up after
+ deleting them.
+* #787: Cannot use non-seekable stream-wrappers with range requests.
+* Faster XML parsing and generating due to sabre/xml update.
+* The zip release ships with [sabre/vobject 3.5.0][vobj],
+ [sabre/http 4.2.1][http], [sabre/event 2.0.2][evnt],
+ [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml].
+
+
+3.0.7 (2016-01-12)
+------------------
+
+* #752: PHP 7 support for 3.0 branch. (@DeepDiver1975)
+* The zip release ships with [sabre/vobject 3.5.0][vobj],
+ [sabre/http 4.2.1][http], [sabre/event 2.0.2][evnt],
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.3.0][xml].
+
+
+3.0.6 (2016-01-04)
+------------------
+
+* #730: Switched all mysql tables to `utf8mb4` character set, allowing you to
+ use emoji in some tables where you couldn't before.
+* #729: Not all calls to `Sabre\DAV\Tree::getChildren()` were properly cached.
+* #734: Return `418 I'm a Teapot` when generating a multistatus response that
+ has resources with no returned properties.
+* #740: Bugs in `migrate20.php` script.
+* The zip release ships with [sabre/vobject 3.4.8][vobj],
+ [sabre/http 4.1.0][http], [sabre/event 2.0.2][evnt],
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.3.0][xml].
+
+
+3.0.5 (2015-09-15)
+------------------
+
+* #704: Fixed broken uri encoding in multistatus responses. This affected
+ at least CyberDuck, but probably also others.
+* The zip release ships with [sabre/vobject 3.4.7][vobj],
+* The zip release ships with [sabre/vobject 3.4.7][vobj],
+ [sabre/http 4.1.0][http], [sabre/event 2.0.2][evnt],
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.2.0][xml].
+
+
+3.0.4 (2015-09-06)
+------------------
+
+* #703: PropPatch in client is not correctly encoded.
+* #709: Throw exception when running into empty
+ `supported-calendar-component-set`.
+* #711: Don't trigger deserializers for empty elements in `{DAV:}prop`. This
+ fixes issues when using sabre/dav as a client.
+* #705: A `MOVE` request that gets prevented from deleting the source resource
+ will still remove the target resource. Now all events are triggered before
+ any destructive operations.
+* The zip release ships with [sabre/vobject 3.4.7][vobj],
+ [sabre/http 4.1.0][http], [sabre/event 2.0.2][evnt],
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.2.0][xml].
+
+
+3.0.3 (2015-08-06)
+------------------
+
+* #700: Digest Auth fails on `HEAD` requests.
+* Fixed example files to no longer use now-deprecated realm argument.
+* The zip release ships with [sabre/vobject 3.4.6][vobj],
+ [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.1.0][xml].
+
+
+3.0.2 (2015-07-21)
+------------------
+
+* #657: Migration script would break when coming a cross an iCalendar object
+ with no UID.
+* #691: Workaround for broken Windows Phone client.
+* Fixed a whole bunch of incorrect php docblocks.
+* The zip release ships with [sabre/vobject 3.4.5][vobj],
+ [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.1.0][xml].
+
+
+3.0.1 (2015-07-02)
+------------------
+
+* #674: Postgres sql file fixes. (@davesouthey)
+* #677: Resources with the name '0' would not get retrieved when using
+ `Depth: infinity` in a `PROPFIND` request.
+* #680: Fix 'autoprefixing' of dead `{DAV:}href` properties.
+* #675: NTLM support in DAV\Client. (@k42b3)
+* The zip release ships with [sabre/vobject 3.4.5][vobj],
+ [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.1.0][xml].
+
+
+3.0.0 (2015-06-02)
+------------------
+
+* No changes since last beta.
+* The zip release ships with [sabre/vobject 3.4.5][vobj],
+ [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.0.0][xml].
+
+
+3.0.0-beta3 (2015-05-29)
+------------------------
+
+* Fixed deserializing href properties with no value.
+* Fixed deserializing `{DAV:}propstat` without a `{DAV:}prop`.
+* #668: More information about vcf-export-plugin in browser plugin.
+* #669: Add export button to browser plugin for address books. (@mgee)
+* #670: multiget report hrefs were not decoded.
+* The zip release ships with [sabre/vobject 3.4.4][vobj],
+ [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.0.0][xml].
+
+
+3.0.0-beta2 (2015-05-27)
+------------------------
+
+* A node's properties should not overwrite properties that were already set.
+* Some uris were not correctly encoded in notifications.
+* The zip release ships with [sabre/vobject 3.4.4][vobj],
+ [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.0.0][xml].
+
+
+3.0.0-beta1 (2015-05-25)
+------------------------
+
+* `migrate22.php` is now called `migrate30.php`.
+* Using php-cs-fixer for automated coding standards enforcement and fixing.
+* #660: principals could break html output.
+* #662: Fixed several bugs in the `share` request parser.
+* #665: Fix a bug in serialization of complex properties in the proppatch
+ request in the client.
+* #666: expand-property report did not correctly prepend the base uri when
+ generating uris, this caused delegation to break.
+* #659: Don't throw errors when when etag-related checks are done on
+ collections.
+* Fully supporting the updated `Prefer` header syntax, as defined in
+ [rfc7240][rfc7240].
+* The zip release ships with [sabre/vobject 3.4.3][vobj],
+ [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.0.0][xml].
+
+
+3.0.0-alpha1 (2015-05-19)
+-------------------------
+
+* It's now possible to get all property information from files using the
+ browser plugin.
+* Browser plugin will now show a 'calendar export' button when the
+ ics-export plugin is enabled.
+* Some nodes that by default showed the current time as their last
+ modification time, now no longer has a last modification time.
+* CardDAV namespace was missing from default namespaceMap.
+* #646: Properties can now control their own HTML output in the browser plugin.
+* #646: Nicer HTML output for the `{DAV:}acl` property.
+* Browser plugin no longer shows a few properties that take up a lot of space,
+ but are likely not really interesting for most users.
+* #654: Added a collection, `Sabre\DAVACL\FS\HomeCollection` for automatically
+ creating a private home collection per-user.
+* Changed all MySQL columns from `VARCHAR` to `VARBINARY` where possible.
+* Improved older migration scripts a bit to allow easier testing.
+* The zip release ships with [sabre/vobject 3.4.3][vobj],
+ [sabre/http 4.0.0-alpha3][http], [sabre/event 2.0.2][evnt],
+ [sabre/uri 1.0.1][uri] and [sabre/xml 0.4.3][xml].
+
+
+2.2.0-alpha4 (2015-04-13)
+-------------------------
+
+* Complete rewrite of the XML system. We now use our own [sabre/xml][xml],
+ which has a much smarter XML Reader and Writer.
+* BC Break: It's no longer possible to instantiate the Locks plugin without
+ a locks backend. I'm not sure why this ever made sense.
+* Simplified the Locking system and fixed a bug related to if tokens checking
+ locks unrelated to the current request.
+* `FSExt` Directory and File no longer do custom property storage. This
+ functionality is already covered pretty well by the `PropertyStorage` plugin,
+ so please switch.
+* Renamed `Sabre\CardDAV\UserAddressBooks` to `Sabre\CardDAV\AddressBookHome`
+ to be more consistent with `CalendarHome` as well as the CardDAV
+ specification.
+* `Sabre\DAV\IExtendedCollection` now receives a `Sabre\DAV\MkCol` object as
+ its second argument, and no longer receives seperate properties and
+ resourcetype arguments.
+* `MKCOL` now integrates better with propertystorage plugins.
+* #623: Remove need of temporary files when working with Range requests.
+ (@dratini0)
+* The zip release ships with [sabre/vobject 3.4.2][vobj],
+ [sabre/http 4.0.0-alpha1][http], [sabre/event 2.0.1][evnt],
+ [sabre/uri 1.0.0][uri] and [sabre/xml 0.4.3][xml].
+
+
+2.2.0-alpha3 (2015-02-25)
+-------------------------
+
+* Contains all the changes introduced between 2.1.2 and 2.1.3.
+* The zip release ships with [sabre/vobject 3.4.2][vobj],
+ [sabre/http 4.0.0-alpha1][http], [sabre/event 2.0.1][evnt] and
+ [sabre/uri 1.0.0][uri].
+
+
+2.2.0-alpha2 (2015-01-09)
+-------------------------
+
+* Renamed `Sabre\DAV\Auth\Backend\BackendInterface::requireAuth` to
+ `challenge`, which is a more correct and better sounding name.
+* The zip release ships with [sabre/vobject 3.3.5][vobj],
+ [sabre/http 3.0.4][http], [sabre/event 2.0.1][evnt].
+
+
+2.2.0-alpha1 (2014-12-10)
+-------------------------
+
+* The browser plugin now has a new page with information about your sabredav
+ server, and shows information about every plugin that's loaded in the
+ system.
+* #191: The Authentication system can now support multiple authentication
+ backends.
+* Removed: all `$tableName` arguments from every PDO backend. This was already
+ deprecated, but has now been fully removed. All of these have been replaced
+ with public properties.
+* Deleted several classes that were already deprecated much earlier:
+ * `Sabre\CalDAV\CalendarRootNode`
+ * `Sabre\CalDAV\UserCalendars`
+ * `Sabre\DAV\Exception\FileNotFound`
+ * `Sabre\DAV\Locks\Backend\FS`
+ * `Sabre\DAV\PartialUpdate\IFile`
+ * `Sabre\DAV\URLUtil`
+* Removed: `Sabre\DAV\Client::addTrustedCertificates` and
+ `Sabre\DAV\Client::setVerifyPeer`.
+* Removed: `Sabre\DAV\Plugin::getPlugin()` can now no longer return plugins
+ based on its class name.
+* Removed: `Sabre\DAVACL\Plugin::getPrincipalByEmail()`.
+* #560: GuessContentType plugin will now set content-type to
+ `application/octet-stream` if a better content-type could not be determined.
+* #568: Added a `componentType` argument to `ICSExportPlugin`, allowing you to
+ specifically fetch `VEVENT`, `VTODO` or `VJOURNAL`.
+* #582: Authentication backend interface changed to be stateless. If you
+ implemented your own authentication backend, make sure you upgrade your class
+ to the latest API!
+* #582: `Sabre\DAV\Auth\Plugin::getCurrentUser()` is now deprecated. Use
+ `Sabre\DAV\Auth\Plugin::getCurrentPrincipal()` instead.
+* #193: Fix `Sabre\DAV\FSExt\Directory::getQuotaInfo()` on windows.
+
+
+2.1.11 (2016-10-06)
+-------------------
+
+* #805: It wasn't possible to create calendars that hold events, journals and
+ todos using MySQL, because the `components` column was 1 byte too small.
+* The zip release ships with [sabre/vobject 3.5.3][vobj],
+ [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].
+
+
+2.1.10 (2016-03-10)
+-------------------
+
+* #784: Sync logs for address books were not correctly cleaned up after
+ deleting them.
+* The zip release ships with [sabre/vobject 3.5.0][vobj],
+ [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].
+
+
+2.1.9 (2016-01-25)
+------------------
+
+* #674: PHP7 support (@DeepDiver1975).
+* The zip release ships with [sabre/vobject 3.5.0][vobj],
+ [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].
+
+
+2.1.8 (2016-01-04)
+------------------
+
+* #729: Fixed a caching problem in the Tree object.
+* #740: Bugs in `migrate20.php` script.
+* The zip release ships with [sabre/vobject 3.4.8][vobj],
+ [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].
+
+
+2.1.7 (2015-09-05)
+------------------
+
+* #705: A `MOVE` request that gets prevented from deleting the source resource
+ will still remove the target resource. Now all events are triggered before
+ any destructive operations.
+* The zip release ships with [sabre/vobject 3.4.7][vobj],
+ [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].
+
+
+2.1.6 (2015-07-21)
+------------------
+
+* #657: Migration script would break when coming a cross an iCalendar object
+ with no UID.
+* #691: Workaround for broken Windows Phone client.
+* The zip release ships with [sabre/vobject 3.4.5][vobj],
+ [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].
+
+
+2.1.5 (2015-07-11)
+------------------
+
+* #677: Resources with the name '0' would not get retrieved when using
+ `Depth: infinity` in a `PROPFIND` request.
+* The zip release ships with [sabre/vobject 3.4.5][vobj],
+ [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].
+
+
+2.1.4 (2015-05-25)
+------------------
+
+* #651: Double-encoded path in the browser plugin. Should fix a few broken
+ links in some setups.
+* #650: Correctly cleaning up change info after deleting calendars (@ErrOrnAmE).
+* #658: Updating `schedule-calendar-default-URL` does not work well, so we're
+ disabling it until there's a better fix.
+* The zip release ships with [sabre/vobject 3.4.3][vobj],
+ [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].
+
+
+2.1.3 (2015-02-25)
+------------------
+
+* #586: `SCHEDULE-STATUS` should not contain a reason-phrase.
+* #539: Fixed a bug related to scheduling in shared calendars.
+* #595: Support for calendar-timezone in iCalendar exports.
+* #581: findByUri would send empty prefixes to the principal backend (@soydeedo)
+* #611: Escaping a bit more HTML output in the browser plugin. (@LukasReschke)
+* #610: Don't allow discovery of arbitrary files using `..` in the browser
+ plugin (@LukasReschke).
+* Browser plugin now shows quota properties.
+* #612: PropertyStorage didn't delete properties from nodes when a node's
+ parents get deleted.
+* #581: Fixed problems related to finding attendee information during
+ scheduling.
+* The zip release ships with [sabre/vobject 3.4.2][vobj],
+ [sabre/http 3.0.4][http], and [sabre/event 2.0.1][evnt].
+
+
+2.1.2 (2014-12-10)
+------------------
+
+* #566: Another issue related to the migration script, which would cause
+ scheduling to not work well for events that were already added before the
+ migration.
+* #567: Doing freebusy requests on accounts that had 0 calendars would throw
+ a `E_NOTICE`.
+* #572: `HEAD` requests trigger a PHP warning.
+* #579: Browser plugin can throw exception for a few resourcetypes that didn't
+ have an icon defined.
+* The zip release ships with [sabre/vobject 3.3.4][vobj],
+ [sabre/http 3.0.4][http], and [sabre/event 2.0.1][evnt].
+
+
+2.1.1 (2014-11-22)
+------------------
+
+* #561: IMip Plugin didn't strip mailto: from email addresses.
+* #566: Migration process had 2 problems related to adding the `uid` field
+ to the `calendarobjects` table.
+* The zip release ships with [sabre/vobject 3.3.4][vobj],
+ [sabre/http 3.0.2][http], and [sabre/event 2.0.1][evnt].
+
+
+2.1.0 (2014-11-19)
+------------------
+
+* #541: CalDAV PDO backend didn't respect overridden PDO table names.
+* #550: Scheduling invites are no longer delivered into shared calendars.
+* #554: `calendar-multiget` `REPORT` did not work on inbox items.
+* #555: The `calendar-timezone` property is now respected for floating times
+ and all-day events in the `calendar-query`, `calendar-multiget` and
+ `free-busy-query` REPORTs.
+* #555: The `calendar-timezone` property is also respected for scheduling
+ free-busy requests.
+* #547: CalDAV system too aggressively 'corrects' incoming iCalendar data, and
+ as a result doesn't return an etag for common cases.
+* The zip release ships with [sabre/vobject 3.3.4][vobj],
+ [sabre/http 3.0.2][http], and [sabre/event 2.0.1][evnt].
+
+
+2.1.0-alpha2 (2014-10-23)
+-------------------------
+
+* Added: calendar-user-address-set to default principal search properties
+ list. This should fix iOS attendee autocomplete support.
+* Changed: Moved all 'notifications' functionality from `Sabre\CalDAV\Plugin`
+ to a new plugin: `Sabre\CalDAV\Notifications\Plugin`. If you want to use
+ notifications-related functionality, just add this plugin.
+* Changed: Accessing the caldav inbox, outbox or notification collection no
+ longer triggers getCalendarsForUser() on backends.
+* #533: New invites are no longer delivered to taks-only calendars.
+* #538: Added `calendarObjectChange` event.
+* Scheduling speedups.
+* #539: added `afterResponse` event. (@joserobleda)
+* Deprecated: All the "tableName" constructor arguments for all the PDO
+ backends are now deprecated. They still work, but will be removed in the
+ next major sabredav version. Every argument that is now deprecated can now
+ be accessed as a public property on the respective backends.
+* #529: Added getCalendarObjectByUID to PDO backend, speeding up scheduling
+ operations on large calendars.
+* The zip release ships with [sabre/vobject 3.3.3][vobj],
+ [sabre/http 3.0.2][http], and [sabre/event 2.0.1][evnt].
+
+
+2.1.0-alpha1 (2014-09-23)
+-------------------------
+
+* Added: Support for [rfc6638][rfc6638], also known as CalDAV Scheduling.
+* Added: Automatically converting between vCard 3, 4 and jCard using the
+ `Accept:` header, in CardDAV reports, and automatically converting from
+ jCard to vCard upon `PUT`. It's important to note that your backends _may_
+ now receive both vCard 3.0 and 4.0.
+* Added: #444. Collections can now opt-in to support high-speed `MOVE`.
+* Changed: PropertyStorage backends now have a `move` method.
+* Added: `beforeMove`, and `afterMove` events.
+* Changed: A few database changes for the CalDAV PDO backend. Make sure you
+ run `bin/migrate21.php` to upgrade your database schema.
+* Changed: CalDAV backends have a new method: `getCalendarObjectByUID`. This
+ method MUST be implemented by all backends, but the `AbstractBackend` has a
+ simple default implementation for this.
+* Changed: `Sabre\CalDAV\UserCalendars` has been renamed to
+ `Sabre\CalDAV\CalendarHome`.
+* Changed: `Sabre\CalDAV\CalendarRootNode` has been renamed to
+ `Sabre\CalDAV\CalendarRoot`.
+* Changed: The IMipHandler has been completely removed. With CalDAV scheduling
+ support, it is no longer needed. It's functionality has been replaced by
+ `Sabre\CalDAV\Schedule\IMipPlugin`, which can now send emails for clients
+ other than iCal.
+* Removed: `Sabre\DAV\ObjectTree` and `Sabre\DAV\Tree\FileSystem`. All this
+ functionality has been merged into `Sabre\DAV\Tree`.
+* Changed: PrincipalBackend now has a findByUri method.
+* Changed: `PrincipalBackend::searchPrincipals` has a new optional `test`
+ argument.
+* Added: Support for the `{http://calendarserver.org/ns/}email-address-set`
+ property.
+* #460: PropertyStorage must move properties during `MOVE` requests.
+* Changed: Restructured the zip distribution to be a little bit more lean
+ and consistent.
+* #524: Full support for the `test="anyof"` attribute in principal-search
+ `REPORT`.
+* #472: Always returning lock tokens in the lockdiscovery property.
+* Directory entries in the Browser plugin are sorted by type and name.
+ (@aklomp)
+* #486: It's now possible to return additional properties when an 'allprop'
+ PROPFIND request is being done. (@aklomp)
+* Changed: Now return HTTP errors when an addressbook-query REPORT is done
+ on a uri that's not a vcard. This should help with debugging this common
+ mistake.
+* Changed: `PUT` requests with a `Content-Range` header now emit a 400 status
+ instead of 501, as per RFC7231.
+* Added: Browser plugin can now display the contents of the
+ `{DAV:}supported-privilege-set` property.
+* Added: Now reporting `CALDAV:max-resource-size`, but we're not actively
+ restricting it yet.
+* Changed: CalDAV plugin is now responsible for reporting
+ `CALDAV:supported-collation-set` and `CALDAV:supported-calendar-data`
+ properties.
+* Added: Now reporting `CARDDAV:max-resource-size`, but we're not actively
+ restricting it yet.
+* Added: Support for `CARDDAV:supported-collation-set`.
+* Changed: CardDAV plugin is now responsible for reporting
+ `CARDDAV:supported-address-data`. This functionality has been removed from
+ the CardDAV PDO backend.
+* When a REPORT is not supported, we now emit HTTP error 415, instead of 403.
+* #348: `HEAD` requests now work wherever `GET` also works.
+* Changed: Lower priority for the iMip plugins `schedule` event listener.
+* Added: #523 Custom CalDAV backends can now mark any calendar as read-only.
+* The zip release ships with [sabre/vobject 3.3.3][vobj],
+ [sabre/http 3.0.0][http], and [sabre/event 2.0.0][evnt].
+
+
+2.0.9 (2015-09-04)
+------------------
+
+* #705: A `MOVE` request that gets prevented from deleting the source resource
+ will still remove the target resource. Now all events are triggered before
+ any destructive operations.
+* The zip release ships with [sabre/vobject 3.4.6][vobj],
+ [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt].
+
+
+
+2.0.8 (2015-07-11)
+------------------
+
+* #677: Resources with the name '0' would not get retrieved when using
+ `Depth: infinity` in a `PROPFIND` request.
+* The zip release ships with [sabre/vobject 3.3.5][vobj],
+ [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt].
+
+
+2.0.7 (2015-05-25)
+------------------
+
+* #650: Correctly cleaning up change info after deleting calendars (@ErrOrnAmE).
+* The zip release ships with [sabre/vobject 3.3.4][vobj],
+ [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt].
+
+
+2.0.6 (2014-12-10)
+------------------
+
+* Added `Sabre\CalDAV\CalendarRoot` as an alias for
+ `Sabre\CalDAV\CalendarRootNode`. The latter is going to be deprecated in 2.1,
+ so this makes it slightly easier to write code that works in both branches.
+* #497: Making sure we're initializing the sync-token field with a value after
+ migration.
+* The zip release ships with [sabre/vobject 3.3.4][vobj],
+ [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt].
+
+
+2.0.5 (2014-10-14)
+------------------
+
+* #514: CalDAV PDO backend didn't work when overriding the 'calendar changes'
+ database table name.
+* #515: 304 status code was not being sent when checking preconditions.
+* The zip release ships with [sabre/vobject 3.3.3][vobj],
+ [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt].
+
+
+2.0.4 (2014-08-27)
+------------------
+
+* #483: typo in calendars creation for PostgreSQL.
+* #487: Locks are now automatically removed after a node has been deleted.
+* #496: Improve CalDAV and CardDAV sync when there is no webdav-sync support.
+* Added: Automatically mapping internal sync-tokens to getctag.
+* The zip release ships with [sabre/vobject 3.3.1][vobj],
+ [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt].
+
+
+2.0.3 (2014-07-14)
+------------------
+
+* #474: Fixed PropertyStorage `pathFilter()`.
+* #476: CSP policy incorrect, causing stylesheets to not load in the browser
+ plugin.
+* #475: Href properties in the browser plugin sometimes included a backslash.
+* #478: `TooMuchMatches` exception never worked. This was fixed, and we also
+ took this opportunity to rename it to `TooManyMatches`.
+* The zip release ships with [sabre/vobject 3.2.4][vobj],
+ [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt].
+
+
+2.0.2 (2014-06-12)
+------------------
+
+* #470: Fixed compatibility with PHP < 5.4.14.
+* #467: Fixed a problem in `examples/calendarserver.php`.
+* #466: All the postgresql sample files have been updated.
+* Fixed: An error would be thrown if a client did a propfind on a node the
+ user didn't have access to.
+* Removed: Old and broken example code from the `examples/` directory.
+* The zip release ships with [sabre/vobject 3.2.3][vobj],
+ [sabre/http 2.0.3][http], and [sabre/event 1.0.1][evnt].
+
+
+2.0.1 (2014-05-28)
+------------------
+
+* #459: PROPFIND requests on Files with no Depth header would return a fatal
+ error.
+* #464: A PROPFIND allprops request should not return properties with status
+ 404.
+* The zip release ships with [sabre/vobject 3.2.2][vobj],
+ [sabre/http 2.0.3][http], and [sabre/event 1.0.0][evnt].
+
+
+2.0.0 (2014-05-22)
+------------------
+
+* The zip release ships with [sabre/vobject 3.2.2][vobj],
+ [sabre/http 2.0.3][http], and [sabre/event 1.0.0][evnt].
+* Fixed: #456: Issue in sqlite migration script.
+* Updated: MySQL database schema optimized by using more efficient column types.
+* Cleaned up browser design.
+
+
+2.0.0-beta1 (2014-05-15)
+-------------------------
+
+* The zip release ships with [sabre/vobject 3.2.2][vobj],
+ [sabre/http 2.0.3][http], and [sabre/event 1.0.0][evnt].
+* BC Break: Property updating and fetching got refactored. Read the [migration
+ document][mi20] for more information. This allows for creation of a generic
+ property storage, and other property-related functionality that was not
+ possible before.
+* BC Break: Removed `propertyUpdate`, `beforeGetProperties` and
+ `afterGetProperties` events.
+* Fixed: #413: Memory optimizations for the CardDAV PDO backend.
+* Updated: Brand new browser plugin with more debugging features and a design
+ that is slightly less painful.
+* Added: Support for the `{DAV:}supported-method-set` property server-wide.
+* Making it easier for implementors to override how the CardDAV addressbook
+ home is located.
+* Fixed: Issue #422 Preconditions were not being set on PUT on non-existent
+ files. Not really a chance for data-loss, but incorrect nevertheless.
+* Fixed: Issue #428: Etag check with `If:` fails if the target is a collection.
+* Fixed: Issues #430, #431, #433: Locks plugin didn't not properly release
+ filesystem based locks.
+* Fixed: #443. Support for creating new calendar subscriptions for OS X 10.9.2
+ and up.
+* Removed: `Sabre\DAV\Server::NODE_*` constants.
+* Moved all precondition checking into a central place, instead of having to
+ think about it on a per-method basis.
+* jCal transformation for calendar-query REPORT now works again.
+* Switched to PSR-4
+* Fixed: #175. Returning ETag header upon a failed `If-Match` or
+ `If-None-Match` check.
+* Removed: `lib/Sabre/autoload.php`. Use `vendor/autoload.php` instead.
+* Removed: all the rfc documentation from the sabre/dav source. This made the
+ package needlessly larger.
+* Updated: Issue #439. Lots of updates in PATCH support. The
+ Sabre_DAV_PartialUpdate_IFile interface is now deprecated and will be
+ removed in a future version.
+* Added: `Sabre\DAV\Exception\LengthRequired`.
+
+1.9.0-alpha2 (2014-01-14)
+-------------------------
+
+* The zip release ships with sabre/vobject 3.1.3, sabre/http 2.0.1, and
+ sabre/event 1.0.0.
+* Added: Browser can now inspect any node, if ?sabreaction=browser is appended.
+* Fixed: Issue #178. Support for multiple items in the Timeout header.
+* Fixed: Issue #382. Stricter checking if calendar-query is allowed to run.
+* Added: Depth: Infinity support for PROPFIND request. Thanks Thomas Müller and
+ Markus Goetz.
+
+
+1.9.0-alpha1 (2013-11-07)
+-------------------------
+
+* The zip release ships with sabre/vobject 3.1.3, sabre/http 2.0.0alpha5, and
+ sabre/event 1.0.0.
+* BC Break: The CardDAV and CalDAV BackendInterface each have a new method:
+ getMultipleCards and getMultipleCalendarObjects. The Abstract and PDO backends
+ have default implementations, but if you implement that interface directly,
+ this method is now required.
+* BC Break: XML property classes now receive an extra argument in their
+ unserialize method ($propertyMap). This allows for recursively parsing
+ properties, if needed.
+* BC Break: Now using sabre/event for event emitting/subscription. For plugin
+ authors this means Server::subscribeEvent is now Server::on, and
+ Server::broadcastEvent is now Server::emit.
+* BC Break: Almost all core functionality moved into a CorePlugin.
+* BC Break: Most events triggered by the server got an overhaul.
+* Changed: Sabre\HTTP now moved into a dedicated sabre/http package.
+* Added: Support for WebDAV-sync (rfc6578).
+* Added: Support for caldav-subscriptions, which is an easy way for caldav
+ clients to manage a list of subscriptions on the server.
+* Added: Support for emitting and receiving jCal instead of iCalendar for
+ CalDAV.
+* Added: BasicCallback authenticaton backend, for creating simple authentication
+ systems without having to define any classes.
+* Added: A $transactionType property on the server class. This can be used for
+ logging and performance measuring purposes.
+* Fixed: If event handlers modify the request body from a PUT request, an ETag
+ is no longer sent back.
+* Added: Sabre\DAV\IMultiGet to optimize requests that retrieve information
+ about lists of resources.
+* Added: MultiGet support to default CalDAV and CardDAV backends, speeding up
+ the multiget and sync reports quite a bit!
+* Added: ICSExportPlugin can now generate jCal, filter on time-ranges and expand
+ recurrences.
+* Fixed: Read-only access to calendars still allows the sharee to modify basic
+ calendar properties, such as the displayname and color.
+* Changed: The default supportedPrivilegeSet has changed. Most privileges are no
+ longer marked as abstract.
+* Changed: More elegant ACL management for CalendarObject and Card nodes.
+* Added: Browser plugin now marks a carddav directory as type Directory, and a
+ shared calendar as 'Shared'.
+* Added: When debugExceptions is turned on, all previous exceptions are also
+ traversed.
+* Removed: Got rid of the Version classes for CalDAV, CardDAV, HTTP, and DAVACL.
+ Now that there's no separate packages anymore, this makes a bit more sense.
+* Added: Generalized the multistatus response parser a bit more, for better
+ re-use.
+* Added: Sabre\DAV\Client now has support for complex properties for PROPPATCH.
+ (Issue #299).
+* Added: Sabre\DAV\Client has support for gzip and deflate encoding.
+* Added: Sabre\DAV\Client now has support for sending objects as streams.
+* Added: Deserializer for {DAV:}current-user-privilege-set.
+* Added: Addressbooks or backends can now specify custom acl rules when creating
+ cards.
+* Added: The ability for plugins to validate custom tokens in If: headers.
+* Changed: Completely refactored the Lock plugin to deal with the new If: header
+ system.
+* Added: Checking preconditions for MOVE, COPY, DELETE and PROPPATCH methods.
+* Added: has() method on DAV\Property\SupportedReportSet.
+* Added: If header now gets checked (with ETag) all the time. Before the dealing
+ with the If-header was a responsibility of the Locking plugin.
+* Fixed: Outbox access for delegates.
+* Added: Issue 333: It's now possible to override the calendar-home in the
+ CalDAV plugin.
+* Added: A negotiateContentType to HTTP\Request. A convenience method.
+* Fixed: Issue 349: Denying copying or moving a resource into it's own subtree.
+* Fixed: SabreDAV catches every exception again.
+* Added: Issue #358, adding a component=vevent parameter to the content-types
+ for calendar objects, if the caldav backend provides this info.
+
+
+1.8.12-stable (2015-01-21)
+--------------------------
+
+* The zip release ships with sabre/vobject 2.1.7.
+* #568: Support empty usernames and passwords in basic auth.
+
+
+1.8.11 (2014-12-10)
+-------------------
+
+* The zip release ships with sabre/vobject 2.1.6.
+* Updated: MySQL database schema optimized by using more efficient column types.
+* #516: The DAV client will now only redirect to HTTP and HTTPS urls.
+
+
+1.8.10 (2014-05-15)
+-------------------
+
+* The zip release ships with sabre/vobject 2.1.4.
+* includes changes from version 1.7.12.
+
+
+1.8.9 (2014-02-26)
+------------------
+
+* The zip release ships with sabre/vobject 2.1.3.
+* includes changes from version 1.7.11.
+
+
+1.8.8 (2014-02-09)
+------------------
+
+* includes changes from version 1.7.10.
+* The zip release ships with sabre/vobject 2.1.3.
+
+1.8.7 (2013-10-02)
+------------------
+
+* the zip release ships with sabre/vobject 2.1.3.
+* includes changes from version 1.7.9.
+
+
+1.8.6 (2013-06-18)
+------------------
+
+* The zip release ships with sabre/vobject 2.1.0.
+* Includes changes from version 1.7.8.
+
+
+1.8.5 (2013-04-11)
+------------------
+
+* The zip release ships with sabre/vobject 2.0.7.
+* Includes changes from version 1.7.7.
+
+
+1.8.4 (2013-04-08)
+------------------
+
+* The zip release ships with sabre/vobject 2.0.7.
+* Includes changes from version 1.7.6.
+
+
+1.8.3 (2013-03-01)
+------------------
+
+* The zip release ships with sabre/vobject 2.0.6.
+* Includes changes from version 1.7.5.
+* Fixed: organizer email-address for shared calendars is now prefixed with
+ mailto:, as it should.
+
+
+1.8.2 (2013-01-19)
+------------------
+
+* The zip release ships with sabre/vobject 2.0.5.
+* Includes changes from version 1.7.4.
+
+
+1.8.1 (2012-12-01)
+------------------
+
+* The zip release ships with sabre/vobject 2.0.5.
+* Includes changes from version 1.7.3.
+* Fixed: Typo in 1.7 migration script caused it to fail.
+
+
+1.8.0 (2012-11-08)
+------------------
+
+* The zip release ships with sabre/vobject 2.0.5.
+* BC Break: Moved the entire codebase to PHP namespaces.
+* BC Break: Every backend package (CalDAV, CardDAV, Auth, Locks, Principals) now
+ has consistent naming conventions. There's a BackendInterface, and an
+ AbstractBackend class.
+* BC Break: Changed a bunch of constructor signatures in the CalDAV package, to
+ reduce dependencies on the ACL package.
+* BC Break: Sabre_CalDAV_ISharedCalendar now also has a getShares method, so
+ sharees can figure out who is also on a shared calendar.
+* Added: Sabre_DAVACL_IPrincipalCollection interface, to advertise support for
+ principal-property-search on any node.
+* Added: Simple console script to fire up a fileserver in the current directory
+ using PHP 5.4's built-in webserver.
+* Added: Sharee's can now also read out the list of invites for a shared
+ calendar.
+* Added: The Proxy principal classes now both implement an interface, for
+ greater flexibility.
+
+
+1.7.13 (2014-07-28)
+-------------------
+
+* The zip release ships with sabre/vobject 2.1.4.
+* Changed: Removed phing and went with a custom build script for now.
+
+
+1.7.12 (2014-05-15)
+-------------------
+
+* The zip release ships with sabre/vobject 2.1.4.
+* Updated: Issue #439. Lots of updates in PATCH support. The
+ Sabre_DAV_PartialUpdate_IFile interface is now deprecated and will be removed
+ in a future version.
+* Fixed: Restoring old setting after changing libxml_disable_entity_loader.
+* Fixed: Issue #422: Preconditions were not being set on PUT on non-existent
+ files. Not really a chance for data-loss, but incorrect nevertheless.
+* Fixed: Issue #427: Now checking preconditions on DELETE requests.
+* Fixed: Issue #428: Etag check with If: fails if the target is a collection.
+* Fixed: Issue #393: PATCH request with missing end-range was handled
+ incorrectly.
+* Added: Sabre_DAV_Exception_LengthRequired to omit 411 errors.
+
+
+1.7.11 (2014-02-26)
+-------------------
+
+* The zip release ships with sabre/vobject 2.1.3.
+* Fixed: Issue #407: large downloads failed.
+* Fixed: Issue #414: XXE security problem on older PHP versions.
+
+
+1.7.10 (2014-02-09)
+-------------------
+
+* Fixed: Issue #374: Don't urlescape colon (:) when it's not required.
+* Fixed: Potential security vulnerability in the http client.
+
+
+1.7.9 (2013-10-02)
+------------------
+
+* The zip release ships with sabre/vobject 2.1.3.
+* Fixed: Issue #365. Incorrect output when principal urls have spaces in them.
+* Added: Issue #367: Automatically adding a UID to vcards that don't have them.
+
+
+1.7.8 (2013-06-17)
+------------------
+
+* The zip release ships with sabre/vobject 2.1.0.
+* Changed: Sabre\DAV\Client::verifyPeer is now a protected property (instead of
+ private).
+* Fixed: Text was incorrectly escaped in the Href and HrefList properties,
+ disallowing urls with ampersands (&) in them.
+* Added: deserializer for Sabre\DAVACL\Property\CurrentUserPrivilegeSet.
+* Fixed: Issue 335: Client only deserializes properties with status 200.
+* Fixed: Issue 341: Escaping xml in 423 Locked error responses.
+* Added: Issue 339: beforeGetPropertiesForPath event.
+
+
+1.7.7 (2013-04-11)
+------------------
+
+* The zip release ships with sabre/vobject 2.0.7.
+* Fixed: Assets in the browser plugins were not being served on windows
+ machines.
+
+
+1.7.6 (2013-04-08)
+------------------
+
+* The zip release ships with sabre/vobject 2.0.7.
+* Fixed: vcardurl in database schema can now hold 255 characters instead of 80
+ (which is often way to small).
+* Fixed: The browser plugin potentially allowed people to open any arbitrary
+ file on windows servers (CVE-2013-1939).
+
+
+1.7.5 (2013-03-01)
+------------------
+
+* The zip release ships with sabre/vobject 2.0.6.
+* Change: No longer advertising support for 4.0 vcards. iOS and OS X address
+ book don't handle this well, and just advertising 3.0 support seems like the
+ most logical course of action.
+* Added: ->setVerifyPeers to Sabre_DAV_Client (greatly resisting against it,
+ don't use this..).
+
+
+1.7.4 (2013-01-19)
+------------------
+
+* The zip release ships with sabre/vobject 2.0.5.
+* Changed: To be compatible with MS Office 2011 for Mac, a workaround was
+ removed that was added to support old versions of Windows XP (pre-SP3).
+ Indeed! We needed a crazy workaround to work with one MS product in the past,
+ and we can't keep that workaround to be compatible with another MS product.
+* Fixed: expand-properties REPORT had incorrect values for the href element.
+* Fixed: Range requests now work for non-seekable streams. (Thanks Alfred
+ Klomp).
+* Fixed: Changed serialization of {DAV:}getlastmodified and {DAV:}supportedlock
+ to improve compatibility with MS Office 2011 for Mac.
+* Changed: reverted the automatic translation of 'DAV:' xml namespaces to
+ 'urn:DAV' when parsing files. Issues were reported with libxml 2.6.32, on a
+ relatively recent debian release, so we'll wait till 2015 to take this one out
+ again.
+* Added: Sabre_DAV_Exception_ServiceUnavailable, for emitting 503's.
+
+
+1.7.3 (2012-12-01)
+------------------
+
+* The zip release ships with sabre/vobject 2.0.5.
+* Fixed: Removing double slashes from getPropertiesForPath.
+* Change: Marked a few more properties in the CardDAV as protected, instead of
+ private.
+* Fixed: SharingPlugin now plays nicer with other plugins with similar
+ functionality.
+* Fixed: Issue 174. Sending back HTTP/1.0 for requests with this version.
+
+
+1.7.2 (2012-11-08)
+------------------
+
+* The zip release ships with sabre/vobject 2.0.5.
+* Added: ACL plugin advertises support for 'calendarserver-principal-
+ property-search'.
+* Fixed: [#153] Allowing for relative http principals in iMip requests.
+* Added: Support for cs:first-name and cs:last-name properties in sharing
+ invites.
+* Fixed: Made a bunch of properties protected, where they were private before.
+* Added: Some non-standard properties for sharing to improve compatibility.
+* Fixed: some bugfixes in postgres sql script.
+* Fixed: When requesting some properties using PROPFIND, they could show up as
+ both '200 Ok' and '403 Forbidden'.
+* Fixed: calendar-proxy principals were not checked for deeper principal
+ membership than 1 level.
+* Fixed: setGroupMemberSet argument now correctly receives relative principal
+ urls, instead of the absolute ones.
+* Fixed: Server class will filter out any bonus properties if any extra were
+ returned. This means the implementor of the IProperty class can be a bit
+ lazier when implementing. Note: bug numbers after this line refer to Google
+ Code tickets. We're using github now.
+
+
+1.7.1 (2012-10-07)
+------------------
+
+* Fixed: include path problem in the migration script.
+
+
+1.7.0 (2012-10-06)
+------------------
+
+* BC Break: The calendarobjects database table has a bunch of new fields, and a
+ migration script is required to ensure everything will keep working. Read the
+ wiki for more details.
+* BC Break: The ICalendar interface now has a new method: calendarQuery.
+* BC Break: In this version a number of classes have been deleted, that have
+ been previously deprecated. Namely: - Sabre_DAV_Directory (now:
+ Sabre_DAV_Collection) - Sabre_DAV_SimpleDirectory (now:
+ Sabre_DAV_SimpleCollection)
+* BC Break: Sabre_CalDAV_Schedule_IMip::sendMessage now has an extra argument.
+ If you extended this class, you should fix this method. It's only used for
+ informational purposes.
+* BC Break: The DAV: namespace is no longer converted to urn:DAV. This was a
+ workaround for a bug in older PHP versions (pre-5.3).
+* Removed: Sabre.includes.php was deprecated, and is now removed.
+* Removed: Sabre_CalDAV_Server was deprecated, and is now removed. Please use
+ Sabre_DAV_Server and check the examples in the examples/ directory.
+* Changed: The Sabre_VObject library now spawned into it's own project! The
+ VObject library is still included in the SabreDAV zip package.
+* Added: Experimental interfaces to allow implementation of caldav-sharing. Note
+ that no implementation is provided yet, just the api hooks.
+* Added: Free-busy reporting compliant with the caldav-scheduling standard. This
+ allows iCal and other clients to fetch other users' free-busy data.
+* Added: Experimental NotificationSupport interface to add caldav notifications.
+* Added: VCF Export plugin. If enabled, it can generate an export of an entire
+ addressbook.
+* Added: Support for PATCH using a SabreDAV format, to live-patch files.
+* Added: Support for Prefer: return-minimal and Brief: t headers for PROPFIND
+ and PROPPATCH requests.
+* Changed: Responsibility for dealing with the calendar-query is now moved from
+ the CalDAV plugin to the CalDAV backends. This allows for heavy optimizations.
+* Changed: The CalDAV PDO backend is now a lot faster for common calendar
+ queries.
+* Changed: We are now using the composer autoloader.
+* Changed: The CalDAV backend now all implement an interface.
+* Changed: Instead of Sabre_DAV_Property, Sabre_DAV_PropertyInterface is now the
+ basis of every property class.
+* Update: Caching results for principal lookups. This should cut down queries
+ and performance for a number of heavy requests.
+* Update: ObjectTree caches lookups much more aggresively, which will help
+ especially speeding up a bunch of REPORT queries.
+* Added: Support for the schedule-calendar-transp property.
+* Fixed: Marking both the text/calendar and text/x-vcard as UTF-8 encoded.
+* Fixed: Workaround for the SOGO connector, as it doesn't understand receiving
+ "text/x-vcard; charset=utf-8" for a contenttype.
+* Added: Sabre_DAV_Client now throws more specific exceptions in cases where we
+ already has an exception class.
+* Added: Sabre_DAV_PartialUpdate. This plugin allows you to use the PATCH method
+ to update parts of a file.
+* Added: Tons of timezone name mappings for Microsoft Exchange.
+* Added: Support for an 'exception' event in the server class.
+* Fixed: Uploaded VCards without a UID are now rejected. (thanks Dominik!)
+* Fixed: Rejecting calendar objects if they are not in the
+ supported-calendar-component list. (thanks Armin!)
+* Fixed: Issue 219: serialize() now reorders correctly.
+* Fixed: Sabre_DAV_XMLUtil no longer returns empty $dom->childNodes if there is
+ whitespace in $dom.
+* Fixed: Returning 409 Conflict instead of 500 when an attempt is made to create
+ a file as a child of something that's not a collection.
+* Fixed: Issue 237: xml-encoding values in SabreDAV error responses.
+* Fixed: Returning 403, instead of 501 when an unknown REPORT is requested.
+* Fixed: Postfixing slash on {DAV:}owner properties.
+* Fixed: Several embarrassing spelling mistakes in docblocks.
+
+
+1.6.10 (2013-06-17)
+-------------------
+
+* Fixed: Text was incorrectly escaped in the Href and HrefList properties,
+ disallowing urls with ampersands (&) in them.
+* Fixed: Issue 341: Escaping xml in 423 Locked error responses.
+
+
+1.6.9 (2013-04-11)
+------------------
+
+* Fixed: Assets in the browser plugins were not being served on windows
+ machines.
+
+
+1.6.8 (2013-04-08)
+------------------
+
+* Fixed: vcardurl in database schema can now hold 255 characters instead of 80
+ (which is often way to small).
+* Fixed: The browser plugin potentially allowed people to open any arbitrary
+ file on windows servers. (CVE-2013-1939).
+
+
+1.6.7 (2013-03-01)
+------------------
+
+* Change: No longer advertising support for 4.0 vcards. iOS and OS X address
+ book don't handle this well, and just advertising 3.0 support seems like the
+ most logical course of action.
+* Added: ->setVerifyPeers to Sabre_DAV_Client (greatly resisting against it,
+ don't use this..).
+
+
+1.6.6 (2013-01-19)
+------------------
+
+* Fixed: Backported a fix for broken XML serialization in error responses.
+ (Thanks @DeepDiver1975!)
+
+
+1.6.5 (2012-10-04)
+------------------
+
+* Fixed: Workaround for line-ending bug OS X 10.8 addressbook has.
+* Added: Ability to allow users to set SSL certificates for the Client class.
+ (Thanks schiesbn!).
+* Fixed: Directory indexes with lots of nodes should be a lot faster.
+* Fixed: Issue 235: E_NOTICE thrown when doing a propfind request with
+ Sabre_DAV_Client, and no valid properties are returned.
+* Fixed: Issue with filtering on alarms in tasks.
+
+
+1.6.4 (2012-08-02)
+------------------
+
+* Fixed: Issue 220: Calendar-query filters may fail when filtering on alarms, if
+ an overridden event has it's alarm removed.
+* Fixed: Compatibility for OS/X 10.8 iCal in the IMipHandler.
+* Fixed: Issue 222: beforeWriteContent shouldn't be called for lock requests.
+* Fixed: Problem with POST requests to the outbox if mailto: was not lower
+ cased.
+* Fixed: Yearly recurrence rule expansion on leap-days no behaves correctly.
+* Fixed: Correctly checking if recurring, all-day events with no dtstart fall in
+ a timerange if the start of the time-range exceeds the start of the instance
+ of an event, but not the end.
+* Fixed: All-day recurring events wouldn't match if an occurence ended exactly
+ on the start of a time-range.
+* Fixed: HTTP basic auth did not correctly deal with passwords containing colons
+ on some servers.
+* Fixed: Issue 228: DTEND is now non-inclusive for all-day events in the
+ calendar-query REPORT and free-busy calculations.
+
+
+1.6.3 (2012-06-12)
+------------------
+
+* Added: It's now possible to specify in Sabre_DAV_Client which type of
+ authentication is to be used.
+* Fixed: Issue 206: Sabre_DAV_Client PUT requests are fixed.
+* Fixed: Issue 205: Parsing an iCalendar 0-second date interval.
+* Fixed: Issue 112: Stronger validation of iCalendar objects. Now making sure
+ every iCalendar object only contains 1 component, and disallowing vcards,
+ forcing every component to have a UID.
+* Fixed: Basic validation for vcards in the CardDAV plugin.
+* Fixed: Issue 213: Workaround for an Evolution bug, that prevented it from
+ updating events.
+* Fixed: Issue 211: A time-limit query on a non-relative alarm trigger in a
+ recurring event could result in an endless loop.
+* Fixed: All uri fields are now a maximum of 200 characters. The Bynari outlook
+ plugin used much longer strings so this should improve compatibility.
+* Fixed: Added a workaround for a bug in KDE 4.8.2 contact syncing. See
+ https://bugs.kde.org/show_bug.cgi?id=300047
+* Fixed: Issue 217: Sabre_DAV_Tree_FileSystem was pretty broken.
+
+
+1.6.2 (2012-04-16)
+------------------
+
+* Fixed: Sabre_VObject_Node::$parent should have been public.
+* Fixed: Recurrence rules of events are now taken into consideration when doing
+ time-range queries on alarms.
+* Fixed: Added a workaround for the fact that php's DateInterval cannot parse
+ weeks and days at the same time.
+* Added: Sabre_DAV_Server::$exposeVersion, allowing you to hide SabreDAV's
+ version number from various outputs.
+* Fixed: DTSTART values would be incorrect when expanding events.
+* Fixed: DTSTART and DTEND would be incorrect for expansion of WEEKLY BYDAY
+ recurrences.
+* Fixed: Issue 203: A problem with overridden events hitting the exact date and
+ time of a subsequent event in the recurrence set.
+* Fixed: There was a problem with recurrence rules, for example the 5th tuesday
+ of the month, if this day did not exist.
+* Added: New HTTP status codes from draft-nottingham-http-new-status-04.
+
+
+1.6.1 (2012-03-05)
+------------------
+
+* Added: createFile and put() can now return an ETag.
+* Added: Sending back an ETag on for operations on CardDAV backends. This should
+ help with OS X 10.6 Addressbook compatibility.
+* Fixed: Fixed a bug where an infinite loop could occur in the recurrence
+ iterator if the recurrence was YEARLY, with a BYMONTH rule, and either BYDAY
+ or BYMONTHDAY match the first day of the month.
+* Fixed: Events that are excluded using EXDATE are still counted in the COUNT=
+ parameter in the RRULE property.
+* Added: Support for time-range filters on VALARM components.
+* Fixed: Correctly filtering all-day events.
+* Fixed: Sending back correct mimetypes from the browser plugin (thanks
+ Jürgen).
+* Fixed: Issue 195: Sabre_CardDAV pear package had an incorrect dependency.
+* Fixed: Calendardata would be destroyed when performing a MOVE request.
+
+
+1.6.0 (2012-02-22)
+------------------
+
+* BC Break: Now requires PHP 5.3
+* BC Break: Any node that implemented Sabre_DAVACL_IACL must now also implement
+ the getSupportedPrivilegeSet method. See website for details.
+* BC Break: Moved functions from Sabre_CalDAV_XMLUtil to
+ Sabre_VObject_DateTimeParser.
+* BC Break: The Sabre_DAVACL_IPrincipalCollection now has two new methods:
+ 'searchPrincipals' and 'updatePrincipal'.
+* BC Break: Sabre_DAV_ILockable is removed and all related per-node locking
+ functionality.
+* BC Break: Sabre_DAV_Exception_FileNotFound is now deprecated in favor of
+ Sabre_DAV_Exception_NotFound. The former will be removed in a later version.
+* BC Break: Removed Sabre_CalDAV_ICalendarUtil, use Sabre_VObject instead.
+* BC Break: Sabre_CalDAV_Server is now deprecated, check out the documentation
+ on how to setup a caldav server with just Sabre_DAV_Server.
+* BC Break: Default Principals PDO backend now needs a new field in the
+ 'principals' table. See the website for details.
+* Added: Ability to create new calendars and addressbooks from within the
+ browser plugin.
+* Added: Browser plugin: icons for various nodes.
+* Added: Support for FREEBUSY reports!
+* Added: Support for creating principals with admin-level privileges.
+* Added: Possibility to let server send out invitation emails on behalf of
+ CalDAV client, using Sabre_CalDAV_Schedule_IMip.
+* Changed: beforeCreateFile event now passes data argument by reference.
+* Changed: The 'propertyMap' property from Sabre_VObject_Reader, must now be
+ specified in Sabre_VObject_Property::$classMap.
+* Added: Ability for plugins to tell the ACL plugin which principal plugins are
+ searchable.
+* Added: [DAVACL] Per-node overriding of supported privileges. This allows for
+ custom privileges where needed.
+* Added: [DAVACL] Public 'principalSearch' method on the DAVACL plugin, which
+ allows for easy searching for principals, based on their properties.
+* Added: Sabre_VObject_Component::getComponents() to return a list of only
+ components and not properties.
+* Added: An includes.php file in every sub-package (CalDAV, CardDAV, DAV,
+ DAVACL, HTTP, VObject) as an alternative to the autoloader. This often works
+ much faster.
+* Added: Support for the 'Me card', which allows Addressbook.app users specify
+ which vcard is their own.
+* Added: Support for updating principal properties in the DAVACL principal
+ backends.
+* Changed: Major refactoring in the calendar-query REPORT code. Should make
+ things more flexible and correct.
+* Changed: The calendar-proxy-[read|write] principals will now only appear in
+ the tree, if they actually exist in the Principal backend. This should reduce
+ some problems people have been having with this.
+* Changed: Sabre_VObject_Element_* classes are now renamed to
+ Sabre_VObject_Property. Old classes are retained for backwards compatibility,
+ but this will be removed in the future.
+* Added: Sabre_VObject_FreeBusyGenerator to generate free-busy reports based on
+ lists of events.
+* Added: Sabre_VObject_RecurrenceIterator to find all the dates and times for
+ recurring events.
+* Fixed: Issue 97: Correctly handling RRULE for the calendar-query REPORT.
+* Fixed: Issue 154: Encoding of VObject parameters with no value was incorrect.
+* Added: Support for {DAV:}acl-restrictions property from RFC3744.
+* Added: The contentlength for calendar objects can now be supplied by a CalDAV
+ backend, allowing for more optimizations.
+* Fixed: Much faster implementation of Sabre_DAV_URLUtil::encodePath.
+* Fixed: {DAV:}getcontentlength may now be not specified.
+* Fixed: Issue 66: Using rawurldecode instead of urldecode to decode paths from
+ clients. This means that + will now be treated as a literal rather than a
+ space, and this should improve compatibility with the Windows built-in client.
+* Added: Sabre_DAV_Exception_PaymentRequired exception, to emit HTTP 402 status
+ codes.
+* Added: Some mysql unique constraints to example files.
+* Fixed: Correctly formatting HTTP dates.
+* Fixed: Issue 94: Sending back Last-Modified header for 304 responses.
+* Added: Sabre_VObject_Component_VEvent, Sabre_VObject_Component_VJournal,
+ Sabre_VObject_Component_VTodo and Sabre_VObject_Component_VCalendar.
+* Changed: Properties are now also automatically mapped to their appropriate
+ classes, if they are created using the add() or __set() methods.
+* Changed: Cloning VObject objects now clones the entire tree, rather than just
+ the default shallow copy.
+* Added: Support for recurrence expansion in the CALDAV:calendar-multiget and
+ CALDAV:calendar-query REPORTS.
+* Changed: CalDAV PDO backend now sorts calendars based on the internal
+ 'calendarorder' field.
+* Added: Issue 181: Carddav backends may no optionally not supply the carddata
+ in getCards, if etag and size are specified. This may speed up certain
+ requests.
+* Added: More arguments to beforeWriteContent and beforeCreateFile (see
+ WritingPlugins wiki document).
+* Added: Hook for iCalendar validation. This allows us to validate iCalendar
+ objects when they're uploaded. At the moment we're just validating syntax.
+* Added: VObject now support Windows Timezone names correctly (thanks mrpace2).
+* Added: If a timezonename could not be detected, we fall back on the default
+ PHP timezone.
+* Added: Now a Composer package (thanks willdurand).
+* Fixed: Support for \N as a newline character in the VObject reader.
+* Added: afterWriteContent, afterCreateFile and afterUnbind events.
+* Added: Postgresql example files. Not part of the unittests though, so use at
+ your own risk.
+* Fixed: Issue 182: Removed backticks from sql queries, so it will work with
+ Postgres.
+
+
+1.5.9 (2012-04-16)
+------------------
+
+* Fixed: Issue with parsing timezone identifiers that were surrounded by quotes.
+ (Fixes emClient compatibility).
+
+
+1.5.8 (2012-02-22)
+------------------
+
+* Fixed: Issue 95: Another timezone parsing issue, this time in calendar-query.
+
+
+1.5.7 (2012-02-19)
+------------------
+
+* Fixed: VObject properties are now always encoded before components.
+* Fixed: Sabre_DAVACL had issues with multiple levels of privilege aggregration.
+* Changed: Added 'GuessContentType' plugin to fileserver.php example.
+* Fixed: The Browser plugin will now trigger the correct events when creating
+ files.
+* Fixed: The ICSExportPlugin now considers ACL's.
+* Added: Made it optional to supply carddata from an Addressbook backend when
+ requesting getCards. This can make some operations much faster, and could
+ result in much lower memory use.
+* Fixed: Issue 187: Sabre_DAV_UUIDUtil was missing from includes file.
+* Fixed: Issue 191: beforeUnlock was triggered twice.
+
+
+1.5.6 (2012-01-07)
+------------------
+
+* Fixed: Issue 174: VObject could break UTF-8 characters.
+* Fixed: pear package installation issues.
+
+
+1.5.5 (2011-12-16)
+------------------
+
+* Fixed: CalDAV time-range filter workaround for recurring events.
+* Fixed: Bug in Sabre_DAV_Locks_Backend_File that didn't allow multiple files to
+ be locked at the same time.
+
+
+1.5.4 (2011-10-28)
+------------------
+
+* Fixed: GuessContentType plugin now supports mixed case file extensions.
+* Fixed: DATE-TIME encoding was wrong in VObject. (we used 'DATETIME').
+* Changed: Sending back HTTP 204 after a PUT request on an existing resource
+ instead of HTTP 200. This should fix Evolution CardDAV client compatibility.
+* Fixed: Issue 95: Parsing X-LIC-LOCATION if it's available.
+* Added: All VObject elements now have a reference to their parent node.
+
+
+1.5.3 (2011-09-28)
+------------------
+
+* Fixed: Sabre_DAV_Collection was missing from the includes file.
+* Fixed: Issue 152. iOS 1.4.2 apparantly requires HTTP/1.1 200 OK to be in
+ uppercase.
+* Fixed: Issue 153: Support for files with mixed newline styles in
+ Sabre_VObject.
+* Fixed: Issue 159: Automatically converting any vcard and icalendardata to
+ UTF-8.
+* Added: Sabre_DAV_SimpleFile class for easy static file creation.
+* Added: Issue 158: Support for the CARDDAV:supported-address-data property.
+
+
+1.5.2 (2011-09-21)
+------------------
+
+* Fixed: carddata and calendardata MySQL fields are now of type 'mediumblob'.
+ 'TEXT' was too small sometimes to hold all the data.
+* Fixed: {DAV:}supported-report-set is now correctly reporting the reports for
+ IAddressBook.
+* Added: Sabre_VObject_Property::add() to add duplicate parameters to
+ properties.
+* Added: Issue 151: Sabre_CalDAV_ICalendar and Sabre_CalDAV_ICalendarObject
+ interfaces.
+* Fixed: Issue 140: Not returning 201 Created if an event cancelled the creation
+ of a file.
+* Fixed: Issue 150: Faster URLUtil::encodePath() implementation.
+* Fixed: Issue 144: Browser plugin could interfere with
+ TemporaryFileFilterPlugin if it was loaded first.
+* Added: It's not possible to specify more 'alternate uris' in principal
+ backends.
+
+
+1.5.1 (2011-08-24)
+------------------
+
+* Fixed: Issue 137. Hiding action interface in HTML browser for non-collections.
+* Fixed: addressbook-query is now correctly returned from the
+ {DAV:}supported-report-set property.
+* Fixed: Issue 142: Bugs in groupwareserver.php example.
+* Fixed: Issue 139: Rejecting PUT requests with Content-Range.
+
+
+1.5.0 (2011-08-12)
+------------------
+
+* Added: CardDAV support.
+* Added: An experimental WebDAV client.
+* Added: MIME-Directory grouping support in the VObject library. This is very
+ useful for people attempting to parse vcards.
+* BC Break: Adding parameters with the VObject libraries now overwrites the
+ previous parameter, rather than just add it. This makes more sense for 99% of
+ the cases.
+* BC Break: lib/Sabre.autoload.php is now removed in favor of
+ lib/Sabre/autoload.php.
+* Deprecated: Sabre_DAV_Directory is now deprecated and will be removed in a
+ future version. Use Sabre_DAV_Collection instead.
+* Deprecated: Sabre_DAV_SimpleDirectory is now deprecated and will be removed in
+ a future version. Use Sabre_DAV_SimpleCollection instead.
+* Fixed: Problem with overriding tablenames for the CalDAV backend.
+* Added: Clark-notation parser to XML utility.
+* Added: unset() support to VObject components.
+* Fixed: Refactored CalDAV property fetching to be faster and simpler.
+* Added: Central string-matcher for CalDAV and CardDAV plugins.
+* Added: i;unicode-casemap support
+* Fixed: VObject bug: wouldn't parse parameters if they weren't specified in
+ uppercase.
+* Fixed: VObject bug: Parameters now behave more like Properties.
+* Fixed: VObject bug: Parameters with no value are now correctly parsed.
+* Changed: If calendars don't specify which components they allow, 'all'
+ components are assumed (e.g.: VEVENT, VTODO, VJOURNAL).
+* Changed: Browser plugin now uses POST variable 'sabreAction' instead of
+ 'action' to reduce the chance of collisions.
+
+
+1.4.4 (2011-07-07)
+------------------
+
+* Fixed: Issue 131: Custom CalDAV backends could break in certain cases.
+* Added: The option to override the default tablename all PDO backends use.
+ (Issue 60).
+* Fixed: Issue 124: 'File' authentication backend now takes realm into
+ consideration.
+* Fixed: Sabre_DAV_Property_HrefList now properly deserializes. This allows
+ users to update the {DAV:}group-member-set property.
+* Added: Helper functions for DateTime-values in Sabre_VObject package.
+* Added: VObject library can now automatically map iCalendar properties to
+ custom classes.
+
+
+1.4.3 (2011-04-25)
+------------------
+
+* Fixed: Issue 123: Added workaround for Windows 7 UNLOCK bug.
+* Fixed: datatype of lastmodified field in mysql.calendars.sql. Please change
+ the DATETIME field to an INT to ensure this field will work correctly.
+* Change: Sabre_DAV_Property_Principal is now renamed to
+ Sabre_DAVACL_Property_Principal.
+* Added: API level support for ACL HTTP method.
+* Fixed: Bug in serializing {DAV:}acl property.
+* Added: deserializer for {DAV:}resourcetype property.
+* Added: deserializer for {DAV:}acl property.
+* Added: deserializer for {DAV:}principal property.
+
+
+1.4.2-beta (2011-04-01)
+-----------------------
+
+* Added: It's not possible to disable listing of nodes that are denied read
+ access by ACL.
+* Fixed: Changed a few properties in CalDAV classes from private to protected.
+* Fixed: Issue 119: Terrible things could happen when relying on guessBaseUri,
+ the server was running on the root of the domain and a user tried to access a
+ file ending in .php. This is a slight BC break.
+* Fixed: Issue 118: Lock tokens in If headers without a uri should be treated as
+ the request uri, not 'all relevant uri's.
+* Fixed: Issue 120: PDO backend was incorrectly fetching too much locks in cases
+ where there were similar named locked files in a directory.
+
+
+1.4.1-beta (2011-02-26)
+-----------------------
+
+* Fixed: Sabre_DAV_Locks_Backend_PDO returned too many locks.
+* Fixed: Sabre_HTTP_Request::getHeader didn't return Content-Type when running
+ on apache, so a few workarounds were added.
+* Change: Slightly changed CalDAV Backend API's, to allow for heavy
+ optimizations. This is non-bc breaking.
+
+
+1.4.0-beta (2011-02-12)
+-----------------------
+
+* Added: Partly RFC3744 ACL support.
+* Added: Calendar-delegation (caldav-proxy) support.
+* BC break: In order to fix Issue 99, a new argument had to be added to
+ Sabre_DAV_Locks_Backend_*::getLocks classes. Consult the classes for details.
+* Deprecated: Sabre_DAV_Locks_Backend_FS is now deprecated and will be removed
+ in a later version. Use PDO or the new File class instead.
+* Deprecated: The Sabre_CalDAV_ICalendarUtil class is now marked deprecated, and
+ will be removed in a future version. Please use Sabre_VObject instead.
+* Removed: All principal-related functionality has been removed from the
+ Sabre_DAV_Auth_Plugin, and moved to the Sabre_DAVACL_Plugin.
+* Added: VObject library, for easy vcard/icalendar parsing using a natural
+ interface.
+* Added: Ability to automatically generate full .ics feeds off calendars. To
+ use: Add the Sabre_CalDAV_ICSExportPlugin, and add ?export to your calendar
+ url.
+* Added: Plugins can now specify a pluginname, for easy access using
+ Sabre_DAV_Server::getPlugin().
+* Added: beforeGetProperties event.
+* Added: updateProperties event.
+* Added: Principal listings and calendar-access can now be done privately,
+ disallowing users from accessing or modifying other users' data.
+* Added: You can now pass arrays to the Sabre_DAV_Server constructor. If it's an
+ array with node-objects, a Root collection will automatically be created, and
+ the nodes are used as top-level children.
+* Added: The principal base uri is now customizable. It used to be hardcoded to
+ 'principals/[user]'.
+* Added: getSupportedReportSet method in ServerPlugin class. This allows you to
+ easily specify which reports you're implementing.
+* Added: A '..' link to the HTML browser.
+* Fixed: Issue 99: Locks on child elements were ignored when their parent nodes
+ were deleted.
+* Fixed: Issue 90: lockdiscovery property and LOCK response now include a
+ {DAV}lockroot element.
+* Fixed: Issue 96: support for 'default' collation in CalDAV text-match filters.
+* Fixed: Issue 102: Ensuring that copy and move with identical source and
+ destination uri's fails.
+* Fixed: Issue 105: Supporting MKCALENDAR with no body.
+* Fixed: Issue 109: Small fixes in Sabre_HTTP_Util.
+* Fixed: Issue 111: Properly catching the ownername in a lock (if it's a string)
+* Fixed: Sabre_DAV_ObjectTree::nodeExist always returned false for the root
+ node.
+* Added: Global way to easily supply new resourcetypes for certain node classes.
+* Fixed: Issue 59: Allowing the user to override the authentication realm in
+ Sabre_CalDAV_Server.
+* Update: Issue 97: Looser time-range checking if there's a recurrence rule in
+ an event. This fixes 'missing recurring events'.
+
+
+1.3.0 (2010-10-14)
+------------------
+
+* Added: childExists method to Sabre_DAV_ICollection. This is an api break, so
+ if you implement Sabre_DAV_ICollection directly, add the method.
+* Changed: Almost all HTTP method implementations now take a uri argument,
+ including events. This allows for internal rerouting of certain calls. If you
+ have custom plugins, make sure they use this argument. If they don't, they
+ will likely still work, but it might get in the way of future changes.
+* Changed: All getETag methods MUST now surround the etag with double-quotes.
+ This was a mistake made in all previous SabreDAV versions. If you don't do
+ this, any If-Match, If-None-Match and If: headers using Etags will work
+ incorrectly. (Issue 85).
+* Added: Sabre_DAV_Auth_Backend_AbstractBasic class, which can be used to easily
+ implement basic authentication.
+* Removed: Sabre_DAV_PermissionDenied class. Use Sabre_DAV_Forbidden instead.
+* Removed: Sabre_DAV_IDirectory interface, use Sabre_DAV_ICollection instead.
+* Added: Browser plugin now uses {DAV:}displayname if this property is
+ available.
+* Added: Cache layer in the ObjectTree.
+* Added: Tree classes now have a delete and getChildren method.
+* Fixed: If-Modified-Since and If-Unmodified-Since would be incorrect if the
+ date is an exact match.
+* Fixed: Support for multiple ETags in If-Match and If-None-Match headers.
+* Fixed: Improved baseUrl handling.
+* Fixed: Issue 67: Non-seekable stream support in ::put()/::get().
+* Fixed: Issue 65: Invalid dates are now ignored.
+* Updated: Refactoring in Sabre_CalDAV to make everything a bit more ledgable.
+* Fixed: Issue 88, Issue 89: Fixed compatibility for running SabreDAV on
+ Windows.
+* Fixed: Issue 86: Fixed Content-Range top-boundary from 'file size' to 'file
+ size'-1.
+
+
+1.2.5 (2010-08-18)
+------------------
+
+* Fixed: Issue 73: guessBaseUrl fails for some servers.
+* Fixed: Issue 67: SabreDAV works better with non-seekable streams.
+* Fixed: If-Modified-Since and If-Unmodified-Since would be incorrect if
+ the date is an exact match.
+
+
+1.2.4 (2010-07-13)
+------------------
+
+* Fixed: Issue 62: Guessing baseUrl fails when url contains a query-string.
+* Added: Apache configuration sample for CGI/FastCGI setups.
+* Fixed: Issue 64: Only returning calendar-data when it was actually requested.
+
+
+1.2.3 (2010-06-26)
+------------------
+
+* Fixed: Issue 57: Supporting quotes around etags in If-Match and If-None-Match
+
+
+1.2.2 (2010-06-21)
+------------------
+
+* Updated: SabreDAV now attempts to guess the BaseURI if it's not set.
+* Updated: Better compatibility with BitKinex
+* Fixed: Issue 56: Incorrect behaviour for If-None-Match headers and GET
+ requests.
+* Fixed: Issue with certain encoded paths in Browser Plugin.
+
+
+1.2.1 (2010-06-07)
+------------------
+
+* Fixed: Issue 50, patch by Mattijs Hoitink.
+* Fixed: Issue 51, Adding windows 7 lockfiles to TemporaryFileFilter.
+* Fixed: Issue 38, Allowing custom filters to be added to TemporaryFileFilter.
+* Fixed: Issue 53, ETags in the If: header were always failing. This behaviour
+ is now corrected.
+* Added: Apache Authentication backend, in case authentication through .htaccess
+ is desired.
+* Updated: Small improvements to example files.
+
+
+1.2.0 (2010-05-24)
+------------------
+
+* Fixed: Browser plugin now displays international characters.
+* Changed: More properties in CalDAV classes are now protected instead of
+ private.
+
+
+1.2.0beta3 (2010-05-14)
+-----------------------
+
+* Fixed: Custom properties were not properly sent back for allprops requests.
+* Fixed: Issue 49, incorrect parsing of PROPPATCH, affecting Office 2007.
+* Changed: Removed CalDAV items from includes.php, and added a few missing ones.
+
+
+1.2.0beta2 (2010-05-04)
+-----------------------
+
+* Fixed: Issue 46: Fatal error for some non-existent nodes.
+* Updated: some example sql to include email address.
+* Added: 208 and 508 statuscodes from RFC5842.
+* Added: Apache2 configuration examples
+
+
+1.2.0beta1 (2010-04-28)
+-----------------------
+
+* Fixed: redundant namespace declaration in resourcetypes.
+* Fixed: 2 locking bugs triggered by litmus when no Sabre_DAV_ILockable
+ interface is used.
+* Changed: using http://sabredav.org/ns for all custom xml properties.
+* Added: email address property to principals.
+* Updated: CalendarObject validation.
+
+
+1.2.0alpha4 (2010-04-24)
+------------------------
+
+* Added: Support for If-Range, If-Match, If-None-Match, If-Modified-Since,
+ If-Unmodified-Since.
+* Changed: Brand new build system. Functionality is split up between Sabre,
+ Sabre_HTTP, Sabre_DAV and Sabre_CalDAV packages. In addition to that a new
+ non-pear package will be created with all this functionality combined.
+* Changed: Autoloader moved to Sabre/autoload.php.
+* Changed: The Allow: header is now more accurate, with appropriate HTTP methods
+ per uri.
+* Changed: Now throwing back Sabre_DAV_Exception_MethodNotAllowed on a few
+ places where Sabre_DAV_Exception_NotImplemented was used.
+
+
+1.2.0alpha3 (2010-04-20)
+------------------------
+
+* Update: Complete rewrite of property updating. Now easier to use and atomic.
+* Fixed: Issue 16, automatically adding trailing / to baseUri.
+* Added: text/plain is used for .txt files in GuessContentType plugin.
+* Added: support for principal-property-search and principal-search-property-set
+ reports.
+* Added: Issue 31: Hiding exception information by default. Can be turned on
+ with the Sabre_DAV_Server::$debugExceptions property.
+
+
+1.2.0alpha2 (2010-04-08)
+------------------------
+
+* Added: Calendars are now private and can only be read by the owner.
+* Fixed: double namespace declaration in multistatus responses.
+* Added: MySQL database dumps. MySQL is now also supported next to SQLite.
+* Added: expand-properties REPORT from RFC 3253.
+* Added: Sabre_DAV_Property_IHref interface for properties exposing urls.
+* Added: Issue 25: Throwing error on broken Finder behaviour.
+* Changed: Authentication backend is now aware of current user.
+
+
+1.2.0alpha1 (2010-03-31)
+------------------------
+
+* Fixed: Issue 26: Workaround for broken GVFS behaviour with encoded special
+ characters.
+* Fixed: Issue 34: Incorrect Lock-Token response header for LOCK. Fixes Office
+ 2010 compatibility.
+* Added: Issue 35: SabreDAV version to header to OPTIONS response to ease
+ debugging.
+* Fixed: Issue 36: Incorrect variable name, throwing error in some requests.
+* Fixed: Issue 37: Incorrect smultron regex in temporary filefilter.
+* Fixed: Issue 33: Converting ISO-8859-1 characters to UTF-8.
+* Fixed: Issue 39 & Issue 40: Basename fails on non-utf-8 locales.
+* Added: More unittests.
+* Added: SabreDAV version to all error responses.
+* Added: URLUtil class for decoding urls.
+* Changed: Now using pear.sabredav.org pear channel.
+* Changed: Sabre_DAV_Server::getCopyAndMoveInfo is now a public method.
+
+
+1.1.2-alpha (2010-03-18)
+------------------------
+
+* Added: RFC5397 - current-user-principal support.
+* Fixed: Issue 27: encoding entities in property responses.
+* Added: naturalselection script now allows the user to specify a 'minimum
+ number of bytes' for deletion. This should reduce load due to less crawling
+* Added: Full support for the calendar-query report.
+* Added: More unittests.
+* Added: Support for complex property deserialization through the static
+ ::unserialize() method.
+* Added: Support for modifying calendar-component-set
+* Fixed: Issue 29: Added TIMEOUT_INFINITE constant
+
+
+1.1.1-alpha (2010-03-11)
+------------------------
+
+* Added: RFC5689 - Extended MKCOL support.
+* Fixed: Evolution support for CalDAV.
+* Fixed: PDO-locks backend was pretty much completely broken. This is 100%
+ unittested now.
+* Added: support for ctags.
+* Fixed: Comma's between HTTP methods in 'Allow' method.
+* Changed: default argument for Sabre_DAV_Locks_Backend_FS. This means a
+ datadirectory must always be specified from now on.
+* Changed: Moved Sabre_DAV_Server::parseProps to
+ Sabre_DAV_XMLUtil::parseProperties.
+* Changed: Sabre_DAV_IDirectory is now Sabre_DAV_ICollection.
+* Changed: Sabre_DAV_Exception_PermissionDenied is now
+ Sabre_DAV_Exception_Forbidden.
+* Changed: Sabre_CalDAV_ICalendarCollection is removed.
+* Added: Sabre_DAV_IExtendedCollection.
+* Added: Many more unittests.
+* Added: support for calendar-timezone property.
+
+
+1.1.0-alpha (2010-03-01)
+------------------------
+
+* Note: This version is forked from version 1.0.5, so release dates may be out
+ of order.
+* Added: CalDAV - RFC 4791
+* Removed: Sabre_PHP_Exception. PHP has a built-in ErrorException for this.
+* Added: PDO authentication backend.
+* Added: Example sql for auth, caldav, locks for sqlite.
+* Added: Sabre_DAV_Browser_GuessContentType plugin
+* Changed: Authentication plugin refactored, making it possible to implement
+ non-digest authentication.
+* Fixed: Better error display in browser plugin.
+* Added: Support for {DAV:}supported-report-set
+* Added: XML utility class with helper functions for the WebDAV protocol.
+* Added: Tons of unittests
+* Added: PrincipalCollection and Principal classes
+* Added: Sabre_DAV_Server::getProperties for easy property retrieval
+* Changed: {DAV:}resourceType defaults to 0
+* Changed: Any non-null resourceType now gets a / appended to the href value.
+ Before this was just for {DAV:}collection's, but this is now also the case for
+ for example {DAV:}principal.
+* Changed: The Href property class can now optionally create non-relative uri's.
+* Changed: Sabre_HTTP_Response now returns false if headers are already sent and
+ header-methods are called.
+* Fixed: Issue 19: HEAD requests on Collections
+* Fixed: Issue 21: Typo in Sabre_DAV_Property_Response
+* Fixed: Issue 18: Doesn't work with Evolution Contacts
+
+
+1.0.15 (2010-05-28)
+-------------------
+
+* Added: Issue 31: Hiding exception information by default. Can be turned on
+ with the Sabre_DAV_Server::$debugExceptions property.
+* Added: Moved autoload from lib/ to lib/Sabre/autoload.php. This is also the
+ case in the upcoming 1.2.0, so it will improve future compatibility.
+
+
+1.0.14 (2010-04-15)
+-------------------
+
+* Fixed: double namespace declaration in multistatus responses.
+
+
+1.0.13 (2010-03-30)
+-------------------
+
+* Fixed: Issue 40: Last references to basename/dirname
+
+
+1.0.12 (2010-03-30)
+-------------------
+
+* Fixed: Issue 37: Incorrect smultron regex in temporary filefilter.
+* Fixed: Issue 26: Workaround for broken GVFS behaviour with encoded special
+ characters.
+* Fixed: Issue 33: Converting ISO-8859-1 characters to UTF-8.
+* Fixed: Issue 39: Basename fails on non-utf-8 locales.
+* Added: More unittests.
+* Added: SabreDAV version to all error responses.
+* Added: URLUtil class for decoding urls.
+* Updated: Now using pear.sabredav.org pear channel.
+
+
+1.0.11 (2010-03-23)
+-------------------
+
+* Non-public release. This release is identical to 1.0.10, but it is used to
+ test releasing packages to pear.sabredav.org.
+
+
+1.0.10 (2010-03-22)
+-------------------
+
+* Fixed: Issue 34: Invalid Lock-Token header response.
+* Added: Issue 35: Adding SabreDAV version to HTTP OPTIONS responses.
+
+
+1.0.9 (2010-03-19)
+------------------
+
+* Fixed: Issue 27: Entities not being encoded in PROPFIND responses.
+* Fixed: Issue 29: Added missing TIMEOUT_INFINITE constant.
+
+
+1.0.8 (2010-03-03)
+------------------
+
+* Fixed: Issue 21: typos causing errors
+* Fixed: Issue 23: Comma's between methods in Allow header.
+* Added: Sabre_DAV_ICollection interface, to aid in future compatibility.
+* Added: Sabre_DAV_Exception_Forbidden exception. This will replace
+ Sabre_DAV_Exception_PermissionDenied in the future, and can already be used to
+ ensure future compatibility.
+
+
+1.0.7 (2010-02-24)
+------------------
+
+* Fixed: Issue 19 regression for MS Office
+
+
+1.0.6 (2010-02-23)
+------------------
+
+* Fixed: Issue 19: HEAD requests on Collections
+
+
+1.0.5 (2010-01-22)
+------------------
+
+* Fixed: Fatal error when a malformed url was used for unlocking, in conjuction
+ with Sabre.autoload.php due to a incorrect filename.
+* Fixed: Improved unittests and build system
+
+
+1.0.4 (2010-01-11)
+------------------
+
+* Fixed: needed 2 different releases. One for googlecode and one for pearfarm.
+ This is to retain the old method to install SabreDAV until pearfarm becomes
+ the standard installation method.
+
+
+1.0.3 (2010-01-11)
+------------------
+
+* Added: RFC4709 support (davmount)
+* Added: 6 unittests
+* Added: naturalselection. A tool to keep cache directories below a specified
+ theshold.
+* Changed: Now using pearfarm.org channel server.
+
+
+1.0.1 (2009-12-22)
+------------------
+
+* Fixed: Issue 15: typos in examples
+* Fixed: Minor pear installation issues
+
+
+1.0.0 (2009-11-02)
+------------------
+
+* Added: SimpleDirectory class. This class allows creating static directory
+ structures with ease.
+* Changed: Custom complex properties and exceptions now get an instance of
+ Sabre_DAV_Server as their first argument in serialize()
+* Changed: Href complex property now prepends server's baseUri
+* Changed: delete before an overwriting copy/move is now handles by server class
+ instead of tree classes
+* Changed: events must now explicitly return false to stop execution. Before,
+ execution would be stopped by anything loosely evaluating to false.
+* Changed: the getPropertiesForPath method now takes a different set of
+ arguments, and returns a different response. This allows plugin developers to
+ return statuses for properties other than 200 and 404. The hrefs are now also
+ always calculated relative to the baseUri, and not the uri of the request.
+* Changed: generatePropFindResponse is renamed to generateMultiStatus, and now
+ takes a list of properties similar to the response of getPropertiesForPath.
+ This was also needed to improve flexibility for plugin development.
+* Changed: Auth plugins are no longer included. They were not yet stable
+ quality, so they will probably be reintroduced in a later version.
+* Changed: PROPPATCH also used generateMultiStatus now.
+* Removed: unknownProperties event. This is replaced by the afterGetProperties
+ event, which should provide more flexibility.
+* Fixed: Only calling getSize() on IFile instances in httpHead()
+* Added: beforeBind event. This is invoked upon file or directory creation
+* Added: beforeWriteContent event, this is invoked by PUT and LOCK on an
+ existing resource.
+* Added: beforeUnbind event. This is invoked right before deletion of any
+ resource.
+* Added: afterGetProperties event. This event can be used to make modifications
+ to property responses.
+* Added: beforeLock and beforeUnlock events.
+* Added: afterBind event.
+* Fixed: Copy and Move could fail in the root directory. This is now fixed.
+* Added: Plugins can now be retrieved by their classname. This is useful for
+ inter-plugin communication.
+* Added: The Auth backend can now return usernames and user-id's.
+* Added: The Auth backend got a getUsers method
+* Added: Sabre_DAV_FSExt_Directory now returns quota info
+
+
+0.12.1-beta (2009-09-11)
+------------------------
+
+* Fixed: UNLOCK bug. Unlock didn't work at all
+
+
+0.12-beta (2009-09-10)
+----------------------
+
+* Updated: Browser plugin now shows multiple {DAV:}resourcetype values if
+ available.
+* Added: Experimental PDO backend for Locks Manager
+* Fixed: Sending Content-Length: 0 for every empty response. This improves NGinx
+ compatibility.
+* Fixed: Last modification time is reported in UTC timezone. This improves
+ Finder compatibility.
+
+
+0.11-beta (2009-08-11)
+----------------------
+
+* Updated: Now in Beta
+* Updated: Pear package no longer includes docs/ directory. These just contained
+ rfc's, which are publicly available. This reduces the package from ~800k to
+ ~60k
+* Added: generatePropfindResponse now takes a baseUri argument
+* Added: ResourceType property can now contain multiple resourcetypes.
+* Fixed: Issue 13.
+
+
+0.10-alpha (2009-08-03)
+-----------------------
+
+* Added: Plugin to automatically map GET requests to non-files to PROPFIND
+ (Sabre_DAV_Browser_MapGetToPropFind). This should allow easier debugging of
+ complicated WebDAV setups.
+* Added: Sabre_DAV_Property_Href class. For future use.
+* Added: Ability to choose to use auth-int, auth or both for HTTP Digest
+ authentication. (Issue 11)
+* Changed: Made more methods in Sabre_DAV_Server public.
+* Fixed: TemporaryFileFilter plugin now intercepts HTTP LOCK requests to
+ non-existent files. (Issue 12)
+* Added: Central list of defined xml namespace prefixes. This can reduce
+ Bandwidth and legibility for xml bodies with user-defined namespaces.
+* Added: now a PEAR-compatible package again, thanks to Michael Gauthier
+* Changed: moved default copy and move logic from ObjectTree to Tree class
+
+0.9a-alpha (2009-07-21)
+----------------------
+
+* Fixed: Broken release
+
+0.9-alpha (2009-07-21)
+----------------------
+
+* Changed: Major refactoring, removed most of the logic from the Tree objects.
+ The Server class now directly works with the INode, IFile and IDirectory
+ objects. If you created your own Tree objects, this will most likely break in
+ this release.
+* Changed: Moved all the Locking logic from the Tree and Server classes into a
+ separate plugin.
+* Changed: TemporaryFileFilter is now a plugin.
+* Added: Comes with an autoloader script. This can be used instead of the
+ includer script, and is preferred by some people.
+* Added: AWS Authentication class.
+* Added: simpleserversetup.py script. This will quickly get a fileserver up and
+ running.
+* Added: When subscribing to events, it is now possible to supply a priority.
+ This is for example needed to ensure that the Authentication Plugin is used
+ before any other Plugin.
+* Added: 22 new tests.
+* Added: Users-manager plugin for .htdigest files. Experimental and subject to
+ change.
+* Added: RFC 2324 HTTP 418 status code
+* Fixed: Exclusive locks could in some cases be picked up as shared locks
+* Fixed: Digest auth for non-apache servers had a bug (still not actually tested
+ this well).
+
+
+0.8-alpha (2009-05-30)
+----------------------
+
+* Changed: Renamed all exceptions! This is a compatibility break. Every
+ Exception now follows Sabre_DAV_Exception_FileNotFound convention instead of
+ Sabre_DAV_FileNotFoundException.
+* Added: Browser plugin now allows uploading and creating directories straight
+ from the browser.
+* Added: 12 more unittests
+* Fixed: Locking bug, which became prevalent on Windows Vista.
+* Fixed: Netdrive support
+* Fixed: TemporaryFileFilter filtered out too many files. Fixed some of the
+ regexes.
+* Fixed: Added README and ChangeLog to package
+
+
+0.7-alpha (2009-03-29)
+----------------------
+
+* Added: System to return complex properties from PROPFIND.
+* Added: support for {DAV:}supportedlock.
+* Added: support for {DAV:}lockdiscovery.
+* Added: 6 new tests.
+* Added: New plugin system.
+* Added: Simple HTML directory plugin, for browser access.
+* Added: Server class now sends back standard pre-condition error xml bodies.
+ This was new since RFC4918.
+* Added: Sabre_DAV_Tree_Aggregate, which can 'host' multiple Tree objects into
+ one.
+* Added: simple basis for HTTP REPORT method. This method is not used yet, but
+ can be used by plugins to add reports.
+* Changed: ->getSize is only called for files, no longer for collections. r303
+* Changed: Sabre_DAV_FilterTree is now Sabre_DAV_Tree_Filter
+* Changed: Sabre_DAV_TemporaryFileFilter is now called
+ Sabre_DAV_Tree_TemporaryFileFilter.
+* Changed: removed functions (get(/set)HTTPRequest(/Response)) from Server
+ class, and using a public property instead.
+* Fixed: bug related to parsing proppatch and propfind requests. Didn't show up
+ in most clients, but it needed fixing regardless. (r255)
+* Fixed: auth-int is now properly supported within HTTP Digest.
+* Fixed: Using application/xml for a mimetype vs. text/xml as per RFC4918 sec
+ 8.2.
+* Fixed: TemporaryFileFilter now lets through GET's if they actually exist on
+ the backend. (r274)
+* Fixed: Some methods didn't get passed through in the FilterTree (r283).
+* Fixed: LockManager is now slightly more complex, Tree classes slightly less.
+ (r287)
+
+
+0.6-alpha (2009-02-16)
+----------------------
+
+* Added: Now uses streams for files, instead of strings. This means it won't
+ require to hold entire files in memory, which can be an issue if you're
+ dealing with big files. Note that this breaks compatibility for put() and
+ createFile methods.
+* Added: HTTP Digest Authentication helper class.
+* Added: Support for HTTP Range header
+* Added: Support for ETags within If: headers
+* Added: The API can now return ETags and override the default Content-Type
+* Added: starting with basic framework for unittesting, using PHPUnit.
+* Added: 49 unittests.
+* Added: Abstraction for the HTTP request.
+* Updated: Using Clark Notation for tags in properties. This means tags are
+ serialized as {namespace}tagName instead of namespace#tagName
+* Fixed: HTTP_BasicAuth class now works as expected.
+* Fixed: DAV_Server uses / for a default baseUrl.
+* Fixed: Last modification date is no longer ignored in PROPFIND.
+* Fixed: PROPFIND now sends back information about the requestUri even when
+ "Depth: 1" is specified.
+
+
+0.5-alpha (2009-01-14)
+----------------------
+
+* Added: Added a very simple example for implementing a mapping to PHP file
+ streams. This should allow easy implementation of for example a WebDAV to FTP
+ proxy.
+* Added: HTTP Basic Authentication helper class.
+* Added: Sabre_HTTP_Response class. This centralizes HTTP operations and will be
+ a start towards the creating of a testing framework.
+* Updated: Backwards compatibility break: all require_once() statements are
+ removed from all the files. It is now recommended to use autoloading of
+ classes, or just including lib/Sabre.includes.php. This fix was made to allow
+ easier integration into applications not using this standard inclusion model.
+* Updated: Better in-file documentation.
+* Updated: Sabre_DAV_Tree can now work with Sabre_DAV_LockManager.
+* Updated: Fixes a shared-lock bug.
+* Updated: Removed ?> from the bottom of each php file.
+* Updated: Split up some operations from Sabre_DAV_Server to
+ Sabre_HTTP_Response.
+* Fixed: examples are now actually included in the pear package.
+
+
+0.4-alpha (2008-11-05)
+----------------------
+
+* Passes all litmus tests!
+* Added: more examples
+* Added: Custom property support
+* Added: Shared lock support
+* Added: Depth support to locks
+* Added: Locking on unmapped urls (non-existent nodes)
+* Fixed: Advertising as WebDAV class 3 support
+
+
+0.3-alpha (2008-06-29)
+----------------------
+
+* Fully working in MS Windows clients.
+* Added: temporary file filter: support for smultron files.
+* Added: Phing build scripts
+* Added: PEAR package
+* Fixed: MOVE bug identified using finder.
+* Fixed: Using gzuncompress instead of gzdecode in the temporary file filter.
+ This seems more common.
+
+
+0.2-alpha (2008-05-27)
+----------------------
+
+* Somewhat working in Windows clients
+* Added: Working PROPPATCH method (doesn't support custom properties yet)
+* Added: Temporary filename handling system
+* Added: Sabre_DAV_IQuota to return quota information
+* Added: PROPFIND now reads the request body and only supplies the requested
+ properties
+
+
+0.1-alpha (2008-04-04)
+----------------------
+
+* First release!
+* Passes litmus: basic, http and copymove test.
+* Fully working in Finder and DavFS2.
+
+Project started: 2007-12-13
+
+[vobj]: http://sabre.io/vobject/
+[evnt]: http://sabre.io/event/
+[http]: http://sabre.io/http/
+[uri]: http://sabre.io/uri/
+[xml]: http://sabre.io/xml/
+[mi20]: http://sabre.io/dav/upgrade/1.8-to-2.0/
+[rfc6638]: http://tools.ietf.org/html/rfc6638 "CalDAV Scheduling"
+[rfc7240]: http://tools.ietf.org/html/rfc7240
+[calendar-availability]: https://tools.ietf.org/html/draft-daboo-calendar-availability-05
diff --git a/plugins/panakour/backup/vendor/sabre/dav/CONTRIBUTING.md b/plugins/panakour/backup/vendor/sabre/dav/CONTRIBUTING.md
new file mode 100644
index 000000000..b937db64f
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/CONTRIBUTING.md
@@ -0,0 +1,109 @@
+Contributing to sabre projects
+==============================
+
+Want to contribute to sabre/dav? Here are some guidelines to ensure your patch
+gets accepted.
+
+
+Building a new feature? Contact us first
+----------------------------------------
+
+We may not want to accept every feature that comes our way. Sometimes
+features are out of scope for our projects.
+
+We don't want to waste your time, so by having a quick chat with us first,
+you may find out quickly if the feature makes sense to us, and we can give
+some tips on how to best build the feature.
+
+If we don't accept the feature, it could be for a number of reasons. For
+instance, we've rejected features in the past because we felt uncomfortable
+assuming responsibility for maintaining the feature.
+
+In those cases, it's often possible to keep the feature separate from the
+sabre projects. sabre/dav for instance has a plugin system, and there's no
+reason the feature can't live in a project you own.
+
+In that case, definitely let us know about your plugin as well, so we can
+feature it on [sabre.io][4].
+
+We are often on [IRC][5], in the #sabredav channel on freenode. If there's
+no one there, post a message on the [mailing list][6].
+
+
+Coding standards
+----------------
+
+sabre projects follow:
+
+1. [PSR-1][1]
+2. [PSR-4][2]
+
+sabre projects don't follow [PSR-2][3].
+
+In addition to that, here's a list of basic rules:
+
+1. PHP 5.4 array syntax must be used every where. This means you use `[` and
+ `]` instead of `array(` and `)`.
+2. Use PHP namespaces everywhere.
+3. Use 4 spaces for indentation.
+4. Try to keep your lines under 80 characters. This is not a hard rule, as
+ there are many places in the source where it felt more sensibile to not
+ do so. In particular, function declarations are never split over multiple
+ lines.
+5. Opening braces (`{`) are _always_ on the same line as the `class`, `if`,
+ `function`, etc. they belong to.
+6. `public` must be omitted from method declarations. It must also be omitted
+ for static properties.
+7. All files should use unix-line endings (`\n`).
+8. Files must omit the closing php tag (`?>`).
+9. `true`, `false` and `null` are always lower-case.
+10. Constants are always upper-case.
+11. Any of the rules stated before may be broken where this is the pragmatic
+ thing to do.
+
+
+Unit test requirements
+----------------------
+
+Any new feature or change requires unittests. We use [PHPUnit][7] for all our
+tests.
+
+Adding unittests will greatly increase the likelyhood of us quickly accepting
+your pull request. If unittests are not included though for whatever reason,
+we'd still _love_ your pull request.
+
+We may have to write the tests ourselves, which can increase the time it takes
+to accept the patch, but we'd still really like your contribution!
+
+To run the testsuite jump into the directory `cd tests` and trigger `phpunit`.
+Make sure you did a `composer install` beforehand.
+
+Release process
+---------------
+
+Generally, these are the steps taken to do releases.
+
+1. Update the changelog. Every repo will have a `CHANGELOG.md` file. This file
+ should have a new version, and contain all the changes since the last
+ release. I generally run a `git diff` to figure out if I missed any changes.
+ This file should also have the current date.
+2. If there were BC breaks, this usually now means a major version bump.
+3. Ensure that `lib/Version.php` or `lib/DAV/Version.php` also matches this
+ version number.
+4. Tag the release (Example `git tag 3.0.1` and push the tag (`git push --tags`).
+5. (only for the sabre/dav project), create a zip distribution. Run
+ `php bin/build.php`.
+6. For the relevant project, go to github and click the 'releases' tab. On this
+ tab I create the release with the relevant version. I also set the
+ description of the release to the same information of the changelog. In the
+ case of the `sabre/dav` project I also upload the zip distribution here.
+7. Write a blog post on sabre.io. This also automatically updates twitter.
+
+
+[1]: http://www.php-fig.org/psr/psr-1/
+[2]: http://www.php-fig.org/psr/psr-4/
+[3]: http://www.php-fig.org/psr/psr-2/
+[4]: http://sabre.io/
+[5]: irc://freenode.net/#sabredav
+[6]: http://groups.google.com/group/sabredav-discuss
+[7]: http://phpunit.de/
diff --git a/plugins/panakour/backup/vendor/sabre/dav/LICENSE b/plugins/panakour/backup/vendor/sabre/dav/LICENSE
new file mode 100644
index 000000000..fd3539e33
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/LICENSE
@@ -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.
diff --git a/plugins/panakour/backup/vendor/sabre/dav/README.md b/plugins/panakour/backup/vendor/sabre/dav/README.md
new file mode 100644
index 000000000..acdb1e03c
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/README.md
@@ -0,0 +1,38 @@
+ 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 | minimum PHP version |
+| ------------ | ------ | ------------------- |
+| master | [](https://travis-ci.org/sabre-io/dav) | PHP 7.1 |
+| 3.1 | [](https://travis-ci.org/sabre-io/dav) | PHP 5.5 |
+| 3.0 | [](https://travis-ci.org/sabre-io/dav) | PHP 5.4 |
+| 2.1 | [](https://travis-ci.org/sabre-io/dav) | PHP 5.4 |
+| 2.0 | [](https://travis-ci.org/sabre-io/dav) | PHP 5.4 |
+| 1.8 | [](https://travis-ci.org/sabre-io/dav) | PHP 5.3 |
+| 1.7 | [](https://travis-ci.org/sabre-io/dav) | PHP 5.3 |
+| 1.6 | [](https://travis-ci.org/sabre-io/dav) | 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.
diff --git a/plugins/panakour/backup/vendor/sabre/dav/bin/build.php b/plugins/panakour/backup/vendor/sabre/dav/bin/build.php
new file mode 100644
index 000000000..54174a777
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/bin/build.php
@@ -0,0 +1,169 @@
+#!/usr/bin/env php
+ [
+ '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";
+ die(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";
+ die(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";
+ die(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";
+ die(1);
+ }
+
+ echo " Removing pointless files\n";
+ unlink('build/SabreDAV/composer.json');
+ unlink('build/SabreDAV/composer.lock');
+
+ echo " Moving important files to the root of the project\n";
+
+ $fileNames = [
+ 'CHANGELOG.md',
+ 'LICENSE',
+ 'README.md',
+ 'examples',
+ ];
+ foreach ($fileNames as $fileName) {
+ echo " $fileName\n";
+ rename('build/SabreDAV/vendor/sabre/dav/'.$fileName, 'build/SabreDAV/'.$fileName);
+ }
+
+ //
+
+ echo "\n";
+ echo "Zipping the sabredav distribution\n\n";
+ system('cd build; zip -qr sabredav-'.$version.'.zip SabreDAV');
+
+ echo 'Done.';
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/bin/googlecode_upload.py b/plugins/panakour/backup/vendor/sabre/dav/bin/googlecode_upload.py
new file mode 100644
index 000000000..caafd5ded
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/bin/googlecode_upload.py
@@ -0,0 +1,248 @@
+#!/usr/bin/env python
+#
+# Copyright 2006, 2007 Google Inc. All Rights Reserved.
+# Author: danderson@google.com (David Anderson)
+#
+# Script for uploading files to a Google Code project.
+#
+# This is intended to be both a useful script for people who want to
+# streamline project uploads and a reference implementation for
+# uploading files to Google Code projects.
+#
+# To upload a file to Google Code, you need to provide a path to the
+# file on your local machine, a small summary of what the file is, a
+# project name, and a valid account that is a member or owner of that
+# project. You can optionally provide a list of labels that apply to
+# the file. The file will be uploaded under the same name that it has
+# in your local filesystem (that is, the "basename" or last path
+# component). Run the script with '--help' to get the exact syntax
+# and available options.
+#
+# Note that the upload script requests that you enter your
+# googlecode.com password. This is NOT your Gmail account password!
+# This is the password you use on googlecode.com for committing to
+# Subversion and uploading files. You can find your password by going
+# to http://code.google.com/hosting/settings when logged in with your
+# Gmail account. If you have already committed to your project's
+# Subversion repository, the script will automatically retrieve your
+# credentials from there (unless disabled, see the output of '--help'
+# for details).
+#
+# If you are looking at this script as a reference for implementing
+# your own Google Code file uploader, then you should take a look at
+# the upload() function, which is the meat of the uploader. You
+# basically need to build a multipart/form-data POST request with the
+# right fields and send it to https://PROJECT.googlecode.com/files .
+# Authenticate the request using HTTP Basic authentication, as is
+# shown below.
+#
+# Licensed under the terms of the Apache Software License 2.0:
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Questions, comments, feature requests and patches are most welcome.
+# Please direct all of these to the Google Code users group:
+# http://groups.google.com/group/google-code-hosting
+
+"""Google Code file uploader script.
+"""
+
+__author__ = 'danderson@google.com (David Anderson)'
+
+import httplib
+import os.path
+import optparse
+import getpass
+import base64
+import sys
+
+
+def upload(file, project_name, user_name, password, summary, labels=None):
+ """Upload a file to a Google Code project's file server.
+
+ Args:
+ file: The local path to the file.
+ project_name: The name of your project on Google Code.
+ user_name: Your Google account name.
+ password: The googlecode.com password for your account.
+ Note that this is NOT your global Google Account password!
+ summary: A small description for the file.
+ labels: an optional list of label strings with which to tag the file.
+
+ Returns: a tuple:
+ http_status: 201 if the upload succeeded, something else if an
+ error occurred.
+ http_reason: The human-readable string associated with http_status
+ file_url: If the upload succeeded, the URL of the file on Google
+ Code, None otherwise.
+ """
+ # The login is the user part of user@gmail.com. If the login provided
+ # is in the full user@domain form, strip it down.
+ if user_name.endswith('@gmail.com'):
+ user_name = user_name[:user_name.index('@gmail.com')]
+
+ form_fields = [('summary', summary)]
+ if labels is not None:
+ form_fields.extend([('label', l.strip()) for l in labels])
+
+ content_type, body = encode_upload_request(form_fields, file)
+
+ upload_host = '%s.googlecode.com' % project_name
+ upload_uri = '/files'
+ auth_token = base64.b64encode('%s:%s'% (user_name, password))
+ headers = {
+ 'Authorization': 'Basic %s' % auth_token,
+ 'User-Agent': 'Googlecode.com uploader v0.9.4',
+ 'Content-Type': content_type,
+ }
+
+ server = httplib.HTTPSConnection(upload_host)
+ server.request('POST', upload_uri, body, headers)
+ resp = server.getresponse()
+ server.close()
+
+ if resp.status == 201:
+ location = resp.getheader('Location', None)
+ else:
+ location = None
+ return resp.status, resp.reason, location
+
+
+def encode_upload_request(fields, file_path):
+ """Encode the given fields and file into a multipart form body.
+
+ fields is a sequence of (name, value) pairs. file is the path of
+ the file to upload. The file will be uploaded to Google Code with
+ the same file name.
+
+ Returns: (content_type, body) ready for httplib.HTTP instance
+ """
+ BOUNDARY = '----------Googlecode_boundary_reindeer_flotilla'
+ CRLF = '\r\n'
+
+ body = []
+
+ # Add the metadata about the upload first
+ for key, value in fields:
+ body.extend(
+ ['--' + BOUNDARY,
+ 'Content-Disposition: form-data; name="%s"' % key,
+ '',
+ value,
+ ])
+
+ # Now add the file itself
+ file_name = os.path.basename(file_path)
+ f = open(file_path, 'rb')
+ file_content = f.read()
+ f.close()
+
+ body.extend(
+ ['--' + BOUNDARY,
+ 'Content-Disposition: form-data; name="filename"; filename="%s"'
+ % file_name,
+ # The upload server determines the mime-type, no need to set it.
+ 'Content-Type: application/octet-stream',
+ '',
+ file_content,
+ ])
+
+ # Finalize the form body
+ body.extend(['--' + BOUNDARY + '--', ''])
+
+ return 'multipart/form-data; boundary=%s' % BOUNDARY, CRLF.join(body)
+
+
+def upload_find_auth(file_path, project_name, summary, labels=None,
+ user_name=None, password=None, tries=3):
+ """Find credentials and upload a file to a Google Code project's file server.
+
+ file_path, project_name, summary, and labels are passed as-is to upload.
+
+ Args:
+ file_path: The local path to the file.
+ project_name: The name of your project on Google Code.
+ summary: A small description for the file.
+ labels: an optional list of label strings with which to tag the file.
+ config_dir: Path to Subversion configuration directory, 'none', or None.
+ user_name: Your Google account name.
+ tries: How many attempts to make.
+ """
+
+ while tries > 0:
+ if user_name is None:
+ # Read username if not specified or loaded from svn config, or on
+ # subsequent tries.
+ sys.stdout.write('Please enter your googlecode.com username: ')
+ sys.stdout.flush()
+ user_name = sys.stdin.readline().rstrip()
+ if password is None:
+ # Read password if not loaded from svn config, or on subsequent tries.
+ print 'Please enter your googlecode.com password.'
+ print '** Note that this is NOT your Gmail account password! **'
+ print 'It is the password you use to access Subversion repositories,'
+ print 'and can be found here: http://code.google.com/hosting/settings'
+ password = getpass.getpass()
+
+ status, reason, url = upload(file_path, project_name, user_name, password,
+ summary, labels)
+ # Returns 403 Forbidden instead of 401 Unauthorized for bad
+ # credentials as of 2007-07-17.
+ if status in [httplib.FORBIDDEN, httplib.UNAUTHORIZED]:
+ # Rest for another try.
+ user_name = password = None
+ tries = tries - 1
+ else:
+ # We're done.
+ break
+
+ return status, reason, url
+
+
+def main():
+ parser = optparse.OptionParser(usage='googlecode-upload.py -s SUMMARY '
+ '-p PROJECT [options] FILE')
+ parser.add_option('-s', '--summary', dest='summary',
+ help='Short description of the file')
+ parser.add_option('-p', '--project', dest='project',
+ help='Google Code project name')
+ parser.add_option('-u', '--user', dest='user',
+ help='Your Google Code username')
+ parser.add_option('-w', '--password', dest='password',
+ help='Your Google Code password')
+ parser.add_option('-l', '--labels', dest='labels',
+ help='An optional list of comma-separated labels to attach '
+ 'to the file')
+
+ options, args = parser.parse_args()
+
+ if not options.summary:
+ parser.error('File summary is missing.')
+ elif not options.project:
+ parser.error('Project name is missing.')
+ elif len(args) < 1:
+ parser.error('File to upload not provided.')
+ elif len(args) > 1:
+ parser.error('Only one file may be specified.')
+
+ file_path = args[0]
+
+ if options.labels:
+ labels = options.labels.split(',')
+ else:
+ labels = None
+
+ status, reason, url = upload_find_auth(file_path, options.project,
+ options.summary, labels,
+ options.user, options.password)
+ if url:
+ print 'The file was uploaded successfully.'
+ print 'URL: %s' % url
+ return 0
+ else:
+ print 'An error occurred. Your file was not uploaded.'
+ print 'Google Code upload server said: %s (%s)' % (reason, status)
+ return 1
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/plugins/panakour/backup/vendor/sabre/dav/bin/migrateto20.php b/plugins/panakour/backup/vendor/sabre/dav/bin/migrateto20.php
new file mode 100644
index 000000000..c7a8d9e35
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/bin/migrateto20.php
@@ -0,0 +1,417 @@
+#!/usr/bin/env php
+setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
+
+$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
+
+switch ($driver) {
+ case 'mysql':
+ echo "Detected MySQL.\n";
+ break;
+ case 'sqlite':
+ echo "Detected SQLite.\n";
+ break;
+ default:
+ echo 'Error: unsupported driver: '.$driver."\n";
+ die(-1);
+}
+
+foreach (['calendar', 'addressbook'] as $itemType) {
+ $tableName = $itemType.'s';
+ $tableNameOld = $tableName.'_old';
+ $changesTable = $itemType.'changes';
+
+ echo "Upgrading '$tableName'\n";
+
+ // The only cross-db way to do this, is to just fetch a single record.
+ $row = $pdo->query("SELECT * FROM $tableName LIMIT 1")->fetch();
+
+ if (!$row) {
+ echo "No records were found in the '$tableName' table.\n";
+ echo "\n";
+ echo "We're going to rename the old table to $tableNameOld (just in case).\n";
+ echo "and re-create the new table.\n";
+
+ switch ($driver) {
+ case 'mysql':
+ $pdo->exec("RENAME TABLE $tableName TO $tableNameOld");
+ switch ($itemType) {
+ case 'calendar':
+ $pdo->exec("
+ CREATE TABLE calendars (
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ principaluri VARCHAR(100),
+ displayname VARCHAR(100),
+ uri VARCHAR(200),
+ synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1',
+ description TEXT,
+ calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ calendarcolor VARCHAR(10),
+ timezone TEXT,
+ components VARCHAR(20),
+ transparent TINYINT(1) NOT NULL DEFAULT '0',
+ UNIQUE(principaluri, uri)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+ ");
+ break;
+ case 'addressbook':
+ $pdo->exec("
+ CREATE TABLE addressbooks (
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ principaluri VARCHAR(255),
+ displayname VARCHAR(255),
+ uri VARCHAR(200),
+ description TEXT,
+ synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1',
+ UNIQUE(principaluri, uri)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+ ");
+ break;
+ }
+ break;
+
+ case 'sqlite':
+
+ $pdo->exec("ALTER TABLE $tableName RENAME TO $tableNameOld");
+
+ switch ($itemType) {
+ case 'calendar':
+ $pdo->exec('
+ CREATE TABLE calendars (
+ id integer primary key asc,
+ principaluri text,
+ displayname text,
+ uri text,
+ synctoken integer,
+ description text,
+ calendarorder integer,
+ calendarcolor text,
+ timezone text,
+ components text,
+ transparent bool
+ );
+ ');
+ break;
+ case 'addressbook':
+ $pdo->exec('
+ CREATE TABLE addressbooks (
+ id integer primary key asc,
+ principaluri text,
+ displayname text,
+ uri text,
+ description text,
+ synctoken integer
+ );
+ ');
+
+ break;
+ }
+ break;
+ }
+ echo "Creation of 2.0 $tableName table is complete\n";
+ } else {
+ // Checking if there's a synctoken field already.
+ if (array_key_exists('synctoken', $row)) {
+ echo "The 'synctoken' field already exists in the $tableName table.\n";
+ echo "It's likely you already upgraded, so we're simply leaving\n";
+ echo "the $tableName table alone\n";
+ } else {
+ echo "1.8 table schema detected\n";
+ switch ($driver) {
+ case 'mysql':
+ $pdo->exec("ALTER TABLE $tableName ADD synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1'");
+ $pdo->exec("ALTER TABLE $tableName DROP ctag");
+ $pdo->exec("UPDATE $tableName SET synctoken = '1'");
+ break;
+ case 'sqlite':
+ $pdo->exec("ALTER TABLE $tableName ADD synctoken integer");
+ $pdo->exec("UPDATE $tableName SET synctoken = '1'");
+ echo "Note: there's no easy way to remove fields in sqlite.\n";
+ echo "The ctag field is no longer used, but it's kept in place\n";
+ break;
+ }
+
+ echo "Upgraded '$tableName' to 2.0 schema.\n";
+ }
+ }
+
+ try {
+ $pdo->query("SELECT * FROM $changesTable LIMIT 1");
+
+ echo "'$changesTable' already exists. Assuming that this part of the\n";
+ echo "upgrade was already completed.\n";
+ } catch (Exception $e) {
+ echo "Creating '$changesTable' table.\n";
+
+ switch ($driver) {
+ case 'mysql':
+ $pdo->exec("
+ CREATE TABLE $changesTable (
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ uri VARCHAR(200) NOT NULL,
+ synctoken INT(11) UNSIGNED NOT NULL,
+ {$itemType}id INT(11) UNSIGNED NOT NULL,
+ operation TINYINT(1) NOT NULL,
+ INDEX {$itemType}id_synctoken ({$itemType}id, synctoken)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+
+ ");
+ break;
+ case 'sqlite':
+ $pdo->exec("
+
+ CREATE TABLE $changesTable (
+ id integer primary key asc,
+ uri text,
+ synctoken integer,
+ {$itemType}id integer,
+ operation bool
+ );
+
+ ");
+ $pdo->exec("CREATE INDEX {$itemType}id_synctoken ON $changesTable ({$itemType}id, synctoken);");
+ break;
+ }
+ }
+}
+
+try {
+ $pdo->query('SELECT * FROM calendarsubscriptions LIMIT 1');
+
+ echo "'calendarsubscriptions' already exists. Assuming that this part of the\n";
+ echo "upgrade was already completed.\n";
+} catch (Exception $e) {
+ echo "Creating calendarsubscriptions table.\n";
+
+ switch ($driver) {
+ case 'mysql':
+ $pdo->exec("
+CREATE TABLE calendarsubscriptions (
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ uri VARCHAR(200) NOT NULL,
+ principaluri VARCHAR(100) NOT NULL,
+ source TEXT,
+ displayname VARCHAR(100),
+ refreshrate VARCHAR(10),
+ calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ calendarcolor VARCHAR(10),
+ striptodos TINYINT(1) NULL,
+ stripalarms TINYINT(1) NULL,
+ stripattachments TINYINT(1) NULL,
+ lastmodified INT(11) UNSIGNED,
+ UNIQUE(principaluri, uri)
+);
+ ");
+ break;
+ case 'sqlite':
+ $pdo->exec('
+
+CREATE TABLE calendarsubscriptions (
+ id integer primary key asc,
+ uri text,
+ principaluri text,
+ source text,
+ displayname text,
+ refreshrate text,
+ calendarorder integer,
+ calendarcolor text,
+ striptodos bool,
+ stripalarms bool,
+ stripattachments bool,
+ lastmodified int
+);
+ ');
+
+ $pdo->exec('CREATE INDEX principaluri_uri ON calendarsubscriptions (principaluri, uri);');
+ break;
+ }
+}
+
+try {
+ $pdo->query('SELECT * FROM propertystorage LIMIT 1');
+
+ echo "'propertystorage' already exists. Assuming that this part of the\n";
+ echo "upgrade was already completed.\n";
+} catch (Exception $e) {
+ echo "Creating propertystorage table.\n";
+
+ switch ($driver) {
+ case 'mysql':
+ $pdo->exec('
+CREATE TABLE propertystorage (
+ id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ path VARBINARY(1024) NOT NULL,
+ name VARBINARY(100) NOT NULL,
+ value MEDIUMBLOB
+);
+ ');
+ $pdo->exec('
+CREATE UNIQUE INDEX path_property ON propertystorage (path(600), name(100));
+ ');
+ break;
+ case 'sqlite':
+ $pdo->exec('
+CREATE TABLE propertystorage (
+ id integer primary key asc,
+ path TEXT,
+ name TEXT,
+ value TEXT
+);
+ ');
+ $pdo->exec('
+CREATE UNIQUE INDEX path_property ON propertystorage (path, name);
+ ');
+
+ break;
+ }
+}
+
+echo "Upgrading cards table to 2.0 schema\n";
+
+try {
+ $create = false;
+ $row = $pdo->query('SELECT * FROM cards LIMIT 1')->fetch();
+ if (!$row) {
+ $random = mt_rand(1000, 9999);
+ echo "There was no data in the cards table, so we're re-creating it\n";
+ echo "The old table will be renamed to cards_old$random, just in case.\n";
+
+ $create = true;
+
+ switch ($driver) {
+ case 'mysql':
+ $pdo->exec("RENAME TABLE cards TO cards_old$random");
+ break;
+ case 'sqlite':
+ $pdo->exec("ALTER TABLE cards RENAME TO cards_old$random");
+ break;
+ }
+ }
+} catch (Exception $e) {
+ echo "Exception while checking cards table. Assuming that the table does not yet exist.\n";
+ echo 'Debug: ', $e->getMessage(), "\n";
+ $create = true;
+}
+
+if ($create) {
+ switch ($driver) {
+ case 'mysql':
+ $pdo->exec('
+CREATE TABLE cards (
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ addressbookid INT(11) UNSIGNED NOT NULL,
+ carddata MEDIUMBLOB,
+ uri VARCHAR(200),
+ lastmodified INT(11) UNSIGNED,
+ etag VARBINARY(32),
+ size INT(11) UNSIGNED NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+
+ ');
+ break;
+
+ case 'sqlite':
+
+ $pdo->exec('
+CREATE TABLE cards (
+ id integer primary key asc,
+ addressbookid integer,
+ carddata blob,
+ uri text,
+ lastmodified integer,
+ etag text,
+ size integer
+);
+ ');
+ break;
+ }
+} else {
+ switch ($driver) {
+ case 'mysql':
+ $pdo->exec('
+ ALTER TABLE cards
+ ADD etag VARBINARY(32),
+ ADD size INT(11) UNSIGNED NOT NULL;
+ ');
+ break;
+
+ case 'sqlite':
+
+ $pdo->exec('
+ ALTER TABLE cards ADD etag text;
+ ALTER TABLE cards ADD size integer;
+ ');
+ break;
+ }
+ echo "Reading all old vcards and populating etag and size fields.\n";
+ $result = $pdo->query('SELECT id, carddata FROM cards');
+ $stmt = $pdo->prepare('UPDATE cards SET etag = ?, size = ? WHERE id = ?');
+ while ($row = $result->fetch(\PDO::FETCH_ASSOC)) {
+ $stmt->execute([
+ md5($row['carddata']),
+ strlen($row['carddata']),
+ $row['id'],
+ ]);
+ }
+}
+
+echo "Upgrade to 2.0 schema completed.\n";
diff --git a/plugins/panakour/backup/vendor/sabre/dav/bin/migrateto21.php b/plugins/panakour/backup/vendor/sabre/dav/bin/migrateto21.php
new file mode 100644
index 000000000..909643583
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/bin/migrateto21.php
@@ -0,0 +1,166 @@
+#!/usr/bin/env php
+setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
+
+$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
+
+switch ($driver) {
+ case 'mysql':
+ echo "Detected MySQL.\n";
+ break;
+ case 'sqlite':
+ echo "Detected SQLite.\n";
+ break;
+ default:
+ echo 'Error: unsupported driver: '.$driver."\n";
+ die(-1);
+}
+
+echo "Upgrading 'calendarobjects'\n";
+$addUid = false;
+try {
+ $result = $pdo->query('SELECT * FROM calendarobjects LIMIT 1');
+ $row = $result->fetch(\PDO::FETCH_ASSOC);
+
+ if (!$row) {
+ echo "No data in table. Going to try to add the uid field anyway.\n";
+ $addUid = true;
+ } elseif (array_key_exists('uid', $row)) {
+ echo "uid field exists. Assuming that this part of the migration has\n";
+ echo "Already been completed.\n";
+ } else {
+ echo "2.0 schema detected.\n";
+ $addUid = true;
+ }
+} catch (Exception $e) {
+ echo "Could not find a calendarobjects table. Skipping this part of the\n";
+ echo "upgrade.\n";
+}
+
+if ($addUid) {
+ switch ($driver) {
+ case 'mysql':
+ $pdo->exec('ALTER TABLE calendarobjects ADD uid VARCHAR(200)');
+ break;
+ case 'sqlite':
+ $pdo->exec('ALTER TABLE calendarobjects ADD uid TEXT');
+ break;
+ }
+
+ $result = $pdo->query('SELECT id, calendardata FROM calendarobjects');
+ $stmt = $pdo->prepare('UPDATE calendarobjects SET uid = ? WHERE id = ?');
+ $counter = 0;
+
+ while ($row = $result->fetch(\PDO::FETCH_ASSOC)) {
+ try {
+ $vobj = \Sabre\VObject\Reader::read($row['calendardata']);
+ } catch (\Exception $e) {
+ echo "Warning! Item with id $row[id] could not be parsed!\n";
+ continue;
+ }
+ $uid = null;
+ $item = $vobj->getBaseComponent();
+ if (!isset($item->UID)) {
+ echo "Warning! Item with id $item[id] does NOT have a UID property and this is required.\n";
+ continue;
+ }
+ $uid = (string) $item->UID;
+ $stmt->execute([$uid, $row['id']]);
+ ++$counter;
+ }
+}
+
+echo "Creating 'schedulingobjects'\n";
+
+switch ($driver) {
+ case 'mysql':
+ $pdo->exec('CREATE TABLE IF NOT EXISTS schedulingobjects
+(
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ principaluri VARCHAR(255),
+ calendardata MEDIUMBLOB,
+ uri VARCHAR(200),
+ lastmodified INT(11) UNSIGNED,
+ etag VARCHAR(32),
+ size INT(11) UNSIGNED NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+ ');
+ break;
+
+ case 'sqlite':
+ $pdo->exec('CREATE TABLE IF NOT EXISTS schedulingobjects (
+ id integer primary key asc,
+ principaluri text,
+ calendardata blob,
+ uri text,
+ lastmodified integer,
+ etag text,
+ size integer
+)
+');
+ break;
+}
+
+echo "Done.\n";
+
+echo "Upgrade to 2.1 schema completed.\n";
diff --git a/plugins/panakour/backup/vendor/sabre/dav/bin/migrateto30.php b/plugins/panakour/backup/vendor/sabre/dav/bin/migrateto30.php
new file mode 100644
index 000000000..25e544c2a
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/bin/migrateto30.php
@@ -0,0 +1,161 @@
+#!/usr/bin/env php
+setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
+
+$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
+
+switch ($driver) {
+ case 'mysql':
+ echo "Detected MySQL.\n";
+ break;
+ case 'sqlite':
+ echo "Detected SQLite.\n";
+ break;
+ default:
+ echo 'Error: unsupported driver: '.$driver."\n";
+ die(-1);
+}
+
+echo "Upgrading 'propertystorage'\n";
+$addValueType = false;
+try {
+ $result = $pdo->query('SELECT * FROM propertystorage LIMIT 1');
+ $row = $result->fetch(\PDO::FETCH_ASSOC);
+
+ if (!$row) {
+ echo "No data in table. Going to re-create the table.\n";
+ $random = mt_rand(1000, 9999);
+ echo "Renaming propertystorage -> propertystorage_old$random and creating new table.\n";
+
+ switch ($driver) {
+ case 'mysql':
+ $pdo->exec('RENAME TABLE propertystorage TO propertystorage_old'.$random);
+ $pdo->exec('
+ CREATE TABLE propertystorage (
+ id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ path VARBINARY(1024) NOT NULL,
+ name VARBINARY(100) NOT NULL,
+ valuetype INT UNSIGNED,
+ value MEDIUMBLOB
+ );
+ ');
+ $pdo->exec('CREATE UNIQUE INDEX path_property_'.$random.' ON propertystorage (path(600), name(100));');
+ break;
+ case 'sqlite':
+ $pdo->exec('ALTER TABLE propertystorage RENAME TO propertystorage_old'.$random);
+ $pdo->exec('
+CREATE TABLE propertystorage (
+ id integer primary key asc,
+ path text,
+ name text,
+ valuetype integer,
+ value blob
+);');
+
+ $pdo->exec('CREATE UNIQUE INDEX path_property_'.$random.' ON propertystorage (path, name);');
+ break;
+ }
+ } elseif (array_key_exists('valuetype', $row)) {
+ echo "valuetype field exists. Assuming that this part of the migration has\n";
+ echo "Already been completed.\n";
+ } else {
+ echo "2.1 schema detected. Going to perform upgrade.\n";
+ $addValueType = true;
+ }
+} catch (Exception $e) {
+ echo "Could not find a propertystorage table. Skipping this part of the\n";
+ echo "upgrade.\n";
+ echo $e->getMessage(), "\n";
+}
+
+if ($addValueType) {
+ switch ($driver) {
+ case 'mysql':
+ $pdo->exec('ALTER TABLE propertystorage ADD valuetype INT UNSIGNED');
+ break;
+ case 'sqlite':
+ $pdo->exec('ALTER TABLE propertystorage ADD valuetype INT');
+
+ break;
+ }
+
+ $pdo->exec('UPDATE propertystorage SET valuetype = 1 WHERE valuetype IS NULL ');
+}
+
+echo "Migrating vcardurl\n";
+
+$result = $pdo->query('SELECT id, uri, vcardurl FROM principals WHERE vcardurl IS NOT NULL');
+$stmt1 = $pdo->prepare('INSERT INTO propertystorage (path, name, valuetype, value) VALUES (?, ?, 3, ?)');
+
+while ($row = $result->fetch(\PDO::FETCH_ASSOC)) {
+ // Inserting the new record
+ $stmt1->execute([
+ 'addressbooks/'.basename($row['uri']),
+ '{http://calendarserver.org/ns/}me-card',
+ serialize(new Sabre\DAV\Xml\Property\Href($row['vcardurl'])),
+ ]);
+
+ echo serialize(new Sabre\DAV\Xml\Property\Href($row['vcardurl']));
+}
+
+echo "Done.\n";
+echo "Upgrade to 3.0 schema completed.\n";
diff --git a/plugins/panakour/backup/vendor/sabre/dav/bin/migrateto32.php b/plugins/panakour/backup/vendor/sabre/dav/bin/migrateto32.php
new file mode 100644
index 000000000..57fd35507
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/bin/migrateto32.php
@@ -0,0 +1,258 @@
+#!/usr/bin/env php
+setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
+
+$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
+
+switch ($driver) {
+ case 'mysql':
+ echo "Detected MySQL.\n";
+ break;
+ case 'sqlite':
+ echo "Detected SQLite.\n";
+ break;
+ default:
+ echo 'Error: unsupported driver: '.$driver."\n";
+ die(-1);
+}
+
+echo "Creating 'calendarinstances'\n";
+$addValueType = false;
+try {
+ $result = $pdo->query('SELECT * FROM calendarinstances LIMIT 1');
+ $result->fetch(\PDO::FETCH_ASSOC);
+ echo "calendarinstances exists. Assuming this part of the migration has already been done.\n";
+} catch (Exception $e) {
+ echo "calendarinstances does not yet exist. Creating table and migrating data.\n";
+
+ switch ($driver) {
+ case 'mysql':
+ $pdo->exec(<<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(<<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(<<exec(<<exec(<<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()
diff --git a/plugins/panakour/backup/vendor/sabre/dav/bin/sabredav b/plugins/panakour/backup/vendor/sabre/dav/bin/sabredav
new file mode 100644
index 000000000..032371ba8
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/bin/sabredav
@@ -0,0 +1,2 @@
+#!/bin/sh
+php -S 0.0.0.0:8080 `dirname $0`/sabredav.php
diff --git a/plugins/panakour/backup/vendor/sabre/dav/bin/sabredav.php b/plugins/panakour/backup/vendor/sabre/dav/bin/sabredav.php
new file mode 100644
index 000000000..28341b587
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/bin/sabredav.php
@@ -0,0 +1,51 @@
+stream = fopen('php://stdout', 'w');
+ }
+
+ public function log($msg)
+ {
+ fwrite($this->stream, $msg."\n");
+ }
+}
+
+$log = new CliLog();
+
+if ('cli-server' !== php_sapi_name()) {
+ die('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();
diff --git a/plugins/panakour/backup/vendor/sabre/dav/composer.json b/plugins/panakour/backup/vendor/sabre/dav/composer.json
new file mode 100644
index 000000000..f0fbf7af7
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/composer.json
@@ -0,0 +1,90 @@
+{
+ "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",
+ "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",
+ "ext-json": "*"
+ },
+ "require-dev" : {
+ "friendsofphp/php-cs-fixer": "~2.16.1",
+ "phpstan/phpstan": "^0.12",
+ "phpunit/phpunit" : "^7.5 || ^8.5 || ^9.0",
+ "evert/phpdoc-md" : "~0.1.0",
+ "monolog/monolog": "^1.18"
+ },
+ "suggest" : {
+ "ext-curl" : "*",
+ "ext-pdo" : "*",
+ "ext-imap": "*"
+ },
+ "autoload": {
+ "psr-4" : {
+ "Sabre\\DAV\\" : "lib/DAV/",
+ "Sabre\\DAVACL\\" : "lib/DAVACL/",
+ "Sabre\\CalDAV\\" : "lib/CalDAV/",
+ "Sabre\\CardDAV\\" : "lib/CardDAV/"
+ }
+ },
+ "autoload-dev" : {
+ "psr-4" : {
+ "Sabre\\" : "tests/Sabre/",
+ "Sabre\\CalDAV\\" : "tests/Sabre/CalDAV",
+ "Sabre\\CardDAV\\" : "tests/Sabre/CardDAV",
+ "Sabre\\DAV\\" : "tests/Sabre/DAV",
+ "Sabre\\DAV\\Property\\" : "tests/Sabre/DAV/Xml/Property",
+ "Sabre\\DAVACL\\" : "tests/Sabre/DAVACL",
+ "Sabre\\HTTP\\" : "tests/Sabre/HTTP"
+ }
+ },
+ "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"
+ ]
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/addressbookserver.php b/plugins/panakour/backup/vendor/sabre/dav/examples/addressbookserver.php
new file mode 100644
index 000000000..48f1e9170
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/addressbookserver.php
@@ -0,0 +1,51 @@
+setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+// Autoloader
+require_once 'vendor/autoload.php';
+
+// Backends
+$authBackend = new Sabre\DAV\Auth\Backend\PDO($pdo);
+$principalBackend = new Sabre\DAVACL\PrincipalBackend\PDO($pdo);
+$carddavBackend = new Sabre\CardDAV\Backend\PDO($pdo);
+//$caldavBackend = new Sabre\CalDAV\Backend\PDO($pdo);
+
+// Setting up the directory tree //
+$nodes = [
+ new Sabre\DAVACL\PrincipalCollection($principalBackend),
+// new Sabre\CalDAV\CalendarRoot($authBackend, $caldavBackend),
+ new Sabre\CardDAV\AddressBookRoot($principalBackend, $carddavBackend),
+];
+
+// The object tree needs in turn to be passed to the server class
+$server = new Sabre\DAV\Server($nodes);
+$server->setBaseUri($baseUri);
+
+// Plugins
+$server->addPlugin(new Sabre\DAV\Auth\Plugin($authBackend));
+$server->addPlugin(new Sabre\DAV\Browser\Plugin());
+//$server->addPlugin(new Sabre\CalDAV\Plugin());
+$server->addPlugin(new Sabre\CardDAV\Plugin());
+$server->addPlugin(new Sabre\DAVACL\Plugin());
+$server->addPlugin(new Sabre\DAV\Sync\Plugin());
+
+// And off we go!
+$server->start();
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/calendarserver.php b/plugins/panakour/backup/vendor/sabre/dav/examples/calendarserver.php
new file mode 100644
index 000000000..88ea96114
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/calendarserver.php
@@ -0,0 +1,75 @@
+setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+// Files we need
+require_once 'vendor/autoload.php';
+
+// Backends
+$authBackend = new Sabre\DAV\Auth\Backend\PDO($pdo);
+$calendarBackend = new Sabre\CalDAV\Backend\PDO($pdo);
+$principalBackend = new Sabre\DAVACL\PrincipalBackend\PDO($pdo);
+
+// Directory structure
+$tree = [
+ new Sabre\CalDAV\Principal\Collection($principalBackend),
+ new Sabre\CalDAV\CalendarRoot($principalBackend, $calendarBackend),
+];
+
+$server = new Sabre\DAV\Server($tree);
+
+if (isset($baseUri)) {
+ $server->setBaseUri($baseUri);
+}
+
+/* Server Plugins */
+$authPlugin = new Sabre\DAV\Auth\Plugin($authBackend);
+$server->addPlugin($authPlugin);
+
+$aclPlugin = new Sabre\DAVACL\Plugin();
+$server->addPlugin($aclPlugin);
+
+/* CalDAV support */
+$caldavPlugin = new Sabre\CalDAV\Plugin();
+$server->addPlugin($caldavPlugin);
+
+/* Calendar subscription support */
+$server->addPlugin(
+ new Sabre\CalDAV\Subscriptions\Plugin()
+);
+
+/* Calendar scheduling support */
+$server->addPlugin(
+ new Sabre\CalDAV\Schedule\Plugin()
+);
+
+/* WebDAV-Sync plugin */
+$server->addPlugin(new Sabre\DAV\Sync\Plugin());
+
+/* CalDAV Sharing support */
+$server->addPlugin(new Sabre\DAV\Sharing\Plugin());
+$server->addPlugin(new Sabre\CalDAV\SharingPlugin());
+
+// Support for html frontend
+$browser = new Sabre\DAV\Browser\Plugin();
+$server->addPlugin($browser);
+
+// And off we go!
+$server->start();
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/fileserver.php b/plugins/panakour/backup/vendor/sabre/dav/examples/fileserver.php
new file mode 100644
index 000000000..667f55df1
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/fileserver.php
@@ -0,0 +1,56 @@
+setBaseUri($baseUri);
+}
+
+// Support for LOCK and UNLOCK
+$lockBackend = new \Sabre\DAV\Locks\Backend\File($tmpDir.'/locksdb');
+$lockPlugin = new \Sabre\DAV\Locks\Plugin($lockBackend);
+$server->addPlugin($lockPlugin);
+
+// Support for html frontend
+$browser = new \Sabre\DAV\Browser\Plugin();
+$server->addPlugin($browser);
+
+// Automatically guess (some) contenttypes, based on extension
+$server->addPlugin(new \Sabre\DAV\Browser\GuessContentType());
+
+// Authentication backend
+$authBackend = new \Sabre\DAV\Auth\Backend\File('.htdigest');
+$auth = new \Sabre\DAV\Auth\Plugin($authBackend);
+$server->addPlugin($auth);
+
+// Temporary file filter
+$tempFF = new \Sabre\DAV\TemporaryFileFilterPlugin($tmpDir);
+$server->addPlugin($tempFF);
+
+// And off we go!
+$server->start();
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/groupwareserver.php b/plugins/panakour/backup/vendor/sabre/dav/examples/groupwareserver.php
new file mode 100644
index 000000000..675c0a154
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/groupwareserver.php
@@ -0,0 +1,91 @@
+setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+// Autoloader
+require_once 'vendor/autoload.php';
+
+/**
+ * The backends. Yes we do really need all of them.
+ *
+ * This allows any developer to subclass just any of them and hook into their
+ * own backend systems.
+ */
+$authBackend = new \Sabre\DAV\Auth\Backend\PDO($pdo);
+$principalBackend = new \Sabre\DAVACL\PrincipalBackend\PDO($pdo);
+$carddavBackend = new \Sabre\CardDAV\Backend\PDO($pdo);
+$caldavBackend = new \Sabre\CalDAV\Backend\PDO($pdo);
+
+/**
+ * The directory tree.
+ *
+ * Basically this is an array which contains the 'top-level' directories in the
+ * WebDAV server.
+ */
+$nodes = [
+ // /principals
+ new \Sabre\CalDAV\Principal\Collection($principalBackend),
+ // /calendars
+ new \Sabre\CalDAV\CalendarRoot($principalBackend, $caldavBackend),
+ // /addressbook
+ new \Sabre\CardDAV\AddressBookRoot($principalBackend, $carddavBackend),
+];
+
+// The object tree needs in turn to be passed to the server class
+$server = new \Sabre\DAV\Server($nodes);
+if (isset($baseUri)) {
+ $server->setBaseUri($baseUri);
+}
+
+// Plugins
+$server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend));
+$server->addPlugin(new \Sabre\DAV\Browser\Plugin());
+$server->addPlugin(new \Sabre\DAV\Sync\Plugin());
+$server->addPlugin(new \Sabre\DAV\Sharing\Plugin());
+$server->addPlugin(new \Sabre\DAVACL\Plugin());
+
+// CalDAV plugins
+$server->addPlugin(new \Sabre\CalDAV\Plugin());
+$server->addPlugin(new \Sabre\CalDAV\Schedule\Plugin());
+$server->addPlugin(new \Sabre\CalDAV\SharingPlugin());
+$server->addPlugin(new \Sabre\CalDAV\ICSExportPlugin());
+
+// CardDAV plugins
+$server->addPlugin(new \Sabre\CardDAV\Plugin());
+$server->addPlugin(new \Sabre\CardDAV\VCFExportPlugin());
+
+// And off we go!
+$server->start();
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/minimal.php b/plugins/panakour/backup/vendor/sabre/dav/examples/minimal.php
new file mode 100644
index 000000000..fb7a1fe7a
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/minimal.php
@@ -0,0 +1,20 @@
+addPlugin(
+ new Sabre\DAV\Browser\Plugin()
+);
+
+$server->start();
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/sql/mysql.addressbooks.sql b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/mysql.addressbooks.sql
new file mode 100644
index 000000000..9ec88babe
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/mysql.addressbooks.sql
@@ -0,0 +1,28 @@
+CREATE TABLE addressbooks (
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ principaluri VARBINARY(255),
+ displayname VARCHAR(255),
+ uri VARBINARY(200),
+ description TEXT,
+ synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1',
+ UNIQUE(principaluri(100), uri(100))
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+CREATE TABLE cards (
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ addressbookid INT(11) UNSIGNED NOT NULL,
+ carddata MEDIUMBLOB,
+ uri VARBINARY(200),
+ lastmodified INT(11) UNSIGNED,
+ etag VARBINARY(32),
+ size INT(11) UNSIGNED NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+CREATE TABLE addressbookchanges (
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ uri VARBINARY(200) NOT NULL,
+ synctoken INT(11) UNSIGNED NOT NULL,
+ addressbookid INT(11) UNSIGNED NOT NULL,
+ operation TINYINT(1) NOT NULL,
+ INDEX addressbookid_synctoken (addressbookid, synctoken)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/sql/mysql.calendars.sql b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/mysql.calendars.sql
new file mode 100644
index 000000000..21c5bcb44
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/mysql.calendars.sql
@@ -0,0 +1,76 @@
+CREATE TABLE calendarobjects (
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ calendardata MEDIUMBLOB,
+ uri VARBINARY(200),
+ calendarid INTEGER UNSIGNED NOT NULL,
+ lastmodified INT(11) UNSIGNED,
+ etag VARBINARY(32),
+ size INT(11) UNSIGNED NOT NULL,
+ componenttype VARBINARY(8),
+ firstoccurence INT(11) UNSIGNED,
+ lastoccurence INT(11) UNSIGNED,
+ uid VARBINARY(200),
+ UNIQUE(calendarid, uri),
+ INDEX calendarid_time (calendarid, firstoccurence)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+CREATE TABLE calendars (
+ id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ synctoken INTEGER UNSIGNED NOT NULL DEFAULT '1',
+ components VARBINARY(21)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+CREATE TABLE calendarinstances (
+ id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ calendarid INTEGER UNSIGNED NOT NULL,
+ principaluri VARBINARY(100),
+ access TINYINT(1) NOT NULL DEFAULT '1' COMMENT '1 = owner, 2 = read, 3 = readwrite',
+ displayname VARCHAR(100),
+ uri VARBINARY(200),
+ 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;
+
+CREATE TABLE calendarchanges (
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ uri VARBINARY(200) NOT NULL,
+ synctoken INT(11) UNSIGNED NOT NULL,
+ calendarid INT(11) UNSIGNED NOT NULL,
+ operation TINYINT(1) NOT NULL,
+ INDEX calendarid_synctoken (calendarid, synctoken)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+CREATE TABLE calendarsubscriptions (
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ uri VARBINARY(200) NOT NULL,
+ principaluri VARBINARY(100) NOT NULL,
+ source TEXT,
+ displayname VARCHAR(100),
+ refreshrate VARCHAR(10),
+ calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ calendarcolor VARBINARY(10),
+ striptodos TINYINT(1) NULL,
+ stripalarms TINYINT(1) NULL,
+ stripattachments TINYINT(1) NULL,
+ lastmodified INT(11) UNSIGNED,
+ UNIQUE(principaluri, uri)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+CREATE TABLE schedulingobjects (
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ principaluri VARBINARY(255),
+ calendardata MEDIUMBLOB,
+ uri VARBINARY(200),
+ lastmodified INT(11) UNSIGNED,
+ etag VARBINARY(32),
+ size INT(11) UNSIGNED NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/sql/mysql.locks.sql b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/mysql.locks.sql
new file mode 100644
index 000000000..96a3a88d9
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/mysql.locks.sql
@@ -0,0 +1,12 @@
+CREATE TABLE locks (
+ id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ owner VARCHAR(100),
+ timeout INTEGER UNSIGNED,
+ created INTEGER,
+ token VARBINARY(100),
+ scope TINYINT,
+ depth TINYINT,
+ uri VARBINARY(1000),
+ INDEX(token),
+ INDEX(uri(100))
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/sql/mysql.principals.sql b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/mysql.principals.sql
new file mode 100644
index 000000000..ea0d16a27
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/mysql.principals.sql
@@ -0,0 +1,20 @@
+CREATE TABLE principals (
+ id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ uri VARBINARY(200) NOT NULL,
+ email VARBINARY(80),
+ displayname VARCHAR(80),
+ UNIQUE(uri)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+CREATE TABLE groupmembers (
+ id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ principal_id INTEGER UNSIGNED NOT NULL,
+ member_id INTEGER UNSIGNED NOT NULL,
+ UNIQUE(principal_id, member_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+INSERT INTO principals (uri,email,displayname) VALUES
+('principals/admin', 'admin@example.org','Administrator'),
+('principals/admin/calendar-proxy-read', null, null),
+('principals/admin/calendar-proxy-write', null, null);
+
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/sql/mysql.propertystorage.sql b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/mysql.propertystorage.sql
new file mode 100644
index 000000000..1b5ca5ac6
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/mysql.propertystorage.sql
@@ -0,0 +1,9 @@
+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
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+CREATE UNIQUE INDEX path_property ON propertystorage (path(600), name(100));
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/sql/mysql.users.sql b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/mysql.users.sql
new file mode 100644
index 000000000..22ac312d5
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/mysql.users.sql
@@ -0,0 +1,9 @@
+CREATE TABLE users (
+ id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ username VARBINARY(50),
+ digesta1 VARBINARY(32),
+ UNIQUE(username)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+INSERT INTO users (username,digesta1) VALUES
+('admin', '87fd274b7b6c01e48d7c2f965da8ddf7');
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/sql/pgsql.addressbooks.sql b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/pgsql.addressbooks.sql
new file mode 100644
index 000000000..98f414f42
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/pgsql.addressbooks.sql
@@ -0,0 +1,44 @@
+CREATE TABLE addressbooks (
+ id SERIAL NOT NULL,
+ principaluri VARCHAR(255),
+ displayname VARCHAR(255),
+ uri VARCHAR(200),
+ description TEXT,
+ synctoken INTEGER NOT NULL DEFAULT 1
+);
+
+ALTER TABLE ONLY addressbooks
+ ADD CONSTRAINT addressbooks_pkey PRIMARY KEY (id);
+
+CREATE UNIQUE INDEX addressbooks_ukey
+ ON addressbooks USING btree (principaluri, uri);
+
+CREATE TABLE cards (
+ id SERIAL NOT NULL,
+ addressbookid INTEGER NOT NULL,
+ carddata BYTEA,
+ uri VARCHAR(200),
+ lastmodified INTEGER,
+ etag VARCHAR(32),
+ size INTEGER NOT NULL
+);
+
+ALTER TABLE ONLY cards
+ ADD CONSTRAINT cards_pkey PRIMARY KEY (id);
+
+CREATE UNIQUE INDEX cards_ukey
+ ON cards USING btree (addressbookid, uri);
+
+CREATE TABLE addressbookchanges (
+ id SERIAL NOT NULL,
+ uri VARCHAR(200) NOT NULL,
+ synctoken INTEGER NOT NULL,
+ addressbookid INTEGER NOT NULL,
+ operation SMALLINT NOT NULL
+);
+
+ALTER TABLE ONLY addressbookchanges
+ ADD CONSTRAINT addressbookchanges_pkey PRIMARY KEY (id);
+
+CREATE INDEX addressbookchanges_addressbookid_synctoken_ix
+ ON addressbookchanges USING btree (addressbookid, synctoken);
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/sql/pgsql.calendars.sql b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/pgsql.calendars.sql
new file mode 100644
index 000000000..67dc41a5a
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/pgsql.calendars.sql
@@ -0,0 +1,105 @@
+CREATE TABLE calendarobjects (
+ id SERIAL NOT NULL,
+ calendardata BYTEA,
+ uri VARCHAR(200),
+ calendarid INTEGER NOT NULL,
+ lastmodified INTEGER,
+ etag VARCHAR(32),
+ size INTEGER NOT NULL,
+ componenttype VARCHAR(8),
+ firstoccurence INTEGER,
+ lastoccurence INTEGER,
+ uid VARCHAR(200)
+);
+
+ALTER TABLE ONLY calendarobjects
+ ADD CONSTRAINT calendarobjects_pkey PRIMARY KEY (id);
+
+CREATE UNIQUE INDEX calendarobjects_ukey
+ ON calendarobjects USING btree (calendarid, uri);
+
+
+CREATE TABLE calendars (
+ id SERIAL NOT NULL,
+ synctoken INTEGER NOT NULL DEFAULT 1,
+ components VARCHAR(21)
+);
+
+ALTER TABLE ONLY calendars
+ ADD CONSTRAINT calendars_pkey PRIMARY KEY (id);
+
+
+CREATE TABLE calendarinstances (
+ id SERIAL NOT NULL,
+ calendarid INTEGER NOT NULL,
+ principaluri VARCHAR(100),
+ access SMALLINT NOT NULL DEFAULT '1', -- '1 = owner, 2 = read, 3 = readwrite'
+ displayname VARCHAR(100),
+ uri VARCHAR(200),
+ description TEXT,
+ calendarorder INTEGER NOT NULL DEFAULT 0,
+ calendarcolor VARCHAR(10),
+ timezone TEXT,
+ transparent SMALLINT NOT NULL DEFAULT '0',
+ share_href VARCHAR(100),
+ share_displayname VARCHAR(100),
+ share_invitestatus SMALLINT NOT NULL DEFAULT '2' -- '1 = noresponse, 2 = accepted, 3 = declined, 4 = invalid'
+);
+
+ALTER TABLE ONLY calendarinstances
+ ADD CONSTRAINT calendarinstances_pkey PRIMARY KEY (id);
+
+CREATE UNIQUE INDEX calendarinstances_principaluri_uri
+ ON calendarinstances USING btree (principaluri, uri);
+
+
+CREATE UNIQUE INDEX calendarinstances_principaluri_calendarid
+ ON calendarinstances USING btree (principaluri, calendarid);
+
+CREATE UNIQUE INDEX calendarinstances_principaluri_share_href
+ ON calendarinstances USING btree (principaluri, share_href);
+
+CREATE TABLE calendarsubscriptions (
+ id SERIAL NOT NULL,
+ uri VARCHAR(200) NOT NULL,
+ principaluri VARCHAR(100) NOT NULL,
+ source TEXT,
+ displayname VARCHAR(100),
+ refreshrate VARCHAR(10),
+ calendarorder INTEGER NOT NULL DEFAULT 0,
+ calendarcolor VARCHAR(10),
+ striptodos SMALLINT NULL,
+ stripalarms SMALLINT NULL,
+ stripattachments SMALLINT NULL,
+ lastmodified INTEGER
+);
+
+ALTER TABLE ONLY calendarsubscriptions
+ ADD CONSTRAINT calendarsubscriptions_pkey PRIMARY KEY (id);
+
+CREATE UNIQUE INDEX calendarsubscriptions_ukey
+ ON calendarsubscriptions USING btree (principaluri, uri);
+
+CREATE TABLE calendarchanges (
+ id SERIAL NOT NULL,
+ uri VARCHAR(200) NOT NULL,
+ synctoken INTEGER NOT NULL,
+ calendarid INTEGER NOT NULL,
+ operation SMALLINT NOT NULL DEFAULT 0
+);
+
+ALTER TABLE ONLY calendarchanges
+ ADD CONSTRAINT calendarchanges_pkey PRIMARY KEY (id);
+
+CREATE INDEX calendarchanges_calendarid_synctoken_ix
+ ON calendarchanges USING btree (calendarid, synctoken);
+
+CREATE TABLE schedulingobjects (
+ id SERIAL NOT NULL,
+ principaluri VARCHAR(255),
+ calendardata BYTEA,
+ uri VARCHAR(200),
+ lastmodified INTEGER,
+ etag VARCHAR(32),
+ size INTEGER NOT NULL
+);
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/sql/pgsql.locks.sql b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/pgsql.locks.sql
new file mode 100644
index 000000000..0290528ce
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/pgsql.locks.sql
@@ -0,0 +1,19 @@
+CREATE TABLE locks (
+ id SERIAL NOT NULL,
+ owner VARCHAR(100),
+ timeout INTEGER,
+ created INTEGER,
+ token VARCHAR(100),
+ scope SMALLINT,
+ depth SMALLINT,
+ uri TEXT
+);
+
+ALTER TABLE ONLY locks
+ ADD CONSTRAINT locks_pkey PRIMARY KEY (id);
+
+CREATE INDEX locks_token_ix
+ ON locks USING btree (token);
+
+CREATE INDEX locks_uri_ix
+ ON locks USING btree (uri);
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/sql/pgsql.principals.sql b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/pgsql.principals.sql
new file mode 100644
index 000000000..5a65260a2
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/pgsql.principals.sql
@@ -0,0 +1,30 @@
+CREATE TABLE principals (
+ id SERIAL NOT NULL,
+ uri VARCHAR(200) NOT NULL,
+ email VARCHAR(80),
+ displayname VARCHAR(80)
+);
+
+ALTER TABLE ONLY principals
+ ADD CONSTRAINT principals_pkey PRIMARY KEY (id);
+
+CREATE UNIQUE INDEX principals_ukey
+ ON principals USING btree (uri);
+
+CREATE TABLE groupmembers (
+ id SERIAL NOT NULL,
+ principal_id INTEGER NOT NULL,
+ member_id INTEGER NOT NULL
+);
+
+ALTER TABLE ONLY groupmembers
+ ADD CONSTRAINT groupmembers_pkey PRIMARY KEY (id);
+
+CREATE UNIQUE INDEX groupmembers_ukey
+ ON groupmembers USING btree (principal_id, member_id);
+
+INSERT INTO principals (uri,email,displayname) VALUES
+('principals/admin', 'admin@example.org','Administrator'),
+('principals/admin/calendar-proxy-read', null, null),
+('principals/admin/calendar-proxy-write', null, null);
+
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/sql/pgsql.propertystorage.sql b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/pgsql.propertystorage.sql
new file mode 100644
index 000000000..d1463faae
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/pgsql.propertystorage.sql
@@ -0,0 +1,13 @@
+CREATE TABLE propertystorage (
+ id SERIAL NOT NULL,
+ path VARCHAR(1024) NOT NULL,
+ name VARCHAR(100) NOT NULL,
+ valuetype INT,
+ value BYTEA
+);
+
+ALTER TABLE ONLY propertystorage
+ ADD CONSTRAINT propertystorage_pkey PRIMARY KEY (id);
+
+CREATE UNIQUE INDEX propertystorage_ukey
+ ON propertystorage (path, name);
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/sql/pgsql.users.sql b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/pgsql.users.sql
new file mode 100644
index 000000000..9d6047b8c
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/pgsql.users.sql
@@ -0,0 +1,14 @@
+CREATE TABLE users (
+ id SERIAL NOT NULL,
+ username VARCHAR(50),
+ digesta1 VARCHAR(32)
+);
+
+ALTER TABLE ONLY users
+ ADD CONSTRAINT users_pkey PRIMARY KEY (id);
+
+CREATE UNIQUE INDEX users_ukey
+ ON users USING btree (username);
+
+INSERT INTO users (username,digesta1) VALUES
+('admin', '87fd274b7b6c01e48d7c2f965da8ddf7');
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/sql/sqlite.addressbooks.sql b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/sqlite.addressbooks.sql
new file mode 100644
index 000000000..60bd70804
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/sqlite.addressbooks.sql
@@ -0,0 +1,31 @@
+CREATE TABLE addressbooks (
+ id integer primary key asc NOT NULL,
+ principaluri text NOT NULL,
+ displayname text,
+ uri text NOT NULL,
+ description text,
+ synctoken integer DEFAULT 1 NOT NULL,
+ UNIQUE(principaluri, uri)
+);
+
+
+CREATE TABLE cards (
+ id integer primary key asc NOT NULL,
+ addressbookid integer NOT NULL,
+ carddata blob,
+ uri text NOT NULL,
+ lastmodified integer,
+ etag text,
+ size integer,
+ UNIQUE(addressbookid, uri)
+);
+
+CREATE TABLE addressbookchanges (
+ id integer primary key asc NOT NULL,
+ uri text,
+ synctoken integer NOT NULL,
+ addressbookid integer NOT NULL,
+ operation integer NOT NULL
+);
+
+CREATE INDEX addressbookid_synctoken ON addressbookchanges (addressbookid, synctoken);
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/sql/sqlite.calendars.sql b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/sqlite.calendars.sql
new file mode 100644
index 000000000..b3dc10a17
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/sqlite.calendars.sql
@@ -0,0 +1,78 @@
+CREATE TABLE calendarobjects (
+ id integer primary key asc NOT NULL,
+ calendardata blob NOT NULL,
+ uri text NOT NULL,
+ calendarid integer NOT NULL,
+ lastmodified integer NOT NULL,
+ etag text NOT NULL,
+ size integer NOT NULL,
+ componenttype text,
+ firstoccurence integer,
+ lastoccurence integer,
+ uid text,
+ UNIQUE(calendarid,uri)
+);
+CREATE INDEX calendarid_time ON calendarobjects (calendarid, firstoccurence);
+
+CREATE TABLE calendars (
+ id integer primary key asc NOT NULL,
+ synctoken integer DEFAULT 1 NOT NULL,
+ components text NOT NULL
+);
+
+CREATE TABLE calendarinstances (
+ id integer primary key asc NOT NULL,
+ calendarid integer NOT NULL,
+ principaluri text NULL,
+ access integer COMMENT '1 = owner, 2 = read, 3 = readwrite' NOT NULL DEFAULT '1',
+ displayname text,
+ uri text NOT NULL,
+ 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)
+);
+
+CREATE TABLE calendarchanges (
+ id integer primary key asc NOT NULL,
+ uri text,
+ synctoken integer NOT NULL,
+ calendarid integer NOT NULL,
+ operation integer NOT NULL
+);
+
+CREATE INDEX calendarid_synctoken ON calendarchanges (calendarid, synctoken);
+
+CREATE TABLE calendarsubscriptions (
+ id integer primary key asc NOT NULL,
+ uri text NOT NULL,
+ principaluri text NOT NULL,
+ source text NOT NULL,
+ displayname text,
+ refreshrate text,
+ calendarorder integer,
+ calendarcolor text,
+ striptodos bool,
+ stripalarms bool,
+ stripattachments bool,
+ lastmodified int,
+ UNIQUE(principaluri, uri)
+);
+
+CREATE TABLE schedulingobjects (
+ id integer primary key asc NOT NULL,
+ principaluri text NOT NULL,
+ calendardata blob,
+ uri text NOT NULL,
+ lastmodified integer,
+ etag text NOT NULL,
+ size integer NOT NULL,
+ UNIQUE(principaluri, uri)
+);
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/sql/sqlite.locks.sql b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/sqlite.locks.sql
new file mode 100644
index 000000000..dd0346614
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/sqlite.locks.sql
@@ -0,0 +1,11 @@
+CREATE TABLE 'locks' (
+ id integer primary key asc NOT NULL,
+ owner text,
+ timeout integer,
+ created integer,
+ token text,
+ scope integer,
+ depth integer,
+ uri text
+);
+CREATE INDEX idx_uri ON 'locks' (uri);
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/sql/sqlite.principals.sql b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/sqlite.principals.sql
new file mode 100644
index 000000000..4105156f8
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/sqlite.principals.sql
@@ -0,0 +1,20 @@
+CREATE TABLE principals (
+ id INTEGER PRIMARY KEY ASC NOT NULL,
+ uri TEXT NOT NULL,
+ email TEXT,
+ displayname TEXT,
+ UNIQUE(uri)
+);
+
+CREATE TABLE groupmembers (
+ id INTEGER PRIMARY KEY ASC NOT NULL,
+ principal_id INTEGER NOT NULL,
+ member_id INTEGER NOT NULL,
+ UNIQUE(principal_id, member_id)
+);
+
+
+INSERT INTO principals (uri,email,displayname) VALUES ('principals/admin', 'admin@example.org','Administrator');
+INSERT INTO principals (uri,email,displayname) VALUES ('principals/admin/calendar-proxy-read', null, null);
+INSERT INTO principals (uri,email,displayname) VALUES ('principals/admin/calendar-proxy-write', null, null);
+
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/sql/sqlite.propertystorage.sql b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/sqlite.propertystorage.sql
new file mode 100644
index 000000000..4ccf48431
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/sqlite.propertystorage.sql
@@ -0,0 +1,9 @@
+CREATE TABLE propertystorage (
+ id integer primary key asc NOT NULL,
+ path text NOT NULL,
+ name text NOT NULL,
+ valuetype integer NOT NULL,
+ value string
+);
+
+CREATE UNIQUE INDEX path_property ON propertystorage (path, name);
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/sql/sqlite.users.sql b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/sqlite.users.sql
new file mode 100644
index 000000000..5597b058a
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/sql/sqlite.users.sql
@@ -0,0 +1,9 @@
+CREATE TABLE users (
+ id integer primary key asc NOT NULL,
+ username TEXT NOT NULL,
+ digesta1 TEXT NOT NULL,
+ UNIQUE(username)
+);
+
+INSERT INTO users (username,digesta1) VALUES
+('admin', '87fd274b7b6c01e48d7c2f965da8ddf7');
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/webserver/apache2_htaccess.conf b/plugins/panakour/backup/vendor/sabre/dav/examples/webserver/apache2_htaccess.conf
new file mode 100644
index 000000000..c5f29ba80
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/webserver/apache2_htaccess.conf
@@ -0,0 +1,16 @@
+RewriteEngine On
+# This makes every request go to server.php
+RewriteRule (.*) server.php [L]
+
+# Output buffering needs to be off, to prevent high memory usage
+php_flag output_buffering off
+
+# This is also to prevent high memory usage
+php_flag always_populate_raw_post_data off
+
+# This is almost a given, but magic quotes is *still* on on some
+# linux distributions
+php_flag magic_quotes_gpc off
+
+# SabreDAV is not compatible with mbstring function overloading
+php_flag mbstring.func_overload off
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/webserver/apache2_vhost.conf b/plugins/panakour/backup/vendor/sabre/dav/examples/webserver/apache2_vhost.conf
new file mode 100644
index 000000000..27ff4127b
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/webserver/apache2_vhost.conf
@@ -0,0 +1,29 @@
+# This is a sample configuration to setup a dedicated Apache vhost for WebDAV.
+#
+# The main thing that should be configured is the servername, and the path to
+# your SabreDAV installation (DocumentRoot).
+#
+# This configuration assumed mod_php5 is used, as it sets a few default php
+# settings as well.
+
+
+ # Don't forget to change the server name
+ # ServerName dav.example.org
+
+ # The DocumentRoot is also required
+ # DocumentRoot /home/sabredav/
+
+ RewriteEngine On
+ # This makes every request go to server.php
+ RewriteRule ^/(.*)$ /server.php [L]
+
+ # Output buffering needs to be off, to prevent high memory usage
+ php_flag output_buffering off
+
+ # This is also to prevent high memory usage
+ php_flag always_populate_raw_post_data off
+
+ # SabreDAV is not compatible with mbstring function overloading
+ php_flag mbstring.func_overload off
+
+
diff --git a/plugins/panakour/backup/vendor/sabre/dav/examples/webserver/apache2_vhost_cgi.conf b/plugins/panakour/backup/vendor/sabre/dav/examples/webserver/apache2_vhost_cgi.conf
new file mode 100644
index 000000000..9784c7b95
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/examples/webserver/apache2_vhost_cgi.conf
@@ -0,0 +1,21 @@
+# This is a sample configuration to setup a dedicated Apache vhost for WebDAV.
+#
+# The main thing that should be configured is the servername, and the path to
+# your SabreDAV installation (DocumentRoot).
+#
+# This configuration assumes CGI or FastCGI is used.
+
+
+ # Don't forget to change the server name
+ # ServerName dav.example.org
+
+ # The DocumentRoot is also required
+ # DocumentRoot /home/sabredav/
+
+ # This makes every request go to server.php. This also makes sure
+ # the Authentication information is available. If your server script is
+ # not called server.php, be sure to change it.
+ RewriteEngine On
+ RewriteRule ^/(.*)$ /server.php [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+
+
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php
new file mode 100644
index 000000000..c32c86489
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php
@@ -0,0 +1,216 @@
+getCalendarObject($calendarId, $uri);
+ }, $uris);
+ }
+
+ /**
+ * Performs a calendar-query on the contents of this calendar.
+ *
+ * The calendar-query is defined in RFC4791 : CalDAV. Using the
+ * calendar-query it is possible for a client to request a specific set of
+ * object, based on contents of iCalendar properties, date-ranges and
+ * iCalendar component types (VTODO, VEVENT).
+ *
+ * This method should just return a list of (relative) urls that match this
+ * query.
+ *
+ * The list of filters are specified as an array. The exact array is
+ * documented by \Sabre\CalDAV\CalendarQueryParser.
+ *
+ * Note that it is extremely likely that getCalendarObject for every path
+ * returned from this method will be called almost immediately after. You
+ * may want to anticipate this to speed up these requests.
+ *
+ * This method provides a default implementation, which parses *all* the
+ * iCalendar objects in the specified calendar.
+ *
+ * This default may well be good enough for personal use, and calendars
+ * that aren't very large. But if you anticipate high usage, big calendars
+ * or high loads, you are strongly adviced to optimize certain paths.
+ *
+ * The best way to do so is override this method and to optimize
+ * specifically for 'common filters'.
+ *
+ * Requests that are extremely common are:
+ * * requests for just VEVENTS
+ * * requests for just VTODO
+ * * requests with a time-range-filter on either VEVENT or VTODO.
+ *
+ * ..and combinations of these requests. It may not be worth it to try to
+ * handle every possible situation and just rely on the (relatively
+ * easy to use) CalendarQueryValidator to handle the rest.
+ *
+ * Note that especially time-range-filters may be difficult to parse. A
+ * time-range filter specified on a VEVENT must for instance also handle
+ * recurrence rules correctly.
+ * A good example of how to interprete all these filters can also simply
+ * be found in \Sabre\CalDAV\CalendarQueryFilter. This class is as correct
+ * as possible, so it gives you a good idea on what type of stuff you need
+ * to think of.
+ *
+ * @param mixed $calendarId
+ *
+ * @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];
+ }
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php
new file mode 100644
index 000000000..8bfa7446a
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php
@@ -0,0 +1,273 @@
+ 'displayname',
+ '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
+ '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone',
+ '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
+ '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
+ ];
+
+ /**
+ * List of subscription properties, and how they map to database fieldnames.
+ *
+ * @var array
+ */
+ public $subscriptionPropertyMap = [
+ '{DAV:}displayname' => 'displayname',
+ '{http://apple.com/ns/ical/}refreshrate' => 'refreshrate',
+ '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
+ '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
+ '{http://calendarserver.org/ns/}subscribed-strip-todos' => 'striptodos',
+ '{http://calendarserver.org/ns/}subscribed-strip-alarms' => 'stripalarms',
+ '{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
+ ];
+
+ /**
+ * 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)
+ {
+ $fields = array_values($this->propertyMap);
+ $fields[] = 'calendarid';
+ $fields[] = 'uri';
+ $fields[] = 'synctoken';
+ $fields[] = 'components';
+ $fields[] = 'principaluri';
+ $fields[] = 'transparent';
+ $fields[] = 'access';
+
+ // Making fields a comma-delimited list
+ $fields = implode(', ', $fields);
+ $stmt = $this->pdo->prepare(<<calendarInstancesTableName}.id as id, $fields FROM {$this->calendarInstancesTableName}
+ LEFT JOIN {$this->calendarTableName} ON
+ {$this->calendarInstancesTableName}.calendarid = {$this->calendarTableName}.id
+WHERE principaluri = ? ORDER BY calendarorder ASC
+SQL
+ );
+ $stmt->execute([$principalUri]);
+
+ $calendars = [];
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ $components = [];
+ if ($row['components']) {
+ $components = explode(',', $row['components']);
+ }
+
+ $calendar = [
+ 'id' => [(int) $row['calendarid'], (int) $row['id']],
+ 'uri' => $row['uri'],
+ 'principaluri' => $row['principaluri'],
+ '{'.CalDAV\Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
+ '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
+ '{'.CalDAV\Plugin::NS_CALDAV.'}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet($components),
+ '{'.CalDAV\Plugin::NS_CALDAV.'}schedule-calendar-transp' => new CalDAV\Xml\Property\ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
+ 'share-resource-uri' => '/ns/share/'.$row['calendarid'],
+ ];
+
+ $calendar['share-access'] = (int) $row['access'];
+ // 1 = owner, 2 = readonly, 3 = readwrite
+ if ($row['access'] > 1) {
+ // We need to find more information about the original owner.
+ //$stmt2 = $this->pdo->prepare('SELECT principaluri FROM ' . $this->calendarInstancesTableName . ' WHERE access = 1 AND id = ?');
+ //$stmt2->execute([$row['id']]);
+
+ // read-only is for backwards compatbility. Might go away in
+ // the future.
+ $calendar['read-only'] = \Sabre\DAV\Sharing\Plugin::ACCESS_READ === (int) $row['access'];
+ }
+
+ foreach ($this->propertyMap as $xmlName => $dbName) {
+ $calendar[$xmlName] = $row[$dbName];
+ }
+
+ $calendars[] = $calendar;
+ }
+
+ 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)
+ {
+ $fieldNames = [
+ 'principaluri',
+ 'uri',
+ 'transparent',
+ 'calendarid',
+ ];
+ $values = [
+ ':principaluri' => $principalUri,
+ ':uri' => $calendarUri,
+ ':transparent' => 0,
+ ];
+
+ $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
+ if (!isset($properties[$sccs])) {
+ // Default value
+ $components = 'VEVENT,VTODO';
+ } else {
+ if (!($properties[$sccs] instanceof CalDAV\Xml\Property\SupportedCalendarComponentSet)) {
+ throw new DAV\Exception('The '.$sccs.' property must be of type: \Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet');
+ }
+ $components = implode(',', $properties[$sccs]->getValue());
+ }
+ $transp = '{'.CalDAV\Plugin::NS_CALDAV.'}schedule-calendar-transp';
+ if (isset($properties[$transp])) {
+ $values[':transparent'] = 'transparent' === $properties[$transp]->getValue() ? 1 : 0;
+ }
+ $stmt = $this->pdo->prepare('INSERT INTO '.$this->calendarTableName.' (synctoken, components) VALUES (1, ?)');
+ $stmt->execute([$components]);
+
+ $calendarId = $this->pdo->lastInsertId(
+ $this->calendarTableName.'_id_seq'
+ );
+
+ $values[':calendarid'] = $calendarId;
+
+ foreach ($this->propertyMap as $xmlName => $dbName) {
+ if (isset($properties[$xmlName])) {
+ $values[':'.$dbName] = $properties[$xmlName];
+ $fieldNames[] = $dbName;
+ }
+ }
+
+ $stmt = $this->pdo->prepare('INSERT INTO '.$this->calendarInstancesTableName.' ('.implode(', ', $fieldNames).') VALUES ('.implode(', ', array_keys($values)).')');
+
+ $stmt->execute($values);
+
+ return [
+ $calendarId,
+ $this->pdo->lastInsertId($this->calendarInstancesTableName.'_id_seq'),
+ ];
+ }
+
+ /**
+ * 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)
+ {
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
+ }
+ list($calendarId, $instanceId) = $calendarId;
+
+ $supportedProperties = array_keys($this->propertyMap);
+ $supportedProperties[] = '{'.CalDAV\Plugin::NS_CALDAV.'}schedule-calendar-transp';
+
+ $propPatch->handle($supportedProperties, function ($mutations) use ($calendarId, $instanceId) {
+ $newValues = [];
+ foreach ($mutations as $propertyName => $propertyValue) {
+ switch ($propertyName) {
+ case '{'.CalDAV\Plugin::NS_CALDAV.'}schedule-calendar-transp':
+ $fieldName = 'transparent';
+ $newValues[$fieldName] = 'transparent' === $propertyValue->getValue();
+ break;
+ default:
+ $fieldName = $this->propertyMap[$propertyName];
+ $newValues[$fieldName] = $propertyValue;
+ break;
+ }
+ }
+ $valuesSql = [];
+ foreach ($newValues as $fieldName => $value) {
+ $valuesSql[] = $fieldName.' = ?';
+ }
+
+ $stmt = $this->pdo->prepare('UPDATE '.$this->calendarInstancesTableName.' SET '.implode(', ', $valuesSql).' WHERE id = ?');
+ $newValues['id'] = $instanceId;
+ $stmt->execute(array_values($newValues));
+
+ $this->addChange($calendarId, '', 2);
+
+ return true;
+ });
+ }
+
+ /**
+ * Delete a calendar and all it's objects.
+ *
+ * @param mixed $calendarId
+ */
+ public function deleteCalendar($calendarId)
+ {
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
+ }
+ list($calendarId, $instanceId) = $calendarId;
+
+ $stmt = $this->pdo->prepare('SELECT access FROM '.$this->calendarInstancesTableName.' where id = ?');
+ $stmt->execute([$instanceId]);
+ $access = (int) $stmt->fetchColumn();
+
+ if (\Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER === $access) {
+ /**
+ * If the user is the owner of the calendar, we delete all data and all
+ * instances.
+ **/
+ $stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarObjectTableName.' WHERE calendarid = ?');
+ $stmt->execute([$calendarId]);
+
+ $stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarChangesTableName.' WHERE calendarid = ?');
+ $stmt->execute([$calendarId]);
+
+ $stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarInstancesTableName.' WHERE calendarid = ?');
+ $stmt->execute([$calendarId]);
+
+ $stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarTableName.' WHERE id = ?');
+ $stmt->execute([$calendarId]);
+ } else {
+ /**
+ * If it was an instance of a shared calendar, we only delete that
+ * instance.
+ */
+ $stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarInstancesTableName.' WHERE id = ?');
+ $stmt->execute([$instanceId]);
+ }
+ }
+
+ /**
+ * 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)
+ {
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
+ }
+ list($calendarId, $instanceId) = $calendarId;
+
+ $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, componenttype FROM '.$this->calendarObjectTableName.' WHERE calendarid = ?');
+ $stmt->execute([$calendarId]);
+
+ $result = [];
+ foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
+ $result[] = [
+ 'id' => $row['id'],
+ 'uri' => $row['uri'],
+ 'lastmodified' => (int) $row['lastmodified'],
+ 'etag' => '"'.$row['etag'].'"',
+ 'size' => (int) $row['size'],
+ 'component' => strtolower($row['componenttype']),
+ ];
+ }
+
+ 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 mixed $calendarId
+ * @param string $objectUri
+ *
+ * @return array|null
+ */
+ public function getCalendarObject($calendarId, $objectUri)
+ {
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
+ }
+ list($calendarId, $instanceId) = $calendarId;
+
+ $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM '.$this->calendarObjectTableName.' WHERE calendarid = ? AND uri = ?');
+ $stmt->execute([$calendarId, $objectUri]);
+ $row = $stmt->fetch(\PDO::FETCH_ASSOC);
+
+ if (!$row) {
+ return null;
+ }
+
+ return [
+ 'id' => $row['id'],
+ 'uri' => $row['uri'],
+ 'lastmodified' => (int) $row['lastmodified'],
+ 'etag' => '"'.$row['etag'].'"',
+ 'size' => (int) $row['size'],
+ 'calendardata' => $row['calendardata'],
+ 'component' => strtolower($row['componenttype']),
+ ];
+ }
+
+ /**
+ * 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)
+ {
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
+ }
+ list($calendarId, $instanceId) = $calendarId;
+
+ $result = [];
+ foreach (array_chunk($uris, 900) as $chunk) {
+ $query = 'SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM '.$this->calendarObjectTableName.' WHERE calendarid = ? AND uri IN (';
+ // Inserting a whole bunch of question marks
+ $query .= implode(',', array_fill(0, count($chunk), '?'));
+ $query .= ')';
+
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute(array_merge([$calendarId], $chunk));
+
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ $result[] = [
+ 'id' => $row['id'],
+ 'uri' => $row['uri'],
+ 'lastmodified' => (int) $row['lastmodified'],
+ 'etag' => '"'.$row['etag'].'"',
+ 'size' => (int) $row['size'],
+ 'calendardata' => $row['calendardata'],
+ 'component' => strtolower($row['componenttype']),
+ ];
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * 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)
+ {
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
+ }
+ list($calendarId, $instanceId) = $calendarId;
+
+ $extraData = $this->getDenormalizedData($calendarData);
+
+ $stmt = $this->pdo->prepare('INSERT INTO '.$this->calendarObjectTableName.' (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES (?,?,?,?,?,?,?,?,?,?)');
+ $stmt->execute([
+ $calendarId,
+ $objectUri,
+ $calendarData,
+ time(),
+ $extraData['etag'],
+ $extraData['size'],
+ $extraData['componentType'],
+ $extraData['firstOccurence'],
+ $extraData['lastOccurence'],
+ $extraData['uid'],
+ ]);
+ $this->addChange($calendarId, $objectUri, 1);
+
+ return '"'.$extraData['etag'].'"';
+ }
+
+ /**
+ * 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)
+ {
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
+ }
+ list($calendarId, $instanceId) = $calendarId;
+
+ $extraData = $this->getDenormalizedData($calendarData);
+
+ $stmt = $this->pdo->prepare('UPDATE '.$this->calendarObjectTableName.' SET calendardata = ?, lastmodified = ?, etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ?, uid = ? WHERE calendarid = ? AND uri = ?');
+ $stmt->execute([$calendarData, time(), $extraData['etag'], $extraData['size'], $extraData['componentType'], $extraData['firstOccurence'], $extraData['lastOccurence'], $extraData['uid'], $calendarId, $objectUri]);
+
+ $this->addChange($calendarId, $objectUri, 2);
+
+ return '"'.$extraData['etag'].'"';
+ }
+
+ /**
+ * Parses some information from calendar objects, used for optimized
+ * calendar-queries.
+ *
+ * Returns an array with the following keys:
+ * * etag - An md5 checksum of the object without the quotes.
+ * * size - Size of the object in bytes
+ * * componentType - VEVENT, VTODO or VJOURNAL
+ * * firstOccurence
+ * * lastOccurence
+ * * uid - value of the UID property
+ *
+ * @param string $calendarData
+ *
+ * @return array
+ */
+ protected function getDenormalizedData($calendarData)
+ {
+ $vObject = VObject\Reader::read($calendarData);
+ $componentType = null;
+ $component = null;
+ $firstOccurence = null;
+ $lastOccurence = null;
+ $uid = null;
+ foreach ($vObject->getComponents() as $component) {
+ if ('VTIMEZONE' !== $component->name) {
+ $componentType = $component->name;
+ $uid = (string) $component->UID;
+ break;
+ }
+ }
+ if (!$componentType) {
+ throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
+ }
+ if ('VEVENT' === $componentType) {
+ $firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp();
+ // Finding the last occurence is a bit harder
+ if (!isset($component->RRULE)) {
+ if (isset($component->DTEND)) {
+ $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp();
+ } elseif (isset($component->DURATION)) {
+ $endDate = clone $component->DTSTART->getDateTime();
+ $endDate = $endDate->add(VObject\DateTimeParser::parse($component->DURATION->getValue()));
+ $lastOccurence = $endDate->getTimeStamp();
+ } elseif (!$component->DTSTART->hasTime()) {
+ $endDate = clone $component->DTSTART->getDateTime();
+ $endDate = $endDate->modify('+1 day');
+ $lastOccurence = $endDate->getTimeStamp();
+ } else {
+ $lastOccurence = $firstOccurence;
+ }
+ } else {
+ $it = new VObject\Recur\EventIterator($vObject, (string) $component->UID);
+ $maxDate = new \DateTime(self::MAX_DATE);
+ if ($it->isInfinite()) {
+ $lastOccurence = $maxDate->getTimeStamp();
+ } else {
+ $end = $it->getDtEnd();
+ while ($it->valid() && $end < $maxDate) {
+ $end = $it->getDtEnd();
+ $it->next();
+ }
+ $lastOccurence = $end->getTimeStamp();
+ }
+ }
+
+ // Ensure Occurence values are positive
+ if ($firstOccurence < 0) {
+ $firstOccurence = 0;
+ }
+ if ($lastOccurence < 0) {
+ $lastOccurence = 0;
+ }
+ }
+
+ // Destroy circular references to PHP will GC the object.
+ $vObject->destroy();
+
+ return [
+ 'etag' => md5($calendarData),
+ 'size' => strlen($calendarData),
+ 'componentType' => $componentType,
+ 'firstOccurence' => $firstOccurence,
+ 'lastOccurence' => $lastOccurence,
+ 'uid' => $uid,
+ ];
+ }
+
+ /**
+ * 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)
+ {
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
+ }
+ list($calendarId, $instanceId) = $calendarId;
+
+ $stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarObjectTableName.' WHERE calendarid = ? AND uri = ?');
+ $stmt->execute([$calendarId, $objectUri]);
+
+ $this->addChange($calendarId, $objectUri, 3);
+ }
+
+ /**
+ * Performs a calendar-query on the contents of this calendar.
+ *
+ * The calendar-query is defined in RFC4791 : CalDAV. Using the
+ * calendar-query it is possible for a client to request a specific set of
+ * object, based on contents of iCalendar properties, date-ranges and
+ * iCalendar component types (VTODO, VEVENT).
+ *
+ * This method should just return a list of (relative) urls that match this
+ * query.
+ *
+ * The list of filters are specified as an array. The exact array is
+ * documented by \Sabre\CalDAV\CalendarQueryParser.
+ *
+ * Note that it is extremely likely that getCalendarObject for every path
+ * returned from this method will be called almost immediately after. You
+ * may want to anticipate this to speed up these requests.
+ *
+ * This method provides a default implementation, which parses *all* the
+ * iCalendar objects in the specified calendar.
+ *
+ * This default may well be good enough for personal use, and calendars
+ * that aren't very large. But if you anticipate high usage, big calendars
+ * or high loads, you are strongly adviced to optimize certain paths.
+ *
+ * The best way to do so is override this method and to optimize
+ * specifically for 'common filters'.
+ *
+ * Requests that are extremely common are:
+ * * requests for just VEVENTS
+ * * requests for just VTODO
+ * * requests with a time-range-filter on a VEVENT.
+ *
+ * ..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.
+ *
+ * This specific implementation (for the PDO) backend optimizes filters on
+ * specific components, and VEVENT time-ranges.
+ *
+ * @param mixed $calendarId
+ *
+ * @return array
+ */
+ public function calendarQuery($calendarId, array $filters)
+ {
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
+ }
+ list($calendarId, $instanceId) = $calendarId;
+
+ $componentType = null;
+ $requirePostFilter = true;
+ $timeRange = null;
+
+ // if no filters were specified, we don't need to filter after a query
+ if (!$filters['prop-filters'] && !$filters['comp-filters']) {
+ $requirePostFilter = false;
+ }
+
+ // Figuring out if there's a component filter
+ if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
+ $componentType = $filters['comp-filters'][0]['name'];
+
+ // Checking if we need post-filters
+ if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
+ $requirePostFilter = false;
+ }
+ // There was a time-range filter
+ if ('VEVENT' == $componentType && isset($filters['comp-filters'][0]['time-range'])) {
+ $timeRange = $filters['comp-filters'][0]['time-range'];
+
+ // If start time OR the end time is not specified, we can do a
+ // 100% accurate mysql query.
+ if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
+ $requirePostFilter = false;
+ }
+ }
+ }
+
+ if ($requirePostFilter) {
+ $query = 'SELECT uri, calendardata FROM '.$this->calendarObjectTableName.' WHERE calendarid = :calendarid';
+ } else {
+ $query = 'SELECT uri FROM '.$this->calendarObjectTableName.' WHERE calendarid = :calendarid';
+ }
+
+ $values = [
+ 'calendarid' => $calendarId,
+ ];
+
+ if ($componentType) {
+ $query .= ' AND componenttype = :componenttype';
+ $values['componenttype'] = $componentType;
+ }
+
+ if ($timeRange && $timeRange['start']) {
+ $query .= ' AND lastoccurence > :startdate';
+ $values['startdate'] = $timeRange['start']->getTimeStamp();
+ }
+ if ($timeRange && $timeRange['end']) {
+ $query .= ' AND firstoccurence < :enddate';
+ $values['enddate'] = $timeRange['end']->getTimeStamp();
+ }
+
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute($values);
+
+ $result = [];
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ if ($requirePostFilter) {
+ if (!$this->validateFilterForObject($row, $filters)) {
+ continue;
+ }
+ }
+ $result[] = $row['uri'];
+ }
+
+ 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)
+ {
+ $query = <<calendarObjectTableName AS calendarobjects
+LEFT JOIN
+ $this->calendarInstancesTableName AS calendar_instances
+ ON calendarobjects.calendarid = calendar_instances.calendarid
+WHERE
+ calendar_instances.principaluri = ?
+ AND
+ calendarobjects.uid = ?
+ AND
+ calendar_instances.access = 1
+SQL;
+
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute([$principalUri, $uid]);
+
+ if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ return $row['calendaruri'].'/'.$row['objecturi'];
+ }
+ }
+
+ /**
+ * 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 mixed $calendarId
+ * @param string $syncToken
+ * @param int $syncLevel
+ * @param int $limit
+ *
+ * @return array
+ */
+ public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null)
+ {
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
+ }
+ list($calendarId, $instanceId) = $calendarId;
+
+ // Current synctoken
+ $stmt = $this->pdo->prepare('SELECT synctoken FROM '.$this->calendarTableName.' WHERE id = ?');
+ $stmt->execute([$calendarId]);
+ $currentToken = $stmt->fetchColumn(0);
+
+ if (is_null($currentToken)) {
+ return null;
+ }
+
+ $result = [
+ 'syncToken' => $currentToken,
+ 'added' => [],
+ 'modified' => [],
+ 'deleted' => [],
+ ];
+
+ if ($syncToken) {
+ $query = 'SELECT uri, operation FROM '.$this->calendarChangesTableName.' WHERE synctoken >= ? AND synctoken < ? AND calendarid = ? ORDER BY synctoken';
+ if ($limit > 0) {
+ $query .= ' LIMIT '.(int) $limit;
+ }
+
+ // Fetching all changes
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute([$syncToken, $currentToken, $calendarId]);
+
+ $changes = [];
+
+ // This loop ensures that any duplicates are overwritten, only the
+ // last change on a node is relevant.
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ $changes[$row['uri']] = $row['operation'];
+ }
+
+ foreach ($changes as $uri => $operation) {
+ switch ($operation) {
+ case 1:
+ $result['added'][] = $uri;
+ break;
+ case 2:
+ $result['modified'][] = $uri;
+ break;
+ case 3:
+ $result['deleted'][] = $uri;
+ break;
+ }
+ }
+ } else {
+ // No synctoken supplied, this is the initial sync.
+ $query = 'SELECT uri FROM '.$this->calendarObjectTableName.' WHERE calendarid = ?';
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute([$calendarId]);
+
+ $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Adds a change record to the calendarchanges table.
+ *
+ * @param mixed $calendarId
+ * @param string $objectUri
+ * @param int $operation 1 = add, 2 = modify, 3 = delete
+ */
+ protected function addChange($calendarId, $objectUri, $operation)
+ {
+ $stmt = $this->pdo->prepare('INSERT INTO '.$this->calendarChangesTableName.' (uri, synctoken, calendarid, operation) SELECT ?, synctoken, ?, ? FROM '.$this->calendarTableName.' WHERE id = ?');
+ $stmt->execute([
+ $objectUri,
+ $calendarId,
+ $operation,
+ $calendarId,
+ ]);
+ $stmt = $this->pdo->prepare('UPDATE '.$this->calendarTableName.' SET synctoken = synctoken + 1 WHERE id = ?');
+ $stmt->execute([
+ $calendarId,
+ ]);
+ }
+
+ /**
+ * 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.
+ * * source. Url to the actual feed
+ *
+ * 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).
+ * 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)
+ {
+ $fields = array_values($this->subscriptionPropertyMap);
+ $fields[] = 'id';
+ $fields[] = 'uri';
+ $fields[] = 'source';
+ $fields[] = 'principaluri';
+ $fields[] = 'lastmodified';
+
+ // Making fields a comma-delimited list
+ $fields = implode(', ', $fields);
+ $stmt = $this->pdo->prepare('SELECT '.$fields.' FROM '.$this->calendarSubscriptionsTableName.' WHERE principaluri = ? ORDER BY calendarorder ASC');
+ $stmt->execute([$principalUri]);
+
+ $subscriptions = [];
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ $subscription = [
+ 'id' => $row['id'],
+ 'uri' => $row['uri'],
+ 'principaluri' => $row['principaluri'],
+ 'source' => $row['source'],
+ 'lastmodified' => $row['lastmodified'],
+
+ '{'.CalDAV\Plugin::NS_CALDAV.'}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
+ ];
+
+ foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
+ if (!is_null($row[$dbName])) {
+ $subscription[$xmlName] = $row[$dbName];
+ }
+ }
+
+ $subscriptions[] = $subscription;
+ }
+
+ return $subscriptions;
+ }
+
+ /**
+ * 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)
+ {
+ $fieldNames = [
+ 'principaluri',
+ 'uri',
+ 'source',
+ 'lastmodified',
+ ];
+
+ if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
+ throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
+ }
+
+ $values = [
+ ':principaluri' => $principalUri,
+ ':uri' => $uri,
+ ':source' => $properties['{http://calendarserver.org/ns/}source']->getHref(),
+ ':lastmodified' => time(),
+ ];
+
+ foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
+ if (isset($properties[$xmlName])) {
+ $values[':'.$dbName] = $properties[$xmlName];
+ $fieldNames[] = $dbName;
+ }
+ }
+
+ $stmt = $this->pdo->prepare('INSERT INTO '.$this->calendarSubscriptionsTableName.' ('.implode(', ', $fieldNames).') VALUES ('.implode(', ', array_keys($values)).')');
+ $stmt->execute($values);
+
+ return $this->pdo->lastInsertId(
+ $this->calendarSubscriptionsTableName.'_id_seq'
+ );
+ }
+
+ /**
+ * 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)
+ {
+ $supportedProperties = array_keys($this->subscriptionPropertyMap);
+ $supportedProperties[] = '{http://calendarserver.org/ns/}source';
+
+ $propPatch->handle($supportedProperties, function ($mutations) use ($subscriptionId) {
+ $newValues = [];
+
+ foreach ($mutations as $propertyName => $propertyValue) {
+ if ('{http://calendarserver.org/ns/}source' === $propertyName) {
+ $newValues['source'] = $propertyValue->getHref();
+ } else {
+ $fieldName = $this->subscriptionPropertyMap[$propertyName];
+ $newValues[$fieldName] = $propertyValue;
+ }
+ }
+
+ // Now we're generating the sql query.
+ $valuesSql = [];
+ foreach ($newValues as $fieldName => $value) {
+ $valuesSql[] = $fieldName.' = ?';
+ }
+
+ $stmt = $this->pdo->prepare('UPDATE '.$this->calendarSubscriptionsTableName.' SET '.implode(', ', $valuesSql).', lastmodified = ? WHERE id = ?');
+ $newValues['lastmodified'] = time();
+ $newValues['id'] = $subscriptionId;
+ $stmt->execute(array_values($newValues));
+
+ return true;
+ });
+ }
+
+ /**
+ * Deletes a subscription.
+ *
+ * @param mixed $subscriptionId
+ */
+ public function deleteSubscription($subscriptionId)
+ {
+ $stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarSubscriptionsTableName.' WHERE id = ?');
+ $stmt->execute([$subscriptionId]);
+ }
+
+ /**
+ * Returns a single scheduling object.
+ *
+ * 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)
+ {
+ $stmt = $this->pdo->prepare('SELECT uri, calendardata, lastmodified, etag, size FROM '.$this->schedulingObjectTableName.' WHERE principaluri = ? AND uri = ?');
+ $stmt->execute([$principalUri, $objectUri]);
+ $row = $stmt->fetch(\PDO::FETCH_ASSOC);
+
+ if (!$row) {
+ return null;
+ }
+
+ return [
+ 'uri' => $row['uri'],
+ 'calendardata' => $row['calendardata'],
+ 'lastmodified' => $row['lastmodified'],
+ 'etag' => '"'.$row['etag'].'"',
+ 'size' => (int) $row['size'],
+ ];
+ }
+
+ /**
+ * 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)
+ {
+ $stmt = $this->pdo->prepare('SELECT id, calendardata, uri, lastmodified, etag, size FROM '.$this->schedulingObjectTableName.' WHERE principaluri = ?');
+ $stmt->execute([$principalUri]);
+
+ $result = [];
+ foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
+ $result[] = [
+ 'calendardata' => $row['calendardata'],
+ 'uri' => $row['uri'],
+ 'lastmodified' => $row['lastmodified'],
+ 'etag' => '"'.$row['etag'].'"',
+ 'size' => (int) $row['size'],
+ ];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Deletes a scheduling object.
+ *
+ * @param string $principalUri
+ * @param string $objectUri
+ */
+ public function deleteSchedulingObject($principalUri, $objectUri)
+ {
+ $stmt = $this->pdo->prepare('DELETE FROM '.$this->schedulingObjectTableName.' WHERE principaluri = ? AND uri = ?');
+ $stmt->execute([$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)
+ {
+ $stmt = $this->pdo->prepare('INSERT INTO '.$this->schedulingObjectTableName.' (principaluri, calendardata, uri, lastmodified, etag, size) VALUES (?, ?, ?, ?, ?, ?)');
+
+ if (is_resource($objectData)) {
+ $objectData = stream_get_contents($objectData);
+ }
+
+ $stmt->execute([$principalUri, $objectData, $objectUri, time(), md5($objectData), strlen($objectData)]);
+ }
+
+ /**
+ * Updates the list of shares.
+ *
+ * @param mixed $calendarId
+ * @param \Sabre\DAV\Xml\Element\Sharee[] $sharees
+ */
+ public function updateInvites($calendarId, array $sharees)
+ {
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
+ }
+ $currentInvites = $this->getInvites($calendarId);
+ list($calendarId, $instanceId) = $calendarId;
+
+ $removeStmt = $this->pdo->prepare('DELETE FROM '.$this->calendarInstancesTableName.' WHERE calendarid = ? AND share_href = ? AND access IN (2,3)');
+ $updateStmt = $this->pdo->prepare('UPDATE '.$this->calendarInstancesTableName.' SET access = ?, share_displayname = ?, share_invitestatus = ? WHERE calendarid = ? AND share_href = ?');
+
+ $insertStmt = $this->pdo->prepare('
+INSERT INTO '.$this->calendarInstancesTableName.'
+ (
+ calendarid,
+ principaluri,
+ access,
+ displayname,
+ uri,
+ description,
+ calendarorder,
+ calendarcolor,
+ timezone,
+ transparent,
+ share_href,
+ share_displayname,
+ share_invitestatus
+ )
+ SELECT
+ ?,
+ ?,
+ ?,
+ displayname,
+ ?,
+ description,
+ calendarorder,
+ calendarcolor,
+ timezone,
+ 1,
+ ?,
+ ?,
+ ?
+ FROM '.$this->calendarInstancesTableName.' WHERE id = ?');
+
+ foreach ($sharees as $sharee) {
+ if (\Sabre\DAV\Sharing\Plugin::ACCESS_NOACCESS === $sharee->access) {
+ // if access was set no NOACCESS, it means access for an
+ // existing sharee was removed.
+ $removeStmt->execute([$calendarId, $sharee->href]);
+ continue;
+ }
+
+ if (is_null($sharee->principal)) {
+ // If the server could not determine the principal automatically,
+ // we will mark the invite status as invalid.
+ $sharee->inviteStatus = \Sabre\DAV\Sharing\Plugin::INVITE_INVALID;
+ } else {
+ // Because sabre/dav does not yet have an invitation system,
+ // every invite is automatically accepted for now.
+ $sharee->inviteStatus = \Sabre\DAV\Sharing\Plugin::INVITE_ACCEPTED;
+ }
+
+ foreach ($currentInvites as $oldSharee) {
+ if ($oldSharee->href === $sharee->href) {
+ // This is an update
+ $sharee->properties = array_merge(
+ $oldSharee->properties,
+ $sharee->properties
+ );
+ $updateStmt->execute([
+ $sharee->access,
+ isset($sharee->properties['{DAV:}displayname']) ? $sharee->properties['{DAV:}displayname'] : null,
+ $sharee->inviteStatus ?: $oldSharee->inviteStatus,
+ $calendarId,
+ $sharee->href,
+ ]);
+ continue 2;
+ }
+ }
+ // If we got here, it means it was a new sharee
+ $insertStmt->execute([
+ $calendarId,
+ $sharee->principal,
+ $sharee->access,
+ \Sabre\DAV\UUIDUtil::getUUID(),
+ $sharee->href,
+ isset($sharee->properties['{DAV:}displayname']) ? $sharee->properties['{DAV:}displayname'] : null,
+ $sharee->inviteStatus ?: \Sabre\DAV\Sharing\Plugin::INVITE_NORESPONSE,
+ $instanceId,
+ ]);
+ }
+ }
+
+ /**
+ * Returns the list of people whom a calendar is shared with.
+ *
+ * Every item in the returned list must be a Sharee object with at
+ * least the following properties set:
+ * $href
+ * $shareAccess
+ * $inviteStatus
+ *
+ * and optionally:
+ * $properties
+ *
+ * @param mixed $calendarId
+ *
+ * @return \Sabre\DAV\Xml\Element\Sharee[]
+ */
+ public function getInvites($calendarId)
+ {
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to getInvites() is expected to be an array with a calendarId and an instanceId');
+ }
+ list($calendarId, $instanceId) = $calendarId;
+
+ $query = <<calendarInstancesTableName}
+WHERE
+ calendarid = ?
+SQL;
+
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute([$calendarId]);
+
+ $result = [];
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ $result[] = new Sharee([
+ 'href' => isset($row['share_href']) ? $row['share_href'] : \Sabre\HTTP\encodePath($row['principaluri']),
+ 'access' => (int) $row['access'],
+ /// Everyone is always immediately accepted, for now.
+ 'inviteStatus' => (int) $row['share_invitestatus'],
+ 'properties' => !empty($row['share_displayname'])
+ ? ['{DAV:}displayname' => $row['share_displayname']]
+ : [],
+ 'principal' => $row['principaluri'],
+ ]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Publishes a calendar.
+ *
+ * @param mixed $calendarId
+ * @param bool $value
+ */
+ public function setPublishStatus($calendarId, $value)
+ {
+ throw new DAV\Exception\NotImplemented('Not implemented');
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php
new file mode 100644
index 000000000..69467e554
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php
@@ -0,0 +1,66 @@
+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]);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Backend/SubscriptionSupport.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Backend/SubscriptionSupport.php
new file mode 100644
index 000000000..7655c2e11
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Backend/SubscriptionSupport.php
@@ -0,0 +1,89 @@
+ '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
+ */
+ public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null);
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Calendar.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Calendar.php
new file mode 100644
index 000000000..9f32e702a
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Calendar.php
@@ -0,0 +1,451 @@
+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 $calendarData
+ *
+ * @return string|null
+ */
+ public function createFile($name, $calendarData = null)
+ {
+ if (is_resource($calendarData)) {
+ $calendarData = stream_get_contents($calendarData);
+ }
+
+ return $this->caldavBackend->createCalendarObject($this->calendarInfo['id'], $name, $calendarData);
+ }
+
+ /**
+ * Deletes the calendar.
+ */
+ 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'
+ * ]
+ * ];
+ *
+ * The syncToken property should reflect the *current* syncToken of the
+ * collection, as reported getSyncToken(). This is needed here too, to
+ * ensure the operation is atomic.
+ *
+ * If the syncToken is specified as null, this is an initial sync, and all
+ * members should be reported.
+ *
+ * The modified property is an array of nodenames that have changed since
+ * the last token.
+ *
+ * The deleted property is an array with nodenames, that have been deleted
+ * from collection.
+ *
+ * The second argument is basically the 'depth' of the report. If it's 1,
+ * you only have to report changes that happened only directly in immediate
+ * descendants. If it's 2, it should also include changes from the nodes
+ * below the child collections. (grandchildren)
+ *
+ * The third (optional) argument allows a client to specify how many
+ * results should be returned at most. If the limit is not specified, it
+ * should be treated as infinite.
+ *
+ * If the limit (infinite or not) is higher than you're willing to return,
+ * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
+ *
+ * If the syncToken is expired (due to data cleanup) or unknown, you must
+ * return null.
+ *
+ * The limit is 'suggestive'. You are free to ignore it.
+ *
+ * @param string $syncToken
+ * @param int $syncLevel
+ * @param int $limit
+ *
+ * @return array
+ */
+ 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
+ );
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/CalendarHome.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/CalendarHome.php
new file mode 100644
index 000000000..159ddcc67
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/CalendarHome.php
@@ -0,0 +1,356 @@
+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 $filename
+ * @param resource $data
+ */
+ public function createFile($filename, $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);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/CalendarObject.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/CalendarObject.php
new file mode 100644
index 000000000..671f4b5db
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/CalendarObject.php
@@ -0,0 +1,223 @@
+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,
+ ],
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php
new file mode 100644
index 000000000..7ce1c05b7
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php
@@ -0,0 +1,340 @@
+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 ($filter['time-range']) {
+ foreach ($parent->{$filter['name']} as $subComponent) {
+ if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['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 ($filter['time-range']) {
+ foreach ($parent->{$filter['name']} as $subComponent) {
+ if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['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');
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/CalendarRoot.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/CalendarRoot.php
new file mode 100644
index 000000000..3038d218f
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/CalendarRoot.php
@@ -0,0 +1,75 @@
+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);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php
new file mode 100644
index 000000000..e94378a68
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php
@@ -0,0 +1,31 @@
+ownerDocument;
+
+ $np = $doc->createElementNS(CalDAV\Plugin::NS_CALDAV, 'cal:supported-calendar-component');
+ $errorNode->appendChild($np);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php
new file mode 100644
index 000000000..9171e36e7
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php
@@ -0,0 +1,377 @@
+server = $server;
+ $server->on('method:GET', [$this, 'httpGet'], 90);
+ $server->on('browserButtonActions', function ($path, $node, &$actions) {
+ if ($node instanceof ICalendar) {
+ $actions .= '';
+ }
+ });
+ }
+
+ /**
+ * 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/',
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/ICalendar.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/ICalendar.php
new file mode 100644
index 000000000..8636e0bae
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/ICalendar.php
@@ -0,0 +1,20 @@
+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;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Notifications/ICollection.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Notifications/ICollection.php
new file mode 100644
index 000000000..b12fb3909
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Notifications/ICollection.php
@@ -0,0 +1,25 @@
+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 litteral 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 a url to a principal, or null if there's no owner
+ *
+ * @return string|null
+ */
+ public function getOwner()
+ {
+ return $this->principalUri;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.php
new file mode 100644
index 000000000..56b2fe938
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.php
@@ -0,0 +1,161 @@
+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/',
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Plugin.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Plugin.php
new file mode 100644
index 000000000..da172049e
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Plugin.php
@@ -0,0 +1,1011 @@
+server->tree->getNodeForPath($parent);
+
+ if ($node instanceof DAV\IExtendedCollection) {
+ try {
+ $node->getChild($name);
+ } catch (DAV\Exception\NotFound $e) {
+ return ['MKCALENDAR'];
+ }
+ }
+
+ return [];
+ }
+
+ /**
+ * Returns the path to a principal's calendar home.
+ *
+ * The return url must not end with a slash.
+ * This function should return null in case a principal did not have
+ * a calendar home.
+ *
+ * @param string $principalUrl
+ *
+ * @return string
+ */
+ public function getCalendarHomeForPrincipal($principalUrl)
+ {
+ // The default behavior for most sabre/dav servers is that there is a
+ // principals root node, which contains users directly under it.
+ //
+ // This function assumes that there are two components in a principal
+ // path. If there's more, we don't return a calendar home. This
+ // excludes things like the calendar-proxy-read principal (which it
+ // should).
+ $parts = explode('/', trim($principalUrl, '/'));
+ if (2 !== count($parts)) {
+ return;
+ }
+ if ('principals' !== $parts[0]) {
+ return;
+ }
+
+ return self::CALENDAR_ROOT.'/'.$parts[1];
+ }
+
+ /**
+ * Returns a list of features for the DAV: HTTP header.
+ *
+ * @return array
+ */
+ public function getFeatures()
+ {
+ return ['calendar-access', 'calendar-proxy'];
+ }
+
+ /**
+ * Returns a plugin name.
+ *
+ * Using this name other plugins will be able to access other plugins
+ * using DAV\Server::getPlugin
+ *
+ * @return string
+ */
+ public function getPluginName()
+ {
+ return 'caldav';
+ }
+
+ /**
+ * Returns a list of reports this plugin supports.
+ *
+ * This will be used in the {DAV:}supported-report-set property.
+ * Note that you still need to subscribe to the 'report' event to actually
+ * implement them
+ *
+ * @param string $uri
+ *
+ * @return array
+ */
+ public function getSupportedReportSet($uri)
+ {
+ $node = $this->server->tree->getNodeForPath($uri);
+
+ $reports = [];
+ if ($node instanceof ICalendarObjectContainer || $node instanceof ICalendarObject) {
+ $reports[] = '{'.self::NS_CALDAV.'}calendar-multiget';
+ $reports[] = '{'.self::NS_CALDAV.'}calendar-query';
+ }
+ if ($node instanceof ICalendar) {
+ $reports[] = '{'.self::NS_CALDAV.'}free-busy-query';
+ }
+ // iCal has a bug where it assumes that sync support is enabled, only
+ // if we say we support it on the calendar-home, even though this is
+ // not actually the case.
+ if ($node instanceof CalendarHome && $this->server->getPlugin('sync')) {
+ $reports[] = '{DAV:}sync-collection';
+ }
+
+ return $reports;
+ }
+
+ /**
+ * Initializes the plugin.
+ */
+ public function initialize(DAV\Server $server)
+ {
+ $this->server = $server;
+
+ $server->on('method:MKCALENDAR', [$this, 'httpMkCalendar']);
+ $server->on('report', [$this, 'report']);
+ $server->on('propFind', [$this, 'propFind']);
+ $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
+ $server->on('beforeCreateFile', [$this, 'beforeCreateFile']);
+ $server->on('beforeWriteContent', [$this, 'beforeWriteContent']);
+ $server->on('afterMethod:GET', [$this, 'httpAfterGET']);
+ $server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']);
+
+ $server->xml->namespaceMap[self::NS_CALDAV] = 'cal';
+ $server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs';
+
+ $server->xml->elementMap['{'.self::NS_CALDAV.'}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet';
+ $server->xml->elementMap['{'.self::NS_CALDAV.'}calendar-query'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarQueryReport';
+ $server->xml->elementMap['{'.self::NS_CALDAV.'}calendar-multiget'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarMultiGetReport';
+ $server->xml->elementMap['{'.self::NS_CALDAV.'}free-busy-query'] = 'Sabre\\CalDAV\\Xml\\Request\\FreeBusyQueryReport';
+ $server->xml->elementMap['{'.self::NS_CALDAV.'}mkcalendar'] = 'Sabre\\CalDAV\\Xml\\Request\\MkCalendar';
+ $server->xml->elementMap['{'.self::NS_CALDAV.'}schedule-calendar-transp'] = 'Sabre\\CalDAV\\Xml\\Property\\ScheduleCalendarTransp';
+ $server->xml->elementMap['{'.self::NS_CALDAV.'}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet';
+
+ $server->resourceTypeMapping['\\Sabre\\CalDAV\\ICalendar'] = '{urn:ietf:params:xml:ns:caldav}calendar';
+
+ $server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyRead'] = '{http://calendarserver.org/ns/}calendar-proxy-read';
+ $server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyWrite'] = '{http://calendarserver.org/ns/}calendar-proxy-write';
+
+ array_push($server->protectedProperties,
+ '{'.self::NS_CALDAV.'}supported-calendar-component-set',
+ '{'.self::NS_CALDAV.'}supported-calendar-data',
+ '{'.self::NS_CALDAV.'}max-resource-size',
+ '{'.self::NS_CALDAV.'}min-date-time',
+ '{'.self::NS_CALDAV.'}max-date-time',
+ '{'.self::NS_CALDAV.'}max-instances',
+ '{'.self::NS_CALDAV.'}max-attendees-per-instance',
+ '{'.self::NS_CALDAV.'}calendar-home-set',
+ '{'.self::NS_CALDAV.'}supported-collation-set',
+ '{'.self::NS_CALDAV.'}calendar-data',
+
+ // CalendarServer extensions
+ '{'.self::NS_CALENDARSERVER.'}getctag',
+ '{'.self::NS_CALENDARSERVER.'}calendar-proxy-read-for',
+ '{'.self::NS_CALENDARSERVER.'}calendar-proxy-write-for'
+ );
+
+ if ($aclPlugin = $server->getPlugin('acl')) {
+ $aclPlugin->principalSearchPropertySet['{'.self::NS_CALDAV.'}calendar-user-address-set'] = 'Calendar address';
+ }
+ }
+
+ /**
+ * This functions handles REPORT requests specific to CalDAV.
+ *
+ * @param string $reportName
+ * @param mixed $report
+ * @param mixed $path
+ *
+ * @return bool
+ */
+ public function report($reportName, $report, $path)
+ {
+ switch ($reportName) {
+ case '{'.self::NS_CALDAV.'}calendar-multiget':
+ $this->server->transactionType = 'report-calendar-multiget';
+ $this->calendarMultiGetReport($report);
+
+ return false;
+ case '{'.self::NS_CALDAV.'}calendar-query':
+ $this->server->transactionType = 'report-calendar-query';
+ $this->calendarQueryReport($report);
+
+ return false;
+ case '{'.self::NS_CALDAV.'}free-busy-query':
+ $this->server->transactionType = 'report-free-busy-query';
+ $this->freeBusyQueryReport($report);
+
+ return false;
+ }
+ }
+
+ /**
+ * This function handles the MKCALENDAR HTTP method, which creates
+ * a new calendar.
+ *
+ * @return bool
+ */
+ public function httpMkCalendar(RequestInterface $request, ResponseInterface $response)
+ {
+ $body = $request->getBodyAsString();
+ $path = $request->getPath();
+
+ $properties = [];
+
+ if ($body) {
+ try {
+ $mkcalendar = $this->server->xml->expect(
+ '{urn:ietf:params:xml:ns:caldav}mkcalendar',
+ $body
+ );
+ } catch (\Sabre\Xml\ParseException $e) {
+ throw new BadRequest($e->getMessage(), 0, $e);
+ }
+ $properties = $mkcalendar->getProperties();
+ }
+
+ // iCal abuses MKCALENDAR since iCal 10.9.2 to create server-stored
+ // subscriptions. Before that it used MKCOL which was the correct way
+ // to do this.
+ //
+ // If the body had a {DAV:}resourcetype, it means we stumbled upon this
+ // request, and we simply use it instead of the pre-defined list.
+ if (isset($properties['{DAV:}resourcetype'])) {
+ $resourceType = $properties['{DAV:}resourcetype']->getValue();
+ } else {
+ $resourceType = ['{DAV:}collection', '{urn:ietf:params:xml:ns:caldav}calendar'];
+ }
+
+ $this->server->createCollection($path, new MkCol($resourceType, $properties));
+
+ $response->setStatus(201);
+ $response->setHeader('Content-Length', 0);
+
+ // This breaks the method chain.
+ return false;
+ }
+
+ /**
+ * PropFind.
+ *
+ * This method handler is invoked before any after properties for a
+ * resource are fetched. This allows us to add in any CalDAV specific
+ * properties.
+ */
+ public function propFind(DAV\PropFind $propFind, DAV\INode $node)
+ {
+ $ns = '{'.self::NS_CALDAV.'}';
+
+ if ($node instanceof ICalendarObjectContainer) {
+ $propFind->handle($ns.'max-resource-size', $this->maxResourceSize);
+ $propFind->handle($ns.'supported-calendar-data', function () {
+ return new Xml\Property\SupportedCalendarData();
+ });
+ $propFind->handle($ns.'supported-collation-set', function () {
+ return new Xml\Property\SupportedCollationSet();
+ });
+ }
+
+ if ($node instanceof DAVACL\IPrincipal) {
+ $principalUrl = $node->getPrincipalUrl();
+
+ $propFind->handle('{'.self::NS_CALDAV.'}calendar-home-set', function () use ($principalUrl) {
+ $calendarHomePath = $this->getCalendarHomeForPrincipal($principalUrl);
+ if (is_null($calendarHomePath)) {
+ return null;
+ }
+
+ return new LocalHref($calendarHomePath.'/');
+ });
+ // The calendar-user-address-set property is basically mapped to
+ // the {DAV:}alternate-URI-set property.
+ $propFind->handle('{'.self::NS_CALDAV.'}calendar-user-address-set', function () use ($node) {
+ $addresses = $node->getAlternateUriSet();
+ $addresses[] = $this->server->getBaseUri().$node->getPrincipalUrl().'/';
+
+ return new LocalHref($addresses);
+ });
+ // For some reason somebody thought it was a good idea to add
+ // another one of these properties. We're supporting it too.
+ $propFind->handle('{'.self::NS_CALENDARSERVER.'}email-address-set', function () use ($node) {
+ $addresses = $node->getAlternateUriSet();
+ $emails = [];
+ foreach ($addresses as $address) {
+ if ('mailto:' === substr($address, 0, 7)) {
+ $emails[] = substr($address, 7);
+ }
+ }
+
+ return new Xml\Property\EmailAddressSet($emails);
+ });
+
+ // These two properties are shortcuts for ical to easily find
+ // other principals this principal has access to.
+ $propRead = '{'.self::NS_CALENDARSERVER.'}calendar-proxy-read-for';
+ $propWrite = '{'.self::NS_CALENDARSERVER.'}calendar-proxy-write-for';
+
+ if (404 === $propFind->getStatus($propRead) || 404 === $propFind->getStatus($propWrite)) {
+ $aclPlugin = $this->server->getPlugin('acl');
+ $membership = $aclPlugin->getPrincipalMembership($propFind->getPath());
+ $readList = [];
+ $writeList = [];
+
+ foreach ($membership as $group) {
+ $groupNode = $this->server->tree->getNodeForPath($group);
+
+ $listItem = Uri\split($group)[0].'/';
+
+ // If the node is either ap proxy-read or proxy-write
+ // group, we grab the parent principal and add it to the
+ // list.
+ if ($groupNode instanceof Principal\IProxyRead) {
+ $readList[] = $listItem;
+ }
+ if ($groupNode instanceof Principal\IProxyWrite) {
+ $writeList[] = $listItem;
+ }
+ }
+
+ $propFind->set($propRead, new LocalHref($readList));
+ $propFind->set($propWrite, new LocalHref($writeList));
+ }
+ } // instanceof IPrincipal
+
+ if ($node instanceof ICalendarObject) {
+ // The calendar-data property is not supposed to be a 'real'
+ // property, but in large chunks of the spec it does act as such.
+ // Therefore we simply expose it as a property.
+ $propFind->handle('{'.self::NS_CALDAV.'}calendar-data', function () use ($node) {
+ $val = $node->get();
+ if (is_resource($val)) {
+ $val = stream_get_contents($val);
+ }
+
+ // Taking out \r to not screw up the xml output
+ return str_replace("\r", '', $val);
+ });
+ }
+ }
+
+ /**
+ * This function handles the calendar-multiget REPORT.
+ *
+ * This report is used by the client to fetch the content of a series
+ * of urls. Effectively avoiding a lot of redundant requests.
+ *
+ * @param CalendarMultiGetReport $report
+ */
+ public function calendarMultiGetReport($report)
+ {
+ $needsJson = 'application/calendar+json' === $report->contentType;
+
+ $timeZones = [];
+ $propertyList = [];
+
+ $paths = array_map(
+ [$this->server, 'calculateUri'],
+ $report->hrefs
+ );
+
+ foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $uri => $objProps) {
+ if (($needsJson || $report->expand) && isset($objProps[200]['{'.self::NS_CALDAV.'}calendar-data'])) {
+ $vObject = VObject\Reader::read($objProps[200]['{'.self::NS_CALDAV.'}calendar-data']);
+
+ if ($report->expand) {
+ // We're expanding, and for that we need to figure out the
+ // calendar's timezone.
+ list($calendarPath) = Uri\split($uri);
+ if (!isset($timeZones[$calendarPath])) {
+ // Checking the calendar-timezone property.
+ $tzProp = '{'.self::NS_CALDAV.'}calendar-timezone';
+ $tzResult = $this->server->getProperties($calendarPath, [$tzProp]);
+ if (isset($tzResult[$tzProp])) {
+ // This property contains a VCALENDAR with a single
+ // VTIMEZONE.
+ $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
+ $timeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
+ } else {
+ // Defaulting to UTC.
+ $timeZone = new DateTimeZone('UTC');
+ }
+ $timeZones[$calendarPath] = $timeZone;
+ }
+
+ $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $timeZones[$calendarPath]);
+ }
+ if ($needsJson) {
+ $objProps[200]['{'.self::NS_CALDAV.'}calendar-data'] = json_encode($vObject->jsonSerialize());
+ } else {
+ $objProps[200]['{'.self::NS_CALDAV.'}calendar-data'] = $vObject->serialize();
+ }
+ // Destroy circular references so PHP will garbage collect the
+ // object.
+ $vObject->destroy();
+ }
+
+ $propertyList[] = $objProps;
+ }
+
+ $prefer = $this->server->getHTTPPrefer();
+
+ $this->server->httpResponse->setStatus(207);
+ $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
+ $this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, 'minimal' === $prefer['return']));
+ }
+
+ /**
+ * This function handles the calendar-query REPORT.
+ *
+ * This report is used by clients to request calendar objects based on
+ * complex conditions.
+ *
+ * @param Xml\Request\CalendarQueryReport $report
+ */
+ public function calendarQueryReport($report)
+ {
+ $path = $this->server->getRequestUri();
+
+ $needsJson = 'application/calendar+json' === $report->contentType;
+
+ $node = $this->server->tree->getNodeForPath($this->server->getRequestUri());
+ $depth = $this->server->getHTTPDepth(0);
+
+ // The default result is an empty array
+ $result = [];
+
+ $calendarTimeZone = null;
+ if ($report->expand) {
+ // We're expanding, and for that we need to figure out the
+ // calendar's timezone.
+ $tzProp = '{'.self::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 so PHP will garbage collect the
+ // object.
+ $vtimezoneObj->destroy();
+ } else {
+ // Defaulting to UTC.
+ $calendarTimeZone = new DateTimeZone('UTC');
+ }
+ }
+
+ // The calendarobject was requested directly. In this case we handle
+ // this locally.
+ if (0 == $depth && $node instanceof ICalendarObject) {
+ $requestedCalendarData = true;
+ $requestedProperties = $report->properties;
+
+ if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) {
+ // We always retrieve calendar-data, as we need it for filtering.
+ $requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data';
+
+ // If calendar-data wasn't explicitly requested, we need to remove
+ // it after processing.
+ $requestedCalendarData = false;
+ }
+
+ $properties = $this->server->getPropertiesForPath(
+ $path,
+ $requestedProperties,
+ 0
+ );
+
+ // This array should have only 1 element, the first calendar
+ // object.
+ $properties = current($properties);
+
+ // If there wasn't any calendar-data returned somehow, we ignore
+ // this.
+ if (isset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) {
+ $validator = new CalendarQueryValidator();
+
+ $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
+ if ($validator->validate($vObject, $report->filters)) {
+ // If the client didn't require the calendar-data property,
+ // we won't give it back.
+ if (!$requestedCalendarData) {
+ unset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
+ } else {
+ if ($report->expand) {
+ $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone);
+ }
+ if ($needsJson) {
+ $properties[200]['{'.self::NS_CALDAV.'}calendar-data'] = json_encode($vObject->jsonSerialize());
+ } elseif ($report->expand) {
+ $properties[200]['{'.self::NS_CALDAV.'}calendar-data'] = $vObject->serialize();
+ }
+ }
+
+ $result = [$properties];
+ }
+ // Destroy circular references so PHP will garbage collect the
+ // object.
+ $vObject->destroy();
+ }
+ }
+
+ if ($node instanceof ICalendarObjectContainer && 0 === $depth) {
+ if (0 === strpos((string) $this->server->httpRequest->getHeader('User-Agent'), 'MSFT-')) {
+ // Microsoft clients incorrectly supplied depth as 0, when it actually
+ // should have set depth to 1. We're implementing a workaround here
+ // to deal with this.
+ //
+ // This targets at least the following clients:
+ // Windows 10
+ // Windows Phone 8, 10
+ $depth = 1;
+ } else {
+ throw new BadRequest('A calendar-query REPORT on a calendar with a Depth: 0 is undefined. Set Depth to 1');
+ }
+ }
+
+ // If we're dealing with a calendar, the calendar itself is responsible
+ // for the calendar-query.
+ if ($node instanceof ICalendarObjectContainer && 1 == $depth) {
+ $nodePaths = $node->calendarQuery($report->filters);
+
+ foreach ($nodePaths as $path) {
+ list($properties) =
+ $this->server->getPropertiesForPath($this->server->getRequestUri().'/'.$path, $report->properties);
+
+ if (($needsJson || $report->expand)) {
+ $vObject = VObject\Reader::read($properties[200]['{'.self::NS_CALDAV.'}calendar-data']);
+
+ if ($report->expand) {
+ $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone);
+ }
+
+ if ($needsJson) {
+ $properties[200]['{'.self::NS_CALDAV.'}calendar-data'] = json_encode($vObject->jsonSerialize());
+ } else {
+ $properties[200]['{'.self::NS_CALDAV.'}calendar-data'] = $vObject->serialize();
+ }
+
+ // Destroy circular references so PHP will garbage collect the
+ // object.
+ $vObject->destroy();
+ }
+ $result[] = $properties;
+ }
+ }
+
+ $prefer = $this->server->getHTTPPrefer();
+
+ $this->server->httpResponse->setStatus(207);
+ $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
+ $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, 'minimal' === $prefer['return']));
+ }
+
+ /**
+ * This method is responsible for parsing the request and generating the
+ * response for the CALDAV:free-busy-query REPORT.
+ */
+ protected function freeBusyQueryReport(Xml\Request\FreeBusyQueryReport $report)
+ {
+ $uri = $this->server->getRequestUri();
+
+ $acl = $this->server->getPlugin('acl');
+ if ($acl) {
+ $acl->checkPrivileges($uri, '{'.self::NS_CALDAV.'}read-free-busy');
+ }
+
+ $calendar = $this->server->tree->getNodeForPath($uri);
+ if (!$calendar instanceof ICalendar) {
+ throw new DAV\Exception\NotImplemented('The free-busy-query REPORT is only implemented on calendars');
+ }
+
+ $tzProp = '{'.self::NS_CALDAV.'}calendar-timezone';
+
+ // Figuring out the default timezone for the calendar, for floating
+ // times.
+ $calendarProps = $this->server->getProperties($uri, [$tzProp]);
+
+ if (isset($calendarProps[$tzProp])) {
+ $vtimezoneObj = VObject\Reader::read($calendarProps[$tzProp]);
+ $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
+ // Destroy circular references so PHP will garbage collect the object.
+ $vtimezoneObj->destroy();
+ } else {
+ $calendarTimeZone = new DateTimeZone('UTC');
+ }
+
+ // Doing a calendar-query first, to make sure we get the most
+ // performance.
+ $urls = $calendar->calendarQuery([
+ 'name' => 'VCALENDAR',
+ 'comp-filters' => [
+ [
+ 'name' => 'VEVENT',
+ 'comp-filters' => [],
+ 'prop-filters' => [],
+ 'is-not-defined' => false,
+ 'time-range' => [
+ 'start' => $report->start,
+ 'end' => $report->end,
+ ],
+ ],
+ ],
+ 'prop-filters' => [],
+ 'is-not-defined' => false,
+ 'time-range' => null,
+ ]);
+
+ $objects = array_map(function ($url) use ($calendar) {
+ $obj = $calendar->getChild($url)->get();
+
+ return $obj;
+ }, $urls);
+
+ $generator = new VObject\FreeBusyGenerator();
+ $generator->setObjects($objects);
+ $generator->setTimeRange($report->start, $report->end);
+ $generator->setTimeZone($calendarTimeZone);
+ $result = $generator->getResult();
+ $result = $result->serialize();
+
+ $this->server->httpResponse->setStatus(200);
+ $this->server->httpResponse->setHeader('Content-Type', 'text/calendar');
+ $this->server->httpResponse->setHeader('Content-Length', strlen($result));
+ $this->server->httpResponse->setBody($result);
+ }
+
+ /**
+ * This method is triggered before a file gets updated with new content.
+ *
+ * This plugin uses this method to ensure that CalDAV objects receive
+ * valid calendar data.
+ *
+ * @param string $path
+ * @param resource $data
+ * @param bool $modified should be set to true, if this event handler
+ * changed &$data
+ */
+ public function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified)
+ {
+ if (!$node instanceof ICalendarObject) {
+ return;
+ }
+
+ // We're onyl interested in ICalendarObject nodes that are inside of a
+ // real calendar. This is to avoid triggering validation and scheduling
+ // for non-calendars (such as an inbox).
+ list($parent) = Uri\split($path);
+ $parentNode = $this->server->tree->getNodeForPath($parent);
+
+ if (!$parentNode instanceof ICalendar) {
+ return;
+ }
+
+ $this->validateICalendar(
+ $data,
+ $path,
+ $modified,
+ $this->server->httpRequest,
+ $this->server->httpResponse,
+ false
+ );
+ }
+
+ /**
+ * This method is triggered before a new file is created.
+ *
+ * This plugin uses this method to ensure that newly created calendar
+ * objects contain valid calendar data.
+ *
+ * @param string $path
+ * @param resource $data
+ * @param bool $modified should be set to true, if this event handler
+ * changed &$data
+ */
+ public function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified)
+ {
+ if (!$parentNode instanceof ICalendar) {
+ return;
+ }
+
+ $this->validateICalendar(
+ $data,
+ $path,
+ $modified,
+ $this->server->httpRequest,
+ $this->server->httpResponse,
+ true
+ );
+ }
+
+ /**
+ * Checks if the submitted iCalendar data is in fact, valid.
+ *
+ * An exception is thrown if it's not.
+ *
+ * @param resource|string $data
+ * @param string $path
+ * @param bool $modified should be set to true, if this event handler
+ * changed &$data
+ * @param RequestInterface $request the http request
+ * @param ResponseInterface $response the http response
+ * @param bool $isNew is the item a new one, or an update
+ */
+ protected function validateICalendar(&$data, $path, &$modified, RequestInterface $request, ResponseInterface $response, $isNew)
+ {
+ // If it's a stream, we convert it to a string first.
+ if (is_resource($data)) {
+ $data = stream_get_contents($data);
+ }
+
+ $before = $data;
+
+ try {
+ // If the data starts with a [, we can reasonably assume we're dealing
+ // with a jCal object.
+ if ('[' === substr($data, 0, 1)) {
+ $vobj = VObject\Reader::readJson($data);
+
+ // Converting $data back to iCalendar, as that's what we
+ // technically support everywhere.
+ $data = $vobj->serialize();
+ $modified = true;
+ } else {
+ $vobj = VObject\Reader::read($data);
+ }
+ } catch (VObject\ParseException $e) {
+ throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid iCalendar 2.0 data. Parse error: '.$e->getMessage());
+ }
+
+ if ('VCALENDAR' !== $vobj->name) {
+ throw new DAV\Exception\UnsupportedMediaType('This collection can only support iCalendar objects.');
+ }
+
+ $sCCS = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
+
+ // Get the Supported Components for the target calendar
+ list($parentPath) = Uri\split($path);
+ $calendarProperties = $this->server->getProperties($parentPath, [$sCCS]);
+
+ if (isset($calendarProperties[$sCCS])) {
+ $supportedComponents = $calendarProperties[$sCCS]->getValue();
+ } else {
+ $supportedComponents = ['VJOURNAL', 'VTODO', 'VEVENT'];
+ }
+
+ $foundType = null;
+
+ foreach ($vobj->getComponents() as $component) {
+ switch ($component->name) {
+ case 'VTIMEZONE':
+ continue 2;
+ case 'VEVENT':
+ case 'VTODO':
+ case 'VJOURNAL':
+ $foundType = $component->name;
+ break;
+ }
+ }
+
+ if (!$foundType || !in_array($foundType, $supportedComponents)) {
+ throw new Exception\InvalidComponentType('iCalendar objects must at least have a component of type '.implode(', ', $supportedComponents));
+ }
+
+ $options = VObject\Node::PROFILE_CALDAV;
+ $prefer = $this->server->getHTTPPrefer();
+
+ if ('strict' !== $prefer['handling']) {
+ $options |= VObject\Node::REPAIR;
+ }
+
+ $messages = $vobj->validate($options);
+
+ $highestLevel = 0;
+ $warningMessage = null;
+
+ // $messages contains a list of problems with the vcard, along with
+ // their severity.
+ foreach ($messages as $message) {
+ if ($message['level'] > $highestLevel) {
+ // Recording the highest reported error level.
+ $highestLevel = $message['level'];
+ $warningMessage = $message['message'];
+ }
+ switch ($message['level']) {
+ case 1:
+ // Level 1 means that there was a problem, but it was repaired.
+ $modified = true;
+ break;
+ case 2:
+ // Level 2 means a warning, but not critical
+ break;
+ case 3:
+ // Level 3 means a critical error
+ throw new DAV\Exception\UnsupportedMediaType('Validation error in iCalendar: '.$message['message']);
+ }
+ }
+ if ($warningMessage) {
+ $response->setHeader(
+ 'X-Sabre-Ew-Gross',
+ 'iCalendar validation warning: '.$warningMessage
+ );
+ }
+
+ // We use an extra variable to allow event handles to tell us whether
+ // the object was modified or not.
+ //
+ // This helps us determine if we need to re-serialize the object.
+ $subModified = false;
+
+ $this->server->emit(
+ 'calendarObjectChange',
+ [
+ $request,
+ $response,
+ $vobj,
+ $parentPath,
+ &$subModified,
+ $isNew,
+ ]
+ );
+
+ if ($modified || $subModified) {
+ // An event handler told us that it modified the object.
+ $data = $vobj->serialize();
+
+ // Using md5 to figure out if there was an *actual* change.
+ if (!$modified && 0 !== strcmp($data, $before)) {
+ $modified = true;
+ }
+ }
+
+ // Destroy circular references so PHP will garbage collect the object.
+ $vobj->destroy();
+ }
+
+ /**
+ * This method is triggered whenever a subsystem reqeuests the privileges
+ * that are supported on a particular node.
+ */
+ public function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet)
+ {
+ if ($node instanceof ICalendar) {
+ $supportedPrivilegeSet['{DAV:}read']['aggregates']['{'.self::NS_CALDAV.'}read-free-busy'] = [
+ 'abstract' => false,
+ 'aggregates' => [],
+ ];
+ }
+ }
+
+ /**
+ * This method is used to generate HTML output for the
+ * DAV\Browser\Plugin. This allows us to generate an interface users
+ * can use to create new calendars.
+ *
+ * @param string $output
+ *
+ * @return bool
+ */
+ public function htmlActionsPanel(DAV\INode $node, &$output)
+ {
+ if (!$node instanceof CalendarHome) {
+ return;
+ }
+
+ $output .= '
+
';
+
+ return false;
+ }
+
+ /**
+ * This event is triggered after GET requests.
+ *
+ * This is used to transform data into jCal, if this was requested.
+ */
+ public function httpAfterGet(RequestInterface $request, ResponseInterface $response)
+ {
+ $contentType = $response->getHeader('Content-Type');
+ if (null === $contentType || false === strpos($contentType, 'text/calendar')) {
+ return;
+ }
+
+ $result = HTTP\negotiateContentType(
+ $request->getHeader('Accept'),
+ ['text/calendar', 'application/calendar+json']
+ );
+
+ if ('application/calendar+json' !== $result) {
+ // Do nothing
+ return;
+ }
+
+ // Transforming.
+ $vobj = VObject\Reader::read($response->getBody());
+
+ $jsonBody = json_encode($vobj->jsonSerialize());
+ $response->setBody($jsonBody);
+
+ // Destroy circular references so PHP will garbage collect the object.
+ $vobj->destroy();
+
+ $response->setHeader('Content-Type', 'application/calendar+json');
+ $response->setHeader('Content-Length', strlen($jsonBody));
+ }
+
+ /**
+ * 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 (rfc4791)',
+ 'link' => 'http://sabre.io/dav/caldav/',
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Principal/Collection.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Principal/Collection.php
new file mode 100644
index 000000000..6d0230331
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Principal/Collection.php
@@ -0,0 +1,32 @@
+principalBackend, $principalInfo);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Principal/IProxyRead.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Principal/IProxyRead.php
new file mode 100644
index 000000000..96e6991c5
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Principal/IProxyRead.php
@@ -0,0 +1,21 @@
+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();
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php
new file mode 100644
index 000000000..2d1ce7c46
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php
@@ -0,0 +1,161 @@
+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();
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Principal/User.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Principal/User.php
new file mode 100644
index 000000000..88bf4b4f7
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Principal/User.php
@@ -0,0 +1,136 @@
+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;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php
new file mode 100644
index 000000000..64a94becc
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php
@@ -0,0 +1,17 @@
+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/',
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php
new file mode 100644
index 000000000..384b503df
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php
@@ -0,0 +1,17 @@
+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;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php
new file mode 100644
index 000000000..1442c4cc6
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php
@@ -0,0 +1,119 @@
+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,
+ ],
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php
new file mode 100644
index 000000000..6f8f68432
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php
@@ -0,0 +1,999 @@
+server = $server;
+ $server->on('method:POST', [$this, 'httpPost']);
+ $server->on('propFind', [$this, 'propFind']);
+ $server->on('propPatch', [$this, 'propPatch']);
+ $server->on('calendarObjectChange', [$this, 'calendarObjectChange']);
+ $server->on('beforeUnbind', [$this, 'beforeUnbind']);
+ $server->on('schedule', [$this, 'scheduleLocalDelivery']);
+ $server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']);
+
+ $ns = '{'.self::NS_CALDAV.'}';
+
+ /*
+ * This information ensures that the {DAV:}resourcetype property has
+ * the correct values.
+ */
+ $server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IOutbox'] = $ns.'schedule-outbox';
+ $server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IInbox'] = $ns.'schedule-inbox';
+
+ /*
+ * Properties we protect are made read-only by the server.
+ */
+ array_push($server->protectedProperties,
+ $ns.'schedule-inbox-URL',
+ $ns.'schedule-outbox-URL',
+ $ns.'calendar-user-address-set',
+ $ns.'calendar-user-type',
+ $ns.'schedule-default-calendar-URL'
+ );
+ }
+
+ /**
+ * Use this method to tell the server this plugin defines additional
+ * HTTP methods.
+ *
+ * This method is passed a uri. It should only return HTTP methods that are
+ * available for the specified uri.
+ *
+ * @param string $uri
+ *
+ * @return array
+ */
+ public function getHTTPMethods($uri)
+ {
+ try {
+ $node = $this->server->tree->getNodeForPath($uri);
+ } catch (NotFound $e) {
+ return [];
+ }
+
+ if ($node instanceof IOutbox) {
+ return ['POST'];
+ }
+
+ return [];
+ }
+
+ /**
+ * This method handles POST request for the outbox.
+ *
+ * @return bool
+ */
+ public function httpPost(RequestInterface $request, ResponseInterface $response)
+ {
+ // Checking if this is a text/calendar content type
+ $contentType = $request->getHeader('Content-Type');
+ if (!$contentType || 0 !== strpos($contentType, 'text/calendar')) {
+ return;
+ }
+
+ $path = $request->getPath();
+
+ // Checking if we're talking to an outbox
+ try {
+ $node = $this->server->tree->getNodeForPath($path);
+ } catch (NotFound $e) {
+ return;
+ }
+ if (!$node instanceof IOutbox) {
+ return;
+ }
+
+ $this->server->transactionType = 'post-caldav-outbox';
+ $this->outboxRequest($node, $request, $response);
+
+ // Returning false breaks the event chain and tells the server we've
+ // handled the request.
+ return false;
+ }
+
+ /**
+ * This method handler is invoked during fetching of properties.
+ *
+ * We use this event to add calendar-auto-schedule-specific properties.
+ */
+ public function propFind(PropFind $propFind, INode $node)
+ {
+ if ($node instanceof DAVACL\IPrincipal) {
+ $caldavPlugin = $this->server->getPlugin('caldav');
+ $principalUrl = $node->getPrincipalUrl();
+
+ // schedule-outbox-URL property
+ $propFind->handle('{'.self::NS_CALDAV.'}schedule-outbox-URL', function () use ($principalUrl, $caldavPlugin) {
+ $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
+ if (!$calendarHomePath) {
+ return null;
+ }
+ $outboxPath = $calendarHomePath.'/outbox/';
+
+ return new LocalHref($outboxPath);
+ });
+ // schedule-inbox-URL property
+ $propFind->handle('{'.self::NS_CALDAV.'}schedule-inbox-URL', function () use ($principalUrl, $caldavPlugin) {
+ $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
+ if (!$calendarHomePath) {
+ return null;
+ }
+ $inboxPath = $calendarHomePath.'/inbox/';
+
+ return new LocalHref($inboxPath);
+ });
+
+ $propFind->handle('{'.self::NS_CALDAV.'}schedule-default-calendar-URL', function () use ($principalUrl, $caldavPlugin) {
+ // We don't support customizing this property yet, so in the
+ // meantime we just grab the first calendar in the home-set.
+ $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
+
+ if (!$calendarHomePath) {
+ return null;
+ }
+
+ $sccs = '{'.self::NS_CALDAV.'}supported-calendar-component-set';
+
+ $result = $this->server->getPropertiesForPath($calendarHomePath, [
+ '{DAV:}resourcetype',
+ '{DAV:}share-access',
+ $sccs,
+ ], 1);
+
+ foreach ($result as $child) {
+ if (!isset($child[200]['{DAV:}resourcetype']) || !$child[200]['{DAV:}resourcetype']->is('{'.self::NS_CALDAV.'}calendar')) {
+ // Node is either not a calendar
+ continue;
+ }
+ if (isset($child[200]['{DAV:}share-access'])) {
+ $shareAccess = $child[200]['{DAV:}share-access']->getValue();
+ if (Sharing\Plugin::ACCESS_NOTSHARED !== $shareAccess && Sharing\Plugin::ACCESS_SHAREDOWNER !== $shareAccess) {
+ // Node is a shared node, not owned by the relevant
+ // user.
+ continue;
+ }
+ }
+ if (!isset($child[200][$sccs]) || in_array('VEVENT', $child[200][$sccs]->getValue())) {
+ // Either there is no supported-calendar-component-set
+ // (which is fine) or we found one that supports VEVENT.
+ return new LocalHref($child['href']);
+ }
+ }
+ });
+
+ // The server currently reports every principal to be of type
+ // 'INDIVIDUAL'
+ $propFind->handle('{'.self::NS_CALDAV.'}calendar-user-type', function () {
+ return 'INDIVIDUAL';
+ });
+ }
+
+ // Mapping the old property to the new property.
+ $propFind->handle('{http://calendarserver.org/ns/}calendar-availability', function () use ($propFind, $node) {
+ // In case it wasn't clear, the only difference is that we map the
+ // old property to a different namespace.
+ $availProp = '{'.self::NS_CALDAV.'}calendar-availability';
+ $subPropFind = new PropFind(
+ $propFind->getPath(),
+ [$availProp]
+ );
+
+ $this->server->getPropertiesByNode(
+ $subPropFind,
+ $node
+ );
+
+ $propFind->set(
+ '{http://calendarserver.org/ns/}calendar-availability',
+ $subPropFind->get($availProp),
+ $subPropFind->getStatus($availProp)
+ );
+ });
+ }
+
+ /**
+ * This method is called during property updates.
+ *
+ * @param string $path
+ */
+ public function propPatch($path, PropPatch $propPatch)
+ {
+ // Mapping the old property to the new property.
+ $propPatch->handle('{http://calendarserver.org/ns/}calendar-availability', function ($value) use ($path) {
+ $availProp = '{'.self::NS_CALDAV.'}calendar-availability';
+ $subPropPatch = new PropPatch([$availProp => $value]);
+ $this->server->emit('propPatch', [$path, $subPropPatch]);
+ $subPropPatch->commit();
+
+ return $subPropPatch->getResult()[$availProp];
+ });
+ }
+
+ /**
+ * This method is triggered whenever there was a calendar object gets
+ * created or updated.
+ *
+ * @param RequestInterface $request HTTP request
+ * @param ResponseInterface $response HTTP Response
+ * @param VCalendar $vCal Parsed iCalendar object
+ * @param mixed $calendarPath Path to calendar collection
+ * @param mixed $modified the iCalendar object has been touched
+ * @param mixed $isNew Whether this was a new item or we're updating one
+ */
+ public function calendarObjectChange(RequestInterface $request, ResponseInterface $response, VCalendar $vCal, $calendarPath, &$modified, $isNew)
+ {
+ if (!$this->scheduleReply($this->server->httpRequest)) {
+ return;
+ }
+
+ $calendarNode = $this->server->tree->getNodeForPath($calendarPath);
+
+ $addresses = $this->getAddressesForPrincipal(
+ $calendarNode->getOwner()
+ );
+
+ if (!$isNew) {
+ $node = $this->server->tree->getNodeForPath($request->getPath());
+ $oldObj = Reader::read($node->get());
+ } else {
+ $oldObj = null;
+ }
+
+ $this->processICalendarChange($oldObj, $vCal, $addresses, [], $modified);
+
+ if ($oldObj) {
+ // Destroy circular references so PHP will GC the object.
+ $oldObj->destroy();
+ }
+ }
+
+ /**
+ * This method is responsible for delivering the ITip message.
+ */
+ public function deliver(ITip\Message $iTipMessage)
+ {
+ $this->server->emit('schedule', [$iTipMessage]);
+ if (!$iTipMessage->scheduleStatus) {
+ $iTipMessage->scheduleStatus = '5.2;There was no system capable of delivering the scheduling message';
+ }
+ // In case the change was considered 'insignificant', we are going to
+ // remove any error statuses, if any. See ticket #525.
+ list($baseCode) = explode('.', $iTipMessage->scheduleStatus);
+ if (!$iTipMessage->significantChange && in_array($baseCode, ['3', '5'])) {
+ $iTipMessage->scheduleStatus = null;
+ }
+ }
+
+ /**
+ * This method is triggered before a file gets deleted.
+ *
+ * We use this event to make sure that when this happens, attendees get
+ * cancellations, and organizers get 'DECLINED' statuses.
+ *
+ * @param string $path
+ */
+ public function beforeUnbind($path)
+ {
+ // FIXME: We shouldn't trigger this functionality when we're issuing a
+ // MOVE. This is a hack.
+ if ('MOVE' === $this->server->httpRequest->getMethod()) {
+ return;
+ }
+
+ $node = $this->server->tree->getNodeForPath($path);
+
+ if (!$node instanceof ICalendarObject || $node instanceof ISchedulingObject) {
+ return;
+ }
+
+ if (!$this->scheduleReply($this->server->httpRequest)) {
+ return;
+ }
+
+ $addresses = $this->getAddressesForPrincipal(
+ $node->getOwner()
+ );
+
+ $broker = new ITip\Broker();
+ $messages = $broker->parseEvent(null, $addresses, $node->get());
+
+ foreach ($messages as $message) {
+ $this->deliver($message);
+ }
+ }
+
+ /**
+ * Event handler for the 'schedule' event.
+ *
+ * This handler attempts to look at local accounts to deliver the
+ * scheduling object.
+ */
+ public function scheduleLocalDelivery(ITip\Message $iTipMessage)
+ {
+ $aclPlugin = $this->server->getPlugin('acl');
+
+ // Local delivery is not available if the ACL plugin is not loaded.
+ if (!$aclPlugin) {
+ return;
+ }
+
+ $caldavNS = '{'.self::NS_CALDAV.'}';
+
+ $principalUri = $aclPlugin->getPrincipalByUri($iTipMessage->recipient);
+ if (!$principalUri) {
+ $iTipMessage->scheduleStatus = '3.7;Could not find principal.';
+
+ return;
+ }
+
+ // We found a principal URL, now we need to find its inbox.
+ // Unfortunately we may not have sufficient privileges to find this, so
+ // we are temporarily turning off ACL to let this come through.
+ //
+ // Once we support PHP 5.5, this should be wrapped in a try..finally
+ // block so we can ensure that this privilege gets added again after.
+ $this->server->removeListener('propFind', [$aclPlugin, 'propFind']);
+
+ $result = $this->server->getProperties(
+ $principalUri,
+ [
+ '{DAV:}principal-URL',
+ $caldavNS.'calendar-home-set',
+ $caldavNS.'schedule-inbox-URL',
+ $caldavNS.'schedule-default-calendar-URL',
+ '{http://sabredav.org/ns}email-address',
+ ]
+ );
+
+ // Re-registering the ACL event
+ $this->server->on('propFind', [$aclPlugin, 'propFind'], 20);
+
+ if (!isset($result[$caldavNS.'schedule-inbox-URL'])) {
+ $iTipMessage->scheduleStatus = '5.2;Could not find local inbox';
+
+ return;
+ }
+ if (!isset($result[$caldavNS.'calendar-home-set'])) {
+ $iTipMessage->scheduleStatus = '5.2;Could not locate a calendar-home-set';
+
+ return;
+ }
+ if (!isset($result[$caldavNS.'schedule-default-calendar-URL'])) {
+ $iTipMessage->scheduleStatus = '5.2;Could not find a schedule-default-calendar-URL property';
+
+ return;
+ }
+
+ $calendarPath = $result[$caldavNS.'schedule-default-calendar-URL']->getHref();
+ $homePath = $result[$caldavNS.'calendar-home-set']->getHref();
+ $inboxPath = $result[$caldavNS.'schedule-inbox-URL']->getHref();
+
+ if ('REPLY' === $iTipMessage->method) {
+ $privilege = 'schedule-deliver-reply';
+ } else {
+ $privilege = 'schedule-deliver-invite';
+ }
+
+ if (!$aclPlugin->checkPrivileges($inboxPath, $caldavNS.$privilege, DAVACL\Plugin::R_PARENT, false)) {
+ $iTipMessage->scheduleStatus = '3.8;insufficient privileges: '.$privilege.' is required on the recipient schedule inbox.';
+
+ return;
+ }
+
+ // Next, we're going to find out if the item already exits in one of
+ // the users' calendars.
+ $uid = $iTipMessage->uid;
+
+ $newFileName = 'sabredav-'.\Sabre\DAV\UUIDUtil::getUUID().'.ics';
+
+ $home = $this->server->tree->getNodeForPath($homePath);
+ $inbox = $this->server->tree->getNodeForPath($inboxPath);
+
+ $currentObject = null;
+ $objectNode = null;
+ $isNewNode = false;
+
+ $result = $home->getCalendarObjectByUID($uid);
+ if ($result) {
+ // There was an existing object, we need to update probably.
+ $objectPath = $homePath.'/'.$result;
+ $objectNode = $this->server->tree->getNodeForPath($objectPath);
+ $oldICalendarData = $objectNode->get();
+ $currentObject = Reader::read($oldICalendarData);
+ } else {
+ $isNewNode = true;
+ }
+
+ $broker = new ITip\Broker();
+ $newObject = $broker->processMessage($iTipMessage, $currentObject);
+
+ $inbox->createFile($newFileName, $iTipMessage->message->serialize());
+
+ if (!$newObject) {
+ // We received an iTip message referring to a UID that we don't
+ // have in any calendars yet, and processMessage did not give us a
+ // calendarobject back.
+ //
+ // The implication is that processMessage did not understand the
+ // iTip message.
+ $iTipMessage->scheduleStatus = '5.0;iTip message was not processed by the server, likely because we didn\'t understand it.';
+
+ return;
+ }
+
+ // Note that we are bypassing ACL on purpose by calling this directly.
+ // We may need to look a bit deeper into this later. Supporting ACL
+ // here would be nice.
+ if ($isNewNode) {
+ $calendar = $this->server->tree->getNodeForPath($calendarPath);
+ $calendar->createFile($newFileName, $newObject->serialize());
+ } else {
+ // If the message was a reply, we may have to inform other
+ // attendees of this attendees status. Therefore we're shooting off
+ // another itipMessage.
+ if ('REPLY' === $iTipMessage->method) {
+ $this->processICalendarChange(
+ $oldICalendarData,
+ $newObject,
+ [$iTipMessage->recipient],
+ [$iTipMessage->sender]
+ );
+ }
+ $objectNode->put($newObject->serialize());
+ }
+ $iTipMessage->scheduleStatus = '1.2;Message delivered locally';
+ }
+
+ /**
+ * This method is triggered whenever a subsystem requests the privileges
+ * that are supported on a particular node.
+ *
+ * We need to add a number of privileges for scheduling purposes.
+ */
+ public function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet)
+ {
+ $ns = '{'.self::NS_CALDAV.'}';
+ if ($node instanceof IOutbox) {
+ $supportedPrivilegeSet[$ns.'schedule-send'] = [
+ 'abstract' => false,
+ 'aggregates' => [
+ $ns.'schedule-send-invite' => [
+ 'abstract' => false,
+ 'aggregates' => [],
+ ],
+ $ns.'schedule-send-reply' => [
+ 'abstract' => false,
+ 'aggregates' => [],
+ ],
+ $ns.'schedule-send-freebusy' => [
+ 'abstract' => false,
+ 'aggregates' => [],
+ ],
+ // Privilege from an earlier scheduling draft, but still
+ // used by some clients.
+ $ns.'schedule-post-vevent' => [
+ 'abstract' => false,
+ 'aggregates' => [],
+ ],
+ ],
+ ];
+ }
+ if ($node instanceof IInbox) {
+ $supportedPrivilegeSet[$ns.'schedule-deliver'] = [
+ 'abstract' => false,
+ 'aggregates' => [
+ $ns.'schedule-deliver-invite' => [
+ 'abstract' => false,
+ 'aggregates' => [],
+ ],
+ $ns.'schedule-deliver-reply' => [
+ 'abstract' => false,
+ 'aggregates' => [],
+ ],
+ $ns.'schedule-query-freebusy' => [
+ 'abstract' => false,
+ 'aggregates' => [],
+ ],
+ ],
+ ];
+ }
+ }
+
+ /**
+ * This method looks at an old iCalendar object, a new iCalendar object and
+ * starts sending scheduling messages based on the changes.
+ *
+ * A list of addresses needs to be specified, so the system knows who made
+ * the update, because the behavior may be different based on if it's an
+ * attendee or an organizer.
+ *
+ * This method may update $newObject to add any status changes.
+ *
+ * @param VCalendar|string $oldObject
+ * @param array $ignore any addresses to not send messages to
+ * @param bool $modified a marker to indicate that the original object
+ * modified by this process
+ */
+ protected function processICalendarChange($oldObject = null, VCalendar $newObject, array $addresses, array $ignore = [], &$modified = false)
+ {
+ $broker = new ITip\Broker();
+ $messages = $broker->parseEvent($newObject, $addresses, $oldObject);
+
+ if ($messages) {
+ $modified = true;
+ }
+
+ foreach ($messages as $message) {
+ if (in_array($message->recipient, $ignore)) {
+ continue;
+ }
+
+ $this->deliver($message);
+
+ if (isset($newObject->VEVENT->ORGANIZER) && ($newObject->VEVENT->ORGANIZER->getNormalizedValue() === $message->recipient)) {
+ if ($message->scheduleStatus) {
+ $newObject->VEVENT->ORGANIZER['SCHEDULE-STATUS'] = $message->getScheduleStatus();
+ }
+ unset($newObject->VEVENT->ORGANIZER['SCHEDULE-FORCE-SEND']);
+ } else {
+ if (isset($newObject->VEVENT->ATTENDEE)) {
+ foreach ($newObject->VEVENT->ATTENDEE as $attendee) {
+ if ($attendee->getNormalizedValue() === $message->recipient) {
+ if ($message->scheduleStatus) {
+ $attendee['SCHEDULE-STATUS'] = $message->getScheduleStatus();
+ }
+ unset($attendee['SCHEDULE-FORCE-SEND']);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a list of addresses that are associated with a principal.
+ *
+ * @param string $principal
+ *
+ * @return array
+ */
+ protected function getAddressesForPrincipal($principal)
+ {
+ $CUAS = '{'.self::NS_CALDAV.'}calendar-user-address-set';
+
+ $properties = $this->server->getProperties(
+ $principal,
+ [$CUAS]
+ );
+
+ // If we can't find this information, we'll stop processing
+ if (!isset($properties[$CUAS])) {
+ return [];
+ }
+
+ $addresses = $properties[$CUAS]->getHrefs();
+
+ return $addresses;
+ }
+
+ /**
+ * This method handles POST requests to the schedule-outbox.
+ *
+ * Currently, two types of requests are supported:
+ * * FREEBUSY requests from RFC 6638
+ * * Simple iTIP messages from draft-desruisseaux-caldav-sched-04
+ *
+ * The latter is from an expired early draft of the CalDAV scheduling
+ * extensions, but iCal depends on a feature from that spec, so we
+ * implement it.
+ */
+ public function outboxRequest(IOutbox $outboxNode, RequestInterface $request, ResponseInterface $response)
+ {
+ $outboxPath = $request->getPath();
+
+ // Parsing the request body
+ try {
+ $vObject = VObject\Reader::read($request->getBody());
+ } catch (VObject\ParseException $e) {
+ throw new BadRequest('The request body must be a valid iCalendar object. Parse error: '.$e->getMessage());
+ }
+
+ // The incoming iCalendar object must have a METHOD property, and a
+ // component. The combination of both determines what type of request
+ // this is.
+ $componentType = null;
+ foreach ($vObject->getComponents() as $component) {
+ if ('VTIMEZONE' !== $component->name) {
+ $componentType = $component->name;
+ break;
+ }
+ }
+ if (is_null($componentType)) {
+ throw new BadRequest('We expected at least one VTODO, VJOURNAL, VFREEBUSY or VEVENT component');
+ }
+
+ // Validating the METHOD
+ $method = strtoupper((string) $vObject->METHOD);
+ if (!$method) {
+ throw new BadRequest('A METHOD property must be specified in iTIP messages');
+ }
+
+ // So we support one type of request:
+ //
+ // REQUEST with a VFREEBUSY component
+
+ $acl = $this->server->getPlugin('acl');
+
+ if ('VFREEBUSY' === $componentType && 'REQUEST' === $method) {
+ $acl && $acl->checkPrivileges($outboxPath, '{'.self::NS_CALDAV.'}schedule-send-freebusy');
+ $this->handleFreeBusyRequest($outboxNode, $vObject, $request, $response);
+
+ // Destroy circular references so PHP can GC the object.
+ $vObject->destroy();
+ unset($vObject);
+ } else {
+ throw new NotImplemented('We only support VFREEBUSY (REQUEST) on this endpoint');
+ }
+ }
+
+ /**
+ * This method is responsible for parsing a free-busy query request and
+ * returning it's result.
+ *
+ * @return string
+ */
+ protected function handleFreeBusyRequest(IOutbox $outbox, VObject\Component $vObject, RequestInterface $request, ResponseInterface $response)
+ {
+ $vFreeBusy = $vObject->VFREEBUSY;
+ $organizer = $vFreeBusy->ORGANIZER;
+
+ $organizer = (string) $organizer;
+
+ // Validating if the organizer matches the owner of the inbox.
+ $owner = $outbox->getOwner();
+
+ $caldavNS = '{'.self::NS_CALDAV.'}';
+
+ $uas = $caldavNS.'calendar-user-address-set';
+ $props = $this->server->getProperties($owner, [$uas]);
+
+ if (empty($props[$uas]) || !in_array($organizer, $props[$uas]->getHrefs())) {
+ throw new Forbidden('The organizer in the request did not match any of the addresses for the owner of this inbox');
+ }
+
+ if (!isset($vFreeBusy->ATTENDEE)) {
+ throw new BadRequest('You must at least specify 1 attendee');
+ }
+
+ $attendees = [];
+ foreach ($vFreeBusy->ATTENDEE as $attendee) {
+ $attendees[] = (string) $attendee;
+ }
+
+ if (!isset($vFreeBusy->DTSTART) || !isset($vFreeBusy->DTEND)) {
+ throw new BadRequest('DTSTART and DTEND must both be specified');
+ }
+
+ $startRange = $vFreeBusy->DTSTART->getDateTime();
+ $endRange = $vFreeBusy->DTEND->getDateTime();
+
+ $results = [];
+ foreach ($attendees as $attendee) {
+ $results[] = $this->getFreeBusyForEmail($attendee, $startRange, $endRange, $vObject);
+ }
+
+ $dom = new \DOMDocument('1.0', 'utf-8');
+ $dom->formatOutput = true;
+ $scheduleResponse = $dom->createElement('cal:schedule-response');
+ foreach ($this->server->xml->namespaceMap as $namespace => $prefix) {
+ $scheduleResponse->setAttribute('xmlns:'.$prefix, $namespace);
+ }
+ $dom->appendChild($scheduleResponse);
+
+ foreach ($results as $result) {
+ $xresponse = $dom->createElement('cal:response');
+
+ $recipient = $dom->createElement('cal:recipient');
+ $recipientHref = $dom->createElement('d:href');
+
+ $recipientHref->appendChild($dom->createTextNode($result['href']));
+ $recipient->appendChild($recipientHref);
+ $xresponse->appendChild($recipient);
+
+ $reqStatus = $dom->createElement('cal:request-status');
+ $reqStatus->appendChild($dom->createTextNode($result['request-status']));
+ $xresponse->appendChild($reqStatus);
+
+ if (isset($result['calendar-data'])) {
+ $calendardata = $dom->createElement('cal:calendar-data');
+ $calendardata->appendChild($dom->createTextNode(str_replace("\r\n", "\n", $result['calendar-data']->serialize())));
+ $xresponse->appendChild($calendardata);
+ }
+ $scheduleResponse->appendChild($xresponse);
+ }
+
+ $response->setStatus(200);
+ $response->setHeader('Content-Type', 'application/xml');
+ $response->setBody($dom->saveXML());
+ }
+
+ /**
+ * Returns free-busy information for a specific address. The returned
+ * data is an array containing the following properties:.
+ *
+ * calendar-data : A VFREEBUSY VObject
+ * request-status : an iTip status code.
+ * href: The principal's email address, as requested
+ *
+ * The following request status codes may be returned:
+ * * 2.0;description
+ * * 3.7;description
+ *
+ * @param string $email address
+ *
+ * @return array
+ */
+ protected function getFreeBusyForEmail($email, \DateTimeInterface $start, \DateTimeInterface $end, VObject\Component $request)
+ {
+ $caldavNS = '{'.self::NS_CALDAV.'}';
+
+ $aclPlugin = $this->server->getPlugin('acl');
+ if ('mailto:' === substr($email, 0, 7)) {
+ $email = substr($email, 7);
+ }
+
+ $result = $aclPlugin->principalSearch(
+ ['{http://sabredav.org/ns}email-address' => $email],
+ [
+ '{DAV:}principal-URL',
+ $caldavNS.'calendar-home-set',
+ $caldavNS.'schedule-inbox-URL',
+ '{http://sabredav.org/ns}email-address',
+ ]
+ );
+
+ if (!count($result)) {
+ return [
+ 'request-status' => '3.7;Could not find principal',
+ 'href' => 'mailto:'.$email,
+ ];
+ }
+
+ if (!isset($result[0][200][$caldavNS.'calendar-home-set'])) {
+ return [
+ 'request-status' => '3.7;No calendar-home-set property found',
+ 'href' => 'mailto:'.$email,
+ ];
+ }
+ if (!isset($result[0][200][$caldavNS.'schedule-inbox-URL'])) {
+ return [
+ 'request-status' => '3.7;No schedule-inbox-URL property found',
+ 'href' => 'mailto:'.$email,
+ ];
+ }
+ $homeSet = $result[0][200][$caldavNS.'calendar-home-set']->getHref();
+ $inboxUrl = $result[0][200][$caldavNS.'schedule-inbox-URL']->getHref();
+
+ // Do we have permission?
+ $aclPlugin->checkPrivileges($inboxUrl, $caldavNS.'schedule-query-freebusy');
+
+ // Grabbing the calendar list
+ $objects = [];
+ $calendarTimeZone = new DateTimeZone('UTC');
+
+ foreach ($this->server->tree->getNodeForPath($homeSet)->getChildren() as $node) {
+ if (!$node instanceof ICalendar) {
+ continue;
+ }
+
+ $sct = $caldavNS.'schedule-calendar-transp';
+ $ctz = $caldavNS.'calendar-timezone';
+ $props = $node->getProperties([$sct, $ctz]);
+
+ if (isset($props[$sct]) && ScheduleCalendarTransp::TRANSPARENT == $props[$sct]->getValue()) {
+ // If a calendar is marked as 'transparent', it means we must
+ // ignore it for free-busy purposes.
+ continue;
+ }
+
+ if (isset($props[$ctz])) {
+ $vtimezoneObj = VObject\Reader::read($props[$ctz]);
+ $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
+
+ // Destroy circular references so PHP can garbage collect the object.
+ $vtimezoneObj->destroy();
+ }
+
+ // Getting the list of object uris within the time-range
+ $urls = $node->calendarQuery([
+ 'name' => 'VCALENDAR',
+ 'comp-filters' => [
+ [
+ 'name' => 'VEVENT',
+ 'comp-filters' => [],
+ 'prop-filters' => [],
+ 'is-not-defined' => false,
+ 'time-range' => [
+ 'start' => $start,
+ 'end' => $end,
+ ],
+ ],
+ ],
+ 'prop-filters' => [],
+ 'is-not-defined' => false,
+ 'time-range' => null,
+ ]);
+
+ $calObjects = array_map(function ($url) use ($node) {
+ $obj = $node->getChild($url)->get();
+
+ return $obj;
+ }, $urls);
+
+ $objects = array_merge($objects, $calObjects);
+ }
+
+ $inboxProps = $this->server->getProperties(
+ $inboxUrl,
+ $caldavNS.'calendar-availability'
+ );
+
+ $vcalendar = new VObject\Component\VCalendar();
+ $vcalendar->METHOD = 'REPLY';
+
+ $generator = new VObject\FreeBusyGenerator();
+ $generator->setObjects($objects);
+ $generator->setTimeRange($start, $end);
+ $generator->setBaseObject($vcalendar);
+ $generator->setTimeZone($calendarTimeZone);
+
+ if ($inboxProps) {
+ $generator->setVAvailability(
+ VObject\Reader::read(
+ $inboxProps[$caldavNS.'calendar-availability']
+ )
+ );
+ }
+
+ $result = $generator->getResult();
+
+ $vcalendar->VFREEBUSY->ATTENDEE = 'mailto:'.$email;
+ $vcalendar->VFREEBUSY->UID = (string) $request->VFREEBUSY->UID;
+ $vcalendar->VFREEBUSY->ORGANIZER = clone $request->VFREEBUSY->ORGANIZER;
+
+ return [
+ 'calendar-data' => $result,
+ 'request-status' => '2.0;Success',
+ 'href' => 'mailto:'.$email,
+ ];
+ }
+
+ /**
+ * This method checks the 'Schedule-Reply' header
+ * and returns false if it's 'F', otherwise true.
+ *
+ * @return bool
+ */
+ private function scheduleReply(RequestInterface $request)
+ {
+ $scheduleReply = $request->getHeader('Schedule-Reply');
+
+ return 'F' !== $scheduleReply;
+ }
+
+ /**
+ * 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 calendar-auto-schedule, as defined in rfc6638',
+ 'link' => 'http://sabre.io/dav/scheduling/',
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php
new file mode 100644
index 000000000..b40f28a94
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php
@@ -0,0 +1,130 @@
+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,
+ ],
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/SharedCalendar.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/SharedCalendar.php
new file mode 100644
index 000000000..818392f57
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/SharedCalendar.php
@@ -0,0 +1,219 @@
+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;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/SharingPlugin.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/SharingPlugin.php
new file mode 100644
index 000000000..090cc34bf
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/SharingPlugin.php
@@ -0,0 +1,350 @@
+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 trigged when a user attempts to update a node's
+ * properties.
+ *
+ * A previous draft of the sharing spec stated that it was possible to use
+ * PROPPATCH to remove 'shared-owner' from the resourcetype, thus unsharing
+ * the calendar.
+ *
+ * Even though this is no longer in the current spec, we keep this around
+ * because OS X 10.7 may still make use of this feature.
+ *
+ * @param string $path
+ */
+ 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/',
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php
new file mode 100644
index 000000000..e83082c52
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php
@@ -0,0 +1,41 @@
+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,
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php
new file mode 100644
index 000000000..3be1b609e
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php
@@ -0,0 +1,204 @@
+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
+ */
+ 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,
+ ],
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php
new file mode 100644
index 000000000..baa4250ab
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php
@@ -0,0 +1,81 @@
+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;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php
new file mode 100644
index 000000000..929000bb8
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php
@@ -0,0 +1,94 @@
+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;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php
new file mode 100644
index 000000000..1e6dd5946
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php
@@ -0,0 +1,79 @@
+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;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php
new file mode 100644
index 000000000..c9a3cb5ac
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php
@@ -0,0 +1,95 @@
+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' => 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.'}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;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php
new file mode 100644
index 000000000..2dbb0f498
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php
@@ -0,0 +1,290 @@
+ $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;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php
new file mode 100644
index 000000000..dbdba3b02
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php
@@ -0,0 +1,199 @@
+ $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;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php
new file mode 100644
index 000000000..e1b393f8b
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php
@@ -0,0 +1,43 @@
+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;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php
new file mode 100644
index 000000000..58acb6d54
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php
@@ -0,0 +1,81 @@
+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');
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php
new file mode 100644
index 000000000..84f7ae02f
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php
@@ -0,0 +1,71 @@
+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);
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php
new file mode 100644
index 000000000..c389ca827
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php
@@ -0,0 +1,120 @@
+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
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php
new file mode 100644
index 000000000..159522025
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php
@@ -0,0 +1,124 @@
+value = $value;
+ }
+
+ /**
+ * Returns the current value.
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * The xmlSerialize method is called during xml writing.
+ *
+ * Use the $writer argument to write its own xml serialization.
+ *
+ * An important note: do _not_ create a parent element. Any element
+ * implementing 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)
+ {
+ switch ($this->value) {
+ case self::TRANSPARENT:
+ $writer->writeElement('{'.Plugin::NS_CALDAV.'}transparent');
+ break;
+ case self::OPAQUE:
+ $writer->writeElement('{'.Plugin::NS_CALDAV.'}opaque');
+ break;
+ }
+ }
+
+ /**
+ * 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)
+ {
+ $elems = Deserializer\enum($reader, Plugin::NS_CALDAV);
+
+ if (in_array('transparent', $elems)) {
+ $value = self::TRANSPARENT;
+ } else {
+ $value = self::OPAQUE;
+ }
+
+ return new self($value);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php
new file mode 100644
index 000000000..d86e7b77c
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php
@@ -0,0 +1,118 @@
+components = $components;
+ }
+
+ /**
+ * Returns the list of supported components.
+ *
+ * @return array
+ */
+ public function getValue()
+ {
+ return $this->components;
+ }
+
+ /**
+ * 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->components as $component) {
+ $writer->startElement('{'.Plugin::NS_CALDAV.'}comp');
+ $writer->writeAttributes(['name' => $component]);
+ $writer->endElement();
+ }
+ }
+
+ /**
+ * 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)
+ {
+ $elems = $reader->parseInnerTree();
+
+ $components = [];
+
+ foreach ((array) $elems as $elem) {
+ if ($elem['name'] === '{'.Plugin::NS_CALDAV.'}comp') {
+ $components[] = $elem['attributes']['name'];
+ }
+ }
+
+ if (!$components) {
+ throw new ParseException('supported-calendar-component-set must have at least one CALDAV:comp element');
+ }
+
+ return new self($components);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php
new file mode 100644
index 000000000..5b0893305
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php
@@ -0,0 +1,57 @@
+startElement('{'.Plugin::NS_CALDAV.'}calendar-data');
+ $writer->writeAttributes([
+ 'content-type' => 'text/calendar',
+ 'version' => '2.0',
+ ]);
+ $writer->endElement(); // calendar-data
+ $writer->startElement('{'.Plugin::NS_CALDAV.'}calendar-data');
+ $writer->writeAttributes([
+ 'content-type' => 'application/calendar+json',
+ ]);
+ $writer->endElement(); // calendar-data
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php
new file mode 100644
index 000000000..c5ffeee38
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php
@@ -0,0 +1,54 @@
+writeElement('{'.Plugin::NS_CALDAV.'}supported-collation', $collation);
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php
new file mode 100644
index 000000000..3b3a94b26
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php
@@ -0,0 +1,119 @@
+next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @return mixed
+ */
+ public static function xmlDeserialize(Reader $reader)
+ {
+ $elems = $reader->parseInnerTree([
+ '{urn:ietf:params:xml:ns:caldav}calendar-data' => 'Sabre\\CalDAV\\Xml\\Filter\\CalendarData',
+ '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue',
+ ]);
+
+ $newProps = [
+ 'hrefs' => [],
+ 'properties' => [],
+ ];
+
+ foreach ($elems as $elem) {
+ switch ($elem['name']) {
+ case '{DAV:}prop':
+ $newProps['properties'] = array_keys($elem['value']);
+ if (isset($elem['value']['{'.Plugin::NS_CALDAV.'}calendar-data'])) {
+ $newProps += $elem['value']['{'.Plugin::NS_CALDAV.'}calendar-data'];
+ }
+ break;
+ case '{DAV:}href':
+ $newProps['hrefs'][] = Uri\resolve($reader->contextUri, $elem['value']);
+ break;
+ }
+ }
+
+ $obj = new self();
+ foreach ($newProps as $key => $value) {
+ $obj->$key = $value;
+ }
+
+ return $obj;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php
new file mode 100644
index 000000000..b3cc299d3
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php
@@ -0,0 +1,137 @@
+next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @return mixed
+ */
+ public static function xmlDeserialize(Reader $reader)
+ {
+ $elems = $reader->parseInnerTree([
+ '{urn:ietf:params:xml:ns:caldav}comp-filter' => 'Sabre\\CalDAV\\Xml\\Filter\\CompFilter',
+ '{urn:ietf:params:xml:ns:caldav}prop-filter' => 'Sabre\\CalDAV\\Xml\\Filter\\PropFilter',
+ '{urn:ietf:params:xml:ns:caldav}param-filter' => 'Sabre\\CalDAV\\Xml\\Filter\\ParamFilter',
+ '{urn:ietf:params:xml:ns:caldav}calendar-data' => 'Sabre\\CalDAV\\Xml\\Filter\\CalendarData',
+ '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue',
+ ]);
+
+ $newProps = [
+ 'filters' => null,
+ 'properties' => [],
+ ];
+
+ if (!is_array($elems)) {
+ $elems = [];
+ }
+
+ foreach ($elems as $elem) {
+ switch ($elem['name']) {
+ case '{DAV:}prop':
+ $newProps['properties'] = array_keys($elem['value']);
+ if (isset($elem['value']['{'.Plugin::NS_CALDAV.'}calendar-data'])) {
+ $newProps += $elem['value']['{'.Plugin::NS_CALDAV.'}calendar-data'];
+ }
+ break;
+ case '{'.Plugin::NS_CALDAV.'}filter':
+ foreach ($elem['value'] as $subElem) {
+ if ($subElem['name'] === '{'.Plugin::NS_CALDAV.'}comp-filter') {
+ if (!is_null($newProps['filters'])) {
+ throw new BadRequest('Only one top-level comp-filter may be defined');
+ }
+ $newProps['filters'] = $subElem['value'];
+ }
+ }
+ break;
+ }
+ }
+
+ if (is_null($newProps['filters'])) {
+ throw new BadRequest('The {'.Plugin::NS_CALDAV.'}filter element is required for this request');
+ }
+
+ $obj = new self();
+ foreach ($newProps as $key => $value) {
+ $obj->$key = $value;
+ }
+
+ return $obj;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php
new file mode 100644
index 000000000..17df05a78
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php
@@ -0,0 +1,90 @@
+next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @return mixed
+ */
+ public static function xmlDeserialize(Reader $reader)
+ {
+ $timeRange = '{'.Plugin::NS_CALDAV.'}time-range';
+
+ $start = null;
+ $end = null;
+
+ foreach ((array) $reader->parseInnerTree([]) as $elem) {
+ if ($elem['name'] !== $timeRange) {
+ continue;
+ }
+
+ $start = empty($elem['attributes']['start']) ?: $elem['attributes']['start'];
+ $end = empty($elem['attributes']['end']) ?: $elem['attributes']['end'];
+ }
+ if (!$start && !$end) {
+ throw new BadRequest('The freebusy report must have a time-range element');
+ }
+ if ($start) {
+ $start = DateTimeParser::parseDateTime($start);
+ }
+ if ($end) {
+ $end = DateTimeParser::parseDateTime($end);
+ }
+ $result = new self();
+ $result->start = $start;
+ $result->end = $end;
+
+ return $result;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php
new file mode 100644
index 000000000..166721eb3
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php
@@ -0,0 +1,145 @@
+href = $href;
+ $this->calendarUri = $calendarUri;
+ $this->inReplyTo = $inReplyTo;
+ $this->summary = $summary;
+ $this->status = $status;
+ }
+
+ /**
+ * 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)
+ {
+ $elems = KeyValue::xmlDeserialize($reader);
+
+ $href = null;
+ $calendarUri = null;
+ $inReplyTo = null;
+ $summary = null;
+ $status = null;
+
+ foreach ($elems as $name => $value) {
+ switch ($name) {
+ case '{'.Plugin::NS_CALENDARSERVER.'}hosturl':
+ foreach ($value as $bla) {
+ if ('{DAV:}href' === $bla['name']) {
+ $calendarUri = $bla['value'];
+ }
+ }
+ break;
+ case '{'.Plugin::NS_CALENDARSERVER.'}invite-accepted':
+ $status = DAV\Sharing\Plugin::INVITE_ACCEPTED;
+ break;
+ case '{'.Plugin::NS_CALENDARSERVER.'}invite-declined':
+ $status = DAV\Sharing\Plugin::INVITE_DECLINED;
+ break;
+ case '{'.Plugin::NS_CALENDARSERVER.'}in-reply-to':
+ $inReplyTo = $value;
+ break;
+ case '{'.Plugin::NS_CALENDARSERVER.'}summary':
+ $summary = $value;
+ break;
+ case '{DAV:}href':
+ $href = $value;
+ break;
+ }
+ }
+ if (is_null($calendarUri)) {
+ throw new BadRequest('The {http://calendarserver.org/ns/}hosturl/{DAV:}href element must exist');
+ }
+
+ return new self($href, $calendarUri, $inReplyTo, $summary, $status);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php
new file mode 100644
index 000000000..b5701e2ea
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php
@@ -0,0 +1,77 @@
+properties;
+ }
+
+ /**
+ * 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)
+ {
+ $self = new self();
+
+ $elementMap = $reader->elementMap;
+ $elementMap['{DAV:}prop'] = 'Sabre\DAV\Xml\Element\Prop';
+ $elementMap['{DAV:}set'] = 'Sabre\Xml\Element\KeyValue';
+ $elems = $reader->parseInnerTree($elementMap);
+
+ foreach ($elems as $elem) {
+ if ('{DAV:}set' === $elem['name']) {
+ $self->properties = array_merge($self->properties, $elem['value']['{DAV:}prop']);
+ }
+ }
+
+ return $self;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php
new file mode 100644
index 000000000..d597b76f1
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php
@@ -0,0 +1,107 @@
+sharees = $sharees;
+ }
+
+ /**
+ * 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)
+ {
+ $elems = $reader->parseGetElements([
+ '{'.Plugin::NS_CALENDARSERVER.'}set' => 'Sabre\\Xml\\Element\\KeyValue',
+ '{'.Plugin::NS_CALENDARSERVER.'}remove' => 'Sabre\\Xml\\Element\\KeyValue',
+ ]);
+
+ $sharees = [];
+
+ foreach ($elems as $elem) {
+ switch ($elem['name']) {
+ case '{'.Plugin::NS_CALENDARSERVER.'}set':
+ $sharee = $elem['value'];
+
+ $sumElem = '{'.Plugin::NS_CALENDARSERVER.'}summary';
+ $commonName = '{'.Plugin::NS_CALENDARSERVER.'}common-name';
+
+ $properties = [];
+ if (isset($sharee[$commonName])) {
+ $properties['{DAV:}displayname'] = $sharee[$commonName];
+ }
+
+ $access = array_key_exists('{'.Plugin::NS_CALENDARSERVER.'}read-write', $sharee)
+ ? \Sabre\DAV\Sharing\Plugin::ACCESS_READWRITE
+ : \Sabre\DAV\Sharing\Plugin::ACCESS_READ;
+
+ $sharees[] = new Sharee([
+ 'href' => $sharee['{DAV:}href'],
+ 'properties' => $properties,
+ 'access' => $access,
+ 'comment' => isset($sharee[$sumElem]) ? $sharee[$sumElem] : null,
+ ]);
+ break;
+
+ case '{'.Plugin::NS_CALENDARSERVER.'}remove':
+ $sharees[] = new Sharee([
+ 'href' => $elem['value']['{DAV:}href'],
+ 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_NOACCESS,
+ ]);
+ break;
+ }
+ }
+
+ return new self($sharees);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/AddressBook.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/AddressBook.php
new file mode 100644
index 000000000..86994f2d5
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/AddressBook.php
@@ -0,0 +1,335 @@
+carddavBackend = $carddavBackend;
+ $this->addressBookInfo = $addressBookInfo;
+ }
+
+ /**
+ * Returns the name of the addressbook.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->addressBookInfo['uri'];
+ }
+
+ /**
+ * Returns a card.
+ *
+ * @param string $name
+ *
+ * @return Card
+ */
+ public function getChild($name)
+ {
+ $obj = $this->carddavBackend->getCard($this->addressBookInfo['id'], $name);
+ if (!$obj) {
+ throw new DAV\Exception\NotFound('Card not found');
+ }
+
+ return new Card($this->carddavBackend, $this->addressBookInfo, $obj);
+ }
+
+ /**
+ * Returns the full list of cards.
+ *
+ * @return array
+ */
+ public function getChildren()
+ {
+ $objs = $this->carddavBackend->getCards($this->addressBookInfo['id']);
+ $children = [];
+ foreach ($objs as $obj) {
+ $obj['acl'] = $this->getChildACL();
+ $children[] = new Card($this->carddavBackend, $this->addressBookInfo, $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->carddavBackend->getMultipleCards($this->addressBookInfo['id'], $paths);
+ $children = [];
+ foreach ($objs as $obj) {
+ $obj['acl'] = $this->getChildACL();
+ $children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj);
+ }
+
+ return $children;
+ }
+
+ /**
+ * Creates a new directory.
+ *
+ * We actually block this, as subdirectories are not allowed in addressbooks.
+ *
+ * @param string $name
+ */
+ public function createDirectory($name)
+ {
+ throw new DAV\Exception\MethodNotAllowed('Creating collections in addressbooks is not allowed');
+ }
+
+ /**
+ * Creates a new file.
+ *
+ * The contents of the new file must be a valid VCARD.
+ *
+ * This method may return an ETag.
+ *
+ * @param string $name
+ * @param resource $vcardData
+ *
+ * @return string|null
+ */
+ public function createFile($name, $vcardData = null)
+ {
+ if (is_resource($vcardData)) {
+ $vcardData = stream_get_contents($vcardData);
+ }
+ // Converting to UTF-8, if needed
+ $vcardData = DAV\StringUtil::ensureUTF8($vcardData);
+
+ return $this->carddavBackend->createCard($this->addressBookInfo['id'], $name, $vcardData);
+ }
+
+ /**
+ * Deletes the entire addressbook.
+ */
+ public function delete()
+ {
+ $this->carddavBackend->deleteAddressBook($this->addressBookInfo['id']);
+ }
+
+ /**
+ * Renames the addressbook.
+ *
+ * @param string $newName
+ */
+ public function setName($newName)
+ {
+ throw new DAV\Exception\MethodNotAllowed('Renaming addressbooks is not yet supported');
+ }
+
+ /**
+ * Returns the last modification date as a unix timestamp.
+ */
+ public function getLastModified()
+ {
+ return null;
+ }
+
+ /**
+ * 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(DAV\PropPatch $propPatch)
+ {
+ return $this->carddavBackend->updateAddressBook($this->addressBookInfo['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.
+ *
+ * @param array $properties
+ *
+ * @return array
+ */
+ public function getProperties($properties)
+ {
+ $response = [];
+ foreach ($properties as $propertyName) {
+ if (isset($this->addressBookInfo[$propertyName])) {
+ $response[$propertyName] = $this->addressBookInfo[$propertyName];
+ }
+ }
+
+ return $response;
+ }
+
+ /**
+ * 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->addressBookInfo['principaluri'];
+ }
+
+ /**
+ * This method returns the ACL's for card nodes in this address book.
+ * The result of this method automatically gets passed to the
+ * card nodes in this address book.
+ *
+ * @return array
+ */
+ public function getChildACL()
+ {
+ return [
+ [
+ 'privilege' => '{DAV:}all',
+ 'principal' => $this->getOwner(),
+ 'protected' => true,
+ ],
+ ];
+ }
+
+ /**
+ * 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->carddavBackend instanceof Backend\SyncSupport &&
+ isset($this->addressBookInfo['{DAV:}sync-token'])
+ ) {
+ return $this->addressBookInfo['{DAV:}sync-token'];
+ }
+ if (
+ $this->carddavBackend instanceof Backend\SyncSupport &&
+ isset($this->addressBookInfo['{http://sabredav.org/ns}sync-token'])
+ ) {
+ return $this->addressBookInfo['{http://sabredav.org/ns}sync-token'];
+ }
+ }
+
+ /**
+ * The getChanges method returns all the changes that have happened, since
+ * the specified syncToken and the current collection.
+ *
+ * This function should return an array, such as the following:
+ *
+ * [
+ * 'syncToken' => 'The current synctoken',
+ * 'added' => [
+ * 'new.txt',
+ * ],
+ * 'modified' => [
+ * 'modified.txt',
+ * ],
+ * 'deleted' => [
+ * 'foo.php.bak',
+ * 'old.txt'
+ * ]
+ * ];
+ *
+ * The syncToken property should reflect the *current* syncToken of the
+ * collection, as reported getSyncToken(). This is needed here too, to
+ * ensure the operation is atomic.
+ *
+ * If the syncToken is specified as null, this is an initial sync, and all
+ * members should be reported.
+ *
+ * The modified property is an array of nodenames that have changed since
+ * the last token.
+ *
+ * The deleted property is an array with nodenames, that have been deleted
+ * from collection.
+ *
+ * The second argument is basically the 'depth' of the report. If it's 1,
+ * you only have to report changes that happened only directly in immediate
+ * descendants. If it's 2, it should also include changes from the nodes
+ * below the child collections. (grandchildren)
+ *
+ * The third (optional) argument allows a client to specify how many
+ * results should be returned at most. If the limit is not specified, it
+ * should be treated as infinite.
+ *
+ * If the limit (infinite or not) is higher than you're willing to return,
+ * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
+ *
+ * If the syncToken is expired (due to data cleanup) or unknown, you must
+ * return null.
+ *
+ * The limit is 'suggestive'. You are free to ignore it.
+ *
+ * @param string $syncToken
+ * @param int $syncLevel
+ * @param int $limit
+ *
+ * @return array
+ */
+ public function getChanges($syncToken, $syncLevel, $limit = null)
+ {
+ if (!$this->carddavBackend instanceof Backend\SyncSupport) {
+ return null;
+ }
+
+ return $this->carddavBackend->getChangesForAddressBook(
+ $this->addressBookInfo['id'],
+ $syncToken,
+ $syncLevel,
+ $limit
+ );
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php
new file mode 100644
index 000000000..884e9b24e
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php
@@ -0,0 +1,178 @@
+carddavBackend = $carddavBackend;
+ $this->principalUri = $principalUri;
+ }
+
+ /**
+ * Returns the name of this object.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ list(, $name) = Uri\split($this->principalUri);
+
+ return $name;
+ }
+
+ /**
+ * Updates the name of this object.
+ *
+ * @param string $name
+ */
+ public function setName($name)
+ {
+ throw new DAV\Exception\MethodNotAllowed();
+ }
+
+ /**
+ * Deletes this object.
+ */
+ public function delete()
+ {
+ throw new DAV\Exception\MethodNotAllowed();
+ }
+
+ /**
+ * 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 $filename
+ * @param resource $data
+ */
+ public function createFile($filename, $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 addressbook, by name.
+ *
+ * @param string $name
+ *
+ * @todo needs optimizing
+ *
+ * @return AddressBook
+ */
+ public function getChild($name)
+ {
+ foreach ($this->getChildren() as $child) {
+ if ($name == $child->getName()) {
+ return $child;
+ }
+ }
+ throw new DAV\Exception\NotFound('Addressbook with name \''.$name.'\' could not be found');
+ }
+
+ /**
+ * Returns a list of addressbooks.
+ *
+ * @return array
+ */
+ public function getChildren()
+ {
+ $addressbooks = $this->carddavBackend->getAddressBooksForUser($this->principalUri);
+ $objs = [];
+ foreach ($addressbooks as $addressbook) {
+ $objs[] = new AddressBook($this->carddavBackend, $addressbook);
+ }
+
+ return $objs;
+ }
+
+ /**
+ * Creates a new address book.
+ *
+ * @param string $name
+ *
+ * @throws DAV\Exception\InvalidResourceType
+ */
+ public function createExtendedCollection($name, MkCol $mkCol)
+ {
+ if (!$mkCol->hasResourceType('{'.Plugin::NS_CARDDAV.'}addressbook')) {
+ throw new DAV\Exception\InvalidResourceType('Unknown resourceType for this collection');
+ }
+ $properties = $mkCol->getRemainingValues();
+ $mkCol->setRemainingResultCode(201);
+ $this->carddavBackend->createAddressBook($this->principalUri, $name, $properties);
+ }
+
+ /**
+ * 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;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php
new file mode 100644
index 000000000..ee1721a45
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php
@@ -0,0 +1,75 @@
+carddavBackend = $carddavBackend;
+ parent::__construct($principalBackend, $principalPrefix);
+ }
+
+ /**
+ * Returns the name of the node.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return Plugin::ADDRESSBOOK_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 AddressBookHome($this->carddavBackend, $principal['uri']);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php
new file mode 100644
index 000000000..a900c6256
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php
@@ -0,0 +1,38 @@
+getCard($addressBookId, $uri);
+ }, $uris);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php
new file mode 100644
index 000000000..6ef34d173
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php
@@ -0,0 +1,194 @@
+pdo = $pdo;
+ }
+
+ /**
+ * Returns the list of addressbooks for a specific user.
+ *
+ * @param string $principalUri
+ *
+ * @return array
+ */
+ public function getAddressBooksForUser($principalUri)
+ {
+ $stmt = $this->pdo->prepare('SELECT id, uri, displayname, principaluri, description, synctoken FROM '.$this->addressBooksTableName.' WHERE principaluri = ?');
+ $stmt->execute([$principalUri]);
+
+ $addressBooks = [];
+
+ foreach ($stmt->fetchAll() as $row) {
+ $addressBooks[] = [
+ 'id' => $row['id'],
+ 'uri' => $row['uri'],
+ 'principaluri' => $row['principaluri'],
+ '{DAV:}displayname' => $row['displayname'],
+ '{'.CardDAV\Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
+ '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
+ '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
+ ];
+ }
+
+ return $addressBooks;
+ }
+
+ /**
+ * Updates properties for an address book.
+ *
+ * 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 string $addressBookId
+ */
+ public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch)
+ {
+ $supportedProperties = [
+ '{DAV:}displayname',
+ '{'.CardDAV\Plugin::NS_CARDDAV.'}addressbook-description',
+ ];
+
+ $propPatch->handle($supportedProperties, function ($mutations) use ($addressBookId) {
+ $updates = [];
+ foreach ($mutations as $property => $newValue) {
+ switch ($property) {
+ case '{DAV:}displayname':
+ $updates['displayname'] = $newValue;
+ break;
+ case '{'.CardDAV\Plugin::NS_CARDDAV.'}addressbook-description':
+ $updates['description'] = $newValue;
+ break;
+ }
+ }
+ $query = 'UPDATE '.$this->addressBooksTableName.' SET ';
+ $first = true;
+ foreach ($updates as $key => $value) {
+ if ($first) {
+ $first = false;
+ } else {
+ $query .= ', ';
+ }
+ $query .= ' '.$key.' = :'.$key.' ';
+ }
+ $query .= ' WHERE id = :addressbookid';
+
+ $stmt = $this->pdo->prepare($query);
+ $updates['addressbookid'] = $addressBookId;
+
+ $stmt->execute($updates);
+
+ $this->addChange($addressBookId, '', 2);
+
+ return true;
+ });
+ }
+
+ /**
+ * Creates a new address book.
+ *
+ * @param string $principalUri
+ * @param string $url just the 'basename' of the url
+ *
+ * @return int Last insert id
+ */
+ public function createAddressBook($principalUri, $url, array $properties)
+ {
+ $values = [
+ 'displayname' => null,
+ 'description' => null,
+ 'principaluri' => $principalUri,
+ 'uri' => $url,
+ ];
+
+ foreach ($properties as $property => $newValue) {
+ switch ($property) {
+ case '{DAV:}displayname':
+ $values['displayname'] = $newValue;
+ break;
+ case '{'.CardDAV\Plugin::NS_CARDDAV.'}addressbook-description':
+ $values['description'] = $newValue;
+ break;
+ default:
+ throw new DAV\Exception\BadRequest('Unknown property: '.$property);
+ }
+ }
+
+ $query = 'INSERT INTO '.$this->addressBooksTableName.' (uri, displayname, description, principaluri, synctoken) VALUES (:uri, :displayname, :description, :principaluri, 1)';
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute($values);
+
+ return $this->pdo->lastInsertId(
+ $this->addressBooksTableName.'_id_seq'
+ );
+ }
+
+ /**
+ * Deletes an entire addressbook and all its contents.
+ *
+ * @param int $addressBookId
+ */
+ public function deleteAddressBook($addressBookId)
+ {
+ $stmt = $this->pdo->prepare('DELETE FROM '.$this->cardsTableName.' WHERE addressbookid = ?');
+ $stmt->execute([$addressBookId]);
+
+ $stmt = $this->pdo->prepare('DELETE FROM '.$this->addressBooksTableName.' WHERE id = ?');
+ $stmt->execute([$addressBookId]);
+
+ $stmt = $this->pdo->prepare('DELETE FROM '.$this->addressBookChangesTableName.' WHERE addressbookid = ?');
+ $stmt->execute([$addressBookId]);
+ }
+
+ /**
+ * Returns all cards for a specific addressbook id.
+ *
+ * This method should return the following properties for each card:
+ * * carddata - raw vcard data
+ * * uri - Some unique url
+ * * lastmodified - A unix timestamp
+ *
+ * It's recommended to also return the following properties:
+ * * etag - A unique etag. This must change every time the card changes.
+ * * size - The size of the card in bytes.
+ *
+ * If these last two properties are provided, less time will be spent
+ * calculating them. If they are specified, you can also ommit carddata.
+ * This may speed up certain requests, especially with large cards.
+ *
+ * @param mixed $addressbookId
+ *
+ * @return array
+ */
+ public function getCards($addressbookId)
+ {
+ $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, size FROM '.$this->cardsTableName.' WHERE addressbookid = ?');
+ $stmt->execute([$addressbookId]);
+
+ $result = [];
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ $row['etag'] = '"'.$row['etag'].'"';
+ $row['lastmodified'] = (int) $row['lastmodified'];
+ $result[] = $row;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns a specific card.
+ *
+ * The same set of properties must be returned as with getCards. The only
+ * exception is that 'carddata' is absolutely required.
+ *
+ * If the card does not exist, you must return false.
+ *
+ * @param mixed $addressBookId
+ * @param string $cardUri
+ *
+ * @return array
+ */
+ public function getCard($addressBookId, $cardUri)
+ {
+ $stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified, etag, size FROM '.$this->cardsTableName.' WHERE addressbookid = ? AND uri = ? LIMIT 1');
+ $stmt->execute([$addressBookId, $cardUri]);
+
+ $result = $stmt->fetch(\PDO::FETCH_ASSOC);
+
+ if (!$result) {
+ return false;
+ }
+
+ $result['etag'] = '"'.$result['etag'].'"';
+ $result['lastmodified'] = (int) $result['lastmodified'];
+
+ return $result;
+ }
+
+ /**
+ * Returns a list of cards.
+ *
+ * This method should work identical to getCard, but instead return all the
+ * cards in the list as an array.
+ *
+ * If the backend supports this, it may allow for some speed-ups.
+ *
+ * @param mixed $addressBookId
+ *
+ * @return array
+ */
+ public function getMultipleCards($addressBookId, array $uris)
+ {
+ $query = 'SELECT id, uri, lastmodified, etag, size, carddata FROM '.$this->cardsTableName.' WHERE addressbookid = ? AND uri IN (';
+ // Inserting a whole bunch of question marks
+ $query .= implode(',', array_fill(0, count($uris), '?'));
+ $query .= ')';
+
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute(array_merge([$addressBookId], $uris));
+ $result = [];
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ $row['etag'] = '"'.$row['etag'].'"';
+ $row['lastmodified'] = (int) $row['lastmodified'];
+ $result[] = $row;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Creates a new card.
+ *
+ * The addressbook id will be passed as the first argument. This is the
+ * same id as it is returned from the getAddressBooksForUser method.
+ *
+ * The cardUri is a base uri, and doesn't include the full path. The
+ * cardData argument is the vcard body, and is passed as a string.
+ *
+ * It is possible to return an ETag from this method. This ETag is for the
+ * newly created resource, and must be enclosed with double quotes (that
+ * is, the string itself must contain the double quotes).
+ *
+ * You should only return the ETag if you store the carddata as-is. If a
+ * subsequent GET request on the same card does not have the same body,
+ * byte-by-byte and you did return an ETag here, clients tend to get
+ * confused.
+ *
+ * If you don't return an ETag, you can just return null.
+ *
+ * @param mixed $addressBookId
+ * @param string $cardUri
+ * @param string $cardData
+ *
+ * @return string|null
+ */
+ public function createCard($addressBookId, $cardUri, $cardData)
+ {
+ $stmt = $this->pdo->prepare('INSERT INTO '.$this->cardsTableName.' (carddata, uri, lastmodified, addressbookid, size, etag) VALUES (?, ?, ?, ?, ?, ?)');
+
+ $etag = md5($cardData);
+
+ $stmt->execute([
+ $cardData,
+ $cardUri,
+ time(),
+ $addressBookId,
+ strlen($cardData),
+ $etag,
+ ]);
+
+ $this->addChange($addressBookId, $cardUri, 1);
+
+ return '"'.$etag.'"';
+ }
+
+ /**
+ * Updates a card.
+ *
+ * The addressbook id will be passed as the first argument. This is the
+ * same id as it is returned from the getAddressBooksForUser method.
+ *
+ * The cardUri is a base uri, and doesn't include the full path. The
+ * cardData argument is the vcard body, and is passed as a string.
+ *
+ * It is possible to return an ETag from this method. This ETag should
+ * match that of the updated resource, and must be enclosed with double
+ * quotes (that is: the string itself must contain the actual quotes).
+ *
+ * You should only return the ETag if you store the carddata as-is. If a
+ * subsequent GET request on the same card does not have the same body,
+ * byte-by-byte and you did return an ETag here, clients tend to get
+ * confused.
+ *
+ * If you don't return an ETag, you can just return null.
+ *
+ * @param mixed $addressBookId
+ * @param string $cardUri
+ * @param string $cardData
+ *
+ * @return string|null
+ */
+ public function updateCard($addressBookId, $cardUri, $cardData)
+ {
+ $stmt = $this->pdo->prepare('UPDATE '.$this->cardsTableName.' SET carddata = ?, lastmodified = ?, size = ?, etag = ? WHERE uri = ? AND addressbookid =?');
+
+ $etag = md5($cardData);
+ $stmt->execute([
+ $cardData,
+ time(),
+ strlen($cardData),
+ $etag,
+ $cardUri,
+ $addressBookId,
+ ]);
+
+ $this->addChange($addressBookId, $cardUri, 2);
+
+ return '"'.$etag.'"';
+ }
+
+ /**
+ * Deletes a card.
+ *
+ * @param mixed $addressBookId
+ * @param string $cardUri
+ *
+ * @return bool
+ */
+ public function deleteCard($addressBookId, $cardUri)
+ {
+ $stmt = $this->pdo->prepare('DELETE FROM '.$this->cardsTableName.' WHERE addressbookid = ? AND uri = ?');
+ $stmt->execute([$addressBookId, $cardUri]);
+
+ $this->addChange($addressBookId, $cardUri, 3);
+
+ return 1 === $stmt->rowCount();
+ }
+
+ /**
+ * The getChanges method returns all the changes that have happened, since
+ * the specified syncToken in the specified address book.
+ *
+ * This function should return an array, such as the following:
+ *
+ * [
+ * 'syncToken' => 'The current synctoken',
+ * 'added' => [
+ * 'new.txt',
+ * ],
+ * 'modified' => [
+ * 'updated.txt',
+ * ],
+ * 'deleted' => [
+ * 'foo.php.bak',
+ * 'old.txt'
+ * ]
+ * ];
+ *
+ * The returned syncToken property should reflect the *current* syncToken
+ * of the addressbook, 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 $addressBookId
+ * @param string $syncToken
+ * @param int $syncLevel
+ * @param int $limit
+ *
+ * @return array
+ */
+ public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null)
+ {
+ // Current synctoken
+ $stmt = $this->pdo->prepare('SELECT synctoken FROM '.$this->addressBooksTableName.' WHERE id = ?');
+ $stmt->execute([$addressBookId]);
+ $currentToken = $stmt->fetchColumn(0);
+
+ if (is_null($currentToken)) {
+ return null;
+ }
+
+ $result = [
+ 'syncToken' => $currentToken,
+ 'added' => [],
+ 'modified' => [],
+ 'deleted' => [],
+ ];
+
+ if ($syncToken) {
+ $query = 'SELECT uri, operation FROM '.$this->addressBookChangesTableName.' WHERE synctoken >= ? AND synctoken < ? AND addressbookid = ? ORDER BY synctoken';
+ if ($limit > 0) {
+ $query .= ' LIMIT '.(int) $limit;
+ }
+
+ // Fetching all changes
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute([$syncToken, $currentToken, $addressBookId]);
+
+ $changes = [];
+
+ // This loop ensures that any duplicates are overwritten, only the
+ // last change on a node is relevant.
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ $changes[$row['uri']] = $row['operation'];
+ }
+
+ foreach ($changes as $uri => $operation) {
+ switch ($operation) {
+ case 1:
+ $result['added'][] = $uri;
+ break;
+ case 2:
+ $result['modified'][] = $uri;
+ break;
+ case 3:
+ $result['deleted'][] = $uri;
+ break;
+ }
+ }
+ } else {
+ // No synctoken supplied, this is the initial sync.
+ $query = 'SELECT uri FROM '.$this->cardsTableName.' WHERE addressbookid = ?';
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute([$addressBookId]);
+
+ $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Adds a change record to the addressbookchanges table.
+ *
+ * @param mixed $addressBookId
+ * @param string $objectUri
+ * @param int $operation 1 = add, 2 = modify, 3 = delete
+ */
+ protected function addChange($addressBookId, $objectUri, $operation)
+ {
+ $stmt = $this->pdo->prepare('INSERT INTO '.$this->addressBookChangesTableName.' (uri, synctoken, addressbookid, operation) SELECT ?, synctoken, ?, ? FROM '.$this->addressBooksTableName.' WHERE id = ?');
+ $stmt->execute([
+ $objectUri,
+ $addressBookId,
+ $operation,
+ $addressBookId,
+ ]);
+ $stmt = $this->pdo->prepare('UPDATE '.$this->addressBooksTableName.' SET synctoken = synctoken + 1 WHERE id = ?');
+ $stmt->execute([
+ $addressBookId,
+ ]);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php
new file mode 100644
index 000000000..071361ed0
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php
@@ -0,0 +1,83 @@
+ '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 $addressBookId
+ * @param string $syncToken
+ * @param int $syncLevel
+ * @param int $limit
+ *
+ * @return array
+ */
+ public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null);
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Card.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Card.php
new file mode 100644
index 000000000..c9cd2bbf6
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Card.php
@@ -0,0 +1,202 @@
+carddavBackend = $carddavBackend;
+ $this->addressBookInfo = $addressBookInfo;
+ $this->cardData = $cardData;
+ }
+
+ /**
+ * Returns the uri for this object.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->cardData['uri'];
+ }
+
+ /**
+ * Returns the VCard-formatted object.
+ *
+ * @return string
+ */
+ public function get()
+ {
+ // Pre-populating 'carddata' is optional. If we don't yet have it
+ // already, we fetch it from the backend.
+ if (!isset($this->cardData['carddata'])) {
+ $this->cardData = $this->carddavBackend->getCard($this->addressBookInfo['id'], $this->cardData['uri']);
+ }
+
+ return $this->cardData['carddata'];
+ }
+
+ /**
+ * Updates the VCard-formatted object.
+ *
+ * @param string $cardData
+ *
+ * @return string|null
+ */
+ public function put($cardData)
+ {
+ if (is_resource($cardData)) {
+ $cardData = stream_get_contents($cardData);
+ }
+
+ // Converting to UTF-8, if needed
+ $cardData = DAV\StringUtil::ensureUTF8($cardData);
+
+ $etag = $this->carddavBackend->updateCard($this->addressBookInfo['id'], $this->cardData['uri'], $cardData);
+ $this->cardData['carddata'] = $cardData;
+ $this->cardData['etag'] = $etag;
+
+ return $etag;
+ }
+
+ /**
+ * Deletes the card.
+ */
+ public function delete()
+ {
+ $this->carddavBackend->deleteCard($this->addressBookInfo['id'], $this->cardData['uri']);
+ }
+
+ /**
+ * Returns the mime content-type.
+ *
+ * @return string
+ */
+ public function getContentType()
+ {
+ return 'text/vcard; charset=utf-8';
+ }
+
+ /**
+ * Returns an ETag for this object.
+ *
+ * @return string
+ */
+ public function getETag()
+ {
+ if (isset($this->cardData['etag'])) {
+ return $this->cardData['etag'];
+ } else {
+ $data = $this->get();
+ if (is_string($data)) {
+ return '"'.md5($data).'"';
+ } else {
+ // We refuse to calculate the md5 if it's a stream.
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Returns the last modification date as a unix timestamp.
+ *
+ * @return int
+ */
+ public function getLastModified()
+ {
+ return isset($this->cardData['lastmodified']) ? $this->cardData['lastmodified'] : null;
+ }
+
+ /**
+ * Returns the size of this object in bytes.
+ *
+ * @return int
+ */
+ public function getSize()
+ {
+ if (array_key_exists('size', $this->cardData)) {
+ return $this->cardData['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->addressBookInfo['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 through the cardData array.
+ if (isset($this->cardData['acl'])) {
+ return $this->cardData['acl'];
+ }
+
+ return [
+ [
+ 'privilege' => '{DAV:}all',
+ 'principal' => $this->addressBookInfo['principaluri'],
+ 'protected' => true,
+ ],
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/IAddressBook.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/IAddressBook.php
new file mode 100644
index 000000000..3f489f4e5
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/IAddressBook.php
@@ -0,0 +1,20 @@
+on('propFind', [$this, 'propFindEarly']);
+ $server->on('propFind', [$this, 'propFindLate'], 150);
+ $server->on('report', [$this, 'report']);
+ $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
+ $server->on('beforeWriteContent', [$this, 'beforeWriteContent']);
+ $server->on('beforeCreateFile', [$this, 'beforeCreateFile']);
+ $server->on('afterMethod:GET', [$this, 'httpAfterGet']);
+
+ $server->xml->namespaceMap[self::NS_CARDDAV] = 'card';
+
+ $server->xml->elementMap['{'.self::NS_CARDDAV.'}addressbook-query'] = 'Sabre\\CardDAV\\Xml\\Request\\AddressBookQueryReport';
+ $server->xml->elementMap['{'.self::NS_CARDDAV.'}addressbook-multiget'] = 'Sabre\\CardDAV\\Xml\\Request\\AddressBookMultiGetReport';
+
+ /* Mapping Interfaces to {DAV:}resourcetype values */
+ $server->resourceTypeMapping['Sabre\\CardDAV\\IAddressBook'] = '{'.self::NS_CARDDAV.'}addressbook';
+ $server->resourceTypeMapping['Sabre\\CardDAV\\IDirectory'] = '{'.self::NS_CARDDAV.'}directory';
+
+ /* Adding properties that may never be changed */
+ $server->protectedProperties[] = '{'.self::NS_CARDDAV.'}supported-address-data';
+ $server->protectedProperties[] = '{'.self::NS_CARDDAV.'}max-resource-size';
+ $server->protectedProperties[] = '{'.self::NS_CARDDAV.'}addressbook-home-set';
+ $server->protectedProperties[] = '{'.self::NS_CARDDAV.'}supported-collation-set';
+
+ $server->xml->elementMap['{http://calendarserver.org/ns/}me-card'] = 'Sabre\\DAV\\Xml\\Property\\Href';
+
+ $this->server = $server;
+ }
+
+ /**
+ * Returns a list of supported features.
+ *
+ * This is used in the DAV: header in the OPTIONS and PROPFIND requests.
+ *
+ * @return array
+ */
+ public function getFeatures()
+ {
+ return ['addressbook'];
+ }
+
+ /**
+ * Returns a list of reports this plugin supports.
+ *
+ * This will be used in the {DAV:}supported-report-set property.
+ * Note that you still need to subscribe to the 'report' event to actually
+ * implement them
+ *
+ * @param string $uri
+ *
+ * @return array
+ */
+ public function getSupportedReportSet($uri)
+ {
+ $node = $this->server->tree->getNodeForPath($uri);
+ if ($node instanceof IAddressBook || $node instanceof ICard) {
+ return [
+ '{'.self::NS_CARDDAV.'}addressbook-multiget',
+ '{'.self::NS_CARDDAV.'}addressbook-query',
+ ];
+ }
+
+ return [];
+ }
+
+ /**
+ * Adds all CardDAV-specific properties.
+ */
+ public function propFindEarly(DAV\PropFind $propFind, DAV\INode $node)
+ {
+ $ns = '{'.self::NS_CARDDAV.'}';
+
+ if ($node instanceof IAddressBook) {
+ $propFind->handle($ns.'max-resource-size', $this->maxResourceSize);
+ $propFind->handle($ns.'supported-address-data', function () {
+ return new Xml\Property\SupportedAddressData();
+ });
+ $propFind->handle($ns.'supported-collation-set', function () {
+ return new Xml\Property\SupportedCollationSet();
+ });
+ }
+ if ($node instanceof DAVACL\IPrincipal) {
+ $path = $propFind->getPath();
+
+ $propFind->handle('{'.self::NS_CARDDAV.'}addressbook-home-set', function () use ($path) {
+ return new LocalHref($this->getAddressBookHomeForPrincipal($path).'/');
+ });
+
+ if ($this->directories) {
+ $propFind->handle('{'.self::NS_CARDDAV.'}directory-gateway', function () {
+ return new LocalHref($this->directories);
+ });
+ }
+ }
+
+ if ($node instanceof ICard) {
+ // The address-data property is not supposed to be a 'real'
+ // property, but in large chunks of the spec it does act as such.
+ // Therefore we simply expose it as a property.
+ $propFind->handle('{'.self::NS_CARDDAV.'}address-data', function () use ($node) {
+ $val = $node->get();
+ if (is_resource($val)) {
+ $val = stream_get_contents($val);
+ }
+
+ return $val;
+ });
+ }
+ }
+
+ /**
+ * This functions handles REPORT requests specific to CardDAV.
+ *
+ * @param string $reportName
+ * @param \DOMNode $dom
+ * @param mixed $path
+ *
+ * @return bool
+ */
+ public function report($reportName, $dom, $path)
+ {
+ switch ($reportName) {
+ case '{'.self::NS_CARDDAV.'}addressbook-multiget':
+ $this->server->transactionType = 'report-addressbook-multiget';
+ $this->addressbookMultiGetReport($dom);
+
+ return false;
+ case '{'.self::NS_CARDDAV.'}addressbook-query':
+ $this->server->transactionType = 'report-addressbook-query';
+ $this->addressBookQueryReport($dom);
+
+ return false;
+ default:
+ return;
+ }
+ }
+
+ /**
+ * Returns the addressbook home for a given principal.
+ *
+ * @param string $principal
+ *
+ * @return string
+ */
+ protected function getAddressbookHomeForPrincipal($principal)
+ {
+ list(, $principalId) = Uri\split($principal);
+
+ return self::ADDRESSBOOK_ROOT.'/'.$principalId;
+ }
+
+ /**
+ * This function handles the addressbook-multiget REPORT.
+ *
+ * This report is used by the client to fetch the content of a series
+ * of urls. Effectively avoiding a lot of redundant requests.
+ *
+ * @param Xml\Request\AddressBookMultiGetReport $report
+ */
+ public function addressbookMultiGetReport($report)
+ {
+ $contentType = $report->contentType;
+ $version = $report->version;
+ if ($version) {
+ $contentType .= '; version='.$version;
+ }
+
+ $vcardType = $this->negotiateVCard(
+ $contentType
+ );
+
+ $propertyList = [];
+ $paths = array_map(
+ [$this->server, 'calculateUri'],
+ $report->hrefs
+ );
+ foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $props) {
+ if (isset($props['200']['{'.self::NS_CARDDAV.'}address-data'])) {
+ $props['200']['{'.self::NS_CARDDAV.'}address-data'] = $this->convertVCard(
+ $props[200]['{'.self::NS_CARDDAV.'}address-data'],
+ $vcardType
+ );
+ }
+ $propertyList[] = $props;
+ }
+
+ $prefer = $this->server->getHTTPPrefer();
+
+ $this->server->httpResponse->setStatus(207);
+ $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
+ $this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, 'minimal' === $prefer['return']));
+ }
+
+ /**
+ * This method is triggered before a file gets updated with new content.
+ *
+ * This plugin uses this method to ensure that Card nodes receive valid
+ * vcard data.
+ *
+ * @param string $path
+ * @param resource $data
+ * @param bool $modified should be set to true, if this event handler
+ * changed &$data
+ */
+ public function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified)
+ {
+ if (!$node instanceof ICard) {
+ return;
+ }
+
+ $this->validateVCard($data, $modified);
+ }
+
+ /**
+ * This method is triggered before a new file is created.
+ *
+ * This plugin uses this method to ensure that Card nodes receive valid
+ * vcard data.
+ *
+ * @param string $path
+ * @param resource $data
+ * @param bool $modified should be set to true, if this event handler
+ * changed &$data
+ */
+ public function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified)
+ {
+ if (!$parentNode instanceof IAddressBook) {
+ return;
+ }
+
+ $this->validateVCard($data, $modified);
+ }
+
+ /**
+ * Checks if the submitted iCalendar data is in fact, valid.
+ *
+ * An exception is thrown if it's not.
+ *
+ * @param resource|string $data
+ * @param bool $modified should be set to true, if this event handler
+ * changed &$data
+ */
+ protected function validateVCard(&$data, &$modified)
+ {
+ // If it's a stream, we convert it to a string first.
+ if (is_resource($data)) {
+ $data = stream_get_contents($data);
+ }
+
+ $before = $data;
+
+ try {
+ // If the data starts with a [, we can reasonably assume we're dealing
+ // with a jCal object.
+ if ('[' === substr($data, 0, 1)) {
+ $vobj = VObject\Reader::readJson($data);
+
+ // Converting $data back to iCalendar, as that's what we
+ // technically support everywhere.
+ $data = $vobj->serialize();
+ $modified = true;
+ } else {
+ $vobj = VObject\Reader::read($data);
+ }
+ } catch (VObject\ParseException $e) {
+ throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid vCard or jCard data. Parse error: '.$e->getMessage());
+ }
+
+ if ('VCARD' !== $vobj->name) {
+ throw new DAV\Exception\UnsupportedMediaType('This collection can only support vcard objects.');
+ }
+
+ $options = VObject\Node::PROFILE_CARDDAV;
+ $prefer = $this->server->getHTTPPrefer();
+
+ if ('strict' !== $prefer['handling']) {
+ $options |= VObject\Node::REPAIR;
+ }
+
+ $messages = $vobj->validate($options);
+
+ $highestLevel = 0;
+ $warningMessage = null;
+
+ // $messages contains a list of problems with the vcard, along with
+ // their severity.
+ foreach ($messages as $message) {
+ if ($message['level'] > $highestLevel) {
+ // Recording the highest reported error level.
+ $highestLevel = $message['level'];
+ $warningMessage = $message['message'];
+ }
+
+ switch ($message['level']) {
+ case 1:
+ // Level 1 means that there was a problem, but it was repaired.
+ $modified = true;
+ break;
+ case 2:
+ // Level 2 means a warning, but not critical
+ break;
+ case 3:
+ // Level 3 means a critical error
+ throw new DAV\Exception\UnsupportedMediaType('Validation error in vCard: '.$message['message']);
+ }
+ }
+ if ($warningMessage) {
+ $this->server->httpResponse->setHeader(
+ 'X-Sabre-Ew-Gross',
+ 'vCard validation warning: '.$warningMessage
+ );
+
+ // Re-serializing object.
+ $data = $vobj->serialize();
+ if (!$modified && 0 !== strcmp($data, $before)) {
+ // This ensures that the system does not send an ETag back.
+ $modified = true;
+ }
+ }
+
+ // Destroy circular references to PHP will GC the object.
+ $vobj->destroy();
+ }
+
+ /**
+ * This function handles the addressbook-query REPORT.
+ *
+ * This report is used by the client to filter an addressbook based on a
+ * complex query.
+ *
+ * @param Xml\Request\AddressBookQueryReport $report
+ */
+ protected function addressbookQueryReport($report)
+ {
+ $depth = $this->server->getHTTPDepth(0);
+
+ if (0 == $depth) {
+ $candidateNodes = [
+ $this->server->tree->getNodeForPath($this->server->getRequestUri()),
+ ];
+ if (!$candidateNodes[0] instanceof ICard) {
+ throw new ReportNotSupported('The addressbook-query report is not supported on this url with Depth: 0');
+ }
+ } else {
+ $candidateNodes = $this->server->tree->getChildren($this->server->getRequestUri());
+ }
+
+ $contentType = $report->contentType;
+ if ($report->version) {
+ $contentType .= '; version='.$report->version;
+ }
+
+ $vcardType = $this->negotiateVCard(
+ $contentType
+ );
+
+ $validNodes = [];
+ foreach ($candidateNodes as $node) {
+ if (!$node instanceof ICard) {
+ continue;
+ }
+
+ $blob = $node->get();
+ if (is_resource($blob)) {
+ $blob = stream_get_contents($blob);
+ }
+
+ if (!$this->validateFilters($blob, $report->filters, $report->test)) {
+ continue;
+ }
+
+ $validNodes[] = $node;
+
+ if ($report->limit && $report->limit <= count($validNodes)) {
+ // We hit the maximum number of items, we can stop now.
+ break;
+ }
+ }
+
+ $result = [];
+ foreach ($validNodes as $validNode) {
+ if (0 == $depth) {
+ $href = $this->server->getRequestUri();
+ } else {
+ $href = $this->server->getRequestUri().'/'.$validNode->getName();
+ }
+
+ list($props) = $this->server->getPropertiesForPath($href, $report->properties, 0);
+
+ if (isset($props[200]['{'.self::NS_CARDDAV.'}address-data'])) {
+ $props[200]['{'.self::NS_CARDDAV.'}address-data'] = $this->convertVCard(
+ $props[200]['{'.self::NS_CARDDAV.'}address-data'],
+ $vcardType,
+ $report->addressDataProperties
+ );
+ }
+ $result[] = $props;
+ }
+
+ $prefer = $this->server->getHTTPPrefer();
+
+ $this->server->httpResponse->setStatus(207);
+ $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
+ $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, 'minimal' === $prefer['return']));
+ }
+
+ /**
+ * Validates if a vcard makes it throught a list of filters.
+ *
+ * @param string $vcardData
+ * @param string $test anyof or allof (which means OR or AND)
+ *
+ * @return bool
+ */
+ public function validateFilters($vcardData, array $filters, $test)
+ {
+ if (!$filters) {
+ return true;
+ }
+ $vcard = VObject\Reader::read($vcardData);
+
+ foreach ($filters as $filter) {
+ $isDefined = isset($vcard->{$filter['name']});
+ if ($filter['is-not-defined']) {
+ if ($isDefined) {
+ $success = false;
+ } else {
+ $success = true;
+ }
+ } elseif ((!$filter['param-filters'] && !$filter['text-matches']) || !$isDefined) {
+ // We only need to check for existence
+ $success = $isDefined;
+ } else {
+ $vProperties = $vcard->select($filter['name']);
+
+ $results = [];
+ if ($filter['param-filters']) {
+ $results[] = $this->validateParamFilters($vProperties, $filter['param-filters'], $filter['test']);
+ }
+ if ($filter['text-matches']) {
+ $texts = [];
+ foreach ($vProperties as $vProperty) {
+ $texts[] = $vProperty->getValue();
+ }
+
+ $results[] = $this->validateTextMatches($texts, $filter['text-matches'], $filter['test']);
+ }
+
+ if (1 === count($results)) {
+ $success = $results[0];
+ } else {
+ if ('anyof' === $filter['test']) {
+ $success = $results[0] || $results[1];
+ } else {
+ $success = $results[0] && $results[1];
+ }
+ }
+ } // else
+
+ // There are two conditions where we can already determine whether
+ // or not this filter succeeds.
+ if ('anyof' === $test && $success) {
+ // Destroy circular references to PHP will GC the object.
+ $vcard->destroy();
+
+ return true;
+ }
+ if ('allof' === $test && !$success) {
+ // Destroy circular references to PHP will GC the object.
+ $vcard->destroy();
+
+ return false;
+ }
+ } // foreach
+
+ // Destroy circular references to PHP will GC the object.
+ $vcard->destroy();
+
+ // If we got all the way here, it means we haven't been able to
+ // determine early if the test failed or not.
+ //
+ // This implies for 'anyof' that the test failed, and for 'allof' that
+ // we succeeded. Sounds weird, but makes sense.
+ return 'allof' === $test;
+ }
+
+ /**
+ * Validates if a param-filter can be applied to a specific property.
+ *
+ * @todo currently we're only validating the first parameter of the passed
+ * property. Any subsequence parameters with the same name are
+ * ignored.
+ *
+ * @param string $test
+ *
+ * @return bool
+ */
+ protected function validateParamFilters(array $vProperties, array $filters, $test)
+ {
+ foreach ($filters as $filter) {
+ $isDefined = false;
+ foreach ($vProperties as $vProperty) {
+ $isDefined = isset($vProperty[$filter['name']]);
+ if ($isDefined) {
+ break;
+ }
+ }
+
+ if ($filter['is-not-defined']) {
+ if ($isDefined) {
+ $success = false;
+ } else {
+ $success = true;
+ }
+
+ // If there's no text-match, we can just check for existence
+ } elseif (!$filter['text-match'] || !$isDefined) {
+ $success = $isDefined;
+ } else {
+ $success = false;
+ foreach ($vProperties as $vProperty) {
+ // If we got all the way here, we'll need to validate the
+ // text-match filter.
+ $success = DAV\StringUtil::textMatch($vProperty[$filter['name']]->getValue(), $filter['text-match']['value'], $filter['text-match']['collation'], $filter['text-match']['match-type']);
+ if ($success) {
+ break;
+ }
+ }
+ if ($filter['text-match']['negate-condition']) {
+ $success = !$success;
+ }
+ } // else
+
+ // There are two conditions where we can already determine whether
+ // or not this filter succeeds.
+ if ('anyof' === $test && $success) {
+ return true;
+ }
+ if ('allof' === $test && !$success) {
+ return false;
+ }
+ }
+
+ // If we got all the way here, it means we haven't been able to
+ // determine early if the test failed or not.
+ //
+ // This implies for 'anyof' that the test failed, and for 'allof' that
+ // we succeeded. Sounds weird, but makes sense.
+ return 'allof' === $test;
+ }
+
+ /**
+ * Validates if a text-filter can be applied to a specific property.
+ *
+ * @param string $test
+ *
+ * @return bool
+ */
+ protected function validateTextMatches(array $texts, array $filters, $test)
+ {
+ foreach ($filters as $filter) {
+ $success = false;
+ foreach ($texts as $haystack) {
+ $success = DAV\StringUtil::textMatch($haystack, $filter['value'], $filter['collation'], $filter['match-type']);
+
+ // Breaking on the first match
+ if ($success) {
+ break;
+ }
+ }
+ if ($filter['negate-condition']) {
+ $success = !$success;
+ }
+
+ if ($success && 'anyof' === $test) {
+ return true;
+ }
+
+ if (!$success && 'allof' == $test) {
+ return false;
+ }
+ }
+
+ // If we got all the way here, it means we haven't been able to
+ // determine early if the test failed or not.
+ //
+ // This implies for 'anyof' that the test failed, and for 'allof' that
+ // we succeeded. Sounds weird, but makes sense.
+ return 'allof' === $test;
+ }
+
+ /**
+ * This event is triggered when fetching properties.
+ *
+ * This event is scheduled late in the process, after most work for
+ * propfind has been done.
+ */
+ public function propFindLate(DAV\PropFind $propFind, DAV\INode $node)
+ {
+ // If the request was made using the SOGO connector, we must rewrite
+ // the content-type property. By default SabreDAV will send back
+ // text/x-vcard; charset=utf-8, but for SOGO we must strip that last
+ // part.
+ if (false === strpos((string) $this->server->httpRequest->getHeader('User-Agent'), 'Thunderbird')) {
+ return;
+ }
+ $contentType = $propFind->get('{DAV:}getcontenttype');
+ if (null !== $contentType) {
+ list($part) = explode(';', $contentType);
+ if ('text/x-vcard' === $part || 'text/vcard' === $part) {
+ $propFind->set('{DAV:}getcontenttype', 'text/x-vcard');
+ }
+ }
+ }
+
+ /**
+ * This method is used to generate HTML output for the
+ * Sabre\DAV\Browser\Plugin. This allows us to generate an interface users
+ * can use to create new addressbooks.
+ *
+ * @param string $output
+ *
+ * @return bool
+ */
+ public function htmlActionsPanel(DAV\INode $node, &$output)
+ {
+ if (!$node instanceof AddressBookHome) {
+ return;
+ }
+
+ $output .= '
+
';
+
+ return false;
+ }
+
+ /**
+ * This event is triggered after GET requests.
+ *
+ * This is used to transform data into jCal, if this was requested.
+ */
+ public function httpAfterGet(RequestInterface $request, ResponseInterface $response)
+ {
+ $contentType = $response->getHeader('Content-Type');
+ if (null === $contentType || false === strpos($contentType, 'text/vcard')) {
+ return;
+ }
+
+ $target = $this->negotiateVCard($request->getHeader('Accept'), $mimeType);
+
+ $newBody = $this->convertVCard(
+ $response->getBody(),
+ $target
+ );
+
+ $response->setBody($newBody);
+ $response->setHeader('Content-Type', $mimeType.'; charset=utf-8');
+ $response->setHeader('Content-Length', strlen($newBody));
+ }
+
+ /**
+ * This helper function performs the content-type negotiation for vcards.
+ *
+ * It will return one of the following strings:
+ * 1. vcard3
+ * 2. vcard4
+ * 3. jcard
+ *
+ * It defaults to vcard3.
+ *
+ * @param string $input
+ * @param string $mimeType
+ *
+ * @return string
+ */
+ protected function negotiateVCard($input, &$mimeType = null)
+ {
+ $result = HTTP\negotiateContentType(
+ $input,
+ [
+ // Most often used mime-type. Version 3
+ 'text/x-vcard',
+ // The correct standard mime-type. Defaults to version 3 as
+ // well.
+ 'text/vcard',
+ // vCard 4
+ 'text/vcard; version=4.0',
+ // vCard 3
+ 'text/vcard; version=3.0',
+ // jCard
+ 'application/vcard+json',
+ ]
+ );
+
+ $mimeType = $result;
+ switch ($result) {
+ default:
+ case 'text/x-vcard':
+ case 'text/vcard':
+ case 'text/vcard; version=3.0':
+ $mimeType = 'text/vcard';
+
+ return 'vcard3';
+ case 'text/vcard; version=4.0':
+ return 'vcard4';
+ case 'application/vcard+json':
+ return 'jcard';
+
+ // @codeCoverageIgnoreStart
+ }
+ // @codeCoverageIgnoreEnd
+ }
+
+ /**
+ * Converts a vcard blob to a different version, or jcard.
+ *
+ * @param string|resource $data
+ * @param string $target
+ * @param array $propertiesFilter
+ *
+ * @return string
+ */
+ protected function convertVCard($data, $target, array $propertiesFilter = null)
+ {
+ if (is_resource($data)) {
+ $data = stream_get_contents($data);
+ }
+ $input = VObject\Reader::read($data);
+ if (!empty($propertiesFilter)) {
+ $propertiesFilter = array_merge(['UID', 'VERSION', 'FN'], $propertiesFilter);
+ $keys = array_unique(array_map(function ($child) {
+ return $child->name;
+ }, $input->children()));
+ $keys = array_diff($keys, $propertiesFilter);
+ foreach ($keys as $key) {
+ unset($input->$key);
+ }
+ $data = $input->serialize();
+ }
+ $output = null;
+ try {
+ switch ($target) {
+ default:
+ case 'vcard3':
+ if (VObject\Document::VCARD30 === $input->getDocumentType()) {
+ // Do nothing
+ return $data;
+ }
+ $output = $input->convert(VObject\Document::VCARD30);
+
+ return $output->serialize();
+ case 'vcard4':
+ if (VObject\Document::VCARD40 === $input->getDocumentType()) {
+ // Do nothing
+ return $data;
+ }
+ $output = $input->convert(VObject\Document::VCARD40);
+
+ return $output->serialize();
+ case 'jcard':
+ $output = $input->convert(VObject\Document::VCARD40);
+
+ return json_encode($output);
+ }
+ } finally {
+ // Destroy circular references to PHP will GC the object.
+ $input->destroy();
+ if (!is_null($output)) {
+ $output->destroy();
+ }
+ }
+ }
+
+ /**
+ * Returns a plugin name.
+ *
+ * Using this name other plugins will be able to access other plugins
+ * using DAV\Server::getPlugin
+ *
+ * @return string
+ */
+ public function getPluginName()
+ {
+ return 'carddav';
+ }
+
+ /**
+ * 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 CardDAV (rfc6352)',
+ 'link' => 'http://sabre.io/dav/carddav/',
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php
new file mode 100644
index 000000000..431391e04
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php
@@ -0,0 +1,165 @@
+server = $server;
+ $this->server->on('method:GET', [$this, 'httpGet'], 90);
+ $server->on('browserButtonActions', function ($path, $node, &$actions) {
+ if ($node instanceof IAddressBook) {
+ $actions .= '';
+ }
+ });
+ }
+
+ /**
+ * Intercepts GET requests on addressbook urls ending with ?export.
+ *
+ * @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->tree->getNodeForPath($path);
+
+ if (!($node instanceof IAddressBook)) {
+ return;
+ }
+
+ $this->server->transactionType = 'get-addressbook-export';
+
+ // Checking ACL, if available.
+ if ($aclPlugin = $this->server->getPlugin('acl')) {
+ $aclPlugin->checkPrivileges($path, '{DAV:}read');
+ }
+
+ $nodes = $this->server->getPropertiesForPath($path, [
+ '{'.Plugin::NS_CARDDAV.'}address-data',
+ ], 1);
+
+ $format = 'text/directory';
+
+ $output = null;
+ $filenameExtension = null;
+
+ switch ($format) {
+ case 'text/directory':
+ $output = $this->generateVCF($nodes);
+ $filenameExtension = '.vcf';
+ break;
+ }
+
+ $filename = preg_replace(
+ '/[^a-zA-Z0-9-_ ]/um',
+ '',
+ $node->getName()
+ );
+ $filename .= '-'.date('Y-m-d').$filenameExtension;
+
+ $response->setHeader('Content-Disposition', 'attachment; filename="'.$filename.'"');
+ $response->setHeader('Content-Type', $format);
+
+ $response->setStatus(200);
+ $response->setBody($output);
+
+ // Returning false to break the event chain
+ return false;
+ }
+
+ /**
+ * Merges all vcard objects, and builds one big vcf export.
+ *
+ * @return string
+ */
+ public function generateVCF(array $nodes)
+ {
+ $output = '';
+
+ foreach ($nodes as $node) {
+ if (!isset($node[200]['{'.Plugin::NS_CARDDAV.'}address-data'])) {
+ continue;
+ }
+ $nodeData = $node[200]['{'.Plugin::NS_CARDDAV.'}address-data'];
+
+ // Parsing this node so VObject can clean up the output.
+ $vcard = VObject\Reader::read($nodeData);
+ $output .= $vcard->serialize();
+
+ // Destroy circular references to PHP will GC the object.
+ $vcard->destroy();
+ }
+
+ return $output;
+ }
+
+ /**
+ * 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 'vcf-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 CardDAV addressbooks as a single vCard file.',
+ 'link' => 'http://sabre.io/dav/vcf-export-plugin/',
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php
new file mode 100644
index 000000000..b60fcebb6
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php
@@ -0,0 +1,66 @@
+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/vcard',
+ 'version' => $reader->getAttribute('version') ?: '3.0',
+ ];
+
+ $elems = (array) $reader->parseInnerTree();
+ $elems = array_filter($elems, function ($element) {
+ return '{urn:ietf:params:xml:ns:carddav}prop' === $element['name'] &&
+ isset($element['attributes']['name']);
+ });
+ $result['addressDataProperties'] = array_map(function ($element) {
+ return $element['attributes']['name'];
+ }, $elems);
+
+ return $result;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php
new file mode 100644
index 000000000..0a7ec0659
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php
@@ -0,0 +1,86 @@
+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_CARDDAV.'}is-not-defined':
+ $result['is-not-defined'] = true;
+ break;
+ case '{'.Plugin::NS_CARDDAV.'}text-match':
+ $matchType = isset($elem['attributes']['match-type']) ? $elem['attributes']['match-type'] : 'contains';
+
+ if (!in_array($matchType, ['contains', 'equals', 'starts-with', 'ends-with'])) {
+ throw new BadRequest('Unknown match-type: '.$matchType);
+ }
+ $result['text-match'] = [
+ 'negate-condition' => isset($elem['attributes']['negate-condition']) && 'yes' === $elem['attributes']['negate-condition'],
+ 'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;unicode-casemap',
+ 'value' => $elem['value'],
+ 'match-type' => $matchType,
+ ];
+ break;
+ }
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php
new file mode 100644
index 000000000..5dedac800
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php
@@ -0,0 +1,95 @@
+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,
+ 'test' => 'anyof',
+ 'is-not-defined' => false,
+ 'param-filters' => [],
+ 'text-matches' => [],
+ ];
+
+ $att = $reader->parseAttributes();
+ $result['name'] = $att['name'];
+
+ if (isset($att['test']) && 'allof' === $att['test']) {
+ $result['test'] = 'allof';
+ }
+
+ $elems = $reader->parseInnerTree();
+
+ if (is_array($elems)) {
+ foreach ($elems as $elem) {
+ switch ($elem['name']) {
+ case '{'.Plugin::NS_CARDDAV.'}param-filter':
+ $result['param-filters'][] = $elem['value'];
+ break;
+ case '{'.Plugin::NS_CARDDAV.'}is-not-defined':
+ $result['is-not-defined'] = true;
+ break;
+ case '{'.Plugin::NS_CARDDAV.'}text-match':
+ $matchType = isset($elem['attributes']['match-type']) ? $elem['attributes']['match-type'] : 'contains';
+
+ if (!in_array($matchType, ['contains', 'equals', 'starts-with', 'ends-with'])) {
+ throw new BadRequest('Unknown match-type: '.$matchType);
+ }
+ $result['text-matches'][] = [
+ 'negate-condition' => isset($elem['attributes']['negate-condition']) && 'yes' === $elem['attributes']['negate-condition'],
+ 'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;unicode-casemap',
+ 'value' => $elem['value'],
+ 'match-type' => $matchType,
+ ];
+ break;
+ }
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php
new file mode 100644
index 000000000..fe5f976a0
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php
@@ -0,0 +1,77 @@
+ 'text/vcard', 'version' => '3.0'],
+ ['contentType' => 'text/vcard', 'version' => '4.0'],
+ ['contentType' => 'application/vcard+json', 'version' => '4.0'],
+ ];
+ }
+
+ $this->supportedData = $supportedData;
+ }
+
+ /**
+ * 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->supportedData as $supported) {
+ $writer->startElement('{'.Plugin::NS_CARDDAV.'}address-data-type');
+ $writer->writeAttributes([
+ 'content-type' => $supported['contentType'],
+ 'version' => $supported['version'],
+ ]);
+ $writer->endElement(); // address-data-type
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php
new file mode 100644
index 000000000..b19eddd9c
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php
@@ -0,0 +1,44 @@
+writeElement('{urn:ietf:params:xml:ns:carddav}supported-collation', $coll);
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php
new file mode 100644
index 000000000..c11d2dd73
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php
@@ -0,0 +1,109 @@
+next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @return mixed
+ */
+ public static function xmlDeserialize(Reader $reader)
+ {
+ $elems = $reader->parseInnerTree([
+ '{urn:ietf:params:xml:ns:carddav}address-data' => 'Sabre\\CardDAV\\Xml\\Filter\\AddressData',
+ '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue',
+ ]);
+
+ $newProps = [
+ 'hrefs' => [],
+ 'properties' => [],
+ ];
+
+ foreach ($elems as $elem) {
+ switch ($elem['name']) {
+ case '{DAV:}prop':
+ $newProps['properties'] = array_keys($elem['value']);
+ if (isset($elem['value']['{'.Plugin::NS_CARDDAV.'}address-data'])) {
+ $newProps += $elem['value']['{'.Plugin::NS_CARDDAV.'}address-data'];
+ }
+ break;
+ case '{DAV:}href':
+ $newProps['hrefs'][] = Uri\resolve($reader->contextUri, $elem['value']);
+ break;
+ }
+ }
+
+ $obj = new self();
+ foreach ($newProps as $key => $value) {
+ $obj->$key = $value;
+ }
+
+ return $obj;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php
new file mode 100644
index 000000000..d3651ae61
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php
@@ -0,0 +1,194 @@
+next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @return mixed
+ */
+ public static function xmlDeserialize(Reader $reader)
+ {
+ $elems = (array) $reader->parseInnerTree([
+ '{urn:ietf:params:xml:ns:carddav}prop-filter' => 'Sabre\\CardDAV\\Xml\\Filter\\PropFilter',
+ '{urn:ietf:params:xml:ns:carddav}param-filter' => 'Sabre\\CardDAV\\Xml\\Filter\\ParamFilter',
+ '{urn:ietf:params:xml:ns:carddav}address-data' => 'Sabre\\CardDAV\\Xml\\Filter\\AddressData',
+ '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue',
+ ]);
+
+ $newProps = [
+ 'filters' => null,
+ 'properties' => [],
+ 'test' => 'anyof',
+ 'limit' => null,
+ ];
+
+ if (!is_array($elems)) {
+ $elems = [];
+ }
+
+ foreach ($elems as $elem) {
+ switch ($elem['name']) {
+ case '{DAV:}prop':
+ $newProps['properties'] = array_keys($elem['value']);
+ if (isset($elem['value']['{'.Plugin::NS_CARDDAV.'}address-data'])) {
+ $newProps += $elem['value']['{'.Plugin::NS_CARDDAV.'}address-data'];
+ }
+ break;
+ case '{'.Plugin::NS_CARDDAV.'}filter':
+
+ if (!is_null($newProps['filters'])) {
+ throw new BadRequest('You can only include 1 {'.Plugin::NS_CARDDAV.'}filter element');
+ }
+ if (isset($elem['attributes']['test'])) {
+ $newProps['test'] = $elem['attributes']['test'];
+ if ('allof' !== $newProps['test'] && 'anyof' !== $newProps['test']) {
+ throw new BadRequest('The "test" attribute must be one of "allof" or "anyof"');
+ }
+ }
+
+ $newProps['filters'] = [];
+ foreach ((array) $elem['value'] as $subElem) {
+ if ($subElem['name'] === '{'.Plugin::NS_CARDDAV.'}prop-filter') {
+ $newProps['filters'][] = $subElem['value'];
+ }
+ }
+ break;
+ case '{'.Plugin::NS_CARDDAV.'}limit':
+ foreach ($elem['value'] as $child) {
+ if ($child['name'] === '{'.Plugin::NS_CARDDAV.'}nresults') {
+ $newProps['limit'] = (int) $child['value'];
+ }
+ }
+ break;
+ }
+ }
+
+ if (is_null($newProps['filters'])) {
+ /*
+ * We are supposed to throw this error, but KDE sometimes does not
+ * include the filter element, and we need to treat it as if no
+ * filters are supplied
+ */
+ //throw new BadRequest('The {' . Plugin::NS_CARDDAV . '}filter element is required for this request');
+ $newProps['filters'] = [];
+ }
+
+ $obj = new self();
+ foreach ($newProps as $key => $value) {
+ $obj->$key = $value;
+ }
+
+ return $obj;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php
new file mode 100644
index 000000000..3132333b7
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php
@@ -0,0 +1,136 @@
+realm = $realm;
+ }
+
+ /**
+ * When this method is called, the backend must check if authentication was
+ * successful.
+ *
+ * The returned value must be one of the following
+ *
+ * [true, "principals/username"]
+ * [false, "reason for failure"]
+ *
+ * If authentication was successful, it's expected that the authentication
+ * backend returns a so-called principal url.
+ *
+ * Examples of a principal url:
+ *
+ * principals/admin
+ * principals/user1
+ * principals/users/joe
+ * principals/uid/123457
+ *
+ * If you don't use WebDAV ACL (RFC3744) we recommend that you simply
+ * return a string such as:
+ *
+ * principals/users/[username]
+ *
+ * @return array
+ */
+ public function check(RequestInterface $request, ResponseInterface $response)
+ {
+ $auth = new HTTP\Auth\Basic(
+ $this->realm,
+ $request,
+ $response
+ );
+
+ $userpass = $auth->getCredentials();
+ if (!$userpass) {
+ return [false, "No 'Authorization: Basic' header found. Either the client didn't send one, or the server is misconfigured"];
+ }
+ if (!$this->validateUserPass($userpass[0], $userpass[1])) {
+ return [false, 'Username or password was incorrect'];
+ }
+
+ return [true, $this->principalPrefix.$userpass[0]];
+ }
+
+ /**
+ * This method is called when a user could not be authenticated, and
+ * authentication was required for the current request.
+ *
+ * This gives you the opportunity to set authentication headers. The 401
+ * status code will already be set.
+ *
+ * In this case of Basic Auth, this would for example mean that the
+ * following header needs to be set:
+ *
+ * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
+ *
+ * Keep in mind that in the case of multiple authentication backends, other
+ * WWW-Authenticate headers may already have been set, and you'll want to
+ * append your own WWW-Authenticate header instead of overwriting the
+ * existing one.
+ */
+ public function challenge(RequestInterface $request, ResponseInterface $response)
+ {
+ $auth = new HTTP\Auth\Basic(
+ $this->realm,
+ $request,
+ $response
+ );
+ $auth->requireLogin();
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBearer.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBearer.php
new file mode 100644
index 000000000..b6817479f
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBearer.php
@@ -0,0 +1,130 @@
+realm = $realm;
+ }
+
+ /**
+ * When this method is called, the backend must check if authentication was
+ * successful.
+ *
+ * The returned value must be one of the following
+ *
+ * [true, "principals/username"]
+ * [false, "reason for failure"]
+ *
+ * If authentication was successful, it's expected that the authentication
+ * backend returns a so-called principal url.
+ *
+ * Examples of a principal url:
+ *
+ * principals/admin
+ * principals/user1
+ * principals/users/joe
+ * principals/uid/123457
+ *
+ * If you don't use WebDAV ACL (RFC3744) we recommend that you simply
+ * return a string such as:
+ *
+ * principals/users/[username]
+ *
+ * @return array
+ */
+ public function check(RequestInterface $request, ResponseInterface $response)
+ {
+ $auth = new HTTP\Auth\Bearer(
+ $this->realm,
+ $request,
+ $response
+ );
+
+ $bearerToken = $auth->getToken($request);
+ if (!$bearerToken) {
+ return [false, "No 'Authorization: Bearer' header found. Either the client didn't send one, or the server is mis-configured"];
+ }
+ $principalUrl = $this->validateBearerToken($bearerToken);
+ if (!$principalUrl) {
+ return [false, 'Bearer token was incorrect'];
+ }
+
+ return [true, $principalUrl];
+ }
+
+ /**
+ * This method is called when a user could not be authenticated, and
+ * authentication was required for the current request.
+ *
+ * This gives you the opportunity to set authentication headers. The 401
+ * status code will already be set.
+ *
+ * In this case of Bearer Auth, this would for example mean that the
+ * following header needs to be set:
+ *
+ * $response->addHeader('WWW-Authenticate', 'Bearer realm=SabreDAV');
+ *
+ * Keep in mind that in the case of multiple authentication backends, other
+ * WWW-Authenticate headers may already have been set, and you'll want to
+ * append your own WWW-Authenticate header instead of overwriting the
+ * existing one.
+ */
+ public function challenge(RequestInterface $request, ResponseInterface $response)
+ {
+ $auth = new HTTP\Auth\Bearer(
+ $this->realm,
+ $request,
+ $response
+ );
+ $auth->requireLogin();
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php
new file mode 100644
index 000000000..297655da3
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php
@@ -0,0 +1,160 @@
+realm = $realm;
+ }
+
+ /**
+ * Returns a users digest hash based on the username and realm.
+ *
+ * If the user was not known, null must be returned.
+ *
+ * @param string $realm
+ * @param string $username
+ *
+ * @return string|null
+ */
+ abstract public function getDigestHash($realm, $username);
+
+ /**
+ * When this method is called, the backend must check if authentication was
+ * successful.
+ *
+ * The returned value must be one of the following
+ *
+ * [true, "principals/username"]
+ * [false, "reason for failure"]
+ *
+ * If authentication was successful, it's expected that the authentication
+ * backend returns a so-called principal url.
+ *
+ * Examples of a principal url:
+ *
+ * principals/admin
+ * principals/user1
+ * principals/users/joe
+ * principals/uid/123457
+ *
+ * If you don't use WebDAV ACL (RFC3744) we recommend that you simply
+ * return a string such as:
+ *
+ * principals/users/[username]
+ *
+ * @return array
+ */
+ public function check(RequestInterface $request, ResponseInterface $response)
+ {
+ $digest = new HTTP\Auth\Digest(
+ $this->realm,
+ $request,
+ $response
+ );
+ $digest->init();
+
+ $username = $digest->getUsername();
+
+ // No username was given
+ if (!$username) {
+ return [false, "No 'Authorization: Digest' header found. Either the client didn't send one, or the server is misconfigured"];
+ }
+
+ $hash = $this->getDigestHash($this->realm, $username);
+ // If this was false, the user account didn't exist
+ if (false === $hash || is_null($hash)) {
+ return [false, 'Username or password was incorrect'];
+ }
+ if (!is_string($hash)) {
+ throw new DAV\Exception('The returned value from getDigestHash must be a string or null');
+ }
+
+ // If this was false, the password or part of the hash was incorrect.
+ if (!$digest->validateA1($hash)) {
+ return [false, 'Username or password was incorrect'];
+ }
+
+ return [true, $this->principalPrefix.$username];
+ }
+
+ /**
+ * This method is called when a user could not be authenticated, and
+ * authentication was required for the current request.
+ *
+ * This gives you the opportunity to set authentication headers. The 401
+ * status code will already be set.
+ *
+ * In this case of Basic Auth, this would for example mean that the
+ * following header needs to be set:
+ *
+ * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
+ *
+ * Keep in mind that in the case of multiple authentication backends, other
+ * WWW-Authenticate headers may already have been set, and you'll want to
+ * append your own WWW-Authenticate header instead of overwriting the
+ * existing one.
+ */
+ public function challenge(RequestInterface $request, ResponseInterface $response)
+ {
+ $auth = new HTTP\Auth\Digest(
+ $this->realm,
+ $request,
+ $response
+ );
+ $auth->init();
+
+ $oldStatus = $response->getStatus() ?: 200;
+ $auth->requireLogin();
+
+ // Preventing the digest utility from modifying the http status code,
+ // this should be handled by the main plugin.
+ $response->setStatus($oldStatus);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php
new file mode 100644
index 000000000..ebf67cab2
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php
@@ -0,0 +1,93 @@
+getRawServerValue('REMOTE_USER');
+ if (is_null($remoteUser)) {
+ $remoteUser = $request->getRawServerValue('REDIRECT_REMOTE_USER');
+ }
+ if (is_null($remoteUser)) {
+ $remoteUser = $request->getRawServerValue('PHP_AUTH_USER');
+ }
+ if (is_null($remoteUser)) {
+ return [false, 'No REMOTE_USER, REDIRECT_REMOTE_USER, or PHP_AUTH_USER property was found in the PHP $_SERVER super-global. This likely means your server is not configured correctly'];
+ }
+
+ return [true, $this->principalPrefix.$remoteUser];
+ }
+
+ /**
+ * This method is called when a user could not be authenticated, and
+ * authentication was required for the current request.
+ *
+ * This gives you the opportunity to set authentication headers. The 401
+ * status code will already be set.
+ *
+ * In this case of Basic Auth, this would for example mean that the
+ * following header needs to be set:
+ *
+ * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
+ *
+ * Keep in mind that in the case of multiple authentication backends, other
+ * WWW-Authenticate headers may already have been set, and you'll want to
+ * append your own WWW-Authenticate header instead of overwriting the
+ * existing one.
+ */
+ public function challenge(RequestInterface $request, ResponseInterface $response)
+ {
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php
new file mode 100644
index 000000000..133eac926
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php
@@ -0,0 +1,65 @@
+addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
+ *
+ * Keep in mind that in the case of multiple authentication backends, other
+ * WWW-Authenticate headers may already have been set, and you'll want to
+ * append your own WWW-Authenticate header instead of overwriting the
+ * existing one.
+ */
+ public function challenge(RequestInterface $request, ResponseInterface $response);
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php
new file mode 100644
index 000000000..5a8bb98ce
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php
@@ -0,0 +1,56 @@
+callBack = $callBack;
+ }
+
+ /**
+ * Validates a username and password.
+ *
+ * This method should return true or false depending on if login
+ * succeeded.
+ *
+ * @param string $username
+ * @param string $password
+ *
+ * @return bool
+ */
+ protected function validateUserPass($username, $password)
+ {
+ $cb = $this->callBack;
+
+ return $cb($username, $password);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/File.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/File.php
new file mode 100644
index 000000000..ea2d39679
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/File.php
@@ -0,0 +1,74 @@
+loadFile($filename);
+ }
+ }
+
+ /**
+ * Loads an htdigest-formatted file. This method can be called multiple times if
+ * more than 1 file is used.
+ *
+ * @param string $filename
+ */
+ public function loadFile($filename)
+ {
+ foreach (file($filename, FILE_IGNORE_NEW_LINES) as $line) {
+ if (2 !== substr_count($line, ':')) {
+ throw new DAV\Exception('Malformed htdigest file. Every line should contain 2 colons');
+ }
+ list($username, $realm, $A1) = explode(':', $line);
+
+ if (!preg_match('/^[a-zA-Z0-9]{32}$/', $A1)) {
+ throw new DAV\Exception('Malformed htdigest file. Invalid md5 hash');
+ }
+ $this->users[$realm.':'.$username] = $A1;
+ }
+ }
+
+ /**
+ * Returns a users' information.
+ *
+ * @param string $realm
+ * @param string $username
+ *
+ * @return string
+ */
+ public function getDigestHash($realm, $username)
+ {
+ return isset($this->users[$realm.':'.$username]) ? $this->users[$realm.':'.$username] : false;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/IMAP.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/IMAP.php
new file mode 100644
index 000000000..3a1831116
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/IMAP.php
@@ -0,0 +1,82 @@
+mailbox = $mailbox;
+ }
+
+ /**
+ * Connects to an IMAP server and tries to authenticate.
+ *
+ * @param string $username
+ * @param string $password
+ *
+ * @return bool
+ */
+ protected function imapOpen($username, $password)
+ {
+ $success = false;
+
+ try {
+ $imap = imap_open($this->mailbox, $username, $password, OP_HALFOPEN | OP_READONLY, 1);
+ if ($imap) {
+ $success = true;
+ }
+ } catch (\ErrorException $e) {
+ error_log($e->getMessage());
+ }
+
+ $errors = imap_errors();
+ if ($errors) {
+ foreach ($errors as $error) {
+ error_log($error);
+ }
+ }
+
+ if (isset($imap) && $imap) {
+ imap_close($imap);
+ }
+
+ return $success;
+ }
+
+ /**
+ * Validates a username and password by trying to authenticate against IMAP.
+ *
+ * @param string $username
+ * @param string $password
+ *
+ * @return bool
+ */
+ protected function validateUserPass($username, $password)
+ {
+ return $this->imapOpen($username, $password);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php
new file mode 100644
index 000000000..9a06912d1
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php
@@ -0,0 +1,55 @@
+pdo = $pdo;
+ }
+
+ /**
+ * Returns the digest hash for a user.
+ *
+ * @param string $realm
+ * @param string $username
+ *
+ * @return string|null
+ */
+ public function getDigestHash($realm, $username)
+ {
+ $stmt = $this->pdo->prepare('SELECT digesta1 FROM '.$this->tableName.' WHERE username = ?');
+ $stmt->execute([$username]);
+
+ return $stmt->fetchColumn() ?: null;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Plugin.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Plugin.php
new file mode 100644
index 000000000..68adbede5
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Auth/Plugin.php
@@ -0,0 +1,259 @@
+addBackend($authBackend);
+ }
+ }
+
+ /**
+ * Adds an authentication backend to the plugin.
+ */
+ public function addBackend(Backend\BackendInterface $authBackend)
+ {
+ $this->backends[] = $authBackend;
+ }
+
+ /**
+ * Initializes the plugin. This function is automatically called by the server.
+ */
+ public function initialize(Server $server)
+ {
+ $server->on('beforeMethod:*', [$this, 'beforeMethod'], 10);
+ }
+
+ /**
+ * Returns a plugin name.
+ *
+ * Using this name other plugins will be able to access other plugins
+ * using DAV\Server::getPlugin
+ *
+ * @return string
+ */
+ public function getPluginName()
+ {
+ return 'auth';
+ }
+
+ /**
+ * Returns the currently logged-in principal.
+ *
+ * This will return a string such as:
+ *
+ * principals/username
+ * principals/users/username
+ *
+ * This method will return null if nobody is logged in.
+ *
+ * @return string|null
+ */
+ public function getCurrentPrincipal()
+ {
+ return $this->currentPrincipal;
+ }
+
+ /**
+ * This method is called before any HTTP method and forces users to be authenticated.
+ *
+ * @return bool
+ */
+ public function beforeMethod(RequestInterface $request, ResponseInterface $response)
+ {
+ if ($this->currentPrincipal) {
+ // We already have authentication information. This means that the
+ // event has already fired earlier, and is now likely fired for a
+ // sub-request.
+ //
+ // We don't want to authenticate users twice, so we simply don't do
+ // anything here. See Issue #700 for additional reasoning.
+ //
+ // This is not a perfect solution, but will be fixed once the
+ // "currently authenticated principal" is information that's not
+ // not associated with the plugin, but rather per-request.
+ //
+ // See issue #580 for more information about that.
+ return;
+ }
+
+ $authResult = $this->check($request, $response);
+
+ if ($authResult[0]) {
+ // Auth was successful
+ $this->currentPrincipal = $authResult[1];
+ $this->loginFailedReasons = null;
+
+ return;
+ }
+
+ // If we got here, it means that no authentication backend was
+ // successful in authenticating the user.
+ $this->currentPrincipal = null;
+ $this->loginFailedReasons = $authResult[1];
+
+ if ($this->autoRequireLogin) {
+ $this->challenge($request, $response);
+ throw new NotAuthenticated(implode(', ', $authResult[1]));
+ }
+ }
+
+ /**
+ * Checks authentication credentials, and logs the user in if possible.
+ *
+ * This method returns an array. The first item in the array is a boolean
+ * indicating if login was successful.
+ *
+ * If login was successful, the second item in the array will contain the
+ * current principal url/path of the logged in user.
+ *
+ * If login was not successful, the second item in the array will contain a
+ * an array with strings. The strings are a list of reasons why login was
+ * unsuccessful. For every auth backend there will be one reason, so usually
+ * there's just one.
+ *
+ * @return array
+ */
+ public function check(RequestInterface $request, ResponseInterface $response)
+ {
+ if (!$this->backends) {
+ throw new \Sabre\DAV\Exception('No authentication backends were configured on this server.');
+ }
+ $reasons = [];
+ foreach ($this->backends as $backend) {
+ $result = $backend->check(
+ $request,
+ $response
+ );
+
+ if (!is_array($result) || 2 !== count($result) || !is_bool($result[0]) || !is_string($result[1])) {
+ throw new \Sabre\DAV\Exception('The authentication backend did not return a correct value from the check() method.');
+ }
+
+ if ($result[0]) {
+ $this->currentPrincipal = $result[1];
+ // Exit early
+ return [true, $result[1]];
+ }
+ $reasons[] = $result[1];
+ }
+
+ return [false, $reasons];
+ }
+
+ /**
+ * This method sends authentication challenges to the user.
+ *
+ * This method will for example cause a HTTP Basic backend to set a
+ * WWW-Authorization header, indicating to the client that it should
+ * authenticate.
+ *
+ * @return array
+ */
+ public function challenge(RequestInterface $request, ResponseInterface $response)
+ {
+ foreach ($this->backends as $backend) {
+ $backend->challenge($request, $response);
+ }
+ }
+
+ /**
+ * List of reasons why login failed for the last login operation.
+ *
+ * @var string[]|null
+ */
+ protected $loginFailedReasons;
+
+ /**
+ * Returns a list of reasons why login was unsuccessful.
+ *
+ * This method will return the login failed reasons for the last login
+ * operation. One for each auth backend.
+ *
+ * This method returns null if the last authentication attempt was
+ * successful, or if there was no authentication attempt yet.
+ *
+ * @return string[]|null
+ */
+ public function getLoginFailedReasons()
+ {
+ return $this->loginFailedReasons;
+ }
+
+ /**
+ * 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' => 'Generic authentication plugin',
+ 'link' => 'http://sabre.io/dav/authentication/',
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/GuessContentType.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/GuessContentType.php
new file mode 100644
index 000000000..5cda0b842
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/GuessContentType.php
@@ -0,0 +1,93 @@
+ 'image/jpeg',
+ 'gif' => 'image/gif',
+ 'png' => 'image/png',
+
+ // groupware
+ 'ics' => 'text/calendar',
+ 'vcf' => 'text/vcard',
+
+ // text
+ 'txt' => 'text/plain',
+ ];
+
+ /**
+ * Initializes the plugin.
+ */
+ public function initialize(DAV\Server $server)
+ {
+ // Using a relatively low priority (200) to allow other extensions
+ // to set the content-type first.
+ $server->on('propFind', [$this, 'propFind'], 200);
+ }
+
+ /**
+ * Our PROPFIND handler.
+ *
+ * Here we set a contenttype, if the node didn't already have one.
+ */
+ public function propFind(PropFind $propFind, INode $node)
+ {
+ $propFind->handle('{DAV:}getcontenttype', function () use ($propFind) {
+ list(, $fileName) = Uri\split($propFind->getPath());
+
+ return $this->getContentType($fileName);
+ });
+ }
+
+ /**
+ * Simple method to return the contenttype.
+ *
+ * @param string $fileName
+ *
+ * @return string
+ */
+ protected function getContentType($fileName)
+ {
+ if (null !== $fileName) {
+ // Just grabbing the extension
+ $extension = strtolower(substr($fileName, strrpos($fileName, '.') + 1));
+ if (isset($this->extensionMap[$extension])) {
+ return $this->extensionMap[$extension];
+ }
+ }
+
+ return 'application/octet-stream';
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php
new file mode 100644
index 000000000..be5a28456
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php
@@ -0,0 +1,34 @@
+baseUri = $baseUri;
+ $this->namespaceMap = $namespaceMap;
+ }
+
+ /**
+ * Generates a 'full' url based on a relative one.
+ *
+ * For relative urls, the base of the application is taken as the reference
+ * url, not the 'current url of the current request'.
+ *
+ * Absolute urls are left alone.
+ *
+ * @param string $path
+ *
+ * @return string
+ */
+ public function fullUrl($path)
+ {
+ return Uri\resolve($this->baseUri, $path);
+ }
+
+ /**
+ * Escape string for HTML output.
+ *
+ * @param scalar $input
+ *
+ * @return string
+ */
+ public function h($input)
+ {
+ return htmlspecialchars((string) $input, ENT_COMPAT, 'UTF-8');
+ }
+
+ /**
+ * Generates a full -tag.
+ *
+ * Url is automatically expanded. If label is not specified, we re-use the
+ * url.
+ *
+ * @param string $url
+ * @param string $label
+ *
+ * @return string
+ */
+ public function link($url, $label = null)
+ {
+ $url = $this->h($this->fullUrl($url));
+
+ return ''.($label ? $this->h($label) : $url).'';
+ }
+
+ /**
+ * This method takes an xml element in clark-notation, and turns it into a
+ * shortened version with a prefix, if it was a known namespace.
+ *
+ * @param string $element
+ *
+ * @return string
+ */
+ public function xmlName($element)
+ {
+ list($ns, $localName) = XmlService::parseClarkNotation($element);
+ if (isset($this->namespaceMap[$ns])) {
+ $propName = $this->namespaceMap[$ns].':'.$localName;
+ } else {
+ $propName = $element;
+ }
+
+ return ''.$this->h($propName).'';
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php
new file mode 100644
index 000000000..0bbe70c66
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php
@@ -0,0 +1,58 @@
+server = $server;
+ $this->server->on('method:GET', [$this, 'httpGet'], 90);
+ }
+
+ /**
+ * This method intercepts GET requests to non-files, and changes it into an HTTP PROPFIND request.
+ *
+ * @return bool
+ */
+ public function httpGet(RequestInterface $request, ResponseInterface $response)
+ {
+ $node = $this->server->tree->getNodeForPath($request->getPath());
+ if ($node instanceof DAV\IFile) {
+ return;
+ }
+
+ $subRequest = clone $request;
+ $subRequest->setMethod('PROPFIND');
+
+ $this->server->invokeMethod($subRequest, $response);
+
+ return false;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/Plugin.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/Plugin.php
new file mode 100644
index 000000000..915f2895b
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/Plugin.php
@@ -0,0 +1,789 @@
+enablePost = $enablePost;
+ }
+
+ /**
+ * Initializes the plugin and subscribes to events.
+ */
+ public function initialize(DAV\Server $server)
+ {
+ $this->server = $server;
+ $this->server->on('method:GET', [$this, 'httpGetEarly'], 90);
+ $this->server->on('method:GET', [$this, 'httpGet'], 200);
+ $this->server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel'], 200);
+ if ($this->enablePost) {
+ $this->server->on('method:POST', [$this, 'httpPOST']);
+ }
+ }
+
+ /**
+ * This method intercepts GET requests that have ?sabreAction=info
+ * appended to the URL.
+ *
+ * @return bool
+ */
+ public function httpGetEarly(RequestInterface $request, ResponseInterface $response)
+ {
+ $params = $request->getQueryParameters();
+ if (isset($params['sabreAction']) && 'info' === $params['sabreAction']) {
+ return $this->httpGet($request, $response);
+ }
+ }
+
+ /**
+ * This method intercepts GET requests to collections and returns the html.
+ *
+ * @return bool
+ */
+ public function httpGet(RequestInterface $request, ResponseInterface $response)
+ {
+ // We're not using straight-up $_GET, because we want everything to be
+ // unit testable.
+ $getVars = $request->getQueryParameters();
+
+ // CSP headers
+ $response->setHeader('Content-Security-Policy', "default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';");
+
+ $sabreAction = isset($getVars['sabreAction']) ? $getVars['sabreAction'] : null;
+
+ switch ($sabreAction) {
+ case 'asset':
+ // Asset handling, such as images
+ $this->serveAsset(isset($getVars['assetName']) ? $getVars['assetName'] : null);
+
+ return false;
+ default:
+ case 'info':
+ try {
+ $this->server->tree->getNodeForPath($request->getPath());
+ } catch (DAV\Exception\NotFound $e) {
+ // We're simply stopping when the file isn't found to not interfere
+ // with other plugins.
+ return;
+ }
+
+ $response->setStatus(200);
+ $response->setHeader('Content-Type', 'text/html; charset=utf-8');
+
+ $response->setBody(
+ $this->generateDirectoryIndex($request->getPath())
+ );
+
+ return false;
+
+ case 'plugins':
+ $response->setStatus(200);
+ $response->setHeader('Content-Type', 'text/html; charset=utf-8');
+
+ $response->setBody(
+ $this->generatePluginListing()
+ );
+
+ return false;
+ }
+ }
+
+ /**
+ * Handles POST requests for tree operations.
+ *
+ * @return bool
+ */
+ public function httpPOST(RequestInterface $request, ResponseInterface $response)
+ {
+ $contentType = $request->getHeader('Content-Type');
+ list($contentType) = explode(';', $contentType);
+ if ('application/x-www-form-urlencoded' !== $contentType &&
+ 'multipart/form-data' !== $contentType) {
+ return;
+ }
+ $postVars = $request->getPostData();
+
+ if (!isset($postVars['sabreAction'])) {
+ return;
+ }
+
+ $uri = $request->getPath();
+
+ if ($this->server->emit('onBrowserPostAction', [$uri, $postVars['sabreAction'], $postVars])) {
+ switch ($postVars['sabreAction']) {
+ case 'mkcol':
+ if (isset($postVars['name']) && trim($postVars['name'])) {
+ // Using basename() because we won't allow slashes
+ list(, $folderName) = Uri\split(trim($postVars['name']));
+
+ if (isset($postVars['resourceType'])) {
+ $resourceType = explode(',', $postVars['resourceType']);
+ } else {
+ $resourceType = ['{DAV:}collection'];
+ }
+
+ $properties = [];
+ foreach ($postVars as $varName => $varValue) {
+ // Any _POST variable in clark notation is treated
+ // like a property.
+ if ('{' === $varName[0]) {
+ // PHP will convert any dots to underscores.
+ // This leaves us with no way to differentiate
+ // the two.
+ // Therefore we replace the string *DOT* with a
+ // real dot. * is not allowed in uris so we
+ // should be good.
+ $varName = str_replace('*DOT*', '.', $varName);
+ $properties[$varName] = $varValue;
+ }
+ }
+
+ $mkCol = new MkCol(
+ $resourceType,
+ $properties
+ );
+ $this->server->createCollection($uri.'/'.$folderName, $mkCol);
+ }
+ break;
+
+ // @codeCoverageIgnoreStart
+ case 'put':
+
+ if ($_FILES) {
+ $file = current($_FILES);
+ } else {
+ break;
+ }
+
+ list(, $newName) = Uri\split(trim($file['name']));
+ if (isset($postVars['name']) && trim($postVars['name'])) {
+ $newName = trim($postVars['name']);
+ }
+
+ // Making sure we only have a 'basename' component
+ list(, $newName) = Uri\split($newName);
+
+ if (is_uploaded_file($file['tmp_name'])) {
+ $this->server->createFile($uri.'/'.$newName, fopen($file['tmp_name'], 'r'));
+ }
+ break;
+ // @codeCoverageIgnoreEnd
+ }
+ }
+ $response->setHeader('Location', $request->getUrl());
+ $response->setStatus(302);
+
+ return false;
+ }
+
+ /**
+ * Escapes a string for html.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function escapeHTML($value)
+ {
+ return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
+ }
+
+ /**
+ * Generates the html directory index for a given url.
+ *
+ * @param string $path
+ *
+ * @return string
+ */
+ public function generateDirectoryIndex($path)
+ {
+ $html = $this->generateHeader($path ? $path : '/', $path);
+
+ $node = $this->server->tree->getNodeForPath($path);
+ if ($node instanceof DAV\ICollection) {
+ $html .= "
+
+
+ ';
+
+ return $html;
+ }
+
+ /**
+ * Generates the page footer.
+ *
+ * Returns html.
+ *
+ * @return string
+ */
+ public function generateFooter()
+ {
+ $version = '';
+ if (DAV\Server::$exposeVersion) {
+ $version = DAV\Version::VERSION;
+ }
+ $year = date('Y');
+
+ return <<Generated by SabreDAV $version (c)2007-$year http://sabre.io/
+
+
+HTML;
+ }
+
+ /**
+ * This method is used to generate the 'actions panel' output for
+ * collections.
+ *
+ * This specifically generates the interfaces for creating new files, and
+ * creating new directories.
+ *
+ * @param mixed $output
+ * @param string $path
+ */
+ public function htmlActionsPanel(DAV\INode $node, &$output, $path)
+ {
+ if (!$node instanceof DAV\ICollection) {
+ return;
+ }
+
+ // We also know fairly certain that if an object is a non-extended
+ // SimpleCollection, we won't need to show the panel either.
+ if ('Sabre\\DAV\\SimpleCollection' === get_class($node)) {
+ return;
+ }
+
+ $output .= <<
+
Create new folder
+
+
+
+
+
+HTML;
+ }
+
+ /**
+ * This method takes a path/name of an asset and turns it into url
+ * suiteable for http access.
+ *
+ * @param string $assetName
+ *
+ * @return string
+ */
+ protected function getAssetUrl($assetName)
+ {
+ return $this->server->getBaseUri().'?sabreAction=asset&assetName='.urlencode($assetName);
+ }
+
+ /**
+ * This method returns a local pathname to an asset.
+ *
+ * @param string $assetName
+ *
+ * @throws DAV\Exception\NotFound
+ *
+ * @return string
+ */
+ protected function getLocalAssetPath($assetName)
+ {
+ $assetDir = __DIR__.'/assets/';
+ $path = $assetDir.$assetName;
+
+ // Making sure people aren't trying to escape from the base path.
+ $path = str_replace('\\', '/', $path);
+ if (false !== strpos($path, '/../') || '/..' === strrchr($path, '/')) {
+ throw new DAV\Exception\NotFound('Path does not exist, or escaping from the base path was detected');
+ }
+ $realPath = realpath($path);
+ if ($realPath && 0 === strpos($realPath, realpath($assetDir)) && file_exists($path)) {
+ return $path;
+ }
+ throw new DAV\Exception\NotFound('Path does not exist, or escaping from the base path was detected');
+ }
+
+ /**
+ * This method reads an asset from disk and generates a full http response.
+ *
+ * @param string $assetName
+ */
+ protected function serveAsset($assetName)
+ {
+ $assetPath = $this->getLocalAssetPath($assetName);
+
+ // Rudimentary mime type detection
+ $mime = 'application/octet-stream';
+ $map = [
+ 'ico' => 'image/vnd.microsoft.icon',
+ 'png' => 'image/png',
+ 'css' => 'text/css',
+ ];
+
+ $ext = substr($assetName, strrpos($assetName, '.') + 1);
+ if (isset($map[$ext])) {
+ $mime = $map[$ext];
+ }
+
+ $this->server->httpResponse->setHeader('Content-Type', $mime);
+ $this->server->httpResponse->setHeader('Content-Length', filesize($assetPath));
+ $this->server->httpResponse->setHeader('Cache-Control', 'public, max-age=1209600');
+ $this->server->httpResponse->setStatus(200);
+ $this->server->httpResponse->setBody(fopen($assetPath, 'r'));
+ }
+
+ /**
+ * Sort helper function: compares two directory entries based on type and
+ * display name. Collections sort above other types.
+ *
+ * @param array $a
+ * @param array $b
+ *
+ * @return int
+ */
+ protected function compareNodes($a, $b)
+ {
+ $typeA = (isset($a['{DAV:}resourcetype']))
+ ? (in_array('{DAV:}collection', $a['{DAV:}resourcetype']->getValue()))
+ : false;
+
+ $typeB = (isset($b['{DAV:}resourcetype']))
+ ? (in_array('{DAV:}collection', $b['{DAV:}resourcetype']->getValue()))
+ : false;
+
+ // If same type, sort alphabetically by filename:
+ if ($typeA === $typeB) {
+ return strnatcasecmp($a['displayPath'], $b['displayPath']);
+ }
+
+ return ($typeA < $typeB) ? 1 : -1;
+ }
+
+ /**
+ * Maps a resource type to a human-readable string and icon.
+ *
+ * @param DAV\INode $node
+ *
+ * @return array
+ */
+ private function mapResourceType(array $resourceTypes, $node)
+ {
+ if (!$resourceTypes) {
+ if ($node instanceof DAV\IFile) {
+ return [
+ 'string' => 'File',
+ 'icon' => 'file',
+ ];
+ } else {
+ return [
+ 'string' => 'Unknown',
+ 'icon' => 'cog',
+ ];
+ }
+ }
+
+ $types = [
+ '{http://calendarserver.org/ns/}calendar-proxy-write' => [
+ 'string' => 'Proxy-Write',
+ 'icon' => 'people',
+ ],
+ '{http://calendarserver.org/ns/}calendar-proxy-read' => [
+ 'string' => 'Proxy-Read',
+ 'icon' => 'people',
+ ],
+ '{urn:ietf:params:xml:ns:caldav}schedule-outbox' => [
+ 'string' => 'Outbox',
+ 'icon' => 'inbox',
+ ],
+ '{urn:ietf:params:xml:ns:caldav}schedule-inbox' => [
+ 'string' => 'Inbox',
+ 'icon' => 'inbox',
+ ],
+ '{urn:ietf:params:xml:ns:caldav}calendar' => [
+ 'string' => 'Calendar',
+ 'icon' => 'calendar',
+ ],
+ '{http://calendarserver.org/ns/}shared-owner' => [
+ 'string' => 'Shared',
+ 'icon' => 'calendar',
+ ],
+ '{http://calendarserver.org/ns/}subscribed' => [
+ 'string' => 'Subscription',
+ 'icon' => 'calendar',
+ ],
+ '{urn:ietf:params:xml:ns:carddav}directory' => [
+ 'string' => 'Directory',
+ 'icon' => 'globe',
+ ],
+ '{urn:ietf:params:xml:ns:carddav}addressbook' => [
+ 'string' => 'Address book',
+ 'icon' => 'book',
+ ],
+ '{DAV:}principal' => [
+ 'string' => 'Principal',
+ 'icon' => 'person',
+ ],
+ '{DAV:}collection' => [
+ 'string' => 'Collection',
+ 'icon' => 'folder',
+ ],
+ ];
+
+ $info = [
+ 'string' => [],
+ 'icon' => 'cog',
+ ];
+ foreach ($resourceTypes as $k => $resourceType) {
+ if (isset($types[$resourceType])) {
+ $info['string'][] = $types[$resourceType]['string'];
+ } else {
+ $info['string'][] = $resourceType;
+ }
+ }
+ foreach ($types as $key => $resourceInfo) {
+ if (in_array($key, $resourceTypes)) {
+ $info['icon'] = $resourceInfo['icon'];
+ break;
+ }
+ }
+ $info['string'] = implode(', ', $info['string']);
+
+ return $info;
+ }
+
+ /**
+ * Draws a table row for a property.
+ *
+ * @param string $name
+ * @param mixed $value
+ *
+ * @return string
+ */
+ private function drawPropertyRow($name, $value)
+ {
+ $html = new HtmlOutputHelper(
+ $this->server->getBaseUri(),
+ $this->server->xml->namespaceMap
+ );
+
+ return '
'.$html->xmlName($name).'
'.$this->drawPropertyValue($html, $value).'
';
+ }
+
+ /**
+ * Draws a table row for a property.
+ *
+ * @param HtmlOutputHelper $html
+ * @param mixed $value
+ *
+ * @return string
+ */
+ private function drawPropertyValue($html, $value)
+ {
+ if (is_scalar($value)) {
+ return $html->h($value);
+ } elseif ($value instanceof HtmlOutput) {
+ return $value->toHtml($html);
+ } elseif ($value instanceof \Sabre\Xml\XmlSerializable) {
+ // There's no default html output for this property, we're going
+ // to output the actual xml serialization instead.
+ $xml = $this->server->xml->write('{DAV:}root', $value, $this->server->getBaseUri());
+ // removing first and last line, as they contain our root
+ // element.
+ $xml = explode("\n", $xml);
+ $xml = array_slice($xml, 2, -2);
+
+ return '
'.$html->h(implode("\n", $xml)).'
';
+ } else {
+ return 'unknown';
+ }
+ }
+
+ /**
+ * 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 'browser';
+ }
+
+ /**
+ * 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' => 'Generates HTML indexes and debug information for your sabre/dav server',
+ 'link' => 'http://sabre.io/dav/browser-plugin/',
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/PropFindAll.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/PropFindAll.php
new file mode 100644
index 000000000..34702bdd8
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/PropFindAll.php
@@ -0,0 +1,128 @@
+handle('{DAV:}displayname', function() {
+ * return 'hello';
+ * });
+ *
+ * Note that handle will only work the first time. If null is returned, the
+ * value is ignored.
+ *
+ * It's also possible to not pass a callback, but immediately pass a value
+ *
+ * @param string $propertyName
+ * @param mixed $valueOrCallBack
+ */
+ public function handle($propertyName, $valueOrCallBack)
+ {
+ if (is_callable($valueOrCallBack)) {
+ $value = $valueOrCallBack();
+ } else {
+ $value = $valueOrCallBack;
+ }
+ if (!is_null($value)) {
+ $this->result[$propertyName] = [200, $value];
+ }
+ }
+
+ /**
+ * Sets the value of the property.
+ *
+ * If status is not supplied, the status will default to 200 for non-null
+ * properties, and 404 for null properties.
+ *
+ * @param string $propertyName
+ * @param mixed $value
+ * @param int $status
+ */
+ public function set($propertyName, $value, $status = null)
+ {
+ if (is_null($status)) {
+ $status = is_null($value) ? 404 : 200;
+ }
+ $this->result[$propertyName] = [$status, $value];
+ }
+
+ /**
+ * Returns the current value for a property.
+ *
+ * @param string $propertyName
+ *
+ * @return mixed
+ */
+ public function get($propertyName)
+ {
+ return isset($this->result[$propertyName]) ? $this->result[$propertyName][1] : null;
+ }
+
+ /**
+ * Returns the current status code for a property name.
+ *
+ * If the property does not appear in the list of requested properties,
+ * null will be returned.
+ *
+ * @param string $propertyName
+ *
+ * @return int|null
+ */
+ public function getStatus($propertyName)
+ {
+ return isset($this->result[$propertyName]) ? $this->result[$propertyName][0] : 404;
+ }
+
+ /**
+ * Returns all propertynames that have a 404 status, and thus don't have a
+ * value yet.
+ *
+ * @return array
+ */
+ public function get404Properties()
+ {
+ $result = [];
+ foreach ($this->result as $propertyName => $stuff) {
+ if (404 === $stuff[0]) {
+ $result[] = $propertyName;
+ }
+ }
+ // If there's nothing in this list, we're adding one fictional item.
+ if (!$result) {
+ $result[] = '{http://sabredav.org/ns}idk';
+ }
+
+ return $result;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/favicon.ico b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/favicon.ico
new file mode 100644
index 000000000..2b2c10a22
Binary files /dev/null and b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/favicon.ico differ
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/ICON-LICENSE b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/ICON-LICENSE
new file mode 100644
index 000000000..2199f4a69
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/ICON-LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Waybury
+
+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.
\ No newline at end of file
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.css b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.css
new file mode 100644
index 000000000..e74867400
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.css
@@ -0,0 +1,510 @@
+@font-face {
+ font-family: 'Icons';
+ src: url('?sabreAction=asset&assetName=openiconic/open-iconic.eot');
+ src: url('?sabreAction=asset&assetName=openiconic/open-iconic.eot?#iconic-sm') format('embedded-opentype'), url('?sabreAction=asset&assetName=openiconic/open-iconic.woff') format('woff'), url('?sabreAction=asset&assetName=openiconic/open-iconic.ttf') format('truetype'), url('?sabreAction=asset&assetName=openiconic/open-iconic.otf') format('opentype'), url('?sabreAction=asset&assetName=openiconic/open-iconic.svg#iconic-sm') format('svg');
+ font-weight: normal;
+ font-style: normal;
+}
+
+.oi[data-glyph].oi-text-replace {
+ font-size: 0;
+ line-height: 0;
+}
+
+.oi[data-glyph].oi-text-replace:before {
+ width: 1em;
+ text-align: center;
+}
+
+.oi[data-glyph]:before {
+ font-family: 'Icons';
+ display: inline-block;
+ speak: none;
+ line-height: 1;
+ vertical-align: baseline;
+ font-weight: normal;
+ font-style: normal;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.oi[data-glyph]:empty:before {
+ width: 1em;
+ text-align: center;
+ box-sizing: content-box;
+}
+
+.oi[data-glyph].oi-align-left:before {
+ text-align: left;
+}
+
+.oi[data-glyph].oi-align-right:before {
+ text-align: right;
+}
+
+.oi[data-glyph].oi-align-center:before {
+ text-align: center;
+}
+
+.oi[data-glyph].oi-flip-horizontal:before {
+ -webkit-transform: scale(-1, 1);
+ -ms-transform: scale(-1, 1);
+ transform: scale(-1, 1);
+}
+.oi[data-glyph].oi-flip-vertical:before {
+ -webkit-transform: scale(1, -1);
+ -ms-transform: scale(-1, 1);
+ transform: scale(1, -1);
+}
+.oi[data-glyph].oi-flip-horizontal-vertical:before {
+ -webkit-transform: scale(-1, -1);
+ -ms-transform: scale(-1, 1);
+ transform: scale(-1, -1);
+}
+
+
+.oi[data-glyph=account-login]:before { content:'\e000'; }
+
+.oi[data-glyph=account-logout]:before { content:'\e001'; }
+
+.oi[data-glyph=action-redo]:before { content:'\e002'; }
+
+.oi[data-glyph=action-undo]:before { content:'\e003'; }
+
+.oi[data-glyph=align-center]:before { content:'\e004'; }
+
+.oi[data-glyph=align-left]:before { content:'\e005'; }
+
+.oi[data-glyph=align-right]:before { content:'\e006'; }
+
+.oi[data-glyph=aperture]:before { content:'\e007'; }
+
+.oi[data-glyph=arrow-bottom]:before { content:'\e008'; }
+
+.oi[data-glyph=arrow-circle-bottom]:before { content:'\e009'; }
+
+.oi[data-glyph=arrow-circle-left]:before { content:'\e00a'; }
+
+.oi[data-glyph=arrow-circle-right]:before { content:'\e00b'; }
+
+.oi[data-glyph=arrow-circle-top]:before { content:'\e00c'; }
+
+.oi[data-glyph=arrow-left]:before { content:'\e00d'; }
+
+.oi[data-glyph=arrow-right]:before { content:'\e00e'; }
+
+.oi[data-glyph=arrow-thick-bottom]:before { content:'\e00f'; }
+
+.oi[data-glyph=arrow-thick-left]:before { content:'\e010'; }
+
+.oi[data-glyph=arrow-thick-right]:before { content:'\e011'; }
+
+.oi[data-glyph=arrow-thick-top]:before { content:'\e012'; }
+
+.oi[data-glyph=arrow-top]:before { content:'\e013'; }
+
+.oi[data-glyph=audio-spectrum]:before { content:'\e014'; }
+
+.oi[data-glyph=audio]:before { content:'\e015'; }
+
+.oi[data-glyph=badge]:before { content:'\e016'; }
+
+.oi[data-glyph=ban]:before { content:'\e017'; }
+
+.oi[data-glyph=bar-chart]:before { content:'\e018'; }
+
+.oi[data-glyph=basket]:before { content:'\e019'; }
+
+.oi[data-glyph=battery-empty]:before { content:'\e01a'; }
+
+.oi[data-glyph=battery-full]:before { content:'\e01b'; }
+
+.oi[data-glyph=beaker]:before { content:'\e01c'; }
+
+.oi[data-glyph=bell]:before { content:'\e01d'; }
+
+.oi[data-glyph=bluetooth]:before { content:'\e01e'; }
+
+.oi[data-glyph=bold]:before { content:'\e01f'; }
+
+.oi[data-glyph=bolt]:before { content:'\e020'; }
+
+.oi[data-glyph=book]:before { content:'\e021'; }
+
+.oi[data-glyph=bookmark]:before { content:'\e022'; }
+
+.oi[data-glyph=box]:before { content:'\e023'; }
+
+.oi[data-glyph=briefcase]:before { content:'\e024'; }
+
+.oi[data-glyph=british-pound]:before { content:'\e025'; }
+
+.oi[data-glyph=browser]:before { content:'\e026'; }
+
+.oi[data-glyph=brush]:before { content:'\e027'; }
+
+.oi[data-glyph=bug]:before { content:'\e028'; }
+
+.oi[data-glyph=bullhorn]:before { content:'\e029'; }
+
+.oi[data-glyph=calculator]:before { content:'\e02a'; }
+
+.oi[data-glyph=calendar]:before { content:'\e02b'; }
+
+.oi[data-glyph=camera-slr]:before { content:'\e02c'; }
+
+.oi[data-glyph=caret-bottom]:before { content:'\e02d'; }
+
+.oi[data-glyph=caret-left]:before { content:'\e02e'; }
+
+.oi[data-glyph=caret-right]:before { content:'\e02f'; }
+
+.oi[data-glyph=caret-top]:before { content:'\e030'; }
+
+.oi[data-glyph=cart]:before { content:'\e031'; }
+
+.oi[data-glyph=chat]:before { content:'\e032'; }
+
+.oi[data-glyph=check]:before { content:'\e033'; }
+
+.oi[data-glyph=chevron-bottom]:before { content:'\e034'; }
+
+.oi[data-glyph=chevron-left]:before { content:'\e035'; }
+
+.oi[data-glyph=chevron-right]:before { content:'\e036'; }
+
+.oi[data-glyph=chevron-top]:before { content:'\e037'; }
+
+.oi[data-glyph=circle-check]:before { content:'\e038'; }
+
+.oi[data-glyph=circle-x]:before { content:'\e039'; }
+
+.oi[data-glyph=clipboard]:before { content:'\e03a'; }
+
+.oi[data-glyph=clock]:before { content:'\e03b'; }
+
+.oi[data-glyph=cloud-download]:before { content:'\e03c'; }
+
+.oi[data-glyph=cloud-upload]:before { content:'\e03d'; }
+
+.oi[data-glyph=cloud]:before { content:'\e03e'; }
+
+.oi[data-glyph=cloudy]:before { content:'\e03f'; }
+
+.oi[data-glyph=code]:before { content:'\e040'; }
+
+.oi[data-glyph=cog]:before { content:'\e041'; }
+
+.oi[data-glyph=collapse-down]:before { content:'\e042'; }
+
+.oi[data-glyph=collapse-left]:before { content:'\e043'; }
+
+.oi[data-glyph=collapse-right]:before { content:'\e044'; }
+
+.oi[data-glyph=collapse-up]:before { content:'\e045'; }
+
+.oi[data-glyph=command]:before { content:'\e046'; }
+
+.oi[data-glyph=comment-square]:before { content:'\e047'; }
+
+.oi[data-glyph=compass]:before { content:'\e048'; }
+
+.oi[data-glyph=contrast]:before { content:'\e049'; }
+
+.oi[data-glyph=copywriting]:before { content:'\e04a'; }
+
+.oi[data-glyph=credit-card]:before { content:'\e04b'; }
+
+.oi[data-glyph=crop]:before { content:'\e04c'; }
+
+.oi[data-glyph=dashboard]:before { content:'\e04d'; }
+
+.oi[data-glyph=data-transfer-download]:before { content:'\e04e'; }
+
+.oi[data-glyph=data-transfer-upload]:before { content:'\e04f'; }
+
+.oi[data-glyph=delete]:before { content:'\e050'; }
+
+.oi[data-glyph=dial]:before { content:'\e051'; }
+
+.oi[data-glyph=document]:before { content:'\e052'; }
+
+.oi[data-glyph=dollar]:before { content:'\e053'; }
+
+.oi[data-glyph=double-quote-sans-left]:before { content:'\e054'; }
+
+.oi[data-glyph=double-quote-sans-right]:before { content:'\e055'; }
+
+.oi[data-glyph=double-quote-serif-left]:before { content:'\e056'; }
+
+.oi[data-glyph=double-quote-serif-right]:before { content:'\e057'; }
+
+.oi[data-glyph=droplet]:before { content:'\e058'; }
+
+.oi[data-glyph=eject]:before { content:'\e059'; }
+
+.oi[data-glyph=elevator]:before { content:'\e05a'; }
+
+.oi[data-glyph=ellipses]:before { content:'\e05b'; }
+
+.oi[data-glyph=envelope-closed]:before { content:'\e05c'; }
+
+.oi[data-glyph=envelope-open]:before { content:'\e05d'; }
+
+.oi[data-glyph=euro]:before { content:'\e05e'; }
+
+.oi[data-glyph=excerpt]:before { content:'\e05f'; }
+
+.oi[data-glyph=expand-down]:before { content:'\e060'; }
+
+.oi[data-glyph=expand-left]:before { content:'\e061'; }
+
+.oi[data-glyph=expand-right]:before { content:'\e062'; }
+
+.oi[data-glyph=expand-up]:before { content:'\e063'; }
+
+.oi[data-glyph=external-link]:before { content:'\e064'; }
+
+.oi[data-glyph=eye]:before { content:'\e065'; }
+
+.oi[data-glyph=eyedropper]:before { content:'\e066'; }
+
+.oi[data-glyph=file]:before { content:'\e067'; }
+
+.oi[data-glyph=fire]:before { content:'\e068'; }
+
+.oi[data-glyph=flag]:before { content:'\e069'; }
+
+.oi[data-glyph=flash]:before { content:'\e06a'; }
+
+.oi[data-glyph=folder]:before { content:'\e06b'; }
+
+.oi[data-glyph=fork]:before { content:'\e06c'; }
+
+.oi[data-glyph=fullscreen-enter]:before { content:'\e06d'; }
+
+.oi[data-glyph=fullscreen-exit]:before { content:'\e06e'; }
+
+.oi[data-glyph=globe]:before { content:'\e06f'; }
+
+.oi[data-glyph=graph]:before { content:'\e070'; }
+
+.oi[data-glyph=grid-four-up]:before { content:'\e071'; }
+
+.oi[data-glyph=grid-three-up]:before { content:'\e072'; }
+
+.oi[data-glyph=grid-two-up]:before { content:'\e073'; }
+
+.oi[data-glyph=hard-drive]:before { content:'\e074'; }
+
+.oi[data-glyph=header]:before { content:'\e075'; }
+
+.oi[data-glyph=headphones]:before { content:'\e076'; }
+
+.oi[data-glyph=heart]:before { content:'\e077'; }
+
+.oi[data-glyph=home]:before { content:'\e078'; }
+
+.oi[data-glyph=image]:before { content:'\e079'; }
+
+.oi[data-glyph=inbox]:before { content:'\e07a'; }
+
+.oi[data-glyph=infinity]:before { content:'\e07b'; }
+
+.oi[data-glyph=info]:before { content:'\e07c'; }
+
+.oi[data-glyph=italic]:before { content:'\e07d'; }
+
+.oi[data-glyph=justify-center]:before { content:'\e07e'; }
+
+.oi[data-glyph=justify-left]:before { content:'\e07f'; }
+
+.oi[data-glyph=justify-right]:before { content:'\e080'; }
+
+.oi[data-glyph=key]:before { content:'\e081'; }
+
+.oi[data-glyph=laptop]:before { content:'\e082'; }
+
+.oi[data-glyph=layers]:before { content:'\e083'; }
+
+.oi[data-glyph=lightbulb]:before { content:'\e084'; }
+
+.oi[data-glyph=link-broken]:before { content:'\e085'; }
+
+.oi[data-glyph=link-intact]:before { content:'\e086'; }
+
+.oi[data-glyph=list-rich]:before { content:'\e087'; }
+
+.oi[data-glyph=list]:before { content:'\e088'; }
+
+.oi[data-glyph=location]:before { content:'\e089'; }
+
+.oi[data-glyph=lock-locked]:before { content:'\e08a'; }
+
+.oi[data-glyph=lock-unlocked]:before { content:'\e08b'; }
+
+.oi[data-glyph=loop-circular]:before { content:'\e08c'; }
+
+.oi[data-glyph=loop-square]:before { content:'\e08d'; }
+
+.oi[data-glyph=loop]:before { content:'\e08e'; }
+
+.oi[data-glyph=magnifying-glass]:before { content:'\e08f'; }
+
+.oi[data-glyph=map-marker]:before { content:'\e090'; }
+
+.oi[data-glyph=map]:before { content:'\e091'; }
+
+.oi[data-glyph=media-pause]:before { content:'\e092'; }
+
+.oi[data-glyph=media-play]:before { content:'\e093'; }
+
+.oi[data-glyph=media-record]:before { content:'\e094'; }
+
+.oi[data-glyph=media-skip-backward]:before { content:'\e095'; }
+
+.oi[data-glyph=media-skip-forward]:before { content:'\e096'; }
+
+.oi[data-glyph=media-step-backward]:before { content:'\e097'; }
+
+.oi[data-glyph=media-step-forward]:before { content:'\e098'; }
+
+.oi[data-glyph=media-stop]:before { content:'\e099'; }
+
+.oi[data-glyph=medical-cross]:before { content:'\e09a'; }
+
+.oi[data-glyph=menu]:before { content:'\e09b'; }
+
+.oi[data-glyph=microphone]:before { content:'\e09c'; }
+
+.oi[data-glyph=minus]:before { content:'\e09d'; }
+
+.oi[data-glyph=monitor]:before { content:'\e09e'; }
+
+.oi[data-glyph=moon]:before { content:'\e09f'; }
+
+.oi[data-glyph=move]:before { content:'\e0a0'; }
+
+.oi[data-glyph=musical-note]:before { content:'\e0a1'; }
+
+.oi[data-glyph=paperclip]:before { content:'\e0a2'; }
+
+.oi[data-glyph=pencil]:before { content:'\e0a3'; }
+
+.oi[data-glyph=people]:before { content:'\e0a4'; }
+
+.oi[data-glyph=person]:before { content:'\e0a5'; }
+
+.oi[data-glyph=phone]:before { content:'\e0a6'; }
+
+.oi[data-glyph=pie-chart]:before { content:'\e0a7'; }
+
+.oi[data-glyph=pin]:before { content:'\e0a8'; }
+
+.oi[data-glyph=play-circle]:before { content:'\e0a9'; }
+
+.oi[data-glyph=plus]:before { content:'\e0aa'; }
+
+.oi[data-glyph=power-standby]:before { content:'\e0ab'; }
+
+.oi[data-glyph=print]:before { content:'\e0ac'; }
+
+.oi[data-glyph=project]:before { content:'\e0ad'; }
+
+.oi[data-glyph=pulse]:before { content:'\e0ae'; }
+
+.oi[data-glyph=puzzle-piece]:before { content:'\e0af'; }
+
+.oi[data-glyph=question-mark]:before { content:'\e0b0'; }
+
+.oi[data-glyph=rain]:before { content:'\e0b1'; }
+
+.oi[data-glyph=random]:before { content:'\e0b2'; }
+
+.oi[data-glyph=reload]:before { content:'\e0b3'; }
+
+.oi[data-glyph=resize-both]:before { content:'\e0b4'; }
+
+.oi[data-glyph=resize-height]:before { content:'\e0b5'; }
+
+.oi[data-glyph=resize-width]:before { content:'\e0b6'; }
+
+.oi[data-glyph=rss-alt]:before { content:'\e0b7'; }
+
+.oi[data-glyph=rss]:before { content:'\e0b8'; }
+
+.oi[data-glyph=script]:before { content:'\e0b9'; }
+
+.oi[data-glyph=share-boxed]:before { content:'\e0ba'; }
+
+.oi[data-glyph=share]:before { content:'\e0bb'; }
+
+.oi[data-glyph=shield]:before { content:'\e0bc'; }
+
+.oi[data-glyph=signal]:before { content:'\e0bd'; }
+
+.oi[data-glyph=signpost]:before { content:'\e0be'; }
+
+.oi[data-glyph=sort-ascending]:before { content:'\e0bf'; }
+
+.oi[data-glyph=sort-descending]:before { content:'\e0c0'; }
+
+.oi[data-glyph=spreadsheet]:before { content:'\e0c1'; }
+
+.oi[data-glyph=star]:before { content:'\e0c2'; }
+
+.oi[data-glyph=sun]:before { content:'\e0c3'; }
+
+.oi[data-glyph=tablet]:before { content:'\e0c4'; }
+
+.oi[data-glyph=tag]:before { content:'\e0c5'; }
+
+.oi[data-glyph=tags]:before { content:'\e0c6'; }
+
+.oi[data-glyph=target]:before { content:'\e0c7'; }
+
+.oi[data-glyph=task]:before { content:'\e0c8'; }
+
+.oi[data-glyph=terminal]:before { content:'\e0c9'; }
+
+.oi[data-glyph=text]:before { content:'\e0ca'; }
+
+.oi[data-glyph=thumb-down]:before { content:'\e0cb'; }
+
+.oi[data-glyph=thumb-up]:before { content:'\e0cc'; }
+
+.oi[data-glyph=timer]:before { content:'\e0cd'; }
+
+.oi[data-glyph=transfer]:before { content:'\e0ce'; }
+
+.oi[data-glyph=trash]:before { content:'\e0cf'; }
+
+.oi[data-glyph=underline]:before { content:'\e0d0'; }
+
+.oi[data-glyph=vertical-align-bottom]:before { content:'\e0d1'; }
+
+.oi[data-glyph=vertical-align-center]:before { content:'\e0d2'; }
+
+.oi[data-glyph=vertical-align-top]:before { content:'\e0d3'; }
+
+.oi[data-glyph=video]:before { content:'\e0d4'; }
+
+.oi[data-glyph=volume-high]:before { content:'\e0d5'; }
+
+.oi[data-glyph=volume-low]:before { content:'\e0d6'; }
+
+.oi[data-glyph=volume-off]:before { content:'\e0d7'; }
+
+.oi[data-glyph=warning]:before { content:'\e0d8'; }
+
+.oi[data-glyph=wifi]:before { content:'\e0d9'; }
+
+.oi[data-glyph=wrench]:before { content:'\e0da'; }
+
+.oi[data-glyph=x]:before { content:'\e0db'; }
+
+.oi[data-glyph=yen]:before { content:'\e0dc'; }
+
+.oi[data-glyph=zoom-in]:before { content:'\e0dd'; }
+
+.oi[data-glyph=zoom-out]:before { content:'\e0de'; }
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.eot b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.eot
new file mode 100644
index 000000000..7ca7c170f
Binary files /dev/null and b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.eot differ
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.otf b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.otf
new file mode 100644
index 000000000..d79fb13a1
Binary files /dev/null and b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.otf differ
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.svg b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.svg
new file mode 100644
index 000000000..0792c003a
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.svg
@@ -0,0 +1,543 @@
+
+
+
+
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.ttf b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.ttf
new file mode 100644
index 000000000..0f94acd1e
Binary files /dev/null and b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.ttf differ
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.woff b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.woff
new file mode 100644
index 000000000..793176af4
Binary files /dev/null and b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.woff differ
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css
new file mode 100644
index 000000000..8869597f0
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css
@@ -0,0 +1,228 @@
+/* Start of reset */
+
+* {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+body {
+ margin: 0;
+}
+
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+fieldset {
+ border: 1px solid #c0c0c0;
+ margin: 0 2px;
+ padding: 0.35em 0.625em 0.75em;
+}
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+td,
+th {
+ padding: 0;
+}
+
+/** End of reset */
+
+
+body {
+ font-family: 'Roboto', sans-serif;
+ font-size: 14px;
+ line-height: 22px;
+ font-weight: 300;
+}
+h1 {
+ font-size: 42px;
+ line-height: 44px;
+ padding-bottom: 5px;
+ color: #b10610;
+ margin-top: 10px;
+ margin-bottom: 30px;
+}
+h2 {
+ color: #333333;
+ font-size: 28px;
+ line-height: 44px;
+ font-weight: 300;
+}
+h3 {
+ font-size: 21px;
+ margin-top: 20px;
+ margin-bottom: 10px;
+}
+a {
+ color: #31a1cd;
+}
+h1 a {
+ text-decoration: none;
+}
+h2 a {
+ color: #333333;
+}
+a:visited {
+ color: #6098a2;
+}
+h2 a:visited {
+ color: #333333;
+}
+a:hover {
+ color: #b10610;
+}
+hr {
+ border: none;
+ border-top: 1px dashed #c9ea75;
+ margin-top: 30px;
+ margin-bottom: 30px;
+}
+header {
+ background: #eeeeee;
+}
+header a {
+ font-size: 28px;
+ font-weight: 500;
+ color: #333;
+ text-decoration: none;
+}
+.logo {
+ padding: 5px 10px;
+}
+.logo img {
+ vertical-align: middle;
+ border: 0;
+}
+input, button, select {
+ font: inherit;
+ color: inherit;
+}
+
+input[type=text], select {
+ border: 1px solid #bbbbbb;
+ line-height: 22px;
+ padding: 5px 10px;
+ border-radius: 3px;
+}
+
+nav {
+ padding: 5px;
+}
+
+.btn, button, input[type=submit] {
+ display: inline-block;
+ color: white;
+ background: #4fa3ac;
+ padding: 9px 15px;
+ border-radius: 2px;
+ border: 0;
+ text-decoration: none;
+}
+a.btn:visited {
+ color: white;
+}
+
+.btn.disabled {
+ background: #eeeeee;
+ color: #bbbbbb;
+}
+section {
+ margin: 40px 10px;
+}
+
+section table {
+ height: 40px;
+}
+
+.nodeTable tr {
+ border-bottom: 3px solid white;
+}
+
+.nodeTable td {
+ padding: 10px 10px 10px 10px;
+
+}
+
+.nodeTable a {
+ text-decoration: none;
+}
+
+.nodeTable .nameColumn {
+ font-weight: bold;
+ padding: 10px 20px;
+ background: #ebf5f6;
+ min-width: 200px;
+}
+.nodeTable .oi {
+ color: #b10610;
+}
+
+.propTable tr {
+ height: 40px;
+}
+
+.propTable th {
+ background: #f6f6f6;
+ padding: 0 10px;
+ text-align: left;
+}
+
+.propTable td {
+ padding: 0 10px;
+ background: #eeeeee;
+}
+
+.propTable pre {
+ font-size: 80%;
+ background: #f8f8f8;
+}
+
+.actions {
+ border: 1px dotted #76baa6;
+ padding: 20px;
+ margin-bottom: 20px;
+
+}
+
+.actions h3 {
+ margin-top: 10px;
+ margin-bottom: 30px;
+ padding-bottom: 20px;
+ border-bottom: 1px solid #eeeeee;
+}
+
+.actions label {
+ width: 150px;
+ display: inline-block;
+ line-height: 40px;
+}
+
+.actions input[type=text], select {
+ width: 450px;
+}
+
+.actions input[type=submit] {
+ display: inline-block;
+ margin-left: 153px;
+}
+
+footer {
+ padding: 50px 0;
+ font-size: 80%;
+ text-align: center;
+}
+
+ul.tree {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+ul.tree ul {
+ list-style: none;
+ padding-left: 10px;
+ border-left: 4px solid #ccc;
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.png b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.png
new file mode 100644
index 000000000..48a97398a
Binary files /dev/null and b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.png differ
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Client.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Client.php
new file mode 100644
index 000000000..a9de71cdb
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Client.php
@@ -0,0 +1,429 @@
+xml->elementMap.
+ * It's deprecated as of version 3.0.0, and should no longer be used.
+ *
+ * @deprecated
+ *
+ * @var array
+ */
+ public $propertyMap = [];
+
+ /**
+ * Base URI.
+ *
+ * This URI will be used to resolve relative urls.
+ *
+ * @var string
+ */
+ protected $baseUri;
+
+ /**
+ * Basic authentication.
+ */
+ const AUTH_BASIC = 1;
+
+ /**
+ * Digest authentication.
+ */
+ const AUTH_DIGEST = 2;
+
+ /**
+ * NTLM authentication.
+ */
+ const AUTH_NTLM = 4;
+
+ /**
+ * Identity encoding, which basically does not nothing.
+ */
+ const ENCODING_IDENTITY = 1;
+
+ /**
+ * Deflate encoding.
+ */
+ const ENCODING_DEFLATE = 2;
+
+ /**
+ * Gzip encoding.
+ */
+ const ENCODING_GZIP = 4;
+
+ /**
+ * Sends all encoding headers.
+ */
+ const ENCODING_ALL = 7;
+
+ /**
+ * Content-encoding.
+ *
+ * @var int
+ */
+ protected $encoding = self::ENCODING_IDENTITY;
+
+ /**
+ * Constructor.
+ *
+ * Settings are provided through the 'settings' argument. The following
+ * settings are supported:
+ *
+ * * baseUri
+ * * userName (optional)
+ * * password (optional)
+ * * proxy (optional)
+ * * authType (optional)
+ * * encoding (optional)
+ *
+ * authType must be a bitmap, using self::AUTH_BASIC, self::AUTH_DIGEST
+ * and self::AUTH_NTLM. If you know which authentication method will be
+ * used, it's recommended to set it, as it will save a great deal of
+ * requests to 'discover' this information.
+ *
+ * Encoding is a bitmap with one of the ENCODING constants.
+ */
+ public function __construct(array $settings)
+ {
+ if (!isset($settings['baseUri'])) {
+ throw new \InvalidArgumentException('A baseUri must be provided');
+ }
+
+ parent::__construct();
+
+ $this->baseUri = $settings['baseUri'];
+
+ if (isset($settings['proxy'])) {
+ $this->addCurlSetting(CURLOPT_PROXY, $settings['proxy']);
+ }
+
+ if (isset($settings['userName'])) {
+ $userName = $settings['userName'];
+ $password = isset($settings['password']) ? $settings['password'] : '';
+
+ if (isset($settings['authType'])) {
+ $curlType = 0;
+ if ($settings['authType'] & self::AUTH_BASIC) {
+ $curlType |= CURLAUTH_BASIC;
+ }
+ if ($settings['authType'] & self::AUTH_DIGEST) {
+ $curlType |= CURLAUTH_DIGEST;
+ }
+ if ($settings['authType'] & self::AUTH_NTLM) {
+ $curlType |= CURLAUTH_NTLM;
+ }
+ } else {
+ $curlType = CURLAUTH_BASIC | CURLAUTH_DIGEST;
+ }
+
+ $this->addCurlSetting(CURLOPT_HTTPAUTH, $curlType);
+ $this->addCurlSetting(CURLOPT_USERPWD, $userName.':'.$password);
+ }
+
+ if (isset($settings['encoding'])) {
+ $encoding = $settings['encoding'];
+
+ $encodings = [];
+ if ($encoding & self::ENCODING_IDENTITY) {
+ $encodings[] = 'identity';
+ }
+ if ($encoding & self::ENCODING_DEFLATE) {
+ $encodings[] = 'deflate';
+ }
+ if ($encoding & self::ENCODING_GZIP) {
+ $encodings[] = 'gzip';
+ }
+ $this->addCurlSetting(CURLOPT_ENCODING, implode(',', $encodings));
+ }
+
+ $this->addCurlSetting(CURLOPT_USERAGENT, 'sabre-dav/'.Version::VERSION.' (http://sabre.io/)');
+
+ $this->xml = new Xml\Service();
+ // BC
+ $this->propertyMap = &$this->xml->elementMap;
+ }
+
+ /**
+ * Does a PROPFIND request.
+ *
+ * The list of requested properties must be specified as an array, in clark
+ * notation.
+ *
+ * The returned array will contain a list of filenames as keys, and
+ * properties as values.
+ *
+ * The properties array will contain the list of properties. Only properties
+ * that are actually returned from the server (without error) will be
+ * returned, anything else is discarded.
+ *
+ * Depth should be either 0 or 1. A depth of 1 will cause a request to be
+ * made to the server to also return all child resources.
+ *
+ * @param string $url
+ * @param int $depth
+ *
+ * @return array
+ */
+ public function propFind($url, array $properties, $depth = 0)
+ {
+ $dom = new \DOMDocument('1.0', 'UTF-8');
+ $dom->formatOutput = true;
+ $root = $dom->createElementNS('DAV:', 'd:propfind');
+ $prop = $dom->createElement('d:prop');
+
+ foreach ($properties as $property) {
+ list(
+ $namespace,
+ $elementName
+ ) = \Sabre\Xml\Service::parseClarkNotation($property);
+
+ if ('DAV:' === $namespace) {
+ $element = $dom->createElement('d:'.$elementName);
+ } else {
+ $element = $dom->createElementNS($namespace, 'x:'.$elementName);
+ }
+
+ $prop->appendChild($element);
+ }
+
+ $dom->appendChild($root)->appendChild($prop);
+ $body = $dom->saveXML();
+
+ $url = $this->getAbsoluteUrl($url);
+
+ $request = new HTTP\Request('PROPFIND', $url, [
+ 'Depth' => $depth,
+ 'Content-Type' => 'application/xml',
+ ], $body);
+
+ $response = $this->send($request);
+
+ if ((int) $response->getStatus() >= 400) {
+ throw new HTTP\ClientHttpException($response);
+ }
+
+ $result = $this->parseMultiStatus($response->getBodyAsString());
+
+ // If depth was 0, we only return the top item
+ if (0 === $depth) {
+ reset($result);
+ $result = current($result);
+
+ return isset($result[200]) ? $result[200] : [];
+ }
+
+ $newResult = [];
+ foreach ($result as $href => $statusList) {
+ $newResult[$href] = isset($statusList[200]) ? $statusList[200] : [];
+ }
+
+ return $newResult;
+ }
+
+ /**
+ * Updates a list of properties on the server.
+ *
+ * The list of properties must have clark-notation properties for the keys,
+ * and the actual (string) value for the value. If the value is null, an
+ * attempt is made to delete the property.
+ *
+ * @param string $url
+ *
+ * @return bool
+ */
+ public function propPatch($url, array $properties)
+ {
+ $propPatch = new Xml\Request\PropPatch();
+ $propPatch->properties = $properties;
+ $xml = $this->xml->write(
+ '{DAV:}propertyupdate',
+ $propPatch
+ );
+
+ $url = $this->getAbsoluteUrl($url);
+ $request = new HTTP\Request('PROPPATCH', $url, [
+ 'Content-Type' => 'application/xml',
+ ], $xml);
+ $response = $this->send($request);
+
+ if ($response->getStatus() >= 400) {
+ throw new HTTP\ClientHttpException($response);
+ }
+
+ if (207 === $response->getStatus()) {
+ // If it's a 207, the request could still have failed, but the
+ // information is hidden in the response body.
+ $result = $this->parseMultiStatus($response->getBodyAsString());
+
+ $errorProperties = [];
+ foreach ($result as $href => $statusList) {
+ foreach ($statusList as $status => $properties) {
+ if ($status >= 400) {
+ foreach ($properties as $propName => $propValue) {
+ $errorProperties[] = $propName.' ('.$status.')';
+ }
+ }
+ }
+ }
+ if ($errorProperties) {
+ throw new HTTP\ClientException('PROPPATCH failed. The following properties errored: '.implode(', ', $errorProperties));
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Performs an HTTP options request.
+ *
+ * This method returns all the features from the 'DAV:' header as an array.
+ * If there was no DAV header, or no contents this method will return an
+ * empty array.
+ *
+ * @return array
+ */
+ public function options()
+ {
+ $request = new HTTP\Request('OPTIONS', $this->getAbsoluteUrl(''));
+ $response = $this->send($request);
+
+ $dav = $response->getHeader('Dav');
+ if (!$dav) {
+ return [];
+ }
+
+ $features = explode(',', $dav);
+ foreach ($features as &$v) {
+ $v = trim($v);
+ }
+
+ return $features;
+ }
+
+ /**
+ * Performs an actual HTTP request, and returns the result.
+ *
+ * If the specified url is relative, it will be expanded based on the base
+ * url.
+ *
+ * The returned array contains 3 keys:
+ * * body - the response body
+ * * httpCode - a HTTP code (200, 404, etc)
+ * * headers - a list of response http headers. The header names have
+ * been lowercased.
+ *
+ * For large uploads, it's highly recommended to specify body as a stream
+ * resource. You can easily do this by simply passing the result of
+ * fopen(..., 'r').
+ *
+ * This method will throw an exception if an HTTP error was received. Any
+ * HTTP status code above 399 is considered an error.
+ *
+ * Note that it is no longer recommended to use this method, use the send()
+ * method instead.
+ *
+ * @param string $method
+ * @param string $url
+ * @param string|resource|null $body
+ *
+ * @throws clientException, in case a curl error occurred
+ *
+ * @return array
+ */
+ public function request($method, $url = '', $body = null, array $headers = [])
+ {
+ $url = $this->getAbsoluteUrl($url);
+
+ $response = $this->send(new HTTP\Request($method, $url, $headers, $body));
+
+ return [
+ 'body' => $response->getBodyAsString(),
+ 'statusCode' => (int) $response->getStatus(),
+ 'headers' => array_change_key_case($response->getHeaders()),
+ ];
+ }
+
+ /**
+ * Returns the full url based on the given url (which may be relative). All
+ * urls are expanded based on the base url as given by the server.
+ *
+ * @param string $url
+ *
+ * @return string
+ */
+ public function getAbsoluteUrl($url)
+ {
+ return Uri\resolve(
+ $this->baseUri,
+ $url
+ );
+ }
+
+ /**
+ * Parses a WebDAV multistatus response body.
+ *
+ * This method returns an array with the following structure
+ *
+ * [
+ * 'url/to/resource' => [
+ * '200' => [
+ * '{DAV:}property1' => 'value1',
+ * '{DAV:}property2' => 'value2',
+ * ],
+ * '404' => [
+ * '{DAV:}property1' => null,
+ * '{DAV:}property2' => null,
+ * ],
+ * ],
+ * 'url/to/resource2' => [
+ * .. etc ..
+ * ]
+ * ]
+ *
+ * @param string $body xml body
+ *
+ * @return array
+ */
+ public function parseMultiStatus($body)
+ {
+ $multistatus = $this->xml->expect('{DAV:}multistatus', $body);
+
+ $result = [];
+
+ foreach ($multistatus->getResponses() as $response) {
+ $result[$response->getHref()] = $response->getResponseProperties();
+ }
+
+ return $result;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Collection.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Collection.php
new file mode 100644
index 000000000..2728bb27e
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Collection.php
@@ -0,0 +1,106 @@
+getChildren() as $child) {
+ if ($child->getName() === $name) {
+ return $child;
+ }
+ }
+ throw new Exception\NotFound('File not found: '.$name);
+ }
+
+ /**
+ * Checks is a child-node exists.
+ *
+ * It is generally a good idea to try and override this. Usually it can be optimized.
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public function childExists($name)
+ {
+ try {
+ $this->getChild($name);
+
+ return true;
+ } catch (Exception\NotFound $e) {
+ return false;
+ }
+ }
+
+ /**
+ * 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)
+ {
+ throw new Exception\Forbidden('Permission denied to create file (filename '.$name.')');
+ }
+
+ /**
+ * Creates a new subdirectory.
+ *
+ * @param string $name
+ *
+ * @throws Exception\Forbidden
+ */
+ public function createDirectory($name)
+ {
+ throw new Exception\Forbidden('Permission denied to create directory');
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/CorePlugin.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/CorePlugin.php
new file mode 100644
index 000000000..74350c28d
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/CorePlugin.php
@@ -0,0 +1,902 @@
+server = $server;
+ $server->on('method:GET', [$this, 'httpGet']);
+ $server->on('method:OPTIONS', [$this, 'httpOptions']);
+ $server->on('method:HEAD', [$this, 'httpHead']);
+ $server->on('method:DELETE', [$this, 'httpDelete']);
+ $server->on('method:PROPFIND', [$this, 'httpPropFind']);
+ $server->on('method:PROPPATCH', [$this, 'httpPropPatch']);
+ $server->on('method:PUT', [$this, 'httpPut']);
+ $server->on('method:MKCOL', [$this, 'httpMkcol']);
+ $server->on('method:MOVE', [$this, 'httpMove']);
+ $server->on('method:COPY', [$this, 'httpCopy']);
+ $server->on('method:REPORT', [$this, 'httpReport']);
+
+ $server->on('propPatch', [$this, 'propPatchProtectedPropertyCheck'], 90);
+ $server->on('propPatch', [$this, 'propPatchNodeUpdate'], 200);
+ $server->on('propFind', [$this, 'propFind']);
+ $server->on('propFind', [$this, 'propFindNode'], 120);
+ $server->on('propFind', [$this, 'propFindLate'], 200);
+
+ $server->on('exception', [$this, 'exception']);
+ }
+
+ /**
+ * Returns a plugin name.
+ *
+ * Using this name other plugins will be able to access other plugins
+ * using DAV\Server::getPlugin
+ *
+ * @return string
+ */
+ public function getPluginName()
+ {
+ return 'core';
+ }
+
+ /**
+ * This is the default implementation for the GET method.
+ *
+ * @return bool
+ */
+ public function httpGet(RequestInterface $request, ResponseInterface $response)
+ {
+ $path = $request->getPath();
+ $node = $this->server->tree->getNodeForPath($path);
+
+ if (!$node instanceof IFile) {
+ return;
+ }
+
+ if ('HEAD' === $request->getHeader('X-Sabre-Original-Method')) {
+ $body = '';
+ } else {
+ $body = $node->get();
+
+ // Converting string into stream, if needed.
+ if (is_string($body)) {
+ $stream = fopen('php://temp', 'r+');
+ fwrite($stream, $body);
+ rewind($stream);
+ $body = $stream;
+ }
+ }
+
+ /*
+ * TODO: getetag, getlastmodified, getsize should also be used using
+ * this method
+ */
+ $httpHeaders = $this->server->getHTTPHeaders($path);
+
+ /* ContentType needs to get a default, because many webservers will otherwise
+ * default to text/html, and we don't want this for security reasons.
+ */
+ if (!isset($httpHeaders['Content-Type'])) {
+ $httpHeaders['Content-Type'] = 'application/octet-stream';
+ }
+
+ if (isset($httpHeaders['Content-Length'])) {
+ $nodeSize = $httpHeaders['Content-Length'];
+
+ // Need to unset Content-Length, because we'll handle that during figuring out the range
+ unset($httpHeaders['Content-Length']);
+ } else {
+ $nodeSize = null;
+ }
+
+ $response->addHeaders($httpHeaders);
+
+ $range = $this->server->getHTTPRange();
+ $ifRange = $request->getHeader('If-Range');
+ $ignoreRangeHeader = false;
+
+ // If ifRange is set, and range is specified, we first need to check
+ // the precondition.
+ if ($nodeSize && $range && $ifRange) {
+ // if IfRange is parsable as a date we'll treat it as a DateTime
+ // otherwise, we must treat it as an etag.
+ try {
+ $ifRangeDate = new \DateTime($ifRange);
+
+ // It's a date. We must check if the entity is modified since
+ // the specified date.
+ if (!isset($httpHeaders['Last-Modified'])) {
+ $ignoreRangeHeader = true;
+ } else {
+ $modified = new \DateTime($httpHeaders['Last-Modified']);
+ if ($modified > $ifRangeDate) {
+ $ignoreRangeHeader = true;
+ }
+ }
+ } catch (\Exception $e) {
+ // It's an entity. We can do a simple comparison.
+ if (!isset($httpHeaders['ETag'])) {
+ $ignoreRangeHeader = true;
+ } elseif ($httpHeaders['ETag'] !== $ifRange) {
+ $ignoreRangeHeader = true;
+ }
+ }
+ }
+
+ // We're only going to support HTTP ranges if the backend provided a filesize
+ if (!$ignoreRangeHeader && $nodeSize && $range) {
+ // Determining the exact byte offsets
+ if (!is_null($range[0])) {
+ $start = $range[0];
+ $end = $range[1] ? $range[1] : $nodeSize - 1;
+ if ($start >= $nodeSize) {
+ throw new Exception\RequestedRangeNotSatisfiable('The start offset ('.$range[0].') exceeded the size of the entity ('.$nodeSize.')');
+ }
+ if ($end < $start) {
+ throw new Exception\RequestedRangeNotSatisfiable('The end offset ('.$range[1].') is lower than the start offset ('.$range[0].')');
+ }
+ if ($end >= $nodeSize) {
+ $end = $nodeSize - 1;
+ }
+ } else {
+ $start = $nodeSize - $range[1];
+ $end = $nodeSize - 1;
+
+ if ($start < 0) {
+ $start = 0;
+ }
+ }
+
+ // Streams may advertise themselves as seekable, but still not
+ // actually allow fseek. We'll manually go forward in the stream
+ // if fseek failed.
+ if (!stream_get_meta_data($body)['seekable'] || -1 === fseek($body, $start, SEEK_SET)) {
+ $consumeBlock = 8192;
+ for ($consumed = 0; $start - $consumed > 0;) {
+ if (feof($body)) {
+ throw new Exception\RequestedRangeNotSatisfiable('The start offset ('.$start.') exceeded the size of the entity ('.$consumed.')');
+ }
+ $consumed += strlen(fread($body, min($start - $consumed, $consumeBlock)));
+ }
+ }
+
+ $response->setHeader('Content-Length', $end - $start + 1);
+ $response->setHeader('Content-Range', 'bytes '.$start.'-'.$end.'/'.$nodeSize);
+ $response->setStatus(206);
+ $response->setBody($body);
+ } else {
+ if ($nodeSize) {
+ $response->setHeader('Content-Length', $nodeSize);
+ }
+ $response->setStatus(200);
+ $response->setBody($body);
+ }
+ // Sending back false will interrupt the event chain and tell the server
+ // we've handled this method.
+ return false;
+ }
+
+ /**
+ * HTTP OPTIONS.
+ *
+ * @return bool
+ */
+ public function httpOptions(RequestInterface $request, ResponseInterface $response)
+ {
+ $methods = $this->server->getAllowedMethods($request->getPath());
+
+ $response->setHeader('Allow', strtoupper(implode(', ', $methods)));
+ $features = ['1', '3', 'extended-mkcol'];
+
+ foreach ($this->server->getPlugins() as $plugin) {
+ $features = array_merge($features, $plugin->getFeatures());
+ }
+
+ $response->setHeader('DAV', implode(', ', $features));
+ $response->setHeader('MS-Author-Via', 'DAV');
+ $response->setHeader('Accept-Ranges', 'bytes');
+ $response->setHeader('Content-Length', '0');
+ $response->setStatus(200);
+
+ // Sending back false will interrupt the event chain and tell the server
+ // we've handled this method.
+ return false;
+ }
+
+ /**
+ * HTTP HEAD.
+ *
+ * This method is normally used to take a peak at a url, and only get the
+ * HTTP response headers, without the body. This is used by clients to
+ * determine if a remote file was changed, so they can use a local cached
+ * version, instead of downloading it again
+ *
+ * @return bool
+ */
+ public function httpHead(RequestInterface $request, ResponseInterface $response)
+ {
+ // This is implemented by changing the HEAD request to a GET request,
+ // and telling the request handler that is doesn't need to create the body.
+ $subRequest = clone $request;
+ $subRequest->setMethod('GET');
+ $subRequest->setHeader('X-Sabre-Original-Method', 'HEAD');
+
+ try {
+ $this->server->invokeMethod($subRequest, $response, false);
+ } catch (Exception\NotImplemented $e) {
+ // Some clients may do HEAD requests on collections, however, GET
+ // requests and HEAD requests _may_ not be defined on a collection,
+ // which would trigger a 501.
+ // This breaks some clients though, so we're transforming these
+ // 501s into 200s.
+ $response->setStatus(200);
+ $response->setBody('');
+ $response->setHeader('Content-Type', 'text/plain');
+ $response->setHeader('X-Sabre-Real-Status', $e->getHTTPCode());
+ }
+
+ // Sending back false will interrupt the event chain and tell the server
+ // we've handled this method.
+ return false;
+ }
+
+ /**
+ * HTTP Delete.
+ *
+ * The HTTP delete method, deletes a given uri
+ */
+ public function httpDelete(RequestInterface $request, ResponseInterface $response)
+ {
+ $path = $request->getPath();
+
+ if (!$this->server->emit('beforeUnbind', [$path])) {
+ return false;
+ }
+ $this->server->tree->delete($path);
+ $this->server->emit('afterUnbind', [$path]);
+
+ $response->setStatus(204);
+ $response->setHeader('Content-Length', '0');
+
+ // Sending back false will interrupt the event chain and tell the server
+ // we've handled this method.
+ return false;
+ }
+
+ /**
+ * WebDAV PROPFIND.
+ *
+ * This WebDAV method requests information about an uri resource, or a list of resources
+ * If a client wants to receive the properties for a single resource it will add an HTTP Depth: header with a 0 value
+ * If the value is 1, it means that it also expects a list of sub-resources (e.g.: files in a directory)
+ *
+ * The request body contains an XML data structure that has a list of properties the client understands
+ * The response body is also an xml document, containing information about every uri resource and the requested properties
+ *
+ * It has to return a HTTP 207 Multi-status status code
+ */
+ public function httpPropFind(RequestInterface $request, ResponseInterface $response)
+ {
+ $path = $request->getPath();
+
+ $requestBody = $request->getBodyAsString();
+ if (strlen($requestBody)) {
+ try {
+ $propFindXml = $this->server->xml->expect('{DAV:}propfind', $requestBody);
+ } catch (ParseException $e) {
+ throw new BadRequest($e->getMessage(), 0, $e);
+ }
+ } else {
+ $propFindXml = new Xml\Request\PropFind();
+ $propFindXml->allProp = true;
+ $propFindXml->properties = [];
+ }
+
+ $depth = $this->server->getHTTPDepth(1);
+ // The only two options for the depth of a propfind is 0 or 1 - as long as depth infinity is not enabled
+ if (!$this->server->enablePropfindDepthInfinity && 0 != $depth) {
+ $depth = 1;
+ }
+
+ $newProperties = $this->server->getPropertiesIteratorForPath($path, $propFindXml->properties, $depth);
+
+ // This is a multi-status response
+ $response->setStatus(207);
+ $response->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ $response->setHeader('Vary', 'Brief,Prefer');
+
+ // Normally this header is only needed for OPTIONS responses, however..
+ // iCal seems to also depend on these being set for PROPFIND. Since
+ // this is not harmful, we'll add it.
+ $features = ['1', '3', 'extended-mkcol'];
+ foreach ($this->server->getPlugins() as $plugin) {
+ $features = array_merge($features, $plugin->getFeatures());
+ }
+ $response->setHeader('DAV', implode(', ', $features));
+
+ $prefer = $this->server->getHTTPPrefer();
+ $minimal = 'minimal' === $prefer['return'];
+
+ $data = $this->server->generateMultiStatus($newProperties, $minimal);
+ $response->setBody($data);
+
+ // Sending back false will interrupt the event chain and tell the server
+ // we've handled this method.
+ return false;
+ }
+
+ /**
+ * WebDAV PROPPATCH.
+ *
+ * This method is called to update properties on a Node. The request is an XML body with all the mutations.
+ * In this XML body it is specified which properties should be set/updated and/or deleted
+ *
+ * @return bool
+ */
+ public function httpPropPatch(RequestInterface $request, ResponseInterface $response)
+ {
+ $path = $request->getPath();
+
+ try {
+ $propPatch = $this->server->xml->expect('{DAV:}propertyupdate', $request->getBody());
+ } catch (ParseException $e) {
+ throw new BadRequest($e->getMessage(), 0, $e);
+ }
+ $newProperties = $propPatch->properties;
+
+ $result = $this->server->updateProperties($path, $newProperties);
+
+ $prefer = $this->server->getHTTPPrefer();
+ $response->setHeader('Vary', 'Brief,Prefer');
+
+ if ('minimal' === $prefer['return']) {
+ // If return-minimal is specified, we only have to check if the
+ // request was successful, and don't need to return the
+ // multi-status.
+ $ok = true;
+ foreach ($result as $prop => $code) {
+ if ((int) $code > 299) {
+ $ok = false;
+ }
+ }
+
+ if ($ok) {
+ $response->setStatus(204);
+
+ return false;
+ }
+ }
+
+ $response->setStatus(207);
+ $response->setHeader('Content-Type', 'application/xml; charset=utf-8');
+
+ // Reorganizing the result for generateMultiStatus
+ $multiStatus = [];
+ foreach ($result as $propertyName => $code) {
+ if (isset($multiStatus[$code])) {
+ $multiStatus[$code][$propertyName] = null;
+ } else {
+ $multiStatus[$code] = [$propertyName => null];
+ }
+ }
+ $multiStatus['href'] = $path;
+
+ $response->setBody(
+ $this->server->generateMultiStatus([$multiStatus])
+ );
+
+ // Sending back false will interrupt the event chain and tell the server
+ // we've handled this method.
+ return false;
+ }
+
+ /**
+ * HTTP PUT method.
+ *
+ * This HTTP method updates a file, or creates a new one.
+ *
+ * If a new resource was created, a 201 Created status code should be returned. If an existing resource is updated, it's a 204 No Content
+ *
+ * @return bool
+ */
+ public function httpPut(RequestInterface $request, ResponseInterface $response)
+ {
+ $body = $request->getBodyAsStream();
+ $path = $request->getPath();
+
+ // Intercepting Content-Range
+ if ($request->getHeader('Content-Range')) {
+ /*
+ An origin server that allows PUT on a given target resource MUST send
+ a 400 (Bad Request) response to a PUT request that contains a
+ Content-Range header field.
+
+ Reference: http://tools.ietf.org/html/rfc7231#section-4.3.4
+ */
+ throw new Exception\BadRequest('Content-Range on PUT requests are forbidden.');
+ }
+
+ // Intercepting the Finder problem
+ if (($expected = $request->getHeader('X-Expected-Entity-Length')) && $expected > 0) {
+ /*
+ Many webservers will not cooperate well with Finder PUT requests,
+ because it uses 'Chunked' transfer encoding for the request body.
+
+ The symptom of this problem is that Finder sends files to the
+ server, but they arrive as 0-length files in PHP.
+
+ If we don't do anything, the user might think they are uploading
+ files successfully, but they end up empty on the server. Instead,
+ we throw back an error if we detect this.
+
+ The reason Finder uses Chunked, is because it thinks the files
+ might change as it's being uploaded, and therefore the
+ Content-Length can vary.
+
+ Instead it sends the X-Expected-Entity-Length header with the size
+ of the file at the very start of the request. If this header is set,
+ but we don't get a request body we will fail the request to
+ protect the end-user.
+ */
+
+ // Only reading first byte
+ $firstByte = fread($body, 1);
+ if (1 !== strlen($firstByte)) {
+ throw new Exception\Forbidden('This server is not compatible with OS/X finder. Consider using a different WebDAV client or webserver.');
+ }
+
+ // The body needs to stay intact, so we copy everything to a
+ // temporary stream.
+
+ $newBody = fopen('php://temp', 'r+');
+ fwrite($newBody, $firstByte);
+ stream_copy_to_stream($body, $newBody);
+ rewind($newBody);
+
+ $body = $newBody;
+ }
+
+ if ($this->server->tree->nodeExists($path)) {
+ $node = $this->server->tree->getNodeForPath($path);
+
+ // If the node is a collection, we'll deny it
+ if (!($node instanceof IFile)) {
+ throw new Exception\Conflict('PUT is not allowed on non-files.');
+ }
+ if (!$this->server->updateFile($path, $body, $etag)) {
+ return false;
+ }
+
+ $response->setHeader('Content-Length', '0');
+ if ($etag) {
+ $response->setHeader('ETag', $etag);
+ }
+ $response->setStatus(204);
+ } else {
+ $etag = null;
+ // If we got here, the resource didn't exist yet.
+ if (!$this->server->createFile($path, $body, $etag)) {
+ // For one reason or another the file was not created.
+ return false;
+ }
+
+ $response->setHeader('Content-Length', '0');
+ if ($etag) {
+ $response->setHeader('ETag', $etag);
+ }
+ $response->setStatus(201);
+ }
+
+ // Sending back false will interrupt the event chain and tell the server
+ // we've handled this method.
+ return false;
+ }
+
+ /**
+ * WebDAV MKCOL.
+ *
+ * The MKCOL method is used to create a new collection (directory) on the server
+ *
+ * @return bool
+ */
+ public function httpMkcol(RequestInterface $request, ResponseInterface $response)
+ {
+ $requestBody = $request->getBodyAsString();
+ $path = $request->getPath();
+
+ if ($requestBody) {
+ $contentType = $request->getHeader('Content-Type');
+ if (null === $contentType || (0 !== strpos($contentType, 'application/xml') && 0 !== strpos($contentType, 'text/xml'))) {
+ // We must throw 415 for unsupported mkcol bodies
+ throw new Exception\UnsupportedMediaType('The request body for the MKCOL request must have an xml Content-Type');
+ }
+
+ try {
+ $mkcol = $this->server->xml->expect('{DAV:}mkcol', $requestBody);
+ } catch (\Sabre\Xml\ParseException $e) {
+ throw new Exception\BadRequest($e->getMessage(), 0, $e);
+ }
+
+ $properties = $mkcol->getProperties();
+
+ if (!isset($properties['{DAV:}resourcetype'])) {
+ throw new Exception\BadRequest('The mkcol request must include a {DAV:}resourcetype property');
+ }
+ $resourceType = $properties['{DAV:}resourcetype']->getValue();
+ unset($properties['{DAV:}resourcetype']);
+ } else {
+ $properties = [];
+ $resourceType = ['{DAV:}collection'];
+ }
+
+ $mkcol = new MkCol($resourceType, $properties);
+
+ $result = $this->server->createCollection($path, $mkcol);
+
+ if (is_array($result)) {
+ $response->setStatus(207);
+ $response->setHeader('Content-Type', 'application/xml; charset=utf-8');
+
+ $response->setBody(
+ $this->server->generateMultiStatus([$result])
+ );
+ } else {
+ $response->setHeader('Content-Length', '0');
+ $response->setStatus(201);
+ }
+
+ // Sending back false will interrupt the event chain and tell the server
+ // we've handled this method.
+ return false;
+ }
+
+ /**
+ * WebDAV HTTP MOVE method.
+ *
+ * This method moves one uri to a different uri. A lot of the actual request processing is done in getCopyMoveInfo
+ *
+ * @return bool
+ */
+ public function httpMove(RequestInterface $request, ResponseInterface $response)
+ {
+ $path = $request->getPath();
+
+ $moveInfo = $this->server->getCopyAndMoveInfo($request);
+
+ if ($moveInfo['destinationExists']) {
+ if (!$this->server->emit('beforeUnbind', [$moveInfo['destination']])) {
+ return false;
+ }
+ }
+ if (!$this->server->emit('beforeUnbind', [$path])) {
+ return false;
+ }
+ if (!$this->server->emit('beforeBind', [$moveInfo['destination']])) {
+ return false;
+ }
+ if (!$this->server->emit('beforeMove', [$path, $moveInfo['destination']])) {
+ return false;
+ }
+
+ if ($moveInfo['destinationExists']) {
+ $this->server->tree->delete($moveInfo['destination']);
+ $this->server->emit('afterUnbind', [$moveInfo['destination']]);
+ }
+
+ $this->server->tree->move($path, $moveInfo['destination']);
+
+ // Its important afterMove is called before afterUnbind, because it
+ // allows systems to transfer data from one path to another.
+ // PropertyStorage uses this. If afterUnbind was first, it would clean
+ // up all the properties before it has a chance.
+ $this->server->emit('afterMove', [$path, $moveInfo['destination']]);
+ $this->server->emit('afterUnbind', [$path]);
+ $this->server->emit('afterBind', [$moveInfo['destination']]);
+
+ // If a resource was overwritten we should send a 204, otherwise a 201
+ $response->setHeader('Content-Length', '0');
+ $response->setStatus($moveInfo['destinationExists'] ? 204 : 201);
+
+ // Sending back false will interrupt the event chain and tell the server
+ // we've handled this method.
+ return false;
+ }
+
+ /**
+ * WebDAV HTTP COPY method.
+ *
+ * This method copies one uri to a different uri, and works much like the MOVE request
+ * A lot of the actual request processing is done in getCopyMoveInfo
+ *
+ * @return bool
+ */
+ public function httpCopy(RequestInterface $request, ResponseInterface $response)
+ {
+ $path = $request->getPath();
+
+ $copyInfo = $this->server->getCopyAndMoveInfo($request);
+
+ if (!$this->server->emit('beforeBind', [$copyInfo['destination']])) {
+ return false;
+ }
+ if ($copyInfo['destinationExists']) {
+ if (!$this->server->emit('beforeUnbind', [$copyInfo['destination']])) {
+ return false;
+ }
+ $this->server->tree->delete($copyInfo['destination']);
+ }
+
+ $this->server->tree->copy($path, $copyInfo['destination']);
+ $this->server->emit('afterBind', [$copyInfo['destination']]);
+
+ // If a resource was overwritten we should send a 204, otherwise a 201
+ $response->setHeader('Content-Length', '0');
+ $response->setStatus($copyInfo['destinationExists'] ? 204 : 201);
+
+ // Sending back false will interrupt the event chain and tell the server
+ // we've handled this method.
+ return false;
+ }
+
+ /**
+ * HTTP REPORT method implementation.
+ *
+ * Although the REPORT method is not part of the standard WebDAV spec (it's from rfc3253)
+ * It's used in a lot of extensions, so it made sense to implement it into the core.
+ *
+ * @return bool
+ */
+ public function httpReport(RequestInterface $request, ResponseInterface $response)
+ {
+ $path = $request->getPath();
+
+ $result = $this->server->xml->parse(
+ $request->getBody(),
+ $request->getUrl(),
+ $rootElementName
+ );
+
+ if ($this->server->emit('report', [$rootElementName, $result, $path])) {
+ // If emit returned true, it means the report was not supported
+ throw new Exception\ReportNotSupported();
+ }
+
+ // Sending back false will interrupt the event chain and tell the server
+ // we've handled this method.
+ return false;
+ }
+
+ /**
+ * This method is called during property updates.
+ *
+ * Here we check if a user attempted to update a protected property and
+ * ensure that the process fails if this is the case.
+ *
+ * @param string $path
+ */
+ public function propPatchProtectedPropertyCheck($path, PropPatch $propPatch)
+ {
+ // Comparing the mutation list to the list of protected properties.
+ $mutations = $propPatch->getMutations();
+
+ $protected = array_intersect(
+ $this->server->protectedProperties,
+ array_keys($mutations)
+ );
+
+ if ($protected) {
+ $propPatch->setResultCode($protected, 403);
+ }
+ }
+
+ /**
+ * This method is called during property updates.
+ *
+ * Here we check if a node implements IProperties and let the node handle
+ * updating of (some) properties.
+ *
+ * @param string $path
+ */
+ public function propPatchNodeUpdate($path, PropPatch $propPatch)
+ {
+ // This should trigger a 404 if the node doesn't exist.
+ $node = $this->server->tree->getNodeForPath($path);
+
+ if ($node instanceof IProperties) {
+ $node->propPatch($propPatch);
+ }
+ }
+
+ /**
+ * This method is called when properties are retrieved.
+ *
+ * Here we add all the default properties.
+ */
+ public function propFind(PropFind $propFind, INode $node)
+ {
+ $propFind->handle('{DAV:}getlastmodified', function () use ($node) {
+ $lm = $node->getLastModified();
+ if ($lm) {
+ return new Xml\Property\GetLastModified($lm);
+ }
+ });
+
+ if ($node instanceof IFile) {
+ $propFind->handle('{DAV:}getcontentlength', [$node, 'getSize']);
+ $propFind->handle('{DAV:}getetag', [$node, 'getETag']);
+ $propFind->handle('{DAV:}getcontenttype', [$node, 'getContentType']);
+ }
+
+ if ($node instanceof IQuota) {
+ $quotaInfo = null;
+ $propFind->handle('{DAV:}quota-used-bytes', function () use (&$quotaInfo, $node) {
+ $quotaInfo = $node->getQuotaInfo();
+
+ return $quotaInfo[0];
+ });
+ $propFind->handle('{DAV:}quota-available-bytes', function () use (&$quotaInfo, $node) {
+ if (!$quotaInfo) {
+ $quotaInfo = $node->getQuotaInfo();
+ }
+
+ return $quotaInfo[1];
+ });
+ }
+
+ $propFind->handle('{DAV:}supported-report-set', function () use ($propFind) {
+ $reports = [];
+ foreach ($this->server->getPlugins() as $plugin) {
+ $reports = array_merge($reports, $plugin->getSupportedReportSet($propFind->getPath()));
+ }
+
+ return new Xml\Property\SupportedReportSet($reports);
+ });
+ $propFind->handle('{DAV:}resourcetype', function () use ($node) {
+ return new Xml\Property\ResourceType($this->server->getResourceTypeForNode($node));
+ });
+ $propFind->handle('{DAV:}supported-method-set', function () use ($propFind) {
+ return new Xml\Property\SupportedMethodSet(
+ $this->server->getAllowedMethods($propFind->getPath())
+ );
+ });
+ }
+
+ /**
+ * Fetches properties for a node.
+ *
+ * This event is called a bit later, so plugins have a chance first to
+ * populate the result.
+ */
+ public function propFindNode(PropFind $propFind, INode $node)
+ {
+ if ($node instanceof IProperties && $propertyNames = $propFind->get404Properties()) {
+ $nodeProperties = $node->getProperties($propertyNames);
+ foreach ($nodeProperties as $propertyName => $propertyValue) {
+ $propFind->set($propertyName, $propertyValue, 200);
+ }
+ }
+ }
+
+ /**
+ * This method is called when properties are retrieved.
+ *
+ * This specific handler is called very late in the process, because we
+ * want other systems to first have a chance to handle the properties.
+ */
+ public function propFindLate(PropFind $propFind, INode $node)
+ {
+ $propFind->handle('{http://calendarserver.org/ns/}getctag', function () use ($propFind) {
+ // If we already have a sync-token from the current propFind
+ // request, we can re-use that.
+ $val = $propFind->get('{http://sabredav.org/ns}sync-token');
+ if ($val) {
+ return $val;
+ }
+
+ $val = $propFind->get('{DAV:}sync-token');
+ if ($val && is_scalar($val)) {
+ return $val;
+ }
+ if ($val && $val instanceof Xml\Property\Href) {
+ return substr($val->getHref(), strlen(Sync\Plugin::SYNCTOKEN_PREFIX));
+ }
+
+ // If we got here, the earlier two properties may simply not have
+ // been part of the earlier request. We're going to fetch them.
+ $result = $this->server->getProperties($propFind->getPath(), [
+ '{http://sabredav.org/ns}sync-token',
+ '{DAV:}sync-token',
+ ]);
+
+ if (isset($result['{http://sabredav.org/ns}sync-token'])) {
+ return $result['{http://sabredav.org/ns}sync-token'];
+ }
+ if (isset($result['{DAV:}sync-token'])) {
+ $val = $result['{DAV:}sync-token'];
+ if (is_scalar($val)) {
+ return $val;
+ } elseif ($val instanceof Xml\Property\Href) {
+ return substr($val->getHref(), strlen(Sync\Plugin::SYNCTOKEN_PREFIX));
+ }
+ }
+ });
+ }
+
+ /**
+ * Listens for exception events, and automatically logs them.
+ *
+ * @param Exception $e
+ */
+ public function exception($e)
+ {
+ $logLevel = \Psr\Log\LogLevel::CRITICAL;
+ if ($e instanceof \Sabre\DAV\Exception) {
+ // If it's a standard sabre/dav exception, it means we have a http
+ // status code available.
+ $code = $e->getHTTPCode();
+
+ if ($code >= 400 && $code < 500) {
+ // user error
+ $logLevel = \Psr\Log\LogLevel::INFO;
+ } else {
+ // Server-side error. We mark it's as an error, but it's not
+ // critical.
+ $logLevel = \Psr\Log\LogLevel::ERROR;
+ }
+ }
+
+ $this->server->getLogger()->log(
+ $logLevel,
+ 'Uncaught exception',
+ [
+ 'exception' => $e,
+ ]
+ );
+ }
+
+ /**
+ * Returns a bunch of meta-data about the plugin.
+ *
+ * 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' => 'The Core plugin provides a lot of the basic functionality required by WebDAV, such as a default implementation for all HTTP and WebDAV methods.',
+ 'link' => null,
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception.php
new file mode 100644
index 000000000..9fc1d16bb
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception.php
@@ -0,0 +1,50 @@
+lock) {
+ $error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:no-conflicting-lock');
+ $errorNode->appendChild($error);
+ $error->appendChild($errorNode->ownerDocument->createElementNS('DAV:', 'd:href', $this->lock->uri));
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/Forbidden.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/Forbidden.php
new file mode 100644
index 000000000..2f882c396
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/Forbidden.php
@@ -0,0 +1,29 @@
+ownerDocument->createElementNS('DAV:', 'd:valid-resourcetype');
+ $errorNode->appendChild($error);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/InvalidSyncToken.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/InvalidSyncToken.php
new file mode 100644
index 000000000..37b28ca54
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/InvalidSyncToken.php
@@ -0,0 +1,34 @@
+ownerDocument->createElementNS('DAV:', 'd:valid-sync-token');
+ $errorNode->appendChild($error);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/LengthRequired.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/LengthRequired.php
new file mode 100644
index 000000000..9d26fcb10
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/LengthRequired.php
@@ -0,0 +1,30 @@
+ownerDocument->createElementNS('DAV:', 'd:lock-token-matches-request-uri');
+ $errorNode->appendChild($error);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/Locked.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/Locked.php
new file mode 100644
index 000000000..28263cf13
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/Locked.php
@@ -0,0 +1,68 @@
+lock = $lock;
+ }
+
+ /**
+ * Returns the HTTP statuscode for this exception.
+ *
+ * @return int
+ */
+ public function getHTTPCode()
+ {
+ return 423;
+ }
+
+ /**
+ * This method allows the exception to include additional information into the WebDAV error response.
+ */
+ public function serialize(DAV\Server $server, \DOMElement $errorNode)
+ {
+ if ($this->lock) {
+ $error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:lock-token-submitted');
+ $errorNode->appendChild($error);
+
+ $href = $errorNode->ownerDocument->createElementNS('DAV:', 'd:href');
+ $href->appendChild($errorNode->ownerDocument->createTextNode($this->lock->uri));
+ $error->appendChild(
+ $href
+ );
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/MethodNotAllowed.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/MethodNotAllowed.php
new file mode 100644
index 000000000..d1ac349bd
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/MethodNotAllowed.php
@@ -0,0 +1,45 @@
+getAllowedMethods($server->getRequestUri());
+
+ return [
+ 'Allow' => strtoupper(implode(', ', $methods)),
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/NotAuthenticated.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/NotAuthenticated.php
new file mode 100644
index 000000000..0a5ba9b5a
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/NotAuthenticated.php
@@ -0,0 +1,30 @@
+header = $header;
+ }
+
+ /**
+ * Returns the HTTP statuscode for this exception.
+ *
+ * @return int
+ */
+ public function getHTTPCode()
+ {
+ return 412;
+ }
+
+ /**
+ * This method allows the exception to include additional information into the WebDAV error response.
+ */
+ public function serialize(DAV\Server $server, \DOMElement $errorNode)
+ {
+ if ($this->header) {
+ $prop = $errorNode->ownerDocument->createElement('s:header');
+ $prop->nodeValue = $this->header;
+ $errorNode->appendChild($prop);
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/ReportNotSupported.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/ReportNotSupported.php
new file mode 100644
index 000000000..a483838e6
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/ReportNotSupported.php
@@ -0,0 +1,28 @@
+ownerDocument->createElementNS('DAV:', 'd:supported-report');
+ $errorNode->appendChild($error);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/RequestedRangeNotSatisfiable.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/RequestedRangeNotSatisfiable.php
new file mode 100644
index 000000000..6ccb5b8c8
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/RequestedRangeNotSatisfiable.php
@@ -0,0 +1,30 @@
+
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class ServiceUnavailable extends DAV\Exception
+{
+ /**
+ * Returns the HTTP statuscode for this exception.
+ *
+ * @return int
+ */
+ public function getHTTPCode()
+ {
+ return 503;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/TooManyMatches.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/TooManyMatches.php
new file mode 100644
index 000000000..3f7d2d5fb
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/TooManyMatches.php
@@ -0,0 +1,34 @@
+ownerDocument->createElementNS('DAV:', 'd:number-of-matches-within-limits');
+ $errorNode->appendChild($error);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/UnsupportedMediaType.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/UnsupportedMediaType.php
new file mode 100644
index 000000000..bc9da30da
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Exception/UnsupportedMediaType.php
@@ -0,0 +1,30 @@
+path.'/'.$name;
+ file_put_contents($newPath, $data);
+ clearstatcache(true, $newPath);
+ }
+
+ /**
+ * Creates a new subdirectory.
+ *
+ * @param string $name
+ */
+ public function createDirectory($name)
+ {
+ $newPath = $this->path.'/'.$name;
+ mkdir($newPath);
+ clearstatcache(true, $newPath);
+ }
+
+ /**
+ * Returns a specific child node, referenced by its name.
+ *
+ * This method must throw DAV\Exception\NotFound if the node does not
+ * exist.
+ *
+ * @param string $name
+ *
+ * @throws DAV\Exception\NotFound
+ *
+ * @return DAV\INode
+ */
+ public function getChild($name)
+ {
+ $path = $this->path.'/'.$name;
+
+ if (!file_exists($path)) {
+ throw new DAV\Exception\NotFound('File with name '.$path.' could not be located');
+ }
+ if (is_dir($path)) {
+ return new self($path);
+ } else {
+ return new File($path);
+ }
+ }
+
+ /**
+ * Returns an array with all the child nodes.
+ *
+ * @return DAV\INode[]
+ */
+ public function getChildren()
+ {
+ $nodes = [];
+ $iterator = new \FilesystemIterator(
+ $this->path,
+ \FilesystemIterator::CURRENT_AS_SELF
+ | \FilesystemIterator::SKIP_DOTS
+ );
+ foreach ($iterator as $entry) {
+ $nodes[] = $this->getChild($entry->getFilename());
+ }
+
+ return $nodes;
+ }
+
+ /**
+ * Checks if a child exists.
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public function childExists($name)
+ {
+ $path = $this->path.'/'.$name;
+
+ return file_exists($path);
+ }
+
+ /**
+ * Deletes all files in this directory, and then itself.
+ */
+ public function delete()
+ {
+ foreach ($this->getChildren() as $child) {
+ $child->delete();
+ }
+ rmdir($this->path);
+ }
+
+ /**
+ * Returns available diskspace information.
+ *
+ * @return array
+ */
+ public function getQuotaInfo()
+ {
+ $absolute = realpath($this->path);
+
+ return [
+ disk_total_space($absolute) - disk_free_space($absolute),
+ disk_free_space($absolute),
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/FS/File.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/FS/File.php
new file mode 100644
index 000000000..b78a80138
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/FS/File.php
@@ -0,0 +1,87 @@
+path, $data);
+ clearstatcache(true, $this->path);
+ }
+
+ /**
+ * Returns the data.
+ *
+ * @return resource
+ */
+ public function get()
+ {
+ return fopen($this->path, 'r');
+ }
+
+ /**
+ * Delete the current file.
+ */
+ public function delete()
+ {
+ unlink($this->path);
+ }
+
+ /**
+ * Returns the size of the node, in bytes.
+ *
+ * @return int
+ */
+ public function getSize()
+ {
+ return filesize($this->path);
+ }
+
+ /**
+ * Returns the ETag for a file.
+ *
+ * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
+ * The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
+ *
+ * Return null if the ETag can not effectively be determined
+ *
+ * @return mixed
+ */
+ public function getETag()
+ {
+ return '"'.sha1(
+ fileinode($this->path).
+ filesize($this->path).
+ filemtime($this->path)
+ ).'"';
+ }
+
+ /**
+ * Returns the mime-type for a file.
+ *
+ * If null is returned, we'll assume application/octet-stream
+ *
+ * @return mixed
+ */
+ public function getContentType()
+ {
+ return null;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/FS/Node.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/FS/Node.php
new file mode 100644
index 000000000..32aa74755
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/FS/Node.php
@@ -0,0 +1,96 @@
+path = $path;
+ $this->overrideName = $overrideName;
+ }
+
+ /**
+ * Returns the name of the node.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ if ($this->overrideName) {
+ return $this->overrideName;
+ }
+
+ list(, $name) = Uri\split($this->path);
+
+ return $name;
+ }
+
+ /**
+ * Renames the node.
+ *
+ * @param string $name The new name
+ */
+ public function setName($name)
+ {
+ if ($this->overrideName) {
+ throw new Forbidden('This node cannot be renamed');
+ }
+
+ list($parentPath) = Uri\split($this->path);
+ list(, $newName) = Uri\split($name);
+
+ $newPath = $parentPath.'/'.$newName;
+ rename($this->path, $newPath);
+
+ $this->path = $newPath;
+ }
+
+ /**
+ * Returns the last modification time, as a unix timestamp.
+ *
+ * @return int
+ */
+ public function getLastModified()
+ {
+ return filemtime($this->path);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/FSExt/Directory.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/FSExt/Directory.php
new file mode 100644
index 000000000..d6aea0094
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/FSExt/Directory.php
@@ -0,0 +1,212 @@
+path.'/'.$name;
+ file_put_contents($newPath, $data);
+ clearstatcache(true, $newPath);
+
+ return '"'.sha1(
+ fileinode($newPath).
+ filesize($newPath).
+ filemtime($newPath)
+ ).'"';
+ }
+
+ /**
+ * Creates a new subdirectory.
+ *
+ * @param string $name
+ */
+ public function createDirectory($name)
+ {
+ // We're not allowing dots
+ if ('.' == $name || '..' == $name) {
+ throw new DAV\Exception\Forbidden('Permission denied to . and ..');
+ }
+ $newPath = $this->path.'/'.$name;
+ mkdir($newPath);
+ clearstatcache(true, $newPath);
+ }
+
+ /**
+ * Returns a specific child node, referenced by its name.
+ *
+ * This method must throw Sabre\DAV\Exception\NotFound if the node does not
+ * exist.
+ *
+ * @param string $name
+ *
+ * @throws DAV\Exception\NotFound
+ *
+ * @return DAV\INode
+ */
+ public function getChild($name)
+ {
+ $path = $this->path.'/'.$name;
+
+ if (!file_exists($path)) {
+ throw new DAV\Exception\NotFound('File could not be located');
+ }
+ if ('.' == $name || '..' == $name) {
+ throw new DAV\Exception\Forbidden('Permission denied to . and ..');
+ }
+ if (is_dir($path)) {
+ return new self($path);
+ } else {
+ return new File($path);
+ }
+ }
+
+ /**
+ * Checks if a child exists.
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public function childExists($name)
+ {
+ if ('.' == $name || '..' == $name) {
+ throw new DAV\Exception\Forbidden('Permission denied to . and ..');
+ }
+ $path = $this->path.'/'.$name;
+
+ return file_exists($path);
+ }
+
+ /**
+ * Returns an array with all the child nodes.
+ *
+ * @return DAV\INode[]
+ */
+ public function getChildren()
+ {
+ $nodes = [];
+ $iterator = new \FilesystemIterator(
+ $this->path,
+ \FilesystemIterator::CURRENT_AS_SELF
+ | \FilesystemIterator::SKIP_DOTS
+ );
+
+ foreach ($iterator as $entry) {
+ $nodes[] = $this->getChild($entry->getFilename());
+ }
+
+ return $nodes;
+ }
+
+ /**
+ * Deletes all files in this directory, and then itself.
+ *
+ * @return bool
+ */
+ public function delete()
+ {
+ // Deleting all children
+ foreach ($this->getChildren() as $child) {
+ $child->delete();
+ }
+
+ // Removing the directory itself
+ rmdir($this->path);
+
+ return true;
+ }
+
+ /**
+ * Returns available diskspace information.
+ *
+ * @return array
+ */
+ public function getQuotaInfo()
+ {
+ $total = disk_total_space(realpath($this->path));
+ $free = disk_free_space(realpath($this->path));
+
+ return [
+ $total - $free,
+ $free,
+ ];
+ }
+
+ /**
+ * Moves a node into this collection.
+ *
+ * It is up to the implementors to:
+ * 1. Create the new resource.
+ * 2. Remove the old resource.
+ * 3. Transfer any properties or other data.
+ *
+ * Generally you should make very sure that your collection can easily move
+ * the move.
+ *
+ * If you don't, just return false, which will trigger sabre/dav to handle
+ * the move itself. If you return true from this function, the assumption
+ * is that the move was successful.
+ *
+ * @param string $targetName new local file/collection name
+ * @param string $sourcePath Full path to source node
+ * @param DAV\INode $sourceNode Source node itself
+ *
+ * @return bool
+ */
+ public function moveInto($targetName, $sourcePath, DAV\INode $sourceNode)
+ {
+ // We only support FSExt\Directory or FSExt\File objects, so
+ // anything else we want to quickly reject.
+ if (!$sourceNode instanceof self && !$sourceNode instanceof File) {
+ return false;
+ }
+
+ // PHP allows us to access protected properties from other objects, as
+ // long as they are defined in a class that has a shared inheritance
+ // with the current class.
+ return rename($sourceNode->path, $this->path.'/'.$targetName);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/FSExt/File.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/FSExt/File.php
new file mode 100644
index 000000000..060ef5a48
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/FSExt/File.php
@@ -0,0 +1,150 @@
+path, $data);
+ clearstatcache(true, $this->path);
+
+ return $this->getETag();
+ }
+
+ /**
+ * Updates the file based on a range specification.
+ *
+ * The first argument is the data, which is either a readable stream
+ * resource or a string.
+ *
+ * The second argument is the type of update we're doing.
+ * This is either:
+ * * 1. append
+ * * 2. update based on a start byte
+ * * 3. update based on an end byte
+ *;
+ * The third argument is the start or end byte.
+ *
+ * After a successful put operation, you may choose to return an ETag. The
+ * ETAG must always be surrounded by double-quotes. These quotes must
+ * appear in the actual string you're returning.
+ *
+ * Clients may use the ETag from a PUT request to later on make sure that
+ * when they update the file, the contents haven't changed in the mean
+ * time.
+ *
+ * @param resource|string $data
+ * @param int $rangeType
+ * @param int $offset
+ *
+ * @return string|null
+ */
+ public function patch($data, $rangeType, $offset = null)
+ {
+ switch ($rangeType) {
+ case 1:
+ $f = fopen($this->path, 'a');
+ break;
+ case 2:
+ $f = fopen($this->path, 'c');
+ fseek($f, $offset);
+ break;
+ case 3:
+ $f = fopen($this->path, 'c');
+ fseek($f, $offset, SEEK_END);
+ break;
+ }
+ if (is_string($data)) {
+ fwrite($f, $data);
+ } else {
+ stream_copy_to_stream($data, $f);
+ }
+ fclose($f);
+ clearstatcache(true, $this->path);
+
+ return $this->getETag();
+ }
+
+ /**
+ * Returns the data.
+ *
+ * @return resource
+ */
+ public function get()
+ {
+ return fopen($this->path, 'r');
+ }
+
+ /**
+ * Delete the current file.
+ *
+ * @return bool
+ */
+ public function delete()
+ {
+ return unlink($this->path);
+ }
+
+ /**
+ * Returns the ETag for a file.
+ *
+ * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
+ * The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
+ *
+ * Return null if the ETag can not effectively be determined
+ *
+ * @return string|null
+ */
+ public function getETag()
+ {
+ return '"'.sha1(
+ fileinode($this->path).
+ filesize($this->path).
+ filemtime($this->path)
+ ).'"';
+ }
+
+ /**
+ * Returns the mime-type for a file.
+ *
+ * If null is returned, we'll assume application/octet-stream
+ *
+ * @return string|null
+ */
+ public function getContentType()
+ {
+ return null;
+ }
+
+ /**
+ * Returns the size of the file, in bytes.
+ *
+ * @return int
+ */
+ public function getSize()
+ {
+ return filesize($this->path);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/File.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/File.php
new file mode 100644
index 000000000..daf83aa4d
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/File.php
@@ -0,0 +1,93 @@
+locksFile = $locksFile;
+ }
+
+ /**
+ * Returns a list of Sabre\DAV\Locks\LockInfo objects.
+ *
+ * This method should return all the locks for a particular uri, including
+ * locks that might be set on a parent uri.
+ *
+ * If returnChildLocks is set to true, this method should also look for
+ * any locks in the subtree of the uri for locks.
+ *
+ * @param string $uri
+ * @param bool $returnChildLocks
+ *
+ * @return array
+ */
+ public function getLocks($uri, $returnChildLocks)
+ {
+ $newLocks = [];
+
+ $locks = $this->getData();
+
+ foreach ($locks as $lock) {
+ if ($lock->uri === $uri ||
+ //deep locks on parents
+ (0 != $lock->depth && 0 === strpos($uri, $lock->uri.'/')) ||
+
+ // locks on children
+ ($returnChildLocks && (0 === strpos($lock->uri, $uri.'/')))) {
+ $newLocks[] = $lock;
+ }
+ }
+
+ // Checking if we can remove any of these locks
+ foreach ($newLocks as $k => $lock) {
+ if (time() > $lock->timeout + $lock->created) {
+ unset($newLocks[$k]);
+ }
+ }
+
+ return $newLocks;
+ }
+
+ /**
+ * Locks a uri.
+ *
+ * @param string $uri
+ *
+ * @return bool
+ */
+ public function lock($uri, LockInfo $lockInfo)
+ {
+ // We're making the lock timeout 30 minutes
+ $lockInfo->timeout = 1800;
+ $lockInfo->created = time();
+ $lockInfo->uri = $uri;
+
+ $locks = $this->getData();
+
+ foreach ($locks as $k => $lock) {
+ if (
+ ($lock->token == $lockInfo->token) ||
+ (time() > $lock->timeout + $lock->created)
+ ) {
+ unset($locks[$k]);
+ }
+ }
+ $locks[] = $lockInfo;
+ $this->putData($locks);
+
+ return true;
+ }
+
+ /**
+ * Removes a lock from a uri.
+ *
+ * @param string $uri
+ *
+ * @return bool
+ */
+ public function unlock($uri, LockInfo $lockInfo)
+ {
+ $locks = $this->getData();
+ foreach ($locks as $k => $lock) {
+ if ($lock->token == $lockInfo->token) {
+ unset($locks[$k]);
+ $this->putData($locks);
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Loads the lockdata from the filesystem.
+ *
+ * @return array
+ */
+ protected function getData()
+ {
+ if (!file_exists($this->locksFile)) {
+ return [];
+ }
+
+ // opening up the file, and creating a shared lock
+ $handle = fopen($this->locksFile, 'r');
+ flock($handle, LOCK_SH);
+
+ // Reading data until the eof
+ $data = stream_get_contents($handle);
+
+ // We're all good
+ flock($handle, LOCK_UN);
+ fclose($handle);
+
+ // Unserializing and checking if the resource file contains data for this file
+ $data = unserialize($data);
+ if (!$data) {
+ return [];
+ }
+
+ return $data;
+ }
+
+ /**
+ * Saves the lockdata.
+ */
+ protected function putData(array $newData)
+ {
+ // opening up the file, and creating an exclusive lock
+ $handle = fopen($this->locksFile, 'a+');
+ flock($handle, LOCK_EX);
+
+ // We can only truncate and rewind once the lock is acquired.
+ ftruncate($handle, 0);
+ rewind($handle);
+
+ fwrite($handle, serialize($newData));
+ flock($handle, LOCK_UN);
+ fclose($handle);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php
new file mode 100644
index 000000000..3f425f98d
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php
@@ -0,0 +1,172 @@
+pdo = $pdo;
+ }
+
+ /**
+ * Returns a list of Sabre\DAV\Locks\LockInfo objects.
+ *
+ * This method should return all the locks for a particular uri, including
+ * locks that might be set on a parent uri.
+ *
+ * If returnChildLocks is set to true, this method should also look for
+ * any locks in the subtree of the uri for locks.
+ *
+ * @param string $uri
+ * @param bool $returnChildLocks
+ *
+ * @return array
+ */
+ public function getLocks($uri, $returnChildLocks)
+ {
+ // NOTE: the following 10 lines or so could be easily replaced by
+ // pure sql. MySQL's non-standard string concatenation prevents us
+ // from doing this though.
+ $query = 'SELECT owner, token, timeout, created, scope, depth, uri FROM '.$this->tableName.' WHERE (created > (? - timeout)) AND ((uri = ?)';
+ $params = [time(), $uri];
+
+ // We need to check locks for every part in the uri.
+ $uriParts = explode('/', $uri);
+
+ // We already covered the last part of the uri
+ array_pop($uriParts);
+
+ $currentPath = '';
+
+ foreach ($uriParts as $part) {
+ if ($currentPath) {
+ $currentPath .= '/';
+ }
+ $currentPath .= $part;
+
+ $query .= ' OR (depth!=0 AND uri = ?)';
+ $params[] = $currentPath;
+ }
+
+ if ($returnChildLocks) {
+ $query .= ' OR (uri LIKE ?)';
+ $params[] = $uri.'/%';
+ }
+ $query .= ')';
+
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute($params);
+ $result = $stmt->fetchAll();
+
+ $lockList = [];
+ foreach ($result as $row) {
+ $lockInfo = new LockInfo();
+ $lockInfo->owner = $row['owner'];
+ $lockInfo->token = $row['token'];
+ $lockInfo->timeout = $row['timeout'];
+ $lockInfo->created = $row['created'];
+ $lockInfo->scope = $row['scope'];
+ $lockInfo->depth = $row['depth'];
+ $lockInfo->uri = $row['uri'];
+ $lockList[] = $lockInfo;
+ }
+
+ return $lockList;
+ }
+
+ /**
+ * Locks a uri.
+ *
+ * @param string $uri
+ *
+ * @return bool
+ */
+ public function lock($uri, LockInfo $lockInfo)
+ {
+ // We're making the lock timeout 30 minutes
+ $lockInfo->timeout = 30 * 60;
+ $lockInfo->created = time();
+ $lockInfo->uri = $uri;
+
+ $locks = $this->getLocks($uri, false);
+ $exists = false;
+ foreach ($locks as $lock) {
+ if ($lock->token == $lockInfo->token) {
+ $exists = true;
+ }
+ }
+
+ if ($exists) {
+ $stmt = $this->pdo->prepare('UPDATE '.$this->tableName.' SET owner = ?, timeout = ?, scope = ?, depth = ?, uri = ?, created = ? WHERE token = ?');
+ $stmt->execute([
+ $lockInfo->owner,
+ $lockInfo->timeout,
+ $lockInfo->scope,
+ $lockInfo->depth,
+ $uri,
+ $lockInfo->created,
+ $lockInfo->token,
+ ]);
+ } else {
+ $stmt = $this->pdo->prepare('INSERT INTO '.$this->tableName.' (owner,timeout,scope,depth,uri,created,token) VALUES (?,?,?,?,?,?,?)');
+ $stmt->execute([
+ $lockInfo->owner,
+ $lockInfo->timeout,
+ $lockInfo->scope,
+ $lockInfo->depth,
+ $uri,
+ $lockInfo->created,
+ $lockInfo->token,
+ ]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Removes a lock from a uri.
+ *
+ * @param string $uri
+ *
+ * @return bool
+ */
+ public function unlock($uri, LockInfo $lockInfo)
+ {
+ $stmt = $this->pdo->prepare('DELETE FROM '.$this->tableName.' WHERE uri = ? AND token = ?');
+ $stmt->execute([$uri, $lockInfo->token]);
+
+ return 1 === $stmt->rowCount();
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Locks/LockInfo.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Locks/LockInfo.php
new file mode 100644
index 000000000..df8227566
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Locks/LockInfo.php
@@ -0,0 +1,82 @@
+addPlugin($lockPlugin);
+ *
+ * @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 DAV\ServerPlugin
+{
+ /**
+ * locksBackend.
+ *
+ * @var Backend\BackendInterface
+ */
+ protected $locksBackend;
+
+ /**
+ * server.
+ *
+ * @var DAV\Server
+ */
+ protected $server;
+
+ /**
+ * __construct.
+ */
+ public function __construct(Backend\BackendInterface $locksBackend)
+ {
+ $this->locksBackend = $locksBackend;
+ }
+
+ /**
+ * Initializes the plugin.
+ *
+ * This method is automatically called by the Server class after addPlugin.
+ */
+ public function initialize(DAV\Server $server)
+ {
+ $this->server = $server;
+
+ $this->server->xml->elementMap['{DAV:}lockinfo'] = 'Sabre\\DAV\\Xml\\Request\\Lock';
+
+ $server->on('method:LOCK', [$this, 'httpLock']);
+ $server->on('method:UNLOCK', [$this, 'httpUnlock']);
+ $server->on('validateTokens', [$this, 'validateTokens']);
+ $server->on('propFind', [$this, 'propFind']);
+ $server->on('afterUnbind', [$this, 'afterUnbind']);
+ }
+
+ /**
+ * 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 'locks';
+ }
+
+ /**
+ * This method is called after most properties have been found
+ * it allows us to add in any Lock-related properties.
+ */
+ public function propFind(DAV\PropFind $propFind, DAV\INode $node)
+ {
+ $propFind->handle('{DAV:}supportedlock', function () {
+ return new DAV\Xml\Property\SupportedLock();
+ });
+ $propFind->handle('{DAV:}lockdiscovery', function () use ($propFind) {
+ return new DAV\Xml\Property\LockDiscovery(
+ $this->getLocks($propFind->getPath())
+ );
+ });
+ }
+
+ /**
+ * Use this method to tell the server this plugin defines additional
+ * HTTP methods.
+ *
+ * This method is passed a uri. It should only return HTTP methods that are
+ * available for the specified uri.
+ *
+ * @param string $uri
+ *
+ * @return array
+ */
+ public function getHTTPMethods($uri)
+ {
+ return ['LOCK', 'UNLOCK'];
+ }
+
+ /**
+ * Returns a list of features for the HTTP OPTIONS Dav: header.
+ *
+ * In this case this is only the number 2. The 2 in the Dav: header
+ * indicates the server supports locks.
+ *
+ * @return array
+ */
+ public function getFeatures()
+ {
+ return [2];
+ }
+
+ /**
+ * Returns all lock information on a particular uri.
+ *
+ * This function should return an array with Sabre\DAV\Locks\LockInfo objects. If there are no locks on a file, return an empty array.
+ *
+ * Additionally there is also the possibility of locks on parent nodes, so we'll need to traverse every part of the tree
+ * If the $returnChildLocks argument is set to true, we'll also traverse all the children of the object
+ * for any possible locks and return those as well.
+ *
+ * @param string $uri
+ * @param bool $returnChildLocks
+ *
+ * @return array
+ */
+ public function getLocks($uri, $returnChildLocks = false)
+ {
+ return $this->locksBackend->getLocks($uri, $returnChildLocks);
+ }
+
+ /**
+ * Locks an uri.
+ *
+ * The WebDAV lock request can be operated to either create a new lock on a file, or to refresh an existing lock
+ * If a new lock is created, a full XML body should be supplied, containing information about the lock such as the type
+ * of lock (shared or exclusive) and the owner of the lock
+ *
+ * If a lock is to be refreshed, no body should be supplied and there should be a valid If header containing the lock
+ *
+ * Additionally, a lock can be requested for a non-existent file. In these case we're obligated to create an empty file as per RFC4918:S7.3
+ *
+ * @return bool
+ */
+ public function httpLock(RequestInterface $request, ResponseInterface $response)
+ {
+ $uri = $request->getPath();
+
+ $existingLocks = $this->getLocks($uri);
+
+ if ($body = $request->getBodyAsString()) {
+ // This is a new lock request
+
+ $existingLock = null;
+ // Checking if there's already non-shared locks on the uri.
+ foreach ($existingLocks as $existingLock) {
+ if (LockInfo::EXCLUSIVE === $existingLock->scope) {
+ throw new DAV\Exception\ConflictingLock($existingLock);
+ }
+ }
+
+ $lockInfo = $this->parseLockRequest($body);
+ $lockInfo->depth = $this->server->getHTTPDepth();
+ $lockInfo->uri = $uri;
+ if ($existingLock && LockInfo::SHARED != $lockInfo->scope) {
+ throw new DAV\Exception\ConflictingLock($existingLock);
+ }
+ } else {
+ // Gonna check if this was a lock refresh.
+ $existingLocks = $this->getLocks($uri);
+ $conditions = $this->server->getIfConditions($request);
+ $found = null;
+
+ foreach ($existingLocks as $existingLock) {
+ foreach ($conditions as $condition) {
+ foreach ($condition['tokens'] as $token) {
+ if ($token['token'] === 'opaquelocktoken:'.$existingLock->token) {
+ $found = $existingLock;
+ break 3;
+ }
+ }
+ }
+ }
+
+ // If none were found, this request is in error.
+ if (is_null($found)) {
+ if ($existingLocks) {
+ throw new DAV\Exception\Locked(reset($existingLocks));
+ } else {
+ throw new DAV\Exception\BadRequest('An xml body is required for lock requests');
+ }
+ }
+
+ // This must have been a lock refresh
+ $lockInfo = $found;
+
+ // The resource could have been locked through another uri.
+ if ($uri != $lockInfo->uri) {
+ $uri = $lockInfo->uri;
+ }
+ }
+
+ if ($timeout = $this->getTimeoutHeader()) {
+ $lockInfo->timeout = $timeout;
+ }
+
+ $newFile = false;
+
+ // If we got this far.. we should go check if this node actually exists. If this is not the case, we need to create it first
+ try {
+ $this->server->tree->getNodeForPath($uri);
+
+ // We need to call the beforeWriteContent event for RFC3744
+ // Edit: looks like this is not used, and causing problems now.
+ //
+ // See Issue 222
+ // $this->server->emit('beforeWriteContent',array($uri));
+ } catch (DAV\Exception\NotFound $e) {
+ // It didn't, lets create it
+ $this->server->createFile($uri, fopen('php://memory', 'r'));
+ $newFile = true;
+ }
+
+ $this->lockNode($uri, $lockInfo);
+
+ $response->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ $response->setHeader('Lock-Token', 'token.'>');
+ $response->setStatus($newFile ? 201 : 200);
+ $response->setBody($this->generateLockResponse($lockInfo));
+
+ // Returning false will interrupt the event chain and mark this method
+ // as 'handled'.
+ return false;
+ }
+
+ /**
+ * Unlocks a uri.
+ *
+ * This WebDAV method allows you to remove a lock from a node. The client should provide a valid locktoken through the Lock-token http header
+ * The server should return 204 (No content) on success
+ */
+ public function httpUnlock(RequestInterface $request, ResponseInterface $response)
+ {
+ $lockToken = $request->getHeader('Lock-Token');
+
+ // If the locktoken header is not supplied, we need to throw a bad request exception
+ if (!$lockToken) {
+ throw new DAV\Exception\BadRequest('No lock token was supplied');
+ }
+ $path = $request->getPath();
+ $locks = $this->getLocks($path);
+
+ // Windows sometimes forgets to include < and > in the Lock-Token
+ // header
+ if ('<' !== $lockToken[0]) {
+ $lockToken = '<'.$lockToken.'>';
+ }
+
+ foreach ($locks as $lock) {
+ if ('token.'>' == $lockToken) {
+ $this->unlockNode($path, $lock);
+ $response->setHeader('Content-Length', '0');
+ $response->setStatus(204);
+
+ // Returning false will break the method chain, and mark the
+ // method as 'handled'.
+ return false;
+ }
+ }
+
+ // If we got here, it means the locktoken was invalid
+ throw new DAV\Exception\LockTokenMatchesRequestUri();
+ }
+
+ /**
+ * This method is called after a node is deleted.
+ *
+ * We use this event to clean up any locks that still exist on the node.
+ *
+ * @param string $path
+ */
+ public function afterUnbind($path)
+ {
+ $locks = $this->getLocks($path, $includeChildren = true);
+ foreach ($locks as $lock) {
+ $this->unlockNode($path, $lock);
+ }
+ }
+
+ /**
+ * Locks a uri.
+ *
+ * All the locking information is supplied in the lockInfo object. The object has a suggested timeout, but this can be safely ignored
+ * It is important that if the existing timeout is ignored, the property is overwritten, as this needs to be sent back to the client
+ *
+ * @param string $uri
+ *
+ * @return bool
+ */
+ public function lockNode($uri, LockInfo $lockInfo)
+ {
+ if (!$this->server->emit('beforeLock', [$uri, $lockInfo])) {
+ return;
+ }
+
+ return $this->locksBackend->lock($uri, $lockInfo);
+ }
+
+ /**
+ * Unlocks a uri.
+ *
+ * This method removes a lock from a uri. It is assumed all the supplied information is correct and verified
+ *
+ * @param string $uri
+ *
+ * @return bool
+ */
+ public function unlockNode($uri, LockInfo $lockInfo)
+ {
+ if (!$this->server->emit('beforeUnlock', [$uri, $lockInfo])) {
+ return;
+ }
+
+ return $this->locksBackend->unlock($uri, $lockInfo);
+ }
+
+ /**
+ * Returns the contents of the HTTP Timeout header.
+ *
+ * The method formats the header into an integer.
+ *
+ * @return int
+ */
+ public function getTimeoutHeader()
+ {
+ $header = $this->server->httpRequest->getHeader('Timeout');
+
+ if ($header) {
+ if (0 === stripos($header, 'second-')) {
+ $header = (int) (substr($header, 7));
+ } elseif (0 === stripos($header, 'infinite')) {
+ $header = LockInfo::TIMEOUT_INFINITE;
+ } else {
+ throw new DAV\Exception\BadRequest('Invalid HTTP timeout header');
+ }
+ } else {
+ $header = 0;
+ }
+
+ return $header;
+ }
+
+ /**
+ * Generates the response for successful LOCK requests.
+ *
+ * @return string
+ */
+ protected function generateLockResponse(LockInfo $lockInfo)
+ {
+ return $this->server->xml->write('{DAV:}prop', [
+ '{DAV:}lockdiscovery' => new DAV\Xml\Property\LockDiscovery([$lockInfo]),
+ ]);
+ }
+
+ /**
+ * The validateTokens event is triggered before every request.
+ *
+ * It's a moment where this plugin can check all the supplied lock tokens
+ * in the If: header, and check if they are valid.
+ *
+ * In addition, it will also ensure that it checks any missing lokens that
+ * must be present in the request, and reject requests without the proper
+ * tokens.
+ *
+ * @param mixed $conditions
+ */
+ public function validateTokens(RequestInterface $request, &$conditions)
+ {
+ // First we need to gather a list of locks that must be satisfied.
+ $mustLocks = [];
+ $method = $request->getMethod();
+
+ // Methods not in that list are operations that doesn't alter any
+ // resources, and we don't need to check the lock-states for.
+ switch ($method) {
+ case 'DELETE':
+ $mustLocks = array_merge($mustLocks, $this->getLocks(
+ $request->getPath(),
+ true
+ ));
+ break;
+ case 'MKCOL':
+ case 'MKCALENDAR':
+ case 'PROPPATCH':
+ case 'PUT':
+ case 'PATCH':
+ $mustLocks = array_merge($mustLocks, $this->getLocks(
+ $request->getPath(),
+ false
+ ));
+ break;
+ case 'MOVE':
+ $mustLocks = array_merge($mustLocks, $this->getLocks(
+ $request->getPath(),
+ true
+ ));
+ $mustLocks = array_merge($mustLocks, $this->getLocks(
+ $this->server->calculateUri($request->getHeader('Destination')),
+ false
+ ));
+ break;
+ case 'COPY':
+ $mustLocks = array_merge($mustLocks, $this->getLocks(
+ $this->server->calculateUri($request->getHeader('Destination')),
+ false
+ ));
+ break;
+ case 'LOCK':
+ //Temporary measure.. figure out later why this is needed
+ // Here we basically ignore all incoming tokens...
+ foreach ($conditions as $ii => $condition) {
+ foreach ($condition['tokens'] as $jj => $token) {
+ $conditions[$ii]['tokens'][$jj]['validToken'] = true;
+ }
+ }
+
+ return;
+ }
+
+ // It's possible that there's identical locks, because of shared
+ // parents. We're removing the duplicates here.
+ $tmp = [];
+ foreach ($mustLocks as $lock) {
+ $tmp[$lock->token] = $lock;
+ }
+ $mustLocks = array_values($tmp);
+
+ foreach ($conditions as $kk => $condition) {
+ foreach ($condition['tokens'] as $ii => $token) {
+ // Lock tokens always start with opaquelocktoken:
+ if ('opaquelocktoken:' !== substr($token['token'], 0, 16)) {
+ continue;
+ }
+
+ $checkToken = substr($token['token'], 16);
+ // Looping through our list with locks.
+ foreach ($mustLocks as $jj => $mustLock) {
+ if ($mustLock->token == $checkToken) {
+ // We have a match!
+ // Removing this one from mustlocks
+ unset($mustLocks[$jj]);
+
+ // Marking the condition as valid.
+ $conditions[$kk]['tokens'][$ii]['validToken'] = true;
+
+ // Advancing to the next token
+ continue 2;
+ }
+ }
+
+ // If we got here, it means that there was a
+ // lock-token, but it was not in 'mustLocks'.
+ //
+ // This is an edge-case, as it could mean that token
+ // was specified with a url that was not 'required' to
+ // check. So we're doing one extra lookup to make sure
+ // we really don't know this token.
+ //
+ // This also gets triggered when the user specified a
+ // lock-token that was expired.
+ $oddLocks = $this->getLocks($condition['uri']);
+ foreach ($oddLocks as $oddLock) {
+ if ($oddLock->token === $checkToken) {
+ // We have a hit!
+ $conditions[$kk]['tokens'][$ii]['validToken'] = true;
+ continue 2;
+ }
+ }
+
+ // If we get all the way here, the lock-token was
+ // really unknown.
+ }
+ }
+
+ // If there's any locks left in the 'mustLocks' array, it means that
+ // the resource was locked and we must block it.
+ if ($mustLocks) {
+ throw new DAV\Exception\Locked(reset($mustLocks));
+ }
+ }
+
+ /**
+ * Parses a webdav lock xml body, and returns a new Sabre\DAV\Locks\LockInfo object.
+ *
+ * @param string $body
+ *
+ * @return LockInfo
+ */
+ protected function parseLockRequest($body)
+ {
+ $result = $this->server->xml->expect(
+ '{DAV:}lockinfo',
+ $body
+ );
+
+ $lockInfo = new LockInfo();
+
+ $lockInfo->owner = $result->owner;
+ $lockInfo->token = DAV\UUIDUtil::getUUID();
+ $lockInfo->scope = $result->scope;
+
+ return $lockInfo;
+ }
+
+ /**
+ * 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' => 'The locks plugin turns this server into a class-2 WebDAV server and adds support for LOCK and UNLOCK',
+ 'link' => 'http://sabre.io/dav/locks/',
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/MkCol.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/MkCol.php
new file mode 100644
index 000000000..f3c5ea5c0
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/MkCol.php
@@ -0,0 +1,71 @@
+resourceType = $resourceType;
+ parent::__construct($mutations);
+ }
+
+ /**
+ * Returns the resourcetype of the new collection.
+ *
+ * @return string[]
+ */
+ public function getResourceType()
+ {
+ return $this->resourceType;
+ }
+
+ /**
+ * Returns true or false if the MKCOL operation has at least the specified
+ * resource type.
+ *
+ * If the resourcetype is specified as an array, all resourcetypes are
+ * checked.
+ *
+ * @param string|string[] $resourceType
+ *
+ * @return bool
+ */
+ public function hasResourceType($resourceType)
+ {
+ return 0 === count(array_diff((array) $resourceType, $this->resourceType));
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Mount/Plugin.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Mount/Plugin.php
new file mode 100644
index 000000000..b7f4851f6
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Mount/Plugin.php
@@ -0,0 +1,78 @@
+server = $server;
+ $this->server->on('method:GET', [$this, 'httpGet'], 90);
+ }
+
+ /**
+ * 'beforeMethod' event handles. This event handles intercepts GET requests ending
+ * with ?mount.
+ *
+ * @return bool
+ */
+ public function httpGet(RequestInterface $request, ResponseInterface $response)
+ {
+ $queryParams = $request->getQueryParameters();
+ if (!array_key_exists('mount', $queryParams)) {
+ return;
+ }
+
+ $currentUri = $request->getAbsoluteUrl();
+
+ // Stripping off everything after the ?
+ list($currentUri) = explode('?', $currentUri);
+
+ $this->davMount($response, $currentUri);
+
+ // Returning false to break the event chain
+ return false;
+ }
+
+ /**
+ * Generates the davmount response.
+ *
+ * @param string $uri absolute uri
+ */
+ public function davMount(ResponseInterface $response, $uri)
+ {
+ $response->setStatus(200);
+ $response->setHeader('Content-Type', 'application/davmount+xml');
+ ob_start();
+ echo '', "\n";
+ echo "\n";
+ echo ' ', htmlspecialchars($uri, ENT_NOQUOTES, 'UTF-8'), "\n";
+ echo '';
+ $response->setBody(ob_get_clean());
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Node.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Node.php
new file mode 100644
index 000000000..948060d9a
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Node.php
@@ -0,0 +1,51 @@
+addPlugin($patchPlugin);
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Jean-Tiare LE BIGOT (http://www.jtlebi.fr/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Plugin extends DAV\ServerPlugin
+{
+ const RANGE_APPEND = 1;
+ const RANGE_START = 2;
+ const RANGE_END = 3;
+
+ /**
+ * Reference to server.
+ *
+ * @var DAV\Server
+ */
+ protected $server;
+
+ /**
+ * Initializes the plugin.
+ *
+ * This method is automatically called by the Server class after addPlugin.
+ */
+ public function initialize(DAV\Server $server)
+ {
+ $this->server = $server;
+ $server->on('method:PATCH', [$this, 'httpPatch']);
+ }
+
+ /**
+ * Returns a plugin name.
+ *
+ * Using this name other plugins will be able to access other plugins
+ * using DAV\Server::getPlugin
+ *
+ * @return string
+ */
+ public function getPluginName()
+ {
+ return 'partialupdate';
+ }
+
+ /**
+ * Use this method to tell the server this plugin defines additional
+ * HTTP methods.
+ *
+ * This method is passed a uri. It should only return HTTP methods that are
+ * available for the specified uri.
+ *
+ * We claim to support PATCH method (partirl update) if and only if
+ * - the node exist
+ * - the node implements our partial update interface
+ *
+ * @param string $uri
+ *
+ * @return array
+ */
+ public function getHTTPMethods($uri)
+ {
+ $tree = $this->server->tree;
+
+ if ($tree->nodeExists($uri)) {
+ $node = $tree->getNodeForPath($uri);
+ if ($node instanceof IPatchSupport) {
+ return ['PATCH'];
+ }
+ }
+
+ return [];
+ }
+
+ /**
+ * Returns a list of features for the HTTP OPTIONS Dav: header.
+ *
+ * @return array
+ */
+ public function getFeatures()
+ {
+ return ['sabredav-partialupdate'];
+ }
+
+ /**
+ * Patch an uri.
+ *
+ * The WebDAV patch request can be used to modify only a part of an
+ * existing resource. If the resource does not exist yet and the first
+ * offset is not 0, the request fails
+ */
+ public function httpPatch(RequestInterface $request, ResponseInterface $response)
+ {
+ $path = $request->getPath();
+
+ // Get the node. Will throw a 404 if not found
+ $node = $this->server->tree->getNodeForPath($path);
+ if (!$node instanceof IPatchSupport) {
+ throw new DAV\Exception\MethodNotAllowed('The target resource does not support the PATCH method.');
+ }
+
+ $range = $this->getHTTPUpdateRange($request);
+
+ if (!$range) {
+ throw new DAV\Exception\BadRequest('No valid "X-Update-Range" found in the headers');
+ }
+
+ $contentType = strtolower(
+ (string) $request->getHeader('Content-Type')
+ );
+
+ if ('application/x-sabredav-partialupdate' != $contentType) {
+ throw new DAV\Exception\UnsupportedMediaType('Unknown Content-Type header "'.$contentType.'"');
+ }
+
+ $len = $this->server->httpRequest->getHeader('Content-Length');
+ if (!$len) {
+ throw new DAV\Exception\LengthRequired('A Content-Length header is required');
+ }
+ switch ($range[0]) {
+ case self::RANGE_START:
+ // Calculate the end-range if it doesn't exist.
+ if (!$range[2]) {
+ $range[2] = $range[1] + $len - 1;
+ } else {
+ if ($range[2] < $range[1]) {
+ throw new DAV\Exception\RequestedRangeNotSatisfiable('The end offset ('.$range[2].') is lower than the start offset ('.$range[1].')');
+ }
+ if ($range[2] - $range[1] + 1 != $len) {
+ throw new DAV\Exception\RequestedRangeNotSatisfiable('Actual data length ('.$len.') is not consistent with begin ('.$range[1].') and end ('.$range[2].') offsets');
+ }
+ }
+ break;
+ }
+
+ if (!$this->server->emit('beforeWriteContent', [$path, $node, null])) {
+ return;
+ }
+
+ $body = $this->server->httpRequest->getBody();
+
+ $etag = $node->patch($body, $range[0], isset($range[1]) ? $range[1] : null);
+
+ $this->server->emit('afterWriteContent', [$path, $node]);
+
+ $response->setHeader('Content-Length', '0');
+ if ($etag) {
+ $response->setHeader('ETag', $etag);
+ }
+ $response->setStatus(204);
+
+ // Breaks the event chain
+ return false;
+ }
+
+ /**
+ * Returns the HTTP custom range update header.
+ *
+ * This method returns null if there is no well-formed HTTP range request
+ * header. It returns array(1) if it was an append request, array(2,
+ * $start, $end) if it's a start and end range, lastly it's array(3,
+ * $endoffset) if the offset was negative, and should be calculated from
+ * the end of the file.
+ *
+ * Examples:
+ *
+ * null - invalid
+ * [1] - append
+ * [2,10,15] - update bytes 10, 11, 12, 13, 14, 15
+ * [2,10,null] - update bytes 10 until the end of the patch body
+ * [3,-5] - update from 5 bytes from the end of the file.
+ *
+ * @return array|null
+ */
+ public function getHTTPUpdateRange(RequestInterface $request)
+ {
+ $range = $request->getHeader('X-Update-Range');
+ if (is_null($range)) {
+ return null;
+ }
+
+ // Matching "Range: bytes=1234-5678: both numbers are optional
+
+ if (!preg_match('/^(append)|(?:bytes=([0-9]+)-([0-9]*))|(?:bytes=(-[0-9]+))$/i', $range, $matches)) {
+ return null;
+ }
+
+ if ('append' === $matches[1]) {
+ return [self::RANGE_APPEND];
+ } elseif (strlen($matches[2]) > 0) {
+ return [self::RANGE_START, (int) $matches[2], (int) $matches[3] ?: null];
+ } else {
+ return [self::RANGE_END, (int) $matches[4]];
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/PropFind.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/PropFind.php
new file mode 100644
index 000000000..e9ffb07cb
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/PropFind.php
@@ -0,0 +1,335 @@
+path = $path;
+ $this->properties = $properties;
+ $this->depth = $depth;
+ $this->requestType = $requestType;
+
+ if (self::ALLPROPS === $requestType) {
+ $this->properties = [
+ '{DAV:}getlastmodified',
+ '{DAV:}getcontentlength',
+ '{DAV:}resourcetype',
+ '{DAV:}quota-used-bytes',
+ '{DAV:}quota-available-bytes',
+ '{DAV:}getetag',
+ '{DAV:}getcontenttype',
+ ];
+ }
+
+ foreach ($this->properties as $propertyName) {
+ // Seeding properties with 404's.
+ $this->result[$propertyName] = [404, null];
+ }
+ $this->itemsLeft = count($this->result);
+ }
+
+ /**
+ * Handles a specific property.
+ *
+ * This method checks whether the specified property was requested in this
+ * PROPFIND request, and if so, it will call the callback and use the
+ * return value for it's value.
+ *
+ * Example:
+ *
+ * $propFind->handle('{DAV:}displayname', function() {
+ * return 'hello';
+ * });
+ *
+ * Note that handle will only work the first time. If null is returned, the
+ * value is ignored.
+ *
+ * It's also possible to not pass a callback, but immediately pass a value
+ *
+ * @param string $propertyName
+ * @param mixed $valueOrCallBack
+ */
+ public function handle($propertyName, $valueOrCallBack)
+ {
+ if ($this->itemsLeft && isset($this->result[$propertyName]) && 404 === $this->result[$propertyName][0]) {
+ if (is_callable($valueOrCallBack)) {
+ $value = $valueOrCallBack();
+ } else {
+ $value = $valueOrCallBack;
+ }
+ if (!is_null($value)) {
+ --$this->itemsLeft;
+ $this->result[$propertyName] = [200, $value];
+ }
+ }
+ }
+
+ /**
+ * Sets the value of the property.
+ *
+ * If status is not supplied, the status will default to 200 for non-null
+ * properties, and 404 for null properties.
+ *
+ * @param string $propertyName
+ * @param mixed $value
+ * @param int $status
+ */
+ public function set($propertyName, $value, $status = null)
+ {
+ if (is_null($status)) {
+ $status = is_null($value) ? 404 : 200;
+ }
+ // If this is an ALLPROPS request and the property is
+ // unknown, add it to the result; else ignore it:
+ if (!isset($this->result[$propertyName])) {
+ if (self::ALLPROPS === $this->requestType) {
+ $this->result[$propertyName] = [$status, $value];
+ }
+
+ return;
+ }
+ if (404 !== $status && 404 === $this->result[$propertyName][0]) {
+ --$this->itemsLeft;
+ } elseif (404 === $status && 404 !== $this->result[$propertyName][0]) {
+ ++$this->itemsLeft;
+ }
+ $this->result[$propertyName] = [$status, $value];
+ }
+
+ /**
+ * Returns the current value for a property.
+ *
+ * @param string $propertyName
+ *
+ * @return mixed
+ */
+ public function get($propertyName)
+ {
+ return isset($this->result[$propertyName]) ? $this->result[$propertyName][1] : null;
+ }
+
+ /**
+ * Returns the current status code for a property name.
+ *
+ * If the property does not appear in the list of requested properties,
+ * null will be returned.
+ *
+ * @param string $propertyName
+ *
+ * @return int|null
+ */
+ public function getStatus($propertyName)
+ {
+ return isset($this->result[$propertyName]) ? $this->result[$propertyName][0] : null;
+ }
+
+ /**
+ * Updates the path for this PROPFIND.
+ *
+ * @param string $path
+ */
+ public function setPath($path)
+ {
+ $this->path = $path;
+ }
+
+ /**
+ * Returns the path this PROPFIND request is for.
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * Returns the depth of this propfind request.
+ *
+ * @return int
+ */
+ public function getDepth()
+ {
+ return $this->depth;
+ }
+
+ /**
+ * Updates the depth of this propfind request.
+ *
+ * @param int $depth
+ */
+ public function setDepth($depth)
+ {
+ $this->depth = $depth;
+ }
+
+ /**
+ * Returns all propertynames that have a 404 status, and thus don't have a
+ * value yet.
+ *
+ * @return array
+ */
+ public function get404Properties()
+ {
+ if (0 === $this->itemsLeft) {
+ return [];
+ }
+ $result = [];
+ foreach ($this->result as $propertyName => $stuff) {
+ if (404 === $stuff[0]) {
+ $result[] = $propertyName;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns the full list of requested properties.
+ *
+ * This returns just their names, not a status or value.
+ *
+ * @return array
+ */
+ public function getRequestedProperties()
+ {
+ return $this->properties;
+ }
+
+ /**
+ * Returns true if this was an '{DAV:}allprops' request.
+ *
+ * @return bool
+ */
+ public function isAllProps()
+ {
+ return self::ALLPROPS === $this->requestType;
+ }
+
+ /**
+ * Returns a result array that's often used in multistatus responses.
+ *
+ * The array uses status codes as keys, and property names and value pairs
+ * as the value of the top array.. such as :
+ *
+ * [
+ * 200 => [ '{DAV:}displayname' => 'foo' ],
+ * ]
+ *
+ * @return array
+ */
+ public function getResultForMultiStatus()
+ {
+ $r = [
+ 200 => [],
+ 404 => [],
+ ];
+ foreach ($this->result as $propertyName => $info) {
+ if (!isset($r[$info[0]])) {
+ $r[$info[0]] = [$propertyName => $info[1]];
+ } else {
+ $r[$info[0]][$propertyName] = $info[1];
+ }
+ }
+ // Removing the 404's for multi-status requests.
+ if (self::ALLPROPS === $this->requestType) {
+ unset($r[404]);
+ }
+
+ return $r;
+ }
+
+ /**
+ * The path that we're fetching properties for.
+ *
+ * @var string
+ */
+ protected $path;
+
+ /**
+ * The Depth of the request.
+ *
+ * 0 means only the current item. 1 means the current item + its children.
+ * It can also be DEPTH_INFINITY if this is enabled in the server.
+ *
+ * @var int
+ */
+ protected $depth = 0;
+
+ /**
+ * The type of request. See the TYPE constants.
+ */
+ protected $requestType;
+
+ /**
+ * A list of requested properties.
+ *
+ * @var array
+ */
+ protected $properties = [];
+
+ /**
+ * The result of the operation.
+ *
+ * The keys in this array are property names.
+ * The values are an array with two elements: the http status code and then
+ * optionally a value.
+ *
+ * Example:
+ *
+ * [
+ * "{DAV:}owner" : [404],
+ * "{DAV:}displayname" : [200, "Admin"]
+ * ]
+ *
+ * @var array
+ */
+ protected $result = [];
+
+ /**
+ * This is used as an internal counter for the number of properties that do
+ * not yet have a value.
+ *
+ * @var int
+ */
+ protected $itemsLeft;
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/PropPatch.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/PropPatch.php
new file mode 100644
index 000000000..092909dea
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/PropPatch.php
@@ -0,0 +1,337 @@
+mutations = $mutations;
+ }
+
+ /**
+ * Call this function if you wish to handle updating certain properties.
+ * For instance, your class may be responsible for handling updates for the
+ * {DAV:}displayname property.
+ *
+ * In that case, call this method with the first argument
+ * "{DAV:}displayname" and a second argument that's a method that does the
+ * actual updating.
+ *
+ * It's possible to specify more than one property as an array.
+ *
+ * The callback must return a boolean or an it. If the result is true, the
+ * operation was considered successful. If it's false, it's consided
+ * failed.
+ *
+ * If the result is an integer, we'll use that integer as the http status
+ * code associated with the operation.
+ *
+ * @param string|string[] $properties
+ */
+ public function handle($properties, callable $callback)
+ {
+ $usedProperties = [];
+ foreach ((array) $properties as $propertyName) {
+ if (array_key_exists($propertyName, $this->mutations) && !isset($this->result[$propertyName])) {
+ $usedProperties[] = $propertyName;
+ // HTTP Accepted
+ $this->result[$propertyName] = 202;
+ }
+ }
+
+ // Only registering if there's any unhandled properties.
+ if (!$usedProperties) {
+ return;
+ }
+ $this->propertyUpdateCallbacks[] = [
+ // If the original argument to this method was a string, we need
+ // to also make sure that it stays that way, so the commit function
+ // knows how to format the arguments to the callback.
+ is_string($properties) ? $properties : $usedProperties,
+ $callback,
+ ];
+ }
+
+ /**
+ * Call this function if you wish to handle _all_ properties that haven't
+ * been handled by anything else yet. Note that you effectively claim with
+ * this that you promise to process _all_ properties that are coming in.
+ */
+ public function handleRemaining(callable $callback)
+ {
+ $properties = $this->getRemainingMutations();
+ if (!$properties) {
+ // Nothing to do, don't register callback
+ return;
+ }
+
+ foreach ($properties as $propertyName) {
+ // HTTP Accepted
+ $this->result[$propertyName] = 202;
+
+ $this->propertyUpdateCallbacks[] = [
+ $properties,
+ $callback,
+ ];
+ }
+ }
+
+ /**
+ * Sets the result code for one or more properties.
+ *
+ * @param string|string[] $properties
+ * @param int $resultCode
+ */
+ public function setResultCode($properties, $resultCode)
+ {
+ foreach ((array) $properties as $propertyName) {
+ $this->result[$propertyName] = $resultCode;
+ }
+
+ if ($resultCode >= 400) {
+ $this->failed = true;
+ }
+ }
+
+ /**
+ * Sets the result code for all properties that did not have a result yet.
+ *
+ * @param int $resultCode
+ */
+ public function setRemainingResultCode($resultCode)
+ {
+ $this->setResultCode(
+ $this->getRemainingMutations(),
+ $resultCode
+ );
+ }
+
+ /**
+ * Returns the list of properties that don't have a result code yet.
+ *
+ * This method returns a list of property names, but not its values.
+ *
+ * @return string[]
+ */
+ public function getRemainingMutations()
+ {
+ $remaining = [];
+ foreach ($this->mutations as $propertyName => $propValue) {
+ if (!isset($this->result[$propertyName])) {
+ $remaining[] = $propertyName;
+ }
+ }
+
+ return $remaining;
+ }
+
+ /**
+ * Returns the list of properties that don't have a result code yet.
+ *
+ * This method returns list of properties and their values.
+ *
+ * @return array
+ */
+ public function getRemainingValues()
+ {
+ $remaining = [];
+ foreach ($this->mutations as $propertyName => $propValue) {
+ if (!isset($this->result[$propertyName])) {
+ $remaining[$propertyName] = $propValue;
+ }
+ }
+
+ return $remaining;
+ }
+
+ /**
+ * Performs the actual update, and calls all callbacks.
+ *
+ * This method returns true or false depending on if the operation was
+ * successful.
+ *
+ * @return bool
+ */
+ public function commit()
+ {
+ // First we validate if every property has a handler
+ foreach ($this->mutations as $propertyName => $value) {
+ if (!isset($this->result[$propertyName])) {
+ $this->failed = true;
+ $this->result[$propertyName] = 403;
+ }
+ }
+
+ foreach ($this->propertyUpdateCallbacks as $callbackInfo) {
+ if ($this->failed) {
+ break;
+ }
+ if (is_string($callbackInfo[0])) {
+ $this->doCallbackSingleProp($callbackInfo[0], $callbackInfo[1]);
+ } else {
+ $this->doCallbackMultiProp($callbackInfo[0], $callbackInfo[1]);
+ }
+ }
+
+ /*
+ * If anywhere in this operation updating a property failed, we must
+ * update all other properties accordingly.
+ */
+ if ($this->failed) {
+ foreach ($this->result as $propertyName => $status) {
+ if (202 === $status) {
+ // Failed dependency
+ $this->result[$propertyName] = 424;
+ }
+ }
+ }
+
+ return !$this->failed;
+ }
+
+ /**
+ * Executes a property callback with the single-property syntax.
+ *
+ * @param string $propertyName
+ */
+ private function doCallBackSingleProp($propertyName, callable $callback)
+ {
+ $result = $callback($this->mutations[$propertyName]);
+ if (is_bool($result)) {
+ if ($result) {
+ if (is_null($this->mutations[$propertyName])) {
+ // Delete
+ $result = 204;
+ } else {
+ // Update
+ $result = 200;
+ }
+ } else {
+ // Fail
+ $result = 403;
+ }
+ }
+ if (!is_int($result)) {
+ throw new UnexpectedValueException('A callback sent to handle() did not return an int or a bool');
+ }
+ $this->result[$propertyName] = $result;
+ if ($result >= 400) {
+ $this->failed = true;
+ }
+ }
+
+ /**
+ * Executes a property callback with the multi-property syntax.
+ */
+ private function doCallBackMultiProp(array $propertyList, callable $callback)
+ {
+ $argument = [];
+ foreach ($propertyList as $propertyName) {
+ $argument[$propertyName] = $this->mutations[$propertyName];
+ }
+
+ $result = $callback($argument);
+
+ if (is_array($result)) {
+ foreach ($propertyList as $propertyName) {
+ if (!isset($result[$propertyName])) {
+ $resultCode = 500;
+ } else {
+ $resultCode = $result[$propertyName];
+ }
+ if ($resultCode >= 400) {
+ $this->failed = true;
+ }
+ $this->result[$propertyName] = $resultCode;
+ }
+ } elseif (true === $result) {
+ // Success
+ foreach ($argument as $propertyName => $propertyValue) {
+ $this->result[$propertyName] = is_null($propertyValue) ? 204 : 200;
+ }
+ } elseif (false === $result) {
+ // Fail :(
+ $this->failed = true;
+ foreach ($propertyList as $propertyName) {
+ $this->result[$propertyName] = 403;
+ }
+ } else {
+ throw new UnexpectedValueException('A callback sent to handle() did not return an array or a bool');
+ }
+ }
+
+ /**
+ * Returns the result of the operation.
+ *
+ * @return array
+ */
+ public function getResult()
+ {
+ return $this->result;
+ }
+
+ /**
+ * Returns the full list of mutations.
+ *
+ * @return array
+ */
+ public function getMutations()
+ {
+ return $this->mutations;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/BackendInterface.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/BackendInterface.php
new file mode 100644
index 000000000..64a8825cb
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/BackendInterface.php
@@ -0,0 +1,75 @@
+isAllProps().
+ *
+ * @param string $path
+ */
+ public function propFind($path, PropFind $propFind);
+
+ /**
+ * Updates properties for a path.
+ *
+ * This method received a PropPatch object, which contains all the
+ * information about the update.
+ *
+ * Usually you would want to call 'handleRemaining' on this object, to get;
+ * a list of all properties that need to be stored.
+ *
+ * @param string $path
+ */
+ public function propPatch($path, PropPatch $propPatch);
+
+ /**
+ * This method is called after a node is deleted.
+ *
+ * This allows a backend to clean up all associated properties.
+ *
+ * The delete method will get called once for the deletion of an entire
+ * tree.
+ *
+ * @param string $path
+ */
+ public function delete($path);
+
+ /**
+ * This method is called after a successful MOVE.
+ *
+ * This should be used to migrate all properties from one path to another.
+ * Note that entire collections may be moved, so ensure that all properties
+ * for children are also moved along.
+ *
+ * @param string $source
+ * @param string $destination
+ */
+ public function move($source, $destination);
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php
new file mode 100644
index 000000000..896033192
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php
@@ -0,0 +1,224 @@
+pdo = $pdo;
+ }
+
+ /**
+ * Fetches properties for a path.
+ *
+ * This method received a PropFind object, which contains all the
+ * information about the properties that need to be fetched.
+ *
+ * Usually you would just want to call 'get404Properties' on this object,
+ * as this will give you the _exact_ list of properties that need to be
+ * fetched, and haven't yet.
+ *
+ * However, you can also support the 'allprops' property here. In that
+ * case, you should check for $propFind->isAllProps().
+ *
+ * @param string $path
+ */
+ public function propFind($path, PropFind $propFind)
+ {
+ if (!$propFind->isAllProps() && 0 === count($propFind->get404Properties())) {
+ return;
+ }
+
+ $query = 'SELECT name, value, valuetype FROM '.$this->tableName.' WHERE path = ?';
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute([$path]);
+
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ if ('resource' === gettype($row['value'])) {
+ $row['value'] = stream_get_contents($row['value']);
+ }
+ switch ($row['valuetype']) {
+ case null:
+ case self::VT_STRING:
+ $propFind->set($row['name'], $row['value']);
+ break;
+ case self::VT_XML:
+ $propFind->set($row['name'], new Complex($row['value']));
+ break;
+ case self::VT_OBJECT:
+ $propFind->set($row['name'], unserialize($row['value']));
+ break;
+ }
+ }
+ }
+
+ /**
+ * Updates properties for a path.
+ *
+ * This method received a PropPatch object, which contains all the
+ * information about the update.
+ *
+ * Usually you would want to call 'handleRemaining' on this object, to get;
+ * a list of all properties that need to be stored.
+ *
+ * @param string $path
+ */
+ public function propPatch($path, PropPatch $propPatch)
+ {
+ $propPatch->handleRemaining(function ($properties) use ($path) {
+ if ('pgsql' === $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
+ $updateSql = <<tableName} (path, name, valuetype, value)
+VALUES (:path, :name, :valuetype, :value)
+ON CONFLICT (path, name)
+DO UPDATE SET valuetype = :valuetype, value = :value
+SQL;
+ } else {
+ $updateSql = <<tableName} (path, name, valuetype, value)
+VALUES (:path, :name, :valuetype, :value)
+SQL;
+ }
+
+ $updateStmt = $this->pdo->prepare($updateSql);
+ $deleteStmt = $this->pdo->prepare('DELETE FROM '.$this->tableName.' WHERE path = ? AND name = ?');
+
+ foreach ($properties as $name => $value) {
+ if (!is_null($value)) {
+ if (is_scalar($value)) {
+ $valueType = self::VT_STRING;
+ } elseif ($value instanceof Complex) {
+ $valueType = self::VT_XML;
+ $value = $value->getXml();
+ } else {
+ $valueType = self::VT_OBJECT;
+ $value = serialize($value);
+ }
+
+ $updateStmt->bindParam('path', $path, \PDO::PARAM_STR);
+ $updateStmt->bindParam('name', $name, \PDO::PARAM_STR);
+ $updateStmt->bindParam('valuetype', $valueType, \PDO::PARAM_INT);
+ $updateStmt->bindParam('value', $value, \PDO::PARAM_LOB);
+
+ $updateStmt->execute();
+ } else {
+ $deleteStmt->execute([$path, $name]);
+ }
+ }
+
+ return true;
+ });
+ }
+
+ /**
+ * This method is called after a node is deleted.
+ *
+ * This allows a backend to clean up all associated properties.
+ *
+ * The delete method will get called once for the deletion of an entire
+ * tree.
+ *
+ * @param string $path
+ */
+ public function delete($path)
+ {
+ $stmt = $this->pdo->prepare('DELETE FROM '.$this->tableName." WHERE path = ? OR path LIKE ? ESCAPE '='");
+ $childPath = strtr(
+ $path,
+ [
+ '=' => '==',
+ '%' => '=%',
+ '_' => '=_',
+ ]
+ ).'/%';
+
+ $stmt->execute([$path, $childPath]);
+ }
+
+ /**
+ * This method is called after a successful MOVE.
+ *
+ * This should be used to migrate all properties from one path to another.
+ * Note that entire collections may be moved, so ensure that all properties
+ * for children are also moved along.
+ *
+ * @param string $source
+ * @param string $destination
+ */
+ public function move($source, $destination)
+ {
+ // I don't know a way to write this all in a single sql query that's
+ // also compatible across db engines, so we're letting PHP do all the
+ // updates. Much slower, but it should still be pretty fast in most
+ // cases.
+ $select = $this->pdo->prepare('SELECT id, path FROM '.$this->tableName.' WHERE path = ? OR path LIKE ?');
+ $select->execute([$source, $source.'/%']);
+
+ $update = $this->pdo->prepare('UPDATE '.$this->tableName.' SET path = ? WHERE id = ?');
+ while ($row = $select->fetch(\PDO::FETCH_ASSOC)) {
+ // Sanity check. SQL may select too many records, such as records
+ // with different cases.
+ if ($row['path'] !== $source && 0 !== strpos($row['path'], $source.'/')) {
+ continue;
+ }
+
+ $trailingPart = substr($row['path'], strlen($source) + 1);
+ $newPath = $destination;
+ if ($trailingPart) {
+ $newPath .= '/'.$trailingPart;
+ }
+ $update->execute([$newPath, $row['id']]);
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/PropertyStorage/Plugin.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/PropertyStorage/Plugin.php
new file mode 100644
index 000000000..da47ec9a9
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/PropertyStorage/Plugin.php
@@ -0,0 +1,176 @@
+backend = $backend;
+ }
+
+ /**
+ * 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->on('propFind', [$this, 'propFind'], 130);
+ $server->on('propPatch', [$this, 'propPatch'], 300);
+ $server->on('afterMove', [$this, 'afterMove']);
+ $server->on('afterUnbind', [$this, 'afterUnbind']);
+ }
+
+ /**
+ * Called during PROPFIND operations.
+ *
+ * If there's any requested properties that don't have a value yet, this
+ * plugin will look in the property storage backend to find them.
+ */
+ public function propFind(PropFind $propFind, INode $node)
+ {
+ $path = $propFind->getPath();
+ $pathFilter = $this->pathFilter;
+ if ($pathFilter && !$pathFilter($path)) {
+ return;
+ }
+ $this->backend->propFind($propFind->getPath(), $propFind);
+ }
+
+ /**
+ * Called during PROPPATCH operations.
+ *
+ * If there's any updated properties that haven't been stored, the
+ * propertystorage backend can handle it.
+ *
+ * @param string $path
+ */
+ public function propPatch($path, PropPatch $propPatch)
+ {
+ $pathFilter = $this->pathFilter;
+ if ($pathFilter && !$pathFilter($path)) {
+ return;
+ }
+ $this->backend->propPatch($path, $propPatch);
+ }
+
+ /**
+ * Called after a node is deleted.
+ *
+ * This allows the backend to clean up any properties still in the
+ * database.
+ *
+ * @param string $path
+ */
+ public function afterUnbind($path)
+ {
+ $pathFilter = $this->pathFilter;
+ if ($pathFilter && !$pathFilter($path)) {
+ return;
+ }
+ $this->backend->delete($path);
+ }
+
+ /**
+ * Called after a node is moved.
+ *
+ * This allows the backend to move all the associated properties.
+ *
+ * @param string $source
+ * @param string $destination
+ */
+ public function afterMove($source, $destination)
+ {
+ $pathFilter = $this->pathFilter;
+ if ($pathFilter && !$pathFilter($source)) {
+ return;
+ }
+ // If the destination is filtered, afterUnbind will handle cleaning up
+ // the properties.
+ if ($pathFilter && !$pathFilter($destination)) {
+ return;
+ }
+
+ $this->backend->move($source, $destination);
+ }
+
+ /**
+ * 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 'property-storage';
+ }
+
+ /**
+ * 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 any arbitrary WebDAV property to be set on any resource.',
+ 'link' => 'http://sabre.io/dav/property-storage/',
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Server.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Server.php
new file mode 100644
index 000000000..3c237500a
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Server.php
@@ -0,0 +1,1672 @@
+ '{DAV:}collection',
+ ];
+
+ /**
+ * This property allows the usage of Depth: infinity on PROPFIND requests.
+ *
+ * By default Depth: infinity is treated as Depth: 1. Allowing Depth:
+ * infinity is potentially risky, as it allows a single client to do a full
+ * index of the webdav server, which is an easy DoS attack vector.
+ *
+ * Only turn this on if you know what you're doing.
+ *
+ * @var bool
+ */
+ public $enablePropfindDepthInfinity = false;
+
+ /**
+ * Reference to the XML utility object.
+ *
+ * @var Xml\Service
+ */
+ public $xml;
+
+ /**
+ * If this setting is turned off, SabreDAV's version number will be hidden
+ * from various places.
+ *
+ * Some people feel this is a good security measure.
+ *
+ * @var bool
+ */
+ public static $exposeVersion = true;
+
+ /**
+ * If this setting is turned on, any multi status response on any PROPFIND will be streamed to the output buffer.
+ * This will be beneficial for large result sets which will no longer consume a large amount of memory as well as
+ * send back data to the client earlier.
+ *
+ * @var bool
+ */
+ public static $streamMultiStatus = false;
+
+ /**
+ * Sets up the server.
+ *
+ * If a Sabre\DAV\Tree object is passed as an argument, it will
+ * use it as the directory tree. If a Sabre\DAV\INode is passed, it
+ * will create a Sabre\DAV\Tree and use the node as the root.
+ *
+ * If nothing is passed, a Sabre\DAV\SimpleCollection is created in
+ * a Sabre\DAV\Tree.
+ *
+ * If an array is passed, we automatically create a root node, and use
+ * the nodes in the array as top-level children.
+ *
+ * @param Tree|INode|array|null $treeOrNode The tree object
+ */
+ public function __construct($treeOrNode = null)
+ {
+ if ($treeOrNode instanceof Tree) {
+ $this->tree = $treeOrNode;
+ } elseif ($treeOrNode instanceof INode) {
+ $this->tree = new Tree($treeOrNode);
+ } elseif (is_array($treeOrNode)) {
+ $root = new SimpleCollection('root', $treeOrNode);
+ $this->tree = new Tree($root);
+ } elseif (is_null($treeOrNode)) {
+ $root = new SimpleCollection('root');
+ $this->tree = new Tree($root);
+ } else {
+ throw new Exception('Invalid argument passed to constructor. Argument must either be an instance of Sabre\\DAV\\Tree, Sabre\\DAV\\INode, an array or null');
+ }
+
+ $this->xml = new Xml\Service();
+ $this->sapi = new HTTP\Sapi();
+ $this->httpResponse = new HTTP\Response();
+ $this->httpRequest = $this->sapi->getRequest();
+ $this->addPlugin(new CorePlugin());
+ }
+
+ /**
+ * Starts the DAV Server.
+ */
+ public function start()
+ {
+ try {
+ // If nginx (pre-1.2) is used as a proxy server, and SabreDAV as an
+ // origin, we must make sure we send back HTTP/1.0 if this was
+ // requested.
+ // This is mainly because nginx doesn't support Chunked Transfer
+ // Encoding, and this forces the webserver SabreDAV is running on,
+ // to buffer entire responses to calculate Content-Length.
+ $this->httpResponse->setHTTPVersion($this->httpRequest->getHTTPVersion());
+
+ // Setting the base url
+ $this->httpRequest->setBaseUrl($this->getBaseUri());
+ $this->invokeMethod($this->httpRequest, $this->httpResponse);
+ } catch (\Throwable $e) {
+ try {
+ $this->emit('exception', [$e]);
+ } catch (\Exception $ignore) {
+ }
+ $DOM = new \DOMDocument('1.0', 'utf-8');
+ $DOM->formatOutput = true;
+
+ $error = $DOM->createElementNS('DAV:', 'd:error');
+ $error->setAttribute('xmlns:s', self::NS_SABREDAV);
+ $DOM->appendChild($error);
+
+ $h = function ($v) {
+ return htmlspecialchars((string) $v, ENT_NOQUOTES, 'UTF-8');
+ };
+
+ if (self::$exposeVersion) {
+ $error->appendChild($DOM->createElement('s:sabredav-version', $h(Version::VERSION)));
+ }
+
+ $error->appendChild($DOM->createElement('s:exception', $h(get_class($e))));
+ $error->appendChild($DOM->createElement('s:message', $h($e->getMessage())));
+ if ($this->debugExceptions) {
+ $error->appendChild($DOM->createElement('s:file', $h($e->getFile())));
+ $error->appendChild($DOM->createElement('s:line', $h($e->getLine())));
+ $error->appendChild($DOM->createElement('s:code', $h($e->getCode())));
+ $error->appendChild($DOM->createElement('s:stacktrace', $h($e->getTraceAsString())));
+ }
+
+ if ($this->debugExceptions) {
+ $previous = $e;
+ while ($previous = $previous->getPrevious()) {
+ $xPrevious = $DOM->createElement('s:previous-exception');
+ $xPrevious->appendChild($DOM->createElement('s:exception', $h(get_class($previous))));
+ $xPrevious->appendChild($DOM->createElement('s:message', $h($previous->getMessage())));
+ $xPrevious->appendChild($DOM->createElement('s:file', $h($previous->getFile())));
+ $xPrevious->appendChild($DOM->createElement('s:line', $h($previous->getLine())));
+ $xPrevious->appendChild($DOM->createElement('s:code', $h($previous->getCode())));
+ $xPrevious->appendChild($DOM->createElement('s:stacktrace', $h($previous->getTraceAsString())));
+ $error->appendChild($xPrevious);
+ }
+ }
+
+ if ($e instanceof Exception) {
+ $httpCode = $e->getHTTPCode();
+ $e->serialize($this, $error);
+ $headers = $e->getHTTPHeaders($this);
+ } else {
+ $httpCode = 500;
+ $headers = [];
+ }
+ $headers['Content-Type'] = 'application/xml; charset=utf-8';
+
+ $this->httpResponse->setStatus($httpCode);
+ $this->httpResponse->setHeaders($headers);
+ $this->httpResponse->setBody($DOM->saveXML());
+ $this->sapi->sendResponse($this->httpResponse);
+ }
+ }
+
+ /**
+ * Alias of start().
+ *
+ * @deprecated
+ */
+ public function exec()
+ {
+ $this->start();
+ }
+
+ /**
+ * Sets the base server uri.
+ *
+ * @param string $uri
+ */
+ public function setBaseUri($uri)
+ {
+ // If the baseUri does not end with a slash, we must add it
+ if ('/' !== $uri[strlen($uri) - 1]) {
+ $uri .= '/';
+ }
+
+ $this->baseUri = $uri;
+ }
+
+ /**
+ * Returns the base responding uri.
+ *
+ * @return string
+ */
+ public function getBaseUri()
+ {
+ if (is_null($this->baseUri)) {
+ $this->baseUri = $this->guessBaseUri();
+ }
+
+ return $this->baseUri;
+ }
+
+ /**
+ * This method attempts to detect the base uri.
+ * Only the PATH_INFO variable is considered.
+ *
+ * If this variable is not set, the root (/) is assumed.
+ *
+ * @return string
+ */
+ public function guessBaseUri()
+ {
+ $pathInfo = $this->httpRequest->getRawServerValue('PATH_INFO');
+ $uri = $this->httpRequest->getRawServerValue('REQUEST_URI');
+
+ // If PATH_INFO is found, we can assume it's accurate.
+ if (!empty($pathInfo)) {
+ // We need to make sure we ignore the QUERY_STRING part
+ if ($pos = strpos($uri, '?')) {
+ $uri = substr($uri, 0, $pos);
+ }
+
+ // PATH_INFO is only set for urls, such as: /example.php/path
+ // in that case PATH_INFO contains '/path'.
+ // Note that REQUEST_URI is percent encoded, while PATH_INFO is
+ // not, Therefore they are only comparable if we first decode
+ // REQUEST_INFO as well.
+ $decodedUri = HTTP\decodePath($uri);
+
+ // A simple sanity check:
+ if (substr($decodedUri, strlen($decodedUri) - strlen($pathInfo)) === $pathInfo) {
+ $baseUri = substr($decodedUri, 0, strlen($decodedUri) - strlen($pathInfo));
+
+ return rtrim($baseUri, '/').'/';
+ }
+
+ throw new Exception('The REQUEST_URI ('.$uri.') did not end with the contents of PATH_INFO ('.$pathInfo.'). This server might be misconfigured.');
+ }
+
+ // The last fallback is that we're just going to assume the server root.
+ return '/';
+ }
+
+ /**
+ * Adds a plugin to the server.
+ *
+ * For more information, console the documentation of Sabre\DAV\ServerPlugin
+ */
+ public function addPlugin(ServerPlugin $plugin)
+ {
+ $this->plugins[$plugin->getPluginName()] = $plugin;
+ $plugin->initialize($this);
+ }
+
+ /**
+ * Returns an initialized plugin by it's name.
+ *
+ * This function returns null if the plugin was not found.
+ *
+ * @param string $name
+ *
+ * @return ServerPlugin
+ */
+ public function getPlugin($name)
+ {
+ if (isset($this->plugins[$name])) {
+ return $this->plugins[$name];
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns all plugins.
+ *
+ * @return array
+ */
+ public function getPlugins()
+ {
+ return $this->plugins;
+ }
+
+ /**
+ * Returns the PSR-3 logger object.
+ *
+ * @return LoggerInterface
+ */
+ public function getLogger()
+ {
+ if (!$this->logger) {
+ $this->logger = new NullLogger();
+ }
+
+ return $this->logger;
+ }
+
+ /**
+ * Handles a http request, and execute a method based on its name.
+ *
+ * @param bool $sendResponse whether to send the HTTP response to the DAV client
+ */
+ public function invokeMethod(RequestInterface $request, ResponseInterface $response, $sendResponse = true)
+ {
+ $method = $request->getMethod();
+
+ if (!$this->emit('beforeMethod:'.$method, [$request, $response])) {
+ return;
+ }
+
+ if (self::$exposeVersion) {
+ $response->setHeader('X-Sabre-Version', Version::VERSION);
+ }
+
+ $this->transactionType = strtolower($method);
+
+ if (!$this->checkPreconditions($request, $response)) {
+ $this->sapi->sendResponse($response);
+
+ return;
+ }
+
+ if ($this->emit('method:'.$method, [$request, $response])) {
+ $exMessage = 'There was no plugin in the system that was willing to handle this '.$method.' method.';
+ if ('GET' === $method) {
+ $exMessage .= ' Enable the Browser plugin to get a better result here.';
+ }
+
+ // Unsupported method
+ throw new Exception\NotImplemented($exMessage);
+ }
+
+ if (!$this->emit('afterMethod:'.$method, [$request, $response])) {
+ return;
+ }
+
+ if (null === $response->getStatus()) {
+ throw new Exception('No subsystem set a valid HTTP status code. Something must have interrupted the request without providing further detail.');
+ }
+ if ($sendResponse) {
+ $this->sapi->sendResponse($response);
+ $this->emit('afterResponse', [$request, $response]);
+ }
+ }
+
+ // {{{ HTTP/WebDAV protocol helpers
+
+ /**
+ * Returns an array with all the supported HTTP methods for a specific uri.
+ *
+ * @param string $path
+ *
+ * @return array
+ */
+ public function getAllowedMethods($path)
+ {
+ $methods = [
+ 'OPTIONS',
+ 'GET',
+ 'HEAD',
+ 'DELETE',
+ 'PROPFIND',
+ 'PUT',
+ 'PROPPATCH',
+ 'COPY',
+ 'MOVE',
+ 'REPORT',
+ ];
+
+ // The MKCOL is only allowed on an unmapped uri
+ try {
+ $this->tree->getNodeForPath($path);
+ } catch (Exception\NotFound $e) {
+ $methods[] = 'MKCOL';
+ }
+
+ // We're also checking if any of the plugins register any new methods
+ foreach ($this->plugins as $plugin) {
+ $methods = array_merge($methods, $plugin->getHTTPMethods($path));
+ }
+ array_unique($methods);
+
+ return $methods;
+ }
+
+ /**
+ * Gets the uri for the request, keeping the base uri into consideration.
+ *
+ * @return string
+ */
+ public function getRequestUri()
+ {
+ return $this->calculateUri($this->httpRequest->getUrl());
+ }
+
+ /**
+ * Turns a URI such as the REQUEST_URI into a local path.
+ *
+ * This method:
+ * * strips off the base path
+ * * normalizes the path
+ * * uri-decodes the path
+ *
+ * @param string $uri
+ *
+ * @throws Exception\Forbidden A permission denied exception is thrown whenever there was an attempt to supply a uri outside of the base uri
+ *
+ * @return string
+ */
+ public function calculateUri($uri)
+ {
+ if ('' != $uri && '/' != $uri[0] && strpos($uri, '://')) {
+ $uri = parse_url($uri, PHP_URL_PATH);
+ }
+
+ $uri = Uri\normalize(preg_replace('|/+|', '/', $uri));
+ $baseUri = Uri\normalize($this->getBaseUri());
+
+ if (0 === strpos($uri, $baseUri)) {
+ return trim(HTTP\decodePath(substr($uri, strlen($baseUri))), '/');
+
+ // A special case, if the baseUri was accessed without a trailing
+ // slash, we'll accept it as well.
+ } elseif ($uri.'/' === $baseUri) {
+ return '';
+ } else {
+ throw new Exception\Forbidden('Requested uri ('.$uri.') is out of base uri ('.$this->getBaseUri().')');
+ }
+ }
+
+ /**
+ * Returns the HTTP depth header.
+ *
+ * This method returns the contents of the HTTP depth request header. If the depth header was 'infinity' it will return the Sabre\DAV\Server::DEPTH_INFINITY object
+ * It is possible to supply a default depth value, which is used when the depth header has invalid content, or is completely non-existent
+ *
+ * @param mixed $default
+ *
+ * @return int
+ */
+ public function getHTTPDepth($default = self::DEPTH_INFINITY)
+ {
+ // If its not set, we'll grab the default
+ $depth = $this->httpRequest->getHeader('Depth');
+
+ if (is_null($depth)) {
+ return $default;
+ }
+
+ if ('infinity' == $depth) {
+ return self::DEPTH_INFINITY;
+ }
+
+ // If its an unknown value. we'll grab the default
+ if (!ctype_digit($depth)) {
+ return $default;
+ }
+
+ return (int) $depth;
+ }
+
+ /**
+ * Returns the HTTP range header.
+ *
+ * This method returns null if there is no well-formed HTTP range request
+ * header or array($start, $end).
+ *
+ * The first number is the offset of the first byte in the range.
+ * The second number is the offset of the last byte in the range.
+ *
+ * If the second offset is null, it should be treated as the offset of the last byte of the entity
+ * If the first offset is null, the second offset should be used to retrieve the last x bytes of the entity
+ *
+ * @return int[]|null
+ */
+ public function getHTTPRange()
+ {
+ $range = $this->httpRequest->getHeader('range');
+ if (is_null($range)) {
+ return null;
+ }
+
+ // Matching "Range: bytes=1234-5678: both numbers are optional
+
+ if (!preg_match('/^bytes=([0-9]*)-([0-9]*)$/i', $range, $matches)) {
+ return null;
+ }
+
+ if ('' === $matches[1] && '' === $matches[2]) {
+ return null;
+ }
+
+ return [
+ '' !== $matches[1] ? (int) $matches[1] : null,
+ '' !== $matches[2] ? (int) $matches[2] : null,
+ ];
+ }
+
+ /**
+ * Returns the HTTP Prefer header information.
+ *
+ * The prefer header is defined in:
+ * http://tools.ietf.org/html/draft-snell-http-prefer-14
+ *
+ * This method will return an array with options.
+ *
+ * Currently, the following options may be returned:
+ * [
+ * 'return-asynch' => true,
+ * 'return-minimal' => true,
+ * 'return-representation' => true,
+ * 'wait' => 30,
+ * 'strict' => true,
+ * 'lenient' => true,
+ * ]
+ *
+ * This method also supports the Brief header, and will also return
+ * 'return-minimal' if the brief header was set to 't'.
+ *
+ * For the boolean options, false will be returned if the headers are not
+ * specified. For the integer options it will be 'null'.
+ *
+ * @return array
+ */
+ public function getHTTPPrefer()
+ {
+ $result = [
+ // can be true or false
+ 'respond-async' => false,
+ // Could be set to 'representation' or 'minimal'.
+ 'return' => null,
+ // Used as a timeout, is usually a number.
+ 'wait' => null,
+ // can be 'strict' or 'lenient'.
+ 'handling' => false,
+ ];
+
+ if ($prefer = $this->httpRequest->getHeader('Prefer')) {
+ $result = array_merge(
+ $result,
+ HTTP\parsePrefer($prefer)
+ );
+ } elseif ('t' == $this->httpRequest->getHeader('Brief')) {
+ $result['return'] = 'minimal';
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns information about Copy and Move requests.
+ *
+ * This function is created to help getting information about the source and the destination for the
+ * WebDAV MOVE and COPY HTTP request. It also validates a lot of information and throws proper exceptions
+ *
+ * The returned value is an array with the following keys:
+ * * destination - Destination path
+ * * destinationExists - Whether or not the destination is an existing url (and should therefore be overwritten)
+ *
+ * @throws Exception\BadRequest upon missing or broken request headers
+ * @throws Exception\UnsupportedMediaType when trying to copy into a
+ * non-collection
+ * @throws Exception\PreconditionFailed if overwrite is set to false, but
+ * the destination exists
+ * @throws Exception\Forbidden when source and destination paths are
+ * identical
+ * @throws Exception\Conflict when trying to copy a node into its own
+ * subtree
+ *
+ * @return array
+ */
+ public function getCopyAndMoveInfo(RequestInterface $request)
+ {
+ // Collecting the relevant HTTP headers
+ if (!$request->getHeader('Destination')) {
+ throw new Exception\BadRequest('The destination header was not supplied');
+ }
+ $destination = $this->calculateUri($request->getHeader('Destination'));
+ $overwrite = $request->getHeader('Overwrite');
+ if (!$overwrite) {
+ $overwrite = 'T';
+ }
+ if ('T' == strtoupper($overwrite)) {
+ $overwrite = true;
+ } elseif ('F' == strtoupper($overwrite)) {
+ $overwrite = false;
+ }
+ // We need to throw a bad request exception, if the header was invalid
+ else {
+ throw new Exception\BadRequest('The HTTP Overwrite header should be either T or F');
+ }
+ list($destinationDir) = Uri\split($destination);
+
+ try {
+ $destinationParent = $this->tree->getNodeForPath($destinationDir);
+ if (!($destinationParent instanceof ICollection)) {
+ throw new Exception\UnsupportedMediaType('The destination node is not a collection');
+ }
+ } catch (Exception\NotFound $e) {
+ // If the destination parent node is not found, we throw a 409
+ throw new Exception\Conflict('The destination node is not found');
+ }
+
+ try {
+ $destinationNode = $this->tree->getNodeForPath($destination);
+
+ // If this succeeded, it means the destination already exists
+ // we'll need to throw precondition failed in case overwrite is false
+ if (!$overwrite) {
+ throw new Exception\PreconditionFailed('The destination node already exists, and the overwrite header is set to false', 'Overwrite');
+ }
+ } catch (Exception\NotFound $e) {
+ // Destination didn't exist, we're all good
+ $destinationNode = false;
+ }
+
+ $requestPath = $request->getPath();
+ if ($destination === $requestPath) {
+ throw new Exception\Forbidden('Source and destination uri are identical.');
+ }
+ if (substr($destination, 0, strlen($requestPath) + 1) === $requestPath.'/') {
+ throw new Exception\Conflict('The destination may not be part of the same subtree as the source path.');
+ }
+
+ // These are the three relevant properties we need to return
+ return [
+ 'destination' => $destination,
+ 'destinationExists' => (bool) $destinationNode,
+ 'destinationNode' => $destinationNode,
+ ];
+ }
+
+ /**
+ * Returns a list of properties for a path.
+ *
+ * This is a simplified version getPropertiesForPath. If you aren't
+ * interested in status codes, but you just want to have a flat list of
+ * properties, use this method.
+ *
+ * Please note though that any problems related to retrieving properties,
+ * such as permission issues will just result in an empty array being
+ * returned.
+ *
+ * @param string $path
+ * @param array $propertyNames
+ *
+ * @return array
+ */
+ public function getProperties($path, $propertyNames)
+ {
+ $result = $this->getPropertiesForPath($path, $propertyNames, 0);
+ if (isset($result[0][200])) {
+ return $result[0][200];
+ } else {
+ return [];
+ }
+ }
+
+ /**
+ * A kid-friendly way to fetch properties for a node's children.
+ *
+ * The returned array will be indexed by the path of the of child node.
+ * Only properties that are actually found will be returned.
+ *
+ * The parent node will not be returned.
+ *
+ * @param string $path
+ * @param array $propertyNames
+ *
+ * @return array
+ */
+ public function getPropertiesForChildren($path, $propertyNames)
+ {
+ $result = [];
+ foreach ($this->getPropertiesForPath($path, $propertyNames, 1) as $k => $row) {
+ // Skipping the parent path
+ if (0 === $k) {
+ continue;
+ }
+
+ $result[$row['href']] = $row[200];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns a list of HTTP headers for a particular resource.
+ *
+ * The generated http headers are based on properties provided by the
+ * resource. The method basically provides a simple mapping between
+ * DAV property and HTTP header.
+ *
+ * The headers are intended to be used for HEAD and GET requests.
+ *
+ * @param string $path
+ *
+ * @return array
+ */
+ public function getHTTPHeaders($path)
+ {
+ $propertyMap = [
+ '{DAV:}getcontenttype' => 'Content-Type',
+ '{DAV:}getcontentlength' => 'Content-Length',
+ '{DAV:}getlastmodified' => 'Last-Modified',
+ '{DAV:}getetag' => 'ETag',
+ ];
+
+ $properties = $this->getProperties($path, array_keys($propertyMap));
+
+ $headers = [];
+ foreach ($propertyMap as $property => $header) {
+ if (!isset($properties[$property])) {
+ continue;
+ }
+
+ if (is_scalar($properties[$property])) {
+ $headers[$header] = $properties[$property];
+
+ // GetLastModified gets special cased
+ } elseif ($properties[$property] instanceof Xml\Property\GetLastModified) {
+ $headers[$header] = HTTP\toDate($properties[$property]->getTime());
+ }
+ }
+
+ return $headers;
+ }
+
+ /**
+ * Small helper to support PROPFIND with DEPTH_INFINITY.
+ *
+ * @param array $yieldFirst
+ *
+ * @return \Traversable
+ */
+ private function generatePathNodes(PropFind $propFind, array $yieldFirst = null)
+ {
+ if (null !== $yieldFirst) {
+ yield $yieldFirst;
+ }
+ $newDepth = $propFind->getDepth();
+ $path = $propFind->getPath();
+
+ if (self::DEPTH_INFINITY !== $newDepth) {
+ --$newDepth;
+ }
+
+ $propertyNames = $propFind->getRequestedProperties();
+ $propFindType = !empty($propertyNames) ? PropFind::NORMAL : PropFind::ALLPROPS;
+
+ foreach ($this->tree->getChildren($path) as $childNode) {
+ if ('' !== $path) {
+ $subPath = $path.'/'.$childNode->getName();
+ } else {
+ $subPath = $childNode->getName();
+ }
+ $subPropFind = new PropFind($subPath, $propertyNames, $newDepth, $propFindType);
+
+ yield [
+ $subPropFind,
+ $childNode,
+ ];
+
+ if ((self::DEPTH_INFINITY === $newDepth || $newDepth >= 1) && $childNode instanceof ICollection) {
+ foreach ($this->generatePathNodes($subPropFind) as $subItem) {
+ yield $subItem;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a list of properties for a given path.
+ *
+ * The path that should be supplied should have the baseUrl stripped out
+ * The list of properties should be supplied in Clark notation. If the list is empty
+ * 'allprops' is assumed.
+ *
+ * If a depth of 1 is requested child elements will also be returned.
+ *
+ * @param string $path
+ * @param array $propertyNames
+ * @param int $depth
+ *
+ * @return array
+ *
+ * @deprecated Use getPropertiesIteratorForPath() instead (as it's more memory efficient)
+ * @see getPropertiesIteratorForPath()
+ */
+ public function getPropertiesForPath($path, $propertyNames = [], $depth = 0)
+ {
+ return iterator_to_array($this->getPropertiesIteratorForPath($path, $propertyNames, $depth));
+ }
+
+ /**
+ * Returns a list of properties for a given path.
+ *
+ * The path that should be supplied should have the baseUrl stripped out
+ * The list of properties should be supplied in Clark notation. If the list is empty
+ * 'allprops' is assumed.
+ *
+ * If a depth of 1 is requested child elements will also be returned.
+ *
+ * @param string $path
+ * @param array $propertyNames
+ * @param int $depth
+ *
+ * @return \Iterator
+ */
+ public function getPropertiesIteratorForPath($path, $propertyNames = [], $depth = 0)
+ {
+ // The only two options for the depth of a propfind is 0 or 1 - as long as depth infinity is not enabled
+ if (!$this->enablePropfindDepthInfinity && 0 != $depth) {
+ $depth = 1;
+ }
+
+ $path = trim($path, '/');
+
+ $propFindType = $propertyNames ? PropFind::NORMAL : PropFind::ALLPROPS;
+ $propFind = new PropFind($path, (array) $propertyNames, $depth, $propFindType);
+
+ $parentNode = $this->tree->getNodeForPath($path);
+
+ $propFindRequests = [[
+ $propFind,
+ $parentNode,
+ ]];
+
+ if (($depth > 0 || self::DEPTH_INFINITY === $depth) && $parentNode instanceof ICollection) {
+ $propFindRequests = $this->generatePathNodes(clone $propFind, current($propFindRequests));
+ }
+
+ foreach ($propFindRequests as $propFindRequest) {
+ list($propFind, $node) = $propFindRequest;
+ $r = $this->getPropertiesByNode($propFind, $node);
+ if ($r) {
+ $result = $propFind->getResultForMultiStatus();
+ $result['href'] = $propFind->getPath();
+
+ // WebDAV recommends adding a slash to the path, if the path is
+ // a collection.
+ // Furthermore, iCal also demands this to be the case for
+ // principals. This is non-standard, but we support it.
+ $resourceType = $this->getResourceTypeForNode($node);
+ if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) {
+ $result['href'] .= '/';
+ }
+ yield $result;
+ }
+ }
+ }
+
+ /**
+ * Returns a list of properties for a list of paths.
+ *
+ * The path that should be supplied should have the baseUrl stripped out
+ * The list of properties should be supplied in Clark notation. If the list is empty
+ * 'allprops' is assumed.
+ *
+ * The result is returned as an array, with paths for it's keys.
+ * The result may be returned out of order.
+ *
+ * @return array
+ */
+ public function getPropertiesForMultiplePaths(array $paths, array $propertyNames = [])
+ {
+ $result = [
+ ];
+
+ $nodes = $this->tree->getMultipleNodes($paths);
+
+ foreach ($nodes as $path => $node) {
+ $propFind = new PropFind($path, $propertyNames);
+ $r = $this->getPropertiesByNode($propFind, $node);
+ if ($r) {
+ $result[$path] = $propFind->getResultForMultiStatus();
+ $result[$path]['href'] = $path;
+
+ $resourceType = $this->getResourceTypeForNode($node);
+ if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) {
+ $result[$path]['href'] .= '/';
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Determines all properties for a node.
+ *
+ * This method tries to grab all properties for a node. This method is used
+ * internally getPropertiesForPath and a few others.
+ *
+ * It could be useful to call this, if you already have an instance of your
+ * target node and simply want to run through the system to get a correct
+ * list of properties.
+ *
+ * @return bool
+ */
+ public function getPropertiesByNode(PropFind $propFind, INode $node)
+ {
+ return $this->emit('propFind', [$propFind, $node]);
+ }
+
+ /**
+ * This method is invoked by sub-systems creating a new file.
+ *
+ * Currently this is done by HTTP PUT and HTTP LOCK (in the Locks_Plugin).
+ * It was important to get this done through a centralized function,
+ * allowing plugins to intercept this using the beforeCreateFile event.
+ *
+ * This method will return true if the file was actually created
+ *
+ * @param string $uri
+ * @param resource $data
+ * @param string $etag
+ *
+ * @return bool
+ */
+ public function createFile($uri, $data, &$etag = null)
+ {
+ list($dir, $name) = Uri\split($uri);
+
+ if (!$this->emit('beforeBind', [$uri])) {
+ return false;
+ }
+
+ $parent = $this->tree->getNodeForPath($dir);
+ if (!$parent instanceof ICollection) {
+ throw new Exception\Conflict('Files can only be created as children of collections');
+ }
+
+ // It is possible for an event handler to modify the content of the
+ // body, before it gets written. If this is the case, $modified
+ // should be set to true.
+ //
+ // If $modified is true, we must not send back an ETag.
+ $modified = false;
+ if (!$this->emit('beforeCreateFile', [$uri, &$data, $parent, &$modified])) {
+ return false;
+ }
+
+ $etag = $parent->createFile($name, $data);
+
+ if ($modified) {
+ $etag = null;
+ }
+
+ $this->tree->markDirty($dir.'/'.$name);
+
+ $this->emit('afterBind', [$uri]);
+ $this->emit('afterCreateFile', [$uri, $parent]);
+
+ return true;
+ }
+
+ /**
+ * This method is invoked by sub-systems updating a file.
+ *
+ * This method will return true if the file was actually updated
+ *
+ * @param string $uri
+ * @param resource $data
+ * @param string $etag
+ *
+ * @return bool
+ */
+ public function updateFile($uri, $data, &$etag = null)
+ {
+ $node = $this->tree->getNodeForPath($uri);
+
+ // It is possible for an event handler to modify the content of the
+ // body, before it gets written. If this is the case, $modified
+ // should be set to true.
+ //
+ // If $modified is true, we must not send back an ETag.
+ $modified = false;
+ if (!$this->emit('beforeWriteContent', [$uri, $node, &$data, &$modified])) {
+ return false;
+ }
+
+ $etag = $node->put($data);
+ if ($modified) {
+ $etag = null;
+ }
+ $this->emit('afterWriteContent', [$uri, $node]);
+
+ return true;
+ }
+
+ /**
+ * This method is invoked by sub-systems creating a new directory.
+ *
+ * @param string $uri
+ */
+ public function createDirectory($uri)
+ {
+ $this->createCollection($uri, new MkCol(['{DAV:}collection'], []));
+ }
+
+ /**
+ * Use this method to create a new collection.
+ *
+ * @param string $uri The new uri
+ *
+ * @return array|null
+ */
+ public function createCollection($uri, MkCol $mkCol)
+ {
+ list($parentUri, $newName) = Uri\split($uri);
+
+ // Making sure the parent exists
+ try {
+ $parent = $this->tree->getNodeForPath($parentUri);
+ } catch (Exception\NotFound $e) {
+ throw new Exception\Conflict('Parent node does not exist');
+ }
+
+ // Making sure the parent is a collection
+ if (!$parent instanceof ICollection) {
+ throw new Exception\Conflict('Parent node is not a collection');
+ }
+
+ // Making sure the child does not already exist
+ try {
+ $parent->getChild($newName);
+
+ // If we got here.. it means there's already a node on that url, and we need to throw a 405
+ throw new Exception\MethodNotAllowed('The resource you tried to create already exists');
+ } catch (Exception\NotFound $e) {
+ // NotFound is the expected behavior.
+ }
+
+ if (!$this->emit('beforeBind', [$uri])) {
+ return;
+ }
+
+ if ($parent instanceof IExtendedCollection) {
+ /*
+ * If the parent is an instance of IExtendedCollection, it means that
+ * we can pass the MkCol object directly as it may be able to store
+ * properties immediately.
+ */
+ $parent->createExtendedCollection($newName, $mkCol);
+ } else {
+ /*
+ * If the parent is a standard ICollection, it means only
+ * 'standard' collections can be created, so we should fail any
+ * MKCOL operation that carries extra resourcetypes.
+ */
+ if (count($mkCol->getResourceType()) > 1) {
+ throw new Exception\InvalidResourceType('The {DAV:}resourcetype you specified is not supported here.');
+ }
+
+ $parent->createDirectory($newName);
+ }
+
+ // If there are any properties that have not been handled/stored,
+ // we ask the 'propPatch' event to handle them. This will allow for
+ // example the propertyStorage system to store properties upon MKCOL.
+ if ($mkCol->getRemainingMutations()) {
+ $this->emit('propPatch', [$uri, $mkCol]);
+ }
+ $success = $mkCol->commit();
+
+ if (!$success) {
+ $result = $mkCol->getResult();
+
+ $formattedResult = [
+ 'href' => $uri,
+ ];
+
+ foreach ($result as $propertyName => $status) {
+ if (!isset($formattedResult[$status])) {
+ $formattedResult[$status] = [];
+ }
+ $formattedResult[$status][$propertyName] = null;
+ }
+
+ return $formattedResult;
+ }
+
+ $this->tree->markDirty($parentUri);
+ $this->emit('afterBind', [$uri]);
+ }
+
+ /**
+ * This method updates a resource's properties.
+ *
+ * The properties array must be a list of properties. Array-keys are
+ * property names in clarknotation, array-values are it's values.
+ * If a property must be deleted, the value should be null.
+ *
+ * Note that this request should either completely succeed, or
+ * completely fail.
+ *
+ * The response is an array with properties for keys, and http status codes
+ * as their values.
+ *
+ * @param string $path
+ *
+ * @return array
+ */
+ public function updateProperties($path, array $properties)
+ {
+ $propPatch = new PropPatch($properties);
+ $this->emit('propPatch', [$path, $propPatch]);
+ $propPatch->commit();
+
+ return $propPatch->getResult();
+ }
+
+ /**
+ * This method checks the main HTTP preconditions.
+ *
+ * Currently these are:
+ * * If-Match
+ * * If-None-Match
+ * * If-Modified-Since
+ * * If-Unmodified-Since
+ *
+ * The method will return true if all preconditions are met
+ * The method will return false, or throw an exception if preconditions
+ * failed. If false is returned the operation should be aborted, and
+ * the appropriate HTTP response headers are already set.
+ *
+ * Normally this method will throw 412 Precondition Failed for failures
+ * related to If-None-Match, If-Match and If-Unmodified Since. It will
+ * set the status to 304 Not Modified for If-Modified_since.
+ *
+ * @return bool
+ */
+ public function checkPreconditions(RequestInterface $request, ResponseInterface $response)
+ {
+ $path = $request->getPath();
+ $node = null;
+ $lastMod = null;
+ $etag = null;
+
+ if ($ifMatch = $request->getHeader('If-Match')) {
+ // If-Match contains an entity tag. Only if the entity-tag
+ // matches we are allowed to make the request succeed.
+ // If the entity-tag is '*' we are only allowed to make the
+ // request succeed if a resource exists at that url.
+ try {
+ $node = $this->tree->getNodeForPath($path);
+ } catch (Exception\NotFound $e) {
+ throw new Exception\PreconditionFailed('An If-Match header was specified and the resource did not exist', 'If-Match');
+ }
+
+ // Only need to check entity tags if they are not *
+ if ('*' !== $ifMatch) {
+ // There can be multiple ETags
+ $ifMatch = explode(',', $ifMatch);
+ $haveMatch = false;
+ foreach ($ifMatch as $ifMatchItem) {
+ // Stripping any extra spaces
+ $ifMatchItem = trim($ifMatchItem, ' ');
+
+ $etag = $node instanceof IFile ? $node->getETag() : null;
+ if ($etag === $ifMatchItem) {
+ $haveMatch = true;
+ } else {
+ // Evolution has a bug where it sometimes prepends the "
+ // with a \. This is our workaround.
+ if (str_replace('\\"', '"', $ifMatchItem) === $etag) {
+ $haveMatch = true;
+ }
+ }
+ }
+ if (!$haveMatch) {
+ if ($etag) {
+ $response->setHeader('ETag', $etag);
+ }
+ throw new Exception\PreconditionFailed('An If-Match header was specified, but none of the specified ETags matched.', 'If-Match');
+ }
+ }
+ }
+
+ if ($ifNoneMatch = $request->getHeader('If-None-Match')) {
+ // The If-None-Match header contains an ETag.
+ // Only if the ETag does not match the current ETag, the request will succeed
+ // The header can also contain *, in which case the request
+ // will only succeed if the entity does not exist at all.
+ $nodeExists = true;
+ if (!$node) {
+ try {
+ $node = $this->tree->getNodeForPath($path);
+ } catch (Exception\NotFound $e) {
+ $nodeExists = false;
+ }
+ }
+ if ($nodeExists) {
+ $haveMatch = false;
+ if ('*' === $ifNoneMatch) {
+ $haveMatch = true;
+ } else {
+ // There might be multiple ETags
+ $ifNoneMatch = explode(',', $ifNoneMatch);
+ $etag = $node instanceof IFile ? $node->getETag() : null;
+
+ foreach ($ifNoneMatch as $ifNoneMatchItem) {
+ // Stripping any extra spaces
+ $ifNoneMatchItem = trim($ifNoneMatchItem, ' ');
+
+ if ($etag === $ifNoneMatchItem) {
+ $haveMatch = true;
+ }
+ }
+ }
+
+ if ($haveMatch) {
+ if ($etag) {
+ $response->setHeader('ETag', $etag);
+ }
+ if ('GET' === $request->getMethod()) {
+ $response->setStatus(304);
+
+ return false;
+ } else {
+ throw new Exception\PreconditionFailed('An If-None-Match header was specified, but the ETag matched (or * was specified).', 'If-None-Match');
+ }
+ }
+ }
+ }
+
+ if (!$ifNoneMatch && ($ifModifiedSince = $request->getHeader('If-Modified-Since'))) {
+ // The If-Modified-Since header contains a date. We
+ // will only return the entity if it has been changed since
+ // that date. If it hasn't been changed, we return a 304
+ // header
+ // Note that this header only has to be checked if there was no If-None-Match header
+ // as per the HTTP spec.
+ $date = HTTP\parseDate($ifModifiedSince);
+
+ if ($date) {
+ if (is_null($node)) {
+ $node = $this->tree->getNodeForPath($path);
+ }
+ $lastMod = $node->getLastModified();
+ if ($lastMod) {
+ $lastMod = new \DateTime('@'.$lastMod);
+ if ($lastMod <= $date) {
+ $response->setStatus(304);
+ $response->setHeader('Last-Modified', HTTP\toDate($lastMod));
+
+ return false;
+ }
+ }
+ }
+ }
+
+ if ($ifUnmodifiedSince = $request->getHeader('If-Unmodified-Since')) {
+ // The If-Unmodified-Since will allow allow the request if the
+ // entity has not changed since the specified date.
+ $date = HTTP\parseDate($ifUnmodifiedSince);
+
+ // We must only check the date if it's valid
+ if ($date) {
+ if (is_null($node)) {
+ $node = $this->tree->getNodeForPath($path);
+ }
+ $lastMod = $node->getLastModified();
+ if ($lastMod) {
+ $lastMod = new \DateTime('@'.$lastMod);
+ if ($lastMod > $date) {
+ throw new Exception\PreconditionFailed('An If-Unmodified-Since header was specified, but the entity has been changed since the specified date.', 'If-Unmodified-Since');
+ }
+ }
+ }
+ }
+
+ // Now the hardest, the If: header. The If: header can contain multiple
+ // urls, ETags and so-called 'state tokens'.
+ //
+ // Examples of state tokens include lock-tokens (as defined in rfc4918)
+ // and sync-tokens (as defined in rfc6578).
+ //
+ // The only proper way to deal with these, is to emit events, that a
+ // Sync and Lock plugin can pick up.
+ $ifConditions = $this->getIfConditions($request);
+
+ foreach ($ifConditions as $kk => $ifCondition) {
+ foreach ($ifCondition['tokens'] as $ii => $token) {
+ $ifConditions[$kk]['tokens'][$ii]['validToken'] = false;
+ }
+ }
+
+ // Plugins are responsible for validating all the tokens.
+ // If a plugin deemed a token 'valid', it will set 'validToken' to
+ // true.
+ $this->emit('validateTokens', [$request, &$ifConditions]);
+
+ // Now we're going to analyze the result.
+
+ // Every ifCondition needs to validate to true, so we exit as soon as
+ // we have an invalid condition.
+ foreach ($ifConditions as $ifCondition) {
+ $uri = $ifCondition['uri'];
+ $tokens = $ifCondition['tokens'];
+
+ // We only need 1 valid token for the condition to succeed.
+ foreach ($tokens as $token) {
+ $tokenValid = $token['validToken'] || !$token['token'];
+
+ $etagValid = false;
+ if (!$token['etag']) {
+ $etagValid = true;
+ }
+ // Checking the ETag, only if the token was already deemed
+ // valid and there is one.
+ if ($token['etag'] && $tokenValid) {
+ // The token was valid, and there was an ETag. We must
+ // grab the current ETag and check it.
+ $node = $this->tree->getNodeForPath($uri);
+ $etagValid = $node instanceof IFile && $node->getETag() == $token['etag'];
+ }
+
+ if (($tokenValid && $etagValid) ^ $token['negate']) {
+ // Both were valid, so we can go to the next condition.
+ continue 2;
+ }
+ }
+
+ // If we ended here, it means there was no valid ETag + token
+ // combination found for the current condition. This means we fail!
+ throw new Exception\PreconditionFailed('Failed to find a valid token/etag combination for '.$uri, 'If');
+ }
+
+ return true;
+ }
+
+ /**
+ * This method is created to extract information from the WebDAV HTTP 'If:' header.
+ *
+ * The If header can be quite complex, and has a bunch of features. We're using a regex to extract all relevant information
+ * The function will return an array, containing structs with the following keys
+ *
+ * * uri - the uri the condition applies to.
+ * * tokens - The lock token. another 2 dimensional array containing 3 elements
+ *
+ * Example 1:
+ *
+ * If: ()
+ *
+ * Would result in:
+ *
+ * [
+ * [
+ * 'uri' => '/request/uri',
+ * 'tokens' => [
+ * [
+ * [
+ * 'negate' => false,
+ * 'token' => 'opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2',
+ * 'etag' => ""
+ * ]
+ * ]
+ * ],
+ * ]
+ * ]
+ *
+ * Example 2:
+ *
+ * If: (Not ["Im An ETag"]) (["Another ETag"]) (Not ["Path2 ETag"])
+ *
+ * Would result in:
+ *
+ * [
+ * [
+ * 'uri' => 'path',
+ * 'tokens' => [
+ * [
+ * [
+ * 'negate' => true,
+ * 'token' => 'opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2',
+ * 'etag' => '"Im An ETag"'
+ * ],
+ * [
+ * 'negate' => false,
+ * 'token' => '',
+ * 'etag' => '"Another ETag"'
+ * ]
+ * ]
+ * ],
+ * ],
+ * [
+ * 'uri' => 'path2',
+ * 'tokens' => [
+ * [
+ * [
+ * 'negate' => true,
+ * 'token' => '',
+ * 'etag' => '"Path2 ETag"'
+ * ]
+ * ]
+ * ],
+ * ],
+ * ]
+ *
+ * @return array
+ */
+ public function getIfConditions(RequestInterface $request)
+ {
+ $header = $request->getHeader('If');
+ if (!$header) {
+ return [];
+ }
+
+ $matches = [];
+
+ $regex = '/(?:\<(?P.*?)\>\s)?\((?PNot\s)?(?:\<(?P[^\>]*)\>)?(?:\s?)(?:\[(?P[^\]]*)\])?\)/im';
+ preg_match_all($regex, $header, $matches, PREG_SET_ORDER);
+
+ $conditions = [];
+
+ foreach ($matches as $match) {
+ // If there was no uri specified in this match, and there were
+ // already conditions parsed, we add the condition to the list of
+ // conditions for the previous uri.
+ if (!$match['uri'] && count($conditions)) {
+ $conditions[count($conditions) - 1]['tokens'][] = [
+ 'negate' => $match['not'] ? true : false,
+ 'token' => $match['token'],
+ 'etag' => isset($match['etag']) ? $match['etag'] : '',
+ ];
+ } else {
+ if (!$match['uri']) {
+ $realUri = $request->getPath();
+ } else {
+ $realUri = $this->calculateUri($match['uri']);
+ }
+
+ $conditions[] = [
+ 'uri' => $realUri,
+ 'tokens' => [
+ [
+ 'negate' => $match['not'] ? true : false,
+ 'token' => $match['token'],
+ 'etag' => isset($match['etag']) ? $match['etag'] : '',
+ ],
+ ],
+ ];
+ }
+ }
+
+ return $conditions;
+ }
+
+ /**
+ * Returns an array with resourcetypes for a node.
+ *
+ * @return array
+ */
+ public function getResourceTypeForNode(INode $node)
+ {
+ $result = [];
+ foreach ($this->resourceTypeMapping as $className => $resourceType) {
+ if ($node instanceof $className) {
+ $result[] = $resourceType;
+ }
+ }
+
+ return $result;
+ }
+
+ // }}}
+ // {{{ XML Readers & Writers
+
+ /**
+ * Returns a callback generating a WebDAV propfind response body based on a list of nodes.
+ *
+ * If 'strip404s' is set to true, all 404 responses will be removed.
+ *
+ * @param array|\Traversable $fileProperties The list with nodes
+ * @param bool $strip404s
+ *
+ * @return callable|string
+ */
+ public function generateMultiStatus($fileProperties, $strip404s = false)
+ {
+ $w = $this->xml->getWriter();
+ if (self::$streamMultiStatus) {
+ return function () use ($fileProperties, $strip404s, $w) {
+ $w->openUri('php://output');
+ $this->writeMultiStatus($w, $fileProperties, $strip404s);
+ $w->flush();
+ };
+ }
+ $w->openMemory();
+ $this->writeMultiStatus($w, $fileProperties, $strip404s);
+
+ return $w->outputMemory();
+ }
+
+ /**
+ * @param $fileProperties
+ */
+ private function writeMultiStatus(Writer $w, $fileProperties, bool $strip404s)
+ {
+ $w->contextUri = $this->baseUri;
+ $w->startDocument();
+
+ $w->startElement('{DAV:}multistatus');
+
+ foreach ($fileProperties as $entry) {
+ $href = $entry['href'];
+ unset($entry['href']);
+ if ($strip404s) {
+ unset($entry[404]);
+ }
+ $response = new Xml\Element\Response(
+ ltrim($href, '/'),
+ $entry
+ );
+ $w->write([
+ 'name' => '{DAV:}response',
+ 'value' => $response,
+ ]);
+ }
+ $w->endElement();
+ $w->endDocument();
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/ServerPlugin.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/ServerPlugin.php
new file mode 100644
index 000000000..70acb01eb
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/ServerPlugin.php
@@ -0,0 +1,105 @@
+ $this->getPluginName(),
+ 'description' => null,
+ 'link' => null,
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Sharing/ISharedNode.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Sharing/ISharedNode.php
new file mode 100644
index 000000000..a746ac753
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Sharing/ISharedNode.php
@@ -0,0 +1,69 @@
+server = $server;
+
+ $server->xml->elementMap['{DAV:}share-resource'] = 'Sabre\\DAV\\Xml\\Request\\ShareResource';
+
+ array_push(
+ $server->protectedProperties,
+ '{DAV:}share-mode'
+ );
+
+ $server->on('method:POST', [$this, 'httpPost']);
+ $server->on('propFind', [$this, 'propFind']);
+ $server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']);
+ $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
+ $server->on('onBrowserPostAction', [$this, 'browserPostAction']);
+ }
+
+ /**
+ * Updates the list of sharees on a shared resource.
+ *
+ * The sharees array is a list of people that are to be added modified
+ * or removed in the list of shares.
+ *
+ * @param string $path
+ * @param Sharee[] $sharees
+ */
+ public function shareResource($path, array $sharees)
+ {
+ $node = $this->server->tree->getNodeForPath($path);
+
+ if (!$node instanceof ISharedNode) {
+ throw new Forbidden('Sharing is not allowed on this node');
+ }
+
+ // Getting ACL info
+ $acl = $this->server->getPlugin('acl');
+
+ // If there's no ACL support, we allow everything
+ if ($acl) {
+ $acl->checkPrivileges($path, '{DAV:}share');
+ }
+
+ foreach ($sharees as $sharee) {
+ // We're going to attempt to get a local principal uri for a share
+ // href by emitting the getPrincipalByUri event.
+ $principal = null;
+ $this->server->emit('getPrincipalByUri', [$sharee->href, &$principal]);
+ $sharee->principal = $principal;
+ }
+ $node->updateInvites($sharees);
+ }
+
+ /**
+ * This event is triggered when properties are requested for nodes.
+ *
+ * This allows us to inject any sharings-specific properties.
+ */
+ public function propFind(PropFind $propFind, INode $node)
+ {
+ if ($node instanceof ISharedNode) {
+ $propFind->handle('{DAV:}share-access', function () use ($node) {
+ return new Property\ShareAccess($node->getShareAccess());
+ });
+ $propFind->handle('{DAV:}invite', function () use ($node) {
+ return new Property\Invite($node->getInvites());
+ });
+ $propFind->handle('{DAV:}share-resource-uri', function () use ($node) {
+ return new Property\Href($node->getShareResourceUri());
+ });
+ }
+ }
+
+ /**
+ * We intercept this to handle POST requests on shared resources.
+ *
+ * @return bool|null
+ */
+ public function httpPost(RequestInterface $request, ResponseInterface $response)
+ {
+ $path = $request->getPath();
+ $contentType = $request->getHeader('Content-Type');
+ if (null === $contentType) {
+ return;
+ }
+
+ // We're only interested in the davsharing content type.
+ if (false === strpos($contentType, 'application/davsharing+xml')) {
+ return;
+ }
+
+ $message = $this->server->xml->parse(
+ $request->getBody(),
+ $request->getUrl(),
+ $documentType
+ );
+
+ switch ($documentType) {
+ case '{DAV:}share-resource':
+
+ $this->shareResource($path, $message->sharees);
+ $response->setStatus(200);
+ // Adding this because sending a response body may cause issues,
+ // and I wanted some type of indicator the response was handled.
+ $response->setHeader('X-Sabre-Status', 'everything-went-well');
+
+ // Breaking the event chain
+ return false;
+
+ default:
+ throw new BadRequest('Unexpected document type: '.$documentType.' for this Content-Type');
+ }
+ }
+
+ /**
+ * This method is triggered whenever a subsystem reqeuests the privileges
+ * hat are supported on a particular node.
+ *
+ * We need to add a number of privileges for scheduling purposes.
+ */
+ public function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet)
+ {
+ if ($node instanceof ISharedNode) {
+ $supportedPrivilegeSet['{DAV:}share'] = [
+ 'abstract' => false,
+ 'aggregates' => [],
+ ];
+ }
+ }
+
+ /**
+ * Returns a bunch of meta-data about the plugin.
+ *
+ * Providing this information is optional, and is mainly displayed by the
+ * Browser plugin.
+ *
+ * The description key in the returned array may contain html and will not
+ * be sanitized.
+ *
+ * @return array
+ */
+ public function getPluginInfo()
+ {
+ return [
+ 'name' => $this->getPluginName(),
+ 'description' => 'This plugin implements WebDAV resource sharing',
+ 'link' => 'https://github.com/evert/webdav-sharing',
+ ];
+ }
+
+ /**
+ * This method is used to generate HTML output for the
+ * DAV\Browser\Plugin.
+ *
+ * @param string $output
+ * @param string $path
+ *
+ * @return bool|null
+ */
+ public function htmlActionsPanel(INode $node, &$output, $path)
+ {
+ if (!$node instanceof ISharedNode) {
+ return;
+ }
+
+ $aclPlugin = $this->server->getPlugin('acl');
+ if ($aclPlugin) {
+ if (!$aclPlugin->checkPrivileges($path, '{DAV:}share', \Sabre\DAVACL\Plugin::R_PARENT, false)) {
+ // Sharing is not permitted, we will not draw this interface.
+ return;
+ }
+ }
+
+ $output .= '
+
';
+ }
+
+ /**
+ * This method is triggered for POST actions generated by the browser
+ * plugin.
+ *
+ * @param string $path
+ * @param string $action
+ * @param array $postVars
+ */
+ public function browserPostAction($path, $action, $postVars)
+ {
+ if ('share' !== $action) {
+ return;
+ }
+
+ if (empty($postVars['href'])) {
+ throw new BadRequest('The "href" POST parameter is required');
+ }
+ if (empty($postVars['access'])) {
+ throw new BadRequest('The "access" POST parameter is required');
+ }
+
+ $accessMap = [
+ 'readwrite' => self::ACCESS_READWRITE,
+ 'read' => self::ACCESS_READ,
+ 'no-access' => self::ACCESS_NOACCESS,
+ ];
+
+ if (!isset($accessMap[$postVars['access']])) {
+ throw new BadRequest('The "access" POST must be readwrite, read or no-access');
+ }
+ $sharee = new Sharee([
+ 'href' => $postVars['href'],
+ 'access' => $accessMap[$postVars['access']],
+ ]);
+
+ $this->shareResource(
+ $path,
+ [$sharee]
+ );
+
+ return false;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/SimpleCollection.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/SimpleCollection.php
new file mode 100644
index 000000000..3cd14d9ba
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/SimpleCollection.php
@@ -0,0 +1,109 @@
+name = $name;
+ foreach ($children as $key => $child) {
+ if (is_string($child)) {
+ $child = new SimpleFile($key, $child);
+ } elseif (is_array($child)) {
+ $child = new self($key, $child);
+ } elseif (!$child instanceof INode) {
+ throw new InvalidArgumentException('Children must be specified as strings, arrays or instances of Sabre\DAV\INode');
+ }
+ $this->addChild($child);
+ }
+ }
+
+ /**
+ * Adds a new childnode to this collection.
+ */
+ public function addChild(INode $child)
+ {
+ $this->children[$child->getName()] = $child;
+ }
+
+ /**
+ * Returns the name of the collection.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Returns a child object, by its name.
+ *
+ * This method makes use of the getChildren method to grab all the child nodes, and compares the name.
+ * Generally its wise to override this, as this can usually be optimized
+ *
+ * This method must throw Sabre\DAV\Exception\NotFound if the node does not
+ * exist.
+ *
+ * @param string $name
+ *
+ * @throws Exception\NotFound
+ *
+ * @return INode
+ */
+ public function getChild($name)
+ {
+ if (isset($this->children[$name])) {
+ return $this->children[$name];
+ }
+ throw new Exception\NotFound('File not found: '.$name.' in \''.$this->getName().'\'');
+ }
+
+ /**
+ * Returns a list of children for this collection.
+ *
+ * @return INode[]
+ */
+ public function getChildren()
+ {
+ return array_values($this->children);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/SimpleFile.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/SimpleFile.php
new file mode 100644
index 000000000..ca808b672
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/SimpleFile.php
@@ -0,0 +1,118 @@
+name = $name;
+ $this->contents = $contents;
+ $this->mimeType = $mimeType;
+ }
+
+ /**
+ * Returns the node name for this file.
+ *
+ * This name is used to construct the url.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Returns the data.
+ *
+ * This method may either return a string or a readable stream resource
+ *
+ * @return mixed
+ */
+ public function get()
+ {
+ return $this->contents;
+ }
+
+ /**
+ * Returns the size of the file, in bytes.
+ *
+ * @return int
+ */
+ public function getSize()
+ {
+ return strlen($this->contents);
+ }
+
+ /**
+ * Returns the ETag for a file.
+ *
+ * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
+ * The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
+ *
+ * Return null if the ETag can not effectively be determined
+ *
+ * @return string
+ */
+ public function getETag()
+ {
+ return '"'.sha1($this->contents).'"';
+ }
+
+ /**
+ * Returns the mime-type for a file.
+ *
+ * If null is returned, we'll assume application/octet-stream
+ *
+ * @return string
+ */
+ public function getContentType()
+ {
+ return $this->mimeType;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/StringUtil.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/StringUtil.php
new file mode 100644
index 000000000..13a4399e3
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/StringUtil.php
@@ -0,0 +1,88 @@
+ 'The current synctoken',
+ * 'added' => [
+ * 'new.txt',
+ * ],
+ * 'modified' => [
+ * 'modified.txt',
+ * ],
+ * 'deleted' => array(
+ * 'foo.php.bak',
+ * 'old.txt'
+ * )
+ * ];
+ *
+ * The syncToken property should reflect the *current* syncToken of the
+ * collection, as reported getSyncToken(). This is needed here too, to
+ * ensure the operation is atomic.
+ *
+ * If the syncToken is specified as null, this is an initial sync, and all
+ * members should be reported.
+ *
+ * The modified property is an array of nodenames that have changed since
+ * the last token.
+ *
+ * The deleted property is an array with nodenames, that have been deleted
+ * from collection.
+ *
+ * The second argument is basically the 'depth' of the report. If it's 1,
+ * you only have to report changes that happened only directly in immediate
+ * descendants. If it's 2, it should also include changes from the nodes
+ * below the child collections. (grandchildren)
+ *
+ * The third (optional) argument allows a client to specify how many
+ * results should be returned at most. If the limit is not specified, it
+ * should be treated as infinite.
+ *
+ * If the limit (infinite or not) is higher than you're willing to return,
+ * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
+ *
+ * If the syncToken is expired (due to data cleanup) or unknown, you must
+ * return null.
+ *
+ * The limit is 'suggestive'. You are free to ignore it.
+ *
+ * @param string $syncToken
+ * @param int $syncLevel
+ * @param int $limit
+ *
+ * @return array
+ */
+ public function getChanges($syncToken, $syncLevel, $limit = null);
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Sync/Plugin.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Sync/Plugin.php
new file mode 100644
index 000000000..32106abb3
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Sync/Plugin.php
@@ -0,0 +1,240 @@
+server = $server;
+ $server->xml->elementMap['{DAV:}sync-collection'] = 'Sabre\\DAV\\Xml\\Request\\SyncCollectionReport';
+
+ $self = $this;
+
+ $server->on('report', function ($reportName, $dom, $uri) use ($self) {
+ if ('{DAV:}sync-collection' === $reportName) {
+ $this->server->transactionType = 'report-sync-collection';
+ $self->syncCollection($uri, $dom);
+
+ return false;
+ }
+ });
+
+ $server->on('propFind', [$this, 'propFind']);
+ $server->on('validateTokens', [$this, 'validateTokens']);
+ }
+
+ /**
+ * Returns a list of reports this plugin supports.
+ *
+ * This will be used in the {DAV:}supported-report-set property.
+ * Note that you still need to subscribe to the 'report' event to actually
+ * implement them
+ *
+ * @param string $uri
+ *
+ * @return array
+ */
+ public function getSupportedReportSet($uri)
+ {
+ $node = $this->server->tree->getNodeForPath($uri);
+ if ($node instanceof ISyncCollection && $node->getSyncToken()) {
+ return [
+ '{DAV:}sync-collection',
+ ];
+ }
+
+ return [];
+ }
+
+ /**
+ * This method handles the {DAV:}sync-collection HTTP REPORT.
+ *
+ * @param string $uri
+ */
+ public function syncCollection($uri, SyncCollectionReport $report)
+ {
+ // Getting the data
+ $node = $this->server->tree->getNodeForPath($uri);
+ if (!$node instanceof ISyncCollection) {
+ throw new DAV\Exception\ReportNotSupported('The {DAV:}sync-collection REPORT is not supported on this url.');
+ }
+ $token = $node->getSyncToken();
+ if (!$token) {
+ throw new DAV\Exception\ReportNotSupported('No sync information is available at this node');
+ }
+
+ $syncToken = $report->syncToken;
+ if (!is_null($syncToken)) {
+ // Sync-token must start with our prefix
+ if (self::SYNCTOKEN_PREFIX !== substr($syncToken, 0, strlen(self::SYNCTOKEN_PREFIX))) {
+ throw new DAV\Exception\InvalidSyncToken('Invalid or unknown sync token');
+ }
+
+ $syncToken = substr($syncToken, strlen(self::SYNCTOKEN_PREFIX));
+ }
+ $changeInfo = $node->getChanges($syncToken, $report->syncLevel, $report->limit);
+
+ if (is_null($changeInfo)) {
+ throw new DAV\Exception\InvalidSyncToken('Invalid or unknown sync token');
+ }
+
+ // Encoding the response
+ $this->sendSyncCollectionResponse(
+ $changeInfo['syncToken'],
+ $uri,
+ $changeInfo['added'],
+ $changeInfo['modified'],
+ $changeInfo['deleted'],
+ $report->properties
+ );
+ }
+
+ /**
+ * Sends the response to a sync-collection request.
+ *
+ * @param string $syncToken
+ * @param string $collectionUrl
+ */
+ protected function sendSyncCollectionResponse($syncToken, $collectionUrl, array $added, array $modified, array $deleted, array $properties)
+ {
+ $fullPaths = [];
+
+ // Pre-fetching children, if this is possible.
+ foreach (array_merge($added, $modified) as $item) {
+ $fullPath = $collectionUrl.'/'.$item;
+ $fullPaths[] = $fullPath;
+ }
+
+ $responses = [];
+ foreach ($this->server->getPropertiesForMultiplePaths($fullPaths, $properties) as $fullPath => $props) {
+ // The 'Property_Response' class is responsible for generating a
+ // single {DAV:}response xml element.
+ $responses[] = new DAV\Xml\Element\Response($fullPath, $props);
+ }
+
+ // Deleted items also show up as 'responses'. They have no properties,
+ // and a single {DAV:}status element set as 'HTTP/1.1 404 Not Found'.
+ foreach ($deleted as $item) {
+ $fullPath = $collectionUrl.'/'.$item;
+ $responses[] = new DAV\Xml\Element\Response($fullPath, [], 404);
+ }
+ $multiStatus = new DAV\Xml\Response\MultiStatus($responses, self::SYNCTOKEN_PREFIX.$syncToken);
+
+ $this->server->httpResponse->setStatus(207);
+ $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ $this->server->httpResponse->setBody(
+ $this->server->xml->write('{DAV:}multistatus', $multiStatus, $this->server->getBaseUri())
+ );
+ }
+
+ /**
+ * This method is triggered whenever properties are requested for a node.
+ * We intercept this to see if we must return a {DAV:}sync-token.
+ */
+ public function propFind(DAV\PropFind $propFind, DAV\INode $node)
+ {
+ $propFind->handle('{DAV:}sync-token', function () use ($node) {
+ if (!$node instanceof ISyncCollection || !$token = $node->getSyncToken()) {
+ return;
+ }
+
+ return self::SYNCTOKEN_PREFIX.$token;
+ });
+ }
+
+ /**
+ * The validateTokens event is triggered before every request.
+ *
+ * It's a moment where this plugin can check all the supplied lock tokens
+ * in the If: header, and check if they are valid.
+ *
+ * @param array $conditions
+ */
+ public function validateTokens(RequestInterface $request, &$conditions)
+ {
+ foreach ($conditions as $kk => $condition) {
+ foreach ($condition['tokens'] as $ii => $token) {
+ // Sync-tokens must always start with our designated prefix.
+ if (self::SYNCTOKEN_PREFIX !== substr($token['token'], 0, strlen(self::SYNCTOKEN_PREFIX))) {
+ continue;
+ }
+
+ // Checking if the token is a match.
+ $node = $this->server->tree->getNodeForPath($condition['uri']);
+
+ if (
+ $node instanceof ISyncCollection &&
+ $node->getSyncToken() == substr($token['token'], strlen(self::SYNCTOKEN_PREFIX))
+ ) {
+ $conditions[$kk]['tokens'][$ii]['validToken'] = true;
+ }
+ }
+ }
+ }
+
+ /**
+ * 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 WebDAV Collection Sync (rfc6578)',
+ 'link' => 'http://sabre.io/dav/sync/',
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php
new file mode 100644
index 000000000..9f8ec5b54
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php
@@ -0,0 +1,298 @@
+dataDir = $dataDir;
+ }
+
+ /**
+ * Initialize the plugin.
+ *
+ * This is called automatically be the Server class after this plugin is
+ * added with Sabre\DAV\Server::addPlugin()
+ */
+ public function initialize(Server $server)
+ {
+ $this->server = $server;
+ $server->on('beforeMethod:*', [$this, 'beforeMethod']);
+ $server->on('beforeCreateFile', [$this, 'beforeCreateFile']);
+ }
+
+ /**
+ * This method is called before any HTTP method handler.
+ *
+ * This method intercepts any GET, DELETE, PUT and PROPFIND calls to
+ * filenames that are known to match the 'temporary file' regex.
+ *
+ * @return bool
+ */
+ public function beforeMethod(RequestInterface $request, ResponseInterface $response)
+ {
+ if (!$tempLocation = $this->isTempFile($request->getPath())) {
+ return;
+ }
+
+ switch ($request->getMethod()) {
+ case 'GET':
+ return $this->httpGet($request, $response, $tempLocation);
+ case 'PUT':
+ return $this->httpPut($request, $response, $tempLocation);
+ case 'PROPFIND':
+ return $this->httpPropfind($request, $response, $tempLocation);
+ case 'DELETE':
+ return $this->httpDelete($request, $response, $tempLocation);
+ }
+
+ return;
+ }
+
+ /**
+ * This method is invoked if some subsystem creates a new file.
+ *
+ * This is used to deal with HTTP LOCK requests which create a new
+ * file.
+ *
+ * @param string $uri
+ * @param resource $data
+ * @param bool $modified should be set to true, if this event handler
+ * changed &$data
+ *
+ * @return bool
+ */
+ public function beforeCreateFile($uri, $data, ICollection $parent, $modified)
+ {
+ if ($tempPath = $this->isTempFile($uri)) {
+ $hR = $this->server->httpResponse;
+ $hR->setHeader('X-Sabre-Temp', 'true');
+ file_put_contents($tempPath, $data);
+
+ return false;
+ }
+
+ return;
+ }
+
+ /**
+ * This method will check if the url matches the temporary file pattern
+ * if it does, it will return an path based on $this->dataDir for the
+ * temporary file storage.
+ *
+ * @param string $path
+ *
+ * @return bool|string
+ */
+ protected function isTempFile($path)
+ {
+ // We're only interested in the basename.
+ list(, $tempPath) = Uri\split($path);
+
+ if (null === $tempPath) {
+ return false;
+ }
+
+ foreach ($this->temporaryFilePatterns as $tempFile) {
+ if (preg_match($tempFile, $tempPath)) {
+ return $this->getDataDir().'/sabredav_'.md5($path).'.tempfile';
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * This method handles the GET method for temporary files.
+ * If the file doesn't exist, it will return false which will kick in
+ * the regular system for the GET method.
+ *
+ * @param string $tempLocation
+ *
+ * @return bool
+ */
+ public function httpGet(RequestInterface $request, ResponseInterface $hR, $tempLocation)
+ {
+ if (!file_exists($tempLocation)) {
+ return;
+ }
+
+ $hR->setHeader('Content-Type', 'application/octet-stream');
+ $hR->setHeader('Content-Length', filesize($tempLocation));
+ $hR->setHeader('X-Sabre-Temp', 'true');
+ $hR->setStatus(200);
+ $hR->setBody(fopen($tempLocation, 'r'));
+
+ return false;
+ }
+
+ /**
+ * This method handles the PUT method.
+ *
+ * @param string $tempLocation
+ *
+ * @return bool
+ */
+ public function httpPut(RequestInterface $request, ResponseInterface $hR, $tempLocation)
+ {
+ $hR->setHeader('X-Sabre-Temp', 'true');
+
+ $newFile = !file_exists($tempLocation);
+
+ if (!$newFile && ($this->server->httpRequest->getHeader('If-None-Match'))) {
+ throw new Exception\PreconditionFailed('The resource already exists, and an If-None-Match header was supplied');
+ }
+
+ file_put_contents($tempLocation, $this->server->httpRequest->getBody());
+ $hR->setStatus($newFile ? 201 : 200);
+
+ return false;
+ }
+
+ /**
+ * This method handles the DELETE method.
+ *
+ * If the file didn't exist, it will return false, which will make the
+ * standard HTTP DELETE handler kick in.
+ *
+ * @param string $tempLocation
+ *
+ * @return bool
+ */
+ public function httpDelete(RequestInterface $request, ResponseInterface $hR, $tempLocation)
+ {
+ if (!file_exists($tempLocation)) {
+ return;
+ }
+
+ unlink($tempLocation);
+ $hR->setHeader('X-Sabre-Temp', 'true');
+ $hR->setStatus(204);
+
+ return false;
+ }
+
+ /**
+ * This method handles the PROPFIND method.
+ *
+ * It's a very lazy method, it won't bother checking the request body
+ * for which properties were requested, and just sends back a default
+ * set of properties.
+ *
+ * @param string $tempLocation
+ *
+ * @return bool
+ */
+ public function httpPropfind(RequestInterface $request, ResponseInterface $hR, $tempLocation)
+ {
+ if (!file_exists($tempLocation)) {
+ return;
+ }
+
+ $hR->setHeader('X-Sabre-Temp', 'true');
+ $hR->setStatus(207);
+ $hR->setHeader('Content-Type', 'application/xml; charset=utf-8');
+
+ $properties = [
+ 'href' => $request->getPath(),
+ 200 => [
+ '{DAV:}getlastmodified' => new Xml\Property\GetLastModified(filemtime($tempLocation)),
+ '{DAV:}getcontentlength' => filesize($tempLocation),
+ '{DAV:}resourcetype' => new Xml\Property\ResourceType(null),
+ '{'.Server::NS_SABREDAV.'}tempFile' => true,
+ ],
+ ];
+
+ $data = $this->server->generateMultiStatus([$properties]);
+ $hR->setBody($data);
+
+ return false;
+ }
+
+ /**
+ * This method returns the directory where the temporary files should be stored.
+ *
+ * @return string
+ */
+ protected function getDataDir()
+ {
+ return $this->dataDir;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Tree.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Tree.php
new file mode 100644
index 000000000..72e14d522
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Tree.php
@@ -0,0 +1,324 @@
+rootNode = $rootNode;
+ }
+
+ /**
+ * Returns the INode object for the requested path.
+ *
+ * @param string $path
+ *
+ * @return INode
+ */
+ public function getNodeForPath($path)
+ {
+ $path = trim($path, '/');
+ if (isset($this->cache[$path])) {
+ return $this->cache[$path];
+ }
+
+ // Is it the root node?
+ if (!strlen($path)) {
+ return $this->rootNode;
+ }
+
+ // Attempting to fetch its parent
+ list($parentName, $baseName) = Uri\split($path);
+
+ // If there was no parent, we must simply ask it from the root node.
+ if ('' === $parentName) {
+ $node = $this->rootNode->getChild($baseName);
+ } else {
+ // Otherwise, we recursively grab the parent and ask him/her.
+ $parent = $this->getNodeForPath($parentName);
+
+ if (!($parent instanceof ICollection)) {
+ throw new Exception\NotFound('Could not find node at path: '.$path);
+ }
+ $node = $parent->getChild($baseName);
+ }
+
+ $this->cache[$path] = $node;
+
+ return $node;
+ }
+
+ /**
+ * This function allows you to check if a node exists.
+ *
+ * Implementors of this class should override this method to make
+ * it cheaper.
+ *
+ * @param string $path
+ *
+ * @return bool
+ */
+ public function nodeExists($path)
+ {
+ try {
+ // The root always exists
+ if ('' === $path) {
+ return true;
+ }
+
+ list($parent, $base) = Uri\split($path);
+
+ $parentNode = $this->getNodeForPath($parent);
+ if (!$parentNode instanceof ICollection) {
+ return false;
+ }
+
+ return $parentNode->childExists($base);
+ } catch (Exception\NotFound $e) {
+ return false;
+ }
+ }
+
+ /**
+ * Copies a file from path to another.
+ *
+ * @param string $sourcePath The source location
+ * @param string $destinationPath The full destination path
+ */
+ public function copy($sourcePath, $destinationPath)
+ {
+ $sourceNode = $this->getNodeForPath($sourcePath);
+
+ // grab the dirname and basename components
+ list($destinationDir, $destinationName) = Uri\split($destinationPath);
+
+ $destinationParent = $this->getNodeForPath($destinationDir);
+ // Check if the target can handle the copy itself. If not, we do it ourselves.
+ if (!$destinationParent instanceof ICopyTarget || !$destinationParent->copyInto($destinationName, $sourcePath, $sourceNode)) {
+ $this->copyNode($sourceNode, $destinationParent, $destinationName);
+ }
+
+ $this->markDirty($destinationDir);
+ }
+
+ /**
+ * Moves a file from one location to another.
+ *
+ * @param string $sourcePath The path to the file which should be moved
+ * @param string $destinationPath The full destination path, so not just the destination parent node
+ *
+ * @return int
+ */
+ public function move($sourcePath, $destinationPath)
+ {
+ list($sourceDir) = Uri\split($sourcePath);
+ list($destinationDir, $destinationName) = Uri\split($destinationPath);
+
+ if ($sourceDir === $destinationDir) {
+ // If this is a 'local' rename, it means we can just trigger a rename.
+ $sourceNode = $this->getNodeForPath($sourcePath);
+ $sourceNode->setName($destinationName);
+ } else {
+ $newParentNode = $this->getNodeForPath($destinationDir);
+ $moveSuccess = false;
+ if ($newParentNode instanceof IMoveTarget) {
+ // The target collection may be able to handle the move
+ $sourceNode = $this->getNodeForPath($sourcePath);
+ $moveSuccess = $newParentNode->moveInto($destinationName, $sourcePath, $sourceNode);
+ }
+ if (!$moveSuccess) {
+ $this->copy($sourcePath, $destinationPath);
+ $this->getNodeForPath($sourcePath)->delete();
+ }
+ }
+ $this->markDirty($sourceDir);
+ $this->markDirty($destinationDir);
+ }
+
+ /**
+ * Deletes a node from the tree.
+ *
+ * @param string $path
+ */
+ public function delete($path)
+ {
+ $node = $this->getNodeForPath($path);
+ $node->delete();
+
+ list($parent) = Uri\split($path);
+ $this->markDirty($parent);
+ }
+
+ /**
+ * Returns a list of childnodes for a given path.
+ *
+ * @param string $path
+ *
+ * @return \Traversable
+ */
+ public function getChildren($path)
+ {
+ $node = $this->getNodeForPath($path);
+ $basePath = trim($path, '/');
+ if ('' !== $basePath) {
+ $basePath .= '/';
+ }
+
+ foreach ($node->getChildren() as $child) {
+ $this->cache[$basePath.$child->getName()] = $child;
+ yield $child;
+ }
+ }
+
+ /**
+ * This method is called with every tree update.
+ *
+ * Examples of tree updates are:
+ * * node deletions
+ * * node creations
+ * * copy
+ * * move
+ * * renaming nodes
+ *
+ * If Tree classes implement a form of caching, this will allow
+ * them to make sure caches will be expired.
+ *
+ * If a path is passed, it is assumed that the entire subtree is dirty
+ *
+ * @param string $path
+ */
+ public function markDirty($path)
+ {
+ // We don't care enough about sub-paths
+ // flushing the entire cache
+ $path = trim($path, '/');
+ foreach ($this->cache as $nodePath => $node) {
+ if ('' === $path || $nodePath == $path || 0 === strpos($nodePath, $path.'/')) {
+ unset($this->cache[$nodePath]);
+ }
+ }
+ }
+
+ /**
+ * This method tells the tree system to pre-fetch and cache a list of
+ * children of a single parent.
+ *
+ * There are a bunch of operations in the WebDAV stack that request many
+ * children (based on uris), and sometimes fetching many at once can
+ * optimize this.
+ *
+ * This method returns an array with the found nodes. It's keys are the
+ * original paths. The result may be out of order.
+ *
+ * @param array $paths list of nodes that must be fetched
+ *
+ * @return array
+ */
+ public function getMultipleNodes($paths)
+ {
+ // Finding common parents
+ $parents = [];
+ foreach ($paths as $path) {
+ list($parent, $node) = Uri\split($path);
+ if (!isset($parents[$parent])) {
+ $parents[$parent] = [$node];
+ } else {
+ $parents[$parent][] = $node;
+ }
+ }
+
+ $result = [];
+
+ foreach ($parents as $parent => $children) {
+ $parentNode = $this->getNodeForPath($parent);
+ if ($parentNode instanceof IMultiGet) {
+ foreach ($parentNode->getMultipleChildren($children) as $childNode) {
+ $fullPath = $parent.'/'.$childNode->getName();
+ $result[$fullPath] = $childNode;
+ $this->cache[$fullPath] = $childNode;
+ }
+ } else {
+ foreach ($children as $child) {
+ $fullPath = $parent.'/'.$child;
+ $result[$fullPath] = $this->getNodeForPath($fullPath);
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * copyNode.
+ *
+ * @param string $destinationName
+ */
+ protected function copyNode(INode $source, ICollection $destinationParent, $destinationName = null)
+ {
+ if ('' === (string) $destinationName) {
+ $destinationName = $source->getName();
+ }
+
+ if ($source instanceof IFile) {
+ $data = $source->get();
+
+ // If the body was a string, we need to convert it to a stream
+ if (is_string($data)) {
+ $stream = fopen('php://temp', 'r+');
+ fwrite($stream, $data);
+ rewind($stream);
+ $data = $stream;
+ }
+ $destinationParent->createFile($destinationName, $data);
+ $destination = $destinationParent->getChild($destinationName);
+ } elseif ($source instanceof ICollection) {
+ $destinationParent->createDirectory($destinationName);
+
+ $destination = $destinationParent->getChild($destinationName);
+ foreach ($source->getChildren() as $child) {
+ $this->copyNode($child, $destination);
+ }
+ }
+ if ($source instanceof IProperties && $destination instanceof IProperties) {
+ $props = $source->getProperties([]);
+ $propPatch = new PropPatch($props);
+ $destination->propPatch($propPatch);
+ $propPatch->commit();
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/UUIDUtil.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/UUIDUtil.php
new file mode 100644
index 000000000..8c36e1b0b
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/UUIDUtil.php
@@ -0,0 +1,66 @@
+value array.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Prop 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)
+ {
+ // If there's no children, we don't do anything.
+ if ($reader->isEmptyElement) {
+ $reader->next();
+
+ return [];
+ }
+
+ $values = [];
+
+ $reader->read();
+ do {
+ if (Reader::ELEMENT === $reader->nodeType) {
+ $clark = $reader->getClark();
+ $values[$clark] = self::parseCurrentElement($reader)['value'];
+ } else {
+ $reader->read();
+ }
+ } while (Reader::END_ELEMENT !== $reader->nodeType);
+
+ $reader->read();
+
+ return $values;
+ }
+
+ /**
+ * This function behaves similar to Sabre\Xml\Reader::parseCurrentElement,
+ * but instead of creating deep xml array structures, it will turn any
+ * top-level element it doesn't recognize into either a string, or an
+ * XmlFragment class.
+ *
+ * This method returns arn array with 2 properties:
+ * * name - A clark-notation XML element name.
+ * * value - The parsed value.
+ *
+ * @return array
+ */
+ private static function parseCurrentElement(Reader $reader)
+ {
+ $name = $reader->getClark();
+
+ if (array_key_exists($name, $reader->elementMap)) {
+ $deserializer = $reader->elementMap[$name];
+ if (is_subclass_of($deserializer, 'Sabre\\Xml\\XmlDeserializable')) {
+ $value = call_user_func([$deserializer, 'xmlDeserialize'], $reader);
+ } elseif (is_callable($deserializer)) {
+ $value = call_user_func($deserializer, $reader);
+ } else {
+ $type = gettype($deserializer);
+ if ('string' === $type) {
+ $type .= ' ('.$deserializer.')';
+ } elseif ('object' === $type) {
+ $type .= ' ('.get_class($deserializer).')';
+ }
+ throw new \LogicException('Could not use this type as a deserializer: '.$type);
+ }
+ } else {
+ $value = Complex::xmlDeserialize($reader);
+ }
+
+ return [
+ 'name' => $name,
+ 'value' => $value,
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Element/Response.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Element/Response.php
new file mode 100644
index 000000000..45c161fa4
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Element/Response.php
@@ -0,0 +1,237 @@
+href = $href;
+ $this->responseProperties = $responseProperties;
+ $this->httpStatus = $httpStatus;
+ }
+
+ /**
+ * Returns the url.
+ *
+ * @return string
+ */
+ public function getHref()
+ {
+ return $this->href;
+ }
+
+ /**
+ * Returns the httpStatus value.
+ *
+ * @return string
+ */
+ public function getHttpStatus()
+ {
+ return $this->httpStatus;
+ }
+
+ /**
+ * Returns the property list.
+ *
+ * @return array
+ */
+ public function getResponseProperties()
+ {
+ return $this->responseProperties;
+ }
+
+ /**
+ * 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)
+ {
+ if ($status = $this->getHTTPStatus()) {
+ $writer->writeElement('{DAV:}status', 'HTTP/1.1 '.$status.' '.\Sabre\HTTP\Response::$statusCodes[$status]);
+ }
+ $writer->writeElement('{DAV:}href', $writer->contextUri.\Sabre\HTTP\encodePath($this->getHref()));
+
+ $empty = true;
+
+ foreach ($this->getResponseProperties() as $status => $properties) {
+ // Skipping empty lists
+ if (!$properties || (!ctype_digit($status) && !is_int($status))) {
+ continue;
+ }
+ $empty = false;
+ $writer->startElement('{DAV:}propstat');
+ $writer->writeElement('{DAV:}prop', $properties);
+ $writer->writeElement('{DAV:}status', 'HTTP/1.1 '.$status.' '.\Sabre\HTTP\Response::$statusCodes[$status]);
+ $writer->endElement(); // {DAV:}propstat
+ }
+ if ($empty) {
+ /*
+ * The WebDAV spec _requires_ at least one DAV:propstat to appear for
+ * every DAV:response. In some circumstances however, there are no
+ * properties to encode.
+ *
+ * In those cases we MUST specify at least one DAV:propstat anyway, with
+ * no properties.
+ */
+ $writer->writeElement('{DAV:}propstat', [
+ '{DAV:}prop' => [],
+ '{DAV:}status' => 'HTTP/1.1 418 '.\Sabre\HTTP\Response::$statusCodes[418],
+ ]);
+ }
+ }
+
+ /**
+ * 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)
+ {
+ $reader->pushContext();
+
+ $reader->elementMap['{DAV:}propstat'] = 'Sabre\\Xml\\Element\\KeyValue';
+
+ // We are overriding the parser for {DAV:}prop. This deserializer is
+ // almost identical to the one for Sabre\Xml\Element\KeyValue.
+ //
+ // The difference is that if there are any child-elements inside of
+ // {DAV:}prop, that have no value, normally any deserializers are
+ // called. But we don't want this, because a singular element without
+ // child-elements implies 'no value' in {DAV:}prop, so we want to skip
+ // deserializers and just set null for those.
+ $reader->elementMap['{DAV:}prop'] = function (Reader $reader) {
+ if ($reader->isEmptyElement) {
+ $reader->next();
+
+ return [];
+ }
+ $values = [];
+ $reader->read();
+ do {
+ if (Reader::ELEMENT === $reader->nodeType) {
+ $clark = $reader->getClark();
+
+ if ($reader->isEmptyElement) {
+ $values[$clark] = null;
+ $reader->next();
+ } else {
+ $values[$clark] = $reader->parseCurrentElement()['value'];
+ }
+ } else {
+ $reader->read();
+ }
+ } while (Reader::END_ELEMENT !== $reader->nodeType);
+ $reader->read();
+
+ return $values;
+ };
+ $elems = $reader->parseInnerTree();
+ $reader->popContext();
+
+ $href = null;
+ $propertyLists = [];
+ $statusCode = null;
+
+ foreach ($elems as $elem) {
+ switch ($elem['name']) {
+ case '{DAV:}href':
+ $href = $elem['value'];
+ break;
+ case '{DAV:}propstat':
+ $status = $elem['value']['{DAV:}status'];
+ list(, $status) = explode(' ', $status, 3);
+ $properties = isset($elem['value']['{DAV:}prop']) ? $elem['value']['{DAV:}prop'] : [];
+ if ($properties) {
+ $propertyLists[$status] = $properties;
+ }
+ break;
+ case '{DAV:}status':
+ list(, $statusCode) = explode(' ', $elem['value'], 3);
+ break;
+ }
+ }
+
+ return new self($href, $propertyLists, $statusCode);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Element/Sharee.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Element/Sharee.php
new file mode 100644
index 000000000..33564d8f2
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Element/Sharee.php
@@ -0,0 +1,189 @@
+ $v) {
+ if (property_exists($this, $k)) {
+ $this->$k = $v;
+ } else {
+ throw new \InvalidArgumentException('Unknown property: '.$k);
+ }
+ }
+ }
+
+ /**
+ * The xmlSerialize method is called during xml writing.
+ *
+ * Use the $writer argument to write its own xml serialization.
+ *
+ * An important note: do _not_ create a parent element. Any element
+ * implementing 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->write([
+ new Href($this->href),
+ '{DAV:}prop' => $this->properties,
+ '{DAV:}share-access' => new ShareAccess($this->access),
+ ]);
+ switch ($this->inviteStatus) {
+ case Plugin::INVITE_NORESPONSE:
+ $writer->writeElement('{DAV:}invite-noresponse');
+ break;
+ case Plugin::INVITE_ACCEPTED:
+ $writer->writeElement('{DAV:}invite-accepted');
+ break;
+ case Plugin::INVITE_DECLINED:
+ $writer->writeElement('{DAV:}invite-declined');
+ break;
+ case Plugin::INVITE_INVALID:
+ $writer->writeElement('{DAV:}invite-invalid');
+ break;
+ }
+ }
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called 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)
+ {
+ // Temporarily override configuration
+ $reader->pushContext();
+ $reader->elementMap['{DAV:}share-access'] = 'Sabre\DAV\Xml\Property\ShareAccess';
+ $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Deserializer\keyValue';
+
+ $elems = Deserializer\keyValue($reader, 'DAV:');
+
+ // Restore previous configuration
+ $reader->popContext();
+
+ $sharee = new self();
+ if (!isset($elems['href'])) {
+ throw new BadRequest('Every {DAV:}sharee must have a {DAV:}href child-element');
+ }
+ $sharee->href = $elems['href'];
+
+ if (isset($elems['prop'])) {
+ $sharee->properties = $elems['prop'];
+ }
+ if (isset($elems['comment'])) {
+ $sharee->comment = $elems['comment'];
+ }
+ if (!isset($elems['share-access'])) {
+ throw new BadRequest('Every {DAV:}sharee must have a {DAV:}share-access child element');
+ }
+ $sharee->access = $elems['share-access']->getValue();
+
+ return $sharee;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/Complex.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/Complex.php
new file mode 100644
index 000000000..787d30d95
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/Complex.php
@@ -0,0 +1,87 @@
+next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @return mixed
+ */
+ public static function xmlDeserialize(Reader $reader)
+ {
+ $xml = $reader->readInnerXml();
+
+ if (Reader::ELEMENT === $reader->nodeType && $reader->isEmptyElement) {
+ // Easy!
+ $reader->next();
+
+ return null;
+ }
+ // Now we have a copy of the inner xml, we need to traverse it to get
+ // all the strings. If there's no non-string data, we just return the
+ // string, otherwise we return an instance of this class.
+ $reader->read();
+
+ $nonText = false;
+ $text = '';
+
+ while (true) {
+ switch ($reader->nodeType) {
+ case Reader::ELEMENT:
+ $nonText = true;
+ $reader->next();
+ continue 2;
+ case Reader::TEXT:
+ case Reader::CDATA:
+ $text .= $reader->value;
+ break;
+ case Reader::END_ELEMENT:
+ break 2;
+ }
+ $reader->read();
+ }
+
+ // Make sure we advance the cursor one step further.
+ $reader->read();
+
+ if ($nonText) {
+ $new = new self($xml);
+
+ return $new;
+ } else {
+ return $text;
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/GetLastModified.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/GetLastModified.php
new file mode 100644
index 000000000..c6f6d421c
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/GetLastModified.php
@@ -0,0 +1,104 @@
+time = clone $time;
+ } else {
+ $this->time = new DateTime('@'.$time);
+ }
+
+ // Setting timezone to UTC
+ $this->time->setTimezone(new DateTimeZone('UTC'));
+ }
+
+ /**
+ * getTime.
+ *
+ * @return DateTime
+ */
+ public function getTime()
+ {
+ return $this->time;
+ }
+
+ /**
+ * 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)
+ {
+ $writer->write(
+ HTTP\toDate($this->time)
+ );
+ }
+
+ /**
+ * 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)
+ {
+ return
+ new self(new DateTime($reader->parseInnerTree()));
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php
new file mode 100644
index 000000000..0ed14dcf3
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php
@@ -0,0 +1,156 @@
+hrefs = $hrefs;
+ }
+
+ /**
+ * Returns the first Href.
+ *
+ * @return string|null
+ */
+ public function getHref()
+ {
+ return $this->hrefs[0] ?? null;
+ }
+
+ /**
+ * Returns the hrefs as an array.
+ *
+ * @return array
+ */
+ public function getHrefs()
+ {
+ return $this->hrefs;
+ }
+
+ /**
+ * 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->getHrefs() as $href) {
+ $href = Uri\resolve($writer->contextUri, $href);
+ $writer->writeElement('{DAV:}href', $href);
+ }
+ }
+
+ /**
+ * Generate html representation for this value.
+ *
+ * The html output is 100% trusted, and no effort is being made to sanitize
+ * it. It's up to the implementor to sanitize user provided values.
+ *
+ * The output must be in UTF-8.
+ *
+ * The baseUri parameter is a url to the root of the application, and can
+ * be used to construct local links.
+ *
+ * @return string
+ */
+ public function toHtml(HtmlOutputHelper $html)
+ {
+ $links = [];
+ foreach ($this->getHrefs() as $href) {
+ $links[] = $html->link($href);
+ }
+
+ return implode(' ', $links);
+ }
+
+ /**
+ * 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)
+ {
+ $hrefs = [];
+ foreach ((array) $reader->parseInnerTree() as $elem) {
+ if ('{DAV:}href' !== $elem['name']) {
+ continue;
+ }
+
+ $hrefs[] = $elem['value'];
+ }
+ if ($hrefs) {
+ return new self($hrefs);
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/Invite.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/Invite.php
new file mode 100644
index 000000000..e3f0a611b
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/Invite.php
@@ -0,0 +1,66 @@
+sharees = $sharees;
+ }
+
+ /**
+ * The xmlSerialize method is called during xml writing.
+ *
+ * Use the $writer argument to write its own xml serialization.
+ *
+ * An important note: do _not_ create a parent element. Any element
+ * implementing 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->sharees as $sharee) {
+ $writer->writeElement('{DAV:}sharee', $sharee);
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/LocalHref.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/LocalHref.php
new file mode 100644
index 000000000..cb794974f
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/LocalHref.php
@@ -0,0 +1,48 @@
+locks = $locks;
+ }
+
+ /**
+ * 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)
+ {
+ foreach ($this->locks as $lock) {
+ $writer->startElement('{DAV:}activelock');
+
+ $writer->startElement('{DAV:}lockscope');
+ if (LockInfo::SHARED === $lock->scope) {
+ $writer->writeElement('{DAV:}shared');
+ } else {
+ $writer->writeElement('{DAV:}exclusive');
+ }
+
+ $writer->endElement(); // {DAV:}lockscope
+
+ $writer->startElement('{DAV:}locktype');
+ $writer->writeElement('{DAV:}write');
+ $writer->endElement(); // {DAV:}locktype
+
+ if (!self::$hideLockRoot) {
+ $writer->startElement('{DAV:}lockroot');
+ $writer->writeElement('{DAV:}href', $writer->contextUri.$lock->uri);
+ $writer->endElement(); // {DAV:}lockroot
+ }
+ $writer->writeElement('{DAV:}depth', (DAV\Server::DEPTH_INFINITY == $lock->depth ? 'infinity' : $lock->depth));
+ $writer->writeElement('{DAV:}timeout', (LockInfo::TIMEOUT_INFINITE === $lock->timeout ? 'Infinite' : 'Second-'.$lock->timeout));
+
+ // optional according to https://tools.ietf.org/html/rfc4918#section-6.5
+ if (null !== $lock->token && '' !== $lock->token) {
+ $writer->startElement('{DAV:}locktoken');
+ $writer->writeElement('{DAV:}href', 'opaquelocktoken:'.$lock->token);
+ $writer->endElement(); // {DAV:}locktoken
+ }
+
+ if ($lock->owner) {
+ $writer->writeElement('{DAV:}owner', new XmlFragment($lock->owner));
+ }
+ $writer->endElement(); // {DAV:}activelock
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/ResourceType.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/ResourceType.php
new file mode 100644
index 000000000..6532b70c9
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/ResourceType.php
@@ -0,0 +1,121 @@
+value;
+ }
+
+ /**
+ * Checks if the principal contains a certain value.
+ *
+ * @param string $type
+ *
+ * @return bool
+ */
+ public function is($type)
+ {
+ return in_array($type, $this->value);
+ }
+
+ /**
+ * Adds a resourcetype value to this property.
+ *
+ * @param string $type
+ */
+ public function add($type)
+ {
+ $this->value[] = $type;
+ $this->value = array_unique($this->value);
+ }
+
+ /**
+ * 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)
+ {
+ return
+ new self(parent::xmlDeserialize($reader));
+ }
+
+ /**
+ * Generate html representation for this value.
+ *
+ * The html output is 100% trusted, and no effort is being made to sanitize
+ * it. It's up to the implementor to sanitize user provided values.
+ *
+ * The output must be in UTF-8.
+ *
+ * The baseUri parameter is a url to the root of the application, and can
+ * be used to construct local links.
+ *
+ * @return string
+ */
+ public function toHtml(HtmlOutputHelper $html)
+ {
+ return implode(
+ ', ',
+ array_map([$html, 'xmlName'], $this->getValue())
+ );
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/ShareAccess.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/ShareAccess.php
new file mode 100644
index 000000000..fdd55558c
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/ShareAccess.php
@@ -0,0 +1,135 @@
+value = $shareAccess;
+ }
+
+ /**
+ * Returns the current value.
+ *
+ * @return int
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * The xmlSerialize method is called during xml writing.
+ *
+ * Use the $writer argument to write its own xml serialization.
+ *
+ * An important note: do _not_ create a parent element. Any element
+ * implementing 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)
+ {
+ switch ($this->value) {
+ case SharingPlugin::ACCESS_NOTSHARED:
+ $writer->writeElement('{DAV:}not-shared');
+ break;
+ case SharingPlugin::ACCESS_SHAREDOWNER:
+ $writer->writeElement('{DAV:}shared-owner');
+ break;
+ case SharingPlugin::ACCESS_READ:
+ $writer->writeElement('{DAV:}read');
+ break;
+ case SharingPlugin::ACCESS_READWRITE:
+ $writer->writeElement('{DAV:}read-write');
+ break;
+ case SharingPlugin::ACCESS_NOACCESS:
+ $writer->writeElement('{DAV:}no-access');
+ break;
+ }
+ }
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called 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)
+ {
+ $elems = $reader->parseInnerTree();
+ foreach ($elems as $elem) {
+ switch ($elem['name']) {
+ case '{DAV:}not-shared':
+ return new self(SharingPlugin::ACCESS_NOTSHARED);
+ case '{DAV:}shared-owner':
+ return new self(SharingPlugin::ACCESS_SHAREDOWNER);
+ case '{DAV:}read':
+ return new self(SharingPlugin::ACCESS_READ);
+ case '{DAV:}read-write':
+ return new self(SharingPlugin::ACCESS_READWRITE);
+ case '{DAV:}no-access':
+ return new self(SharingPlugin::ACCESS_NOACCESS);
+ }
+ }
+ throw new BadRequest('Invalid value for {DAV:}share-access element');
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedLock.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedLock.php
new file mode 100644
index 000000000..100829c60
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedLock.php
@@ -0,0 +1,52 @@
+writeElement('{DAV:}lockentry', [
+ '{DAV:}lockscope' => ['{DAV:}exclusive' => null],
+ '{DAV:}locktype' => ['{DAV:}write' => null],
+ ]);
+ $writer->writeElement('{DAV:}lockentry', [
+ '{DAV:}lockscope' => ['{DAV:}shared' => null],
+ '{DAV:}locktype' => ['{DAV:}write' => null],
+ ]);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedMethodSet.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedMethodSet.php
new file mode 100644
index 000000000..634401093
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedMethodSet.php
@@ -0,0 +1,114 @@
+methods = $methods;
+ }
+
+ /**
+ * Returns the list of supported http methods.
+ *
+ * @return string[]
+ */
+ public function getValue()
+ {
+ return $this->methods;
+ }
+
+ /**
+ * Returns true or false if the property contains a specific method.
+ *
+ * @param string $methodName
+ *
+ * @return bool
+ */
+ public function has($methodName)
+ {
+ return in_array(
+ $methodName,
+ $this->methods
+ );
+ }
+
+ /**
+ * 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->getValue() as $val) {
+ $writer->startElement('{DAV:}supported-method');
+ $writer->writeAttribute('name', $val);
+ $writer->endElement();
+ }
+ }
+
+ /**
+ * Generate html representation for this value.
+ *
+ * The html output is 100% trusted, and no effort is being made to sanitize
+ * it. It's up to the implementor to sanitize user provided values.
+ *
+ * The output must be in UTF-8.
+ *
+ * The baseUri parameter is a url to the root of the application, and can
+ * be used to construct local links.
+ *
+ * @return string
+ */
+ public function toHtml(HtmlOutputHelper $html)
+ {
+ return implode(
+ ', ',
+ array_map([$html, 'h'], $this->getValue())
+ );
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedReportSet.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedReportSet.php
new file mode 100644
index 000000000..0b4990e96
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedReportSet.php
@@ -0,0 +1,144 @@
+addReport($reports);
+ }
+ }
+
+ /**
+ * Adds a report to this property.
+ *
+ * The report must be a string in clark-notation.
+ * Multiple reports can be specified as an array.
+ *
+ * @param mixed $report
+ */
+ public function addReport($report)
+ {
+ $report = (array) $report;
+
+ foreach ($report as $r) {
+ if (!preg_match('/^{([^}]*)}(.*)$/', $r)) {
+ throw new DAV\Exception('Reportname must be in clark-notation');
+ }
+ $this->reports[] = $r;
+ }
+ }
+
+ /**
+ * Returns the list of supported reports.
+ *
+ * @return string[]
+ */
+ public function getValue()
+ {
+ return $this->reports;
+ }
+
+ /**
+ * Returns true or false if the property contains a specific report.
+ *
+ * @param string $reportName
+ *
+ * @return bool
+ */
+ public function has($reportName)
+ {
+ return in_array(
+ $reportName,
+ $this->reports
+ );
+ }
+
+ /**
+ * 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->getValue() as $val) {
+ $writer->startElement('{DAV:}supported-report');
+ $writer->startElement('{DAV:}report');
+ $writer->writeElement($val);
+ $writer->endElement();
+ $writer->endElement();
+ }
+ }
+
+ /**
+ * Generate html representation for this value.
+ *
+ * The html output is 100% trusted, and no effort is being made to sanitize
+ * it. It's up to the implementor to sanitize user provided values.
+ *
+ * The output must be in UTF-8.
+ *
+ * The baseUri parameter is a url to the root of the application, and can
+ * be used to construct local links.
+ *
+ * @return string
+ */
+ public function toHtml(HtmlOutputHelper $html)
+ {
+ return implode(
+ ', ',
+ array_map([$html, 'xmlName'], $this->getValue())
+ );
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Request/Lock.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Request/Lock.php
new file mode 100644
index 000000000..57d12ef93
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Request/Lock.php
@@ -0,0 +1,84 @@
+next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @return mixed
+ */
+ public static function xmlDeserialize(Reader $reader)
+ {
+ $reader->pushContext();
+ $reader->elementMap['{DAV:}owner'] = 'Sabre\\Xml\\Element\\XmlFragment';
+
+ $values = KeyValue::xmlDeserialize($reader);
+
+ $reader->popContext();
+
+ $new = new self();
+ $new->owner = !empty($values['{DAV:}owner']) ? $values['{DAV:}owner']->getXml() : null;
+ $new->scope = LockInfo::SHARED;
+
+ if (isset($values['{DAV:}lockscope'])) {
+ foreach ($values['{DAV:}lockscope'] as $elem) {
+ if ('{DAV:}exclusive' === $elem['name']) {
+ $new->scope = LockInfo::EXCLUSIVE;
+ }
+ }
+ }
+
+ return $new;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Request/MkCol.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Request/MkCol.php
new file mode 100644
index 000000000..e0d7e90a2
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Request/MkCol.php
@@ -0,0 +1,80 @@
+value array with properties that are supposed to get set
+ * during creation of the new collection.
+ *
+ * @return array
+ */
+ public function getProperties()
+ {
+ return $this->properties;
+ }
+
+ /**
+ * 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)
+ {
+ $self = new self();
+
+ $elementMap = $reader->elementMap;
+ $elementMap['{DAV:}prop'] = 'Sabre\DAV\Xml\Element\Prop';
+ $elementMap['{DAV:}set'] = 'Sabre\Xml\Element\KeyValue';
+ $elementMap['{DAV:}remove'] = 'Sabre\Xml\Element\KeyValue';
+
+ $elems = $reader->parseInnerTree($elementMap);
+
+ foreach ($elems as $elem) {
+ if ('{DAV:}set' === $elem['name']) {
+ $self->properties = array_merge($self->properties, $elem['value']['{DAV:}prop']);
+ }
+ }
+
+ return $self;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Request/PropFind.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Request/PropFind.php
new file mode 100644
index 000000000..505e7c79c
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Request/PropFind.php
@@ -0,0 +1,79 @@
+next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @return mixed
+ */
+ public static function xmlDeserialize(Reader $reader)
+ {
+ $self = new self();
+
+ $reader->pushContext();
+ $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Element\Elements';
+
+ foreach (KeyValue::xmlDeserialize($reader) as $k => $v) {
+ switch ($k) {
+ case '{DAV:}prop':
+ $self->properties = $v;
+ break;
+ case '{DAV:}allprop':
+ $self->allProp = true;
+ }
+ }
+
+ $reader->popContext();
+
+ return $self;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Request/PropPatch.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Request/PropPatch.php
new file mode 100644
index 000000000..4a2709502
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Request/PropPatch.php
@@ -0,0 +1,109 @@
+properties as $propertyName => $propertyValue) {
+ if (is_null($propertyValue)) {
+ $writer->startElement('{DAV:}remove');
+ $writer->write(['{DAV:}prop' => [$propertyName => $propertyValue]]);
+ $writer->endElement();
+ } else {
+ $writer->startElement('{DAV:}set');
+ $writer->write(['{DAV:}prop' => [$propertyName => $propertyValue]]);
+ $writer->endElement();
+ }
+ }
+ }
+
+ /**
+ * 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)
+ {
+ $self = new self();
+
+ $elementMap = $reader->elementMap;
+ $elementMap['{DAV:}prop'] = 'Sabre\DAV\Xml\Element\Prop';
+ $elementMap['{DAV:}set'] = 'Sabre\Xml\Element\KeyValue';
+ $elementMap['{DAV:}remove'] = 'Sabre\Xml\Element\KeyValue';
+
+ $elems = $reader->parseInnerTree($elementMap);
+
+ foreach ($elems as $elem) {
+ if ('{DAV:}set' === $elem['name']) {
+ $self->properties = array_merge($self->properties, $elem['value']['{DAV:}prop']);
+ }
+ if ('{DAV:}remove' === $elem['name']) {
+ // Ensuring there are no values.
+ foreach ($elem['value']['{DAV:}prop'] as $remove => $value) {
+ $self->properties[$remove] = null;
+ }
+ }
+ }
+
+ return $self;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Request/ShareResource.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Request/ShareResource.php
new file mode 100644
index 000000000..79d7dc826
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Request/ShareResource.php
@@ -0,0 +1,80 @@
+sharees = $sharees;
+ }
+
+ /**
+ * 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)
+ {
+ $elems = $reader->parseInnerTree([
+ '{DAV:}sharee' => 'Sabre\DAV\Xml\Element\Sharee',
+ '{DAV:}share-access' => 'Sabre\DAV\Xml\Property\ShareAccess',
+ '{DAV:}prop' => 'Sabre\Xml\Deserializer\keyValue',
+ ]);
+
+ $sharees = [];
+
+ foreach ($elems as $elem) {
+ if ('{DAV:}sharee' !== $elem['name']) {
+ continue;
+ }
+ $sharees[] = $elem['value'];
+ }
+
+ return new self($sharees);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Request/SyncCollectionReport.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Request/SyncCollectionReport.php
new file mode 100644
index 000000000..8dd95765b
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Request/SyncCollectionReport.php
@@ -0,0 +1,118 @@
+next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @return mixed
+ */
+ public static function xmlDeserialize(Reader $reader)
+ {
+ $self = new self();
+
+ $reader->pushContext();
+
+ $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Element\Elements';
+ $elems = KeyValue::xmlDeserialize($reader);
+
+ $reader->popContext();
+
+ $required = [
+ '{DAV:}sync-token',
+ '{DAV:}prop',
+ ];
+
+ foreach ($required as $elem) {
+ if (!array_key_exists($elem, $elems)) {
+ throw new BadRequest('The '.$elem.' element in the {DAV:}sync-collection report is required');
+ }
+ }
+
+ $self->properties = $elems['{DAV:}prop'];
+ $self->syncToken = $elems['{DAV:}sync-token'];
+
+ if (isset($elems['{DAV:}limit'])) {
+ $nresults = null;
+ foreach ($elems['{DAV:}limit'] as $child) {
+ if ('{DAV:}nresults' === $child['name']) {
+ $nresults = (int) $child['value'];
+ }
+ }
+ $self->limit = $nresults;
+ }
+
+ if (isset($elems['{DAV:}sync-level'])) {
+ $value = $elems['{DAV:}sync-level'];
+ if ('infinity' === $value) {
+ $value = \Sabre\DAV\Server::DEPTH_INFINITY;
+ }
+ $self->syncLevel = $value;
+ }
+
+ return $self;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Response/MultiStatus.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Response/MultiStatus.php
new file mode 100644
index 000000000..e824cda42
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Response/MultiStatus.php
@@ -0,0 +1,136 @@
+responses = $responses;
+ $this->syncToken = $syncToken;
+ }
+
+ /**
+ * Returns the response list.
+ *
+ * @return \Sabre\DAV\Xml\Element\Response[]
+ */
+ public function getResponses()
+ {
+ return $this->responses;
+ }
+
+ /**
+ * Returns the sync-token, if available.
+ *
+ * @return string|null
+ */
+ public function getSyncToken()
+ {
+ return $this->syncToken;
+ }
+
+ /**
+ * 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)
+ {
+ foreach ($this->getResponses() as $response) {
+ $writer->writeElement('{DAV:}response', $response);
+ }
+ if ($syncToken = $this->getSyncToken()) {
+ $writer->writeElement('{DAV:}sync-token', $syncToken);
+ }
+ }
+
+ /**
+ * 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)
+ {
+ $elementMap = $reader->elementMap;
+ $elementMap['{DAV:}prop'] = 'Sabre\\DAV\\Xml\\Element\\Prop';
+ $elements = $reader->parseInnerTree($elementMap);
+
+ $responses = [];
+ $syncToken = null;
+
+ if ($elements) {
+ foreach ($elements as $elem) {
+ if ('{DAV:}response' === $elem['name']) {
+ $responses[] = $elem['value'];
+ }
+ if ('{DAV:}sync-token' === $elem['name']) {
+ $syncToken = $elem['value'];
+ }
+ }
+ }
+
+ return new self($responses, $syncToken);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Service.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Service.php
new file mode 100644
index 000000000..4406b022d
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAV/Xml/Service.php
@@ -0,0 +1,47 @@
+ 'Sabre\\DAV\\Xml\\Response\\MultiStatus',
+ '{DAV:}response' => 'Sabre\\DAV\\Xml\\Element\\Response',
+
+ // Requests
+ '{DAV:}propfind' => 'Sabre\\DAV\\Xml\\Request\\PropFind',
+ '{DAV:}propertyupdate' => 'Sabre\\DAV\\Xml\\Request\\PropPatch',
+ '{DAV:}mkcol' => 'Sabre\\DAV\\Xml\\Request\\MkCol',
+
+ // Properties
+ '{DAV:}resourcetype' => 'Sabre\\DAV\\Xml\\Property\\ResourceType',
+ ];
+
+ /**
+ * This is a default list of namespaces.
+ *
+ * If you are defining your own custom namespace, add it here to reduce
+ * bandwidth and improve legibility of xml bodies.
+ *
+ * @var array
+ */
+ public $namespaceMap = [
+ 'DAV:' => 'd',
+ 'http://sabredav.org/ns' => 's',
+ ];
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/ACLTrait.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/ACLTrait.php
new file mode 100644
index 000000000..98c1ce338
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/ACLTrait.php
@@ -0,0 +1,94 @@
+ '{DAV:}all',
+ 'principal' => '{DAV:}owner',
+ 'protected' => true,
+ ],
+ ];
+ }
+
+ /**
+ * Updates the ACL.
+ *
+ * This method will receive a list of new ACE's as an array argument.
+ */
+ public function setACL(array $acl)
+ {
+ throw new \Sabre\DAV\Exception\Forbidden('Setting ACL is not supported on this node');
+ }
+
+ /**
+ * Returns the list of supported privileges for this node.
+ *
+ * The returned data structure is a list of nested privileges.
+ * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
+ * standard structure.
+ *
+ * If null is returned from this method, the default privilege set is used,
+ * which is fine for most common usecases.
+ *
+ * @return array|null
+ */
+ public function getSupportedPrivilegeSet()
+ {
+ return null;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php
new file mode 100644
index 000000000..d26f7d27b
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php
@@ -0,0 +1,178 @@
+principalPrefix = $principalPrefix;
+ $this->principalBackend = $principalBackend;
+ }
+
+ /**
+ * 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 DAV\INode
+ */
+ abstract public function getChildForPrincipal(array $principalInfo);
+
+ /**
+ * Returns the name of this collection.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ list(, $name) = Uri\split($this->principalPrefix);
+
+ return $name;
+ }
+
+ /**
+ * Return the list of users.
+ *
+ * @return array
+ */
+ public function getChildren()
+ {
+ if ($this->disableListing) {
+ throw new DAV\Exception\MethodNotAllowed('Listing members of this collection is disabled');
+ }
+ $children = [];
+ foreach ($this->principalBackend->getPrincipalsByPrefix($this->principalPrefix) as $principalInfo) {
+ $children[] = $this->getChildForPrincipal($principalInfo);
+ }
+
+ return $children;
+ }
+
+ /**
+ * Returns a child object, by its name.
+ *
+ * @param string $name
+ *
+ * @throws DAV\Exception\NotFound
+ *
+ * @return DAV\INode
+ */
+ public function getChild($name)
+ {
+ $principalInfo = $this->principalBackend->getPrincipalByPath($this->principalPrefix.'/'.$name);
+ if (!$principalInfo) {
+ throw new DAV\Exception\NotFound('Principal with name '.$name.' not found');
+ }
+
+ return $this->getChildForPrincipal($principalInfo);
+ }
+
+ /**
+ * This method is used to search for principals matching a set of
+ * properties.
+ *
+ * This search is specifically used by RFC3744's principal-property-search
+ * REPORT. You should at least allow searching on
+ * http://sabredav.org/ns}email-address.
+ *
+ * The actual search should be a unicode-non-case-sensitive search. The
+ * keys in searchProperties are the WebDAV property names, while the values
+ * are the property values to search on.
+ *
+ * By default, if multiple properties are submitted to this method, the
+ * various properties should be combined with 'AND'. If $test is set to
+ * 'anyof', it should be combined using 'OR'.
+ *
+ * This method should simply return a list of 'child names', which may be
+ * used to call $this->getChild in the future.
+ *
+ * @param string $test
+ *
+ * @return array
+ */
+ public function searchPrincipals(array $searchProperties, $test = 'allof')
+ {
+ $result = $this->principalBackend->searchPrincipals($this->principalPrefix, $searchProperties, $test);
+ $r = [];
+
+ foreach ($result as $row) {
+ list(, $r[]) = Uri\split($row);
+ }
+
+ return $r;
+ }
+
+ /**
+ * Finds a principal by its URI.
+ *
+ * This method may receive any type of uri, but mailto: addresses will be
+ * the most common.
+ *
+ * Implementation of this API is optional. It is currently used by the
+ * CalDAV system to find principals based on their email addresses. If this
+ * API is not implemented, some features may not work correctly.
+ *
+ * This method must return a relative principal path, or null, if the
+ * principal was not found or you refuse to find it.
+ *
+ * @param string $uri
+ *
+ * @return string
+ */
+ public function findByUri($uri)
+ {
+ return $this->principalBackend->findByUri($uri, $this->principalPrefix);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Exception/AceConflict.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Exception/AceConflict.php
new file mode 100644
index 000000000..0fc3f778d
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Exception/AceConflict.php
@@ -0,0 +1,31 @@
+ownerDocument;
+
+ $np = $doc->createElementNS('DAV:', 'd:no-ace-conflict');
+ $errorNode->appendChild($np);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Exception/NeedPrivileges.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Exception/NeedPrivileges.php
new file mode 100644
index 000000000..af1f01c2b
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Exception/NeedPrivileges.php
@@ -0,0 +1,73 @@
+uri = $uri;
+ $this->privileges = $privileges;
+
+ parent::__construct('User did not have the required privileges ('.implode(',', $privileges).') for path "'.$uri.'"');
+ }
+
+ /**
+ * Adds in extra information in the xml response.
+ *
+ * This method adds the {DAV:}need-privileges element as defined in rfc3744
+ */
+ public function serialize(DAV\Server $server, \DOMElement $errorNode)
+ {
+ $doc = $errorNode->ownerDocument;
+
+ $np = $doc->createElementNS('DAV:', 'd:need-privileges');
+ $errorNode->appendChild($np);
+
+ foreach ($this->privileges as $privilege) {
+ $resource = $doc->createElementNS('DAV:', 'd:resource');
+ $np->appendChild($resource);
+
+ $resource->appendChild($doc->createElementNS('DAV:', 'd:href', $server->getBaseUri().$this->uri));
+
+ $priv = $doc->createElementNS('DAV:', 'd:privilege');
+ $resource->appendChild($priv);
+
+ preg_match('/^{([^}]*)}(.*)$/', $privilege, $privilegeParts);
+ $priv->appendChild($doc->createElementNS($privilegeParts[1], 'd:'.$privilegeParts[2]));
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Exception/NoAbstract.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Exception/NoAbstract.php
new file mode 100644
index 000000000..b9c66169e
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Exception/NoAbstract.php
@@ -0,0 +1,31 @@
+ownerDocument;
+
+ $np = $doc->createElementNS('DAV:', 'd:no-abstract');
+ $errorNode->appendChild($np);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Exception/NotRecognizedPrincipal.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Exception/NotRecognizedPrincipal.php
new file mode 100644
index 000000000..d4e728497
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Exception/NotRecognizedPrincipal.php
@@ -0,0 +1,31 @@
+ownerDocument;
+
+ $np = $doc->createElementNS('DAV:', 'd:recognized-principal');
+ $errorNode->appendChild($np);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Exception/NotSupportedPrivilege.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Exception/NotSupportedPrivilege.php
new file mode 100644
index 000000000..c04c5faa2
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Exception/NotSupportedPrivilege.php
@@ -0,0 +1,31 @@
+ownerDocument;
+
+ $np = $doc->createElementNS('DAV:', 'd:not-supported-privilege');
+ $errorNode->appendChild($np);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/FS/Collection.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/FS/Collection.php
new file mode 100644
index 000000000..85b04e2bd
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/FS/Collection.php
@@ -0,0 +1,109 @@
+acl = $acl;
+ $this->owner = $owner;
+ }
+
+ /**
+ * Returns a specific child node, referenced by its name.
+ *
+ * This method must throw Sabre\DAV\Exception\NotFound if the node does not
+ * exist.
+ *
+ * @param string $name
+ *
+ * @throws NotFound
+ *
+ * @return \Sabre\DAV\INode
+ */
+ public function getChild($name)
+ {
+ $path = $this->path.'/'.$name;
+
+ if (!file_exists($path)) {
+ throw new NotFound('File could not be located');
+ }
+ if ('.' == $name || '..' == $name) {
+ throw new Forbidden('Permission denied to . and ..');
+ }
+ if (is_dir($path)) {
+ return new self($path, $this->acl, $this->owner);
+ } else {
+ return new File($path, $this->acl, $this->owner);
+ }
+ }
+
+ /**
+ * 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->owner;
+ }
+
+ /**
+ * 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 $this->acl;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/FS/File.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/FS/File.php
new file mode 100644
index 000000000..5506aa2c1
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/FS/File.php
@@ -0,0 +1,78 @@
+acl = $acl;
+ $this->owner = $owner;
+ }
+
+ /**
+ * 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->owner;
+ }
+
+ /**
+ * 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 $this->acl;
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php
new file mode 100644
index 000000000..fa476e094
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php
@@ -0,0 +1,123 @@
+storagePath = $storagePath;
+ }
+
+ /**
+ * Returns the name of the node.
+ *
+ * This is used to generate the url.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->collectionName;
+ }
+
+ /**
+ * Returns a principals' collection of files.
+ *
+ * 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 $principalInfo)
+ {
+ $owner = $principalInfo['uri'];
+ $acl = [
+ [
+ 'privilege' => '{DAV:}all',
+ 'principal' => '{DAV:}owner',
+ 'protected' => true,
+ ],
+ ];
+
+ list(, $principalBaseName) = Uri\split($owner);
+
+ $path = $this->storagePath.'/'.$principalBaseName;
+
+ if (!is_dir($path)) {
+ mkdir($path, 0777, true);
+ }
+
+ return new Collection(
+ $path,
+ $acl,
+ $owner
+ );
+ }
+
+ /**
+ * 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 [
+ [
+ 'principal' => '{DAV:}authenticated',
+ 'privilege' => '{DAV:}read',
+ 'protected' => true,
+ ],
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/IACL.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/IACL.php
new file mode 100644
index 000000000..291fb24ae
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/IACL.php
@@ -0,0 +1,72 @@
+getChild in the future.
+ *
+ * @param string $test
+ *
+ * @return array
+ */
+ public function searchPrincipals(array $searchProperties, $test = 'allof');
+
+ /**
+ * Finds a principal by its URI.
+ *
+ * This method may receive any type of uri, but mailto: addresses will be
+ * the most common.
+ *
+ * Implementation of this API is optional. It is currently used by the
+ * CalDAV system to find principals based on their email addresses. If this
+ * API is not implemented, some features may not work correctly.
+ *
+ * This method must return a relative principal path, or null, if the
+ * principal was not found or you refuse to find it.
+ *
+ * @param string $uri
+ *
+ * @return string
+ */
+ public function findByUri($uri);
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Plugin.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Plugin.php
new file mode 100644
index 000000000..6f071927d
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Plugin.php
@@ -0,0 +1,1549 @@
+ 'Display name',
+ '{http://sabredav.org/ns}email-address' => 'Email address',
+ ];
+
+ /**
+ * Any principal uri's added here, will automatically be added to the list
+ * of ACL's. They will effectively receive {DAV:}all privileges, as a
+ * protected privilege.
+ *
+ * @var array
+ */
+ public $adminPrincipals = [];
+
+ /**
+ * The ACL plugin allows privileges to be assigned to users that are not
+ * logged in. To facilitate that, it modifies the auth plugin's behavior
+ * to only require login when a privileged operation was denied.
+ *
+ * Unauthenticated access can be considered a security concern, so it's
+ * possible to turn this feature off to harden the server's security.
+ *
+ * @var bool
+ */
+ public $allowUnauthenticatedAccess = true;
+
+ /**
+ * Returns a list of features added by this plugin.
+ *
+ * This list is used in the response of a HTTP OPTIONS request.
+ *
+ * @return array
+ */
+ public function getFeatures()
+ {
+ return ['access-control', 'calendarserver-principal-property-search'];
+ }
+
+ /**
+ * Returns a list of available methods for a given url.
+ *
+ * @param string $uri
+ *
+ * @return array
+ */
+ public function getMethods($uri)
+ {
+ return ['ACL'];
+ }
+
+ /**
+ * 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 'acl';
+ }
+
+ /**
+ * Returns a list of reports this plugin supports.
+ *
+ * This will be used in the {DAV:}supported-report-set property.
+ * Note that you still need to subscribe to the 'report' event to actually
+ * implement them
+ *
+ * @param string $uri
+ *
+ * @return array
+ */
+ public function getSupportedReportSet($uri)
+ {
+ return [
+ '{DAV:}expand-property',
+ '{DAV:}principal-match',
+ '{DAV:}principal-property-search',
+ '{DAV:}principal-search-property-set',
+ ];
+ }
+
+ /**
+ * Checks if the current user has the specified privilege(s).
+ *
+ * You can specify a single privilege, or a list of privileges.
+ * This method will throw an exception if the privilege is not available
+ * and return true otherwise.
+ *
+ * @param string $uri
+ * @param array|string $privileges
+ * @param int $recursion
+ * @param bool $throwExceptions if set to false, this method won't throw exceptions
+ *
+ * @throws NeedPrivileges
+ * @throws NotAuthenticated
+ *
+ * @return bool
+ */
+ public function checkPrivileges($uri, $privileges, $recursion = self::R_PARENT, $throwExceptions = true)
+ {
+ if (!is_array($privileges)) {
+ $privileges = [$privileges];
+ }
+
+ $acl = $this->getCurrentUserPrivilegeSet($uri);
+
+ $failed = [];
+ foreach ($privileges as $priv) {
+ if (!in_array($priv, $acl)) {
+ $failed[] = $priv;
+ }
+ }
+
+ if ($failed) {
+ if ($this->allowUnauthenticatedAccess && is_null($this->getCurrentUserPrincipal())) {
+ // We are not authenticated. Kicking in the Auth plugin.
+ $authPlugin = $this->server->getPlugin('auth');
+ $reasons = $authPlugin->getLoginFailedReasons();
+ $authPlugin->challenge(
+ $this->server->httpRequest,
+ $this->server->httpResponse
+ );
+ throw new NotAuthenticated(implode(', ', $reasons).'. Login was needed for privilege: '.implode(', ', $failed).' on '.$uri);
+ }
+ if ($throwExceptions) {
+ throw new NeedPrivileges($uri, $failed);
+ } else {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the standard users' principal.
+ *
+ * This is one authoritative principal url for the current user.
+ * This method will return null if the user wasn't logged in.
+ *
+ * @return string|null
+ */
+ public function getCurrentUserPrincipal()
+ {
+ /** @var $authPlugin \Sabre\DAV\Auth\Plugin */
+ $authPlugin = $this->server->getPlugin('auth');
+ if (!$authPlugin) {
+ return null;
+ }
+
+ return $authPlugin->getCurrentPrincipal();
+ }
+
+ /**
+ * Returns a list of principals that's associated to the current
+ * user, either directly or through group membership.
+ *
+ * @return array
+ */
+ public function getCurrentUserPrincipals()
+ {
+ $currentUser = $this->getCurrentUserPrincipal();
+
+ if (is_null($currentUser)) {
+ return [];
+ }
+
+ return array_merge(
+ [$currentUser],
+ $this->getPrincipalMembership($currentUser)
+ );
+ }
+
+ /**
+ * Sets the default ACL rules.
+ *
+ * These rules are used for all nodes that don't implement the IACL interface.
+ */
+ public function setDefaultAcl(array $acl)
+ {
+ $this->defaultAcl = $acl;
+ }
+
+ /**
+ * Returns the default ACL rules.
+ *
+ * These rules are used for all nodes that don't implement the IACL interface.
+ *
+ * @return array
+ */
+ public function getDefaultAcl()
+ {
+ return $this->defaultAcl;
+ }
+
+ /**
+ * The default ACL rules.
+ *
+ * These rules are used for nodes that don't implement IACL. These default
+ * set of rules allow anyone to do anything, as long as they are
+ * authenticated.
+ *
+ * @var array
+ */
+ protected $defaultAcl = [
+ [
+ 'principal' => '{DAV:}authenticated',
+ 'protected' => true,
+ 'privilege' => '{DAV:}all',
+ ],
+ ];
+
+ /**
+ * This array holds a cache for all the principals that are associated with
+ * a single principal.
+ *
+ * @var array
+ */
+ protected $principalMembershipCache = [];
+
+ /**
+ * Returns all the principal groups the specified principal is a member of.
+ *
+ * @param string $mainPrincipal
+ *
+ * @return array
+ */
+ public function getPrincipalMembership($mainPrincipal)
+ {
+ // First check our cache
+ if (isset($this->principalMembershipCache[$mainPrincipal])) {
+ return $this->principalMembershipCache[$mainPrincipal];
+ }
+
+ $check = [$mainPrincipal];
+ $principals = [];
+
+ while (count($check)) {
+ $principal = array_shift($check);
+
+ $node = $this->server->tree->getNodeForPath($principal);
+ if ($node instanceof IPrincipal) {
+ foreach ($node->getGroupMembership() as $groupMember) {
+ if (!in_array($groupMember, $principals)) {
+ $check[] = $groupMember;
+ $principals[] = $groupMember;
+ }
+ }
+ }
+ }
+
+ // Store the result in the cache
+ $this->principalMembershipCache[$mainPrincipal] = $principals;
+
+ return $principals;
+ }
+
+ /**
+ * Find out of a principal equals another principal.
+ *
+ * This is a quick way to find out whether a principal URI is part of a
+ * group, or any subgroups.
+ *
+ * The first argument is the principal URI you want to check against. For
+ * example the principal group, and the second argument is the principal of
+ * which you want to find out of it is the same as the first principal, or
+ * in a member of the first principal's group or subgroups.
+ *
+ * So the arguments are not interchangeable. If principal A is in group B,
+ * passing 'B', 'A' will yield true, but 'A', 'B' is false.
+ *
+ * If the second argument is not passed, we will use the current user
+ * principal.
+ *
+ * @param string $checkPrincipal
+ * @param string $currentPrincipal
+ *
+ * @return bool
+ */
+ public function principalMatchesPrincipal($checkPrincipal, $currentPrincipal = null)
+ {
+ if (is_null($currentPrincipal)) {
+ $currentPrincipal = $this->getCurrentUserPrincipal();
+ }
+ if ($currentPrincipal === $checkPrincipal) {
+ return true;
+ }
+ if (is_null($currentPrincipal)) {
+ return false;
+ }
+
+ return in_array(
+ $checkPrincipal,
+ $this->getPrincipalMembership($currentPrincipal)
+ );
+ }
+
+ /**
+ * Returns a tree of supported privileges for a resource.
+ *
+ * The returned array structure should be in this form:
+ *
+ * [
+ * [
+ * 'privilege' => '{DAV:}read',
+ * 'abstract' => false,
+ * 'aggregates' => []
+ * ]
+ * ]
+ *
+ * Privileges can be nested using "aggregates". Doing so means that
+ * if you assign someone the aggregating privilege, all the
+ * sub-privileges will automatically be granted.
+ *
+ * Marking a privilege as abstract means that the privilege cannot be
+ * directly assigned, but must be assigned via the parent privilege.
+ *
+ * So a more complex version might look like this:
+ *
+ * [
+ * [
+ * 'privilege' => '{DAV:}read',
+ * 'abstract' => false,
+ * 'aggregates' => [
+ * [
+ * 'privilege' => '{DAV:}read-acl',
+ * 'abstract' => false,
+ * 'aggregates' => [],
+ * ]
+ * ]
+ * ]
+ * ]
+ *
+ * @param string|INode $node
+ *
+ * @return array
+ */
+ public function getSupportedPrivilegeSet($node)
+ {
+ if (is_string($node)) {
+ $node = $this->server->tree->getNodeForPath($node);
+ }
+
+ $supportedPrivileges = null;
+ if ($node instanceof IACL) {
+ $supportedPrivileges = $node->getSupportedPrivilegeSet();
+ }
+
+ if (is_null($supportedPrivileges)) {
+ // Default
+ $supportedPrivileges = [
+ '{DAV:}read' => [
+ 'abstract' => false,
+ 'aggregates' => [
+ '{DAV:}read-acl' => [
+ 'abstract' => false,
+ 'aggregates' => [],
+ ],
+ '{DAV:}read-current-user-privilege-set' => [
+ 'abstract' => false,
+ 'aggregates' => [],
+ ],
+ ],
+ ],
+ '{DAV:}write' => [
+ 'abstract' => false,
+ 'aggregates' => [
+ '{DAV:}write-properties' => [
+ 'abstract' => false,
+ 'aggregates' => [],
+ ],
+ '{DAV:}write-content' => [
+ 'abstract' => false,
+ 'aggregates' => [],
+ ],
+ '{DAV:}unlock' => [
+ 'abstract' => false,
+ 'aggregates' => [],
+ ],
+ ],
+ ],
+ ];
+ if ($node instanceof DAV\ICollection) {
+ $supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}bind'] = [
+ 'abstract' => false,
+ 'aggregates' => [],
+ ];
+ $supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}unbind'] = [
+ 'abstract' => false,
+ 'aggregates' => [],
+ ];
+ }
+ if ($node instanceof IACL) {
+ $supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}write-acl'] = [
+ 'abstract' => false,
+ 'aggregates' => [],
+ ];
+ }
+ }
+
+ $this->server->emit(
+ 'getSupportedPrivilegeSet',
+ [$node, &$supportedPrivileges]
+ );
+
+ return $supportedPrivileges;
+ }
+
+ /**
+ * Returns the supported privilege set as a flat list.
+ *
+ * This is much easier to parse.
+ *
+ * The returned list will be index by privilege name.
+ * The value is a struct containing the following properties:
+ * - aggregates
+ * - abstract
+ * - concrete
+ *
+ * @param string|INode $node
+ *
+ * @return array
+ */
+ final public function getFlatPrivilegeSet($node)
+ {
+ $privs = [
+ 'abstract' => false,
+ 'aggregates' => $this->getSupportedPrivilegeSet($node),
+ ];
+
+ $fpsTraverse = null;
+ $fpsTraverse = function ($privName, $privInfo, $concrete, &$flat) use (&$fpsTraverse) {
+ $myPriv = [
+ 'privilege' => $privName,
+ 'abstract' => isset($privInfo['abstract']) && $privInfo['abstract'],
+ 'aggregates' => [],
+ 'concrete' => isset($privInfo['abstract']) && $privInfo['abstract'] ? $concrete : $privName,
+ ];
+
+ if (isset($privInfo['aggregates'])) {
+ foreach ($privInfo['aggregates'] as $subPrivName => $subPrivInfo) {
+ $myPriv['aggregates'][] = $subPrivName;
+ }
+ }
+
+ $flat[$privName] = $myPriv;
+
+ if (isset($privInfo['aggregates'])) {
+ foreach ($privInfo['aggregates'] as $subPrivName => $subPrivInfo) {
+ $fpsTraverse($subPrivName, $subPrivInfo, $myPriv['concrete'], $flat);
+ }
+ }
+ };
+
+ $flat = [];
+ $fpsTraverse('{DAV:}all', $privs, null, $flat);
+
+ return $flat;
+ }
+
+ /**
+ * Returns the full ACL list.
+ *
+ * Either a uri or a INode may be passed.
+ *
+ * null will be returned if the node doesn't support ACLs.
+ *
+ * @param string|DAV\INode $node
+ *
+ * @return array
+ */
+ public function getAcl($node)
+ {
+ if (is_string($node)) {
+ $node = $this->server->tree->getNodeForPath($node);
+ }
+ if (!$node instanceof IACL) {
+ return $this->getDefaultAcl();
+ }
+ $acl = $node->getACL();
+ foreach ($this->adminPrincipals as $adminPrincipal) {
+ $acl[] = [
+ 'principal' => $adminPrincipal,
+ 'privilege' => '{DAV:}all',
+ 'protected' => true,
+ ];
+ }
+
+ return $acl;
+ }
+
+ /**
+ * Returns a list of privileges the current user has
+ * on a particular node.
+ *
+ * Either a uri or a DAV\INode may be passed.
+ *
+ * null will be returned if the node doesn't support ACLs.
+ *
+ * @param string|DAV\INode $node
+ *
+ * @return array
+ */
+ public function getCurrentUserPrivilegeSet($node)
+ {
+ if (is_string($node)) {
+ $node = $this->server->tree->getNodeForPath($node);
+ }
+
+ $acl = $this->getACL($node);
+
+ $collected = [];
+
+ $isAuthenticated = null !== $this->getCurrentUserPrincipal();
+
+ foreach ($acl as $ace) {
+ $principal = $ace['principal'];
+
+ switch ($principal) {
+ case '{DAV:}owner':
+ $owner = $node->getOwner();
+ if ($owner && $this->principalMatchesPrincipal($owner)) {
+ $collected[] = $ace;
+ }
+ break;
+
+ // 'all' matches for every user
+ case '{DAV:}all':
+ $collected[] = $ace;
+ break;
+
+ case '{DAV:}authenticated':
+ // Authenticated users only
+ if ($isAuthenticated) {
+ $collected[] = $ace;
+ }
+ break;
+
+ case '{DAV:}unauthenticated':
+ // Unauthenticated users only
+ if (!$isAuthenticated) {
+ $collected[] = $ace;
+ }
+ break;
+
+ default:
+ if ($this->principalMatchesPrincipal($ace['principal'])) {
+ $collected[] = $ace;
+ }
+ break;
+ }
+ }
+
+ // Now we deduct all aggregated privileges.
+ $flat = $this->getFlatPrivilegeSet($node);
+
+ $collected2 = [];
+ while (count($collected)) {
+ $current = array_pop($collected);
+ $collected2[] = $current['privilege'];
+
+ if (!isset($flat[$current['privilege']])) {
+ // Ignoring privileges that are not in the supported-privileges list.
+ $this->server->getLogger()->debug('A node has the "'.$current['privilege'].'" in its ACL list, but this privilege was not reported in the supportedPrivilegeSet list. This will be ignored.');
+ continue;
+ }
+ foreach ($flat[$current['privilege']]['aggregates'] as $subPriv) {
+ $collected2[] = $subPriv;
+ $collected[] = $flat[$subPriv];
+ }
+ }
+
+ return array_values(array_unique($collected2));
+ }
+
+ /**
+ * Returns a principal based on its uri.
+ *
+ * Returns null if the principal could not be found.
+ *
+ * @param string $uri
+ *
+ * @return string|null
+ */
+ public function getPrincipalByUri($uri)
+ {
+ $result = null;
+ $collections = $this->principalCollectionSet;
+ foreach ($collections as $collection) {
+ try {
+ $principalCollection = $this->server->tree->getNodeForPath($collection);
+ } catch (NotFound $e) {
+ // Ignore and move on
+ continue;
+ }
+
+ if (!$principalCollection instanceof IPrincipalCollection) {
+ // Not a principal collection, we're simply going to ignore
+ // this.
+ continue;
+ }
+
+ $result = $principalCollection->findByUri($uri);
+ if ($result) {
+ return $result;
+ }
+ }
+ }
+
+ /**
+ * Principal property search.
+ *
+ * This method can search for principals matching certain values in
+ * properties.
+ *
+ * This method will return a list of properties for the matched properties.
+ *
+ * @param array $searchProperties The properties to search on. This is a
+ * key-value list. The keys are property
+ * names, and the values the strings to
+ * match them on.
+ * @param array $requestedProperties this is the list of properties to
+ * return for every match
+ * @param string $collectionUri the principal collection to search on.
+ * If this is ommitted, the standard
+ * principal collection-set will be used
+ * @param string $test "allof" to use AND to search the
+ * properties. 'anyof' for OR.
+ *
+ * @return array This method returns an array structure similar to
+ * Sabre\DAV\Server::getPropertiesForPath. Returned
+ * properties are index by a HTTP status code.
+ */
+ public function principalSearch(array $searchProperties, array $requestedProperties, $collectionUri = null, $test = 'allof')
+ {
+ if (!is_null($collectionUri)) {
+ $uris = [$collectionUri];
+ } else {
+ $uris = $this->principalCollectionSet;
+ }
+
+ $lookupResults = [];
+ foreach ($uris as $uri) {
+ $principalCollection = $this->server->tree->getNodeForPath($uri);
+ if (!$principalCollection instanceof IPrincipalCollection) {
+ // Not a principal collection, we're simply going to ignore
+ // this.
+ continue;
+ }
+
+ $results = $principalCollection->searchPrincipals($searchProperties, $test);
+ foreach ($results as $result) {
+ $lookupResults[] = rtrim($uri, '/').'/'.$result;
+ }
+ }
+
+ $matches = [];
+
+ foreach ($lookupResults as $lookupResult) {
+ list($matches[]) = $this->server->getPropertiesForPath($lookupResult, $requestedProperties, 0);
+ }
+
+ return $matches;
+ }
+
+ /**
+ * Sets up the plugin.
+ *
+ * This method is automatically called by the server class.
+ */
+ public function initialize(DAV\Server $server)
+ {
+ if ($this->allowUnauthenticatedAccess) {
+ $authPlugin = $server->getPlugin('auth');
+ if (!$authPlugin) {
+ throw new \Exception('The Auth plugin must be loaded before the ACL plugin if you want to allow unauthenticated access.');
+ }
+ $authPlugin->autoRequireLogin = false;
+ }
+
+ $this->server = $server;
+ $server->on('propFind', [$this, 'propFind'], 20);
+ $server->on('beforeMethod:*', [$this, 'beforeMethod'], 20);
+ $server->on('beforeBind', [$this, 'beforeBind'], 20);
+ $server->on('beforeUnbind', [$this, 'beforeUnbind'], 20);
+ $server->on('propPatch', [$this, 'propPatch']);
+ $server->on('beforeUnlock', [$this, 'beforeUnlock'], 20);
+ $server->on('report', [$this, 'report']);
+ $server->on('method:ACL', [$this, 'httpAcl']);
+ $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
+ $server->on('getPrincipalByUri', function ($principal, &$uri) {
+ $uri = $this->getPrincipalByUri($principal);
+
+ // Break event chain
+ if ($uri) {
+ return false;
+ }
+ });
+
+ array_push($server->protectedProperties,
+ '{DAV:}alternate-URI-set',
+ '{DAV:}principal-URL',
+ '{DAV:}group-membership',
+ '{DAV:}principal-collection-set',
+ '{DAV:}current-user-principal',
+ '{DAV:}supported-privilege-set',
+ '{DAV:}current-user-privilege-set',
+ '{DAV:}acl',
+ '{DAV:}acl-restrictions',
+ '{DAV:}inherited-acl-set',
+ '{DAV:}owner',
+ '{DAV:}group'
+ );
+
+ // Automatically mapping nodes implementing IPrincipal to the
+ // {DAV:}principal resourcetype.
+ $server->resourceTypeMapping['Sabre\\DAVACL\\IPrincipal'] = '{DAV:}principal';
+
+ // Mapping the group-member-set property to the HrefList property
+ // class.
+ $server->xml->elementMap['{DAV:}group-member-set'] = 'Sabre\\DAV\\Xml\\Property\\Href';
+ $server->xml->elementMap['{DAV:}acl'] = 'Sabre\\DAVACL\\Xml\\Property\\Acl';
+ $server->xml->elementMap['{DAV:}acl-principal-prop-set'] = 'Sabre\\DAVACL\\Xml\\Request\\AclPrincipalPropSetReport';
+ $server->xml->elementMap['{DAV:}expand-property'] = 'Sabre\\DAVACL\\Xml\\Request\\ExpandPropertyReport';
+ $server->xml->elementMap['{DAV:}principal-property-search'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalPropertySearchReport';
+ $server->xml->elementMap['{DAV:}principal-search-property-set'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalSearchPropertySetReport';
+ $server->xml->elementMap['{DAV:}principal-match'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalMatchReport';
+ }
+
+ /* {{{ Event handlers */
+
+ /**
+ * Triggered before any method is handled.
+ */
+ public function beforeMethod(RequestInterface $request, ResponseInterface $response)
+ {
+ $method = $request->getMethod();
+ $path = $request->getPath();
+
+ $exists = $this->server->tree->nodeExists($path);
+
+ // If the node doesn't exists, none of these checks apply
+ if (!$exists) {
+ return;
+ }
+
+ switch ($method) {
+ case 'GET':
+ case 'HEAD':
+ case 'OPTIONS':
+ // For these 3 we only need to know if the node is readable.
+ $this->checkPrivileges($path, '{DAV:}read');
+ break;
+
+ case 'PUT':
+ case 'LOCK':
+ // This method requires the write-content priv if the node
+ // already exists, and bind on the parent if the node is being
+ // created.
+ // The bind privilege is handled in the beforeBind event.
+ $this->checkPrivileges($path, '{DAV:}write-content');
+ break;
+
+ case 'UNLOCK':
+ // Unlock is always allowed at the moment.
+ break;
+
+ case 'PROPPATCH':
+ $this->checkPrivileges($path, '{DAV:}write-properties');
+ break;
+
+ case 'ACL':
+ $this->checkPrivileges($path, '{DAV:}write-acl');
+ break;
+
+ case 'COPY':
+ case 'MOVE':
+ // Copy requires read privileges on the entire source tree.
+ // If the target exists write-content normally needs to be
+ // checked, however, we're deleting the node beforehand and
+ // creating a new one after, so this is handled by the
+ // beforeUnbind event.
+ //
+ // The creation of the new node is handled by the beforeBind
+ // event.
+ //
+ // If MOVE is used beforeUnbind will also be used to check if
+ // the sourcenode can be deleted.
+ $this->checkPrivileges($path, '{DAV:}read', self::R_RECURSIVE);
+ break;
+ }
+ }
+
+ /**
+ * Triggered before a new node is created.
+ *
+ * This allows us to check permissions for any operation that creates a
+ * new node, such as PUT, MKCOL, MKCALENDAR, LOCK, COPY and MOVE.
+ *
+ * @param string $uri
+ */
+ public function beforeBind($uri)
+ {
+ list($parentUri) = Uri\split($uri);
+ $this->checkPrivileges($parentUri, '{DAV:}bind');
+ }
+
+ /**
+ * Triggered before a node is deleted.
+ *
+ * This allows us to check permissions for any operation that will delete
+ * an existing node.
+ *
+ * @param string $uri
+ */
+ public function beforeUnbind($uri)
+ {
+ list($parentUri) = Uri\split($uri);
+ $this->checkPrivileges($parentUri, '{DAV:}unbind', self::R_RECURSIVEPARENTS);
+ }
+
+ /**
+ * Triggered before a node is unlocked.
+ *
+ * @param string $uri
+ * @TODO: not yet implemented
+ */
+ public function beforeUnlock($uri, DAV\Locks\LockInfo $lock)
+ {
+ }
+
+ /**
+ * Triggered before properties are looked up in specific nodes.
+ *
+ * @TODO really should be broken into multiple methods, or even a class.
+ *
+ * @return bool
+ */
+ public function propFind(DAV\PropFind $propFind, DAV\INode $node)
+ {
+ $path = $propFind->getPath();
+
+ // Checking the read permission
+ if (!$this->checkPrivileges($path, '{DAV:}read', self::R_PARENT, false)) {
+ // User is not allowed to read properties
+
+ // Returning false causes the property-fetching system to pretend
+ // that the node does not exist, and will cause it to be hidden
+ // from listings such as PROPFIND or the browser plugin.
+ if ($this->hideNodesFromListings) {
+ return false;
+ }
+
+ // Otherwise we simply mark every property as 403.
+ foreach ($propFind->getRequestedProperties() as $requestedProperty) {
+ $propFind->set($requestedProperty, null, 403);
+ }
+
+ return;
+ }
+
+ /* Adding principal properties */
+ if ($node instanceof IPrincipal) {
+ $propFind->handle('{DAV:}alternate-URI-set', function () use ($node) {
+ return new Href($node->getAlternateUriSet());
+ });
+ $propFind->handle('{DAV:}principal-URL', function () use ($node) {
+ return new Href($node->getPrincipalUrl().'/');
+ });
+ $propFind->handle('{DAV:}group-member-set', function () use ($node) {
+ $members = $node->getGroupMemberSet();
+ foreach ($members as $k => $member) {
+ $members[$k] = rtrim($member, '/').'/';
+ }
+
+ return new Href($members);
+ });
+ $propFind->handle('{DAV:}group-membership', function () use ($node) {
+ $members = $node->getGroupMembership();
+ foreach ($members as $k => $member) {
+ $members[$k] = rtrim($member, '/').'/';
+ }
+
+ return new Href($members);
+ });
+ $propFind->handle('{DAV:}displayname', [$node, 'getDisplayName']);
+ }
+
+ $propFind->handle('{DAV:}principal-collection-set', function () {
+ $val = $this->principalCollectionSet;
+ // Ensuring all collections end with a slash
+ foreach ($val as $k => $v) {
+ $val[$k] = $v.'/';
+ }
+
+ return new Href($val);
+ });
+ $propFind->handle('{DAV:}current-user-principal', function () {
+ if ($url = $this->getCurrentUserPrincipal()) {
+ return new Xml\Property\Principal(Xml\Property\Principal::HREF, $url.'/');
+ } else {
+ return new Xml\Property\Principal(Xml\Property\Principal::UNAUTHENTICATED);
+ }
+ });
+ $propFind->handle('{DAV:}supported-privilege-set', function () use ($node) {
+ return new Xml\Property\SupportedPrivilegeSet($this->getSupportedPrivilegeSet($node));
+ });
+ $propFind->handle('{DAV:}current-user-privilege-set', function () use ($node, $propFind, $path) {
+ if (!$this->checkPrivileges($path, '{DAV:}read-current-user-privilege-set', self::R_PARENT, false)) {
+ $propFind->set('{DAV:}current-user-privilege-set', null, 403);
+ } else {
+ $val = $this->getCurrentUserPrivilegeSet($node);
+
+ return new Xml\Property\CurrentUserPrivilegeSet($val);
+ }
+ });
+ $propFind->handle('{DAV:}acl', function () use ($node, $propFind, $path) {
+ /* The ACL property contains all the permissions */
+ if (!$this->checkPrivileges($path, '{DAV:}read-acl', self::R_PARENT, false)) {
+ $propFind->set('{DAV:}acl', null, 403);
+ } else {
+ $acl = $this->getACL($node);
+
+ return new Xml\Property\Acl($this->getACL($node));
+ }
+ });
+ $propFind->handle('{DAV:}acl-restrictions', function () {
+ return new Xml\Property\AclRestrictions();
+ });
+
+ /* Adding ACL properties */
+ if ($node instanceof IACL) {
+ $propFind->handle('{DAV:}owner', function () use ($node) {
+ return new Href($node->getOwner().'/');
+ });
+ }
+ }
+
+ /**
+ * This method intercepts PROPPATCH methods and make sure the
+ * group-member-set is updated correctly.
+ *
+ * @param string $path
+ */
+ public function propPatch($path, DAV\PropPatch $propPatch)
+ {
+ $propPatch->handle('{DAV:}group-member-set', function ($value) use ($path) {
+ if (is_null($value)) {
+ $memberSet = [];
+ } elseif ($value instanceof Href) {
+ $memberSet = array_map(
+ [$this->server, 'calculateUri'],
+ $value->getHrefs()
+ );
+ } else {
+ throw new DAV\Exception('The group-member-set property MUST be an instance of Sabre\DAV\Property\HrefList or null');
+ }
+ $node = $this->server->tree->getNodeForPath($path);
+ if (!($node instanceof IPrincipal)) {
+ // Fail
+ return false;
+ }
+
+ $node->setGroupMemberSet($memberSet);
+ // We must also clear our cache, just in case
+
+ $this->principalMembershipCache = [];
+
+ return true;
+ });
+ }
+
+ /**
+ * This method handles HTTP REPORT requests.
+ *
+ * @param string $reportName
+ * @param mixed $report
+ * @param mixed $path
+ *
+ * @return bool
+ */
+ public function report($reportName, $report, $path)
+ {
+ switch ($reportName) {
+ case '{DAV:}principal-property-search':
+ $this->server->transactionType = 'report-principal-property-search';
+ $this->principalPropertySearchReport($path, $report);
+
+ return false;
+ case '{DAV:}principal-search-property-set':
+ $this->server->transactionType = 'report-principal-search-property-set';
+ $this->principalSearchPropertySetReport($path, $report);
+
+ return false;
+ case '{DAV:}expand-property':
+ $this->server->transactionType = 'report-expand-property';
+ $this->expandPropertyReport($path, $report);
+
+ return false;
+ case '{DAV:}principal-match':
+ $this->server->transactionType = 'report-principal-match';
+ $this->principalMatchReport($path, $report);
+
+ return false;
+ case '{DAV:}acl-principal-prop-set':
+ $this->server->transactionType = 'acl-principal-prop-set';
+ $this->aclPrincipalPropSetReport($path, $report);
+
+ return false;
+ }
+ }
+
+ /**
+ * This method is responsible for handling the 'ACL' event.
+ *
+ * @return bool
+ */
+ public function httpAcl(RequestInterface $request, ResponseInterface $response)
+ {
+ $path = $request->getPath();
+ $body = $request->getBodyAsString();
+
+ if (!$body) {
+ throw new DAV\Exception\BadRequest('XML body expected in ACL request');
+ }
+
+ $acl = $this->server->xml->expect('{DAV:}acl', $body);
+ $newAcl = $acl->getPrivileges();
+
+ // Normalizing urls
+ foreach ($newAcl as $k => $newAce) {
+ $newAcl[$k]['principal'] = $this->server->calculateUri($newAce['principal']);
+ }
+ $node = $this->server->tree->getNodeForPath($path);
+
+ if (!$node instanceof IACL) {
+ throw new DAV\Exception\MethodNotAllowed('This node does not support the ACL method');
+ }
+
+ $oldAcl = $this->getACL($node);
+
+ $supportedPrivileges = $this->getFlatPrivilegeSet($node);
+
+ /* Checking if protected principals from the existing principal set are
+ not overwritten. */
+ foreach ($oldAcl as $oldAce) {
+ if (!isset($oldAce['protected']) || !$oldAce['protected']) {
+ continue;
+ }
+
+ $found = false;
+ foreach ($newAcl as $newAce) {
+ if (
+ $newAce['privilege'] === $oldAce['privilege'] &&
+ $newAce['principal'] === $oldAce['principal'] &&
+ $newAce['protected']
+ ) {
+ $found = true;
+ }
+ }
+
+ if (!$found) {
+ throw new Exception\AceConflict('This resource contained a protected {DAV:}ace, but this privilege did not occur in the ACL request');
+ }
+ }
+
+ foreach ($newAcl as $newAce) {
+ // Do we recognize the privilege
+ if (!isset($supportedPrivileges[$newAce['privilege']])) {
+ throw new Exception\NotSupportedPrivilege('The privilege you specified ('.$newAce['privilege'].') is not recognized by this server');
+ }
+
+ if ($supportedPrivileges[$newAce['privilege']]['abstract']) {
+ throw new Exception\NoAbstract('The privilege you specified ('.$newAce['privilege'].') is an abstract privilege');
+ }
+
+ // Looking up the principal
+ try {
+ $principal = $this->server->tree->getNodeForPath($newAce['principal']);
+ } catch (NotFound $e) {
+ throw new Exception\NotRecognizedPrincipal('The specified principal ('.$newAce['principal'].') does not exist');
+ }
+ if (!($principal instanceof IPrincipal)) {
+ throw new Exception\NotRecognizedPrincipal('The specified uri ('.$newAce['principal'].') is not a principal');
+ }
+ }
+ $node->setACL($newAcl);
+
+ $response->setStatus(200);
+
+ // Breaking the event chain, because we handled this method.
+ return false;
+ }
+
+ /* }}} */
+
+ /* Reports {{{ */
+
+ /**
+ * The principal-match report is defined in RFC3744, section 9.3.
+ *
+ * This report allows a client to figure out based on the current user,
+ * or a principal URL, the principal URL and principal URLs of groups that
+ * principal belongs to.
+ *
+ * @param string $path
+ */
+ protected function principalMatchReport($path, Xml\Request\PrincipalMatchReport $report)
+ {
+ $depth = $this->server->getHTTPDepth(0);
+ if (0 !== $depth) {
+ throw new BadRequest('The principal-match report is only defined on Depth: 0');
+ }
+
+ $currentPrincipals = $this->getCurrentUserPrincipals();
+
+ $result = [];
+
+ if (Xml\Request\PrincipalMatchReport::SELF === $report->type) {
+ // Finding all principals under the request uri that match the
+ // current principal.
+ foreach ($currentPrincipals as $currentPrincipal) {
+ if ($currentPrincipal === $path || 0 === strpos($currentPrincipal, $path.'/')) {
+ $result[] = $currentPrincipal;
+ }
+ }
+ } else {
+ // We need to find all resources that have a property that matches
+ // one of the current principals.
+ $candidates = $this->server->getPropertiesForPath(
+ $path,
+ [$report->principalProperty],
+ 1
+ );
+
+ foreach ($candidates as $candidate) {
+ if (!isset($candidate[200][$report->principalProperty])) {
+ continue;
+ }
+
+ $hrefs = $candidate[200][$report->principalProperty];
+
+ if (!$hrefs instanceof Href) {
+ continue;
+ }
+
+ foreach ($hrefs->getHrefs() as $href) {
+ if (in_array(trim($href, '/'), $currentPrincipals)) {
+ $result[] = $candidate['href'];
+ continue 2;
+ }
+ }
+ }
+ }
+
+ $responses = [];
+
+ foreach ($result as $item) {
+ $properties = [];
+
+ if ($report->properties) {
+ $foo = $this->server->getPropertiesForPath($item, $report->properties);
+ $foo = $foo[0];
+ $item = $foo['href'];
+ unset($foo['href']);
+ $properties = $foo;
+ }
+
+ $responses[] = new DAV\Xml\Element\Response(
+ $item,
+ $properties,
+ '200'
+ );
+ }
+
+ $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ $this->server->httpResponse->setStatus(207);
+ $this->server->httpResponse->setBody(
+ $this->server->xml->write(
+ '{DAV:}multistatus',
+ $responses,
+ $this->server->getBaseUri()
+ )
+ );
+ }
+
+ /**
+ * The expand-property report is defined in RFC3253 section 3.8.
+ *
+ * This report is very similar to a standard PROPFIND. The difference is
+ * that it has the additional ability to look at properties containing a
+ * {DAV:}href element, follow that property and grab additional elements
+ * there.
+ *
+ * Other rfc's, such as ACL rely on this report, so it made sense to put
+ * it in this plugin.
+ *
+ * @param string $path
+ * @param Xml\Request\ExpandPropertyReport $report
+ */
+ protected function expandPropertyReport($path, $report)
+ {
+ $depth = $this->server->getHTTPDepth(0);
+
+ $result = $this->expandProperties($path, $report->properties, $depth);
+
+ $xml = $this->server->xml->write(
+ '{DAV:}multistatus',
+ new DAV\Xml\Response\MultiStatus($result),
+ $this->server->getBaseUri()
+ );
+ $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ $this->server->httpResponse->setStatus(207);
+ $this->server->httpResponse->setBody($xml);
+ }
+
+ /**
+ * This method expands all the properties and returns
+ * a list with property values.
+ *
+ * @param array $path
+ * @param array $requestedProperties the list of required properties
+ * @param int $depth
+ *
+ * @return array
+ */
+ protected function expandProperties($path, array $requestedProperties, $depth)
+ {
+ $foundProperties = $this->server->getPropertiesForPath($path, array_keys($requestedProperties), $depth);
+
+ $result = [];
+
+ foreach ($foundProperties as $node) {
+ foreach ($requestedProperties as $propertyName => $childRequestedProperties) {
+ // We're only traversing if sub-properties were requested
+ if (!is_array($childRequestedProperties) || 0 === count($childRequestedProperties)) {
+ continue;
+ }
+
+ // We only have to do the expansion if the property was found
+ // and it contains an href element.
+ if (!array_key_exists($propertyName, $node[200])) {
+ continue;
+ }
+
+ if (!$node[200][$propertyName] instanceof DAV\Xml\Property\Href) {
+ continue;
+ }
+
+ $childHrefs = $node[200][$propertyName]->getHrefs();
+ $childProps = [];
+
+ foreach ($childHrefs as $href) {
+ // Gathering the result of the children
+ $childProps[] = [
+ 'name' => '{DAV:}response',
+ 'value' => $this->expandProperties($href, $childRequestedProperties, 0)[0],
+ ];
+ }
+
+ // Replacing the property with its expanded form.
+ $node[200][$propertyName] = $childProps;
+ }
+ $result[] = new DAV\Xml\Element\Response($node['href'], $node);
+ }
+
+ return $result;
+ }
+
+ /**
+ * principalSearchPropertySetReport.
+ *
+ * This method responsible for handing the
+ * {DAV:}principal-search-property-set report. This report returns a list
+ * of properties the client may search on, using the
+ * {DAV:}principal-property-search report.
+ *
+ * @param string $path
+ * @param Xml\Request\PrincipalSearchPropertySetReport $report
+ */
+ protected function principalSearchPropertySetReport($path, $report)
+ {
+ $httpDepth = $this->server->getHTTPDepth(0);
+ if (0 !== $httpDepth) {
+ throw new DAV\Exception\BadRequest('This report is only defined when Depth: 0');
+ }
+
+ $writer = $this->server->xml->getWriter();
+ $writer->openMemory();
+ $writer->startDocument();
+
+ $writer->startElement('{DAV:}principal-search-property-set');
+
+ foreach ($this->principalSearchPropertySet as $propertyName => $description) {
+ $writer->startElement('{DAV:}principal-search-property');
+ $writer->startElement('{DAV:}prop');
+
+ $writer->writeElement($propertyName);
+
+ $writer->endElement(); // prop
+
+ if ($description) {
+ $writer->write([[
+ 'name' => '{DAV:}description',
+ 'value' => $description,
+ 'attributes' => ['xml:lang' => 'en'],
+ ]]);
+ }
+
+ $writer->endElement(); // principal-search-property
+ }
+
+ $writer->endElement(); // principal-search-property-set
+
+ $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ $this->server->httpResponse->setStatus(200);
+ $this->server->httpResponse->setBody($writer->outputMemory());
+ }
+
+ /**
+ * principalPropertySearchReport.
+ *
+ * This method is responsible for handing the
+ * {DAV:}principal-property-search report. This report can be used for
+ * clients to search for groups of principals, based on the value of one
+ * or more properties.
+ *
+ * @param string $path
+ */
+ protected function principalPropertySearchReport($path, Xml\Request\PrincipalPropertySearchReport $report)
+ {
+ if ($report->applyToPrincipalCollectionSet) {
+ $path = null;
+ }
+ if (0 !== $this->server->getHttpDepth('0')) {
+ throw new BadRequest('Depth must be 0');
+ }
+ $result = $this->principalSearch(
+ $report->searchProperties,
+ $report->properties,
+ $path,
+ $report->test
+ );
+
+ $prefer = $this->server->getHTTPPrefer();
+
+ $this->server->httpResponse->setStatus(207);
+ $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
+ $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, 'minimal' === $prefer['return']));
+ }
+
+ /**
+ * aclPrincipalPropSet REPORT.
+ *
+ * This method is responsible for handling the {DAV:}acl-principal-prop-set
+ * REPORT, as defined in:
+ *
+ * https://tools.ietf.org/html/rfc3744#section-9.2
+ *
+ * This REPORT allows a user to quickly fetch information about all
+ * principals specified in the access control list. Most commonly this
+ * is used to for example generate a UI with ACL rules, allowing you
+ * to show names for principals for every entry.
+ *
+ * @param string $path
+ */
+ protected function aclPrincipalPropSetReport($path, Xml\Request\AclPrincipalPropSetReport $report)
+ {
+ if (0 !== $this->server->getHTTPDepth(0)) {
+ throw new BadRequest('The {DAV:}acl-principal-prop-set REPORT only supports Depth 0');
+ }
+
+ // Fetching ACL rules for the given path. We're using the property
+ // API and not the local getACL, because it will ensure that all
+ // business rules and restrictions are applied.
+ $acl = $this->server->getProperties($path, '{DAV:}acl');
+
+ if (!$acl || !isset($acl['{DAV:}acl'])) {
+ throw new Forbidden('Could not fetch ACL rules for this path');
+ }
+
+ $principals = [];
+ foreach ($acl['{DAV:}acl']->getPrivileges() as $ace) {
+ if ('{' === $ace['principal'][0]) {
+ // It's not a principal, it's one of the special rules such as {DAV:}authenticated
+ continue;
+ }
+
+ $principals[] = $ace['principal'];
+ }
+
+ $properties = $this->server->getPropertiesForMultiplePaths(
+ $principals,
+ $report->properties
+ );
+
+ $this->server->httpResponse->setStatus(207);
+ $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ $this->server->httpResponse->setBody(
+ $this->server->generateMultiStatus($properties)
+ );
+ }
+
+ /* }}} */
+
+ /**
+ * This method is used to generate HTML output for the
+ * DAV\Browser\Plugin. This allows us to generate an interface users
+ * can use to create new calendars.
+ *
+ * @param string $output
+ *
+ * @return bool
+ */
+ public function htmlActionsPanel(DAV\INode $node, &$output)
+ {
+ if (!$node instanceof PrincipalCollection) {
+ return;
+ }
+
+ $output .= '
+
';
+
+ 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 WebDAV ACL (rfc3744)',
+ 'link' => 'http://sabre.io/dav/acl/',
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Principal.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Principal.php
new file mode 100644
index 000000000..ada38ab72
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Principal.php
@@ -0,0 +1,199 @@
+principalBackend = $principalBackend;
+ $this->principalProperties = $principalProperties;
+ }
+
+ /**
+ * Returns the full principal url.
+ *
+ * @return string
+ */
+ public function getPrincipalUrl()
+ {
+ return $this->principalProperties['uri'];
+ }
+
+ /**
+ * 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()
+ {
+ $uris = [];
+ if (isset($this->principalProperties['{DAV:}alternate-URI-set'])) {
+ $uris = $this->principalProperties['{DAV:}alternate-URI-set'];
+ }
+
+ if (isset($this->principalProperties['{http://sabredav.org/ns}email-address'])) {
+ $uris[] = 'mailto:'.$this->principalProperties['{http://sabredav.org/ns}email-address'];
+ }
+
+ return array_unique($uris);
+ }
+
+ /**
+ * 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->principalProperties['uri']);
+ }
+
+ /**
+ * 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->principalProperties['uri']);
+ }
+
+ /**
+ * 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 $groupMembers)
+ {
+ $this->principalBackend->setGroupMemberSet($this->principalProperties['uri'], $groupMembers);
+ }
+
+ /**
+ * Returns this principals name.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ $uri = $this->principalProperties['uri'];
+ list(, $name) = Uri\split($uri);
+
+ return $name;
+ }
+
+ /**
+ * Returns the name of the user.
+ *
+ * @return string
+ */
+ public function getDisplayName()
+ {
+ if (isset($this->principalProperties['{DAV:}displayname'])) {
+ return $this->principalProperties['{DAV:}displayname'];
+ } else {
+ return $this->getName();
+ }
+ }
+
+ /**
+ * Returns a list of properties.
+ *
+ * @param array $requestedProperties
+ *
+ * @return array
+ */
+ public function getProperties($requestedProperties)
+ {
+ $newProperties = [];
+ foreach ($requestedProperties as $propName) {
+ if (isset($this->principalProperties[$propName])) {
+ $newProperties[$propName] = $this->principalProperties[$propName];
+ }
+ }
+
+ return $newProperties;
+ }
+
+ /**
+ * 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(DAV\PropPatch $propPatch)
+ {
+ return $this->principalBackend->updatePrincipal(
+ $this->principalProperties['uri'],
+ $propPatch
+ );
+ }
+
+ /**
+ * 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->principalProperties['uri'];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/AbstractBackend.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/AbstractBackend.php
new file mode 100644
index 000000000..03a9c4bad
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/AbstractBackend.php
@@ -0,0 +1,54 @@
+searchPrincipals(
+ $principalPrefix,
+ ['{http://sabredav.org/ns}email-address' => substr($uri, 7)]
+ );
+
+ if ($result) {
+ return $result[0];
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/BackendInterface.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/BackendInterface.php
new file mode 100644
index 000000000..72717a59b
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/BackendInterface.php
@@ -0,0 +1,143 @@
+ [
+ 'dbField' => 'displayname',
+ ],
+
+ /*
+ * This is the users' primary email-address.
+ */
+ '{http://sabredav.org/ns}email-address' => [
+ 'dbField' => 'email',
+ ],
+ ];
+
+ /**
+ * Sets up the backend.
+ */
+ public function __construct(\PDO $pdo)
+ {
+ $this->pdo = $pdo;
+ }
+
+ /**
+ * Returns a list of principals based on a prefix.
+ *
+ * This prefix will often contain something like 'principals'. You are only
+ * expected to return principals that are in this base path.
+ *
+ * You are expected to return at least a 'uri' for every user, you can
+ * return any additional properties if you wish so. Common properties are:
+ * {DAV:}displayname
+ * {http://sabredav.org/ns}email-address - This is a custom SabreDAV
+ * field that's actualy injected in a number of other properties. If
+ * you have an email address, use this property.
+ *
+ * @param string $prefixPath
+ *
+ * @return array
+ */
+ public function getPrincipalsByPrefix($prefixPath)
+ {
+ $fields = [
+ 'uri',
+ ];
+
+ foreach ($this->fieldMap as $key => $value) {
+ $fields[] = $value['dbField'];
+ }
+ $result = $this->pdo->query('SELECT '.implode(',', $fields).' FROM '.$this->tableName);
+
+ $principals = [];
+
+ while ($row = $result->fetch(\PDO::FETCH_ASSOC)) {
+ // Checking if the principal is in the prefix
+ list($rowPrefix) = Uri\split($row['uri']);
+ if ($rowPrefix !== $prefixPath) {
+ continue;
+ }
+
+ $principal = [
+ 'uri' => $row['uri'],
+ ];
+ foreach ($this->fieldMap as $key => $value) {
+ if ($row[$value['dbField']]) {
+ $principal[$key] = $row[$value['dbField']];
+ }
+ }
+ $principals[] = $principal;
+ }
+
+ return $principals;
+ }
+
+ /**
+ * Returns a specific principal, specified by it's path.
+ * The returned structure should be the exact same as from
+ * getPrincipalsByPrefix.
+ *
+ * @param string $path
+ *
+ * @return array
+ */
+ public function getPrincipalByPath($path)
+ {
+ $fields = [
+ 'id',
+ 'uri',
+ ];
+
+ foreach ($this->fieldMap as $key => $value) {
+ $fields[] = $value['dbField'];
+ }
+ $stmt = $this->pdo->prepare('SELECT '.implode(',', $fields).' FROM '.$this->tableName.' WHERE uri = ?');
+ $stmt->execute([$path]);
+
+ $row = $stmt->fetch(\PDO::FETCH_ASSOC);
+ if (!$row) {
+ return;
+ }
+
+ $principal = [
+ 'id' => $row['id'],
+ 'uri' => $row['uri'],
+ ];
+ foreach ($this->fieldMap as $key => $value) {
+ if ($row[$value['dbField']]) {
+ $principal[$key] = $row[$value['dbField']];
+ }
+ }
+
+ return $principal;
+ }
+
+ /**
+ * Updates one ore more webdav properties on a principal.
+ *
+ * The list of mutations is stored in a Sabre\DAV\PropPatch object.
+ * To do the actual updates, you must tell this object which properties
+ * you're going to process with the handle() method.
+ *
+ * Calling the handle method is like telling the PropPatch object "I
+ * promise I can handle updating this property".
+ *
+ * Read the PropPatch documentation for more info and examples.
+ *
+ * @param string $path
+ */
+ public function updatePrincipal($path, DAV\PropPatch $propPatch)
+ {
+ $propPatch->handle(array_keys($this->fieldMap), function ($properties) use ($path) {
+ $query = 'UPDATE '.$this->tableName.' SET ';
+ $first = true;
+
+ $values = [];
+
+ foreach ($properties as $key => $value) {
+ $dbField = $this->fieldMap[$key]['dbField'];
+
+ if (!$first) {
+ $query .= ', ';
+ }
+ $first = false;
+ $query .= $dbField.' = :'.$dbField;
+ $values[$dbField] = $value;
+ }
+
+ $query .= ' WHERE uri = :uri';
+ $values['uri'] = $path;
+
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute($values);
+
+ return true;
+ });
+ }
+
+ /**
+ * This method is used to search for principals matching a set of
+ * properties.
+ *
+ * This search is specifically used by RFC3744's principal-property-search
+ * REPORT.
+ *
+ * The actual search should be a unicode-non-case-sensitive search. The
+ * keys in searchProperties are the WebDAV property names, while the values
+ * are the property values to search on.
+ *
+ * By default, if multiple properties are submitted to this method, the
+ * various properties should be combined with 'AND'. If $test is set to
+ * 'anyof', it should be combined using 'OR'.
+ *
+ * This method should simply return an array with full principal uri's.
+ *
+ * If somebody attempted to search on a property the backend does not
+ * support, you should simply return 0 results.
+ *
+ * You can also just return 0 results if you choose to not support
+ * searching at all, but keep in mind that this may stop certain features
+ * from working.
+ *
+ * @param string $prefixPath
+ * @param string $test
+ *
+ * @return array
+ */
+ public function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof')
+ {
+ if (0 == count($searchProperties)) {
+ return [];
+ } //No criteria
+
+ $query = 'SELECT uri FROM '.$this->tableName.' WHERE ';
+ $values = [];
+ foreach ($searchProperties as $property => $value) {
+ switch ($property) {
+ case '{DAV:}displayname':
+ $column = 'displayname';
+ break;
+ case '{http://sabredav.org/ns}email-address':
+ $column = 'email';
+ break;
+ default:
+ // Unsupported property
+ return [];
+ }
+ if (count($values) > 0) {
+ $query .= (0 == strcmp($test, 'anyof') ? ' OR ' : ' AND ');
+ }
+ $query .= 'lower('.$column.') LIKE lower(?)';
+ $values[] = '%'.$value.'%';
+ }
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute($values);
+
+ $principals = [];
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ // Checking if the principal is in the prefix
+ list($rowPrefix) = Uri\split($row['uri']);
+ if ($rowPrefix !== $prefixPath) {
+ continue;
+ }
+
+ $principals[] = $row['uri'];
+ }
+
+ return $principals;
+ }
+
+ /**
+ * Finds a principal by its URI.
+ *
+ * This method may receive any type of uri, but mailto: addresses will be
+ * the most common.
+ *
+ * Implementation of this API is optional. It is currently used by the
+ * CalDAV system to find principals based on their email addresses. If this
+ * API is not implemented, some features may not work correctly.
+ *
+ * This method must return a relative principal path, or null, if the
+ * principal was not found or you refuse to find it.
+ *
+ * @param string $uri
+ * @param string $principalPrefix
+ *
+ * @return string
+ */
+ public function findByUri($uri, $principalPrefix)
+ {
+ $uriParts = Uri\parse($uri);
+
+ // Only two types of uri are supported :
+ // - the "mailto:" scheme with some non-empty address
+ // - a principals uri, in the form "principals/NAME"
+ // In both cases, `path` must not be empty.
+ if (empty($uriParts['path'])) {
+ return null;
+ }
+
+ $uri = null;
+ if ('mailto' === $uriParts['scheme']) {
+ $query = 'SELECT uri FROM '.$this->tableName.' WHERE lower(email)=lower(?)';
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute([$uriParts['path']]);
+
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ // Checking if the principal is in the prefix
+ list($rowPrefix) = Uri\split($row['uri']);
+ if ($rowPrefix !== $principalPrefix) {
+ continue;
+ }
+
+ $uri = $row['uri'];
+ break; //Stop on first match
+ }
+ } else {
+ $pathParts = Uri\split($uriParts['path']); // We can do this since $uriParts['path'] is not null
+
+ if (2 === count($pathParts) && $pathParts[0] === $principalPrefix) {
+ // Checking that this uri exists
+ $query = 'SELECT * FROM '.$this->tableName.' WHERE uri = ?';
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute([$uriParts['path']]);
+ $rows = $stmt->fetchAll();
+
+ if (count($rows) > 0) {
+ $uri = $uriParts['path'];
+ }
+ }
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Returns the list of members for a group-principal.
+ *
+ * @param string $principal
+ *
+ * @return array
+ */
+ public function getGroupMemberSet($principal)
+ {
+ $principal = $this->getPrincipalByPath($principal);
+ if (!$principal) {
+ throw new DAV\Exception('Principal not found');
+ }
+ $stmt = $this->pdo->prepare('SELECT principals.uri as uri FROM '.$this->groupMembersTableName.' AS groupmembers LEFT JOIN '.$this->tableName.' AS principals ON groupmembers.member_id = principals.id WHERE groupmembers.principal_id = ?');
+ $stmt->execute([$principal['id']]);
+
+ $result = [];
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ $result[] = $row['uri'];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns the list of groups a principal is a member of.
+ *
+ * @param string $principal
+ *
+ * @return array
+ */
+ public function getGroupMembership($principal)
+ {
+ $principal = $this->getPrincipalByPath($principal);
+ if (!$principal) {
+ throw new DAV\Exception('Principal not found');
+ }
+ $stmt = $this->pdo->prepare('SELECT principals.uri as uri FROM '.$this->groupMembersTableName.' AS groupmembers LEFT JOIN '.$this->tableName.' AS principals ON groupmembers.principal_id = principals.id WHERE groupmembers.member_id = ?');
+ $stmt->execute([$principal['id']]);
+
+ $result = [];
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ $result[] = $row['uri'];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Updates the list of group members for a group principal.
+ *
+ * The principals should be passed as a list of uri's.
+ *
+ * @param string $principal
+ */
+ public function setGroupMemberSet($principal, array $members)
+ {
+ // Grabbing the list of principal id's.
+ $stmt = $this->pdo->prepare('SELECT id, uri FROM '.$this->tableName.' WHERE uri IN (? '.str_repeat(', ? ', count($members)).');');
+ $stmt->execute(array_merge([$principal], $members));
+
+ $memberIds = [];
+ $principalId = null;
+
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ if ($row['uri'] == $principal) {
+ $principalId = $row['id'];
+ } else {
+ $memberIds[] = $row['id'];
+ }
+ }
+ if (!$principalId) {
+ throw new DAV\Exception('Principal not found');
+ }
+ // Wiping out old members
+ $stmt = $this->pdo->prepare('DELETE FROM '.$this->groupMembersTableName.' WHERE principal_id = ?;');
+ $stmt->execute([$principalId]);
+
+ foreach ($memberIds as $memberId) {
+ $stmt = $this->pdo->prepare('INSERT INTO '.$this->groupMembersTableName.' (principal_id, member_id) VALUES (?, ?);');
+ $stmt->execute([$principalId, $memberId]);
+ }
+ }
+
+ /**
+ * Creates a new principal.
+ *
+ * This method receives a full path for the new principal. The mkCol object
+ * contains any additional webdav properties specified during the creation
+ * of the principal.
+ *
+ * @param string $path
+ */
+ public function createPrincipal($path, MkCol $mkCol)
+ {
+ $stmt = $this->pdo->prepare('INSERT INTO '.$this->tableName.' (uri) VALUES (?)');
+ $stmt->execute([$path]);
+ $this->updatePrincipal($path, $mkCol);
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php
new file mode 100644
index 000000000..b823b6ceb
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php
@@ -0,0 +1,96 @@
+principalBackend, $principal);
+ }
+
+ /**
+ * Creates a new collection.
+ *
+ * This method will receive a MkCol object with all the information about
+ * the new collection that's being created.
+ *
+ * The MkCol object contains information about the resourceType of the new
+ * collection. If you don't support the specified resourceType, you should
+ * throw Exception\InvalidResourceType.
+ *
+ * The object also contains a list of WebDAV properties for the new
+ * collection.
+ *
+ * You should call the handle() method on this object to specify exactly
+ * which properties you are storing. This allows the system to figure out
+ * exactly which properties you didn't store, which in turn allows other
+ * plugins (such as the propertystorage plugin) to handle storing the
+ * property for you.
+ *
+ * @param string $name
+ *
+ * @throws InvalidResourceType
+ */
+ public function createExtendedCollection($name, MkCol $mkCol)
+ {
+ if (!$mkCol->hasResourceType('{DAV:}principal')) {
+ throw new InvalidResourceType('Only resources of type {DAV:}principal may be created here');
+ }
+
+ $this->principalBackend->createPrincipal(
+ $this->principalPrefix.'/'.$name,
+ $mkCol
+ );
+ }
+
+ /**
+ * 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 [
+ [
+ 'principal' => '{DAV:}authenticated',
+ 'privilege' => '{DAV:}read',
+ 'protected' => true,
+ ],
+ ];
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Xml/Property/Acl.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Xml/Property/Acl.php
new file mode 100644
index 000000000..c6e236dc5
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Xml/Property/Acl.php
@@ -0,0 +1,257 @@
+privileges = $privileges;
+ $this->prefixBaseUrl = $prefixBaseUrl;
+ }
+
+ /**
+ * Returns the list of privileges for this property.
+ *
+ * @return array
+ */
+ public function getPrivileges()
+ {
+ return $this->privileges;
+ }
+
+ /**
+ * 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->privileges as $ace) {
+ $this->serializeAce($writer, $ace);
+ }
+ }
+
+ /**
+ * Generate html representation for this value.
+ *
+ * The html output is 100% trusted, and no effort is being made to sanitize
+ * it. It's up to the implementor to sanitize user provided values.
+ *
+ * The output must be in UTF-8.
+ *
+ * The baseUri parameter is a url to the root of the application, and can
+ * be used to construct local links.
+ *
+ * @return string
+ */
+ public function toHtml(HtmlOutputHelper $html)
+ {
+ ob_start();
+ echo '
';
+ echo '
Principal
Privilege
';
+ foreach ($this->privileges as $privilege) {
+ echo '
';
+ // if it starts with a {, it's a special principal
+ if ('{' === $privilege['principal'][0]) {
+ echo '
';
+
+ return ob_get_clean();
+ }
+
+ /**
+ * 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)
+ {
+ $elementMap = [
+ '{DAV:}ace' => 'Sabre\Xml\Element\KeyValue',
+ '{DAV:}privilege' => 'Sabre\Xml\Element\Elements',
+ '{DAV:}principal' => 'Sabre\DAVACL\Xml\Property\Principal',
+ ];
+
+ $privileges = [];
+
+ foreach ((array) $reader->parseInnerTree($elementMap) as $element) {
+ if ('{DAV:}ace' !== $element['name']) {
+ continue;
+ }
+ $ace = $element['value'];
+
+ if (empty($ace['{DAV:}principal'])) {
+ throw new DAV\Exception\BadRequest('Each {DAV:}ace element must have one {DAV:}principal element');
+ }
+ $principal = $ace['{DAV:}principal'];
+
+ switch ($principal->getType()) {
+ case Principal::HREF:
+ $principal = $principal->getHref();
+ break;
+ case Principal::AUTHENTICATED:
+ $principal = '{DAV:}authenticated';
+ break;
+ case Principal::UNAUTHENTICATED:
+ $principal = '{DAV:}unauthenticated';
+ break;
+ case Principal::ALL:
+ $principal = '{DAV:}all';
+ break;
+ }
+
+ $protected = array_key_exists('{DAV:}protected', $ace);
+
+ if (!isset($ace['{DAV:}grant'])) {
+ throw new DAV\Exception\NotImplemented('Every {DAV:}ace element must have a {DAV:}grant element. {DAV:}deny is not yet supported');
+ }
+ foreach ($ace['{DAV:}grant'] as $elem) {
+ if ('{DAV:}privilege' !== $elem['name']) {
+ continue;
+ }
+
+ foreach ($elem['value'] as $priv) {
+ $privileges[] = [
+ 'principal' => $principal,
+ 'protected' => $protected,
+ 'privilege' => $priv,
+ ];
+ }
+ }
+ }
+
+ return new self($privileges);
+ }
+
+ /**
+ * Serializes a single access control entry.
+ */
+ private function serializeAce(Writer $writer, array $ace)
+ {
+ $writer->startElement('{DAV:}ace');
+
+ switch ($ace['principal']) {
+ case '{DAV:}authenticated':
+ $principal = new Principal(Principal::AUTHENTICATED);
+ break;
+ case '{DAV:}unauthenticated':
+ $principal = new Principal(Principal::UNAUTHENTICATED);
+ break;
+ case '{DAV:}all':
+ $principal = new Principal(Principal::ALL);
+ break;
+ default:
+ $principal = new Principal(Principal::HREF, $ace['principal']);
+ break;
+ }
+
+ $writer->writeElement('{DAV:}principal', $principal);
+ $writer->startElement('{DAV:}grant');
+ $writer->startElement('{DAV:}privilege');
+
+ $writer->writeElement($ace['privilege']);
+
+ $writer->endElement(); // privilege
+ $writer->endElement(); // grant
+
+ if (!empty($ace['protected'])) {
+ $writer->writeElement('{DAV:}protected');
+ }
+
+ $writer->endElement(); // ace
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Xml/Property/AclRestrictions.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Xml/Property/AclRestrictions.php
new file mode 100644
index 000000000..b5629c809
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Xml/Property/AclRestrictions.php
@@ -0,0 +1,42 @@
+writeElement('{DAV:}grant-only');
+ $writer->writeElement('{DAV:}no-invert');
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Xml/Property/CurrentUserPrivilegeSet.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Xml/Property/CurrentUserPrivilegeSet.php
new file mode 100644
index 000000000..e38a45c61
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Xml/Property/CurrentUserPrivilegeSet.php
@@ -0,0 +1,145 @@
+privileges = $privileges;
+ }
+
+ /**
+ * 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->privileges as $privName) {
+ $writer->startElement('{DAV:}privilege');
+ $writer->writeElement($privName);
+ $writer->endElement();
+ }
+ }
+
+ /**
+ * Returns true or false, whether the specified principal appears in the
+ * list.
+ *
+ * @param string $privilegeName
+ *
+ * @return bool
+ */
+ public function has($privilegeName)
+ {
+ return in_array($privilegeName, $this->privileges);
+ }
+
+ /**
+ * Returns the list of privileges.
+ *
+ * @return array
+ */
+ public function getValue()
+ {
+ return $this->privileges;
+ }
+
+ /**
+ * 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 = [];
+
+ $tree = $reader->parseInnerTree(['{DAV:}privilege' => 'Sabre\\Xml\\Element\\Elements']);
+ foreach ($tree as $element) {
+ if ('{DAV:}privilege' !== $element['name']) {
+ continue;
+ }
+ $result[] = $element['value'][0];
+ }
+
+ return new self($result);
+ }
+
+ /**
+ * Generate html representation for this value.
+ *
+ * The html output is 100% trusted, and no effort is being made to sanitize
+ * it. It's up to the implementor to sanitize user provided values.
+ *
+ * The output must be in UTF-8.
+ *
+ * The baseUri parameter is a url to the root of the application, and can
+ * be used to construct local links.
+ *
+ * @return string
+ */
+ public function toHtml(HtmlOutputHelper $html)
+ {
+ return implode(
+ ', ',
+ array_map([$html, 'xmlName'], $this->getValue())
+ );
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Xml/Property/Principal.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Xml/Property/Principal.php
new file mode 100644
index 000000000..24aeffad9
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Xml/Property/Principal.php
@@ -0,0 +1,184 @@
+type = $type;
+ if (self::HREF === $type && is_null($href)) {
+ throw new DAV\Exception('The href argument must be specified for the HREF principal type.');
+ }
+ if ($href) {
+ $href = rtrim($href, '/').'/';
+ parent::__construct($href);
+ }
+ }
+
+ /**
+ * Returns the principal type.
+ *
+ * @return int
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * 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)
+ {
+ switch ($this->type) {
+ case self::UNAUTHENTICATED:
+ $writer->writeElement('{DAV:}unauthenticated');
+ break;
+ case self::AUTHENTICATED:
+ $writer->writeElement('{DAV:}authenticated');
+ break;
+ case self::HREF:
+ parent::xmlSerialize($writer);
+ break;
+ case self::ALL:
+ $writer->writeElement('{DAV:}all');
+ break;
+ }
+ }
+
+ /**
+ * Generate html representation for this value.
+ *
+ * The html output is 100% trusted, and no effort is being made to sanitize
+ * it. It's up to the implementor to sanitize user provided values.
+ *
+ * The output must be in UTF-8.
+ *
+ * The baseUri parameter is a url to the root of the application, and can
+ * be used to construct local links.
+ *
+ * @return string
+ */
+ public function toHtml(HtmlOutputHelper $html)
+ {
+ switch ($this->type) {
+ case self::UNAUTHENTICATED:
+ return 'unauthenticated';
+ case self::AUTHENTICATED:
+ return 'authenticated';
+ case self::HREF:
+ return parent::toHtml($html);
+ case self::ALL:
+ return 'all';
+ }
+ }
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called staticly, 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)
+ {
+ $tree = $reader->parseInnerTree()[0];
+
+ switch ($tree['name']) {
+ case '{DAV:}unauthenticated':
+ return new self(self::UNAUTHENTICATED);
+ case '{DAV:}authenticated':
+ return new self(self::AUTHENTICATED);
+ case '{DAV:}href':
+ return new self(self::HREF, $tree['value']);
+ case '{DAV:}all':
+ return new self(self::ALL);
+ default:
+ throw new BadRequest('Unknown or unsupported principal type: '.$tree['name']);
+ }
+ }
+}
diff --git a/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php
new file mode 100644
index 000000000..6e7514bd9
--- /dev/null
+++ b/plugins/panakour/backup/vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php
@@ -0,0 +1,146 @@
+privileges = $privileges;
+ }
+
+ /**
+ * Returns the privilege value.
+ *
+ * @return array
+ */
+ public function getValue()
+ {
+ return $this->privileges;
+ }
+
+ /**
+ * 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)
+ {
+ $this->serializePriv($writer, '{DAV:}all', ['aggregates' => $this->privileges]);
+ }
+
+ /**
+ * Generate html representation for this value.
+ *
+ * The html output is 100% trusted, and no effort is being made to sanitize
+ * it. It's up to the implementor to sanitize user provided values.
+ *
+ * The output must be in UTF-8.
+ *
+ * The baseUri parameter is a url to the root of the application, and can
+ * be used to construct local links.
+ *
+ * @return string
+ */
+ public function toHtml(HtmlOutputHelper $html)
+ {
+ $traverse = function ($privName, $priv) use (&$traverse, $html) {
+ echo '