Passed
Push — master ( 10aa22...f23c21 )
by Morris
13:04 queued 10s
created
lib/private/legacy/OC_DB_StatementWrapper.php 1 patch
Indentation   +80 added lines, -80 removed lines patch added patch discarded remove patch
@@ -38,92 +38,92 @@
 block discarded – undo
38 38
  * @method array fetchAll(integer $fetchMode = null);
39 39
  */
40 40
 class OC_DB_StatementWrapper {
41
-	/**
42
-	 * @var \Doctrine\DBAL\Driver\Statement
43
-	 */
44
-	private $statement = null;
45
-	private $isManipulation = false;
46
-	private $lastArguments = [];
41
+    /**
42
+     * @var \Doctrine\DBAL\Driver\Statement
43
+     */
44
+    private $statement = null;
45
+    private $isManipulation = false;
46
+    private $lastArguments = [];
47 47
 
48
-	/**
49
-	 * @param boolean $isManipulation
50
-	 */
51
-	public function __construct($statement, $isManipulation) {
52
-		$this->statement = $statement;
53
-		$this->isManipulation = $isManipulation;
54
-	}
48
+    /**
49
+     * @param boolean $isManipulation
50
+     */
51
+    public function __construct($statement, $isManipulation) {
52
+        $this->statement = $statement;
53
+        $this->isManipulation = $isManipulation;
54
+    }
55 55
 
56
-	/**
57
-	 * pass all other function directly to the \Doctrine\DBAL\Driver\Statement
58
-	 */
59
-	public function __call($name,$arguments) {
60
-		return call_user_func_array([$this->statement,$name], $arguments);
61
-	}
56
+    /**
57
+     * pass all other function directly to the \Doctrine\DBAL\Driver\Statement
58
+     */
59
+    public function __call($name,$arguments) {
60
+        return call_user_func_array([$this->statement,$name], $arguments);
61
+    }
62 62
 
63
-	/**
64
-	 * make execute return the result instead of a bool
65
-	 *
66
-	 * @param array $input
67
-	 * @return \OC_DB_StatementWrapper|int|bool
68
-	 */
69
-	public function execute($input = []) {
70
-		$this->lastArguments = $input;
71
-		if (count($input) > 0) {
72
-			$result = $this->statement->execute($input);
73
-		} else {
74
-			$result = $this->statement->execute();
75
-		}
63
+    /**
64
+     * make execute return the result instead of a bool
65
+     *
66
+     * @param array $input
67
+     * @return \OC_DB_StatementWrapper|int|bool
68
+     */
69
+    public function execute($input = []) {
70
+        $this->lastArguments = $input;
71
+        if (count($input) > 0) {
72
+            $result = $this->statement->execute($input);
73
+        } else {
74
+            $result = $this->statement->execute();
75
+        }
76 76
 
77
-		if ($result === false) {
78
-			return false;
79
-		}
80
-		if ($this->isManipulation) {
81
-			return $this->statement->rowCount();
82
-		} else {
83
-			return $this;
84
-		}
85
-	}
77
+        if ($result === false) {
78
+            return false;
79
+        }
80
+        if ($this->isManipulation) {
81
+            return $this->statement->rowCount();
82
+        } else {
83
+            return $this;
84
+        }
85
+    }
86 86
 
87
-	/**
88
-	 * provide an alias for fetch
89
-	 *
90
-	 * @return mixed
91
-	 */
92
-	public function fetchRow() {
93
-		return $this->statement->fetch();
94
-	}
87
+    /**
88
+     * provide an alias for fetch
89
+     *
90
+     * @return mixed
91
+     */
92
+    public function fetchRow() {
93
+        return $this->statement->fetch();
94
+    }
95 95
 
96
-	/**
97
-	 * Provide a simple fetchOne.
98
-	 *
99
-	 * fetch single column from the next row
100
-	 * @param int $column the column number to fetch
101
-	 * @return string
102
-	 */
103
-	public function fetchOne($column = 0) {
104
-		return $this->statement->fetchColumn($column);
105
-	}
96
+    /**
97
+     * Provide a simple fetchOne.
98
+     *
99
+     * fetch single column from the next row
100
+     * @param int $column the column number to fetch
101
+     * @return string
102
+     */
103
+    public function fetchOne($column = 0) {
104
+        return $this->statement->fetchColumn($column);
105
+    }
106 106
 
107
-	/**
108
-	 * Closes the cursor, enabling the statement to be executed again.
109
-	 *
110
-	 * @deprecated Use Result::free() instead.
111
-	 */
112
-	public function closeCursor(): void {
113
-		$this->statement->closeCursor();
114
-	}
107
+    /**
108
+     * Closes the cursor, enabling the statement to be executed again.
109
+     *
110
+     * @deprecated Use Result::free() instead.
111
+     */
112
+    public function closeCursor(): void {
113
+        $this->statement->closeCursor();
114
+    }
115 115
 
116
-	/**
117
-	 * Binds a PHP variable to a corresponding named or question mark placeholder in the
118
-	 * SQL statement that was use to prepare the statement.
119
-	 *
120
-	 * @param mixed $column Either the placeholder name or the 1-indexed placeholder index
121
-	 * @param mixed $variable The variable to bind
122
-	 * @param integer|null $type one of the  PDO::PARAM_* constants
123
-	 * @param integer|null $length max length when using an OUT bind
124
-	 * @return boolean
125
-	 */
126
-	public function bindParam($column, &$variable, $type = null, $length = null) {
127
-		return $this->statement->bindParam($column, $variable, $type, $length);
128
-	}
116
+    /**
117
+     * Binds a PHP variable to a corresponding named or question mark placeholder in the
118
+     * SQL statement that was use to prepare the statement.
119
+     *
120
+     * @param mixed $column Either the placeholder name or the 1-indexed placeholder index
121
+     * @param mixed $variable The variable to bind
122
+     * @param integer|null $type one of the  PDO::PARAM_* constants
123
+     * @param integer|null $length max length when using an OUT bind
124
+     * @return boolean
125
+     */
126
+    public function bindParam($column, &$variable, $type = null, $length = null) {
127
+        return $this->statement->bindParam($column, $variable, $type, $length);
128
+    }
129 129
 }
Please login to merge, or discard this patch.
lib/private/legacy/OC_Util.php 1 patch
Indentation   +1405 added lines, -1405 removed lines patch added patch discarded remove patch
@@ -73,1414 +73,1414 @@
 block discarded – undo
73 73
 use OCP\IUserSession;
74 74
 
75 75
 class OC_Util {
76
-	public static $scripts = [];
77
-	public static $styles = [];
78
-	public static $headers = [];
79
-	private static $rootMounted = false;
80
-	private static $fsSetup = false;
81
-
82
-	/** @var array Local cache of version.php */
83
-	private static $versionCache = null;
84
-
85
-	protected static function getAppManager() {
86
-		return \OC::$server->getAppManager();
87
-	}
88
-
89
-	private static function initLocalStorageRootFS() {
90
-		// mount local file backend as root
91
-		$configDataDirectory = \OC::$server->getSystemConfig()->getValue("datadirectory", OC::$SERVERROOT . "/data");
92
-		//first set up the local "root" storage
93
-		\OC\Files\Filesystem::initMountManager();
94
-		if (!self::$rootMounted) {
95
-			\OC\Files\Filesystem::mount(LocalRootStorage::class, ['datadir' => $configDataDirectory], '/');
96
-			self::$rootMounted = true;
97
-		}
98
-	}
99
-
100
-	/**
101
-	 * mounting an object storage as the root fs will in essence remove the
102
-	 * necessity of a data folder being present.
103
-	 * TODO make home storage aware of this and use the object storage instead of local disk access
104
-	 *
105
-	 * @param array $config containing 'class' and optional 'arguments'
106
-	 * @suppress PhanDeprecatedFunction
107
-	 */
108
-	private static function initObjectStoreRootFS($config) {
109
-		// check misconfiguration
110
-		if (empty($config['class'])) {
111
-			\OCP\Util::writeLog('files', 'No class given for objectstore', ILogger::ERROR);
112
-		}
113
-		if (!isset($config['arguments'])) {
114
-			$config['arguments'] = [];
115
-		}
116
-
117
-		// instantiate object store implementation
118
-		$name = $config['class'];
119
-		if (strpos($name, 'OCA\\') === 0 && substr_count($name, '\\') >= 2) {
120
-			$segments = explode('\\', $name);
121
-			OC_App::loadApp(strtolower($segments[1]));
122
-		}
123
-		$config['arguments']['objectstore'] = new $config['class']($config['arguments']);
124
-		// mount with plain / root object store implementation
125
-		$config['class'] = '\OC\Files\ObjectStore\ObjectStoreStorage';
126
-
127
-		// mount object storage as root
128
-		\OC\Files\Filesystem::initMountManager();
129
-		if (!self::$rootMounted) {
130
-			\OC\Files\Filesystem::mount($config['class'], $config['arguments'], '/');
131
-			self::$rootMounted = true;
132
-		}
133
-	}
134
-
135
-	/**
136
-	 * mounting an object storage as the root fs will in essence remove the
137
-	 * necessity of a data folder being present.
138
-	 *
139
-	 * @param array $config containing 'class' and optional 'arguments'
140
-	 * @suppress PhanDeprecatedFunction
141
-	 */
142
-	private static function initObjectStoreMultibucketRootFS($config) {
143
-		// check misconfiguration
144
-		if (empty($config['class'])) {
145
-			\OCP\Util::writeLog('files', 'No class given for objectstore', ILogger::ERROR);
146
-		}
147
-		if (!isset($config['arguments'])) {
148
-			$config['arguments'] = [];
149
-		}
150
-
151
-		// instantiate object store implementation
152
-		$name = $config['class'];
153
-		if (strpos($name, 'OCA\\') === 0 && substr_count($name, '\\') >= 2) {
154
-			$segments = explode('\\', $name);
155
-			OC_App::loadApp(strtolower($segments[1]));
156
-		}
157
-
158
-		if (!isset($config['arguments']['bucket'])) {
159
-			$config['arguments']['bucket'] = '';
160
-		}
161
-		// put the root FS always in first bucket for multibucket configuration
162
-		$config['arguments']['bucket'] .= '0';
163
-
164
-		$config['arguments']['objectstore'] = new $config['class']($config['arguments']);
165
-		// mount with plain / root object store implementation
166
-		$config['class'] = '\OC\Files\ObjectStore\ObjectStoreStorage';
167
-
168
-		// mount object storage as root
169
-		\OC\Files\Filesystem::initMountManager();
170
-		if (!self::$rootMounted) {
171
-			\OC\Files\Filesystem::mount($config['class'], $config['arguments'], '/');
172
-			self::$rootMounted = true;
173
-		}
174
-	}
175
-
176
-	/**
177
-	 * Can be set up
178
-	 *
179
-	 * @param string $user
180
-	 * @return boolean
181
-	 * @description configure the initial filesystem based on the configuration
182
-	 * @suppress PhanDeprecatedFunction
183
-	 * @suppress PhanAccessMethodInternal
184
-	 */
185
-	public static function setupFS($user = '') {
186
-		//setting up the filesystem twice can only lead to trouble
187
-		if (self::$fsSetup) {
188
-			return false;
189
-		}
190
-
191
-		\OC::$server->getEventLogger()->start('setup_fs', 'Setup filesystem');
192
-
193
-		// If we are not forced to load a specific user we load the one that is logged in
194
-		if ($user === null) {
195
-			$user = '';
196
-		} elseif ($user == "" && \OC::$server->getUserSession()->isLoggedIn()) {
197
-			$user = OC_User::getUser();
198
-		}
199
-
200
-		// load all filesystem apps before, so no setup-hook gets lost
201
-		OC_App::loadApps(['filesystem']);
202
-
203
-		// the filesystem will finish when $user is not empty,
204
-		// mark fs setup here to avoid doing the setup from loading
205
-		// OC_Filesystem
206
-		if ($user != '') {
207
-			self::$fsSetup = true;
208
-		}
209
-
210
-		\OC\Files\Filesystem::initMountManager();
211
-
212
-		$prevLogging = \OC\Files\Filesystem::logWarningWhenAddingStorageWrapper(false);
213
-		\OC\Files\Filesystem::addStorageWrapper('mount_options', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) {
214
-			if ($storage->instanceOfStorage('\OC\Files\Storage\Common')) {
215
-				/** @var \OC\Files\Storage\Common $storage */
216
-				$storage->setMountOptions($mount->getOptions());
217
-			}
218
-			return $storage;
219
-		});
220
-
221
-		\OC\Files\Filesystem::addStorageWrapper('enable_sharing', function ($mountPoint, \OCP\Files\Storage\IStorage $storage, \OCP\Files\Mount\IMountPoint $mount) {
222
-			if (!$mount->getOption('enable_sharing', true)) {
223
-				return new \OC\Files\Storage\Wrapper\PermissionsMask([
224
-					'storage' => $storage,
225
-					'mask' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_SHARE
226
-				]);
227
-			}
228
-			return $storage;
229
-		});
230
-
231
-		// install storage availability wrapper, before most other wrappers
232
-		\OC\Files\Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, \OCP\Files\Storage\IStorage $storage) {
233
-			if (!$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) {
234
-				return new \OC\Files\Storage\Wrapper\Availability(['storage' => $storage]);
235
-			}
236
-			return $storage;
237
-		});
238
-
239
-		\OC\Files\Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) {
240
-			if ($mount->getOption('encoding_compatibility', false) && !$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) {
241
-				return new \OC\Files\Storage\Wrapper\Encoding(['storage' => $storage]);
242
-			}
243
-			return $storage;
244
-		});
245
-
246
-		\OC\Files\Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage) {
247
-			// set up quota for home storages, even for other users
248
-			// which can happen when using sharing
249
-
250
-			/**
251
-			 * @var \OC\Files\Storage\Storage $storage
252
-			 */
253
-			if ($storage->instanceOfStorage('\OC\Files\Storage\Home')
254
-				|| $storage->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage')
255
-			) {
256
-				/** @var \OC\Files\Storage\Home $storage */
257
-				if (is_object($storage->getUser())) {
258
-					$quota = OC_Util::getUserQuota($storage->getUser());
259
-					if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) {
260
-						return new \OC\Files\Storage\Wrapper\Quota(['storage' => $storage, 'quota' => $quota, 'root' => 'files']);
261
-					}
262
-				}
263
-			}
264
-
265
-			return $storage;
266
-		});
267
-
268
-		\OC\Files\Filesystem::addStorageWrapper('readonly', function ($mountPoint, \OCP\Files\Storage\IStorage $storage, \OCP\Files\Mount\IMountPoint $mount) {
269
-			/*
76
+    public static $scripts = [];
77
+    public static $styles = [];
78
+    public static $headers = [];
79
+    private static $rootMounted = false;
80
+    private static $fsSetup = false;
81
+
82
+    /** @var array Local cache of version.php */
83
+    private static $versionCache = null;
84
+
85
+    protected static function getAppManager() {
86
+        return \OC::$server->getAppManager();
87
+    }
88
+
89
+    private static function initLocalStorageRootFS() {
90
+        // mount local file backend as root
91
+        $configDataDirectory = \OC::$server->getSystemConfig()->getValue("datadirectory", OC::$SERVERROOT . "/data");
92
+        //first set up the local "root" storage
93
+        \OC\Files\Filesystem::initMountManager();
94
+        if (!self::$rootMounted) {
95
+            \OC\Files\Filesystem::mount(LocalRootStorage::class, ['datadir' => $configDataDirectory], '/');
96
+            self::$rootMounted = true;
97
+        }
98
+    }
99
+
100
+    /**
101
+     * mounting an object storage as the root fs will in essence remove the
102
+     * necessity of a data folder being present.
103
+     * TODO make home storage aware of this and use the object storage instead of local disk access
104
+     *
105
+     * @param array $config containing 'class' and optional 'arguments'
106
+     * @suppress PhanDeprecatedFunction
107
+     */
108
+    private static function initObjectStoreRootFS($config) {
109
+        // check misconfiguration
110
+        if (empty($config['class'])) {
111
+            \OCP\Util::writeLog('files', 'No class given for objectstore', ILogger::ERROR);
112
+        }
113
+        if (!isset($config['arguments'])) {
114
+            $config['arguments'] = [];
115
+        }
116
+
117
+        // instantiate object store implementation
118
+        $name = $config['class'];
119
+        if (strpos($name, 'OCA\\') === 0 && substr_count($name, '\\') >= 2) {
120
+            $segments = explode('\\', $name);
121
+            OC_App::loadApp(strtolower($segments[1]));
122
+        }
123
+        $config['arguments']['objectstore'] = new $config['class']($config['arguments']);
124
+        // mount with plain / root object store implementation
125
+        $config['class'] = '\OC\Files\ObjectStore\ObjectStoreStorage';
126
+
127
+        // mount object storage as root
128
+        \OC\Files\Filesystem::initMountManager();
129
+        if (!self::$rootMounted) {
130
+            \OC\Files\Filesystem::mount($config['class'], $config['arguments'], '/');
131
+            self::$rootMounted = true;
132
+        }
133
+    }
134
+
135
+    /**
136
+     * mounting an object storage as the root fs will in essence remove the
137
+     * necessity of a data folder being present.
138
+     *
139
+     * @param array $config containing 'class' and optional 'arguments'
140
+     * @suppress PhanDeprecatedFunction
141
+     */
142
+    private static function initObjectStoreMultibucketRootFS($config) {
143
+        // check misconfiguration
144
+        if (empty($config['class'])) {
145
+            \OCP\Util::writeLog('files', 'No class given for objectstore', ILogger::ERROR);
146
+        }
147
+        if (!isset($config['arguments'])) {
148
+            $config['arguments'] = [];
149
+        }
150
+
151
+        // instantiate object store implementation
152
+        $name = $config['class'];
153
+        if (strpos($name, 'OCA\\') === 0 && substr_count($name, '\\') >= 2) {
154
+            $segments = explode('\\', $name);
155
+            OC_App::loadApp(strtolower($segments[1]));
156
+        }
157
+
158
+        if (!isset($config['arguments']['bucket'])) {
159
+            $config['arguments']['bucket'] = '';
160
+        }
161
+        // put the root FS always in first bucket for multibucket configuration
162
+        $config['arguments']['bucket'] .= '0';
163
+
164
+        $config['arguments']['objectstore'] = new $config['class']($config['arguments']);
165
+        // mount with plain / root object store implementation
166
+        $config['class'] = '\OC\Files\ObjectStore\ObjectStoreStorage';
167
+
168
+        // mount object storage as root
169
+        \OC\Files\Filesystem::initMountManager();
170
+        if (!self::$rootMounted) {
171
+            \OC\Files\Filesystem::mount($config['class'], $config['arguments'], '/');
172
+            self::$rootMounted = true;
173
+        }
174
+    }
175
+
176
+    /**
177
+     * Can be set up
178
+     *
179
+     * @param string $user
180
+     * @return boolean
181
+     * @description configure the initial filesystem based on the configuration
182
+     * @suppress PhanDeprecatedFunction
183
+     * @suppress PhanAccessMethodInternal
184
+     */
185
+    public static function setupFS($user = '') {
186
+        //setting up the filesystem twice can only lead to trouble
187
+        if (self::$fsSetup) {
188
+            return false;
189
+        }
190
+
191
+        \OC::$server->getEventLogger()->start('setup_fs', 'Setup filesystem');
192
+
193
+        // If we are not forced to load a specific user we load the one that is logged in
194
+        if ($user === null) {
195
+            $user = '';
196
+        } elseif ($user == "" && \OC::$server->getUserSession()->isLoggedIn()) {
197
+            $user = OC_User::getUser();
198
+        }
199
+
200
+        // load all filesystem apps before, so no setup-hook gets lost
201
+        OC_App::loadApps(['filesystem']);
202
+
203
+        // the filesystem will finish when $user is not empty,
204
+        // mark fs setup here to avoid doing the setup from loading
205
+        // OC_Filesystem
206
+        if ($user != '') {
207
+            self::$fsSetup = true;
208
+        }
209
+
210
+        \OC\Files\Filesystem::initMountManager();
211
+
212
+        $prevLogging = \OC\Files\Filesystem::logWarningWhenAddingStorageWrapper(false);
213
+        \OC\Files\Filesystem::addStorageWrapper('mount_options', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) {
214
+            if ($storage->instanceOfStorage('\OC\Files\Storage\Common')) {
215
+                /** @var \OC\Files\Storage\Common $storage */
216
+                $storage->setMountOptions($mount->getOptions());
217
+            }
218
+            return $storage;
219
+        });
220
+
221
+        \OC\Files\Filesystem::addStorageWrapper('enable_sharing', function ($mountPoint, \OCP\Files\Storage\IStorage $storage, \OCP\Files\Mount\IMountPoint $mount) {
222
+            if (!$mount->getOption('enable_sharing', true)) {
223
+                return new \OC\Files\Storage\Wrapper\PermissionsMask([
224
+                    'storage' => $storage,
225
+                    'mask' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_SHARE
226
+                ]);
227
+            }
228
+            return $storage;
229
+        });
230
+
231
+        // install storage availability wrapper, before most other wrappers
232
+        \OC\Files\Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, \OCP\Files\Storage\IStorage $storage) {
233
+            if (!$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) {
234
+                return new \OC\Files\Storage\Wrapper\Availability(['storage' => $storage]);
235
+            }
236
+            return $storage;
237
+        });
238
+
239
+        \OC\Files\Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) {
240
+            if ($mount->getOption('encoding_compatibility', false) && !$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) {
241
+                return new \OC\Files\Storage\Wrapper\Encoding(['storage' => $storage]);
242
+            }
243
+            return $storage;
244
+        });
245
+
246
+        \OC\Files\Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage) {
247
+            // set up quota for home storages, even for other users
248
+            // which can happen when using sharing
249
+
250
+            /**
251
+             * @var \OC\Files\Storage\Storage $storage
252
+             */
253
+            if ($storage->instanceOfStorage('\OC\Files\Storage\Home')
254
+                || $storage->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage')
255
+            ) {
256
+                /** @var \OC\Files\Storage\Home $storage */
257
+                if (is_object($storage->getUser())) {
258
+                    $quota = OC_Util::getUserQuota($storage->getUser());
259
+                    if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) {
260
+                        return new \OC\Files\Storage\Wrapper\Quota(['storage' => $storage, 'quota' => $quota, 'root' => 'files']);
261
+                    }
262
+                }
263
+            }
264
+
265
+            return $storage;
266
+        });
267
+
268
+        \OC\Files\Filesystem::addStorageWrapper('readonly', function ($mountPoint, \OCP\Files\Storage\IStorage $storage, \OCP\Files\Mount\IMountPoint $mount) {
269
+            /*
270 270
 			 * Do not allow any operations that modify the storage
271 271
 			 */
272
-			if ($mount->getOption('readonly', false)) {
273
-				return new \OC\Files\Storage\Wrapper\PermissionsMask([
274
-					'storage' => $storage,
275
-					'mask' => \OCP\Constants::PERMISSION_ALL & ~(
276
-						\OCP\Constants::PERMISSION_UPDATE |
277
-						\OCP\Constants::PERMISSION_CREATE |
278
-						\OCP\Constants::PERMISSION_DELETE
279
-					),
280
-				]);
281
-			}
282
-			return $storage;
283
-		});
284
-
285
-		OC_Hook::emit('OC_Filesystem', 'preSetup', ['user' => $user]);
286
-
287
-		\OC\Files\Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
288
-
289
-		//check if we are using an object storage
290
-		$objectStore = \OC::$server->getSystemConfig()->getValue('objectstore', null);
291
-		$objectStoreMultibucket = \OC::$server->getSystemConfig()->getValue('objectstore_multibucket', null);
292
-
293
-		// use the same order as in ObjectHomeMountProvider
294
-		if (isset($objectStoreMultibucket)) {
295
-			self::initObjectStoreMultibucketRootFS($objectStoreMultibucket);
296
-		} elseif (isset($objectStore)) {
297
-			self::initObjectStoreRootFS($objectStore);
298
-		} else {
299
-			self::initLocalStorageRootFS();
300
-		}
301
-
302
-		/** @var \OCP\Files\Config\IMountProviderCollection $mountProviderCollection */
303
-		$mountProviderCollection = \OC::$server->query(\OCP\Files\Config\IMountProviderCollection::class);
304
-		$rootMountProviders = $mountProviderCollection->getRootMounts();
305
-
306
-		/** @var \OC\Files\Mount\Manager $mountManager */
307
-		$mountManager = \OC\Files\Filesystem::getMountManager();
308
-		foreach ($rootMountProviders as $rootMountProvider) {
309
-			$mountManager->addMount($rootMountProvider);
310
-		}
311
-
312
-		if ($user != '' && !\OC::$server->getUserManager()->userExists($user)) {
313
-			\OC::$server->getEventLogger()->end('setup_fs');
314
-			return false;
315
-		}
316
-
317
-		//if we aren't logged in, there is no use to set up the filesystem
318
-		if ($user != "") {
319
-			$userDir = '/' . $user . '/files';
320
-
321
-			//jail the user into his "home" directory
322
-			\OC\Files\Filesystem::init($user, $userDir);
323
-
324
-			OC_Hook::emit('OC_Filesystem', 'setup', ['user' => $user, 'user_dir' => $userDir]);
325
-		}
326
-		\OC::$server->getEventLogger()->end('setup_fs');
327
-		return true;
328
-	}
329
-
330
-	/**
331
-	 * check if a password is required for each public link
332
-	 *
333
-	 * @return boolean
334
-	 * @suppress PhanDeprecatedFunction
335
-	 */
336
-	public static function isPublicLinkPasswordRequired() {
337
-		$enforcePassword = \OC::$server->getConfig()->getAppValue('core', 'shareapi_enforce_links_password', 'no');
338
-		return $enforcePassword === 'yes';
339
-	}
340
-
341
-	/**
342
-	 * check if sharing is disabled for the current user
343
-	 * @param IConfig $config
344
-	 * @param IGroupManager $groupManager
345
-	 * @param IUser|null $user
346
-	 * @return bool
347
-	 */
348
-	public static function isSharingDisabledForUser(IConfig $config, IGroupManager $groupManager, $user) {
349
-		if ($config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') {
350
-			$groupsList = $config->getAppValue('core', 'shareapi_exclude_groups_list', '');
351
-			$excludedGroups = json_decode($groupsList);
352
-			if (is_null($excludedGroups)) {
353
-				$excludedGroups = explode(',', $groupsList);
354
-				$newValue = json_encode($excludedGroups);
355
-				$config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue);
356
-			}
357
-			$usersGroups = $groupManager->getUserGroupIds($user);
358
-			if (!empty($usersGroups)) {
359
-				$remainingGroups = array_diff($usersGroups, $excludedGroups);
360
-				// if the user is only in groups which are disabled for sharing then
361
-				// sharing is also disabled for the user
362
-				if (empty($remainingGroups)) {
363
-					return true;
364
-				}
365
-			}
366
-		}
367
-		return false;
368
-	}
369
-
370
-	/**
371
-	 * check if share API enforces a default expire date
372
-	 *
373
-	 * @return boolean
374
-	 * @suppress PhanDeprecatedFunction
375
-	 */
376
-	public static function isDefaultExpireDateEnforced() {
377
-		$isDefaultExpireDateEnabled = \OC::$server->getConfig()->getAppValue('core', 'shareapi_default_expire_date', 'no');
378
-		$enforceDefaultExpireDate = false;
379
-		if ($isDefaultExpireDateEnabled === 'yes') {
380
-			$value = \OC::$server->getConfig()->getAppValue('core', 'shareapi_enforce_expire_date', 'no');
381
-			$enforceDefaultExpireDate = $value === 'yes';
382
-		}
383
-
384
-		return $enforceDefaultExpireDate;
385
-	}
386
-
387
-	/**
388
-	 * Get the quota of a user
389
-	 *
390
-	 * @param IUser|null $user
391
-	 * @return float Quota bytes
392
-	 */
393
-	public static function getUserQuota(?IUser $user) {
394
-		if (is_null($user)) {
395
-			return \OCP\Files\FileInfo::SPACE_UNLIMITED;
396
-		}
397
-		$userQuota = $user->getQuota();
398
-		if ($userQuota === 'none') {
399
-			return \OCP\Files\FileInfo::SPACE_UNLIMITED;
400
-		}
401
-		return OC_Helper::computerFileSize($userQuota);
402
-	}
403
-
404
-	/**
405
-	 * copies the skeleton to the users /files
406
-	 *
407
-	 * @param string $userId
408
-	 * @param \OCP\Files\Folder $userDirectory
409
-	 * @throws \OCP\Files\NotFoundException
410
-	 * @throws \OCP\Files\NotPermittedException
411
-	 * @suppress PhanDeprecatedFunction
412
-	 */
413
-	public static function copySkeleton($userId, \OCP\Files\Folder $userDirectory) {
414
-		$plainSkeletonDirectory = \OC::$server->getConfig()->getSystemValue('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton');
415
-		$userLang = \OC::$server->getL10NFactory()->findLanguage();
416
-		$skeletonDirectory = str_replace('{lang}', $userLang, $plainSkeletonDirectory);
417
-
418
-		if (!file_exists($skeletonDirectory)) {
419
-			$dialectStart = strpos($userLang, '_');
420
-			if ($dialectStart !== false) {
421
-				$skeletonDirectory = str_replace('{lang}', substr($userLang, 0, $dialectStart), $plainSkeletonDirectory);
422
-			}
423
-			if ($dialectStart === false || !file_exists($skeletonDirectory)) {
424
-				$skeletonDirectory = str_replace('{lang}', 'default', $plainSkeletonDirectory);
425
-			}
426
-			if (!file_exists($skeletonDirectory)) {
427
-				$skeletonDirectory = '';
428
-			}
429
-		}
430
-
431
-		$instanceId = \OC::$server->getConfig()->getSystemValue('instanceid', '');
432
-
433
-		if ($instanceId === null) {
434
-			throw new \RuntimeException('no instance id!');
435
-		}
436
-		$appdata = 'appdata_' . $instanceId;
437
-		if ($userId === $appdata) {
438
-			throw new \RuntimeException('username is reserved name: ' . $appdata);
439
-		}
440
-
441
-		if (!empty($skeletonDirectory)) {
442
-			\OCP\Util::writeLog(
443
-				'files_skeleton',
444
-				'copying skeleton for '.$userId.' from '.$skeletonDirectory.' to '.$userDirectory->getFullPath('/'),
445
-				ILogger::DEBUG
446
-			);
447
-			self::copyr($skeletonDirectory, $userDirectory);
448
-			// update the file cache
449
-			$userDirectory->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE);
450
-		}
451
-	}
452
-
453
-	/**
454
-	 * copies a directory recursively by using streams
455
-	 *
456
-	 * @param string $source
457
-	 * @param \OCP\Files\Folder $target
458
-	 * @return void
459
-	 */
460
-	public static function copyr($source, \OCP\Files\Folder $target) {
461
-		$logger = \OC::$server->getLogger();
462
-
463
-		// Verify if folder exists
464
-		$dir = opendir($source);
465
-		if ($dir === false) {
466
-			$logger->error(sprintf('Could not opendir "%s"', $source), ['app' => 'core']);
467
-			return;
468
-		}
469
-
470
-		// Copy the files
471
-		while (false !== ($file = readdir($dir))) {
472
-			if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
473
-				if (is_dir($source . '/' . $file)) {
474
-					$child = $target->newFolder($file);
475
-					self::copyr($source . '/' . $file, $child);
476
-				} else {
477
-					$child = $target->newFile($file);
478
-					$sourceStream = fopen($source . '/' . $file, 'r');
479
-					if ($sourceStream === false) {
480
-						$logger->error(sprintf('Could not fopen "%s"', $source . '/' . $file), ['app' => 'core']);
481
-						closedir($dir);
482
-						return;
483
-					}
484
-					stream_copy_to_stream($sourceStream, $child->fopen('w'));
485
-				}
486
-			}
487
-		}
488
-		closedir($dir);
489
-	}
490
-
491
-	/**
492
-	 * @return void
493
-	 * @suppress PhanUndeclaredMethod
494
-	 */
495
-	public static function tearDownFS() {
496
-		\OC\Files\Filesystem::tearDown();
497
-		\OC::$server->getRootFolder()->clearCache();
498
-		self::$fsSetup = false;
499
-		self::$rootMounted = false;
500
-	}
501
-
502
-	/**
503
-	 * get the current installed version of ownCloud
504
-	 *
505
-	 * @return array
506
-	 */
507
-	public static function getVersion() {
508
-		OC_Util::loadVersion();
509
-		return self::$versionCache['OC_Version'];
510
-	}
511
-
512
-	/**
513
-	 * get the current installed version string of ownCloud
514
-	 *
515
-	 * @return string
516
-	 */
517
-	public static function getVersionString() {
518
-		OC_Util::loadVersion();
519
-		return self::$versionCache['OC_VersionString'];
520
-	}
521
-
522
-	/**
523
-	 * @deprecated the value is of no use anymore
524
-	 * @return string
525
-	 */
526
-	public static function getEditionString() {
527
-		return '';
528
-	}
529
-
530
-	/**
531
-	 * @description get the update channel of the current installed of ownCloud.
532
-	 * @return string
533
-	 */
534
-	public static function getChannel() {
535
-		OC_Util::loadVersion();
536
-		return \OC::$server->getConfig()->getSystemValue('updater.release.channel', self::$versionCache['OC_Channel']);
537
-	}
538
-
539
-	/**
540
-	 * @description get the build number of the current installed of ownCloud.
541
-	 * @return string
542
-	 */
543
-	public static function getBuild() {
544
-		OC_Util::loadVersion();
545
-		return self::$versionCache['OC_Build'];
546
-	}
547
-
548
-	/**
549
-	 * @description load the version.php into the session as cache
550
-	 * @suppress PhanUndeclaredVariable
551
-	 */
552
-	private static function loadVersion() {
553
-		if (self::$versionCache !== null) {
554
-			return;
555
-		}
556
-
557
-		$timestamp = filemtime(OC::$SERVERROOT . '/version.php');
558
-		require OC::$SERVERROOT . '/version.php';
559
-		/** @var int $timestamp */
560
-		self::$versionCache['OC_Version_Timestamp'] = $timestamp;
561
-		/** @var string $OC_Version */
562
-		self::$versionCache['OC_Version'] = $OC_Version;
563
-		/** @var string $OC_VersionString */
564
-		self::$versionCache['OC_VersionString'] = $OC_VersionString;
565
-		/** @var string $OC_Build */
566
-		self::$versionCache['OC_Build'] = $OC_Build;
567
-
568
-		/** @var string $OC_Channel */
569
-		self::$versionCache['OC_Channel'] = $OC_Channel;
570
-	}
571
-
572
-	/**
573
-	 * generates a path for JS/CSS files. If no application is provided it will create the path for core.
574
-	 *
575
-	 * @param string $application application to get the files from
576
-	 * @param string $directory directory within this application (css, js, vendor, etc)
577
-	 * @param string $file the file inside of the above folder
578
-	 * @return string the path
579
-	 */
580
-	private static function generatePath($application, $directory, $file) {
581
-		if (is_null($file)) {
582
-			$file = $application;
583
-			$application = "";
584
-		}
585
-		if (!empty($application)) {
586
-			return "$application/$directory/$file";
587
-		} else {
588
-			return "$directory/$file";
589
-		}
590
-	}
591
-
592
-	/**
593
-	 * add a javascript file
594
-	 *
595
-	 * @param string $application application id
596
-	 * @param string|null $file filename
597
-	 * @param bool $prepend prepend the Script to the beginning of the list
598
-	 * @return void
599
-	 */
600
-	public static function addScript($application, $file = null, $prepend = false) {
601
-		$path = OC_Util::generatePath($application, 'js', $file);
602
-
603
-		// core js files need separate handling
604
-		if ($application !== 'core' && $file !== null) {
605
-			self::addTranslations($application);
606
-		}
607
-		self::addExternalResource($application, $prepend, $path, "script");
608
-	}
609
-
610
-	/**
611
-	 * add a javascript file from the vendor sub folder
612
-	 *
613
-	 * @param string $application application id
614
-	 * @param string|null $file filename
615
-	 * @param bool $prepend prepend the Script to the beginning of the list
616
-	 * @return void
617
-	 */
618
-	public static function addVendorScript($application, $file = null, $prepend = false) {
619
-		$path = OC_Util::generatePath($application, 'vendor', $file);
620
-		self::addExternalResource($application, $prepend, $path, "script");
621
-	}
622
-
623
-	/**
624
-	 * add a translation JS file
625
-	 *
626
-	 * @param string $application application id
627
-	 * @param string|null $languageCode language code, defaults to the current language
628
-	 * @param bool|null $prepend prepend the Script to the beginning of the list
629
-	 */
630
-	public static function addTranslations($application, $languageCode = null, $prepend = false) {
631
-		if (is_null($languageCode)) {
632
-			$languageCode = \OC::$server->getL10NFactory()->findLanguage($application);
633
-		}
634
-		if (!empty($application)) {
635
-			$path = "$application/l10n/$languageCode";
636
-		} else {
637
-			$path = "l10n/$languageCode";
638
-		}
639
-		self::addExternalResource($application, $prepend, $path, "script");
640
-	}
641
-
642
-	/**
643
-	 * add a css file
644
-	 *
645
-	 * @param string $application application id
646
-	 * @param string|null $file filename
647
-	 * @param bool $prepend prepend the Style to the beginning of the list
648
-	 * @return void
649
-	 */
650
-	public static function addStyle($application, $file = null, $prepend = false) {
651
-		$path = OC_Util::generatePath($application, 'css', $file);
652
-		self::addExternalResource($application, $prepend, $path, "style");
653
-	}
654
-
655
-	/**
656
-	 * add a css file from the vendor sub folder
657
-	 *
658
-	 * @param string $application application id
659
-	 * @param string|null $file filename
660
-	 * @param bool $prepend prepend the Style to the beginning of the list
661
-	 * @return void
662
-	 */
663
-	public static function addVendorStyle($application, $file = null, $prepend = false) {
664
-		$path = OC_Util::generatePath($application, 'vendor', $file);
665
-		self::addExternalResource($application, $prepend, $path, "style");
666
-	}
667
-
668
-	/**
669
-	 * add an external resource css/js file
670
-	 *
671
-	 * @param string $application application id
672
-	 * @param bool $prepend prepend the file to the beginning of the list
673
-	 * @param string $path
674
-	 * @param string $type (script or style)
675
-	 * @return void
676
-	 */
677
-	private static function addExternalResource($application, $prepend, $path, $type = "script") {
678
-		if ($type === "style") {
679
-			if (!in_array($path, self::$styles)) {
680
-				if ($prepend === true) {
681
-					array_unshift(self::$styles, $path);
682
-				} else {
683
-					self::$styles[] = $path;
684
-				}
685
-			}
686
-		} elseif ($type === "script") {
687
-			if (!in_array($path, self::$scripts)) {
688
-				if ($prepend === true) {
689
-					array_unshift(self::$scripts, $path);
690
-				} else {
691
-					self::$scripts [] = $path;
692
-				}
693
-			}
694
-		}
695
-	}
696
-
697
-	/**
698
-	 * Add a custom element to the header
699
-	 * If $text is null then the element will be written as empty element.
700
-	 * So use "" to get a closing tag.
701
-	 * @param string $tag tag name of the element
702
-	 * @param array $attributes array of attributes for the element
703
-	 * @param string $text the text content for the element
704
-	 * @param bool $prepend prepend the header to the beginning of the list
705
-	 */
706
-	public static function addHeader($tag, $attributes, $text = null, $prepend = false) {
707
-		$header = [
708
-			'tag' => $tag,
709
-			'attributes' => $attributes,
710
-			'text' => $text
711
-		];
712
-		if ($prepend === true) {
713
-			array_unshift(self::$headers, $header);
714
-		} else {
715
-			self::$headers[] = $header;
716
-		}
717
-	}
718
-
719
-	/**
720
-	 * check if the current server configuration is suitable for ownCloud
721
-	 *
722
-	 * @param \OC\SystemConfig $config
723
-	 * @return array arrays with error messages and hints
724
-	 */
725
-	public static function checkServer(\OC\SystemConfig $config) {
726
-		$l = \OC::$server->getL10N('lib');
727
-		$errors = [];
728
-		$CONFIG_DATADIRECTORY = $config->getValue('datadirectory', OC::$SERVERROOT . '/data');
729
-
730
-		if (!self::needUpgrade($config) && $config->getValue('installed', false)) {
731
-			// this check needs to be done every time
732
-			$errors = self::checkDataDirectoryValidity($CONFIG_DATADIRECTORY);
733
-		}
734
-
735
-		// Assume that if checkServer() succeeded before in this session, then all is fine.
736
-		if (\OC::$server->getSession()->exists('checkServer_succeeded') && \OC::$server->getSession()->get('checkServer_succeeded')) {
737
-			return $errors;
738
-		}
739
-
740
-		$webServerRestart = false;
741
-		$setup = new \OC\Setup(
742
-			$config,
743
-			\OC::$server->get(IniGetWrapper::class),
744
-			\OC::$server->getL10N('lib'),
745
-			\OC::$server->query(\OCP\Defaults::class),
746
-			\OC::$server->getLogger(),
747
-			\OC::$server->getSecureRandom(),
748
-			\OC::$server->query(\OC\Installer::class)
749
-		);
750
-
751
-		$urlGenerator = \OC::$server->getURLGenerator();
752
-
753
-		$availableDatabases = $setup->getSupportedDatabases();
754
-		if (empty($availableDatabases)) {
755
-			$errors[] = [
756
-				'error' => $l->t('No database drivers (sqlite, mysql, or postgresql) installed.'),
757
-				'hint' => '' //TODO: sane hint
758
-			];
759
-			$webServerRestart = true;
760
-		}
761
-
762
-		// Check if config folder is writable.
763
-		if (!OC_Helper::isReadOnlyConfigEnabled()) {
764
-			if (!is_writable(OC::$configDir) or !is_readable(OC::$configDir)) {
765
-				$errors[] = [
766
-					'error' => $l->t('Cannot write into "config" directory'),
767
-					'hint' => $l->t('This can usually be fixed by giving the webserver write access to the config directory. See %s',
768
-						[ $urlGenerator->linkToDocs('admin-dir_permissions') ]) . '. '
769
-						. $l->t('Or, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it. See %s',
770
-						[ $urlGenerator->linkToDocs('admin-config') ])
771
-				];
772
-			}
773
-		}
774
-
775
-		// Check if there is a writable install folder.
776
-		if ($config->getValue('appstoreenabled', true)) {
777
-			if (OC_App::getInstallPath() === null
778
-				|| !is_writable(OC_App::getInstallPath())
779
-				|| !is_readable(OC_App::getInstallPath())
780
-			) {
781
-				$errors[] = [
782
-					'error' => $l->t('Cannot write into "apps" directory'),
783
-					'hint' => $l->t('This can usually be fixed by giving the webserver write access to the apps directory'
784
-						. ' or disabling the appstore in the config file.')
785
-				];
786
-			}
787
-		}
788
-		// Create root dir.
789
-		if ($config->getValue('installed', false)) {
790
-			if (!is_dir($CONFIG_DATADIRECTORY)) {
791
-				$success = @mkdir($CONFIG_DATADIRECTORY);
792
-				if ($success) {
793
-					$errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
794
-				} else {
795
-					$errors[] = [
796
-						'error' => $l->t('Cannot create "data" directory'),
797
-						'hint' => $l->t('This can usually be fixed by giving the webserver write access to the root directory. See %s',
798
-							[$urlGenerator->linkToDocs('admin-dir_permissions')])
799
-					];
800
-				}
801
-			} elseif (!is_writable($CONFIG_DATADIRECTORY) or !is_readable($CONFIG_DATADIRECTORY)) {
802
-				// is_writable doesn't work for NFS mounts, so try to write a file and check if it exists.
803
-				$testFile = sprintf('%s/%s.tmp', $CONFIG_DATADIRECTORY, uniqid('data_dir_writability_test_'));
804
-				$handle = fopen($testFile, 'w');
805
-				if (!$handle || fwrite($handle, 'Test write operation') === false) {
806
-					$permissionsHint = $l->t('Permissions can usually be fixed by giving the webserver write access to the root directory. See %s.',
807
-						[$urlGenerator->linkToDocs('admin-dir_permissions')]);
808
-					$errors[] = [
809
-						'error' => 'Your data directory is not writable',
810
-						'hint' => $permissionsHint
811
-					];
812
-				} else {
813
-					fclose($handle);
814
-					unlink($testFile);
815
-				}
816
-			} else {
817
-				$errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
818
-			}
819
-		}
820
-
821
-		if (!OC_Util::isSetLocaleWorking()) {
822
-			$errors[] = [
823
-				'error' => $l->t('Setting locale to %s failed',
824
-					['en_US.UTF-8/fr_FR.UTF-8/es_ES.UTF-8/de_DE.UTF-8/ru_RU.UTF-8/'
825
-						. 'pt_BR.UTF-8/it_IT.UTF-8/ja_JP.UTF-8/zh_CN.UTF-8']),
826
-				'hint' => $l->t('Please install one of these locales on your system and restart your webserver.')
827
-			];
828
-		}
829
-
830
-		// Contains the dependencies that should be checked against
831
-		// classes = class_exists
832
-		// functions = function_exists
833
-		// defined = defined
834
-		// ini = ini_get
835
-		// If the dependency is not found the missing module name is shown to the EndUser
836
-		// When adding new checks always verify that they pass on Travis as well
837
-		// for ini settings, see https://github.com/owncloud/administration/blob/master/travis-ci/custom.ini
838
-		$dependencies = [
839
-			'classes' => [
840
-				'ZipArchive' => 'zip',
841
-				'DOMDocument' => 'dom',
842
-				'XMLWriter' => 'XMLWriter',
843
-				'XMLReader' => 'XMLReader',
844
-			],
845
-			'functions' => [
846
-				'xml_parser_create' => 'libxml',
847
-				'mb_strcut' => 'mbstring',
848
-				'ctype_digit' => 'ctype',
849
-				'json_encode' => 'JSON',
850
-				'gd_info' => 'GD',
851
-				'gzencode' => 'zlib',
852
-				'iconv' => 'iconv',
853
-				'simplexml_load_string' => 'SimpleXML',
854
-				'hash' => 'HASH Message Digest Framework',
855
-				'curl_init' => 'cURL',
856
-				'openssl_verify' => 'OpenSSL',
857
-			],
858
-			'defined' => [
859
-				'PDO::ATTR_DRIVER_NAME' => 'PDO'
860
-			],
861
-			'ini' => [
862
-				'default_charset' => 'UTF-8',
863
-			],
864
-		];
865
-		$missingDependencies = [];
866
-		$invalidIniSettings = [];
867
-
868
-		$iniWrapper = \OC::$server->get(IniGetWrapper::class);
869
-		foreach ($dependencies['classes'] as $class => $module) {
870
-			if (!class_exists($class)) {
871
-				$missingDependencies[] = $module;
872
-			}
873
-		}
874
-		foreach ($dependencies['functions'] as $function => $module) {
875
-			if (!function_exists($function)) {
876
-				$missingDependencies[] = $module;
877
-			}
878
-		}
879
-		foreach ($dependencies['defined'] as $defined => $module) {
880
-			if (!defined($defined)) {
881
-				$missingDependencies[] = $module;
882
-			}
883
-		}
884
-		foreach ($dependencies['ini'] as $setting => $expected) {
885
-			if (is_bool($expected)) {
886
-				if ($iniWrapper->getBool($setting) !== $expected) {
887
-					$invalidIniSettings[] = [$setting, $expected];
888
-				}
889
-			}
890
-			if (is_int($expected)) {
891
-				if ($iniWrapper->getNumeric($setting) !== $expected) {
892
-					$invalidIniSettings[] = [$setting, $expected];
893
-				}
894
-			}
895
-			if (is_string($expected)) {
896
-				if (strtolower($iniWrapper->getString($setting)) !== strtolower($expected)) {
897
-					$invalidIniSettings[] = [$setting, $expected];
898
-				}
899
-			}
900
-		}
901
-
902
-		foreach ($missingDependencies as $missingDependency) {
903
-			$errors[] = [
904
-				'error' => $l->t('PHP module %s not installed.', [$missingDependency]),
905
-				'hint' => $l->t('Please ask your server administrator to install the module.'),
906
-			];
907
-			$webServerRestart = true;
908
-		}
909
-		foreach ($invalidIniSettings as $setting) {
910
-			if (is_bool($setting[1])) {
911
-				$setting[1] = $setting[1] ? 'on' : 'off';
912
-			}
913
-			$errors[] = [
914
-				'error' => $l->t('PHP setting "%s" is not set to "%s".', [$setting[0], var_export($setting[1], true)]),
915
-				'hint' => $l->t('Adjusting this setting in php.ini will make Nextcloud run again')
916
-			];
917
-			$webServerRestart = true;
918
-		}
919
-
920
-		/**
921
-		 * The mbstring.func_overload check can only be performed if the mbstring
922
-		 * module is installed as it will return null if the checking setting is
923
-		 * not available and thus a check on the boolean value fails.
924
-		 *
925
-		 * TODO: Should probably be implemented in the above generic dependency
926
-		 *       check somehow in the long-term.
927
-		 */
928
-		if ($iniWrapper->getBool('mbstring.func_overload') !== null &&
929
-			$iniWrapper->getBool('mbstring.func_overload') === true) {
930
-			$errors[] = [
931
-				'error' => $l->t('mbstring.func_overload is set to "%s" instead of the expected value "0"', [$iniWrapper->getString('mbstring.func_overload')]),
932
-				'hint' => $l->t('To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini')
933
-			];
934
-		}
935
-
936
-		if (function_exists('xml_parser_create') &&
937
-			LIBXML_LOADED_VERSION < 20700) {
938
-			$version = LIBXML_LOADED_VERSION;
939
-			$major = floor($version / 10000);
940
-			$version -= ($major * 10000);
941
-			$minor = floor($version / 100);
942
-			$version -= ($minor * 100);
943
-			$patch = $version;
944
-			$errors[] = [
945
-				'error' => $l->t('libxml2 2.7.0 is at least required. Currently %s is installed.', [$major . '.' . $minor . '.' . $patch]),
946
-				'hint' => $l->t('To fix this issue update your libxml2 version and restart your web server.')
947
-			];
948
-		}
949
-
950
-		if (!self::isAnnotationsWorking()) {
951
-			$errors[] = [
952
-				'error' => $l->t('PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible.'),
953
-				'hint' => $l->t('This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.')
954
-			];
955
-		}
956
-
957
-		if (!\OC::$CLI && $webServerRestart) {
958
-			$errors[] = [
959
-				'error' => $l->t('PHP modules have been installed, but they are still listed as missing?'),
960
-				'hint' => $l->t('Please ask your server administrator to restart the web server.')
961
-			];
962
-		}
963
-
964
-		$errors = array_merge($errors, self::checkDatabaseVersion());
965
-
966
-		// Cache the result of this function
967
-		\OC::$server->getSession()->set('checkServer_succeeded', count($errors) == 0);
968
-
969
-		return $errors;
970
-	}
971
-
972
-	/**
973
-	 * Check the database version
974
-	 *
975
-	 * @return array errors array
976
-	 */
977
-	public static function checkDatabaseVersion() {
978
-		$l = \OC::$server->getL10N('lib');
979
-		$errors = [];
980
-		$dbType = \OC::$server->getSystemConfig()->getValue('dbtype', 'sqlite');
981
-		if ($dbType === 'pgsql') {
982
-			// check PostgreSQL version
983
-			try {
984
-				$result = \OC_DB::executeAudited('SHOW SERVER_VERSION');
985
-				$data = $result->fetchRow();
986
-				$result->closeCursor();
987
-				if (isset($data['server_version'])) {
988
-					$version = $data['server_version'];
989
-					if (version_compare($version, '9.0.0', '<')) {
990
-						$errors[] = [
991
-							'error' => $l->t('PostgreSQL >= 9 required'),
992
-							'hint' => $l->t('Please upgrade your database version')
993
-						];
994
-					}
995
-				}
996
-			} catch (\Doctrine\DBAL\DBALException $e) {
997
-				$logger = \OC::$server->getLogger();
998
-				$logger->warning('Error occurred while checking PostgreSQL version, assuming >= 9');
999
-				$logger->logException($e);
1000
-			}
1001
-		}
1002
-		return $errors;
1003
-	}
1004
-
1005
-	/**
1006
-	 * Check for correct file permissions of data directory
1007
-	 *
1008
-	 * @param string $dataDirectory
1009
-	 * @return array arrays with error messages and hints
1010
-	 */
1011
-	public static function checkDataDirectoryPermissions($dataDirectory) {
1012
-		if (\OC::$server->getConfig()->getSystemValue('check_data_directory_permissions', true) === false) {
1013
-			return  [];
1014
-		}
1015
-
1016
-		$perms = substr(decoct(@fileperms($dataDirectory)), -3);
1017
-		if (substr($perms, -1) !== '0') {
1018
-			chmod($dataDirectory, 0770);
1019
-			clearstatcache();
1020
-			$perms = substr(decoct(@fileperms($dataDirectory)), -3);
1021
-			if ($perms[2] !== '0') {
1022
-				$l = \OC::$server->getL10N('lib');
1023
-				return [[
1024
-					'error' => $l->t('Your data directory is readable by other users'),
1025
-					'hint' => $l->t('Please change the permissions to 0770 so that the directory cannot be listed by other users.'),
1026
-				]];
1027
-			}
1028
-		}
1029
-		return [];
1030
-	}
1031
-
1032
-	/**
1033
-	 * Check that the data directory exists and is valid by
1034
-	 * checking the existence of the ".ocdata" file.
1035
-	 *
1036
-	 * @param string $dataDirectory data directory path
1037
-	 * @return array errors found
1038
-	 */
1039
-	public static function checkDataDirectoryValidity($dataDirectory) {
1040
-		$l = \OC::$server->getL10N('lib');
1041
-		$errors = [];
1042
-		if ($dataDirectory[0] !== '/') {
1043
-			$errors[] = [
1044
-				'error' => $l->t('Your data directory must be an absolute path'),
1045
-				'hint' => $l->t('Check the value of "datadirectory" in your configuration')
1046
-			];
1047
-		}
1048
-		if (!file_exists($dataDirectory . '/.ocdata')) {
1049
-			$errors[] = [
1050
-				'error' => $l->t('Your data directory is invalid'),
1051
-				'hint' => $l->t('Ensure there is a file called ".ocdata"' .
1052
-					' in the root of the data directory.')
1053
-			];
1054
-		}
1055
-		return $errors;
1056
-	}
1057
-
1058
-	/**
1059
-	 * Check if the user is logged in, redirects to home if not. With
1060
-	 * redirect URL parameter to the request URI.
1061
-	 *
1062
-	 * @return void
1063
-	 */
1064
-	public static function checkLoggedIn() {
1065
-		// Check if we are a user
1066
-		if (!\OC::$server->getUserSession()->isLoggedIn()) {
1067
-			header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute(
1068
-						'core.login.showLoginForm',
1069
-						[
1070
-							'redirect_url' => \OC::$server->getRequest()->getRequestUri(),
1071
-						]
1072
-					)
1073
-			);
1074
-			exit();
1075
-		}
1076
-		// Redirect to 2FA challenge selection if 2FA challenge was not solved yet
1077
-		if (\OC::$server->getTwoFactorAuthManager()->needsSecondFactor(\OC::$server->getUserSession()->getUser())) {
1078
-			header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
1079
-			exit();
1080
-		}
1081
-	}
1082
-
1083
-	/**
1084
-	 * Check if the user is a admin, redirects to home if not
1085
-	 *
1086
-	 * @return void
1087
-	 */
1088
-	public static function checkAdminUser() {
1089
-		OC_Util::checkLoggedIn();
1090
-		if (!OC_User::isAdminUser(OC_User::getUser())) {
1091
-			header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php'));
1092
-			exit();
1093
-		}
1094
-	}
1095
-
1096
-	/**
1097
-	 * Returns the URL of the default page
1098
-	 * based on the system configuration and
1099
-	 * the apps visible for the current user
1100
-	 *
1101
-	 * @return string URL
1102
-	 * @suppress PhanDeprecatedFunction
1103
-	 */
1104
-	public static function getDefaultPageUrl() {
1105
-		/** @var IConfig $config */
1106
-		$config = \OC::$server->get(IConfig::class);
1107
-		$urlGenerator = \OC::$server->getURLGenerator();
1108
-		// Deny the redirect if the URL contains a @
1109
-		// This prevents unvalidated redirects like ?redirect_url=:[email protected]
1110
-		if (isset($_REQUEST['redirect_url']) && strpos($_REQUEST['redirect_url'], '@') === false) {
1111
-			$location = $urlGenerator->getAbsoluteURL(urldecode($_REQUEST['redirect_url']));
1112
-		} else {
1113
-			$defaultPage = \OC::$server->getConfig()->getAppValue('core', 'defaultpage');
1114
-			if ($defaultPage) {
1115
-				$location = $urlGenerator->getAbsoluteURL($defaultPage);
1116
-			} else {
1117
-				$appId = 'files';
1118
-				$defaultApps = explode(',', $config->getSystemValue('defaultapp', 'dashboard,files'));
1119
-
1120
-				/** @var IUserSession $userSession */
1121
-				$userSession = \OC::$server->get(IUserSession::class);
1122
-				$user = $userSession->getUser();
1123
-				if ($user) {
1124
-					$userDefaultApps = explode(',', $config->getUserValue($user->getUID(), 'core', 'defaultapp'));
1125
-					$defaultApps = array_filter(array_merge($userDefaultApps, $defaultApps));
1126
-				}
1127
-
1128
-				// find the first app that is enabled for the current user
1129
-				foreach ($defaultApps as $defaultApp) {
1130
-					$defaultApp = OC_App::cleanAppId(strip_tags($defaultApp));
1131
-					if (static::getAppManager()->isEnabledForUser($defaultApp)) {
1132
-						$appId = $defaultApp;
1133
-						break;
1134
-					}
1135
-				}
1136
-
1137
-				if ($config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true') {
1138
-					$location = $urlGenerator->getAbsoluteURL('/apps/' . $appId . '/');
1139
-				} else {
1140
-					$location = $urlGenerator->getAbsoluteURL('/index.php/apps/' . $appId . '/');
1141
-				}
1142
-			}
1143
-		}
1144
-		return $location;
1145
-	}
1146
-
1147
-	/**
1148
-	 * Redirect to the user default page
1149
-	 *
1150
-	 * @return void
1151
-	 */
1152
-	public static function redirectToDefaultPage() {
1153
-		$location = self::getDefaultPageUrl();
1154
-		header('Location: ' . $location);
1155
-		exit();
1156
-	}
1157
-
1158
-	/**
1159
-	 * get an id unique for this instance
1160
-	 *
1161
-	 * @return string
1162
-	 */
1163
-	public static function getInstanceId() {
1164
-		$id = \OC::$server->getSystemConfig()->getValue('instanceid', null);
1165
-		if (is_null($id)) {
1166
-			// We need to guarantee at least one letter in instanceid so it can be used as the session_name
1167
-			$id = 'oc' . \OC::$server->getSecureRandom()->generate(10, \OCP\Security\ISecureRandom::CHAR_LOWER.\OCP\Security\ISecureRandom::CHAR_DIGITS);
1168
-			\OC::$server->getSystemConfig()->setValue('instanceid', $id);
1169
-		}
1170
-		return $id;
1171
-	}
1172
-
1173
-	/**
1174
-	 * Public function to sanitize HTML
1175
-	 *
1176
-	 * This function is used to sanitize HTML and should be applied on any
1177
-	 * string or array of strings before displaying it on a web page.
1178
-	 *
1179
-	 * @param string|array $value
1180
-	 * @return string|array an array of sanitized strings or a single sanitized string, depends on the input parameter.
1181
-	 */
1182
-	public static function sanitizeHTML($value) {
1183
-		if (is_array($value)) {
1184
-			$value = array_map(function ($value) {
1185
-				return self::sanitizeHTML($value);
1186
-			}, $value);
1187
-		} else {
1188
-			// Specify encoding for PHP<5.4
1189
-			$value = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
1190
-		}
1191
-		return $value;
1192
-	}
1193
-
1194
-	/**
1195
-	 * Public function to encode url parameters
1196
-	 *
1197
-	 * This function is used to encode path to file before output.
1198
-	 * Encoding is done according to RFC 3986 with one exception:
1199
-	 * Character '/' is preserved as is.
1200
-	 *
1201
-	 * @param string $component part of URI to encode
1202
-	 * @return string
1203
-	 */
1204
-	public static function encodePath($component) {
1205
-		$encoded = rawurlencode($component);
1206
-		$encoded = str_replace('%2F', '/', $encoded);
1207
-		return $encoded;
1208
-	}
1209
-
1210
-
1211
-	public function createHtaccessTestFile(\OCP\IConfig $config) {
1212
-		// php dev server does not support htaccess
1213
-		if (php_sapi_name() === 'cli-server') {
1214
-			return false;
1215
-		}
1216
-
1217
-		// testdata
1218
-		$fileName = '/htaccesstest.txt';
1219
-		$testContent = 'This is used for testing whether htaccess is properly enabled to disallow access from the outside. This file can be safely removed.';
1220
-
1221
-		// creating a test file
1222
-		$testFile = $config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $fileName;
1223
-
1224
-		if (file_exists($testFile)) {// already running this test, possible recursive call
1225
-			return false;
1226
-		}
1227
-
1228
-		$fp = @fopen($testFile, 'w');
1229
-		if (!$fp) {
1230
-			throw new OC\HintException('Can\'t create test file to check for working .htaccess file.',
1231
-				'Make sure it is possible for the webserver to write to ' . $testFile);
1232
-		}
1233
-		fwrite($fp, $testContent);
1234
-		fclose($fp);
1235
-
1236
-		return $testContent;
1237
-	}
1238
-
1239
-	/**
1240
-	 * Check if the .htaccess file is working
1241
-	 * @param \OCP\IConfig $config
1242
-	 * @return bool
1243
-	 * @throws Exception
1244
-	 * @throws \OC\HintException If the test file can't get written.
1245
-	 */
1246
-	public function isHtaccessWorking(\OCP\IConfig $config) {
1247
-		if (\OC::$CLI || !$config->getSystemValue('check_for_working_htaccess', true)) {
1248
-			return true;
1249
-		}
1250
-
1251
-		$testContent = $this->createHtaccessTestFile($config);
1252
-		if ($testContent === false) {
1253
-			return false;
1254
-		}
1255
-
1256
-		$fileName = '/htaccesstest.txt';
1257
-		$testFile = $config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $fileName;
1258
-
1259
-		// accessing the file via http
1260
-		$url = \OC::$server->getURLGenerator()->getAbsoluteURL(OC::$WEBROOT . '/data' . $fileName);
1261
-		try {
1262
-			$content = \OC::$server->getHTTPClientService()->newClient()->get($url)->getBody();
1263
-		} catch (\Exception $e) {
1264
-			$content = false;
1265
-		}
1266
-
1267
-		if (strpos($url, 'https:') === 0) {
1268
-			$url = 'http:' . substr($url, 6);
1269
-		} else {
1270
-			$url = 'https:' . substr($url, 5);
1271
-		}
1272
-
1273
-		try {
1274
-			$fallbackContent = \OC::$server->getHTTPClientService()->newClient()->get($url)->getBody();
1275
-		} catch (\Exception $e) {
1276
-			$fallbackContent = false;
1277
-		}
1278
-
1279
-		// cleanup
1280
-		@unlink($testFile);
1281
-
1282
-		/*
272
+            if ($mount->getOption('readonly', false)) {
273
+                return new \OC\Files\Storage\Wrapper\PermissionsMask([
274
+                    'storage' => $storage,
275
+                    'mask' => \OCP\Constants::PERMISSION_ALL & ~(
276
+                        \OCP\Constants::PERMISSION_UPDATE |
277
+                        \OCP\Constants::PERMISSION_CREATE |
278
+                        \OCP\Constants::PERMISSION_DELETE
279
+                    ),
280
+                ]);
281
+            }
282
+            return $storage;
283
+        });
284
+
285
+        OC_Hook::emit('OC_Filesystem', 'preSetup', ['user' => $user]);
286
+
287
+        \OC\Files\Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
288
+
289
+        //check if we are using an object storage
290
+        $objectStore = \OC::$server->getSystemConfig()->getValue('objectstore', null);
291
+        $objectStoreMultibucket = \OC::$server->getSystemConfig()->getValue('objectstore_multibucket', null);
292
+
293
+        // use the same order as in ObjectHomeMountProvider
294
+        if (isset($objectStoreMultibucket)) {
295
+            self::initObjectStoreMultibucketRootFS($objectStoreMultibucket);
296
+        } elseif (isset($objectStore)) {
297
+            self::initObjectStoreRootFS($objectStore);
298
+        } else {
299
+            self::initLocalStorageRootFS();
300
+        }
301
+
302
+        /** @var \OCP\Files\Config\IMountProviderCollection $mountProviderCollection */
303
+        $mountProviderCollection = \OC::$server->query(\OCP\Files\Config\IMountProviderCollection::class);
304
+        $rootMountProviders = $mountProviderCollection->getRootMounts();
305
+
306
+        /** @var \OC\Files\Mount\Manager $mountManager */
307
+        $mountManager = \OC\Files\Filesystem::getMountManager();
308
+        foreach ($rootMountProviders as $rootMountProvider) {
309
+            $mountManager->addMount($rootMountProvider);
310
+        }
311
+
312
+        if ($user != '' && !\OC::$server->getUserManager()->userExists($user)) {
313
+            \OC::$server->getEventLogger()->end('setup_fs');
314
+            return false;
315
+        }
316
+
317
+        //if we aren't logged in, there is no use to set up the filesystem
318
+        if ($user != "") {
319
+            $userDir = '/' . $user . '/files';
320
+
321
+            //jail the user into his "home" directory
322
+            \OC\Files\Filesystem::init($user, $userDir);
323
+
324
+            OC_Hook::emit('OC_Filesystem', 'setup', ['user' => $user, 'user_dir' => $userDir]);
325
+        }
326
+        \OC::$server->getEventLogger()->end('setup_fs');
327
+        return true;
328
+    }
329
+
330
+    /**
331
+     * check if a password is required for each public link
332
+     *
333
+     * @return boolean
334
+     * @suppress PhanDeprecatedFunction
335
+     */
336
+    public static function isPublicLinkPasswordRequired() {
337
+        $enforcePassword = \OC::$server->getConfig()->getAppValue('core', 'shareapi_enforce_links_password', 'no');
338
+        return $enforcePassword === 'yes';
339
+    }
340
+
341
+    /**
342
+     * check if sharing is disabled for the current user
343
+     * @param IConfig $config
344
+     * @param IGroupManager $groupManager
345
+     * @param IUser|null $user
346
+     * @return bool
347
+     */
348
+    public static function isSharingDisabledForUser(IConfig $config, IGroupManager $groupManager, $user) {
349
+        if ($config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') {
350
+            $groupsList = $config->getAppValue('core', 'shareapi_exclude_groups_list', '');
351
+            $excludedGroups = json_decode($groupsList);
352
+            if (is_null($excludedGroups)) {
353
+                $excludedGroups = explode(',', $groupsList);
354
+                $newValue = json_encode($excludedGroups);
355
+                $config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue);
356
+            }
357
+            $usersGroups = $groupManager->getUserGroupIds($user);
358
+            if (!empty($usersGroups)) {
359
+                $remainingGroups = array_diff($usersGroups, $excludedGroups);
360
+                // if the user is only in groups which are disabled for sharing then
361
+                // sharing is also disabled for the user
362
+                if (empty($remainingGroups)) {
363
+                    return true;
364
+                }
365
+            }
366
+        }
367
+        return false;
368
+    }
369
+
370
+    /**
371
+     * check if share API enforces a default expire date
372
+     *
373
+     * @return boolean
374
+     * @suppress PhanDeprecatedFunction
375
+     */
376
+    public static function isDefaultExpireDateEnforced() {
377
+        $isDefaultExpireDateEnabled = \OC::$server->getConfig()->getAppValue('core', 'shareapi_default_expire_date', 'no');
378
+        $enforceDefaultExpireDate = false;
379
+        if ($isDefaultExpireDateEnabled === 'yes') {
380
+            $value = \OC::$server->getConfig()->getAppValue('core', 'shareapi_enforce_expire_date', 'no');
381
+            $enforceDefaultExpireDate = $value === 'yes';
382
+        }
383
+
384
+        return $enforceDefaultExpireDate;
385
+    }
386
+
387
+    /**
388
+     * Get the quota of a user
389
+     *
390
+     * @param IUser|null $user
391
+     * @return float Quota bytes
392
+     */
393
+    public static function getUserQuota(?IUser $user) {
394
+        if (is_null($user)) {
395
+            return \OCP\Files\FileInfo::SPACE_UNLIMITED;
396
+        }
397
+        $userQuota = $user->getQuota();
398
+        if ($userQuota === 'none') {
399
+            return \OCP\Files\FileInfo::SPACE_UNLIMITED;
400
+        }
401
+        return OC_Helper::computerFileSize($userQuota);
402
+    }
403
+
404
+    /**
405
+     * copies the skeleton to the users /files
406
+     *
407
+     * @param string $userId
408
+     * @param \OCP\Files\Folder $userDirectory
409
+     * @throws \OCP\Files\NotFoundException
410
+     * @throws \OCP\Files\NotPermittedException
411
+     * @suppress PhanDeprecatedFunction
412
+     */
413
+    public static function copySkeleton($userId, \OCP\Files\Folder $userDirectory) {
414
+        $plainSkeletonDirectory = \OC::$server->getConfig()->getSystemValue('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton');
415
+        $userLang = \OC::$server->getL10NFactory()->findLanguage();
416
+        $skeletonDirectory = str_replace('{lang}', $userLang, $plainSkeletonDirectory);
417
+
418
+        if (!file_exists($skeletonDirectory)) {
419
+            $dialectStart = strpos($userLang, '_');
420
+            if ($dialectStart !== false) {
421
+                $skeletonDirectory = str_replace('{lang}', substr($userLang, 0, $dialectStart), $plainSkeletonDirectory);
422
+            }
423
+            if ($dialectStart === false || !file_exists($skeletonDirectory)) {
424
+                $skeletonDirectory = str_replace('{lang}', 'default', $plainSkeletonDirectory);
425
+            }
426
+            if (!file_exists($skeletonDirectory)) {
427
+                $skeletonDirectory = '';
428
+            }
429
+        }
430
+
431
+        $instanceId = \OC::$server->getConfig()->getSystemValue('instanceid', '');
432
+
433
+        if ($instanceId === null) {
434
+            throw new \RuntimeException('no instance id!');
435
+        }
436
+        $appdata = 'appdata_' . $instanceId;
437
+        if ($userId === $appdata) {
438
+            throw new \RuntimeException('username is reserved name: ' . $appdata);
439
+        }
440
+
441
+        if (!empty($skeletonDirectory)) {
442
+            \OCP\Util::writeLog(
443
+                'files_skeleton',
444
+                'copying skeleton for '.$userId.' from '.$skeletonDirectory.' to '.$userDirectory->getFullPath('/'),
445
+                ILogger::DEBUG
446
+            );
447
+            self::copyr($skeletonDirectory, $userDirectory);
448
+            // update the file cache
449
+            $userDirectory->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE);
450
+        }
451
+    }
452
+
453
+    /**
454
+     * copies a directory recursively by using streams
455
+     *
456
+     * @param string $source
457
+     * @param \OCP\Files\Folder $target
458
+     * @return void
459
+     */
460
+    public static function copyr($source, \OCP\Files\Folder $target) {
461
+        $logger = \OC::$server->getLogger();
462
+
463
+        // Verify if folder exists
464
+        $dir = opendir($source);
465
+        if ($dir === false) {
466
+            $logger->error(sprintf('Could not opendir "%s"', $source), ['app' => 'core']);
467
+            return;
468
+        }
469
+
470
+        // Copy the files
471
+        while (false !== ($file = readdir($dir))) {
472
+            if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
473
+                if (is_dir($source . '/' . $file)) {
474
+                    $child = $target->newFolder($file);
475
+                    self::copyr($source . '/' . $file, $child);
476
+                } else {
477
+                    $child = $target->newFile($file);
478
+                    $sourceStream = fopen($source . '/' . $file, 'r');
479
+                    if ($sourceStream === false) {
480
+                        $logger->error(sprintf('Could not fopen "%s"', $source . '/' . $file), ['app' => 'core']);
481
+                        closedir($dir);
482
+                        return;
483
+                    }
484
+                    stream_copy_to_stream($sourceStream, $child->fopen('w'));
485
+                }
486
+            }
487
+        }
488
+        closedir($dir);
489
+    }
490
+
491
+    /**
492
+     * @return void
493
+     * @suppress PhanUndeclaredMethod
494
+     */
495
+    public static function tearDownFS() {
496
+        \OC\Files\Filesystem::tearDown();
497
+        \OC::$server->getRootFolder()->clearCache();
498
+        self::$fsSetup = false;
499
+        self::$rootMounted = false;
500
+    }
501
+
502
+    /**
503
+     * get the current installed version of ownCloud
504
+     *
505
+     * @return array
506
+     */
507
+    public static function getVersion() {
508
+        OC_Util::loadVersion();
509
+        return self::$versionCache['OC_Version'];
510
+    }
511
+
512
+    /**
513
+     * get the current installed version string of ownCloud
514
+     *
515
+     * @return string
516
+     */
517
+    public static function getVersionString() {
518
+        OC_Util::loadVersion();
519
+        return self::$versionCache['OC_VersionString'];
520
+    }
521
+
522
+    /**
523
+     * @deprecated the value is of no use anymore
524
+     * @return string
525
+     */
526
+    public static function getEditionString() {
527
+        return '';
528
+    }
529
+
530
+    /**
531
+     * @description get the update channel of the current installed of ownCloud.
532
+     * @return string
533
+     */
534
+    public static function getChannel() {
535
+        OC_Util::loadVersion();
536
+        return \OC::$server->getConfig()->getSystemValue('updater.release.channel', self::$versionCache['OC_Channel']);
537
+    }
538
+
539
+    /**
540
+     * @description get the build number of the current installed of ownCloud.
541
+     * @return string
542
+     */
543
+    public static function getBuild() {
544
+        OC_Util::loadVersion();
545
+        return self::$versionCache['OC_Build'];
546
+    }
547
+
548
+    /**
549
+     * @description load the version.php into the session as cache
550
+     * @suppress PhanUndeclaredVariable
551
+     */
552
+    private static function loadVersion() {
553
+        if (self::$versionCache !== null) {
554
+            return;
555
+        }
556
+
557
+        $timestamp = filemtime(OC::$SERVERROOT . '/version.php');
558
+        require OC::$SERVERROOT . '/version.php';
559
+        /** @var int $timestamp */
560
+        self::$versionCache['OC_Version_Timestamp'] = $timestamp;
561
+        /** @var string $OC_Version */
562
+        self::$versionCache['OC_Version'] = $OC_Version;
563
+        /** @var string $OC_VersionString */
564
+        self::$versionCache['OC_VersionString'] = $OC_VersionString;
565
+        /** @var string $OC_Build */
566
+        self::$versionCache['OC_Build'] = $OC_Build;
567
+
568
+        /** @var string $OC_Channel */
569
+        self::$versionCache['OC_Channel'] = $OC_Channel;
570
+    }
571
+
572
+    /**
573
+     * generates a path for JS/CSS files. If no application is provided it will create the path for core.
574
+     *
575
+     * @param string $application application to get the files from
576
+     * @param string $directory directory within this application (css, js, vendor, etc)
577
+     * @param string $file the file inside of the above folder
578
+     * @return string the path
579
+     */
580
+    private static function generatePath($application, $directory, $file) {
581
+        if (is_null($file)) {
582
+            $file = $application;
583
+            $application = "";
584
+        }
585
+        if (!empty($application)) {
586
+            return "$application/$directory/$file";
587
+        } else {
588
+            return "$directory/$file";
589
+        }
590
+    }
591
+
592
+    /**
593
+     * add a javascript file
594
+     *
595
+     * @param string $application application id
596
+     * @param string|null $file filename
597
+     * @param bool $prepend prepend the Script to the beginning of the list
598
+     * @return void
599
+     */
600
+    public static function addScript($application, $file = null, $prepend = false) {
601
+        $path = OC_Util::generatePath($application, 'js', $file);
602
+
603
+        // core js files need separate handling
604
+        if ($application !== 'core' && $file !== null) {
605
+            self::addTranslations($application);
606
+        }
607
+        self::addExternalResource($application, $prepend, $path, "script");
608
+    }
609
+
610
+    /**
611
+     * add a javascript file from the vendor sub folder
612
+     *
613
+     * @param string $application application id
614
+     * @param string|null $file filename
615
+     * @param bool $prepend prepend the Script to the beginning of the list
616
+     * @return void
617
+     */
618
+    public static function addVendorScript($application, $file = null, $prepend = false) {
619
+        $path = OC_Util::generatePath($application, 'vendor', $file);
620
+        self::addExternalResource($application, $prepend, $path, "script");
621
+    }
622
+
623
+    /**
624
+     * add a translation JS file
625
+     *
626
+     * @param string $application application id
627
+     * @param string|null $languageCode language code, defaults to the current language
628
+     * @param bool|null $prepend prepend the Script to the beginning of the list
629
+     */
630
+    public static function addTranslations($application, $languageCode = null, $prepend = false) {
631
+        if (is_null($languageCode)) {
632
+            $languageCode = \OC::$server->getL10NFactory()->findLanguage($application);
633
+        }
634
+        if (!empty($application)) {
635
+            $path = "$application/l10n/$languageCode";
636
+        } else {
637
+            $path = "l10n/$languageCode";
638
+        }
639
+        self::addExternalResource($application, $prepend, $path, "script");
640
+    }
641
+
642
+    /**
643
+     * add a css file
644
+     *
645
+     * @param string $application application id
646
+     * @param string|null $file filename
647
+     * @param bool $prepend prepend the Style to the beginning of the list
648
+     * @return void
649
+     */
650
+    public static function addStyle($application, $file = null, $prepend = false) {
651
+        $path = OC_Util::generatePath($application, 'css', $file);
652
+        self::addExternalResource($application, $prepend, $path, "style");
653
+    }
654
+
655
+    /**
656
+     * add a css file from the vendor sub folder
657
+     *
658
+     * @param string $application application id
659
+     * @param string|null $file filename
660
+     * @param bool $prepend prepend the Style to the beginning of the list
661
+     * @return void
662
+     */
663
+    public static function addVendorStyle($application, $file = null, $prepend = false) {
664
+        $path = OC_Util::generatePath($application, 'vendor', $file);
665
+        self::addExternalResource($application, $prepend, $path, "style");
666
+    }
667
+
668
+    /**
669
+     * add an external resource css/js file
670
+     *
671
+     * @param string $application application id
672
+     * @param bool $prepend prepend the file to the beginning of the list
673
+     * @param string $path
674
+     * @param string $type (script or style)
675
+     * @return void
676
+     */
677
+    private static function addExternalResource($application, $prepend, $path, $type = "script") {
678
+        if ($type === "style") {
679
+            if (!in_array($path, self::$styles)) {
680
+                if ($prepend === true) {
681
+                    array_unshift(self::$styles, $path);
682
+                } else {
683
+                    self::$styles[] = $path;
684
+                }
685
+            }
686
+        } elseif ($type === "script") {
687
+            if (!in_array($path, self::$scripts)) {
688
+                if ($prepend === true) {
689
+                    array_unshift(self::$scripts, $path);
690
+                } else {
691
+                    self::$scripts [] = $path;
692
+                }
693
+            }
694
+        }
695
+    }
696
+
697
+    /**
698
+     * Add a custom element to the header
699
+     * If $text is null then the element will be written as empty element.
700
+     * So use "" to get a closing tag.
701
+     * @param string $tag tag name of the element
702
+     * @param array $attributes array of attributes for the element
703
+     * @param string $text the text content for the element
704
+     * @param bool $prepend prepend the header to the beginning of the list
705
+     */
706
+    public static function addHeader($tag, $attributes, $text = null, $prepend = false) {
707
+        $header = [
708
+            'tag' => $tag,
709
+            'attributes' => $attributes,
710
+            'text' => $text
711
+        ];
712
+        if ($prepend === true) {
713
+            array_unshift(self::$headers, $header);
714
+        } else {
715
+            self::$headers[] = $header;
716
+        }
717
+    }
718
+
719
+    /**
720
+     * check if the current server configuration is suitable for ownCloud
721
+     *
722
+     * @param \OC\SystemConfig $config
723
+     * @return array arrays with error messages and hints
724
+     */
725
+    public static function checkServer(\OC\SystemConfig $config) {
726
+        $l = \OC::$server->getL10N('lib');
727
+        $errors = [];
728
+        $CONFIG_DATADIRECTORY = $config->getValue('datadirectory', OC::$SERVERROOT . '/data');
729
+
730
+        if (!self::needUpgrade($config) && $config->getValue('installed', false)) {
731
+            // this check needs to be done every time
732
+            $errors = self::checkDataDirectoryValidity($CONFIG_DATADIRECTORY);
733
+        }
734
+
735
+        // Assume that if checkServer() succeeded before in this session, then all is fine.
736
+        if (\OC::$server->getSession()->exists('checkServer_succeeded') && \OC::$server->getSession()->get('checkServer_succeeded')) {
737
+            return $errors;
738
+        }
739
+
740
+        $webServerRestart = false;
741
+        $setup = new \OC\Setup(
742
+            $config,
743
+            \OC::$server->get(IniGetWrapper::class),
744
+            \OC::$server->getL10N('lib'),
745
+            \OC::$server->query(\OCP\Defaults::class),
746
+            \OC::$server->getLogger(),
747
+            \OC::$server->getSecureRandom(),
748
+            \OC::$server->query(\OC\Installer::class)
749
+        );
750
+
751
+        $urlGenerator = \OC::$server->getURLGenerator();
752
+
753
+        $availableDatabases = $setup->getSupportedDatabases();
754
+        if (empty($availableDatabases)) {
755
+            $errors[] = [
756
+                'error' => $l->t('No database drivers (sqlite, mysql, or postgresql) installed.'),
757
+                'hint' => '' //TODO: sane hint
758
+            ];
759
+            $webServerRestart = true;
760
+        }
761
+
762
+        // Check if config folder is writable.
763
+        if (!OC_Helper::isReadOnlyConfigEnabled()) {
764
+            if (!is_writable(OC::$configDir) or !is_readable(OC::$configDir)) {
765
+                $errors[] = [
766
+                    'error' => $l->t('Cannot write into "config" directory'),
767
+                    'hint' => $l->t('This can usually be fixed by giving the webserver write access to the config directory. See %s',
768
+                        [ $urlGenerator->linkToDocs('admin-dir_permissions') ]) . '. '
769
+                        . $l->t('Or, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it. See %s',
770
+                        [ $urlGenerator->linkToDocs('admin-config') ])
771
+                ];
772
+            }
773
+        }
774
+
775
+        // Check if there is a writable install folder.
776
+        if ($config->getValue('appstoreenabled', true)) {
777
+            if (OC_App::getInstallPath() === null
778
+                || !is_writable(OC_App::getInstallPath())
779
+                || !is_readable(OC_App::getInstallPath())
780
+            ) {
781
+                $errors[] = [
782
+                    'error' => $l->t('Cannot write into "apps" directory'),
783
+                    'hint' => $l->t('This can usually be fixed by giving the webserver write access to the apps directory'
784
+                        . ' or disabling the appstore in the config file.')
785
+                ];
786
+            }
787
+        }
788
+        // Create root dir.
789
+        if ($config->getValue('installed', false)) {
790
+            if (!is_dir($CONFIG_DATADIRECTORY)) {
791
+                $success = @mkdir($CONFIG_DATADIRECTORY);
792
+                if ($success) {
793
+                    $errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
794
+                } else {
795
+                    $errors[] = [
796
+                        'error' => $l->t('Cannot create "data" directory'),
797
+                        'hint' => $l->t('This can usually be fixed by giving the webserver write access to the root directory. See %s',
798
+                            [$urlGenerator->linkToDocs('admin-dir_permissions')])
799
+                    ];
800
+                }
801
+            } elseif (!is_writable($CONFIG_DATADIRECTORY) or !is_readable($CONFIG_DATADIRECTORY)) {
802
+                // is_writable doesn't work for NFS mounts, so try to write a file and check if it exists.
803
+                $testFile = sprintf('%s/%s.tmp', $CONFIG_DATADIRECTORY, uniqid('data_dir_writability_test_'));
804
+                $handle = fopen($testFile, 'w');
805
+                if (!$handle || fwrite($handle, 'Test write operation') === false) {
806
+                    $permissionsHint = $l->t('Permissions can usually be fixed by giving the webserver write access to the root directory. See %s.',
807
+                        [$urlGenerator->linkToDocs('admin-dir_permissions')]);
808
+                    $errors[] = [
809
+                        'error' => 'Your data directory is not writable',
810
+                        'hint' => $permissionsHint
811
+                    ];
812
+                } else {
813
+                    fclose($handle);
814
+                    unlink($testFile);
815
+                }
816
+            } else {
817
+                $errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
818
+            }
819
+        }
820
+
821
+        if (!OC_Util::isSetLocaleWorking()) {
822
+            $errors[] = [
823
+                'error' => $l->t('Setting locale to %s failed',
824
+                    ['en_US.UTF-8/fr_FR.UTF-8/es_ES.UTF-8/de_DE.UTF-8/ru_RU.UTF-8/'
825
+                        . 'pt_BR.UTF-8/it_IT.UTF-8/ja_JP.UTF-8/zh_CN.UTF-8']),
826
+                'hint' => $l->t('Please install one of these locales on your system and restart your webserver.')
827
+            ];
828
+        }
829
+
830
+        // Contains the dependencies that should be checked against
831
+        // classes = class_exists
832
+        // functions = function_exists
833
+        // defined = defined
834
+        // ini = ini_get
835
+        // If the dependency is not found the missing module name is shown to the EndUser
836
+        // When adding new checks always verify that they pass on Travis as well
837
+        // for ini settings, see https://github.com/owncloud/administration/blob/master/travis-ci/custom.ini
838
+        $dependencies = [
839
+            'classes' => [
840
+                'ZipArchive' => 'zip',
841
+                'DOMDocument' => 'dom',
842
+                'XMLWriter' => 'XMLWriter',
843
+                'XMLReader' => 'XMLReader',
844
+            ],
845
+            'functions' => [
846
+                'xml_parser_create' => 'libxml',
847
+                'mb_strcut' => 'mbstring',
848
+                'ctype_digit' => 'ctype',
849
+                'json_encode' => 'JSON',
850
+                'gd_info' => 'GD',
851
+                'gzencode' => 'zlib',
852
+                'iconv' => 'iconv',
853
+                'simplexml_load_string' => 'SimpleXML',
854
+                'hash' => 'HASH Message Digest Framework',
855
+                'curl_init' => 'cURL',
856
+                'openssl_verify' => 'OpenSSL',
857
+            ],
858
+            'defined' => [
859
+                'PDO::ATTR_DRIVER_NAME' => 'PDO'
860
+            ],
861
+            'ini' => [
862
+                'default_charset' => 'UTF-8',
863
+            ],
864
+        ];
865
+        $missingDependencies = [];
866
+        $invalidIniSettings = [];
867
+
868
+        $iniWrapper = \OC::$server->get(IniGetWrapper::class);
869
+        foreach ($dependencies['classes'] as $class => $module) {
870
+            if (!class_exists($class)) {
871
+                $missingDependencies[] = $module;
872
+            }
873
+        }
874
+        foreach ($dependencies['functions'] as $function => $module) {
875
+            if (!function_exists($function)) {
876
+                $missingDependencies[] = $module;
877
+            }
878
+        }
879
+        foreach ($dependencies['defined'] as $defined => $module) {
880
+            if (!defined($defined)) {
881
+                $missingDependencies[] = $module;
882
+            }
883
+        }
884
+        foreach ($dependencies['ini'] as $setting => $expected) {
885
+            if (is_bool($expected)) {
886
+                if ($iniWrapper->getBool($setting) !== $expected) {
887
+                    $invalidIniSettings[] = [$setting, $expected];
888
+                }
889
+            }
890
+            if (is_int($expected)) {
891
+                if ($iniWrapper->getNumeric($setting) !== $expected) {
892
+                    $invalidIniSettings[] = [$setting, $expected];
893
+                }
894
+            }
895
+            if (is_string($expected)) {
896
+                if (strtolower($iniWrapper->getString($setting)) !== strtolower($expected)) {
897
+                    $invalidIniSettings[] = [$setting, $expected];
898
+                }
899
+            }
900
+        }
901
+
902
+        foreach ($missingDependencies as $missingDependency) {
903
+            $errors[] = [
904
+                'error' => $l->t('PHP module %s not installed.', [$missingDependency]),
905
+                'hint' => $l->t('Please ask your server administrator to install the module.'),
906
+            ];
907
+            $webServerRestart = true;
908
+        }
909
+        foreach ($invalidIniSettings as $setting) {
910
+            if (is_bool($setting[1])) {
911
+                $setting[1] = $setting[1] ? 'on' : 'off';
912
+            }
913
+            $errors[] = [
914
+                'error' => $l->t('PHP setting "%s" is not set to "%s".', [$setting[0], var_export($setting[1], true)]),
915
+                'hint' => $l->t('Adjusting this setting in php.ini will make Nextcloud run again')
916
+            ];
917
+            $webServerRestart = true;
918
+        }
919
+
920
+        /**
921
+         * The mbstring.func_overload check can only be performed if the mbstring
922
+         * module is installed as it will return null if the checking setting is
923
+         * not available and thus a check on the boolean value fails.
924
+         *
925
+         * TODO: Should probably be implemented in the above generic dependency
926
+         *       check somehow in the long-term.
927
+         */
928
+        if ($iniWrapper->getBool('mbstring.func_overload') !== null &&
929
+            $iniWrapper->getBool('mbstring.func_overload') === true) {
930
+            $errors[] = [
931
+                'error' => $l->t('mbstring.func_overload is set to "%s" instead of the expected value "0"', [$iniWrapper->getString('mbstring.func_overload')]),
932
+                'hint' => $l->t('To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini')
933
+            ];
934
+        }
935
+
936
+        if (function_exists('xml_parser_create') &&
937
+            LIBXML_LOADED_VERSION < 20700) {
938
+            $version = LIBXML_LOADED_VERSION;
939
+            $major = floor($version / 10000);
940
+            $version -= ($major * 10000);
941
+            $minor = floor($version / 100);
942
+            $version -= ($minor * 100);
943
+            $patch = $version;
944
+            $errors[] = [
945
+                'error' => $l->t('libxml2 2.7.0 is at least required. Currently %s is installed.', [$major . '.' . $minor . '.' . $patch]),
946
+                'hint' => $l->t('To fix this issue update your libxml2 version and restart your web server.')
947
+            ];
948
+        }
949
+
950
+        if (!self::isAnnotationsWorking()) {
951
+            $errors[] = [
952
+                'error' => $l->t('PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible.'),
953
+                'hint' => $l->t('This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.')
954
+            ];
955
+        }
956
+
957
+        if (!\OC::$CLI && $webServerRestart) {
958
+            $errors[] = [
959
+                'error' => $l->t('PHP modules have been installed, but they are still listed as missing?'),
960
+                'hint' => $l->t('Please ask your server administrator to restart the web server.')
961
+            ];
962
+        }
963
+
964
+        $errors = array_merge($errors, self::checkDatabaseVersion());
965
+
966
+        // Cache the result of this function
967
+        \OC::$server->getSession()->set('checkServer_succeeded', count($errors) == 0);
968
+
969
+        return $errors;
970
+    }
971
+
972
+    /**
973
+     * Check the database version
974
+     *
975
+     * @return array errors array
976
+     */
977
+    public static function checkDatabaseVersion() {
978
+        $l = \OC::$server->getL10N('lib');
979
+        $errors = [];
980
+        $dbType = \OC::$server->getSystemConfig()->getValue('dbtype', 'sqlite');
981
+        if ($dbType === 'pgsql') {
982
+            // check PostgreSQL version
983
+            try {
984
+                $result = \OC_DB::executeAudited('SHOW SERVER_VERSION');
985
+                $data = $result->fetchRow();
986
+                $result->closeCursor();
987
+                if (isset($data['server_version'])) {
988
+                    $version = $data['server_version'];
989
+                    if (version_compare($version, '9.0.0', '<')) {
990
+                        $errors[] = [
991
+                            'error' => $l->t('PostgreSQL >= 9 required'),
992
+                            'hint' => $l->t('Please upgrade your database version')
993
+                        ];
994
+                    }
995
+                }
996
+            } catch (\Doctrine\DBAL\DBALException $e) {
997
+                $logger = \OC::$server->getLogger();
998
+                $logger->warning('Error occurred while checking PostgreSQL version, assuming >= 9');
999
+                $logger->logException($e);
1000
+            }
1001
+        }
1002
+        return $errors;
1003
+    }
1004
+
1005
+    /**
1006
+     * Check for correct file permissions of data directory
1007
+     *
1008
+     * @param string $dataDirectory
1009
+     * @return array arrays with error messages and hints
1010
+     */
1011
+    public static function checkDataDirectoryPermissions($dataDirectory) {
1012
+        if (\OC::$server->getConfig()->getSystemValue('check_data_directory_permissions', true) === false) {
1013
+            return  [];
1014
+        }
1015
+
1016
+        $perms = substr(decoct(@fileperms($dataDirectory)), -3);
1017
+        if (substr($perms, -1) !== '0') {
1018
+            chmod($dataDirectory, 0770);
1019
+            clearstatcache();
1020
+            $perms = substr(decoct(@fileperms($dataDirectory)), -3);
1021
+            if ($perms[2] !== '0') {
1022
+                $l = \OC::$server->getL10N('lib');
1023
+                return [[
1024
+                    'error' => $l->t('Your data directory is readable by other users'),
1025
+                    'hint' => $l->t('Please change the permissions to 0770 so that the directory cannot be listed by other users.'),
1026
+                ]];
1027
+            }
1028
+        }
1029
+        return [];
1030
+    }
1031
+
1032
+    /**
1033
+     * Check that the data directory exists and is valid by
1034
+     * checking the existence of the ".ocdata" file.
1035
+     *
1036
+     * @param string $dataDirectory data directory path
1037
+     * @return array errors found
1038
+     */
1039
+    public static function checkDataDirectoryValidity($dataDirectory) {
1040
+        $l = \OC::$server->getL10N('lib');
1041
+        $errors = [];
1042
+        if ($dataDirectory[0] !== '/') {
1043
+            $errors[] = [
1044
+                'error' => $l->t('Your data directory must be an absolute path'),
1045
+                'hint' => $l->t('Check the value of "datadirectory" in your configuration')
1046
+            ];
1047
+        }
1048
+        if (!file_exists($dataDirectory . '/.ocdata')) {
1049
+            $errors[] = [
1050
+                'error' => $l->t('Your data directory is invalid'),
1051
+                'hint' => $l->t('Ensure there is a file called ".ocdata"' .
1052
+                    ' in the root of the data directory.')
1053
+            ];
1054
+        }
1055
+        return $errors;
1056
+    }
1057
+
1058
+    /**
1059
+     * Check if the user is logged in, redirects to home if not. With
1060
+     * redirect URL parameter to the request URI.
1061
+     *
1062
+     * @return void
1063
+     */
1064
+    public static function checkLoggedIn() {
1065
+        // Check if we are a user
1066
+        if (!\OC::$server->getUserSession()->isLoggedIn()) {
1067
+            header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute(
1068
+                        'core.login.showLoginForm',
1069
+                        [
1070
+                            'redirect_url' => \OC::$server->getRequest()->getRequestUri(),
1071
+                        ]
1072
+                    )
1073
+            );
1074
+            exit();
1075
+        }
1076
+        // Redirect to 2FA challenge selection if 2FA challenge was not solved yet
1077
+        if (\OC::$server->getTwoFactorAuthManager()->needsSecondFactor(\OC::$server->getUserSession()->getUser())) {
1078
+            header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
1079
+            exit();
1080
+        }
1081
+    }
1082
+
1083
+    /**
1084
+     * Check if the user is a admin, redirects to home if not
1085
+     *
1086
+     * @return void
1087
+     */
1088
+    public static function checkAdminUser() {
1089
+        OC_Util::checkLoggedIn();
1090
+        if (!OC_User::isAdminUser(OC_User::getUser())) {
1091
+            header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php'));
1092
+            exit();
1093
+        }
1094
+    }
1095
+
1096
+    /**
1097
+     * Returns the URL of the default page
1098
+     * based on the system configuration and
1099
+     * the apps visible for the current user
1100
+     *
1101
+     * @return string URL
1102
+     * @suppress PhanDeprecatedFunction
1103
+     */
1104
+    public static function getDefaultPageUrl() {
1105
+        /** @var IConfig $config */
1106
+        $config = \OC::$server->get(IConfig::class);
1107
+        $urlGenerator = \OC::$server->getURLGenerator();
1108
+        // Deny the redirect if the URL contains a @
1109
+        // This prevents unvalidated redirects like ?redirect_url=:[email protected]
1110
+        if (isset($_REQUEST['redirect_url']) && strpos($_REQUEST['redirect_url'], '@') === false) {
1111
+            $location = $urlGenerator->getAbsoluteURL(urldecode($_REQUEST['redirect_url']));
1112
+        } else {
1113
+            $defaultPage = \OC::$server->getConfig()->getAppValue('core', 'defaultpage');
1114
+            if ($defaultPage) {
1115
+                $location = $urlGenerator->getAbsoluteURL($defaultPage);
1116
+            } else {
1117
+                $appId = 'files';
1118
+                $defaultApps = explode(',', $config->getSystemValue('defaultapp', 'dashboard,files'));
1119
+
1120
+                /** @var IUserSession $userSession */
1121
+                $userSession = \OC::$server->get(IUserSession::class);
1122
+                $user = $userSession->getUser();
1123
+                if ($user) {
1124
+                    $userDefaultApps = explode(',', $config->getUserValue($user->getUID(), 'core', 'defaultapp'));
1125
+                    $defaultApps = array_filter(array_merge($userDefaultApps, $defaultApps));
1126
+                }
1127
+
1128
+                // find the first app that is enabled for the current user
1129
+                foreach ($defaultApps as $defaultApp) {
1130
+                    $defaultApp = OC_App::cleanAppId(strip_tags($defaultApp));
1131
+                    if (static::getAppManager()->isEnabledForUser($defaultApp)) {
1132
+                        $appId = $defaultApp;
1133
+                        break;
1134
+                    }
1135
+                }
1136
+
1137
+                if ($config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true') {
1138
+                    $location = $urlGenerator->getAbsoluteURL('/apps/' . $appId . '/');
1139
+                } else {
1140
+                    $location = $urlGenerator->getAbsoluteURL('/index.php/apps/' . $appId . '/');
1141
+                }
1142
+            }
1143
+        }
1144
+        return $location;
1145
+    }
1146
+
1147
+    /**
1148
+     * Redirect to the user default page
1149
+     *
1150
+     * @return void
1151
+     */
1152
+    public static function redirectToDefaultPage() {
1153
+        $location = self::getDefaultPageUrl();
1154
+        header('Location: ' . $location);
1155
+        exit();
1156
+    }
1157
+
1158
+    /**
1159
+     * get an id unique for this instance
1160
+     *
1161
+     * @return string
1162
+     */
1163
+    public static function getInstanceId() {
1164
+        $id = \OC::$server->getSystemConfig()->getValue('instanceid', null);
1165
+        if (is_null($id)) {
1166
+            // We need to guarantee at least one letter in instanceid so it can be used as the session_name
1167
+            $id = 'oc' . \OC::$server->getSecureRandom()->generate(10, \OCP\Security\ISecureRandom::CHAR_LOWER.\OCP\Security\ISecureRandom::CHAR_DIGITS);
1168
+            \OC::$server->getSystemConfig()->setValue('instanceid', $id);
1169
+        }
1170
+        return $id;
1171
+    }
1172
+
1173
+    /**
1174
+     * Public function to sanitize HTML
1175
+     *
1176
+     * This function is used to sanitize HTML and should be applied on any
1177
+     * string or array of strings before displaying it on a web page.
1178
+     *
1179
+     * @param string|array $value
1180
+     * @return string|array an array of sanitized strings or a single sanitized string, depends on the input parameter.
1181
+     */
1182
+    public static function sanitizeHTML($value) {
1183
+        if (is_array($value)) {
1184
+            $value = array_map(function ($value) {
1185
+                return self::sanitizeHTML($value);
1186
+            }, $value);
1187
+        } else {
1188
+            // Specify encoding for PHP<5.4
1189
+            $value = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
1190
+        }
1191
+        return $value;
1192
+    }
1193
+
1194
+    /**
1195
+     * Public function to encode url parameters
1196
+     *
1197
+     * This function is used to encode path to file before output.
1198
+     * Encoding is done according to RFC 3986 with one exception:
1199
+     * Character '/' is preserved as is.
1200
+     *
1201
+     * @param string $component part of URI to encode
1202
+     * @return string
1203
+     */
1204
+    public static function encodePath($component) {
1205
+        $encoded = rawurlencode($component);
1206
+        $encoded = str_replace('%2F', '/', $encoded);
1207
+        return $encoded;
1208
+    }
1209
+
1210
+
1211
+    public function createHtaccessTestFile(\OCP\IConfig $config) {
1212
+        // php dev server does not support htaccess
1213
+        if (php_sapi_name() === 'cli-server') {
1214
+            return false;
1215
+        }
1216
+
1217
+        // testdata
1218
+        $fileName = '/htaccesstest.txt';
1219
+        $testContent = 'This is used for testing whether htaccess is properly enabled to disallow access from the outside. This file can be safely removed.';
1220
+
1221
+        // creating a test file
1222
+        $testFile = $config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $fileName;
1223
+
1224
+        if (file_exists($testFile)) {// already running this test, possible recursive call
1225
+            return false;
1226
+        }
1227
+
1228
+        $fp = @fopen($testFile, 'w');
1229
+        if (!$fp) {
1230
+            throw new OC\HintException('Can\'t create test file to check for working .htaccess file.',
1231
+                'Make sure it is possible for the webserver to write to ' . $testFile);
1232
+        }
1233
+        fwrite($fp, $testContent);
1234
+        fclose($fp);
1235
+
1236
+        return $testContent;
1237
+    }
1238
+
1239
+    /**
1240
+     * Check if the .htaccess file is working
1241
+     * @param \OCP\IConfig $config
1242
+     * @return bool
1243
+     * @throws Exception
1244
+     * @throws \OC\HintException If the test file can't get written.
1245
+     */
1246
+    public function isHtaccessWorking(\OCP\IConfig $config) {
1247
+        if (\OC::$CLI || !$config->getSystemValue('check_for_working_htaccess', true)) {
1248
+            return true;
1249
+        }
1250
+
1251
+        $testContent = $this->createHtaccessTestFile($config);
1252
+        if ($testContent === false) {
1253
+            return false;
1254
+        }
1255
+
1256
+        $fileName = '/htaccesstest.txt';
1257
+        $testFile = $config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $fileName;
1258
+
1259
+        // accessing the file via http
1260
+        $url = \OC::$server->getURLGenerator()->getAbsoluteURL(OC::$WEBROOT . '/data' . $fileName);
1261
+        try {
1262
+            $content = \OC::$server->getHTTPClientService()->newClient()->get($url)->getBody();
1263
+        } catch (\Exception $e) {
1264
+            $content = false;
1265
+        }
1266
+
1267
+        if (strpos($url, 'https:') === 0) {
1268
+            $url = 'http:' . substr($url, 6);
1269
+        } else {
1270
+            $url = 'https:' . substr($url, 5);
1271
+        }
1272
+
1273
+        try {
1274
+            $fallbackContent = \OC::$server->getHTTPClientService()->newClient()->get($url)->getBody();
1275
+        } catch (\Exception $e) {
1276
+            $fallbackContent = false;
1277
+        }
1278
+
1279
+        // cleanup
1280
+        @unlink($testFile);
1281
+
1282
+        /*
1283 1283
 		 * If the content is not equal to test content our .htaccess
1284 1284
 		 * is working as required
1285 1285
 		 */
1286
-		return $content !== $testContent && $fallbackContent !== $testContent;
1287
-	}
1288
-
1289
-	/**
1290
-	 * Check if the setlocal call does not work. This can happen if the right
1291
-	 * local packages are not available on the server.
1292
-	 *
1293
-	 * @return bool
1294
-	 */
1295
-	public static function isSetLocaleWorking() {
1296
-		\Patchwork\Utf8\Bootup::initLocale();
1297
-		if ('' === basename('§')) {
1298
-			return false;
1299
-		}
1300
-		return true;
1301
-	}
1302
-
1303
-	/**
1304
-	 * Check if it's possible to get the inline annotations
1305
-	 *
1306
-	 * @return bool
1307
-	 */
1308
-	public static function isAnnotationsWorking() {
1309
-		$reflection = new \ReflectionMethod(__METHOD__);
1310
-		$docs = $reflection->getDocComment();
1311
-
1312
-		return (is_string($docs) && strlen($docs) > 50);
1313
-	}
1314
-
1315
-	/**
1316
-	 * Check if the PHP module fileinfo is loaded.
1317
-	 *
1318
-	 * @return bool
1319
-	 */
1320
-	public static function fileInfoLoaded() {
1321
-		return function_exists('finfo_open');
1322
-	}
1323
-
1324
-	/**
1325
-	 * clear all levels of output buffering
1326
-	 *
1327
-	 * @return void
1328
-	 */
1329
-	public static function obEnd() {
1330
-		while (ob_get_level()) {
1331
-			ob_end_clean();
1332
-		}
1333
-	}
1334
-
1335
-	/**
1336
-	 * Checks whether the server is running on Mac OS X
1337
-	 *
1338
-	 * @return bool true if running on Mac OS X, false otherwise
1339
-	 */
1340
-	public static function runningOnMac() {
1341
-		return (strtoupper(substr(PHP_OS, 0, 6)) === 'DARWIN');
1342
-	}
1343
-
1344
-	/**
1345
-	 * Handles the case that there may not be a theme, then check if a "default"
1346
-	 * theme exists and take that one
1347
-	 *
1348
-	 * @return string the theme
1349
-	 */
1350
-	public static function getTheme() {
1351
-		$theme = \OC::$server->getSystemConfig()->getValue("theme", '');
1352
-
1353
-		if ($theme === '') {
1354
-			if (is_dir(OC::$SERVERROOT . '/themes/default')) {
1355
-				$theme = 'default';
1356
-			}
1357
-		}
1358
-
1359
-		return $theme;
1360
-	}
1361
-
1362
-	/**
1363
-	 * Normalize a unicode string
1364
-	 *
1365
-	 * @param string $value a not normalized string
1366
-	 * @return bool|string
1367
-	 */
1368
-	public static function normalizeUnicode($value) {
1369
-		if (Normalizer::isNormalized($value)) {
1370
-			return $value;
1371
-		}
1372
-
1373
-		$normalizedValue = Normalizer::normalize($value);
1374
-		if ($normalizedValue === null || $normalizedValue === false) {
1375
-			\OC::$server->getLogger()->warning('normalizing failed for "' . $value . '"', ['app' => 'core']);
1376
-			return $value;
1377
-		}
1378
-
1379
-		return $normalizedValue;
1380
-	}
1381
-
1382
-	/**
1383
-	 * A human readable string is generated based on version and build number
1384
-	 *
1385
-	 * @return string
1386
-	 */
1387
-	public static function getHumanVersion() {
1388
-		$version = OC_Util::getVersionString();
1389
-		$build = OC_Util::getBuild();
1390
-		if (!empty($build) and OC_Util::getChannel() === 'daily') {
1391
-			$version .= ' Build:' . $build;
1392
-		}
1393
-		return $version;
1394
-	}
1395
-
1396
-	/**
1397
-	 * Returns whether the given file name is valid
1398
-	 *
1399
-	 * @param string $file file name to check
1400
-	 * @return bool true if the file name is valid, false otherwise
1401
-	 * @deprecated use \OC\Files\View::verifyPath()
1402
-	 */
1403
-	public static function isValidFileName($file) {
1404
-		$trimmed = trim($file);
1405
-		if ($trimmed === '') {
1406
-			return false;
1407
-		}
1408
-		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
1409
-			return false;
1410
-		}
1411
-
1412
-		// detect part files
1413
-		if (preg_match('/' . \OCP\Files\FileInfo::BLACKLIST_FILES_REGEX . '/', $trimmed) !== 0) {
1414
-			return false;
1415
-		}
1416
-
1417
-		foreach (str_split($trimmed) as $char) {
1418
-			if (strpos(\OCP\Constants::FILENAME_INVALID_CHARS, $char) !== false) {
1419
-				return false;
1420
-			}
1421
-		}
1422
-		return true;
1423
-	}
1424
-
1425
-	/**
1426
-	 * Check whether the instance needs to perform an upgrade,
1427
-	 * either when the core version is higher or any app requires
1428
-	 * an upgrade.
1429
-	 *
1430
-	 * @param \OC\SystemConfig $config
1431
-	 * @return bool whether the core or any app needs an upgrade
1432
-	 * @throws \OC\HintException When the upgrade from the given version is not allowed
1433
-	 */
1434
-	public static function needUpgrade(\OC\SystemConfig $config) {
1435
-		if ($config->getValue('installed', false)) {
1436
-			$installedVersion = $config->getValue('version', '0.0.0');
1437
-			$currentVersion = implode('.', \OCP\Util::getVersion());
1438
-			$versionDiff = version_compare($currentVersion, $installedVersion);
1439
-			if ($versionDiff > 0) {
1440
-				return true;
1441
-			} elseif ($config->getValue('debug', false) && $versionDiff < 0) {
1442
-				// downgrade with debug
1443
-				$installedMajor = explode('.', $installedVersion);
1444
-				$installedMajor = $installedMajor[0] . '.' . $installedMajor[1];
1445
-				$currentMajor = explode('.', $currentVersion);
1446
-				$currentMajor = $currentMajor[0] . '.' . $currentMajor[1];
1447
-				if ($installedMajor === $currentMajor) {
1448
-					// Same major, allow downgrade for developers
1449
-					return true;
1450
-				} else {
1451
-					// downgrade attempt, throw exception
1452
-					throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
1453
-				}
1454
-			} elseif ($versionDiff < 0) {
1455
-				// downgrade attempt, throw exception
1456
-				throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
1457
-			}
1458
-
1459
-			// also check for upgrades for apps (independently from the user)
1460
-			$apps = \OC_App::getEnabledApps(false, true);
1461
-			$shouldUpgrade = false;
1462
-			foreach ($apps as $app) {
1463
-				if (\OC_App::shouldUpgrade($app)) {
1464
-					$shouldUpgrade = true;
1465
-					break;
1466
-				}
1467
-			}
1468
-			return $shouldUpgrade;
1469
-		} else {
1470
-			return false;
1471
-		}
1472
-	}
1473
-
1474
-	/**
1475
-	 * is this Internet explorer ?
1476
-	 *
1477
-	 * @return boolean
1478
-	 */
1479
-	public static function isIe() {
1480
-		if (!isset($_SERVER['HTTP_USER_AGENT'])) {
1481
-			return false;
1482
-		}
1483
-
1484
-		return preg_match(Request::USER_AGENT_IE, $_SERVER['HTTP_USER_AGENT']) === 1;
1485
-	}
1286
+        return $content !== $testContent && $fallbackContent !== $testContent;
1287
+    }
1288
+
1289
+    /**
1290
+     * Check if the setlocal call does not work. This can happen if the right
1291
+     * local packages are not available on the server.
1292
+     *
1293
+     * @return bool
1294
+     */
1295
+    public static function isSetLocaleWorking() {
1296
+        \Patchwork\Utf8\Bootup::initLocale();
1297
+        if ('' === basename('§')) {
1298
+            return false;
1299
+        }
1300
+        return true;
1301
+    }
1302
+
1303
+    /**
1304
+     * Check if it's possible to get the inline annotations
1305
+     *
1306
+     * @return bool
1307
+     */
1308
+    public static function isAnnotationsWorking() {
1309
+        $reflection = new \ReflectionMethod(__METHOD__);
1310
+        $docs = $reflection->getDocComment();
1311
+
1312
+        return (is_string($docs) && strlen($docs) > 50);
1313
+    }
1314
+
1315
+    /**
1316
+     * Check if the PHP module fileinfo is loaded.
1317
+     *
1318
+     * @return bool
1319
+     */
1320
+    public static function fileInfoLoaded() {
1321
+        return function_exists('finfo_open');
1322
+    }
1323
+
1324
+    /**
1325
+     * clear all levels of output buffering
1326
+     *
1327
+     * @return void
1328
+     */
1329
+    public static function obEnd() {
1330
+        while (ob_get_level()) {
1331
+            ob_end_clean();
1332
+        }
1333
+    }
1334
+
1335
+    /**
1336
+     * Checks whether the server is running on Mac OS X
1337
+     *
1338
+     * @return bool true if running on Mac OS X, false otherwise
1339
+     */
1340
+    public static function runningOnMac() {
1341
+        return (strtoupper(substr(PHP_OS, 0, 6)) === 'DARWIN');
1342
+    }
1343
+
1344
+    /**
1345
+     * Handles the case that there may not be a theme, then check if a "default"
1346
+     * theme exists and take that one
1347
+     *
1348
+     * @return string the theme
1349
+     */
1350
+    public static function getTheme() {
1351
+        $theme = \OC::$server->getSystemConfig()->getValue("theme", '');
1352
+
1353
+        if ($theme === '') {
1354
+            if (is_dir(OC::$SERVERROOT . '/themes/default')) {
1355
+                $theme = 'default';
1356
+            }
1357
+        }
1358
+
1359
+        return $theme;
1360
+    }
1361
+
1362
+    /**
1363
+     * Normalize a unicode string
1364
+     *
1365
+     * @param string $value a not normalized string
1366
+     * @return bool|string
1367
+     */
1368
+    public static function normalizeUnicode($value) {
1369
+        if (Normalizer::isNormalized($value)) {
1370
+            return $value;
1371
+        }
1372
+
1373
+        $normalizedValue = Normalizer::normalize($value);
1374
+        if ($normalizedValue === null || $normalizedValue === false) {
1375
+            \OC::$server->getLogger()->warning('normalizing failed for "' . $value . '"', ['app' => 'core']);
1376
+            return $value;
1377
+        }
1378
+
1379
+        return $normalizedValue;
1380
+    }
1381
+
1382
+    /**
1383
+     * A human readable string is generated based on version and build number
1384
+     *
1385
+     * @return string
1386
+     */
1387
+    public static function getHumanVersion() {
1388
+        $version = OC_Util::getVersionString();
1389
+        $build = OC_Util::getBuild();
1390
+        if (!empty($build) and OC_Util::getChannel() === 'daily') {
1391
+            $version .= ' Build:' . $build;
1392
+        }
1393
+        return $version;
1394
+    }
1395
+
1396
+    /**
1397
+     * Returns whether the given file name is valid
1398
+     *
1399
+     * @param string $file file name to check
1400
+     * @return bool true if the file name is valid, false otherwise
1401
+     * @deprecated use \OC\Files\View::verifyPath()
1402
+     */
1403
+    public static function isValidFileName($file) {
1404
+        $trimmed = trim($file);
1405
+        if ($trimmed === '') {
1406
+            return false;
1407
+        }
1408
+        if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
1409
+            return false;
1410
+        }
1411
+
1412
+        // detect part files
1413
+        if (preg_match('/' . \OCP\Files\FileInfo::BLACKLIST_FILES_REGEX . '/', $trimmed) !== 0) {
1414
+            return false;
1415
+        }
1416
+
1417
+        foreach (str_split($trimmed) as $char) {
1418
+            if (strpos(\OCP\Constants::FILENAME_INVALID_CHARS, $char) !== false) {
1419
+                return false;
1420
+            }
1421
+        }
1422
+        return true;
1423
+    }
1424
+
1425
+    /**
1426
+     * Check whether the instance needs to perform an upgrade,
1427
+     * either when the core version is higher or any app requires
1428
+     * an upgrade.
1429
+     *
1430
+     * @param \OC\SystemConfig $config
1431
+     * @return bool whether the core or any app needs an upgrade
1432
+     * @throws \OC\HintException When the upgrade from the given version is not allowed
1433
+     */
1434
+    public static function needUpgrade(\OC\SystemConfig $config) {
1435
+        if ($config->getValue('installed', false)) {
1436
+            $installedVersion = $config->getValue('version', '0.0.0');
1437
+            $currentVersion = implode('.', \OCP\Util::getVersion());
1438
+            $versionDiff = version_compare($currentVersion, $installedVersion);
1439
+            if ($versionDiff > 0) {
1440
+                return true;
1441
+            } elseif ($config->getValue('debug', false) && $versionDiff < 0) {
1442
+                // downgrade with debug
1443
+                $installedMajor = explode('.', $installedVersion);
1444
+                $installedMajor = $installedMajor[0] . '.' . $installedMajor[1];
1445
+                $currentMajor = explode('.', $currentVersion);
1446
+                $currentMajor = $currentMajor[0] . '.' . $currentMajor[1];
1447
+                if ($installedMajor === $currentMajor) {
1448
+                    // Same major, allow downgrade for developers
1449
+                    return true;
1450
+                } else {
1451
+                    // downgrade attempt, throw exception
1452
+                    throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
1453
+                }
1454
+            } elseif ($versionDiff < 0) {
1455
+                // downgrade attempt, throw exception
1456
+                throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
1457
+            }
1458
+
1459
+            // also check for upgrades for apps (independently from the user)
1460
+            $apps = \OC_App::getEnabledApps(false, true);
1461
+            $shouldUpgrade = false;
1462
+            foreach ($apps as $app) {
1463
+                if (\OC_App::shouldUpgrade($app)) {
1464
+                    $shouldUpgrade = true;
1465
+                    break;
1466
+                }
1467
+            }
1468
+            return $shouldUpgrade;
1469
+        } else {
1470
+            return false;
1471
+        }
1472
+    }
1473
+
1474
+    /**
1475
+     * is this Internet explorer ?
1476
+     *
1477
+     * @return boolean
1478
+     */
1479
+    public static function isIe() {
1480
+        if (!isset($_SERVER['HTTP_USER_AGENT'])) {
1481
+            return false;
1482
+        }
1483
+
1484
+        return preg_match(Request::USER_AGENT_IE, $_SERVER['HTTP_USER_AGENT']) === 1;
1485
+    }
1486 1486
 }
Please login to merge, or discard this patch.
apps/files_external/lib/Service/DBConfigService.php 1 patch
Indentation   +496 added lines, -496 removed lines patch added patch discarded remove patch
@@ -37,500 +37,500 @@
 block discarded – undo
37 37
  * Stores the mount config in the database
38 38
  */
39 39
 class DBConfigService {
40
-	public const MOUNT_TYPE_ADMIN = 1;
41
-	public const MOUNT_TYPE_PERSONAl = 2;
42
-
43
-	public const APPLICABLE_TYPE_GLOBAL = 1;
44
-	public const APPLICABLE_TYPE_GROUP = 2;
45
-	public const APPLICABLE_TYPE_USER = 3;
46
-
47
-	/**
48
-	 * @var IDBConnection
49
-	 */
50
-	private $connection;
51
-
52
-	/**
53
-	 * @var ICrypto
54
-	 */
55
-	private $crypto;
56
-
57
-	/**
58
-	 * DBConfigService constructor.
59
-	 *
60
-	 * @param IDBConnection $connection
61
-	 * @param ICrypto $crypto
62
-	 */
63
-	public function __construct(IDBConnection $connection, ICrypto $crypto) {
64
-		$this->connection = $connection;
65
-		$this->crypto = $crypto;
66
-	}
67
-
68
-	/**
69
-	 * @param int $mountId
70
-	 * @return array
71
-	 */
72
-	public function getMountById($mountId) {
73
-		$builder = $this->connection->getQueryBuilder();
74
-		$query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type'])
75
-			->from('external_mounts', 'm')
76
-			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
77
-		$mounts = $this->getMountsFromQuery($query);
78
-		if (count($mounts) > 0) {
79
-			return $mounts[0];
80
-		} else {
81
-			return null;
82
-		}
83
-	}
84
-
85
-	/**
86
-	 * Get all configured mounts
87
-	 *
88
-	 * @return array
89
-	 */
90
-	public function getAllMounts() {
91
-		$builder = $this->connection->getQueryBuilder();
92
-		$query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type'])
93
-			->from('external_mounts');
94
-		return $this->getMountsFromQuery($query);
95
-	}
96
-
97
-	public function getMountsForUser($userId, $groupIds) {
98
-		$builder = $this->connection->getQueryBuilder();
99
-		$query = $builder->select(['m.mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'm.type'])
100
-			->from('external_mounts', 'm')
101
-			->innerJoin('m', 'external_applicable', 'a', $builder->expr()->eq('m.mount_id', 'a.mount_id'))
102
-			->where($builder->expr()->orX(
103
-				$builder->expr()->andX( // global mounts
104
-					$builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_GLOBAL, IQueryBuilder::PARAM_INT)),
105
-					$builder->expr()->isNull('a.value')
106
-				),
107
-				$builder->expr()->andX( // mounts for user
108
-					$builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_USER, IQueryBuilder::PARAM_INT)),
109
-					$builder->expr()->eq('a.value', $builder->createNamedParameter($userId))
110
-				),
111
-				$builder->expr()->andX( // mounts for group
112
-					$builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_GROUP, IQueryBuilder::PARAM_INT)),
113
-					$builder->expr()->in('a.value', $builder->createNamedParameter($groupIds, IQueryBuilder::PARAM_STR_ARRAY))
114
-				)
115
-			));
116
-
117
-		return $this->getMountsFromQuery($query);
118
-	}
119
-
120
-	public function modifyMountsOnUserDelete(string $uid): void {
121
-		$this->modifyMountsOnDelete($uid, self::APPLICABLE_TYPE_USER);
122
-	}
123
-
124
-	public function modifyMountsOnGroupDelete(string $gid): void {
125
-		$this->modifyMountsOnDelete($gid, self::APPLICABLE_TYPE_GROUP);
126
-	}
127
-
128
-	protected function modifyMountsOnDelete(string $applicableId, int $applicableType): void {
129
-		$builder = $this->connection->getQueryBuilder();
130
-		$query = $builder->select(['a.mount_id', $builder->func()->count('a.mount_id', 'count')])
131
-			->from('external_applicable', 'a')
132
-			->leftJoin('a', 'external_applicable', 'b', $builder->expr()->eq('a.mount_id', 'b.mount_id'))
133
-			->where($builder->expr()->andX(
134
-				$builder->expr()->eq('b.type', $builder->createNamedParameter($applicableType, IQueryBuilder::PARAM_INT)),
135
-				$builder->expr()->eq('b.value', $builder->createNamedParameter($applicableId))
136
-			)
137
-			)
138
-			->groupBy(['a.mount_id']);
139
-		$stmt = $query->execute();
140
-		$result = $stmt->fetchAll();
141
-		$stmt->closeCursor();
142
-
143
-		foreach ($result as $row) {
144
-			if ((int)$row['count'] > 1) {
145
-				$this->removeApplicable($row['mount_id'], $applicableType, $applicableId);
146
-			} else {
147
-				$this->removeMount($row['mount_id']);
148
-			}
149
-		}
150
-	}
151
-
152
-	/**
153
-	 * Get admin defined mounts
154
-	 *
155
-	 * @return array
156
-	 */
157
-	public function getAdminMounts() {
158
-		$builder = $this->connection->getQueryBuilder();
159
-		$query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type'])
160
-			->from('external_mounts')
161
-			->where($builder->expr()->eq('type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT)));
162
-		return $this->getMountsFromQuery($query);
163
-	}
164
-
165
-	protected function getForQuery(IQueryBuilder $builder, $type, $value) {
166
-		$query = $builder->select(['m.mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'm.type'])
167
-			->from('external_mounts', 'm')
168
-			->innerJoin('m', 'external_applicable', 'a', $builder->expr()->eq('m.mount_id', 'a.mount_id'))
169
-			->where($builder->expr()->eq('a.type', $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
170
-
171
-		if (is_null($value)) {
172
-			$query = $query->andWhere($builder->expr()->isNull('a.value'));
173
-		} else {
174
-			$query = $query->andWhere($builder->expr()->eq('a.value', $builder->createNamedParameter($value)));
175
-		}
176
-
177
-		return $query;
178
-	}
179
-
180
-	/**
181
-	 * Get mounts by applicable
182
-	 *
183
-	 * @param int $type any of the self::APPLICABLE_TYPE_ constants
184
-	 * @param string|null $value user_id, group_id or null for global mounts
185
-	 * @return array
186
-	 */
187
-	public function getMountsFor($type, $value) {
188
-		$builder = $this->connection->getQueryBuilder();
189
-		$query = $this->getForQuery($builder, $type, $value);
190
-
191
-		return $this->getMountsFromQuery($query);
192
-	}
193
-
194
-	/**
195
-	 * Get admin defined mounts by applicable
196
-	 *
197
-	 * @param int $type any of the self::APPLICABLE_TYPE_ constants
198
-	 * @param string|null $value user_id, group_id or null for global mounts
199
-	 * @return array
200
-	 */
201
-	public function getAdminMountsFor($type, $value) {
202
-		$builder = $this->connection->getQueryBuilder();
203
-		$query = $this->getForQuery($builder, $type, $value);
204
-		$query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT)));
205
-
206
-		return $this->getMountsFromQuery($query);
207
-	}
208
-
209
-	/**
210
-	 * Get admin defined mounts for multiple applicable
211
-	 *
212
-	 * @param int $type any of the self::APPLICABLE_TYPE_ constants
213
-	 * @param string[] $values user_ids or group_ids
214
-	 * @return array
215
-	 */
216
-	public function getAdminMountsForMultiple($type, array $values) {
217
-		$builder = $this->connection->getQueryBuilder();
218
-		$params = array_map(function ($value) use ($builder) {
219
-			return $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR);
220
-		}, $values);
221
-
222
-		$query = $builder->select(['m.mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'm.type'])
223
-			->from('external_mounts', 'm')
224
-			->innerJoin('m', 'external_applicable', 'a', $builder->expr()->eq('m.mount_id', 'a.mount_id'))
225
-			->where($builder->expr()->eq('a.type', $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)))
226
-			->andWhere($builder->expr()->in('a.value', $params));
227
-		$query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT)));
228
-
229
-		return $this->getMountsFromQuery($query);
230
-	}
231
-
232
-	/**
233
-	 * Get user defined mounts by applicable
234
-	 *
235
-	 * @param int $type any of the self::APPLICABLE_TYPE_ constants
236
-	 * @param string|null $value user_id, group_id or null for global mounts
237
-	 * @return array
238
-	 */
239
-	public function getUserMountsFor($type, $value) {
240
-		$builder = $this->connection->getQueryBuilder();
241
-		$query = $this->getForQuery($builder, $type, $value);
242
-		$query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_PERSONAl, IQueryBuilder::PARAM_INT)));
243
-
244
-		return $this->getMountsFromQuery($query);
245
-	}
246
-
247
-	/**
248
-	 * Add a mount to the database
249
-	 *
250
-	 * @param string $mountPoint
251
-	 * @param string $storageBackend
252
-	 * @param string $authBackend
253
-	 * @param int $priority
254
-	 * @param int $type self::MOUNT_TYPE_ADMIN or self::MOUNT_TYPE_PERSONAL
255
-	 * @return int the id of the new mount
256
-	 */
257
-	public function addMount($mountPoint, $storageBackend, $authBackend, $priority, $type) {
258
-		if (!$priority) {
259
-			$priority = 100;
260
-		}
261
-		$builder = $this->connection->getQueryBuilder();
262
-		$query = $builder->insert('external_mounts')
263
-			->values([
264
-				'mount_point' => $builder->createNamedParameter($mountPoint, IQueryBuilder::PARAM_STR),
265
-				'storage_backend' => $builder->createNamedParameter($storageBackend, IQueryBuilder::PARAM_STR),
266
-				'auth_backend' => $builder->createNamedParameter($authBackend, IQueryBuilder::PARAM_STR),
267
-				'priority' => $builder->createNamedParameter($priority, IQueryBuilder::PARAM_INT),
268
-				'type' => $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)
269
-			]);
270
-		$query->execute();
271
-		return (int)$this->connection->lastInsertId('*PREFIX*external_mounts');
272
-	}
273
-
274
-	/**
275
-	 * Remove a mount from the database
276
-	 *
277
-	 * @param int $mountId
278
-	 */
279
-	public function removeMount($mountId) {
280
-		$builder = $this->connection->getQueryBuilder();
281
-		$query = $builder->delete('external_mounts')
282
-			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
283
-		$query->execute();
284
-
285
-		$query = $builder->delete('external_applicable')
286
-			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
287
-		$query->execute();
288
-
289
-		$query = $builder->delete('external_config')
290
-			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
291
-		$query->execute();
292
-
293
-		$query = $builder->delete('external_options')
294
-			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
295
-		$query->execute();
296
-	}
297
-
298
-	/**
299
-	 * @param int $mountId
300
-	 * @param string $newMountPoint
301
-	 */
302
-	public function setMountPoint($mountId, $newMountPoint) {
303
-		$builder = $this->connection->getQueryBuilder();
304
-
305
-		$query = $builder->update('external_mounts')
306
-			->set('mount_point', $builder->createNamedParameter($newMountPoint))
307
-			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
308
-
309
-		$query->execute();
310
-	}
311
-
312
-	/**
313
-	 * @param int $mountId
314
-	 * @param string $newAuthBackend
315
-	 */
316
-	public function setAuthBackend($mountId, $newAuthBackend) {
317
-		$builder = $this->connection->getQueryBuilder();
318
-
319
-		$query = $builder->update('external_mounts')
320
-			->set('auth_backend', $builder->createNamedParameter($newAuthBackend))
321
-			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
322
-
323
-		$query->execute();
324
-	}
325
-
326
-	/**
327
-	 * @param int $mountId
328
-	 * @param string $key
329
-	 * @param string $value
330
-	 */
331
-	public function setConfig($mountId, $key, $value) {
332
-		if ($key === 'password') {
333
-			$value = $this->encryptValue($value);
334
-		}
335
-
336
-		try {
337
-			$builder = $this->connection->getQueryBuilder();
338
-			$builder->insert('external_config')
339
-				->setValue('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))
340
-				->setValue('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR))
341
-				->setValue('value', $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR))
342
-				->execute();
343
-		} catch (UniqueConstraintViolationException $e) {
344
-			$builder = $this->connection->getQueryBuilder();
345
-			$query = $builder->update('external_config')
346
-				->set('value', $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR))
347
-				->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
348
-				->andWhere($builder->expr()->eq('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR)));
349
-			$query->execute();
350
-		}
351
-	}
352
-
353
-	/**
354
-	 * @param int $mountId
355
-	 * @param string $key
356
-	 * @param string $value
357
-	 */
358
-	public function setOption($mountId, $key, $value) {
359
-		try {
360
-			$builder = $this->connection->getQueryBuilder();
361
-			$builder->insert('external_options')
362
-				->setValue('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))
363
-				->setValue('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR))
364
-				->setValue('value', $builder->createNamedParameter(json_encode($value), IQueryBuilder::PARAM_STR))
365
-				->execute();
366
-		} catch (UniqueConstraintViolationException $e) {
367
-			$builder = $this->connection->getQueryBuilder();
368
-			$query = $builder->update('external_options')
369
-				->set('value', $builder->createNamedParameter(json_encode($value), IQueryBuilder::PARAM_STR))
370
-				->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
371
-				->andWhere($builder->expr()->eq('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR)));
372
-			$query->execute();
373
-		}
374
-	}
375
-
376
-	public function addApplicable($mountId, $type, $value) {
377
-		try {
378
-			$builder = $this->connection->getQueryBuilder();
379
-			$builder->insert('external_applicable')
380
-				->setValue('mount_id', $builder->createNamedParameter($mountId))
381
-				->setValue('type', $builder->createNamedParameter($type))
382
-				->setValue('value', $builder->createNamedParameter($value))
383
-				->execute();
384
-		} catch (UniqueConstraintViolationException $e) {
385
-			// applicable exists already
386
-		}
387
-	}
388
-
389
-	public function removeApplicable($mountId, $type, $value) {
390
-		$builder = $this->connection->getQueryBuilder();
391
-		$query = $builder->delete('external_applicable')
392
-			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
393
-			->andWhere($builder->expr()->eq('type', $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
394
-
395
-		if (is_null($value)) {
396
-			$query = $query->andWhere($builder->expr()->isNull('value'));
397
-		} else {
398
-			$query = $query->andWhere($builder->expr()->eq('value', $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR)));
399
-		}
400
-
401
-		$query->execute();
402
-	}
403
-
404
-	private function getMountsFromQuery(IQueryBuilder $query) {
405
-		$result = $query->execute();
406
-		$mounts = $result->fetchAll();
407
-		$uniqueMounts = [];
408
-		foreach ($mounts as $mount) {
409
-			$id = $mount['mount_id'];
410
-			if (!isset($uniqueMounts[$id])) {
411
-				$uniqueMounts[$id] = $mount;
412
-			}
413
-		}
414
-		$uniqueMounts = array_values($uniqueMounts);
415
-
416
-		$mountIds = array_map(function ($mount) {
417
-			return $mount['mount_id'];
418
-		}, $uniqueMounts);
419
-		$mountIds = array_values(array_unique($mountIds));
420
-
421
-		$applicable = $this->getApplicableForMounts($mountIds);
422
-		$config = $this->getConfigForMounts($mountIds);
423
-		$options = $this->getOptionsForMounts($mountIds);
424
-
425
-		return array_map(function ($mount, $applicable, $config, $options) {
426
-			$mount['type'] = (int)$mount['type'];
427
-			$mount['priority'] = (int)$mount['priority'];
428
-			$mount['applicable'] = $applicable;
429
-			$mount['config'] = $config;
430
-			$mount['options'] = $options;
431
-			return $mount;
432
-		}, $uniqueMounts, $applicable, $config, $options);
433
-	}
434
-
435
-	/**
436
-	 * Get mount options from a table grouped by mount id
437
-	 *
438
-	 * @param string $table
439
-	 * @param string[] $fields
440
-	 * @param int[] $mountIds
441
-	 * @return array [$mountId => [['field1' => $value1, ...], ...], ...]
442
-	 */
443
-	private function selectForMounts($table, array $fields, array $mountIds) {
444
-		if (count($mountIds) === 0) {
445
-			return [];
446
-		}
447
-		$builder = $this->connection->getQueryBuilder();
448
-		$fields[] = 'mount_id';
449
-		$placeHolders = array_map(function ($id) use ($builder) {
450
-			return $builder->createPositionalParameter($id, IQueryBuilder::PARAM_INT);
451
-		}, $mountIds);
452
-		$query = $builder->select($fields)
453
-			->from($table)
454
-			->where($builder->expr()->in('mount_id', $placeHolders));
455
-
456
-		$result = $query->execute();
457
-		$rows = $result->fetchAll();
458
-		$result->closeCursor();
459
-
460
-		$result = [];
461
-		foreach ($mountIds as $mountId) {
462
-			$result[$mountId] = [];
463
-		}
464
-		foreach ($rows as $row) {
465
-			if (isset($row['type'])) {
466
-				$row['type'] = (int)$row['type'];
467
-			}
468
-			$result[$row['mount_id']][] = $row;
469
-		}
470
-		return $result;
471
-	}
472
-
473
-	/**
474
-	 * @param int[] $mountIds
475
-	 * @return array [$id => [['type' => $type, 'value' => $value], ...], ...]
476
-	 */
477
-	public function getApplicableForMounts($mountIds) {
478
-		return $this->selectForMounts('external_applicable', ['type', 'value'], $mountIds);
479
-	}
480
-
481
-	/**
482
-	 * @param int[] $mountIds
483
-	 * @return array [$id => ['key1' => $value1, ...], ...]
484
-	 */
485
-	public function getConfigForMounts($mountIds) {
486
-		$mountConfigs = $this->selectForMounts('external_config', ['key', 'value'], $mountIds);
487
-		return array_map([$this, 'createKeyValueMap'], $mountConfigs);
488
-	}
489
-
490
-	/**
491
-	 * @param int[] $mountIds
492
-	 * @return array [$id => ['key1' => $value1, ...], ...]
493
-	 */
494
-	public function getOptionsForMounts($mountIds) {
495
-		$mountOptions = $this->selectForMounts('external_options', ['key', 'value'], $mountIds);
496
-		$optionsMap = array_map([$this, 'createKeyValueMap'], $mountOptions);
497
-		return array_map(function (array $options) {
498
-			return array_map(function ($option) {
499
-				return json_decode($option);
500
-			}, $options);
501
-		}, $optionsMap);
502
-	}
503
-
504
-	/**
505
-	 * @param array $keyValuePairs [['key'=>$key, 'value=>$value], ...]
506
-	 * @return array ['key1' => $value1, ...]
507
-	 */
508
-	private function createKeyValueMap(array $keyValuePairs) {
509
-		$decryptedPairts = array_map(function ($pair) {
510
-			if ($pair['key'] === 'password') {
511
-				$pair['value'] = $this->decryptValue($pair['value']);
512
-			}
513
-			return $pair;
514
-		}, $keyValuePairs);
515
-		$keys = array_map(function ($pair) {
516
-			return $pair['key'];
517
-		}, $decryptedPairts);
518
-		$values = array_map(function ($pair) {
519
-			return $pair['value'];
520
-		}, $decryptedPairts);
521
-
522
-		return array_combine($keys, $values);
523
-	}
524
-
525
-	private function encryptValue($value) {
526
-		return $this->crypto->encrypt($value);
527
-	}
528
-
529
-	private function decryptValue($value) {
530
-		try {
531
-			return $this->crypto->decrypt($value);
532
-		} catch (\Exception $e) {
533
-			return $value;
534
-		}
535
-	}
40
+    public const MOUNT_TYPE_ADMIN = 1;
41
+    public const MOUNT_TYPE_PERSONAl = 2;
42
+
43
+    public const APPLICABLE_TYPE_GLOBAL = 1;
44
+    public const APPLICABLE_TYPE_GROUP = 2;
45
+    public const APPLICABLE_TYPE_USER = 3;
46
+
47
+    /**
48
+     * @var IDBConnection
49
+     */
50
+    private $connection;
51
+
52
+    /**
53
+     * @var ICrypto
54
+     */
55
+    private $crypto;
56
+
57
+    /**
58
+     * DBConfigService constructor.
59
+     *
60
+     * @param IDBConnection $connection
61
+     * @param ICrypto $crypto
62
+     */
63
+    public function __construct(IDBConnection $connection, ICrypto $crypto) {
64
+        $this->connection = $connection;
65
+        $this->crypto = $crypto;
66
+    }
67
+
68
+    /**
69
+     * @param int $mountId
70
+     * @return array
71
+     */
72
+    public function getMountById($mountId) {
73
+        $builder = $this->connection->getQueryBuilder();
74
+        $query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type'])
75
+            ->from('external_mounts', 'm')
76
+            ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
77
+        $mounts = $this->getMountsFromQuery($query);
78
+        if (count($mounts) > 0) {
79
+            return $mounts[0];
80
+        } else {
81
+            return null;
82
+        }
83
+    }
84
+
85
+    /**
86
+     * Get all configured mounts
87
+     *
88
+     * @return array
89
+     */
90
+    public function getAllMounts() {
91
+        $builder = $this->connection->getQueryBuilder();
92
+        $query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type'])
93
+            ->from('external_mounts');
94
+        return $this->getMountsFromQuery($query);
95
+    }
96
+
97
+    public function getMountsForUser($userId, $groupIds) {
98
+        $builder = $this->connection->getQueryBuilder();
99
+        $query = $builder->select(['m.mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'm.type'])
100
+            ->from('external_mounts', 'm')
101
+            ->innerJoin('m', 'external_applicable', 'a', $builder->expr()->eq('m.mount_id', 'a.mount_id'))
102
+            ->where($builder->expr()->orX(
103
+                $builder->expr()->andX( // global mounts
104
+                    $builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_GLOBAL, IQueryBuilder::PARAM_INT)),
105
+                    $builder->expr()->isNull('a.value')
106
+                ),
107
+                $builder->expr()->andX( // mounts for user
108
+                    $builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_USER, IQueryBuilder::PARAM_INT)),
109
+                    $builder->expr()->eq('a.value', $builder->createNamedParameter($userId))
110
+                ),
111
+                $builder->expr()->andX( // mounts for group
112
+                    $builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_GROUP, IQueryBuilder::PARAM_INT)),
113
+                    $builder->expr()->in('a.value', $builder->createNamedParameter($groupIds, IQueryBuilder::PARAM_STR_ARRAY))
114
+                )
115
+            ));
116
+
117
+        return $this->getMountsFromQuery($query);
118
+    }
119
+
120
+    public function modifyMountsOnUserDelete(string $uid): void {
121
+        $this->modifyMountsOnDelete($uid, self::APPLICABLE_TYPE_USER);
122
+    }
123
+
124
+    public function modifyMountsOnGroupDelete(string $gid): void {
125
+        $this->modifyMountsOnDelete($gid, self::APPLICABLE_TYPE_GROUP);
126
+    }
127
+
128
+    protected function modifyMountsOnDelete(string $applicableId, int $applicableType): void {
129
+        $builder = $this->connection->getQueryBuilder();
130
+        $query = $builder->select(['a.mount_id', $builder->func()->count('a.mount_id', 'count')])
131
+            ->from('external_applicable', 'a')
132
+            ->leftJoin('a', 'external_applicable', 'b', $builder->expr()->eq('a.mount_id', 'b.mount_id'))
133
+            ->where($builder->expr()->andX(
134
+                $builder->expr()->eq('b.type', $builder->createNamedParameter($applicableType, IQueryBuilder::PARAM_INT)),
135
+                $builder->expr()->eq('b.value', $builder->createNamedParameter($applicableId))
136
+            )
137
+            )
138
+            ->groupBy(['a.mount_id']);
139
+        $stmt = $query->execute();
140
+        $result = $stmt->fetchAll();
141
+        $stmt->closeCursor();
142
+
143
+        foreach ($result as $row) {
144
+            if ((int)$row['count'] > 1) {
145
+                $this->removeApplicable($row['mount_id'], $applicableType, $applicableId);
146
+            } else {
147
+                $this->removeMount($row['mount_id']);
148
+            }
149
+        }
150
+    }
151
+
152
+    /**
153
+     * Get admin defined mounts
154
+     *
155
+     * @return array
156
+     */
157
+    public function getAdminMounts() {
158
+        $builder = $this->connection->getQueryBuilder();
159
+        $query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type'])
160
+            ->from('external_mounts')
161
+            ->where($builder->expr()->eq('type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT)));
162
+        return $this->getMountsFromQuery($query);
163
+    }
164
+
165
+    protected function getForQuery(IQueryBuilder $builder, $type, $value) {
166
+        $query = $builder->select(['m.mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'm.type'])
167
+            ->from('external_mounts', 'm')
168
+            ->innerJoin('m', 'external_applicable', 'a', $builder->expr()->eq('m.mount_id', 'a.mount_id'))
169
+            ->where($builder->expr()->eq('a.type', $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
170
+
171
+        if (is_null($value)) {
172
+            $query = $query->andWhere($builder->expr()->isNull('a.value'));
173
+        } else {
174
+            $query = $query->andWhere($builder->expr()->eq('a.value', $builder->createNamedParameter($value)));
175
+        }
176
+
177
+        return $query;
178
+    }
179
+
180
+    /**
181
+     * Get mounts by applicable
182
+     *
183
+     * @param int $type any of the self::APPLICABLE_TYPE_ constants
184
+     * @param string|null $value user_id, group_id or null for global mounts
185
+     * @return array
186
+     */
187
+    public function getMountsFor($type, $value) {
188
+        $builder = $this->connection->getQueryBuilder();
189
+        $query = $this->getForQuery($builder, $type, $value);
190
+
191
+        return $this->getMountsFromQuery($query);
192
+    }
193
+
194
+    /**
195
+     * Get admin defined mounts by applicable
196
+     *
197
+     * @param int $type any of the self::APPLICABLE_TYPE_ constants
198
+     * @param string|null $value user_id, group_id or null for global mounts
199
+     * @return array
200
+     */
201
+    public function getAdminMountsFor($type, $value) {
202
+        $builder = $this->connection->getQueryBuilder();
203
+        $query = $this->getForQuery($builder, $type, $value);
204
+        $query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT)));
205
+
206
+        return $this->getMountsFromQuery($query);
207
+    }
208
+
209
+    /**
210
+     * Get admin defined mounts for multiple applicable
211
+     *
212
+     * @param int $type any of the self::APPLICABLE_TYPE_ constants
213
+     * @param string[] $values user_ids or group_ids
214
+     * @return array
215
+     */
216
+    public function getAdminMountsForMultiple($type, array $values) {
217
+        $builder = $this->connection->getQueryBuilder();
218
+        $params = array_map(function ($value) use ($builder) {
219
+            return $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR);
220
+        }, $values);
221
+
222
+        $query = $builder->select(['m.mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'm.type'])
223
+            ->from('external_mounts', 'm')
224
+            ->innerJoin('m', 'external_applicable', 'a', $builder->expr()->eq('m.mount_id', 'a.mount_id'))
225
+            ->where($builder->expr()->eq('a.type', $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)))
226
+            ->andWhere($builder->expr()->in('a.value', $params));
227
+        $query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT)));
228
+
229
+        return $this->getMountsFromQuery($query);
230
+    }
231
+
232
+    /**
233
+     * Get user defined mounts by applicable
234
+     *
235
+     * @param int $type any of the self::APPLICABLE_TYPE_ constants
236
+     * @param string|null $value user_id, group_id or null for global mounts
237
+     * @return array
238
+     */
239
+    public function getUserMountsFor($type, $value) {
240
+        $builder = $this->connection->getQueryBuilder();
241
+        $query = $this->getForQuery($builder, $type, $value);
242
+        $query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_PERSONAl, IQueryBuilder::PARAM_INT)));
243
+
244
+        return $this->getMountsFromQuery($query);
245
+    }
246
+
247
+    /**
248
+     * Add a mount to the database
249
+     *
250
+     * @param string $mountPoint
251
+     * @param string $storageBackend
252
+     * @param string $authBackend
253
+     * @param int $priority
254
+     * @param int $type self::MOUNT_TYPE_ADMIN or self::MOUNT_TYPE_PERSONAL
255
+     * @return int the id of the new mount
256
+     */
257
+    public function addMount($mountPoint, $storageBackend, $authBackend, $priority, $type) {
258
+        if (!$priority) {
259
+            $priority = 100;
260
+        }
261
+        $builder = $this->connection->getQueryBuilder();
262
+        $query = $builder->insert('external_mounts')
263
+            ->values([
264
+                'mount_point' => $builder->createNamedParameter($mountPoint, IQueryBuilder::PARAM_STR),
265
+                'storage_backend' => $builder->createNamedParameter($storageBackend, IQueryBuilder::PARAM_STR),
266
+                'auth_backend' => $builder->createNamedParameter($authBackend, IQueryBuilder::PARAM_STR),
267
+                'priority' => $builder->createNamedParameter($priority, IQueryBuilder::PARAM_INT),
268
+                'type' => $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)
269
+            ]);
270
+        $query->execute();
271
+        return (int)$this->connection->lastInsertId('*PREFIX*external_mounts');
272
+    }
273
+
274
+    /**
275
+     * Remove a mount from the database
276
+     *
277
+     * @param int $mountId
278
+     */
279
+    public function removeMount($mountId) {
280
+        $builder = $this->connection->getQueryBuilder();
281
+        $query = $builder->delete('external_mounts')
282
+            ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
283
+        $query->execute();
284
+
285
+        $query = $builder->delete('external_applicable')
286
+            ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
287
+        $query->execute();
288
+
289
+        $query = $builder->delete('external_config')
290
+            ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
291
+        $query->execute();
292
+
293
+        $query = $builder->delete('external_options')
294
+            ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
295
+        $query->execute();
296
+    }
297
+
298
+    /**
299
+     * @param int $mountId
300
+     * @param string $newMountPoint
301
+     */
302
+    public function setMountPoint($mountId, $newMountPoint) {
303
+        $builder = $this->connection->getQueryBuilder();
304
+
305
+        $query = $builder->update('external_mounts')
306
+            ->set('mount_point', $builder->createNamedParameter($newMountPoint))
307
+            ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
308
+
309
+        $query->execute();
310
+    }
311
+
312
+    /**
313
+     * @param int $mountId
314
+     * @param string $newAuthBackend
315
+     */
316
+    public function setAuthBackend($mountId, $newAuthBackend) {
317
+        $builder = $this->connection->getQueryBuilder();
318
+
319
+        $query = $builder->update('external_mounts')
320
+            ->set('auth_backend', $builder->createNamedParameter($newAuthBackend))
321
+            ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
322
+
323
+        $query->execute();
324
+    }
325
+
326
+    /**
327
+     * @param int $mountId
328
+     * @param string $key
329
+     * @param string $value
330
+     */
331
+    public function setConfig($mountId, $key, $value) {
332
+        if ($key === 'password') {
333
+            $value = $this->encryptValue($value);
334
+        }
335
+
336
+        try {
337
+            $builder = $this->connection->getQueryBuilder();
338
+            $builder->insert('external_config')
339
+                ->setValue('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))
340
+                ->setValue('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR))
341
+                ->setValue('value', $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR))
342
+                ->execute();
343
+        } catch (UniqueConstraintViolationException $e) {
344
+            $builder = $this->connection->getQueryBuilder();
345
+            $query = $builder->update('external_config')
346
+                ->set('value', $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR))
347
+                ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
348
+                ->andWhere($builder->expr()->eq('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR)));
349
+            $query->execute();
350
+        }
351
+    }
352
+
353
+    /**
354
+     * @param int $mountId
355
+     * @param string $key
356
+     * @param string $value
357
+     */
358
+    public function setOption($mountId, $key, $value) {
359
+        try {
360
+            $builder = $this->connection->getQueryBuilder();
361
+            $builder->insert('external_options')
362
+                ->setValue('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))
363
+                ->setValue('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR))
364
+                ->setValue('value', $builder->createNamedParameter(json_encode($value), IQueryBuilder::PARAM_STR))
365
+                ->execute();
366
+        } catch (UniqueConstraintViolationException $e) {
367
+            $builder = $this->connection->getQueryBuilder();
368
+            $query = $builder->update('external_options')
369
+                ->set('value', $builder->createNamedParameter(json_encode($value), IQueryBuilder::PARAM_STR))
370
+                ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
371
+                ->andWhere($builder->expr()->eq('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR)));
372
+            $query->execute();
373
+        }
374
+    }
375
+
376
+    public function addApplicable($mountId, $type, $value) {
377
+        try {
378
+            $builder = $this->connection->getQueryBuilder();
379
+            $builder->insert('external_applicable')
380
+                ->setValue('mount_id', $builder->createNamedParameter($mountId))
381
+                ->setValue('type', $builder->createNamedParameter($type))
382
+                ->setValue('value', $builder->createNamedParameter($value))
383
+                ->execute();
384
+        } catch (UniqueConstraintViolationException $e) {
385
+            // applicable exists already
386
+        }
387
+    }
388
+
389
+    public function removeApplicable($mountId, $type, $value) {
390
+        $builder = $this->connection->getQueryBuilder();
391
+        $query = $builder->delete('external_applicable')
392
+            ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
393
+            ->andWhere($builder->expr()->eq('type', $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
394
+
395
+        if (is_null($value)) {
396
+            $query = $query->andWhere($builder->expr()->isNull('value'));
397
+        } else {
398
+            $query = $query->andWhere($builder->expr()->eq('value', $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR)));
399
+        }
400
+
401
+        $query->execute();
402
+    }
403
+
404
+    private function getMountsFromQuery(IQueryBuilder $query) {
405
+        $result = $query->execute();
406
+        $mounts = $result->fetchAll();
407
+        $uniqueMounts = [];
408
+        foreach ($mounts as $mount) {
409
+            $id = $mount['mount_id'];
410
+            if (!isset($uniqueMounts[$id])) {
411
+                $uniqueMounts[$id] = $mount;
412
+            }
413
+        }
414
+        $uniqueMounts = array_values($uniqueMounts);
415
+
416
+        $mountIds = array_map(function ($mount) {
417
+            return $mount['mount_id'];
418
+        }, $uniqueMounts);
419
+        $mountIds = array_values(array_unique($mountIds));
420
+
421
+        $applicable = $this->getApplicableForMounts($mountIds);
422
+        $config = $this->getConfigForMounts($mountIds);
423
+        $options = $this->getOptionsForMounts($mountIds);
424
+
425
+        return array_map(function ($mount, $applicable, $config, $options) {
426
+            $mount['type'] = (int)$mount['type'];
427
+            $mount['priority'] = (int)$mount['priority'];
428
+            $mount['applicable'] = $applicable;
429
+            $mount['config'] = $config;
430
+            $mount['options'] = $options;
431
+            return $mount;
432
+        }, $uniqueMounts, $applicable, $config, $options);
433
+    }
434
+
435
+    /**
436
+     * Get mount options from a table grouped by mount id
437
+     *
438
+     * @param string $table
439
+     * @param string[] $fields
440
+     * @param int[] $mountIds
441
+     * @return array [$mountId => [['field1' => $value1, ...], ...], ...]
442
+     */
443
+    private function selectForMounts($table, array $fields, array $mountIds) {
444
+        if (count($mountIds) === 0) {
445
+            return [];
446
+        }
447
+        $builder = $this->connection->getQueryBuilder();
448
+        $fields[] = 'mount_id';
449
+        $placeHolders = array_map(function ($id) use ($builder) {
450
+            return $builder->createPositionalParameter($id, IQueryBuilder::PARAM_INT);
451
+        }, $mountIds);
452
+        $query = $builder->select($fields)
453
+            ->from($table)
454
+            ->where($builder->expr()->in('mount_id', $placeHolders));
455
+
456
+        $result = $query->execute();
457
+        $rows = $result->fetchAll();
458
+        $result->closeCursor();
459
+
460
+        $result = [];
461
+        foreach ($mountIds as $mountId) {
462
+            $result[$mountId] = [];
463
+        }
464
+        foreach ($rows as $row) {
465
+            if (isset($row['type'])) {
466
+                $row['type'] = (int)$row['type'];
467
+            }
468
+            $result[$row['mount_id']][] = $row;
469
+        }
470
+        return $result;
471
+    }
472
+
473
+    /**
474
+     * @param int[] $mountIds
475
+     * @return array [$id => [['type' => $type, 'value' => $value], ...], ...]
476
+     */
477
+    public function getApplicableForMounts($mountIds) {
478
+        return $this->selectForMounts('external_applicable', ['type', 'value'], $mountIds);
479
+    }
480
+
481
+    /**
482
+     * @param int[] $mountIds
483
+     * @return array [$id => ['key1' => $value1, ...], ...]
484
+     */
485
+    public function getConfigForMounts($mountIds) {
486
+        $mountConfigs = $this->selectForMounts('external_config', ['key', 'value'], $mountIds);
487
+        return array_map([$this, 'createKeyValueMap'], $mountConfigs);
488
+    }
489
+
490
+    /**
491
+     * @param int[] $mountIds
492
+     * @return array [$id => ['key1' => $value1, ...], ...]
493
+     */
494
+    public function getOptionsForMounts($mountIds) {
495
+        $mountOptions = $this->selectForMounts('external_options', ['key', 'value'], $mountIds);
496
+        $optionsMap = array_map([$this, 'createKeyValueMap'], $mountOptions);
497
+        return array_map(function (array $options) {
498
+            return array_map(function ($option) {
499
+                return json_decode($option);
500
+            }, $options);
501
+        }, $optionsMap);
502
+    }
503
+
504
+    /**
505
+     * @param array $keyValuePairs [['key'=>$key, 'value=>$value], ...]
506
+     * @return array ['key1' => $value1, ...]
507
+     */
508
+    private function createKeyValueMap(array $keyValuePairs) {
509
+        $decryptedPairts = array_map(function ($pair) {
510
+            if ($pair['key'] === 'password') {
511
+                $pair['value'] = $this->decryptValue($pair['value']);
512
+            }
513
+            return $pair;
514
+        }, $keyValuePairs);
515
+        $keys = array_map(function ($pair) {
516
+            return $pair['key'];
517
+        }, $decryptedPairts);
518
+        $values = array_map(function ($pair) {
519
+            return $pair['value'];
520
+        }, $decryptedPairts);
521
+
522
+        return array_combine($keys, $values);
523
+    }
524
+
525
+    private function encryptValue($value) {
526
+        return $this->crypto->encrypt($value);
527
+    }
528
+
529
+    private function decryptValue($value) {
530
+        try {
531
+            return $this->crypto->decrypt($value);
532
+        } catch (\Exception $e) {
533
+            return $value;
534
+        }
535
+    }
536 536
 }
Please login to merge, or discard this patch.
apps/dav/lib/Migration/BuildCalendarSearchIndex.php 2 patches
Indentation   +47 added lines, -47 removed lines patch added patch discarded remove patch
@@ -33,59 +33,59 @@
 block discarded – undo
33 33
 
34 34
 class BuildCalendarSearchIndex implements IRepairStep {
35 35
 
36
-	/** @var IDBConnection */
37
-	private $db;
36
+    /** @var IDBConnection */
37
+    private $db;
38 38
 
39
-	/** @var IJobList */
40
-	private $jobList;
39
+    /** @var IJobList */
40
+    private $jobList;
41 41
 
42
-	/** @var IConfig */
43
-	private $config;
42
+    /** @var IConfig */
43
+    private $config;
44 44
 
45
-	/**
46
-	 * @param IDBConnection $db
47
-	 * @param IJobList $jobList
48
-	 * @param IConfig $config
49
-	 */
50
-	public function __construct(IDBConnection $db,
51
-								IJobList $jobList,
52
-								IConfig $config) {
53
-		$this->db = $db;
54
-		$this->jobList = $jobList;
55
-		$this->config = $config;
56
-	}
45
+    /**
46
+     * @param IDBConnection $db
47
+     * @param IJobList $jobList
48
+     * @param IConfig $config
49
+     */
50
+    public function __construct(IDBConnection $db,
51
+                                IJobList $jobList,
52
+                                IConfig $config) {
53
+        $this->db = $db;
54
+        $this->jobList = $jobList;
55
+        $this->config = $config;
56
+    }
57 57
 
58
-	/**
59
-	 * @return string
60
-	 */
61
-	public function getName() {
62
-		return 'Registering building of calendar search index as background job';
63
-	}
58
+    /**
59
+     * @return string
60
+     */
61
+    public function getName() {
62
+        return 'Registering building of calendar search index as background job';
63
+    }
64 64
 
65
-	/**
66
-	 * @param IOutput $output
67
-	 */
68
-	public function run(IOutput $output) {
69
-		// only run once
70
-		if ($this->config->getAppValue('dav', 'buildCalendarSearchIndex') === 'yes') {
71
-			$output->info('Repair step already executed');
72
-			return;
73
-		}
65
+    /**
66
+     * @param IOutput $output
67
+     */
68
+    public function run(IOutput $output) {
69
+        // only run once
70
+        if ($this->config->getAppValue('dav', 'buildCalendarSearchIndex') === 'yes') {
71
+            $output->info('Repair step already executed');
72
+            return;
73
+        }
74 74
 
75
-		$query = $this->db->getQueryBuilder();
76
-		$query->select($query->createFunction('MAX(' . $query->getColumnName('id') . ')'))
77
-			->from('calendarobjects');
78
-		$result = $query->execute();
79
-		$maxId = (int) $result->fetchColumn();
80
-		$result->closeCursor();
75
+        $query = $this->db->getQueryBuilder();
76
+        $query->select($query->createFunction('MAX(' . $query->getColumnName('id') . ')'))
77
+            ->from('calendarobjects');
78
+        $result = $query->execute();
79
+        $maxId = (int) $result->fetchColumn();
80
+        $result->closeCursor();
81 81
 
82
-		$output->info('Add background job');
83
-		$this->jobList->add(BuildCalendarSearchIndexBackgroundJob::class, [
84
-			'offset' => 0,
85
-			'stopAt' => $maxId
86
-		]);
82
+        $output->info('Add background job');
83
+        $this->jobList->add(BuildCalendarSearchIndexBackgroundJob::class, [
84
+            'offset' => 0,
85
+            'stopAt' => $maxId
86
+        ]);
87 87
 
88
-		// if all were done, no need to redo the repair during next upgrade
89
-		$this->config->setAppValue('dav', 'buildCalendarSearchIndex', 'yes');
90
-	}
88
+        // if all were done, no need to redo the repair during next upgrade
89
+        $this->config->setAppValue('dav', 'buildCalendarSearchIndex', 'yes');
90
+    }
91 91
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -73,7 +73,7 @@
 block discarded – undo
73 73
 		}
74 74
 
75 75
 		$query = $this->db->getQueryBuilder();
76
-		$query->select($query->createFunction('MAX(' . $query->getColumnName('id') . ')'))
76
+		$query->select($query->createFunction('MAX('.$query->getColumnName('id').')'))
77 77
 			->from('calendarobjects');
78 78
 		$result = $query->execute();
79 79
 		$maxId = (int) $result->fetchColumn();
Please login to merge, or discard this patch.
apps/dav/lib/Migration/RegisterBuildReminderIndexBackgroundJob.php 2 patches
Indentation   +49 added lines, -49 removed lines patch added patch discarded remove patch
@@ -41,62 +41,62 @@
 block discarded – undo
41 41
  */
42 42
 class RegisterBuildReminderIndexBackgroundJob implements IRepairStep {
43 43
 
44
-	/** @var IDBConnection */
45
-	private $db;
44
+    /** @var IDBConnection */
45
+    private $db;
46 46
 
47
-	/** @var IJobList */
48
-	private $jobList;
47
+    /** @var IJobList */
48
+    private $jobList;
49 49
 
50
-	/** @var IConfig */
51
-	private $config;
50
+    /** @var IConfig */
51
+    private $config;
52 52
 
53
-	/** @var string */
54
-	private const CONFIG_KEY = 'buildCalendarReminderIndex';
53
+    /** @var string */
54
+    private const CONFIG_KEY = 'buildCalendarReminderIndex';
55 55
 
56
-	/**
57
-	 * @param IDBConnection $db
58
-	 * @param IJobList $jobList
59
-	 * @param IConfig $config
60
-	 */
61
-	public function __construct(IDBConnection $db,
62
-								IJobList $jobList,
63
-								IConfig $config) {
64
-		$this->db = $db;
65
-		$this->jobList = $jobList;
66
-		$this->config = $config;
67
-	}
56
+    /**
57
+     * @param IDBConnection $db
58
+     * @param IJobList $jobList
59
+     * @param IConfig $config
60
+     */
61
+    public function __construct(IDBConnection $db,
62
+                                IJobList $jobList,
63
+                                IConfig $config) {
64
+        $this->db = $db;
65
+        $this->jobList = $jobList;
66
+        $this->config = $config;
67
+    }
68 68
 
69
-	/**
70
-	 * @return string
71
-	 */
72
-	public function getName() {
73
-		return 'Registering building of calendar reminder index as background job';
74
-	}
69
+    /**
70
+     * @return string
71
+     */
72
+    public function getName() {
73
+        return 'Registering building of calendar reminder index as background job';
74
+    }
75 75
 
76
-	/**
77
-	 * @param IOutput $output
78
-	 */
79
-	public function run(IOutput $output) {
80
-		// only run once
81
-		if ($this->config->getAppValue('dav', self::CONFIG_KEY) === 'yes') {
82
-			$output->info('Repair step already executed');
83
-			return;
84
-		}
76
+    /**
77
+     * @param IOutput $output
78
+     */
79
+    public function run(IOutput $output) {
80
+        // only run once
81
+        if ($this->config->getAppValue('dav', self::CONFIG_KEY) === 'yes') {
82
+            $output->info('Repair step already executed');
83
+            return;
84
+        }
85 85
 
86
-		$query = $this->db->getQueryBuilder();
87
-		$query->select($query->createFunction('MAX(' . $query->getColumnName('id') . ')'))
88
-			->from('calendarobjects');
89
-		$result = $query->execute();
90
-		$maxId = (int) $result->fetchColumn();
91
-		$result->closeCursor();
86
+        $query = $this->db->getQueryBuilder();
87
+        $query->select($query->createFunction('MAX(' . $query->getColumnName('id') . ')'))
88
+            ->from('calendarobjects');
89
+        $result = $query->execute();
90
+        $maxId = (int) $result->fetchColumn();
91
+        $result->closeCursor();
92 92
 
93
-		$output->info('Add background job');
94
-		$this->jobList->add(BuildReminderIndexBackgroundJob::class, [
95
-			'offset' => 0,
96
-			'stopAt' => $maxId
97
-		]);
93
+        $output->info('Add background job');
94
+        $this->jobList->add(BuildReminderIndexBackgroundJob::class, [
95
+            'offset' => 0,
96
+            'stopAt' => $maxId
97
+        ]);
98 98
 
99
-		// if all were done, no need to redo the repair during next upgrade
100
-		$this->config->setAppValue('dav', self::CONFIG_KEY, 'yes');
101
-	}
99
+        // if all were done, no need to redo the repair during next upgrade
100
+        $this->config->setAppValue('dav', self::CONFIG_KEY, 'yes');
101
+    }
102 102
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -84,7 +84,7 @@
 block discarded – undo
84 84
 		}
85 85
 
86 86
 		$query = $this->db->getQueryBuilder();
87
-		$query->select($query->createFunction('MAX(' . $query->getColumnName('id') . ')'))
87
+		$query->select($query->createFunction('MAX('.$query->getColumnName('id').')'))
88 88
 			->from('calendarobjects');
89 89
 		$result = $query->execute();
90 90
 		$maxId = (int) $result->fetchColumn();
Please login to merge, or discard this patch.
apps/dav/lib/CalDAV/CalDavBackend.php 2 patches
Indentation   +2725 added lines, -2725 removed lines patch added patch discarded remove patch
@@ -95,2729 +95,2729 @@
 block discarded – undo
95 95
  * @package OCA\DAV\CalDAV
96 96
  */
97 97
 class CalDavBackend extends AbstractBackend implements SyncSupport, SubscriptionSupport, SchedulingSupport {
98
-	public const CALENDAR_TYPE_CALENDAR = 0;
99
-	public const CALENDAR_TYPE_SUBSCRIPTION = 1;
100
-
101
-	public const PERSONAL_CALENDAR_URI = 'personal';
102
-	public const PERSONAL_CALENDAR_NAME = 'Personal';
103
-
104
-	public const RESOURCE_BOOKING_CALENDAR_URI = 'calendar';
105
-	public const RESOURCE_BOOKING_CALENDAR_NAME = 'Calendar';
106
-
107
-	/**
108
-	 * We need to specify a max date, because we need to stop *somewhere*
109
-	 *
110
-	 * On 32 bit system the maximum for a signed integer is 2147483647, so
111
-	 * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
112
-	 * in 2038-01-19 to avoid problems when the date is converted
113
-	 * to a unix timestamp.
114
-	 */
115
-	public const MAX_DATE = '2038-01-01';
116
-
117
-	public const ACCESS_PUBLIC = 4;
118
-	public const CLASSIFICATION_PUBLIC = 0;
119
-	public const CLASSIFICATION_PRIVATE = 1;
120
-	public const CLASSIFICATION_CONFIDENTIAL = 2;
121
-
122
-	/**
123
-	 * List of CalDAV properties, and how they map to database field names
124
-	 * Add your own properties by simply adding on to this array.
125
-	 *
126
-	 * Note that only string-based properties are supported here.
127
-	 *
128
-	 * @var array
129
-	 */
130
-	public $propertyMap = [
131
-		'{DAV:}displayname' => 'displayname',
132
-		'{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
133
-		'{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone',
134
-		'{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
135
-		'{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
136
-	];
137
-
138
-	/**
139
-	 * List of subscription properties, and how they map to database field names.
140
-	 *
141
-	 * @var array
142
-	 */
143
-	public $subscriptionPropertyMap = [
144
-		'{DAV:}displayname' => 'displayname',
145
-		'{http://apple.com/ns/ical/}refreshrate' => 'refreshrate',
146
-		'{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
147
-		'{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
148
-		'{http://calendarserver.org/ns/}subscribed-strip-todos' => 'striptodos',
149
-		'{http://calendarserver.org/ns/}subscribed-strip-alarms' => 'stripalarms',
150
-		'{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
151
-	];
152
-
153
-	/** @var array properties to index */
154
-	public static $indexProperties = ['CATEGORIES', 'COMMENT', 'DESCRIPTION',
155
-		'LOCATION', 'RESOURCES', 'STATUS', 'SUMMARY', 'ATTENDEE', 'CONTACT',
156
-		'ORGANIZER'];
157
-
158
-	/** @var array parameters to index */
159
-	public static $indexParameters = [
160
-		'ATTENDEE' => ['CN'],
161
-		'ORGANIZER' => ['CN'],
162
-	];
163
-
164
-	/**
165
-	 * @var string[] Map of uid => display name
166
-	 */
167
-	protected $userDisplayNames;
168
-
169
-	/** @var IDBConnection */
170
-	private $db;
171
-
172
-	/** @var Backend */
173
-	private $calendarSharingBackend;
174
-
175
-	/** @var Principal */
176
-	private $principalBackend;
177
-
178
-	/** @var IUserManager */
179
-	private $userManager;
180
-
181
-	/** @var ISecureRandom */
182
-	private $random;
183
-
184
-	/** @var ILogger */
185
-	private $logger;
186
-
187
-	/** @var IEventDispatcher */
188
-	private $dispatcher;
189
-
190
-	/** @var EventDispatcherInterface */
191
-	private $legacyDispatcher;
192
-
193
-	/** @var bool */
194
-	private $legacyEndpoint;
195
-
196
-	/** @var string */
197
-	private $dbObjectPropertiesTable = 'calendarobjects_props';
198
-
199
-	/**
200
-	 * CalDavBackend constructor.
201
-	 *
202
-	 * @param IDBConnection $db
203
-	 * @param Principal $principalBackend
204
-	 * @param IUserManager $userManager
205
-	 * @param IGroupManager $groupManager
206
-	 * @param ISecureRandom $random
207
-	 * @param ILogger $logger
208
-	 * @param IEventDispatcher $dispatcher
209
-	 * @param EventDispatcherInterface $legacyDispatcher
210
-	 * @param bool $legacyEndpoint
211
-	 */
212
-	public function __construct(IDBConnection $db,
213
-								Principal $principalBackend,
214
-								IUserManager $userManager,
215
-								IGroupManager $groupManager,
216
-								ISecureRandom $random,
217
-								ILogger $logger,
218
-								IEventDispatcher $dispatcher,
219
-								EventDispatcherInterface $legacyDispatcher,
220
-								bool $legacyEndpoint = false) {
221
-		$this->db = $db;
222
-		$this->principalBackend = $principalBackend;
223
-		$this->userManager = $userManager;
224
-		$this->calendarSharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'calendar');
225
-		$this->random = $random;
226
-		$this->logger = $logger;
227
-		$this->dispatcher = $dispatcher;
228
-		$this->legacyDispatcher = $legacyDispatcher;
229
-		$this->legacyEndpoint = $legacyEndpoint;
230
-	}
231
-
232
-	/**
233
-	 * Return the number of calendars for a principal
234
-	 *
235
-	 * By default this excludes the automatically generated birthday calendar
236
-	 *
237
-	 * @param $principalUri
238
-	 * @param bool $excludeBirthday
239
-	 * @return int
240
-	 */
241
-	public function getCalendarsForUserCount($principalUri, $excludeBirthday = true) {
242
-		$principalUri = $this->convertPrincipal($principalUri, true);
243
-		$query = $this->db->getQueryBuilder();
244
-		$query->select($query->func()->count('*'))
245
-			->from('calendars')
246
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
247
-
248
-		if ($excludeBirthday) {
249
-			$query->andWhere($query->expr()->neq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)));
250
-		}
251
-
252
-		$result = $query->execute();
253
-		$column = (int)$result->fetchColumn();
254
-		$result->closeCursor();
255
-		return $column;
256
-	}
257
-
258
-	/**
259
-	 * Returns a list of calendars for a principal.
260
-	 *
261
-	 * Every project is an array with the following keys:
262
-	 *  * id, a unique id that will be used by other functions to modify the
263
-	 *    calendar. This can be the same as the uri or a database key.
264
-	 *  * uri, which the basename of the uri with which the calendar is
265
-	 *    accessed.
266
-	 *  * principaluri. The owner of the calendar. Almost always the same as
267
-	 *    principalUri passed to this method.
268
-	 *
269
-	 * Furthermore it can contain webdav properties in clark notation. A very
270
-	 * common one is '{DAV:}displayname'.
271
-	 *
272
-	 * Many clients also require:
273
-	 * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
274
-	 * For this property, you can just return an instance of
275
-	 * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
276
-	 *
277
-	 * If you return {http://sabredav.org/ns}read-only and set the value to 1,
278
-	 * ACL will automatically be put in read-only mode.
279
-	 *
280
-	 * @param string $principalUri
281
-	 * @return array
282
-	 */
283
-	public function getCalendarsForUser($principalUri) {
284
-		$principalUriOriginal = $principalUri;
285
-		$principalUri = $this->convertPrincipal($principalUri, true);
286
-		$fields = array_values($this->propertyMap);
287
-		$fields[] = 'id';
288
-		$fields[] = 'uri';
289
-		$fields[] = 'synctoken';
290
-		$fields[] = 'components';
291
-		$fields[] = 'principaluri';
292
-		$fields[] = 'transparent';
293
-
294
-		// Making fields a comma-delimited list
295
-		$query = $this->db->getQueryBuilder();
296
-		$query->select($fields)->from('calendars')
297
-				->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
298
-				->orderBy('calendarorder', 'ASC');
299
-		$stmt = $query->execute();
300
-
301
-		$calendars = [];
302
-		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
303
-			$components = [];
304
-			if ($row['components']) {
305
-				$components = explode(',',$row['components']);
306
-			}
307
-
308
-			$calendar = [
309
-				'id' => $row['id'],
310
-				'uri' => $row['uri'],
311
-				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
312
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
313
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
314
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
315
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
316
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
317
-			];
318
-
319
-			foreach ($this->propertyMap as $xmlName => $dbName) {
320
-				$calendar[$xmlName] = $row[$dbName];
321
-			}
322
-
323
-			$this->addOwnerPrincipal($calendar);
324
-
325
-			if (!isset($calendars[$calendar['id']])) {
326
-				$calendars[$calendar['id']] = $calendar;
327
-			}
328
-		}
329
-
330
-		$stmt->closeCursor();
331
-
332
-		// query for shared calendars
333
-		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
334
-		$principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
335
-
336
-		$principals = array_map(function ($principal) {
337
-			return urldecode($principal);
338
-		}, $principals);
339
-		$principals[] = $principalUri;
340
-
341
-		$fields = array_values($this->propertyMap);
342
-		$fields[] = 'a.id';
343
-		$fields[] = 'a.uri';
344
-		$fields[] = 'a.synctoken';
345
-		$fields[] = 'a.components';
346
-		$fields[] = 'a.principaluri';
347
-		$fields[] = 'a.transparent';
348
-		$fields[] = 's.access';
349
-		$query = $this->db->getQueryBuilder();
350
-		$result = $query->select($fields)
351
-			->from('dav_shares', 's')
352
-			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
353
-			->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
354
-			->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
355
-			->setParameter('type', 'calendar')
356
-			->setParameter('principaluri', $principals, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
357
-			->execute();
358
-
359
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
360
-		while ($row = $result->fetch()) {
361
-			if ($row['principaluri'] === $principalUri) {
362
-				continue;
363
-			}
364
-
365
-			$readOnly = (int) $row['access'] === Backend::ACCESS_READ;
366
-			if (isset($calendars[$row['id']])) {
367
-				if ($readOnly) {
368
-					// New share can not have more permissions then the old one.
369
-					continue;
370
-				}
371
-				if (isset($calendars[$row['id']][$readOnlyPropertyName]) &&
372
-					$calendars[$row['id']][$readOnlyPropertyName] === 0) {
373
-					// Old share is already read-write, no more permissions can be gained
374
-					continue;
375
-				}
376
-			}
377
-
378
-			list(, $name) = Uri\split($row['principaluri']);
379
-			$uri = $row['uri'] . '_shared_by_' . $name;
380
-			$row['displayname'] = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
381
-			$components = [];
382
-			if ($row['components']) {
383
-				$components = explode(',',$row['components']);
384
-			}
385
-			$calendar = [
386
-				'id' => $row['id'],
387
-				'uri' => $uri,
388
-				'principaluri' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
389
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
390
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
391
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
392
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp('transparent'),
393
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
394
-				$readOnlyPropertyName => $readOnly,
395
-			];
396
-
397
-			foreach ($this->propertyMap as $xmlName => $dbName) {
398
-				$calendar[$xmlName] = $row[$dbName];
399
-			}
400
-
401
-			$this->addOwnerPrincipal($calendar);
402
-
403
-			$calendars[$calendar['id']] = $calendar;
404
-		}
405
-		$result->closeCursor();
406
-
407
-		return array_values($calendars);
408
-	}
409
-
410
-	/**
411
-	 * @param $principalUri
412
-	 * @return array
413
-	 */
414
-	public function getUsersOwnCalendars($principalUri) {
415
-		$principalUri = $this->convertPrincipal($principalUri, true);
416
-		$fields = array_values($this->propertyMap);
417
-		$fields[] = 'id';
418
-		$fields[] = 'uri';
419
-		$fields[] = 'synctoken';
420
-		$fields[] = 'components';
421
-		$fields[] = 'principaluri';
422
-		$fields[] = 'transparent';
423
-		// Making fields a comma-delimited list
424
-		$query = $this->db->getQueryBuilder();
425
-		$query->select($fields)->from('calendars')
426
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
427
-			->orderBy('calendarorder', 'ASC');
428
-		$stmt = $query->execute();
429
-		$calendars = [];
430
-		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
431
-			$components = [];
432
-			if ($row['components']) {
433
-				$components = explode(',',$row['components']);
434
-			}
435
-			$calendar = [
436
-				'id' => $row['id'],
437
-				'uri' => $row['uri'],
438
-				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
439
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
440
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
441
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
442
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
443
-			];
444
-			foreach ($this->propertyMap as $xmlName => $dbName) {
445
-				$calendar[$xmlName] = $row[$dbName];
446
-			}
447
-
448
-			$this->addOwnerPrincipal($calendar);
449
-
450
-			if (!isset($calendars[$calendar['id']])) {
451
-				$calendars[$calendar['id']] = $calendar;
452
-			}
453
-		}
454
-		$stmt->closeCursor();
455
-		return array_values($calendars);
456
-	}
457
-
458
-
459
-	/**
460
-	 * @param $uid
461
-	 * @return string
462
-	 */
463
-	private function getUserDisplayName($uid) {
464
-		if (!isset($this->userDisplayNames[$uid])) {
465
-			$user = $this->userManager->get($uid);
466
-
467
-			if ($user instanceof IUser) {
468
-				$this->userDisplayNames[$uid] = $user->getDisplayName();
469
-			} else {
470
-				$this->userDisplayNames[$uid] = $uid;
471
-			}
472
-		}
473
-
474
-		return $this->userDisplayNames[$uid];
475
-	}
476
-
477
-	/**
478
-	 * @return array
479
-	 */
480
-	public function getPublicCalendars() {
481
-		$fields = array_values($this->propertyMap);
482
-		$fields[] = 'a.id';
483
-		$fields[] = 'a.uri';
484
-		$fields[] = 'a.synctoken';
485
-		$fields[] = 'a.components';
486
-		$fields[] = 'a.principaluri';
487
-		$fields[] = 'a.transparent';
488
-		$fields[] = 's.access';
489
-		$fields[] = 's.publicuri';
490
-		$calendars = [];
491
-		$query = $this->db->getQueryBuilder();
492
-		$result = $query->select($fields)
493
-			->from('dav_shares', 's')
494
-			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
495
-			->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
496
-			->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
497
-			->execute();
498
-
499
-		while ($row = $result->fetch()) {
500
-			list(, $name) = Uri\split($row['principaluri']);
501
-			$row['displayname'] = $row['displayname'] . "($name)";
502
-			$components = [];
503
-			if ($row['components']) {
504
-				$components = explode(',',$row['components']);
505
-			}
506
-			$calendar = [
507
-				'id' => $row['id'],
508
-				'uri' => $row['publicuri'],
509
-				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
510
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
511
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
512
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
513
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
514
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
515
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
516
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
517
-			];
518
-
519
-			foreach ($this->propertyMap as $xmlName => $dbName) {
520
-				$calendar[$xmlName] = $row[$dbName];
521
-			}
522
-
523
-			$this->addOwnerPrincipal($calendar);
524
-
525
-			if (!isset($calendars[$calendar['id']])) {
526
-				$calendars[$calendar['id']] = $calendar;
527
-			}
528
-		}
529
-		$result->closeCursor();
530
-
531
-		return array_values($calendars);
532
-	}
533
-
534
-	/**
535
-	 * @param string $uri
536
-	 * @return array
537
-	 * @throws NotFound
538
-	 */
539
-	public function getPublicCalendar($uri) {
540
-		$fields = array_values($this->propertyMap);
541
-		$fields[] = 'a.id';
542
-		$fields[] = 'a.uri';
543
-		$fields[] = 'a.synctoken';
544
-		$fields[] = 'a.components';
545
-		$fields[] = 'a.principaluri';
546
-		$fields[] = 'a.transparent';
547
-		$fields[] = 's.access';
548
-		$fields[] = 's.publicuri';
549
-		$query = $this->db->getQueryBuilder();
550
-		$result = $query->select($fields)
551
-			->from('dav_shares', 's')
552
-			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
553
-			->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
554
-			->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
555
-			->andWhere($query->expr()->eq('s.publicuri', $query->createNamedParameter($uri)))
556
-			->execute();
557
-
558
-		$row = $result->fetch(\PDO::FETCH_ASSOC);
559
-
560
-		$result->closeCursor();
561
-
562
-		if ($row === false) {
563
-			throw new NotFound('Node with name \'' . $uri . '\' could not be found');
564
-		}
565
-
566
-		list(, $name) = Uri\split($row['principaluri']);
567
-		$row['displayname'] = $row['displayname'] . ' ' . "($name)";
568
-		$components = [];
569
-		if ($row['components']) {
570
-			$components = explode(',',$row['components']);
571
-		}
572
-		$calendar = [
573
-			'id' => $row['id'],
574
-			'uri' => $row['publicuri'],
575
-			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
576
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
577
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
578
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
579
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
580
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
581
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
582
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
583
-		];
584
-
585
-		foreach ($this->propertyMap as $xmlName => $dbName) {
586
-			$calendar[$xmlName] = $row[$dbName];
587
-		}
588
-
589
-		$this->addOwnerPrincipal($calendar);
590
-
591
-		return $calendar;
592
-	}
593
-
594
-	/**
595
-	 * @param string $principal
596
-	 * @param string $uri
597
-	 * @return array|null
598
-	 */
599
-	public function getCalendarByUri($principal, $uri) {
600
-		$fields = array_values($this->propertyMap);
601
-		$fields[] = 'id';
602
-		$fields[] = 'uri';
603
-		$fields[] = 'synctoken';
604
-		$fields[] = 'components';
605
-		$fields[] = 'principaluri';
606
-		$fields[] = 'transparent';
607
-
608
-		// Making fields a comma-delimited list
609
-		$query = $this->db->getQueryBuilder();
610
-		$query->select($fields)->from('calendars')
611
-			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
612
-			->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
613
-			->setMaxResults(1);
614
-		$stmt = $query->execute();
615
-
616
-		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
617
-		$stmt->closeCursor();
618
-		if ($row === false) {
619
-			return null;
620
-		}
621
-
622
-		$components = [];
623
-		if ($row['components']) {
624
-			$components = explode(',',$row['components']);
625
-		}
626
-
627
-		$calendar = [
628
-			'id' => $row['id'],
629
-			'uri' => $row['uri'],
630
-			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
631
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
632
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
633
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
634
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
635
-		];
636
-
637
-		foreach ($this->propertyMap as $xmlName => $dbName) {
638
-			$calendar[$xmlName] = $row[$dbName];
639
-		}
640
-
641
-		$this->addOwnerPrincipal($calendar);
642
-
643
-		return $calendar;
644
-	}
645
-
646
-	/**
647
-	 * @param $calendarId
648
-	 * @return array|null
649
-	 */
650
-	public function getCalendarById($calendarId) {
651
-		$fields = array_values($this->propertyMap);
652
-		$fields[] = 'id';
653
-		$fields[] = 'uri';
654
-		$fields[] = 'synctoken';
655
-		$fields[] = 'components';
656
-		$fields[] = 'principaluri';
657
-		$fields[] = 'transparent';
658
-
659
-		// Making fields a comma-delimited list
660
-		$query = $this->db->getQueryBuilder();
661
-		$query->select($fields)->from('calendars')
662
-			->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)))
663
-			->setMaxResults(1);
664
-		$stmt = $query->execute();
665
-
666
-		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
667
-		$stmt->closeCursor();
668
-		if ($row === false) {
669
-			return null;
670
-		}
671
-
672
-		$components = [];
673
-		if ($row['components']) {
674
-			$components = explode(',',$row['components']);
675
-		}
676
-
677
-		$calendar = [
678
-			'id' => $row['id'],
679
-			'uri' => $row['uri'],
680
-			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
681
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
682
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
683
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
684
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
685
-		];
686
-
687
-		foreach ($this->propertyMap as $xmlName => $dbName) {
688
-			$calendar[$xmlName] = $row[$dbName];
689
-		}
690
-
691
-		$this->addOwnerPrincipal($calendar);
692
-
693
-		return $calendar;
694
-	}
695
-
696
-	/**
697
-	 * @param $subscriptionId
698
-	 */
699
-	public function getSubscriptionById($subscriptionId) {
700
-		$fields = array_values($this->subscriptionPropertyMap);
701
-		$fields[] = 'id';
702
-		$fields[] = 'uri';
703
-		$fields[] = 'source';
704
-		$fields[] = 'synctoken';
705
-		$fields[] = 'principaluri';
706
-		$fields[] = 'lastmodified';
707
-
708
-		$query = $this->db->getQueryBuilder();
709
-		$query->select($fields)
710
-			->from('calendarsubscriptions')
711
-			->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
712
-			->orderBy('calendarorder', 'asc');
713
-		$stmt = $query->execute();
714
-
715
-		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
716
-		$stmt->closeCursor();
717
-		if ($row === false) {
718
-			return null;
719
-		}
720
-
721
-		$subscription = [
722
-			'id' => $row['id'],
723
-			'uri' => $row['uri'],
724
-			'principaluri' => $row['principaluri'],
725
-			'source' => $row['source'],
726
-			'lastmodified' => $row['lastmodified'],
727
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
728
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
729
-		];
730
-
731
-		foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
732
-			if (!is_null($row[$dbName])) {
733
-				$subscription[$xmlName] = $row[$dbName];
734
-			}
735
-		}
736
-
737
-		return $subscription;
738
-	}
739
-
740
-	/**
741
-	 * Creates a new calendar for a principal.
742
-	 *
743
-	 * If the creation was a success, an id must be returned that can be used to reference
744
-	 * this calendar in other methods, such as updateCalendar.
745
-	 *
746
-	 * @param string $principalUri
747
-	 * @param string $calendarUri
748
-	 * @param array $properties
749
-	 * @return int
750
-	 */
751
-	public function createCalendar($principalUri, $calendarUri, array $properties) {
752
-		$values = [
753
-			'principaluri' => $this->convertPrincipal($principalUri, true),
754
-			'uri' => $calendarUri,
755
-			'synctoken' => 1,
756
-			'transparent' => 0,
757
-			'components' => 'VEVENT,VTODO',
758
-			'displayname' => $calendarUri
759
-		];
760
-
761
-		// Default value
762
-		$sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
763
-		if (isset($properties[$sccs])) {
764
-			if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) {
765
-				throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
766
-			}
767
-			$values['components'] = implode(',',$properties[$sccs]->getValue());
768
-		} elseif (isset($properties['components'])) {
769
-			// Allow to provide components internally without having
770
-			// to create a SupportedCalendarComponentSet object
771
-			$values['components'] = $properties['components'];
772
-		}
773
-
774
-		$transp = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
775
-		if (isset($properties[$transp])) {
776
-			$values['transparent'] = (int) ($properties[$transp]->getValue() === 'transparent');
777
-		}
778
-
779
-		foreach ($this->propertyMap as $xmlName => $dbName) {
780
-			if (isset($properties[$xmlName])) {
781
-				$values[$dbName] = $properties[$xmlName];
782
-			}
783
-		}
784
-
785
-		$query = $this->db->getQueryBuilder();
786
-		$query->insert('calendars');
787
-		foreach ($values as $column => $value) {
788
-			$query->setValue($column, $query->createNamedParameter($value));
789
-		}
790
-		$query->execute();
791
-		$calendarId = $query->getLastInsertId();
792
-
793
-		$calendarData = $this->getCalendarById($calendarId);
794
-		$this->dispatcher->dispatchTyped(new CalendarCreatedEvent((int)$calendarId, $calendarData));
795
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendar', new GenericEvent(
796
-			'\OCA\DAV\CalDAV\CalDavBackend::createCalendar',
797
-			[
798
-				'calendarId' => $calendarId,
799
-				'calendarData' => $calendarData,
800
-			]));
801
-
802
-		return $calendarId;
803
-	}
804
-
805
-	/**
806
-	 * Updates properties for a calendar.
807
-	 *
808
-	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
809
-	 * To do the actual updates, you must tell this object which properties
810
-	 * you're going to process with the handle() method.
811
-	 *
812
-	 * Calling the handle method is like telling the PropPatch object "I
813
-	 * promise I can handle updating this property".
814
-	 *
815
-	 * Read the PropPatch documentation for more info and examples.
816
-	 *
817
-	 * @param mixed $calendarId
818
-	 * @param PropPatch $propPatch
819
-	 * @return void
820
-	 */
821
-	public function updateCalendar($calendarId, PropPatch $propPatch) {
822
-		$supportedProperties = array_keys($this->propertyMap);
823
-		$supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
824
-
825
-		$propPatch->handle($supportedProperties, function ($mutations) use ($calendarId) {
826
-			$newValues = [];
827
-			foreach ($mutations as $propertyName => $propertyValue) {
828
-				switch ($propertyName) {
829
-					case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp':
830
-						$fieldName = 'transparent';
831
-						$newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent');
832
-						break;
833
-					default:
834
-						$fieldName = $this->propertyMap[$propertyName];
835
-						$newValues[$fieldName] = $propertyValue;
836
-						break;
837
-				}
838
-			}
839
-			$query = $this->db->getQueryBuilder();
840
-			$query->update('calendars');
841
-			foreach ($newValues as $fieldName => $value) {
842
-				$query->set($fieldName, $query->createNamedParameter($value));
843
-			}
844
-			$query->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
845
-			$query->execute();
846
-
847
-			$this->addChange($calendarId, "", 2);
848
-
849
-			$calendarData = $this->getCalendarById($calendarId);
850
-			$shares = $this->getShares($calendarId);
851
-			$this->dispatcher->dispatchTyped(new CalendarUpdatedEvent((int)$calendarId, $calendarData, $shares, $mutations));
852
-			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendar', new GenericEvent(
853
-				'\OCA\DAV\CalDAV\CalDavBackend::updateCalendar',
854
-				[
855
-					'calendarId' => $calendarId,
856
-					'calendarData' => $calendarData,
857
-					'shares' => $shares,
858
-					'propertyMutations' => $mutations,
859
-				]));
860
-
861
-			return true;
862
-		});
863
-	}
864
-
865
-	/**
866
-	 * Delete a calendar and all it's objects
867
-	 *
868
-	 * @param mixed $calendarId
869
-	 * @return void
870
-	 */
871
-	public function deleteCalendar($calendarId) {
872
-		$calendarData = $this->getCalendarById($calendarId);
873
-		$shares = $this->getShares($calendarId);
874
-
875
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar', new GenericEvent(
876
-			'\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar',
877
-			[
878
-				'calendarId' => $calendarId,
879
-				'calendarData' => $calendarData,
880
-				'shares' => $shares,
881
-			]));
882
-
883
-		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `calendartype` = ?');
884
-		$stmt->execute([$calendarId, self::CALENDAR_TYPE_CALENDAR]);
885
-
886
-		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendars` WHERE `id` = ?');
887
-		$stmt->execute([$calendarId]);
888
-
889
-		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarchanges` WHERE `calendarid` = ? AND `calendartype` = ?');
890
-		$stmt->execute([$calendarId, self::CALENDAR_TYPE_CALENDAR]);
891
-
892
-		$this->calendarSharingBackend->deleteAllShares($calendarId);
893
-
894
-		$query = $this->db->getQueryBuilder();
895
-		$query->delete($this->dbObjectPropertiesTable)
896
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
897
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)))
898
-			->execute();
899
-
900
-		if ($calendarData) {
901
-			$this->dispatcher->dispatchTyped(new CalendarDeletedEvent((int)$calendarId, $calendarData, $shares));
902
-		}
903
-	}
904
-
905
-	/**
906
-	 * Delete all of an user's shares
907
-	 *
908
-	 * @param string $principaluri
909
-	 * @return void
910
-	 */
911
-	public function deleteAllSharesByUser($principaluri) {
912
-		$this->calendarSharingBackend->deleteAllSharesByUser($principaluri);
913
-	}
914
-
915
-	/**
916
-	 * Returns all calendar objects within a calendar.
917
-	 *
918
-	 * Every item contains an array with the following keys:
919
-	 *   * calendardata - The iCalendar-compatible calendar data
920
-	 *   * uri - a unique key which will be used to construct the uri. This can
921
-	 *     be any arbitrary string, but making sure it ends with '.ics' is a
922
-	 *     good idea. This is only the basename, or filename, not the full
923
-	 *     path.
924
-	 *   * lastmodified - a timestamp of the last modification time
925
-	 *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
926
-	 *   '"abcdef"')
927
-	 *   * size - The size of the calendar objects, in bytes.
928
-	 *   * component - optional, a string containing the type of object, such
929
-	 *     as 'vevent' or 'vtodo'. If specified, this will be used to populate
930
-	 *     the Content-Type header.
931
-	 *
932
-	 * Note that the etag is optional, but it's highly encouraged to return for
933
-	 * speed reasons.
934
-	 *
935
-	 * The calendardata is also optional. If it's not returned
936
-	 * 'getCalendarObject' will be called later, which *is* expected to return
937
-	 * calendardata.
938
-	 *
939
-	 * If neither etag or size are specified, the calendardata will be
940
-	 * used/fetched to determine these numbers. If both are specified the
941
-	 * amount of times this is needed is reduced by a great degree.
942
-	 *
943
-	 * @param mixed $calendarId
944
-	 * @param int $calendarType
945
-	 * @return array
946
-	 */
947
-	public function getCalendarObjects($calendarId, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
948
-		$query = $this->db->getQueryBuilder();
949
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'componenttype', 'classification'])
950
-			->from('calendarobjects')
951
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
952
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
953
-		$stmt = $query->execute();
954
-
955
-		$result = [];
956
-		foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
957
-			$result[] = [
958
-				'id' => $row['id'],
959
-				'uri' => $row['uri'],
960
-				'lastmodified' => $row['lastmodified'],
961
-				'etag' => '"' . $row['etag'] . '"',
962
-				'calendarid' => $row['calendarid'],
963
-				'size' => (int)$row['size'],
964
-				'component' => strtolower($row['componenttype']),
965
-				'classification' => (int)$row['classification']
966
-			];
967
-		}
968
-
969
-		return $result;
970
-	}
971
-
972
-	/**
973
-	 * Returns information from a single calendar object, based on it's object
974
-	 * uri.
975
-	 *
976
-	 * The object uri is only the basename, or filename and not a full path.
977
-	 *
978
-	 * The returned array must have the same keys as getCalendarObjects. The
979
-	 * 'calendardata' object is required here though, while it's not required
980
-	 * for getCalendarObjects.
981
-	 *
982
-	 * This method must return null if the object did not exist.
983
-	 *
984
-	 * @param mixed $calendarId
985
-	 * @param string $objectUri
986
-	 * @param int $calendarType
987
-	 * @return array|null
988
-	 */
989
-	public function getCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
990
-		$query = $this->db->getQueryBuilder();
991
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
992
-			->from('calendarobjects')
993
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
994
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
995
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
996
-		$stmt = $query->execute();
997
-		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
998
-
999
-		if (!$row) {
1000
-			return null;
1001
-		}
1002
-
1003
-		return [
1004
-			'id' => $row['id'],
1005
-			'uri' => $row['uri'],
1006
-			'lastmodified' => $row['lastmodified'],
1007
-			'etag' => '"' . $row['etag'] . '"',
1008
-			'calendarid' => $row['calendarid'],
1009
-			'size' => (int)$row['size'],
1010
-			'calendardata' => $this->readBlob($row['calendardata']),
1011
-			'component' => strtolower($row['componenttype']),
1012
-			'classification' => (int)$row['classification']
1013
-		];
1014
-	}
1015
-
1016
-	/**
1017
-	 * Returns a list of calendar objects.
1018
-	 *
1019
-	 * This method should work identical to getCalendarObject, but instead
1020
-	 * return all the calendar objects in the list as an array.
1021
-	 *
1022
-	 * If the backend supports this, it may allow for some speed-ups.
1023
-	 *
1024
-	 * @param mixed $calendarId
1025
-	 * @param string[] $uris
1026
-	 * @param int $calendarType
1027
-	 * @return array
1028
-	 */
1029
-	public function getMultipleCalendarObjects($calendarId, array $uris, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
1030
-		if (empty($uris)) {
1031
-			return [];
1032
-		}
1033
-
1034
-		$chunks = array_chunk($uris, 100);
1035
-		$objects = [];
1036
-
1037
-		$query = $this->db->getQueryBuilder();
1038
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
1039
-			->from('calendarobjects')
1040
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1041
-			->andWhere($query->expr()->in('uri', $query->createParameter('uri')))
1042
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
1043
-
1044
-		foreach ($chunks as $uris) {
1045
-			$query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
1046
-			$result = $query->execute();
1047
-
1048
-			while ($row = $result->fetch()) {
1049
-				$objects[] = [
1050
-					'id' => $row['id'],
1051
-					'uri' => $row['uri'],
1052
-					'lastmodified' => $row['lastmodified'],
1053
-					'etag' => '"' . $row['etag'] . '"',
1054
-					'calendarid' => $row['calendarid'],
1055
-					'size' => (int)$row['size'],
1056
-					'calendardata' => $this->readBlob($row['calendardata']),
1057
-					'component' => strtolower($row['componenttype']),
1058
-					'classification' => (int)$row['classification']
1059
-				];
1060
-			}
1061
-			$result->closeCursor();
1062
-		}
1063
-
1064
-		return $objects;
1065
-	}
1066
-
1067
-	/**
1068
-	 * Creates a new calendar object.
1069
-	 *
1070
-	 * The object uri is only the basename, or filename and not a full path.
1071
-	 *
1072
-	 * It is possible return an etag from this function, which will be used in
1073
-	 * the response to this PUT request. Note that the ETag must be surrounded
1074
-	 * by double-quotes.
1075
-	 *
1076
-	 * However, you should only really return this ETag if you don't mangle the
1077
-	 * calendar-data. If the result of a subsequent GET to this object is not
1078
-	 * the exact same as this request body, you should omit the ETag.
1079
-	 *
1080
-	 * @param mixed $calendarId
1081
-	 * @param string $objectUri
1082
-	 * @param string $calendarData
1083
-	 * @param int $calendarType
1084
-	 * @return string
1085
-	 */
1086
-	public function createCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1087
-		$extraData = $this->getDenormalizedData($calendarData);
1088
-
1089
-		$q = $this->db->getQueryBuilder();
1090
-		$q->select($q->func()->count('*'))
1091
-			->from('calendarobjects')
1092
-			->where($q->expr()->eq('calendarid', $q->createNamedParameter($calendarId)))
1093
-			->andWhere($q->expr()->eq('uid', $q->createNamedParameter($extraData['uid'])))
1094
-			->andWhere($q->expr()->eq('calendartype', $q->createNamedParameter($calendarType)));
1095
-
1096
-		$result = $q->execute();
1097
-		$count = (int) $result->fetchColumn();
1098
-		$result->closeCursor();
1099
-
1100
-		if ($count !== 0) {
1101
-			throw new \Sabre\DAV\Exception\BadRequest('Calendar object with uid already exists in this calendar collection.');
1102
-		}
1103
-
1104
-		$query = $this->db->getQueryBuilder();
1105
-		$query->insert('calendarobjects')
1106
-			->values([
1107
-				'calendarid' => $query->createNamedParameter($calendarId),
1108
-				'uri' => $query->createNamedParameter($objectUri),
1109
-				'calendardata' => $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB),
1110
-				'lastmodified' => $query->createNamedParameter(time()),
1111
-				'etag' => $query->createNamedParameter($extraData['etag']),
1112
-				'size' => $query->createNamedParameter($extraData['size']),
1113
-				'componenttype' => $query->createNamedParameter($extraData['componentType']),
1114
-				'firstoccurence' => $query->createNamedParameter($extraData['firstOccurence']),
1115
-				'lastoccurence' => $query->createNamedParameter($extraData['lastOccurence']),
1116
-				'classification' => $query->createNamedParameter($extraData['classification']),
1117
-				'uid' => $query->createNamedParameter($extraData['uid']),
1118
-				'calendartype' => $query->createNamedParameter($calendarType),
1119
-			])
1120
-			->execute();
1121
-
1122
-		$this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType);
1123
-		$this->addChange($calendarId, $objectUri, 1, $calendarType);
1124
-
1125
-		$objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1126
-		if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1127
-			$calendarRow = $this->getCalendarById($calendarId);
1128
-			$shares = $this->getShares($calendarId);
1129
-
1130
-			$this->dispatcher->dispatchTyped(new CalendarObjectCreatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
1131
-			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', new GenericEvent(
1132
-				'\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject',
1133
-				[
1134
-					'calendarId' => $calendarId,
1135
-					'calendarData' => $calendarRow,
1136
-					'shares' => $shares,
1137
-					'objectData' => $objectRow,
1138
-				]
1139
-			));
1140
-		} else {
1141
-			$subscriptionRow = $this->getSubscriptionById($calendarId);
1142
-
1143
-			$this->dispatcher->dispatchTyped(new CachedCalendarObjectCreatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
1144
-			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject', new GenericEvent(
1145
-				'\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject',
1146
-				[
1147
-					'subscriptionId' => $calendarId,
1148
-					'calendarData' => $subscriptionRow,
1149
-					'shares' => [],
1150
-					'objectData' => $objectRow,
1151
-				]
1152
-			));
1153
-		}
1154
-
1155
-		return '"' . $extraData['etag'] . '"';
1156
-	}
1157
-
1158
-	/**
1159
-	 * Updates an existing calendarobject, based on it's uri.
1160
-	 *
1161
-	 * The object uri is only the basename, or filename and not a full path.
1162
-	 *
1163
-	 * It is possible return an etag from this function, which will be used in
1164
-	 * the response to this PUT request. Note that the ETag must be surrounded
1165
-	 * by double-quotes.
1166
-	 *
1167
-	 * However, you should only really return this ETag if you don't mangle the
1168
-	 * calendar-data. If the result of a subsequent GET to this object is not
1169
-	 * the exact same as this request body, you should omit the ETag.
1170
-	 *
1171
-	 * @param mixed $calendarId
1172
-	 * @param string $objectUri
1173
-	 * @param string $calendarData
1174
-	 * @param int $calendarType
1175
-	 * @return string
1176
-	 */
1177
-	public function updateCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1178
-		$extraData = $this->getDenormalizedData($calendarData);
1179
-		$query = $this->db->getQueryBuilder();
1180
-		$query->update('calendarobjects')
1181
-				->set('calendardata', $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB))
1182
-				->set('lastmodified', $query->createNamedParameter(time()))
1183
-				->set('etag', $query->createNamedParameter($extraData['etag']))
1184
-				->set('size', $query->createNamedParameter($extraData['size']))
1185
-				->set('componenttype', $query->createNamedParameter($extraData['componentType']))
1186
-				->set('firstoccurence', $query->createNamedParameter($extraData['firstOccurence']))
1187
-				->set('lastoccurence', $query->createNamedParameter($extraData['lastOccurence']))
1188
-				->set('classification', $query->createNamedParameter($extraData['classification']))
1189
-				->set('uid', $query->createNamedParameter($extraData['uid']))
1190
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1191
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1192
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)))
1193
-			->execute();
1194
-
1195
-		$this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType);
1196
-		$this->addChange($calendarId, $objectUri, 2, $calendarType);
1197
-
1198
-		$objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1199
-		if (is_array($objectRow)) {
1200
-			if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1201
-				$calendarRow = $this->getCalendarById($calendarId);
1202
-				$shares = $this->getShares($calendarId);
1203
-
1204
-				$this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
1205
-				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', new GenericEvent(
1206
-					'\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject',
1207
-					[
1208
-						'calendarId' => $calendarId,
1209
-						'calendarData' => $calendarRow,
1210
-						'shares' => $shares,
1211
-						'objectData' => $objectRow,
1212
-					]
1213
-				));
1214
-			} else {
1215
-				$subscriptionRow = $this->getSubscriptionById($calendarId);
1216
-
1217
-				$this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
1218
-				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject', new GenericEvent(
1219
-					'\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject',
1220
-					[
1221
-						'subscriptionId' => $calendarId,
1222
-						'calendarData' => $subscriptionRow,
1223
-						'shares' => [],
1224
-						'objectData' => $objectRow,
1225
-					]
1226
-				));
1227
-			}
1228
-		}
1229
-
1230
-		return '"' . $extraData['etag'] . '"';
1231
-	}
1232
-
1233
-	/**
1234
-	 * @param int $calendarObjectId
1235
-	 * @param int $classification
1236
-	 */
1237
-	public function setClassification($calendarObjectId, $classification) {
1238
-		if (!in_array($classification, [
1239
-			self::CLASSIFICATION_PUBLIC, self::CLASSIFICATION_PRIVATE, self::CLASSIFICATION_CONFIDENTIAL
1240
-		])) {
1241
-			throw new \InvalidArgumentException();
1242
-		}
1243
-		$query = $this->db->getQueryBuilder();
1244
-		$query->update('calendarobjects')
1245
-			->set('classification', $query->createNamedParameter($classification))
1246
-			->where($query->expr()->eq('id', $query->createNamedParameter($calendarObjectId)))
1247
-			->execute();
1248
-	}
1249
-
1250
-	/**
1251
-	 * Deletes an existing calendar object.
1252
-	 *
1253
-	 * The object uri is only the basename, or filename and not a full path.
1254
-	 *
1255
-	 * @param mixed $calendarId
1256
-	 * @param string $objectUri
1257
-	 * @param int $calendarType
1258
-	 * @return void
1259
-	 */
1260
-	public function deleteCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1261
-		$data = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1262
-		if (is_array($data)) {
1263
-			if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1264
-				$calendarRow = $this->getCalendarById($calendarId);
1265
-				$shares = $this->getShares($calendarId);
1266
-
1267
-				$this->dispatcher->dispatchTyped(new CalendarObjectDeletedEvent((int)$calendarId, $calendarRow, $shares, $data));
1268
-				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', new GenericEvent(
1269
-					'\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject',
1270
-					[
1271
-						'calendarId' => $calendarId,
1272
-						'calendarData' => $calendarRow,
1273
-						'shares' => $shares,
1274
-						'objectData' => $data,
1275
-					]
1276
-				));
1277
-			} else {
1278
-				$subscriptionRow = $this->getSubscriptionById($calendarId);
1279
-
1280
-				$this->dispatcher->dispatchTyped(new CachedCalendarObjectDeletedEvent((int)$calendarId, $subscriptionRow, [], $data));
1281
-				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject', new GenericEvent(
1282
-					'\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject',
1283
-					[
1284
-						'subscriptionId' => $calendarId,
1285
-						'calendarData' => $subscriptionRow,
1286
-						'shares' => [],
1287
-						'objectData' => $data,
1288
-					]
1289
-				));
1290
-			}
1291
-		}
1292
-
1293
-		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ? AND `calendartype` = ?');
1294
-		$stmt->execute([$calendarId, $objectUri, $calendarType]);
1295
-
1296
-		if (is_array($data)) {
1297
-			$this->purgeProperties($calendarId, $data['id'], $calendarType);
1298
-		}
1299
-
1300
-		$this->addChange($calendarId, $objectUri, 3, $calendarType);
1301
-	}
1302
-
1303
-	/**
1304
-	 * Performs a calendar-query on the contents of this calendar.
1305
-	 *
1306
-	 * The calendar-query is defined in RFC4791 : CalDAV. Using the
1307
-	 * calendar-query it is possible for a client to request a specific set of
1308
-	 * object, based on contents of iCalendar properties, date-ranges and
1309
-	 * iCalendar component types (VTODO, VEVENT).
1310
-	 *
1311
-	 * This method should just return a list of (relative) urls that match this
1312
-	 * query.
1313
-	 *
1314
-	 * The list of filters are specified as an array. The exact array is
1315
-	 * documented by Sabre\CalDAV\CalendarQueryParser.
1316
-	 *
1317
-	 * Note that it is extremely likely that getCalendarObject for every path
1318
-	 * returned from this method will be called almost immediately after. You
1319
-	 * may want to anticipate this to speed up these requests.
1320
-	 *
1321
-	 * This method provides a default implementation, which parses *all* the
1322
-	 * iCalendar objects in the specified calendar.
1323
-	 *
1324
-	 * This default may well be good enough for personal use, and calendars
1325
-	 * that aren't very large. But if you anticipate high usage, big calendars
1326
-	 * or high loads, you are strongly advised to optimize certain paths.
1327
-	 *
1328
-	 * The best way to do so is override this method and to optimize
1329
-	 * specifically for 'common filters'.
1330
-	 *
1331
-	 * Requests that are extremely common are:
1332
-	 *   * requests for just VEVENTS
1333
-	 *   * requests for just VTODO
1334
-	 *   * requests with a time-range-filter on either VEVENT or VTODO.
1335
-	 *
1336
-	 * ..and combinations of these requests. It may not be worth it to try to
1337
-	 * handle every possible situation and just rely on the (relatively
1338
-	 * easy to use) CalendarQueryValidator to handle the rest.
1339
-	 *
1340
-	 * Note that especially time-range-filters may be difficult to parse. A
1341
-	 * time-range filter specified on a VEVENT must for instance also handle
1342
-	 * recurrence rules correctly.
1343
-	 * A good example of how to interprete all these filters can also simply
1344
-	 * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
1345
-	 * as possible, so it gives you a good idea on what type of stuff you need
1346
-	 * to think of.
1347
-	 *
1348
-	 * @param mixed $calendarId
1349
-	 * @param array $filters
1350
-	 * @param int $calendarType
1351
-	 * @return array
1352
-	 */
1353
-	public function calendarQuery($calendarId, array $filters, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
1354
-		$componentType = null;
1355
-		$requirePostFilter = true;
1356
-		$timeRange = null;
1357
-
1358
-		// if no filters were specified, we don't need to filter after a query
1359
-		if (!$filters['prop-filters'] && !$filters['comp-filters']) {
1360
-			$requirePostFilter = false;
1361
-		}
1362
-
1363
-		// Figuring out if there's a component filter
1364
-		if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
1365
-			$componentType = $filters['comp-filters'][0]['name'];
1366
-
1367
-			// Checking if we need post-filters
1368
-			if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
1369
-				$requirePostFilter = false;
1370
-			}
1371
-			// There was a time-range filter
1372
-			if ($componentType === 'VEVENT' && isset($filters['comp-filters'][0]['time-range']) && is_array($filters['comp-filters'][0]['time-range'])) {
1373
-				$timeRange = $filters['comp-filters'][0]['time-range'];
1374
-
1375
-				// If start time OR the end time is not specified, we can do a
1376
-				// 100% accurate mysql query.
1377
-				if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
1378
-					$requirePostFilter = false;
1379
-				}
1380
-			}
1381
-		}
1382
-		$columns = ['uri'];
1383
-		if ($requirePostFilter) {
1384
-			$columns = ['uri', 'calendardata'];
1385
-		}
1386
-		$query = $this->db->getQueryBuilder();
1387
-		$query->select($columns)
1388
-			->from('calendarobjects')
1389
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1390
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
1391
-
1392
-		if ($componentType) {
1393
-			$query->andWhere($query->expr()->eq('componenttype', $query->createNamedParameter($componentType)));
1394
-		}
1395
-
1396
-		if ($timeRange && $timeRange['start']) {
1397
-			$query->andWhere($query->expr()->gt('lastoccurence', $query->createNamedParameter($timeRange['start']->getTimeStamp())));
1398
-		}
1399
-		if ($timeRange && $timeRange['end']) {
1400
-			$query->andWhere($query->expr()->lt('firstoccurence', $query->createNamedParameter($timeRange['end']->getTimeStamp())));
1401
-		}
1402
-
1403
-		$stmt = $query->execute();
1404
-
1405
-		$result = [];
1406
-		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1407
-			if ($requirePostFilter) {
1408
-				// validateFilterForObject will parse the calendar data
1409
-				// catch parsing errors
1410
-				try {
1411
-					$matches = $this->validateFilterForObject($row, $filters);
1412
-				} catch (ParseException $ex) {
1413
-					$this->logger->logException($ex, [
1414
-						'app' => 'dav',
1415
-						'message' => 'Caught parsing exception for calendar data. This usually indicates invalid calendar data. calendar-id:'.$calendarId.' uri:'.$row['uri']
1416
-					]);
1417
-					continue;
1418
-				} catch (InvalidDataException $ex) {
1419
-					$this->logger->logException($ex, [
1420
-						'app' => 'dav',
1421
-						'message' => 'Caught invalid data exception for calendar data. This usually indicates invalid calendar data. calendar-id:'.$calendarId.' uri:'.$row['uri']
1422
-					]);
1423
-					continue;
1424
-				}
1425
-
1426
-				if (!$matches) {
1427
-					continue;
1428
-				}
1429
-			}
1430
-			$result[] = $row['uri'];
1431
-		}
1432
-
1433
-		return $result;
1434
-	}
1435
-
1436
-	/**
1437
-	 * custom Nextcloud search extension for CalDAV
1438
-	 *
1439
-	 * TODO - this should optionally cover cached calendar objects as well
1440
-	 *
1441
-	 * @param string $principalUri
1442
-	 * @param array $filters
1443
-	 * @param integer|null $limit
1444
-	 * @param integer|null $offset
1445
-	 * @return array
1446
-	 */
1447
-	public function calendarSearch($principalUri, array $filters, $limit = null, $offset = null) {
1448
-		$calendars = $this->getCalendarsForUser($principalUri);
1449
-		$ownCalendars = [];
1450
-		$sharedCalendars = [];
1451
-
1452
-		$uriMapper = [];
1453
-
1454
-		foreach ($calendars as $calendar) {
1455
-			if ($calendar['{http://owncloud.org/ns}owner-principal'] === $principalUri) {
1456
-				$ownCalendars[] = $calendar['id'];
1457
-			} else {
1458
-				$sharedCalendars[] = $calendar['id'];
1459
-			}
1460
-			$uriMapper[$calendar['id']] = $calendar['uri'];
1461
-		}
1462
-		if (count($ownCalendars) === 0 && count($sharedCalendars) === 0) {
1463
-			return [];
1464
-		}
1465
-
1466
-		$query = $this->db->getQueryBuilder();
1467
-		// Calendar id expressions
1468
-		$calendarExpressions = [];
1469
-		foreach ($ownCalendars as $id) {
1470
-			$calendarExpressions[] = $query->expr()->andX(
1471
-				$query->expr()->eq('c.calendarid',
1472
-					$query->createNamedParameter($id)),
1473
-				$query->expr()->eq('c.calendartype',
1474
-						$query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1475
-		}
1476
-		foreach ($sharedCalendars as $id) {
1477
-			$calendarExpressions[] = $query->expr()->andX(
1478
-				$query->expr()->eq('c.calendarid',
1479
-					$query->createNamedParameter($id)),
1480
-				$query->expr()->eq('c.classification',
1481
-					$query->createNamedParameter(self::CLASSIFICATION_PUBLIC)),
1482
-				$query->expr()->eq('c.calendartype',
1483
-					$query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1484
-		}
1485
-
1486
-		if (count($calendarExpressions) === 1) {
1487
-			$calExpr = $calendarExpressions[0];
1488
-		} else {
1489
-			$calExpr = call_user_func_array([$query->expr(), 'orX'], $calendarExpressions);
1490
-		}
1491
-
1492
-		// Component expressions
1493
-		$compExpressions = [];
1494
-		foreach ($filters['comps'] as $comp) {
1495
-			$compExpressions[] = $query->expr()
1496
-				->eq('c.componenttype', $query->createNamedParameter($comp));
1497
-		}
1498
-
1499
-		if (count($compExpressions) === 1) {
1500
-			$compExpr = $compExpressions[0];
1501
-		} else {
1502
-			$compExpr = call_user_func_array([$query->expr(), 'orX'], $compExpressions);
1503
-		}
1504
-
1505
-		if (!isset($filters['props'])) {
1506
-			$filters['props'] = [];
1507
-		}
1508
-		if (!isset($filters['params'])) {
1509
-			$filters['params'] = [];
1510
-		}
1511
-
1512
-		$propParamExpressions = [];
1513
-		foreach ($filters['props'] as $prop) {
1514
-			$propParamExpressions[] = $query->expr()->andX(
1515
-				$query->expr()->eq('i.name', $query->createNamedParameter($prop)),
1516
-				$query->expr()->isNull('i.parameter')
1517
-			);
1518
-		}
1519
-		foreach ($filters['params'] as $param) {
1520
-			$propParamExpressions[] = $query->expr()->andX(
1521
-				$query->expr()->eq('i.name', $query->createNamedParameter($param['property'])),
1522
-				$query->expr()->eq('i.parameter', $query->createNamedParameter($param['parameter']))
1523
-			);
1524
-		}
1525
-
1526
-		if (count($propParamExpressions) === 1) {
1527
-			$propParamExpr = $propParamExpressions[0];
1528
-		} else {
1529
-			$propParamExpr = call_user_func_array([$query->expr(), 'orX'], $propParamExpressions);
1530
-		}
1531
-
1532
-		$query->select(['c.calendarid', 'c.uri'])
1533
-			->from($this->dbObjectPropertiesTable, 'i')
1534
-			->join('i', 'calendarobjects', 'c', $query->expr()->eq('i.objectid', 'c.id'))
1535
-			->where($calExpr)
1536
-			->andWhere($compExpr)
1537
-			->andWhere($propParamExpr)
1538
-			->andWhere($query->expr()->iLike('i.value',
1539
-				$query->createNamedParameter('%'.$this->db->escapeLikeParameter($filters['search-term']).'%')));
1540
-
1541
-		if ($offset) {
1542
-			$query->setFirstResult($offset);
1543
-		}
1544
-		if ($limit) {
1545
-			$query->setMaxResults($limit);
1546
-		}
1547
-
1548
-		$stmt = $query->execute();
1549
-
1550
-		$result = [];
1551
-		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1552
-			$path = $uriMapper[$row['calendarid']] . '/' . $row['uri'];
1553
-			if (!in_array($path, $result)) {
1554
-				$result[] = $path;
1555
-			}
1556
-		}
1557
-
1558
-		return $result;
1559
-	}
1560
-
1561
-	/**
1562
-	 * used for Nextcloud's calendar API
1563
-	 *
1564
-	 * @param array $calendarInfo
1565
-	 * @param string $pattern
1566
-	 * @param array $searchProperties
1567
-	 * @param array $options
1568
-	 * @param integer|null $limit
1569
-	 * @param integer|null $offset
1570
-	 *
1571
-	 * @return array
1572
-	 */
1573
-	public function search(array $calendarInfo, $pattern, array $searchProperties,
1574
-						   array $options, $limit, $offset) {
1575
-		$outerQuery = $this->db->getQueryBuilder();
1576
-		$innerQuery = $this->db->getQueryBuilder();
1577
-
1578
-		$innerQuery->selectDistinct('op.objectid')
1579
-			->from($this->dbObjectPropertiesTable, 'op')
1580
-			->andWhere($innerQuery->expr()->eq('op.calendarid',
1581
-				$outerQuery->createNamedParameter($calendarInfo['id'])))
1582
-			->andWhere($innerQuery->expr()->eq('op.calendartype',
1583
-				$outerQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1584
-
1585
-		// only return public items for shared calendars for now
1586
-		if ($calendarInfo['principaluri'] !== $calendarInfo['{http://owncloud.org/ns}owner-principal']) {
1587
-			$innerQuery->andWhere($innerQuery->expr()->eq('c.classification',
1588
-				$outerQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1589
-		}
1590
-
1591
-		$or = $innerQuery->expr()->orX();
1592
-		foreach ($searchProperties as $searchProperty) {
1593
-			$or->add($innerQuery->expr()->eq('op.name',
1594
-				$outerQuery->createNamedParameter($searchProperty)));
1595
-		}
1596
-		$innerQuery->andWhere($or);
1597
-
1598
-		if ($pattern !== '') {
1599
-			$innerQuery->andWhere($innerQuery->expr()->iLike('op.value',
1600
-				$outerQuery->createNamedParameter('%' .
1601
-					$this->db->escapeLikeParameter($pattern) . '%')));
1602
-		}
1603
-
1604
-		$outerQuery->select('c.id', 'c.calendardata', 'c.componenttype', 'c.uid', 'c.uri')
1605
-			->from('calendarobjects', 'c');
1606
-
1607
-		if (isset($options['timerange'])) {
1608
-			if (isset($options['timerange']['start']) && $options['timerange']['start'] instanceof DateTime) {
1609
-				$outerQuery->andWhere($outerQuery->expr()->gt('lastoccurence',
1610
-					$outerQuery->createNamedParameter($options['timerange']['start']->getTimeStamp())));
1611
-			}
1612
-			if (isset($options['timerange']['end']) && $options['timerange']['end'] instanceof DateTime) {
1613
-				$outerQuery->andWhere($outerQuery->expr()->lt('firstoccurence',
1614
-					$outerQuery->createNamedParameter($options['timerange']['end']->getTimeStamp())));
1615
-			}
1616
-		}
1617
-
1618
-		if (isset($options['types'])) {
1619
-			$or = $outerQuery->expr()->orX();
1620
-			foreach ($options['types'] as $type) {
1621
-				$or->add($outerQuery->expr()->eq('componenttype',
1622
-					$outerQuery->createNamedParameter($type)));
1623
-			}
1624
-			$outerQuery->andWhere($or);
1625
-		}
1626
-
1627
-		$outerQuery->andWhere($outerQuery->expr()->in('c.id',
1628
-			$outerQuery->createFunction($innerQuery->getSQL())));
1629
-
1630
-		if ($offset) {
1631
-			$outerQuery->setFirstResult($offset);
1632
-		}
1633
-		if ($limit) {
1634
-			$outerQuery->setMaxResults($limit);
1635
-		}
1636
-
1637
-		$result = $outerQuery->execute();
1638
-		$calendarObjects = $result->fetchAll();
1639
-
1640
-		return array_map(function ($o) {
1641
-			$calendarData = Reader::read($o['calendardata']);
1642
-			$comps = $calendarData->getComponents();
1643
-			$objects = [];
1644
-			$timezones = [];
1645
-			foreach ($comps as $comp) {
1646
-				if ($comp instanceof VTimeZone) {
1647
-					$timezones[] = $comp;
1648
-				} else {
1649
-					$objects[] = $comp;
1650
-				}
1651
-			}
1652
-
1653
-			return [
1654
-				'id' => $o['id'],
1655
-				'type' => $o['componenttype'],
1656
-				'uid' => $o['uid'],
1657
-				'uri' => $o['uri'],
1658
-				'objects' => array_map(function ($c) {
1659
-					return $this->transformSearchData($c);
1660
-				}, $objects),
1661
-				'timezones' => array_map(function ($c) {
1662
-					return $this->transformSearchData($c);
1663
-				}, $timezones),
1664
-			];
1665
-		}, $calendarObjects);
1666
-	}
1667
-
1668
-	/**
1669
-	 * @param Component $comp
1670
-	 * @return array
1671
-	 */
1672
-	private function transformSearchData(Component $comp) {
1673
-		$data = [];
1674
-		/** @var Component[] $subComponents */
1675
-		$subComponents = $comp->getComponents();
1676
-		/** @var Property[] $properties */
1677
-		$properties = array_filter($comp->children(), function ($c) {
1678
-			return $c instanceof Property;
1679
-		});
1680
-		$validationRules = $comp->getValidationRules();
1681
-
1682
-		foreach ($subComponents as $subComponent) {
1683
-			$name = $subComponent->name;
1684
-			if (!isset($data[$name])) {
1685
-				$data[$name] = [];
1686
-			}
1687
-			$data[$name][] = $this->transformSearchData($subComponent);
1688
-		}
1689
-
1690
-		foreach ($properties as $property) {
1691
-			$name = $property->name;
1692
-			if (!isset($validationRules[$name])) {
1693
-				$validationRules[$name] = '*';
1694
-			}
1695
-
1696
-			$rule = $validationRules[$property->name];
1697
-			if ($rule === '+' || $rule === '*') { // multiple
1698
-				if (!isset($data[$name])) {
1699
-					$data[$name] = [];
1700
-				}
1701
-
1702
-				$data[$name][] = $this->transformSearchProperty($property);
1703
-			} else { // once
1704
-				$data[$name] = $this->transformSearchProperty($property);
1705
-			}
1706
-		}
1707
-
1708
-		return $data;
1709
-	}
1710
-
1711
-	/**
1712
-	 * @param Property $prop
1713
-	 * @return array
1714
-	 */
1715
-	private function transformSearchProperty(Property $prop) {
1716
-		// No need to check Date, as it extends DateTime
1717
-		if ($prop instanceof Property\ICalendar\DateTime) {
1718
-			$value = $prop->getDateTime();
1719
-		} else {
1720
-			$value = $prop->getValue();
1721
-		}
1722
-
1723
-		return [
1724
-			$value,
1725
-			$prop->parameters()
1726
-		];
1727
-	}
1728
-
1729
-	/**
1730
-	 * @param string $principalUri
1731
-	 * @param string $pattern
1732
-	 * @param array $componentTypes
1733
-	 * @param array $searchProperties
1734
-	 * @param array $searchParameters
1735
-	 * @param array $options
1736
-	 * @return array
1737
-	 */
1738
-	public function searchPrincipalUri(string $principalUri,
1739
-									   string $pattern,
1740
-									   array $componentTypes,
1741
-									   array $searchProperties,
1742
-									   array $searchParameters,
1743
-									   array $options = []): array {
1744
-		$escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false;
1745
-
1746
-		$calendarObjectIdQuery = $this->db->getQueryBuilder();
1747
-		$calendarOr = $calendarObjectIdQuery->expr()->orX();
1748
-		$searchOr = $calendarObjectIdQuery->expr()->orX();
1749
-
1750
-		// Fetch calendars and subscription
1751
-		$calendars = $this->getCalendarsForUser($principalUri);
1752
-		$subscriptions = $this->getSubscriptionsForUser($principalUri);
1753
-		foreach ($calendars as $calendar) {
1754
-			$calendarAnd = $calendarObjectIdQuery->expr()->andX();
1755
-			$calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$calendar['id'])));
1756
-			$calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1757
-
1758
-			// If it's shared, limit search to public events
1759
-			if ($calendar['principaluri'] !== $calendar['{http://owncloud.org/ns}owner-principal']) {
1760
-				$calendarAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1761
-			}
1762
-
1763
-			$calendarOr->add($calendarAnd);
1764
-		}
1765
-		foreach ($subscriptions as $subscription) {
1766
-			$subscriptionAnd = $calendarObjectIdQuery->expr()->andX();
1767
-			$subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$subscription['id'])));
1768
-			$subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
1769
-
1770
-			// If it's shared, limit search to public events
1771
-			if ($subscription['principaluri'] !== $subscription['{http://owncloud.org/ns}owner-principal']) {
1772
-				$subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1773
-			}
1774
-
1775
-			$calendarOr->add($subscriptionAnd);
1776
-		}
1777
-
1778
-		foreach ($searchProperties as $property) {
1779
-			$propertyAnd = $calendarObjectIdQuery->expr()->andX();
1780
-			$propertyAnd->add($calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)));
1781
-			$propertyAnd->add($calendarObjectIdQuery->expr()->isNull('cob.parameter'));
1782
-
1783
-			$searchOr->add($propertyAnd);
1784
-		}
1785
-		foreach ($searchParameters as $property => $parameter) {
1786
-			$parameterAnd = $calendarObjectIdQuery->expr()->andX();
1787
-			$parameterAnd->add($calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)));
1788
-			$parameterAnd->add($calendarObjectIdQuery->expr()->eq('cob.parameter', $calendarObjectIdQuery->createNamedParameter($parameter, IQueryBuilder::PARAM_STR_ARRAY)));
1789
-
1790
-			$searchOr->add($parameterAnd);
1791
-		}
1792
-
1793
-		if ($calendarOr->count() === 0) {
1794
-			return [];
1795
-		}
1796
-		if ($searchOr->count() === 0) {
1797
-			return [];
1798
-		}
1799
-
1800
-		$calendarObjectIdQuery->selectDistinct('cob.objectid')
1801
-			->from($this->dbObjectPropertiesTable, 'cob')
1802
-			->leftJoin('cob', 'calendarobjects', 'co', $calendarObjectIdQuery->expr()->eq('co.id', 'cob.objectid'))
1803
-			->andWhere($calendarObjectIdQuery->expr()->in('co.componenttype', $calendarObjectIdQuery->createNamedParameter($componentTypes, IQueryBuilder::PARAM_STR_ARRAY)))
1804
-			->andWhere($calendarOr)
1805
-			->andWhere($searchOr);
1806
-
1807
-		if ('' !== $pattern) {
1808
-			if (!$escapePattern) {
1809
-				$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter($pattern)));
1810
-			} else {
1811
-				$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
1812
-			}
1813
-		}
1814
-
1815
-		if (isset($options['limit'])) {
1816
-			$calendarObjectIdQuery->setMaxResults($options['limit']);
1817
-		}
1818
-		if (isset($options['offset'])) {
1819
-			$calendarObjectIdQuery->setFirstResult($options['offset']);
1820
-		}
1821
-
1822
-		$result = $calendarObjectIdQuery->execute();
1823
-		$matches = $result->fetchAll();
1824
-		$result->closeCursor();
1825
-		$matches = array_map(static function (array $match):int {
1826
-			return (int) $match['objectid'];
1827
-		}, $matches);
1828
-
1829
-		$query = $this->db->getQueryBuilder();
1830
-		$query->select('calendardata', 'uri', 'calendarid', 'calendartype')
1831
-			->from('calendarobjects')
1832
-			->where($query->expr()->in('id', $query->createNamedParameter($matches, IQueryBuilder::PARAM_INT_ARRAY)));
1833
-
1834
-		$result = $query->execute();
1835
-		$calendarObjects = $result->fetchAll();
1836
-		$result->closeCursor();
1837
-
1838
-		return array_map(function (array $array): array {
1839
-			$array['calendarid'] = (int)$array['calendarid'];
1840
-			$array['calendartype'] = (int)$array['calendartype'];
1841
-			$array['calendardata'] = $this->readBlob($array['calendardata']);
1842
-
1843
-			return $array;
1844
-		}, $calendarObjects);
1845
-	}
1846
-
1847
-	/**
1848
-	 * Searches through all of a users calendars and calendar objects to find
1849
-	 * an object with a specific UID.
1850
-	 *
1851
-	 * This method should return the path to this object, relative to the
1852
-	 * calendar home, so this path usually only contains two parts:
1853
-	 *
1854
-	 * calendarpath/objectpath.ics
1855
-	 *
1856
-	 * If the uid is not found, return null.
1857
-	 *
1858
-	 * This method should only consider * objects that the principal owns, so
1859
-	 * any calendars owned by other principals that also appear in this
1860
-	 * collection should be ignored.
1861
-	 *
1862
-	 * @param string $principalUri
1863
-	 * @param string $uid
1864
-	 * @return string|null
1865
-	 */
1866
-	public function getCalendarObjectByUID($principalUri, $uid) {
1867
-		$query = $this->db->getQueryBuilder();
1868
-		$query->selectAlias('c.uri', 'calendaruri')->selectAlias('co.uri', 'objecturi')
1869
-			->from('calendarobjects', 'co')
1870
-			->leftJoin('co', 'calendars', 'c', $query->expr()->eq('co.calendarid', 'c.id'))
1871
-			->where($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri)))
1872
-			->andWhere($query->expr()->eq('co.uid', $query->createNamedParameter($uid)));
1873
-
1874
-		$stmt = $query->execute();
1875
-
1876
-		if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1877
-			return $row['calendaruri'] . '/' . $row['objecturi'];
1878
-		}
1879
-
1880
-		return null;
1881
-	}
1882
-
1883
-	/**
1884
-	 * The getChanges method returns all the changes that have happened, since
1885
-	 * the specified syncToken in the specified calendar.
1886
-	 *
1887
-	 * This function should return an array, such as the following:
1888
-	 *
1889
-	 * [
1890
-	 *   'syncToken' => 'The current synctoken',
1891
-	 *   'added'   => [
1892
-	 *      'new.txt',
1893
-	 *   ],
1894
-	 *   'modified'   => [
1895
-	 *      'modified.txt',
1896
-	 *   ],
1897
-	 *   'deleted' => [
1898
-	 *      'foo.php.bak',
1899
-	 *      'old.txt'
1900
-	 *   ]
1901
-	 * );
1902
-	 *
1903
-	 * The returned syncToken property should reflect the *current* syncToken
1904
-	 * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
1905
-	 * property This is * needed here too, to ensure the operation is atomic.
1906
-	 *
1907
-	 * If the $syncToken argument is specified as null, this is an initial
1908
-	 * sync, and all members should be reported.
1909
-	 *
1910
-	 * The modified property is an array of nodenames that have changed since
1911
-	 * the last token.
1912
-	 *
1913
-	 * The deleted property is an array with nodenames, that have been deleted
1914
-	 * from collection.
1915
-	 *
1916
-	 * The $syncLevel argument is basically the 'depth' of the report. If it's
1917
-	 * 1, you only have to report changes that happened only directly in
1918
-	 * immediate descendants. If it's 2, it should also include changes from
1919
-	 * the nodes below the child collections. (grandchildren)
1920
-	 *
1921
-	 * The $limit argument allows a client to specify how many results should
1922
-	 * be returned at most. If the limit is not specified, it should be treated
1923
-	 * as infinite.
1924
-	 *
1925
-	 * If the limit (infinite or not) is higher than you're willing to return,
1926
-	 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
1927
-	 *
1928
-	 * If the syncToken is expired (due to data cleanup) or unknown, you must
1929
-	 * return null.
1930
-	 *
1931
-	 * The limit is 'suggestive'. You are free to ignore it.
1932
-	 *
1933
-	 * @param string $calendarId
1934
-	 * @param string $syncToken
1935
-	 * @param int $syncLevel
1936
-	 * @param int $limit
1937
-	 * @param int $calendarType
1938
-	 * @return array
1939
-	 */
1940
-	public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1941
-		// Current synctoken
1942
-		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*calendars` WHERE `id` = ?');
1943
-		$stmt->execute([ $calendarId ]);
1944
-		$currentToken = $stmt->fetchColumn(0);
1945
-
1946
-		if (is_null($currentToken)) {
1947
-			return null;
1948
-		}
1949
-
1950
-		$result = [
1951
-			'syncToken' => $currentToken,
1952
-			'added' => [],
1953
-			'modified' => [],
1954
-			'deleted' => [],
1955
-		];
1956
-
1957
-		if ($syncToken) {
1958
-			$query = "SELECT `uri`, `operation` FROM `*PREFIX*calendarchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `calendarid` = ? AND `calendartype` = ? ORDER BY `synctoken`";
1959
-			if ($limit > 0) {
1960
-				$query .= " LIMIT " . (int)$limit;
1961
-			}
1962
-
1963
-			// Fetching all changes
1964
-			$stmt = $this->db->prepare($query);
1965
-			$stmt->execute([$syncToken, $currentToken, $calendarId, $calendarType]);
1966
-
1967
-			$changes = [];
1968
-
1969
-			// This loop ensures that any duplicates are overwritten, only the
1970
-			// last change on a node is relevant.
1971
-			while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1972
-				$changes[$row['uri']] = $row['operation'];
1973
-			}
1974
-
1975
-			foreach ($changes as $uri => $operation) {
1976
-				switch ($operation) {
1977
-					case 1:
1978
-						$result['added'][] = $uri;
1979
-						break;
1980
-					case 2:
1981
-						$result['modified'][] = $uri;
1982
-						break;
1983
-					case 3:
1984
-						$result['deleted'][] = $uri;
1985
-						break;
1986
-				}
1987
-			}
1988
-		} else {
1989
-			// No synctoken supplied, this is the initial sync.
1990
-			$query = "SELECT `uri` FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `calendartype` = ?";
1991
-			$stmt = $this->db->prepare($query);
1992
-			$stmt->execute([$calendarId, $calendarType]);
1993
-
1994
-			$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
1995
-		}
1996
-		return $result;
1997
-	}
1998
-
1999
-	/**
2000
-	 * Returns a list of subscriptions for a principal.
2001
-	 *
2002
-	 * Every subscription is an array with the following keys:
2003
-	 *  * id, a unique id that will be used by other functions to modify the
2004
-	 *    subscription. This can be the same as the uri or a database key.
2005
-	 *  * uri. This is just the 'base uri' or 'filename' of the subscription.
2006
-	 *  * principaluri. The owner of the subscription. Almost always the same as
2007
-	 *    principalUri passed to this method.
2008
-	 *
2009
-	 * Furthermore, all the subscription info must be returned too:
2010
-	 *
2011
-	 * 1. {DAV:}displayname
2012
-	 * 2. {http://apple.com/ns/ical/}refreshrate
2013
-	 * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
2014
-	 *    should not be stripped).
2015
-	 * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
2016
-	 *    should not be stripped).
2017
-	 * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
2018
-	 *    attachments should not be stripped).
2019
-	 * 6. {http://calendarserver.org/ns/}source (Must be a
2020
-	 *     Sabre\DAV\Property\Href).
2021
-	 * 7. {http://apple.com/ns/ical/}calendar-color
2022
-	 * 8. {http://apple.com/ns/ical/}calendar-order
2023
-	 * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
2024
-	 *    (should just be an instance of
2025
-	 *    Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
2026
-	 *    default components).
2027
-	 *
2028
-	 * @param string $principalUri
2029
-	 * @return array
2030
-	 */
2031
-	public function getSubscriptionsForUser($principalUri) {
2032
-		$fields = array_values($this->subscriptionPropertyMap);
2033
-		$fields[] = 'id';
2034
-		$fields[] = 'uri';
2035
-		$fields[] = 'source';
2036
-		$fields[] = 'principaluri';
2037
-		$fields[] = 'lastmodified';
2038
-		$fields[] = 'synctoken';
2039
-
2040
-		$query = $this->db->getQueryBuilder();
2041
-		$query->select($fields)
2042
-			->from('calendarsubscriptions')
2043
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2044
-			->orderBy('calendarorder', 'asc');
2045
-		$stmt = $query->execute();
2046
-
2047
-		$subscriptions = [];
2048
-		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
2049
-			$subscription = [
2050
-				'id' => $row['id'],
2051
-				'uri' => $row['uri'],
2052
-				'principaluri' => $row['principaluri'],
2053
-				'source' => $row['source'],
2054
-				'lastmodified' => $row['lastmodified'],
2055
-
2056
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
2057
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
2058
-			];
2059
-
2060
-			foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
2061
-				if (!is_null($row[$dbName])) {
2062
-					$subscription[$xmlName] = $row[$dbName];
2063
-				}
2064
-			}
2065
-
2066
-			$subscriptions[] = $subscription;
2067
-		}
2068
-
2069
-		return $subscriptions;
2070
-	}
2071
-
2072
-	/**
2073
-	 * Creates a new subscription for a principal.
2074
-	 *
2075
-	 * If the creation was a success, an id must be returned that can be used to reference
2076
-	 * this subscription in other methods, such as updateSubscription.
2077
-	 *
2078
-	 * @param string $principalUri
2079
-	 * @param string $uri
2080
-	 * @param array $properties
2081
-	 * @return mixed
2082
-	 */
2083
-	public function createSubscription($principalUri, $uri, array $properties) {
2084
-		if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
2085
-			throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
2086
-		}
2087
-
2088
-		$values = [
2089
-			'principaluri' => $principalUri,
2090
-			'uri' => $uri,
2091
-			'source' => $properties['{http://calendarserver.org/ns/}source']->getHref(),
2092
-			'lastmodified' => time(),
2093
-		];
2094
-
2095
-		$propertiesBoolean = ['striptodos', 'stripalarms', 'stripattachments'];
2096
-
2097
-		foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
2098
-			if (array_key_exists($xmlName, $properties)) {
2099
-				$values[$dbName] = $properties[$xmlName];
2100
-				if (in_array($dbName, $propertiesBoolean)) {
2101
-					$values[$dbName] = true;
2102
-				}
2103
-			}
2104
-		}
2105
-
2106
-		$valuesToInsert = [];
2107
-
2108
-		$query = $this->db->getQueryBuilder();
2109
-
2110
-		foreach (array_keys($values) as $name) {
2111
-			$valuesToInsert[$name] = $query->createNamedParameter($values[$name]);
2112
-		}
2113
-
2114
-		$query->insert('calendarsubscriptions')
2115
-			->values($valuesToInsert)
2116
-			->execute();
2117
-
2118
-		$subscriptionId = $this->db->lastInsertId('*PREFIX*calendarsubscriptions');
2119
-
2120
-		$subscriptionRow = $this->getSubscriptionById($subscriptionId);
2121
-		$this->dispatcher->dispatchTyped(new SubscriptionCreatedEvent((int)$subscriptionId, $subscriptionRow));
2122
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createSubscription', new GenericEvent(
2123
-			'\OCA\DAV\CalDAV\CalDavBackend::createSubscription',
2124
-			[
2125
-				'subscriptionId' => $subscriptionId,
2126
-				'subscriptionData' => $subscriptionRow,
2127
-			]));
2128
-
2129
-		return $subscriptionId;
2130
-	}
2131
-
2132
-	/**
2133
-	 * Updates a subscription
2134
-	 *
2135
-	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
2136
-	 * To do the actual updates, you must tell this object which properties
2137
-	 * you're going to process with the handle() method.
2138
-	 *
2139
-	 * Calling the handle method is like telling the PropPatch object "I
2140
-	 * promise I can handle updating this property".
2141
-	 *
2142
-	 * Read the PropPatch documentation for more info and examples.
2143
-	 *
2144
-	 * @param mixed $subscriptionId
2145
-	 * @param PropPatch $propPatch
2146
-	 * @return void
2147
-	 */
2148
-	public function updateSubscription($subscriptionId, PropPatch $propPatch) {
2149
-		$supportedProperties = array_keys($this->subscriptionPropertyMap);
2150
-		$supportedProperties[] = '{http://calendarserver.org/ns/}source';
2151
-
2152
-		$propPatch->handle($supportedProperties, function ($mutations) use ($subscriptionId) {
2153
-			$newValues = [];
2154
-
2155
-			foreach ($mutations as $propertyName => $propertyValue) {
2156
-				if ($propertyName === '{http://calendarserver.org/ns/}source') {
2157
-					$newValues['source'] = $propertyValue->getHref();
2158
-				} else {
2159
-					$fieldName = $this->subscriptionPropertyMap[$propertyName];
2160
-					$newValues[$fieldName] = $propertyValue;
2161
-				}
2162
-			}
2163
-
2164
-			$query = $this->db->getQueryBuilder();
2165
-			$query->update('calendarsubscriptions')
2166
-				->set('lastmodified', $query->createNamedParameter(time()));
2167
-			foreach ($newValues as $fieldName => $value) {
2168
-				$query->set($fieldName, $query->createNamedParameter($value));
2169
-			}
2170
-			$query->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
2171
-				->execute();
2172
-
2173
-			$subscriptionRow = $this->getSubscriptionById($subscriptionId);
2174
-			$this->dispatcher->dispatchTyped(new SubscriptionUpdatedEvent((int)$subscriptionId, $subscriptionRow, [], $mutations));
2175
-			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateSubscription', new GenericEvent(
2176
-				'\OCA\DAV\CalDAV\CalDavBackend::updateSubscription',
2177
-				[
2178
-					'subscriptionId' => $subscriptionId,
2179
-					'subscriptionData' => $subscriptionRow,
2180
-					'propertyMutations' => $mutations,
2181
-				]));
2182
-
2183
-			return true;
2184
-		});
2185
-	}
2186
-
2187
-	/**
2188
-	 * Deletes a subscription.
2189
-	 *
2190
-	 * @param mixed $subscriptionId
2191
-	 * @return void
2192
-	 */
2193
-	public function deleteSubscription($subscriptionId) {
2194
-		$subscriptionRow = $this->getSubscriptionById($subscriptionId);
2195
-
2196
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription', new GenericEvent(
2197
-			'\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription',
2198
-			[
2199
-				'subscriptionId' => $subscriptionId,
2200
-				'subscriptionData' => $this->getSubscriptionById($subscriptionId),
2201
-			]));
2202
-
2203
-		$query = $this->db->getQueryBuilder();
2204
-		$query->delete('calendarsubscriptions')
2205
-			->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
2206
-			->execute();
2207
-
2208
-		$query = $this->db->getQueryBuilder();
2209
-		$query->delete('calendarobjects')
2210
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2211
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2212
-			->execute();
2213
-
2214
-		$query->delete('calendarchanges')
2215
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2216
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2217
-			->execute();
2218
-
2219
-		$query->delete($this->dbObjectPropertiesTable)
2220
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2221
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2222
-			->execute();
2223
-
2224
-		if ($subscriptionRow) {
2225
-			$this->dispatcher->dispatchTyped(new SubscriptionDeletedEvent((int)$subscriptionId, $subscriptionRow, []));
2226
-		}
2227
-	}
2228
-
2229
-	/**
2230
-	 * Returns a single scheduling object for the inbox collection.
2231
-	 *
2232
-	 * The returned array should contain the following elements:
2233
-	 *   * uri - A unique basename for the object. This will be used to
2234
-	 *           construct a full uri.
2235
-	 *   * calendardata - The iCalendar object
2236
-	 *   * lastmodified - The last modification date. Can be an int for a unix
2237
-	 *                    timestamp, or a PHP DateTime object.
2238
-	 *   * etag - A unique token that must change if the object changed.
2239
-	 *   * size - The size of the object, in bytes.
2240
-	 *
2241
-	 * @param string $principalUri
2242
-	 * @param string $objectUri
2243
-	 * @return array
2244
-	 */
2245
-	public function getSchedulingObject($principalUri, $objectUri) {
2246
-		$query = $this->db->getQueryBuilder();
2247
-		$stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
2248
-			->from('schedulingobjects')
2249
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2250
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
2251
-			->execute();
2252
-
2253
-		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
2254
-
2255
-		if (!$row) {
2256
-			return null;
2257
-		}
2258
-
2259
-		return [
2260
-			'uri' => $row['uri'],
2261
-			'calendardata' => $row['calendardata'],
2262
-			'lastmodified' => $row['lastmodified'],
2263
-			'etag' => '"' . $row['etag'] . '"',
2264
-			'size' => (int)$row['size'],
2265
-		];
2266
-	}
2267
-
2268
-	/**
2269
-	 * Returns all scheduling objects for the inbox collection.
2270
-	 *
2271
-	 * These objects should be returned as an array. Every item in the array
2272
-	 * should follow the same structure as returned from getSchedulingObject.
2273
-	 *
2274
-	 * The main difference is that 'calendardata' is optional.
2275
-	 *
2276
-	 * @param string $principalUri
2277
-	 * @return array
2278
-	 */
2279
-	public function getSchedulingObjects($principalUri) {
2280
-		$query = $this->db->getQueryBuilder();
2281
-		$stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
2282
-				->from('schedulingobjects')
2283
-				->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2284
-				->execute();
2285
-
2286
-		$result = [];
2287
-		foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
2288
-			$result[] = [
2289
-				'calendardata' => $row['calendardata'],
2290
-				'uri' => $row['uri'],
2291
-				'lastmodified' => $row['lastmodified'],
2292
-				'etag' => '"' . $row['etag'] . '"',
2293
-				'size' => (int)$row['size'],
2294
-			];
2295
-		}
2296
-
2297
-		return $result;
2298
-	}
2299
-
2300
-	/**
2301
-	 * Deletes a scheduling object from the inbox collection.
2302
-	 *
2303
-	 * @param string $principalUri
2304
-	 * @param string $objectUri
2305
-	 * @return void
2306
-	 */
2307
-	public function deleteSchedulingObject($principalUri, $objectUri) {
2308
-		$query = $this->db->getQueryBuilder();
2309
-		$query->delete('schedulingobjects')
2310
-				->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2311
-				->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
2312
-				->execute();
2313
-	}
2314
-
2315
-	/**
2316
-	 * Creates a new scheduling object. This should land in a users' inbox.
2317
-	 *
2318
-	 * @param string $principalUri
2319
-	 * @param string $objectUri
2320
-	 * @param string $objectData
2321
-	 * @return void
2322
-	 */
2323
-	public function createSchedulingObject($principalUri, $objectUri, $objectData) {
2324
-		$query = $this->db->getQueryBuilder();
2325
-		$query->insert('schedulingobjects')
2326
-			->values([
2327
-				'principaluri' => $query->createNamedParameter($principalUri),
2328
-				'calendardata' => $query->createNamedParameter($objectData, IQueryBuilder::PARAM_LOB),
2329
-				'uri' => $query->createNamedParameter($objectUri),
2330
-				'lastmodified' => $query->createNamedParameter(time()),
2331
-				'etag' => $query->createNamedParameter(md5($objectData)),
2332
-				'size' => $query->createNamedParameter(strlen($objectData))
2333
-			])
2334
-			->execute();
2335
-	}
2336
-
2337
-	/**
2338
-	 * Adds a change record to the calendarchanges table.
2339
-	 *
2340
-	 * @param mixed $calendarId
2341
-	 * @param string $objectUri
2342
-	 * @param int $operation 1 = add, 2 = modify, 3 = delete.
2343
-	 * @param int $calendarType
2344
-	 * @return void
2345
-	 */
2346
-	protected function addChange($calendarId, $objectUri, $operation, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2347
-		$table = $calendarType === self::CALENDAR_TYPE_CALENDAR ? 'calendars': 'calendarsubscriptions';
2348
-
2349
-		$query = $this->db->getQueryBuilder();
2350
-		$query->select('synctoken')
2351
-			->from($table)
2352
-			->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
2353
-		$result = $query->execute();
2354
-		$syncToken = (int)$result->fetchColumn();
2355
-		$result->closeCursor();
2356
-
2357
-		$query = $this->db->getQueryBuilder();
2358
-		$query->insert('calendarchanges')
2359
-			->values([
2360
-				'uri' => $query->createNamedParameter($objectUri),
2361
-				'synctoken' => $query->createNamedParameter($syncToken),
2362
-				'calendarid' => $query->createNamedParameter($calendarId),
2363
-				'operation' => $query->createNamedParameter($operation),
2364
-				'calendartype' => $query->createNamedParameter($calendarType),
2365
-			])
2366
-			->execute();
2367
-
2368
-		$stmt = $this->db->prepare("UPDATE `*PREFIX*$table` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?");
2369
-		$stmt->execute([
2370
-			$calendarId
2371
-		]);
2372
-	}
2373
-
2374
-	/**
2375
-	 * Parses some information from calendar objects, used for optimized
2376
-	 * calendar-queries.
2377
-	 *
2378
-	 * Returns an array with the following keys:
2379
-	 *   * etag - An md5 checksum of the object without the quotes.
2380
-	 *   * size - Size of the object in bytes
2381
-	 *   * componentType - VEVENT, VTODO or VJOURNAL
2382
-	 *   * firstOccurence
2383
-	 *   * lastOccurence
2384
-	 *   * uid - value of the UID property
2385
-	 *
2386
-	 * @param string $calendarData
2387
-	 * @return array
2388
-	 */
2389
-	public function getDenormalizedData($calendarData) {
2390
-		$vObject = Reader::read($calendarData);
2391
-		$vEvents = [];
2392
-		$componentType = null;
2393
-		$component = null;
2394
-		$firstOccurrence = null;
2395
-		$lastOccurrence = null;
2396
-		$uid = null;
2397
-		$classification = self::CLASSIFICATION_PUBLIC;
2398
-		$hasDTSTART = false;
2399
-		foreach ($vObject->getComponents() as $component) {
2400
-			if ($component->name !== 'VTIMEZONE') {
2401
-				// Finding all VEVENTs, and track them
2402
-				if ($component->name === 'VEVENT') {
2403
-					array_push($vEvents, $component);
2404
-					if ($component->DTSTART) {
2405
-						$hasDTSTART = true;
2406
-					}
2407
-				}
2408
-				// Track first component type and uid
2409
-				if ($uid === null) {
2410
-					$componentType = $component->name;
2411
-					$uid = (string)$component->UID;
2412
-				}
2413
-			}
2414
-		}
2415
-		if (!$componentType) {
2416
-			throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
2417
-		}
2418
-
2419
-		if ($hasDTSTART) {
2420
-			$component = $vEvents[0];
2421
-
2422
-			// Finding the last occurrence is a bit harder
2423
-			if (!isset($component->RRULE) && count($vEvents) === 1) {
2424
-				$firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp();
2425
-				if (isset($component->DTEND)) {
2426
-					$lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp();
2427
-				} elseif (isset($component->DURATION)) {
2428
-					$endDate = clone $component->DTSTART->getDateTime();
2429
-					$endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
2430
-					$lastOccurrence = $endDate->getTimeStamp();
2431
-				} elseif (!$component->DTSTART->hasTime()) {
2432
-					$endDate = clone $component->DTSTART->getDateTime();
2433
-					$endDate->modify('+1 day');
2434
-					$lastOccurrence = $endDate->getTimeStamp();
2435
-				} else {
2436
-					$lastOccurrence = $firstOccurrence;
2437
-				}
2438
-			} else {
2439
-				$it = new EventIterator($vEvents);
2440
-				$maxDate = new DateTime(self::MAX_DATE);
2441
-				$firstOccurrence = $it->getDtStart()->getTimestamp();
2442
-				if ($it->isInfinite()) {
2443
-					$lastOccurrence = $maxDate->getTimestamp();
2444
-				} else {
2445
-					$end = $it->getDtEnd();
2446
-					while ($it->valid() && $end < $maxDate) {
2447
-						$end = $it->getDtEnd();
2448
-						$it->next();
2449
-					}
2450
-					$lastOccurrence = $end->getTimestamp();
2451
-				}
2452
-			}
2453
-		}
2454
-
2455
-		if ($component->CLASS) {
2456
-			$classification = CalDavBackend::CLASSIFICATION_PRIVATE;
2457
-			switch ($component->CLASS->getValue()) {
2458
-				case 'PUBLIC':
2459
-					$classification = CalDavBackend::CLASSIFICATION_PUBLIC;
2460
-					break;
2461
-				case 'CONFIDENTIAL':
2462
-					$classification = CalDavBackend::CLASSIFICATION_CONFIDENTIAL;
2463
-					break;
2464
-			}
2465
-		}
2466
-		return [
2467
-			'etag' => md5($calendarData),
2468
-			'size' => strlen($calendarData),
2469
-			'componentType' => $componentType,
2470
-			'firstOccurence' => is_null($firstOccurrence) ? null : max(0, $firstOccurrence),
2471
-			'lastOccurence' => $lastOccurrence,
2472
-			'uid' => $uid,
2473
-			'classification' => $classification
2474
-		];
2475
-	}
2476
-
2477
-	/**
2478
-	 * @param $cardData
2479
-	 * @return bool|string
2480
-	 */
2481
-	private function readBlob($cardData) {
2482
-		if (is_resource($cardData)) {
2483
-			return stream_get_contents($cardData);
2484
-		}
2485
-
2486
-		return $cardData;
2487
-	}
2488
-
2489
-	/**
2490
-	 * @param IShareable $shareable
2491
-	 * @param array $add
2492
-	 * @param array $remove
2493
-	 */
2494
-	public function updateShares($shareable, $add, $remove) {
2495
-		$calendarId = $shareable->getResourceId();
2496
-		$calendarRow = $this->getCalendarById($calendarId);
2497
-		$oldShares = $this->getShares($calendarId);
2498
-
2499
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateShares', new GenericEvent(
2500
-			'\OCA\DAV\CalDAV\CalDavBackend::updateShares',
2501
-			[
2502
-				'calendarId' => $calendarId,
2503
-				'calendarData' => $calendarRow,
2504
-				'shares' => $oldShares,
2505
-				'add' => $add,
2506
-				'remove' => $remove,
2507
-			]));
2508
-		$this->calendarSharingBackend->updateShares($shareable, $add, $remove);
2509
-
2510
-		$this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent((int)$calendarId, $calendarRow, $oldShares, $add, $remove));
2511
-	}
2512
-
2513
-	/**
2514
-	 * @param int $resourceId
2515
-	 * @param int $calendarType
2516
-	 * @return array
2517
-	 */
2518
-	public function getShares($resourceId, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2519
-		return $this->calendarSharingBackend->getShares($resourceId);
2520
-	}
2521
-
2522
-	/**
2523
-	 * @param boolean $value
2524
-	 * @param \OCA\DAV\CalDAV\Calendar $calendar
2525
-	 * @return string|null
2526
-	 */
2527
-	public function setPublishStatus($value, $calendar) {
2528
-		$calendarId = $calendar->getResourceId();
2529
-		$calendarData = $this->getCalendarById($calendarId);
2530
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::publishCalendar', new GenericEvent(
2531
-			'\OCA\DAV\CalDAV\CalDavBackend::updateShares',
2532
-			[
2533
-				'calendarId' => $calendarId,
2534
-				'calendarData' => $calendarData,
2535
-				'public' => $value,
2536
-			]));
2537
-
2538
-		$query = $this->db->getQueryBuilder();
2539
-		if ($value) {
2540
-			$publicUri = $this->random->generate(16, ISecureRandom::CHAR_HUMAN_READABLE);
2541
-			$query->insert('dav_shares')
2542
-				->values([
2543
-					'principaluri' => $query->createNamedParameter($calendar->getPrincipalURI()),
2544
-					'type' => $query->createNamedParameter('calendar'),
2545
-					'access' => $query->createNamedParameter(self::ACCESS_PUBLIC),
2546
-					'resourceid' => $query->createNamedParameter($calendar->getResourceId()),
2547
-					'publicuri' => $query->createNamedParameter($publicUri)
2548
-				]);
2549
-			$query->execute();
2550
-
2551
-			$this->dispatcher->dispatchTyped(new CalendarPublishedEvent((int)$calendarId, $calendarData, $publicUri));
2552
-			return $publicUri;
2553
-		}
2554
-		$query->delete('dav_shares')
2555
-			->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
2556
-			->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)));
2557
-		$query->execute();
2558
-
2559
-		$this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent((int)$calendarId, $calendarData));
2560
-		return null;
2561
-	}
2562
-
2563
-	/**
2564
-	 * @param \OCA\DAV\CalDAV\Calendar $calendar
2565
-	 * @return mixed
2566
-	 */
2567
-	public function getPublishStatus($calendar) {
2568
-		$query = $this->db->getQueryBuilder();
2569
-		$result = $query->select('publicuri')
2570
-			->from('dav_shares')
2571
-			->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
2572
-			->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
2573
-			->execute();
2574
-
2575
-		$row = $result->fetch();
2576
-		$result->closeCursor();
2577
-		return $row ? reset($row) : false;
2578
-	}
2579
-
2580
-	/**
2581
-	 * @param int $resourceId
2582
-	 * @param array $acl
2583
-	 * @return array
2584
-	 */
2585
-	public function applyShareAcl($resourceId, $acl) {
2586
-		return $this->calendarSharingBackend->applyShareAcl($resourceId, $acl);
2587
-	}
2588
-
2589
-
2590
-
2591
-	/**
2592
-	 * update properties table
2593
-	 *
2594
-	 * @param int $calendarId
2595
-	 * @param string $objectUri
2596
-	 * @param string $calendarData
2597
-	 * @param int $calendarType
2598
-	 */
2599
-	public function updateProperties($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2600
-		$objectId = $this->getCalendarObjectId($calendarId, $objectUri, $calendarType);
2601
-
2602
-		try {
2603
-			$vCalendar = $this->readCalendarData($calendarData);
2604
-		} catch (\Exception $ex) {
2605
-			return;
2606
-		}
2607
-
2608
-		$this->purgeProperties($calendarId, $objectId);
2609
-
2610
-		$query = $this->db->getQueryBuilder();
2611
-		$query->insert($this->dbObjectPropertiesTable)
2612
-			->values(
2613
-				[
2614
-					'calendarid' => $query->createNamedParameter($calendarId),
2615
-					'calendartype' => $query->createNamedParameter($calendarType),
2616
-					'objectid' => $query->createNamedParameter($objectId),
2617
-					'name' => $query->createParameter('name'),
2618
-					'parameter' => $query->createParameter('parameter'),
2619
-					'value' => $query->createParameter('value'),
2620
-				]
2621
-			);
2622
-
2623
-		$indexComponents = ['VEVENT', 'VJOURNAL', 'VTODO'];
2624
-		foreach ($vCalendar->getComponents() as $component) {
2625
-			if (!in_array($component->name, $indexComponents)) {
2626
-				continue;
2627
-			}
2628
-
2629
-			foreach ($component->children() as $property) {
2630
-				if (in_array($property->name, self::$indexProperties)) {
2631
-					$value = $property->getValue();
2632
-					// is this a shitty db?
2633
-					if (!$this->db->supports4ByteText()) {
2634
-						$value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
2635
-					}
2636
-					$value = mb_substr($value, 0, 254);
2637
-
2638
-					$query->setParameter('name', $property->name);
2639
-					$query->setParameter('parameter', null);
2640
-					$query->setParameter('value', $value);
2641
-					$query->execute();
2642
-				}
2643
-
2644
-				if (array_key_exists($property->name, self::$indexParameters)) {
2645
-					$parameters = $property->parameters();
2646
-					$indexedParametersForProperty = self::$indexParameters[$property->name];
2647
-
2648
-					foreach ($parameters as $key => $value) {
2649
-						if (in_array($key, $indexedParametersForProperty)) {
2650
-							// is this a shitty db?
2651
-							if ($this->db->supports4ByteText()) {
2652
-								$value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
2653
-							}
2654
-
2655
-							$query->setParameter('name', $property->name);
2656
-							$query->setParameter('parameter', mb_substr($key, 0, 254));
2657
-							$query->setParameter('value', mb_substr($value, 0, 254));
2658
-							$query->execute();
2659
-						}
2660
-					}
2661
-				}
2662
-			}
2663
-		}
2664
-	}
2665
-
2666
-	/**
2667
-	 * deletes all birthday calendars
2668
-	 */
2669
-	public function deleteAllBirthdayCalendars() {
2670
-		$query = $this->db->getQueryBuilder();
2671
-		$result = $query->select(['id'])->from('calendars')
2672
-			->where($query->expr()->eq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)))
2673
-			->execute();
2674
-
2675
-		$ids = $result->fetchAll();
2676
-		foreach ($ids as $id) {
2677
-			$this->deleteCalendar($id['id']);
2678
-		}
2679
-	}
2680
-
2681
-	/**
2682
-	 * @param $subscriptionId
2683
-	 */
2684
-	public function purgeAllCachedEventsForSubscription($subscriptionId) {
2685
-		$query = $this->db->getQueryBuilder();
2686
-		$query->select('uri')
2687
-			->from('calendarobjects')
2688
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2689
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
2690
-		$stmt = $query->execute();
2691
-
2692
-		$uris = [];
2693
-		foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
2694
-			$uris[] = $row['uri'];
2695
-		}
2696
-		$stmt->closeCursor();
2697
-
2698
-		$query = $this->db->getQueryBuilder();
2699
-		$query->delete('calendarobjects')
2700
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2701
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2702
-			->execute();
2703
-
2704
-		$query->delete('calendarchanges')
2705
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2706
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2707
-			->execute();
2708
-
2709
-		$query->delete($this->dbObjectPropertiesTable)
2710
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2711
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2712
-			->execute();
2713
-
2714
-		foreach ($uris as $uri) {
2715
-			$this->addChange($subscriptionId, $uri, 3, self::CALENDAR_TYPE_SUBSCRIPTION);
2716
-		}
2717
-	}
2718
-
2719
-	/**
2720
-	 * Move a calendar from one user to another
2721
-	 *
2722
-	 * @param string $uriName
2723
-	 * @param string $uriOrigin
2724
-	 * @param string $uriDestination
2725
-	 */
2726
-	public function moveCalendar($uriName, $uriOrigin, $uriDestination) {
2727
-		$query = $this->db->getQueryBuilder();
2728
-		$query->update('calendars')
2729
-			->set('principaluri', $query->createNamedParameter($uriDestination))
2730
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($uriOrigin)))
2731
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($uriName)))
2732
-			->execute();
2733
-	}
2734
-
2735
-	/**
2736
-	 * read VCalendar data into a VCalendar object
2737
-	 *
2738
-	 * @param string $objectData
2739
-	 * @return VCalendar
2740
-	 */
2741
-	protected function readCalendarData($objectData) {
2742
-		return Reader::read($objectData);
2743
-	}
2744
-
2745
-	/**
2746
-	 * delete all properties from a given calendar object
2747
-	 *
2748
-	 * @param int $calendarId
2749
-	 * @param int $objectId
2750
-	 */
2751
-	protected function purgeProperties($calendarId, $objectId) {
2752
-		$query = $this->db->getQueryBuilder();
2753
-		$query->delete($this->dbObjectPropertiesTable)
2754
-			->where($query->expr()->eq('objectid', $query->createNamedParameter($objectId)))
2755
-			->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
2756
-		$query->execute();
2757
-	}
2758
-
2759
-	/**
2760
-	 * get ID from a given calendar object
2761
-	 *
2762
-	 * @param int $calendarId
2763
-	 * @param string $uri
2764
-	 * @param int $calendarType
2765
-	 * @return int
2766
-	 */
2767
-	protected function getCalendarObjectId($calendarId, $uri, $calendarType):int {
2768
-		$query = $this->db->getQueryBuilder();
2769
-		$query->select('id')
2770
-			->from('calendarobjects')
2771
-			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
2772
-			->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
2773
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
2774
-
2775
-		$result = $query->execute();
2776
-		$objectIds = $result->fetch();
2777
-		$result->closeCursor();
2778
-
2779
-		if (!isset($objectIds['id'])) {
2780
-			throw new \InvalidArgumentException('Calendarobject does not exists: ' . $uri);
2781
-		}
2782
-
2783
-		return (int)$objectIds['id'];
2784
-	}
2785
-
2786
-	/**
2787
-	 * return legacy endpoint principal name to new principal name
2788
-	 *
2789
-	 * @param $principalUri
2790
-	 * @param $toV2
2791
-	 * @return string
2792
-	 */
2793
-	private function convertPrincipal($principalUri, $toV2) {
2794
-		if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
2795
-			list(, $name) = Uri\split($principalUri);
2796
-			if ($toV2 === true) {
2797
-				return "principals/users/$name";
2798
-			}
2799
-			return "principals/$name";
2800
-		}
2801
-		return $principalUri;
2802
-	}
2803
-
2804
-	/**
2805
-	 * adds information about an owner to the calendar data
2806
-	 *
2807
-	 * @param $calendarInfo
2808
-	 */
2809
-	private function addOwnerPrincipal(&$calendarInfo) {
2810
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
2811
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
2812
-		if (isset($calendarInfo[$ownerPrincipalKey])) {
2813
-			$uri = $calendarInfo[$ownerPrincipalKey];
2814
-		} else {
2815
-			$uri = $calendarInfo['principaluri'];
2816
-		}
2817
-
2818
-		$principalInformation = $this->principalBackend->getPrincipalByPath($uri);
2819
-		if (isset($principalInformation['{DAV:}displayname'])) {
2820
-			$calendarInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
2821
-		}
2822
-	}
98
+    public const CALENDAR_TYPE_CALENDAR = 0;
99
+    public const CALENDAR_TYPE_SUBSCRIPTION = 1;
100
+
101
+    public const PERSONAL_CALENDAR_URI = 'personal';
102
+    public const PERSONAL_CALENDAR_NAME = 'Personal';
103
+
104
+    public const RESOURCE_BOOKING_CALENDAR_URI = 'calendar';
105
+    public const RESOURCE_BOOKING_CALENDAR_NAME = 'Calendar';
106
+
107
+    /**
108
+     * We need to specify a max date, because we need to stop *somewhere*
109
+     *
110
+     * On 32 bit system the maximum for a signed integer is 2147483647, so
111
+     * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
112
+     * in 2038-01-19 to avoid problems when the date is converted
113
+     * to a unix timestamp.
114
+     */
115
+    public const MAX_DATE = '2038-01-01';
116
+
117
+    public const ACCESS_PUBLIC = 4;
118
+    public const CLASSIFICATION_PUBLIC = 0;
119
+    public const CLASSIFICATION_PRIVATE = 1;
120
+    public const CLASSIFICATION_CONFIDENTIAL = 2;
121
+
122
+    /**
123
+     * List of CalDAV properties, and how they map to database field names
124
+     * Add your own properties by simply adding on to this array.
125
+     *
126
+     * Note that only string-based properties are supported here.
127
+     *
128
+     * @var array
129
+     */
130
+    public $propertyMap = [
131
+        '{DAV:}displayname' => 'displayname',
132
+        '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
133
+        '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone',
134
+        '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
135
+        '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
136
+    ];
137
+
138
+    /**
139
+     * List of subscription properties, and how they map to database field names.
140
+     *
141
+     * @var array
142
+     */
143
+    public $subscriptionPropertyMap = [
144
+        '{DAV:}displayname' => 'displayname',
145
+        '{http://apple.com/ns/ical/}refreshrate' => 'refreshrate',
146
+        '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
147
+        '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
148
+        '{http://calendarserver.org/ns/}subscribed-strip-todos' => 'striptodos',
149
+        '{http://calendarserver.org/ns/}subscribed-strip-alarms' => 'stripalarms',
150
+        '{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
151
+    ];
152
+
153
+    /** @var array properties to index */
154
+    public static $indexProperties = ['CATEGORIES', 'COMMENT', 'DESCRIPTION',
155
+        'LOCATION', 'RESOURCES', 'STATUS', 'SUMMARY', 'ATTENDEE', 'CONTACT',
156
+        'ORGANIZER'];
157
+
158
+    /** @var array parameters to index */
159
+    public static $indexParameters = [
160
+        'ATTENDEE' => ['CN'],
161
+        'ORGANIZER' => ['CN'],
162
+    ];
163
+
164
+    /**
165
+     * @var string[] Map of uid => display name
166
+     */
167
+    protected $userDisplayNames;
168
+
169
+    /** @var IDBConnection */
170
+    private $db;
171
+
172
+    /** @var Backend */
173
+    private $calendarSharingBackend;
174
+
175
+    /** @var Principal */
176
+    private $principalBackend;
177
+
178
+    /** @var IUserManager */
179
+    private $userManager;
180
+
181
+    /** @var ISecureRandom */
182
+    private $random;
183
+
184
+    /** @var ILogger */
185
+    private $logger;
186
+
187
+    /** @var IEventDispatcher */
188
+    private $dispatcher;
189
+
190
+    /** @var EventDispatcherInterface */
191
+    private $legacyDispatcher;
192
+
193
+    /** @var bool */
194
+    private $legacyEndpoint;
195
+
196
+    /** @var string */
197
+    private $dbObjectPropertiesTable = 'calendarobjects_props';
198
+
199
+    /**
200
+     * CalDavBackend constructor.
201
+     *
202
+     * @param IDBConnection $db
203
+     * @param Principal $principalBackend
204
+     * @param IUserManager $userManager
205
+     * @param IGroupManager $groupManager
206
+     * @param ISecureRandom $random
207
+     * @param ILogger $logger
208
+     * @param IEventDispatcher $dispatcher
209
+     * @param EventDispatcherInterface $legacyDispatcher
210
+     * @param bool $legacyEndpoint
211
+     */
212
+    public function __construct(IDBConnection $db,
213
+                                Principal $principalBackend,
214
+                                IUserManager $userManager,
215
+                                IGroupManager $groupManager,
216
+                                ISecureRandom $random,
217
+                                ILogger $logger,
218
+                                IEventDispatcher $dispatcher,
219
+                                EventDispatcherInterface $legacyDispatcher,
220
+                                bool $legacyEndpoint = false) {
221
+        $this->db = $db;
222
+        $this->principalBackend = $principalBackend;
223
+        $this->userManager = $userManager;
224
+        $this->calendarSharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'calendar');
225
+        $this->random = $random;
226
+        $this->logger = $logger;
227
+        $this->dispatcher = $dispatcher;
228
+        $this->legacyDispatcher = $legacyDispatcher;
229
+        $this->legacyEndpoint = $legacyEndpoint;
230
+    }
231
+
232
+    /**
233
+     * Return the number of calendars for a principal
234
+     *
235
+     * By default this excludes the automatically generated birthday calendar
236
+     *
237
+     * @param $principalUri
238
+     * @param bool $excludeBirthday
239
+     * @return int
240
+     */
241
+    public function getCalendarsForUserCount($principalUri, $excludeBirthday = true) {
242
+        $principalUri = $this->convertPrincipal($principalUri, true);
243
+        $query = $this->db->getQueryBuilder();
244
+        $query->select($query->func()->count('*'))
245
+            ->from('calendars')
246
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
247
+
248
+        if ($excludeBirthday) {
249
+            $query->andWhere($query->expr()->neq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)));
250
+        }
251
+
252
+        $result = $query->execute();
253
+        $column = (int)$result->fetchColumn();
254
+        $result->closeCursor();
255
+        return $column;
256
+    }
257
+
258
+    /**
259
+     * Returns a list of calendars for a principal.
260
+     *
261
+     * Every project is an array with the following keys:
262
+     *  * id, a unique id that will be used by other functions to modify the
263
+     *    calendar. This can be the same as the uri or a database key.
264
+     *  * uri, which the basename of the uri with which the calendar is
265
+     *    accessed.
266
+     *  * principaluri. The owner of the calendar. Almost always the same as
267
+     *    principalUri passed to this method.
268
+     *
269
+     * Furthermore it can contain webdav properties in clark notation. A very
270
+     * common one is '{DAV:}displayname'.
271
+     *
272
+     * Many clients also require:
273
+     * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
274
+     * For this property, you can just return an instance of
275
+     * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
276
+     *
277
+     * If you return {http://sabredav.org/ns}read-only and set the value to 1,
278
+     * ACL will automatically be put in read-only mode.
279
+     *
280
+     * @param string $principalUri
281
+     * @return array
282
+     */
283
+    public function getCalendarsForUser($principalUri) {
284
+        $principalUriOriginal = $principalUri;
285
+        $principalUri = $this->convertPrincipal($principalUri, true);
286
+        $fields = array_values($this->propertyMap);
287
+        $fields[] = 'id';
288
+        $fields[] = 'uri';
289
+        $fields[] = 'synctoken';
290
+        $fields[] = 'components';
291
+        $fields[] = 'principaluri';
292
+        $fields[] = 'transparent';
293
+
294
+        // Making fields a comma-delimited list
295
+        $query = $this->db->getQueryBuilder();
296
+        $query->select($fields)->from('calendars')
297
+                ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
298
+                ->orderBy('calendarorder', 'ASC');
299
+        $stmt = $query->execute();
300
+
301
+        $calendars = [];
302
+        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
303
+            $components = [];
304
+            if ($row['components']) {
305
+                $components = explode(',',$row['components']);
306
+            }
307
+
308
+            $calendar = [
309
+                'id' => $row['id'],
310
+                'uri' => $row['uri'],
311
+                'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
312
+                '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
313
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
314
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
315
+                '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
316
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
317
+            ];
318
+
319
+            foreach ($this->propertyMap as $xmlName => $dbName) {
320
+                $calendar[$xmlName] = $row[$dbName];
321
+            }
322
+
323
+            $this->addOwnerPrincipal($calendar);
324
+
325
+            if (!isset($calendars[$calendar['id']])) {
326
+                $calendars[$calendar['id']] = $calendar;
327
+            }
328
+        }
329
+
330
+        $stmt->closeCursor();
331
+
332
+        // query for shared calendars
333
+        $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
334
+        $principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
335
+
336
+        $principals = array_map(function ($principal) {
337
+            return urldecode($principal);
338
+        }, $principals);
339
+        $principals[] = $principalUri;
340
+
341
+        $fields = array_values($this->propertyMap);
342
+        $fields[] = 'a.id';
343
+        $fields[] = 'a.uri';
344
+        $fields[] = 'a.synctoken';
345
+        $fields[] = 'a.components';
346
+        $fields[] = 'a.principaluri';
347
+        $fields[] = 'a.transparent';
348
+        $fields[] = 's.access';
349
+        $query = $this->db->getQueryBuilder();
350
+        $result = $query->select($fields)
351
+            ->from('dav_shares', 's')
352
+            ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
353
+            ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
354
+            ->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
355
+            ->setParameter('type', 'calendar')
356
+            ->setParameter('principaluri', $principals, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
357
+            ->execute();
358
+
359
+        $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
360
+        while ($row = $result->fetch()) {
361
+            if ($row['principaluri'] === $principalUri) {
362
+                continue;
363
+            }
364
+
365
+            $readOnly = (int) $row['access'] === Backend::ACCESS_READ;
366
+            if (isset($calendars[$row['id']])) {
367
+                if ($readOnly) {
368
+                    // New share can not have more permissions then the old one.
369
+                    continue;
370
+                }
371
+                if (isset($calendars[$row['id']][$readOnlyPropertyName]) &&
372
+                    $calendars[$row['id']][$readOnlyPropertyName] === 0) {
373
+                    // Old share is already read-write, no more permissions can be gained
374
+                    continue;
375
+                }
376
+            }
377
+
378
+            list(, $name) = Uri\split($row['principaluri']);
379
+            $uri = $row['uri'] . '_shared_by_' . $name;
380
+            $row['displayname'] = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
381
+            $components = [];
382
+            if ($row['components']) {
383
+                $components = explode(',',$row['components']);
384
+            }
385
+            $calendar = [
386
+                'id' => $row['id'],
387
+                'uri' => $uri,
388
+                'principaluri' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
389
+                '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
390
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
391
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
392
+                '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp('transparent'),
393
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
394
+                $readOnlyPropertyName => $readOnly,
395
+            ];
396
+
397
+            foreach ($this->propertyMap as $xmlName => $dbName) {
398
+                $calendar[$xmlName] = $row[$dbName];
399
+            }
400
+
401
+            $this->addOwnerPrincipal($calendar);
402
+
403
+            $calendars[$calendar['id']] = $calendar;
404
+        }
405
+        $result->closeCursor();
406
+
407
+        return array_values($calendars);
408
+    }
409
+
410
+    /**
411
+     * @param $principalUri
412
+     * @return array
413
+     */
414
+    public function getUsersOwnCalendars($principalUri) {
415
+        $principalUri = $this->convertPrincipal($principalUri, true);
416
+        $fields = array_values($this->propertyMap);
417
+        $fields[] = 'id';
418
+        $fields[] = 'uri';
419
+        $fields[] = 'synctoken';
420
+        $fields[] = 'components';
421
+        $fields[] = 'principaluri';
422
+        $fields[] = 'transparent';
423
+        // Making fields a comma-delimited list
424
+        $query = $this->db->getQueryBuilder();
425
+        $query->select($fields)->from('calendars')
426
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
427
+            ->orderBy('calendarorder', 'ASC');
428
+        $stmt = $query->execute();
429
+        $calendars = [];
430
+        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
431
+            $components = [];
432
+            if ($row['components']) {
433
+                $components = explode(',',$row['components']);
434
+            }
435
+            $calendar = [
436
+                'id' => $row['id'],
437
+                'uri' => $row['uri'],
438
+                'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
439
+                '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
440
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
441
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
442
+                '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
443
+            ];
444
+            foreach ($this->propertyMap as $xmlName => $dbName) {
445
+                $calendar[$xmlName] = $row[$dbName];
446
+            }
447
+
448
+            $this->addOwnerPrincipal($calendar);
449
+
450
+            if (!isset($calendars[$calendar['id']])) {
451
+                $calendars[$calendar['id']] = $calendar;
452
+            }
453
+        }
454
+        $stmt->closeCursor();
455
+        return array_values($calendars);
456
+    }
457
+
458
+
459
+    /**
460
+     * @param $uid
461
+     * @return string
462
+     */
463
+    private function getUserDisplayName($uid) {
464
+        if (!isset($this->userDisplayNames[$uid])) {
465
+            $user = $this->userManager->get($uid);
466
+
467
+            if ($user instanceof IUser) {
468
+                $this->userDisplayNames[$uid] = $user->getDisplayName();
469
+            } else {
470
+                $this->userDisplayNames[$uid] = $uid;
471
+            }
472
+        }
473
+
474
+        return $this->userDisplayNames[$uid];
475
+    }
476
+
477
+    /**
478
+     * @return array
479
+     */
480
+    public function getPublicCalendars() {
481
+        $fields = array_values($this->propertyMap);
482
+        $fields[] = 'a.id';
483
+        $fields[] = 'a.uri';
484
+        $fields[] = 'a.synctoken';
485
+        $fields[] = 'a.components';
486
+        $fields[] = 'a.principaluri';
487
+        $fields[] = 'a.transparent';
488
+        $fields[] = 's.access';
489
+        $fields[] = 's.publicuri';
490
+        $calendars = [];
491
+        $query = $this->db->getQueryBuilder();
492
+        $result = $query->select($fields)
493
+            ->from('dav_shares', 's')
494
+            ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
495
+            ->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
496
+            ->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
497
+            ->execute();
498
+
499
+        while ($row = $result->fetch()) {
500
+            list(, $name) = Uri\split($row['principaluri']);
501
+            $row['displayname'] = $row['displayname'] . "($name)";
502
+            $components = [];
503
+            if ($row['components']) {
504
+                $components = explode(',',$row['components']);
505
+            }
506
+            $calendar = [
507
+                'id' => $row['id'],
508
+                'uri' => $row['publicuri'],
509
+                'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
510
+                '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
511
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
512
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
513
+                '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
514
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
515
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
516
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
517
+            ];
518
+
519
+            foreach ($this->propertyMap as $xmlName => $dbName) {
520
+                $calendar[$xmlName] = $row[$dbName];
521
+            }
522
+
523
+            $this->addOwnerPrincipal($calendar);
524
+
525
+            if (!isset($calendars[$calendar['id']])) {
526
+                $calendars[$calendar['id']] = $calendar;
527
+            }
528
+        }
529
+        $result->closeCursor();
530
+
531
+        return array_values($calendars);
532
+    }
533
+
534
+    /**
535
+     * @param string $uri
536
+     * @return array
537
+     * @throws NotFound
538
+     */
539
+    public function getPublicCalendar($uri) {
540
+        $fields = array_values($this->propertyMap);
541
+        $fields[] = 'a.id';
542
+        $fields[] = 'a.uri';
543
+        $fields[] = 'a.synctoken';
544
+        $fields[] = 'a.components';
545
+        $fields[] = 'a.principaluri';
546
+        $fields[] = 'a.transparent';
547
+        $fields[] = 's.access';
548
+        $fields[] = 's.publicuri';
549
+        $query = $this->db->getQueryBuilder();
550
+        $result = $query->select($fields)
551
+            ->from('dav_shares', 's')
552
+            ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
553
+            ->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
554
+            ->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
555
+            ->andWhere($query->expr()->eq('s.publicuri', $query->createNamedParameter($uri)))
556
+            ->execute();
557
+
558
+        $row = $result->fetch(\PDO::FETCH_ASSOC);
559
+
560
+        $result->closeCursor();
561
+
562
+        if ($row === false) {
563
+            throw new NotFound('Node with name \'' . $uri . '\' could not be found');
564
+        }
565
+
566
+        list(, $name) = Uri\split($row['principaluri']);
567
+        $row['displayname'] = $row['displayname'] . ' ' . "($name)";
568
+        $components = [];
569
+        if ($row['components']) {
570
+            $components = explode(',',$row['components']);
571
+        }
572
+        $calendar = [
573
+            'id' => $row['id'],
574
+            'uri' => $row['publicuri'],
575
+            'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
576
+            '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
577
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
578
+            '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
579
+            '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
580
+            '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
581
+            '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
582
+            '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
583
+        ];
584
+
585
+        foreach ($this->propertyMap as $xmlName => $dbName) {
586
+            $calendar[$xmlName] = $row[$dbName];
587
+        }
588
+
589
+        $this->addOwnerPrincipal($calendar);
590
+
591
+        return $calendar;
592
+    }
593
+
594
+    /**
595
+     * @param string $principal
596
+     * @param string $uri
597
+     * @return array|null
598
+     */
599
+    public function getCalendarByUri($principal, $uri) {
600
+        $fields = array_values($this->propertyMap);
601
+        $fields[] = 'id';
602
+        $fields[] = 'uri';
603
+        $fields[] = 'synctoken';
604
+        $fields[] = 'components';
605
+        $fields[] = 'principaluri';
606
+        $fields[] = 'transparent';
607
+
608
+        // Making fields a comma-delimited list
609
+        $query = $this->db->getQueryBuilder();
610
+        $query->select($fields)->from('calendars')
611
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
612
+            ->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
613
+            ->setMaxResults(1);
614
+        $stmt = $query->execute();
615
+
616
+        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
617
+        $stmt->closeCursor();
618
+        if ($row === false) {
619
+            return null;
620
+        }
621
+
622
+        $components = [];
623
+        if ($row['components']) {
624
+            $components = explode(',',$row['components']);
625
+        }
626
+
627
+        $calendar = [
628
+            'id' => $row['id'],
629
+            'uri' => $row['uri'],
630
+            'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
631
+            '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
632
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
633
+            '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
634
+            '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
635
+        ];
636
+
637
+        foreach ($this->propertyMap as $xmlName => $dbName) {
638
+            $calendar[$xmlName] = $row[$dbName];
639
+        }
640
+
641
+        $this->addOwnerPrincipal($calendar);
642
+
643
+        return $calendar;
644
+    }
645
+
646
+    /**
647
+     * @param $calendarId
648
+     * @return array|null
649
+     */
650
+    public function getCalendarById($calendarId) {
651
+        $fields = array_values($this->propertyMap);
652
+        $fields[] = 'id';
653
+        $fields[] = 'uri';
654
+        $fields[] = 'synctoken';
655
+        $fields[] = 'components';
656
+        $fields[] = 'principaluri';
657
+        $fields[] = 'transparent';
658
+
659
+        // Making fields a comma-delimited list
660
+        $query = $this->db->getQueryBuilder();
661
+        $query->select($fields)->from('calendars')
662
+            ->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)))
663
+            ->setMaxResults(1);
664
+        $stmt = $query->execute();
665
+
666
+        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
667
+        $stmt->closeCursor();
668
+        if ($row === false) {
669
+            return null;
670
+        }
671
+
672
+        $components = [];
673
+        if ($row['components']) {
674
+            $components = explode(',',$row['components']);
675
+        }
676
+
677
+        $calendar = [
678
+            'id' => $row['id'],
679
+            'uri' => $row['uri'],
680
+            'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
681
+            '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
682
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
683
+            '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
684
+            '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
685
+        ];
686
+
687
+        foreach ($this->propertyMap as $xmlName => $dbName) {
688
+            $calendar[$xmlName] = $row[$dbName];
689
+        }
690
+
691
+        $this->addOwnerPrincipal($calendar);
692
+
693
+        return $calendar;
694
+    }
695
+
696
+    /**
697
+     * @param $subscriptionId
698
+     */
699
+    public function getSubscriptionById($subscriptionId) {
700
+        $fields = array_values($this->subscriptionPropertyMap);
701
+        $fields[] = 'id';
702
+        $fields[] = 'uri';
703
+        $fields[] = 'source';
704
+        $fields[] = 'synctoken';
705
+        $fields[] = 'principaluri';
706
+        $fields[] = 'lastmodified';
707
+
708
+        $query = $this->db->getQueryBuilder();
709
+        $query->select($fields)
710
+            ->from('calendarsubscriptions')
711
+            ->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
712
+            ->orderBy('calendarorder', 'asc');
713
+        $stmt = $query->execute();
714
+
715
+        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
716
+        $stmt->closeCursor();
717
+        if ($row === false) {
718
+            return null;
719
+        }
720
+
721
+        $subscription = [
722
+            'id' => $row['id'],
723
+            'uri' => $row['uri'],
724
+            'principaluri' => $row['principaluri'],
725
+            'source' => $row['source'],
726
+            'lastmodified' => $row['lastmodified'],
727
+            '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
728
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
729
+        ];
730
+
731
+        foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
732
+            if (!is_null($row[$dbName])) {
733
+                $subscription[$xmlName] = $row[$dbName];
734
+            }
735
+        }
736
+
737
+        return $subscription;
738
+    }
739
+
740
+    /**
741
+     * Creates a new calendar for a principal.
742
+     *
743
+     * If the creation was a success, an id must be returned that can be used to reference
744
+     * this calendar in other methods, such as updateCalendar.
745
+     *
746
+     * @param string $principalUri
747
+     * @param string $calendarUri
748
+     * @param array $properties
749
+     * @return int
750
+     */
751
+    public function createCalendar($principalUri, $calendarUri, array $properties) {
752
+        $values = [
753
+            'principaluri' => $this->convertPrincipal($principalUri, true),
754
+            'uri' => $calendarUri,
755
+            'synctoken' => 1,
756
+            'transparent' => 0,
757
+            'components' => 'VEVENT,VTODO',
758
+            'displayname' => $calendarUri
759
+        ];
760
+
761
+        // Default value
762
+        $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
763
+        if (isset($properties[$sccs])) {
764
+            if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) {
765
+                throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
766
+            }
767
+            $values['components'] = implode(',',$properties[$sccs]->getValue());
768
+        } elseif (isset($properties['components'])) {
769
+            // Allow to provide components internally without having
770
+            // to create a SupportedCalendarComponentSet object
771
+            $values['components'] = $properties['components'];
772
+        }
773
+
774
+        $transp = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
775
+        if (isset($properties[$transp])) {
776
+            $values['transparent'] = (int) ($properties[$transp]->getValue() === 'transparent');
777
+        }
778
+
779
+        foreach ($this->propertyMap as $xmlName => $dbName) {
780
+            if (isset($properties[$xmlName])) {
781
+                $values[$dbName] = $properties[$xmlName];
782
+            }
783
+        }
784
+
785
+        $query = $this->db->getQueryBuilder();
786
+        $query->insert('calendars');
787
+        foreach ($values as $column => $value) {
788
+            $query->setValue($column, $query->createNamedParameter($value));
789
+        }
790
+        $query->execute();
791
+        $calendarId = $query->getLastInsertId();
792
+
793
+        $calendarData = $this->getCalendarById($calendarId);
794
+        $this->dispatcher->dispatchTyped(new CalendarCreatedEvent((int)$calendarId, $calendarData));
795
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendar', new GenericEvent(
796
+            '\OCA\DAV\CalDAV\CalDavBackend::createCalendar',
797
+            [
798
+                'calendarId' => $calendarId,
799
+                'calendarData' => $calendarData,
800
+            ]));
801
+
802
+        return $calendarId;
803
+    }
804
+
805
+    /**
806
+     * Updates properties for a calendar.
807
+     *
808
+     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
809
+     * To do the actual updates, you must tell this object which properties
810
+     * you're going to process with the handle() method.
811
+     *
812
+     * Calling the handle method is like telling the PropPatch object "I
813
+     * promise I can handle updating this property".
814
+     *
815
+     * Read the PropPatch documentation for more info and examples.
816
+     *
817
+     * @param mixed $calendarId
818
+     * @param PropPatch $propPatch
819
+     * @return void
820
+     */
821
+    public function updateCalendar($calendarId, PropPatch $propPatch) {
822
+        $supportedProperties = array_keys($this->propertyMap);
823
+        $supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
824
+
825
+        $propPatch->handle($supportedProperties, function ($mutations) use ($calendarId) {
826
+            $newValues = [];
827
+            foreach ($mutations as $propertyName => $propertyValue) {
828
+                switch ($propertyName) {
829
+                    case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp':
830
+                        $fieldName = 'transparent';
831
+                        $newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent');
832
+                        break;
833
+                    default:
834
+                        $fieldName = $this->propertyMap[$propertyName];
835
+                        $newValues[$fieldName] = $propertyValue;
836
+                        break;
837
+                }
838
+            }
839
+            $query = $this->db->getQueryBuilder();
840
+            $query->update('calendars');
841
+            foreach ($newValues as $fieldName => $value) {
842
+                $query->set($fieldName, $query->createNamedParameter($value));
843
+            }
844
+            $query->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
845
+            $query->execute();
846
+
847
+            $this->addChange($calendarId, "", 2);
848
+
849
+            $calendarData = $this->getCalendarById($calendarId);
850
+            $shares = $this->getShares($calendarId);
851
+            $this->dispatcher->dispatchTyped(new CalendarUpdatedEvent((int)$calendarId, $calendarData, $shares, $mutations));
852
+            $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendar', new GenericEvent(
853
+                '\OCA\DAV\CalDAV\CalDavBackend::updateCalendar',
854
+                [
855
+                    'calendarId' => $calendarId,
856
+                    'calendarData' => $calendarData,
857
+                    'shares' => $shares,
858
+                    'propertyMutations' => $mutations,
859
+                ]));
860
+
861
+            return true;
862
+        });
863
+    }
864
+
865
+    /**
866
+     * Delete a calendar and all it's objects
867
+     *
868
+     * @param mixed $calendarId
869
+     * @return void
870
+     */
871
+    public function deleteCalendar($calendarId) {
872
+        $calendarData = $this->getCalendarById($calendarId);
873
+        $shares = $this->getShares($calendarId);
874
+
875
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar', new GenericEvent(
876
+            '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar',
877
+            [
878
+                'calendarId' => $calendarId,
879
+                'calendarData' => $calendarData,
880
+                'shares' => $shares,
881
+            ]));
882
+
883
+        $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `calendartype` = ?');
884
+        $stmt->execute([$calendarId, self::CALENDAR_TYPE_CALENDAR]);
885
+
886
+        $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendars` WHERE `id` = ?');
887
+        $stmt->execute([$calendarId]);
888
+
889
+        $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarchanges` WHERE `calendarid` = ? AND `calendartype` = ?');
890
+        $stmt->execute([$calendarId, self::CALENDAR_TYPE_CALENDAR]);
891
+
892
+        $this->calendarSharingBackend->deleteAllShares($calendarId);
893
+
894
+        $query = $this->db->getQueryBuilder();
895
+        $query->delete($this->dbObjectPropertiesTable)
896
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
897
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)))
898
+            ->execute();
899
+
900
+        if ($calendarData) {
901
+            $this->dispatcher->dispatchTyped(new CalendarDeletedEvent((int)$calendarId, $calendarData, $shares));
902
+        }
903
+    }
904
+
905
+    /**
906
+     * Delete all of an user's shares
907
+     *
908
+     * @param string $principaluri
909
+     * @return void
910
+     */
911
+    public function deleteAllSharesByUser($principaluri) {
912
+        $this->calendarSharingBackend->deleteAllSharesByUser($principaluri);
913
+    }
914
+
915
+    /**
916
+     * Returns all calendar objects within a calendar.
917
+     *
918
+     * Every item contains an array with the following keys:
919
+     *   * calendardata - The iCalendar-compatible calendar data
920
+     *   * uri - a unique key which will be used to construct the uri. This can
921
+     *     be any arbitrary string, but making sure it ends with '.ics' is a
922
+     *     good idea. This is only the basename, or filename, not the full
923
+     *     path.
924
+     *   * lastmodified - a timestamp of the last modification time
925
+     *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
926
+     *   '"abcdef"')
927
+     *   * size - The size of the calendar objects, in bytes.
928
+     *   * component - optional, a string containing the type of object, such
929
+     *     as 'vevent' or 'vtodo'. If specified, this will be used to populate
930
+     *     the Content-Type header.
931
+     *
932
+     * Note that the etag is optional, but it's highly encouraged to return for
933
+     * speed reasons.
934
+     *
935
+     * The calendardata is also optional. If it's not returned
936
+     * 'getCalendarObject' will be called later, which *is* expected to return
937
+     * calendardata.
938
+     *
939
+     * If neither etag or size are specified, the calendardata will be
940
+     * used/fetched to determine these numbers. If both are specified the
941
+     * amount of times this is needed is reduced by a great degree.
942
+     *
943
+     * @param mixed $calendarId
944
+     * @param int $calendarType
945
+     * @return array
946
+     */
947
+    public function getCalendarObjects($calendarId, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
948
+        $query = $this->db->getQueryBuilder();
949
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'componenttype', 'classification'])
950
+            ->from('calendarobjects')
951
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
952
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
953
+        $stmt = $query->execute();
954
+
955
+        $result = [];
956
+        foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
957
+            $result[] = [
958
+                'id' => $row['id'],
959
+                'uri' => $row['uri'],
960
+                'lastmodified' => $row['lastmodified'],
961
+                'etag' => '"' . $row['etag'] . '"',
962
+                'calendarid' => $row['calendarid'],
963
+                'size' => (int)$row['size'],
964
+                'component' => strtolower($row['componenttype']),
965
+                'classification' => (int)$row['classification']
966
+            ];
967
+        }
968
+
969
+        return $result;
970
+    }
971
+
972
+    /**
973
+     * Returns information from a single calendar object, based on it's object
974
+     * uri.
975
+     *
976
+     * The object uri is only the basename, or filename and not a full path.
977
+     *
978
+     * The returned array must have the same keys as getCalendarObjects. The
979
+     * 'calendardata' object is required here though, while it's not required
980
+     * for getCalendarObjects.
981
+     *
982
+     * This method must return null if the object did not exist.
983
+     *
984
+     * @param mixed $calendarId
985
+     * @param string $objectUri
986
+     * @param int $calendarType
987
+     * @return array|null
988
+     */
989
+    public function getCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
990
+        $query = $this->db->getQueryBuilder();
991
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
992
+            ->from('calendarobjects')
993
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
994
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
995
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
996
+        $stmt = $query->execute();
997
+        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
998
+
999
+        if (!$row) {
1000
+            return null;
1001
+        }
1002
+
1003
+        return [
1004
+            'id' => $row['id'],
1005
+            'uri' => $row['uri'],
1006
+            'lastmodified' => $row['lastmodified'],
1007
+            'etag' => '"' . $row['etag'] . '"',
1008
+            'calendarid' => $row['calendarid'],
1009
+            'size' => (int)$row['size'],
1010
+            'calendardata' => $this->readBlob($row['calendardata']),
1011
+            'component' => strtolower($row['componenttype']),
1012
+            'classification' => (int)$row['classification']
1013
+        ];
1014
+    }
1015
+
1016
+    /**
1017
+     * Returns a list of calendar objects.
1018
+     *
1019
+     * This method should work identical to getCalendarObject, but instead
1020
+     * return all the calendar objects in the list as an array.
1021
+     *
1022
+     * If the backend supports this, it may allow for some speed-ups.
1023
+     *
1024
+     * @param mixed $calendarId
1025
+     * @param string[] $uris
1026
+     * @param int $calendarType
1027
+     * @return array
1028
+     */
1029
+    public function getMultipleCalendarObjects($calendarId, array $uris, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
1030
+        if (empty($uris)) {
1031
+            return [];
1032
+        }
1033
+
1034
+        $chunks = array_chunk($uris, 100);
1035
+        $objects = [];
1036
+
1037
+        $query = $this->db->getQueryBuilder();
1038
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
1039
+            ->from('calendarobjects')
1040
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1041
+            ->andWhere($query->expr()->in('uri', $query->createParameter('uri')))
1042
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
1043
+
1044
+        foreach ($chunks as $uris) {
1045
+            $query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
1046
+            $result = $query->execute();
1047
+
1048
+            while ($row = $result->fetch()) {
1049
+                $objects[] = [
1050
+                    'id' => $row['id'],
1051
+                    'uri' => $row['uri'],
1052
+                    'lastmodified' => $row['lastmodified'],
1053
+                    'etag' => '"' . $row['etag'] . '"',
1054
+                    'calendarid' => $row['calendarid'],
1055
+                    'size' => (int)$row['size'],
1056
+                    'calendardata' => $this->readBlob($row['calendardata']),
1057
+                    'component' => strtolower($row['componenttype']),
1058
+                    'classification' => (int)$row['classification']
1059
+                ];
1060
+            }
1061
+            $result->closeCursor();
1062
+        }
1063
+
1064
+        return $objects;
1065
+    }
1066
+
1067
+    /**
1068
+     * Creates a new calendar object.
1069
+     *
1070
+     * The object uri is only the basename, or filename and not a full path.
1071
+     *
1072
+     * It is possible return an etag from this function, which will be used in
1073
+     * the response to this PUT request. Note that the ETag must be surrounded
1074
+     * by double-quotes.
1075
+     *
1076
+     * However, you should only really return this ETag if you don't mangle the
1077
+     * calendar-data. If the result of a subsequent GET to this object is not
1078
+     * the exact same as this request body, you should omit the ETag.
1079
+     *
1080
+     * @param mixed $calendarId
1081
+     * @param string $objectUri
1082
+     * @param string $calendarData
1083
+     * @param int $calendarType
1084
+     * @return string
1085
+     */
1086
+    public function createCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1087
+        $extraData = $this->getDenormalizedData($calendarData);
1088
+
1089
+        $q = $this->db->getQueryBuilder();
1090
+        $q->select($q->func()->count('*'))
1091
+            ->from('calendarobjects')
1092
+            ->where($q->expr()->eq('calendarid', $q->createNamedParameter($calendarId)))
1093
+            ->andWhere($q->expr()->eq('uid', $q->createNamedParameter($extraData['uid'])))
1094
+            ->andWhere($q->expr()->eq('calendartype', $q->createNamedParameter($calendarType)));
1095
+
1096
+        $result = $q->execute();
1097
+        $count = (int) $result->fetchColumn();
1098
+        $result->closeCursor();
1099
+
1100
+        if ($count !== 0) {
1101
+            throw new \Sabre\DAV\Exception\BadRequest('Calendar object with uid already exists in this calendar collection.');
1102
+        }
1103
+
1104
+        $query = $this->db->getQueryBuilder();
1105
+        $query->insert('calendarobjects')
1106
+            ->values([
1107
+                'calendarid' => $query->createNamedParameter($calendarId),
1108
+                'uri' => $query->createNamedParameter($objectUri),
1109
+                'calendardata' => $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB),
1110
+                'lastmodified' => $query->createNamedParameter(time()),
1111
+                'etag' => $query->createNamedParameter($extraData['etag']),
1112
+                'size' => $query->createNamedParameter($extraData['size']),
1113
+                'componenttype' => $query->createNamedParameter($extraData['componentType']),
1114
+                'firstoccurence' => $query->createNamedParameter($extraData['firstOccurence']),
1115
+                'lastoccurence' => $query->createNamedParameter($extraData['lastOccurence']),
1116
+                'classification' => $query->createNamedParameter($extraData['classification']),
1117
+                'uid' => $query->createNamedParameter($extraData['uid']),
1118
+                'calendartype' => $query->createNamedParameter($calendarType),
1119
+            ])
1120
+            ->execute();
1121
+
1122
+        $this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType);
1123
+        $this->addChange($calendarId, $objectUri, 1, $calendarType);
1124
+
1125
+        $objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1126
+        if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1127
+            $calendarRow = $this->getCalendarById($calendarId);
1128
+            $shares = $this->getShares($calendarId);
1129
+
1130
+            $this->dispatcher->dispatchTyped(new CalendarObjectCreatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
1131
+            $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', new GenericEvent(
1132
+                '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject',
1133
+                [
1134
+                    'calendarId' => $calendarId,
1135
+                    'calendarData' => $calendarRow,
1136
+                    'shares' => $shares,
1137
+                    'objectData' => $objectRow,
1138
+                ]
1139
+            ));
1140
+        } else {
1141
+            $subscriptionRow = $this->getSubscriptionById($calendarId);
1142
+
1143
+            $this->dispatcher->dispatchTyped(new CachedCalendarObjectCreatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
1144
+            $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject', new GenericEvent(
1145
+                '\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject',
1146
+                [
1147
+                    'subscriptionId' => $calendarId,
1148
+                    'calendarData' => $subscriptionRow,
1149
+                    'shares' => [],
1150
+                    'objectData' => $objectRow,
1151
+                ]
1152
+            ));
1153
+        }
1154
+
1155
+        return '"' . $extraData['etag'] . '"';
1156
+    }
1157
+
1158
+    /**
1159
+     * Updates an existing calendarobject, based on it's uri.
1160
+     *
1161
+     * The object uri is only the basename, or filename and not a full path.
1162
+     *
1163
+     * It is possible return an etag from this function, which will be used in
1164
+     * the response to this PUT request. Note that the ETag must be surrounded
1165
+     * by double-quotes.
1166
+     *
1167
+     * However, you should only really return this ETag if you don't mangle the
1168
+     * calendar-data. If the result of a subsequent GET to this object is not
1169
+     * the exact same as this request body, you should omit the ETag.
1170
+     *
1171
+     * @param mixed $calendarId
1172
+     * @param string $objectUri
1173
+     * @param string $calendarData
1174
+     * @param int $calendarType
1175
+     * @return string
1176
+     */
1177
+    public function updateCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1178
+        $extraData = $this->getDenormalizedData($calendarData);
1179
+        $query = $this->db->getQueryBuilder();
1180
+        $query->update('calendarobjects')
1181
+                ->set('calendardata', $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB))
1182
+                ->set('lastmodified', $query->createNamedParameter(time()))
1183
+                ->set('etag', $query->createNamedParameter($extraData['etag']))
1184
+                ->set('size', $query->createNamedParameter($extraData['size']))
1185
+                ->set('componenttype', $query->createNamedParameter($extraData['componentType']))
1186
+                ->set('firstoccurence', $query->createNamedParameter($extraData['firstOccurence']))
1187
+                ->set('lastoccurence', $query->createNamedParameter($extraData['lastOccurence']))
1188
+                ->set('classification', $query->createNamedParameter($extraData['classification']))
1189
+                ->set('uid', $query->createNamedParameter($extraData['uid']))
1190
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1191
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1192
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)))
1193
+            ->execute();
1194
+
1195
+        $this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType);
1196
+        $this->addChange($calendarId, $objectUri, 2, $calendarType);
1197
+
1198
+        $objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1199
+        if (is_array($objectRow)) {
1200
+            if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1201
+                $calendarRow = $this->getCalendarById($calendarId);
1202
+                $shares = $this->getShares($calendarId);
1203
+
1204
+                $this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
1205
+                $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', new GenericEvent(
1206
+                    '\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject',
1207
+                    [
1208
+                        'calendarId' => $calendarId,
1209
+                        'calendarData' => $calendarRow,
1210
+                        'shares' => $shares,
1211
+                        'objectData' => $objectRow,
1212
+                    ]
1213
+                ));
1214
+            } else {
1215
+                $subscriptionRow = $this->getSubscriptionById($calendarId);
1216
+
1217
+                $this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
1218
+                $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject', new GenericEvent(
1219
+                    '\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject',
1220
+                    [
1221
+                        'subscriptionId' => $calendarId,
1222
+                        'calendarData' => $subscriptionRow,
1223
+                        'shares' => [],
1224
+                        'objectData' => $objectRow,
1225
+                    ]
1226
+                ));
1227
+            }
1228
+        }
1229
+
1230
+        return '"' . $extraData['etag'] . '"';
1231
+    }
1232
+
1233
+    /**
1234
+     * @param int $calendarObjectId
1235
+     * @param int $classification
1236
+     */
1237
+    public function setClassification($calendarObjectId, $classification) {
1238
+        if (!in_array($classification, [
1239
+            self::CLASSIFICATION_PUBLIC, self::CLASSIFICATION_PRIVATE, self::CLASSIFICATION_CONFIDENTIAL
1240
+        ])) {
1241
+            throw new \InvalidArgumentException();
1242
+        }
1243
+        $query = $this->db->getQueryBuilder();
1244
+        $query->update('calendarobjects')
1245
+            ->set('classification', $query->createNamedParameter($classification))
1246
+            ->where($query->expr()->eq('id', $query->createNamedParameter($calendarObjectId)))
1247
+            ->execute();
1248
+    }
1249
+
1250
+    /**
1251
+     * Deletes an existing calendar object.
1252
+     *
1253
+     * The object uri is only the basename, or filename and not a full path.
1254
+     *
1255
+     * @param mixed $calendarId
1256
+     * @param string $objectUri
1257
+     * @param int $calendarType
1258
+     * @return void
1259
+     */
1260
+    public function deleteCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1261
+        $data = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1262
+        if (is_array($data)) {
1263
+            if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1264
+                $calendarRow = $this->getCalendarById($calendarId);
1265
+                $shares = $this->getShares($calendarId);
1266
+
1267
+                $this->dispatcher->dispatchTyped(new CalendarObjectDeletedEvent((int)$calendarId, $calendarRow, $shares, $data));
1268
+                $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', new GenericEvent(
1269
+                    '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject',
1270
+                    [
1271
+                        'calendarId' => $calendarId,
1272
+                        'calendarData' => $calendarRow,
1273
+                        'shares' => $shares,
1274
+                        'objectData' => $data,
1275
+                    ]
1276
+                ));
1277
+            } else {
1278
+                $subscriptionRow = $this->getSubscriptionById($calendarId);
1279
+
1280
+                $this->dispatcher->dispatchTyped(new CachedCalendarObjectDeletedEvent((int)$calendarId, $subscriptionRow, [], $data));
1281
+                $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject', new GenericEvent(
1282
+                    '\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject',
1283
+                    [
1284
+                        'subscriptionId' => $calendarId,
1285
+                        'calendarData' => $subscriptionRow,
1286
+                        'shares' => [],
1287
+                        'objectData' => $data,
1288
+                    ]
1289
+                ));
1290
+            }
1291
+        }
1292
+
1293
+        $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ? AND `calendartype` = ?');
1294
+        $stmt->execute([$calendarId, $objectUri, $calendarType]);
1295
+
1296
+        if (is_array($data)) {
1297
+            $this->purgeProperties($calendarId, $data['id'], $calendarType);
1298
+        }
1299
+
1300
+        $this->addChange($calendarId, $objectUri, 3, $calendarType);
1301
+    }
1302
+
1303
+    /**
1304
+     * Performs a calendar-query on the contents of this calendar.
1305
+     *
1306
+     * The calendar-query is defined in RFC4791 : CalDAV. Using the
1307
+     * calendar-query it is possible for a client to request a specific set of
1308
+     * object, based on contents of iCalendar properties, date-ranges and
1309
+     * iCalendar component types (VTODO, VEVENT).
1310
+     *
1311
+     * This method should just return a list of (relative) urls that match this
1312
+     * query.
1313
+     *
1314
+     * The list of filters are specified as an array. The exact array is
1315
+     * documented by Sabre\CalDAV\CalendarQueryParser.
1316
+     *
1317
+     * Note that it is extremely likely that getCalendarObject for every path
1318
+     * returned from this method will be called almost immediately after. You
1319
+     * may want to anticipate this to speed up these requests.
1320
+     *
1321
+     * This method provides a default implementation, which parses *all* the
1322
+     * iCalendar objects in the specified calendar.
1323
+     *
1324
+     * This default may well be good enough for personal use, and calendars
1325
+     * that aren't very large. But if you anticipate high usage, big calendars
1326
+     * or high loads, you are strongly advised to optimize certain paths.
1327
+     *
1328
+     * The best way to do so is override this method and to optimize
1329
+     * specifically for 'common filters'.
1330
+     *
1331
+     * Requests that are extremely common are:
1332
+     *   * requests for just VEVENTS
1333
+     *   * requests for just VTODO
1334
+     *   * requests with a time-range-filter on either VEVENT or VTODO.
1335
+     *
1336
+     * ..and combinations of these requests. It may not be worth it to try to
1337
+     * handle every possible situation and just rely on the (relatively
1338
+     * easy to use) CalendarQueryValidator to handle the rest.
1339
+     *
1340
+     * Note that especially time-range-filters may be difficult to parse. A
1341
+     * time-range filter specified on a VEVENT must for instance also handle
1342
+     * recurrence rules correctly.
1343
+     * A good example of how to interprete all these filters can also simply
1344
+     * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
1345
+     * as possible, so it gives you a good idea on what type of stuff you need
1346
+     * to think of.
1347
+     *
1348
+     * @param mixed $calendarId
1349
+     * @param array $filters
1350
+     * @param int $calendarType
1351
+     * @return array
1352
+     */
1353
+    public function calendarQuery($calendarId, array $filters, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
1354
+        $componentType = null;
1355
+        $requirePostFilter = true;
1356
+        $timeRange = null;
1357
+
1358
+        // if no filters were specified, we don't need to filter after a query
1359
+        if (!$filters['prop-filters'] && !$filters['comp-filters']) {
1360
+            $requirePostFilter = false;
1361
+        }
1362
+
1363
+        // Figuring out if there's a component filter
1364
+        if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
1365
+            $componentType = $filters['comp-filters'][0]['name'];
1366
+
1367
+            // Checking if we need post-filters
1368
+            if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
1369
+                $requirePostFilter = false;
1370
+            }
1371
+            // There was a time-range filter
1372
+            if ($componentType === 'VEVENT' && isset($filters['comp-filters'][0]['time-range']) && is_array($filters['comp-filters'][0]['time-range'])) {
1373
+                $timeRange = $filters['comp-filters'][0]['time-range'];
1374
+
1375
+                // If start time OR the end time is not specified, we can do a
1376
+                // 100% accurate mysql query.
1377
+                if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
1378
+                    $requirePostFilter = false;
1379
+                }
1380
+            }
1381
+        }
1382
+        $columns = ['uri'];
1383
+        if ($requirePostFilter) {
1384
+            $columns = ['uri', 'calendardata'];
1385
+        }
1386
+        $query = $this->db->getQueryBuilder();
1387
+        $query->select($columns)
1388
+            ->from('calendarobjects')
1389
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1390
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
1391
+
1392
+        if ($componentType) {
1393
+            $query->andWhere($query->expr()->eq('componenttype', $query->createNamedParameter($componentType)));
1394
+        }
1395
+
1396
+        if ($timeRange && $timeRange['start']) {
1397
+            $query->andWhere($query->expr()->gt('lastoccurence', $query->createNamedParameter($timeRange['start']->getTimeStamp())));
1398
+        }
1399
+        if ($timeRange && $timeRange['end']) {
1400
+            $query->andWhere($query->expr()->lt('firstoccurence', $query->createNamedParameter($timeRange['end']->getTimeStamp())));
1401
+        }
1402
+
1403
+        $stmt = $query->execute();
1404
+
1405
+        $result = [];
1406
+        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1407
+            if ($requirePostFilter) {
1408
+                // validateFilterForObject will parse the calendar data
1409
+                // catch parsing errors
1410
+                try {
1411
+                    $matches = $this->validateFilterForObject($row, $filters);
1412
+                } catch (ParseException $ex) {
1413
+                    $this->logger->logException($ex, [
1414
+                        'app' => 'dav',
1415
+                        'message' => 'Caught parsing exception for calendar data. This usually indicates invalid calendar data. calendar-id:'.$calendarId.' uri:'.$row['uri']
1416
+                    ]);
1417
+                    continue;
1418
+                } catch (InvalidDataException $ex) {
1419
+                    $this->logger->logException($ex, [
1420
+                        'app' => 'dav',
1421
+                        'message' => 'Caught invalid data exception for calendar data. This usually indicates invalid calendar data. calendar-id:'.$calendarId.' uri:'.$row['uri']
1422
+                    ]);
1423
+                    continue;
1424
+                }
1425
+
1426
+                if (!$matches) {
1427
+                    continue;
1428
+                }
1429
+            }
1430
+            $result[] = $row['uri'];
1431
+        }
1432
+
1433
+        return $result;
1434
+    }
1435
+
1436
+    /**
1437
+     * custom Nextcloud search extension for CalDAV
1438
+     *
1439
+     * TODO - this should optionally cover cached calendar objects as well
1440
+     *
1441
+     * @param string $principalUri
1442
+     * @param array $filters
1443
+     * @param integer|null $limit
1444
+     * @param integer|null $offset
1445
+     * @return array
1446
+     */
1447
+    public function calendarSearch($principalUri, array $filters, $limit = null, $offset = null) {
1448
+        $calendars = $this->getCalendarsForUser($principalUri);
1449
+        $ownCalendars = [];
1450
+        $sharedCalendars = [];
1451
+
1452
+        $uriMapper = [];
1453
+
1454
+        foreach ($calendars as $calendar) {
1455
+            if ($calendar['{http://owncloud.org/ns}owner-principal'] === $principalUri) {
1456
+                $ownCalendars[] = $calendar['id'];
1457
+            } else {
1458
+                $sharedCalendars[] = $calendar['id'];
1459
+            }
1460
+            $uriMapper[$calendar['id']] = $calendar['uri'];
1461
+        }
1462
+        if (count($ownCalendars) === 0 && count($sharedCalendars) === 0) {
1463
+            return [];
1464
+        }
1465
+
1466
+        $query = $this->db->getQueryBuilder();
1467
+        // Calendar id expressions
1468
+        $calendarExpressions = [];
1469
+        foreach ($ownCalendars as $id) {
1470
+            $calendarExpressions[] = $query->expr()->andX(
1471
+                $query->expr()->eq('c.calendarid',
1472
+                    $query->createNamedParameter($id)),
1473
+                $query->expr()->eq('c.calendartype',
1474
+                        $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1475
+        }
1476
+        foreach ($sharedCalendars as $id) {
1477
+            $calendarExpressions[] = $query->expr()->andX(
1478
+                $query->expr()->eq('c.calendarid',
1479
+                    $query->createNamedParameter($id)),
1480
+                $query->expr()->eq('c.classification',
1481
+                    $query->createNamedParameter(self::CLASSIFICATION_PUBLIC)),
1482
+                $query->expr()->eq('c.calendartype',
1483
+                    $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1484
+        }
1485
+
1486
+        if (count($calendarExpressions) === 1) {
1487
+            $calExpr = $calendarExpressions[0];
1488
+        } else {
1489
+            $calExpr = call_user_func_array([$query->expr(), 'orX'], $calendarExpressions);
1490
+        }
1491
+
1492
+        // Component expressions
1493
+        $compExpressions = [];
1494
+        foreach ($filters['comps'] as $comp) {
1495
+            $compExpressions[] = $query->expr()
1496
+                ->eq('c.componenttype', $query->createNamedParameter($comp));
1497
+        }
1498
+
1499
+        if (count($compExpressions) === 1) {
1500
+            $compExpr = $compExpressions[0];
1501
+        } else {
1502
+            $compExpr = call_user_func_array([$query->expr(), 'orX'], $compExpressions);
1503
+        }
1504
+
1505
+        if (!isset($filters['props'])) {
1506
+            $filters['props'] = [];
1507
+        }
1508
+        if (!isset($filters['params'])) {
1509
+            $filters['params'] = [];
1510
+        }
1511
+
1512
+        $propParamExpressions = [];
1513
+        foreach ($filters['props'] as $prop) {
1514
+            $propParamExpressions[] = $query->expr()->andX(
1515
+                $query->expr()->eq('i.name', $query->createNamedParameter($prop)),
1516
+                $query->expr()->isNull('i.parameter')
1517
+            );
1518
+        }
1519
+        foreach ($filters['params'] as $param) {
1520
+            $propParamExpressions[] = $query->expr()->andX(
1521
+                $query->expr()->eq('i.name', $query->createNamedParameter($param['property'])),
1522
+                $query->expr()->eq('i.parameter', $query->createNamedParameter($param['parameter']))
1523
+            );
1524
+        }
1525
+
1526
+        if (count($propParamExpressions) === 1) {
1527
+            $propParamExpr = $propParamExpressions[0];
1528
+        } else {
1529
+            $propParamExpr = call_user_func_array([$query->expr(), 'orX'], $propParamExpressions);
1530
+        }
1531
+
1532
+        $query->select(['c.calendarid', 'c.uri'])
1533
+            ->from($this->dbObjectPropertiesTable, 'i')
1534
+            ->join('i', 'calendarobjects', 'c', $query->expr()->eq('i.objectid', 'c.id'))
1535
+            ->where($calExpr)
1536
+            ->andWhere($compExpr)
1537
+            ->andWhere($propParamExpr)
1538
+            ->andWhere($query->expr()->iLike('i.value',
1539
+                $query->createNamedParameter('%'.$this->db->escapeLikeParameter($filters['search-term']).'%')));
1540
+
1541
+        if ($offset) {
1542
+            $query->setFirstResult($offset);
1543
+        }
1544
+        if ($limit) {
1545
+            $query->setMaxResults($limit);
1546
+        }
1547
+
1548
+        $stmt = $query->execute();
1549
+
1550
+        $result = [];
1551
+        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1552
+            $path = $uriMapper[$row['calendarid']] . '/' . $row['uri'];
1553
+            if (!in_array($path, $result)) {
1554
+                $result[] = $path;
1555
+            }
1556
+        }
1557
+
1558
+        return $result;
1559
+    }
1560
+
1561
+    /**
1562
+     * used for Nextcloud's calendar API
1563
+     *
1564
+     * @param array $calendarInfo
1565
+     * @param string $pattern
1566
+     * @param array $searchProperties
1567
+     * @param array $options
1568
+     * @param integer|null $limit
1569
+     * @param integer|null $offset
1570
+     *
1571
+     * @return array
1572
+     */
1573
+    public function search(array $calendarInfo, $pattern, array $searchProperties,
1574
+                            array $options, $limit, $offset) {
1575
+        $outerQuery = $this->db->getQueryBuilder();
1576
+        $innerQuery = $this->db->getQueryBuilder();
1577
+
1578
+        $innerQuery->selectDistinct('op.objectid')
1579
+            ->from($this->dbObjectPropertiesTable, 'op')
1580
+            ->andWhere($innerQuery->expr()->eq('op.calendarid',
1581
+                $outerQuery->createNamedParameter($calendarInfo['id'])))
1582
+            ->andWhere($innerQuery->expr()->eq('op.calendartype',
1583
+                $outerQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1584
+
1585
+        // only return public items for shared calendars for now
1586
+        if ($calendarInfo['principaluri'] !== $calendarInfo['{http://owncloud.org/ns}owner-principal']) {
1587
+            $innerQuery->andWhere($innerQuery->expr()->eq('c.classification',
1588
+                $outerQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1589
+        }
1590
+
1591
+        $or = $innerQuery->expr()->orX();
1592
+        foreach ($searchProperties as $searchProperty) {
1593
+            $or->add($innerQuery->expr()->eq('op.name',
1594
+                $outerQuery->createNamedParameter($searchProperty)));
1595
+        }
1596
+        $innerQuery->andWhere($or);
1597
+
1598
+        if ($pattern !== '') {
1599
+            $innerQuery->andWhere($innerQuery->expr()->iLike('op.value',
1600
+                $outerQuery->createNamedParameter('%' .
1601
+                    $this->db->escapeLikeParameter($pattern) . '%')));
1602
+        }
1603
+
1604
+        $outerQuery->select('c.id', 'c.calendardata', 'c.componenttype', 'c.uid', 'c.uri')
1605
+            ->from('calendarobjects', 'c');
1606
+
1607
+        if (isset($options['timerange'])) {
1608
+            if (isset($options['timerange']['start']) && $options['timerange']['start'] instanceof DateTime) {
1609
+                $outerQuery->andWhere($outerQuery->expr()->gt('lastoccurence',
1610
+                    $outerQuery->createNamedParameter($options['timerange']['start']->getTimeStamp())));
1611
+            }
1612
+            if (isset($options['timerange']['end']) && $options['timerange']['end'] instanceof DateTime) {
1613
+                $outerQuery->andWhere($outerQuery->expr()->lt('firstoccurence',
1614
+                    $outerQuery->createNamedParameter($options['timerange']['end']->getTimeStamp())));
1615
+            }
1616
+        }
1617
+
1618
+        if (isset($options['types'])) {
1619
+            $or = $outerQuery->expr()->orX();
1620
+            foreach ($options['types'] as $type) {
1621
+                $or->add($outerQuery->expr()->eq('componenttype',
1622
+                    $outerQuery->createNamedParameter($type)));
1623
+            }
1624
+            $outerQuery->andWhere($or);
1625
+        }
1626
+
1627
+        $outerQuery->andWhere($outerQuery->expr()->in('c.id',
1628
+            $outerQuery->createFunction($innerQuery->getSQL())));
1629
+
1630
+        if ($offset) {
1631
+            $outerQuery->setFirstResult($offset);
1632
+        }
1633
+        if ($limit) {
1634
+            $outerQuery->setMaxResults($limit);
1635
+        }
1636
+
1637
+        $result = $outerQuery->execute();
1638
+        $calendarObjects = $result->fetchAll();
1639
+
1640
+        return array_map(function ($o) {
1641
+            $calendarData = Reader::read($o['calendardata']);
1642
+            $comps = $calendarData->getComponents();
1643
+            $objects = [];
1644
+            $timezones = [];
1645
+            foreach ($comps as $comp) {
1646
+                if ($comp instanceof VTimeZone) {
1647
+                    $timezones[] = $comp;
1648
+                } else {
1649
+                    $objects[] = $comp;
1650
+                }
1651
+            }
1652
+
1653
+            return [
1654
+                'id' => $o['id'],
1655
+                'type' => $o['componenttype'],
1656
+                'uid' => $o['uid'],
1657
+                'uri' => $o['uri'],
1658
+                'objects' => array_map(function ($c) {
1659
+                    return $this->transformSearchData($c);
1660
+                }, $objects),
1661
+                'timezones' => array_map(function ($c) {
1662
+                    return $this->transformSearchData($c);
1663
+                }, $timezones),
1664
+            ];
1665
+        }, $calendarObjects);
1666
+    }
1667
+
1668
+    /**
1669
+     * @param Component $comp
1670
+     * @return array
1671
+     */
1672
+    private function transformSearchData(Component $comp) {
1673
+        $data = [];
1674
+        /** @var Component[] $subComponents */
1675
+        $subComponents = $comp->getComponents();
1676
+        /** @var Property[] $properties */
1677
+        $properties = array_filter($comp->children(), function ($c) {
1678
+            return $c instanceof Property;
1679
+        });
1680
+        $validationRules = $comp->getValidationRules();
1681
+
1682
+        foreach ($subComponents as $subComponent) {
1683
+            $name = $subComponent->name;
1684
+            if (!isset($data[$name])) {
1685
+                $data[$name] = [];
1686
+            }
1687
+            $data[$name][] = $this->transformSearchData($subComponent);
1688
+        }
1689
+
1690
+        foreach ($properties as $property) {
1691
+            $name = $property->name;
1692
+            if (!isset($validationRules[$name])) {
1693
+                $validationRules[$name] = '*';
1694
+            }
1695
+
1696
+            $rule = $validationRules[$property->name];
1697
+            if ($rule === '+' || $rule === '*') { // multiple
1698
+                if (!isset($data[$name])) {
1699
+                    $data[$name] = [];
1700
+                }
1701
+
1702
+                $data[$name][] = $this->transformSearchProperty($property);
1703
+            } else { // once
1704
+                $data[$name] = $this->transformSearchProperty($property);
1705
+            }
1706
+        }
1707
+
1708
+        return $data;
1709
+    }
1710
+
1711
+    /**
1712
+     * @param Property $prop
1713
+     * @return array
1714
+     */
1715
+    private function transformSearchProperty(Property $prop) {
1716
+        // No need to check Date, as it extends DateTime
1717
+        if ($prop instanceof Property\ICalendar\DateTime) {
1718
+            $value = $prop->getDateTime();
1719
+        } else {
1720
+            $value = $prop->getValue();
1721
+        }
1722
+
1723
+        return [
1724
+            $value,
1725
+            $prop->parameters()
1726
+        ];
1727
+    }
1728
+
1729
+    /**
1730
+     * @param string $principalUri
1731
+     * @param string $pattern
1732
+     * @param array $componentTypes
1733
+     * @param array $searchProperties
1734
+     * @param array $searchParameters
1735
+     * @param array $options
1736
+     * @return array
1737
+     */
1738
+    public function searchPrincipalUri(string $principalUri,
1739
+                                        string $pattern,
1740
+                                        array $componentTypes,
1741
+                                        array $searchProperties,
1742
+                                        array $searchParameters,
1743
+                                        array $options = []): array {
1744
+        $escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false;
1745
+
1746
+        $calendarObjectIdQuery = $this->db->getQueryBuilder();
1747
+        $calendarOr = $calendarObjectIdQuery->expr()->orX();
1748
+        $searchOr = $calendarObjectIdQuery->expr()->orX();
1749
+
1750
+        // Fetch calendars and subscription
1751
+        $calendars = $this->getCalendarsForUser($principalUri);
1752
+        $subscriptions = $this->getSubscriptionsForUser($principalUri);
1753
+        foreach ($calendars as $calendar) {
1754
+            $calendarAnd = $calendarObjectIdQuery->expr()->andX();
1755
+            $calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$calendar['id'])));
1756
+            $calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1757
+
1758
+            // If it's shared, limit search to public events
1759
+            if ($calendar['principaluri'] !== $calendar['{http://owncloud.org/ns}owner-principal']) {
1760
+                $calendarAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1761
+            }
1762
+
1763
+            $calendarOr->add($calendarAnd);
1764
+        }
1765
+        foreach ($subscriptions as $subscription) {
1766
+            $subscriptionAnd = $calendarObjectIdQuery->expr()->andX();
1767
+            $subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$subscription['id'])));
1768
+            $subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
1769
+
1770
+            // If it's shared, limit search to public events
1771
+            if ($subscription['principaluri'] !== $subscription['{http://owncloud.org/ns}owner-principal']) {
1772
+                $subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1773
+            }
1774
+
1775
+            $calendarOr->add($subscriptionAnd);
1776
+        }
1777
+
1778
+        foreach ($searchProperties as $property) {
1779
+            $propertyAnd = $calendarObjectIdQuery->expr()->andX();
1780
+            $propertyAnd->add($calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)));
1781
+            $propertyAnd->add($calendarObjectIdQuery->expr()->isNull('cob.parameter'));
1782
+
1783
+            $searchOr->add($propertyAnd);
1784
+        }
1785
+        foreach ($searchParameters as $property => $parameter) {
1786
+            $parameterAnd = $calendarObjectIdQuery->expr()->andX();
1787
+            $parameterAnd->add($calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)));
1788
+            $parameterAnd->add($calendarObjectIdQuery->expr()->eq('cob.parameter', $calendarObjectIdQuery->createNamedParameter($parameter, IQueryBuilder::PARAM_STR_ARRAY)));
1789
+
1790
+            $searchOr->add($parameterAnd);
1791
+        }
1792
+
1793
+        if ($calendarOr->count() === 0) {
1794
+            return [];
1795
+        }
1796
+        if ($searchOr->count() === 0) {
1797
+            return [];
1798
+        }
1799
+
1800
+        $calendarObjectIdQuery->selectDistinct('cob.objectid')
1801
+            ->from($this->dbObjectPropertiesTable, 'cob')
1802
+            ->leftJoin('cob', 'calendarobjects', 'co', $calendarObjectIdQuery->expr()->eq('co.id', 'cob.objectid'))
1803
+            ->andWhere($calendarObjectIdQuery->expr()->in('co.componenttype', $calendarObjectIdQuery->createNamedParameter($componentTypes, IQueryBuilder::PARAM_STR_ARRAY)))
1804
+            ->andWhere($calendarOr)
1805
+            ->andWhere($searchOr);
1806
+
1807
+        if ('' !== $pattern) {
1808
+            if (!$escapePattern) {
1809
+                $calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter($pattern)));
1810
+            } else {
1811
+                $calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
1812
+            }
1813
+        }
1814
+
1815
+        if (isset($options['limit'])) {
1816
+            $calendarObjectIdQuery->setMaxResults($options['limit']);
1817
+        }
1818
+        if (isset($options['offset'])) {
1819
+            $calendarObjectIdQuery->setFirstResult($options['offset']);
1820
+        }
1821
+
1822
+        $result = $calendarObjectIdQuery->execute();
1823
+        $matches = $result->fetchAll();
1824
+        $result->closeCursor();
1825
+        $matches = array_map(static function (array $match):int {
1826
+            return (int) $match['objectid'];
1827
+        }, $matches);
1828
+
1829
+        $query = $this->db->getQueryBuilder();
1830
+        $query->select('calendardata', 'uri', 'calendarid', 'calendartype')
1831
+            ->from('calendarobjects')
1832
+            ->where($query->expr()->in('id', $query->createNamedParameter($matches, IQueryBuilder::PARAM_INT_ARRAY)));
1833
+
1834
+        $result = $query->execute();
1835
+        $calendarObjects = $result->fetchAll();
1836
+        $result->closeCursor();
1837
+
1838
+        return array_map(function (array $array): array {
1839
+            $array['calendarid'] = (int)$array['calendarid'];
1840
+            $array['calendartype'] = (int)$array['calendartype'];
1841
+            $array['calendardata'] = $this->readBlob($array['calendardata']);
1842
+
1843
+            return $array;
1844
+        }, $calendarObjects);
1845
+    }
1846
+
1847
+    /**
1848
+     * Searches through all of a users calendars and calendar objects to find
1849
+     * an object with a specific UID.
1850
+     *
1851
+     * This method should return the path to this object, relative to the
1852
+     * calendar home, so this path usually only contains two parts:
1853
+     *
1854
+     * calendarpath/objectpath.ics
1855
+     *
1856
+     * If the uid is not found, return null.
1857
+     *
1858
+     * This method should only consider * objects that the principal owns, so
1859
+     * any calendars owned by other principals that also appear in this
1860
+     * collection should be ignored.
1861
+     *
1862
+     * @param string $principalUri
1863
+     * @param string $uid
1864
+     * @return string|null
1865
+     */
1866
+    public function getCalendarObjectByUID($principalUri, $uid) {
1867
+        $query = $this->db->getQueryBuilder();
1868
+        $query->selectAlias('c.uri', 'calendaruri')->selectAlias('co.uri', 'objecturi')
1869
+            ->from('calendarobjects', 'co')
1870
+            ->leftJoin('co', 'calendars', 'c', $query->expr()->eq('co.calendarid', 'c.id'))
1871
+            ->where($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri)))
1872
+            ->andWhere($query->expr()->eq('co.uid', $query->createNamedParameter($uid)));
1873
+
1874
+        $stmt = $query->execute();
1875
+
1876
+        if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1877
+            return $row['calendaruri'] . '/' . $row['objecturi'];
1878
+        }
1879
+
1880
+        return null;
1881
+    }
1882
+
1883
+    /**
1884
+     * The getChanges method returns all the changes that have happened, since
1885
+     * the specified syncToken in the specified calendar.
1886
+     *
1887
+     * This function should return an array, such as the following:
1888
+     *
1889
+     * [
1890
+     *   'syncToken' => 'The current synctoken',
1891
+     *   'added'   => [
1892
+     *      'new.txt',
1893
+     *   ],
1894
+     *   'modified'   => [
1895
+     *      'modified.txt',
1896
+     *   ],
1897
+     *   'deleted' => [
1898
+     *      'foo.php.bak',
1899
+     *      'old.txt'
1900
+     *   ]
1901
+     * );
1902
+     *
1903
+     * The returned syncToken property should reflect the *current* syncToken
1904
+     * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
1905
+     * property This is * needed here too, to ensure the operation is atomic.
1906
+     *
1907
+     * If the $syncToken argument is specified as null, this is an initial
1908
+     * sync, and all members should be reported.
1909
+     *
1910
+     * The modified property is an array of nodenames that have changed since
1911
+     * the last token.
1912
+     *
1913
+     * The deleted property is an array with nodenames, that have been deleted
1914
+     * from collection.
1915
+     *
1916
+     * The $syncLevel argument is basically the 'depth' of the report. If it's
1917
+     * 1, you only have to report changes that happened only directly in
1918
+     * immediate descendants. If it's 2, it should also include changes from
1919
+     * the nodes below the child collections. (grandchildren)
1920
+     *
1921
+     * The $limit argument allows a client to specify how many results should
1922
+     * be returned at most. If the limit is not specified, it should be treated
1923
+     * as infinite.
1924
+     *
1925
+     * If the limit (infinite or not) is higher than you're willing to return,
1926
+     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
1927
+     *
1928
+     * If the syncToken is expired (due to data cleanup) or unknown, you must
1929
+     * return null.
1930
+     *
1931
+     * The limit is 'suggestive'. You are free to ignore it.
1932
+     *
1933
+     * @param string $calendarId
1934
+     * @param string $syncToken
1935
+     * @param int $syncLevel
1936
+     * @param int $limit
1937
+     * @param int $calendarType
1938
+     * @return array
1939
+     */
1940
+    public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1941
+        // Current synctoken
1942
+        $stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*calendars` WHERE `id` = ?');
1943
+        $stmt->execute([ $calendarId ]);
1944
+        $currentToken = $stmt->fetchColumn(0);
1945
+
1946
+        if (is_null($currentToken)) {
1947
+            return null;
1948
+        }
1949
+
1950
+        $result = [
1951
+            'syncToken' => $currentToken,
1952
+            'added' => [],
1953
+            'modified' => [],
1954
+            'deleted' => [],
1955
+        ];
1956
+
1957
+        if ($syncToken) {
1958
+            $query = "SELECT `uri`, `operation` FROM `*PREFIX*calendarchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `calendarid` = ? AND `calendartype` = ? ORDER BY `synctoken`";
1959
+            if ($limit > 0) {
1960
+                $query .= " LIMIT " . (int)$limit;
1961
+            }
1962
+
1963
+            // Fetching all changes
1964
+            $stmt = $this->db->prepare($query);
1965
+            $stmt->execute([$syncToken, $currentToken, $calendarId, $calendarType]);
1966
+
1967
+            $changes = [];
1968
+
1969
+            // This loop ensures that any duplicates are overwritten, only the
1970
+            // last change on a node is relevant.
1971
+            while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1972
+                $changes[$row['uri']] = $row['operation'];
1973
+            }
1974
+
1975
+            foreach ($changes as $uri => $operation) {
1976
+                switch ($operation) {
1977
+                    case 1:
1978
+                        $result['added'][] = $uri;
1979
+                        break;
1980
+                    case 2:
1981
+                        $result['modified'][] = $uri;
1982
+                        break;
1983
+                    case 3:
1984
+                        $result['deleted'][] = $uri;
1985
+                        break;
1986
+                }
1987
+            }
1988
+        } else {
1989
+            // No synctoken supplied, this is the initial sync.
1990
+            $query = "SELECT `uri` FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `calendartype` = ?";
1991
+            $stmt = $this->db->prepare($query);
1992
+            $stmt->execute([$calendarId, $calendarType]);
1993
+
1994
+            $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
1995
+        }
1996
+        return $result;
1997
+    }
1998
+
1999
+    /**
2000
+     * Returns a list of subscriptions for a principal.
2001
+     *
2002
+     * Every subscription is an array with the following keys:
2003
+     *  * id, a unique id that will be used by other functions to modify the
2004
+     *    subscription. This can be the same as the uri or a database key.
2005
+     *  * uri. This is just the 'base uri' or 'filename' of the subscription.
2006
+     *  * principaluri. The owner of the subscription. Almost always the same as
2007
+     *    principalUri passed to this method.
2008
+     *
2009
+     * Furthermore, all the subscription info must be returned too:
2010
+     *
2011
+     * 1. {DAV:}displayname
2012
+     * 2. {http://apple.com/ns/ical/}refreshrate
2013
+     * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
2014
+     *    should not be stripped).
2015
+     * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
2016
+     *    should not be stripped).
2017
+     * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
2018
+     *    attachments should not be stripped).
2019
+     * 6. {http://calendarserver.org/ns/}source (Must be a
2020
+     *     Sabre\DAV\Property\Href).
2021
+     * 7. {http://apple.com/ns/ical/}calendar-color
2022
+     * 8. {http://apple.com/ns/ical/}calendar-order
2023
+     * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
2024
+     *    (should just be an instance of
2025
+     *    Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
2026
+     *    default components).
2027
+     *
2028
+     * @param string $principalUri
2029
+     * @return array
2030
+     */
2031
+    public function getSubscriptionsForUser($principalUri) {
2032
+        $fields = array_values($this->subscriptionPropertyMap);
2033
+        $fields[] = 'id';
2034
+        $fields[] = 'uri';
2035
+        $fields[] = 'source';
2036
+        $fields[] = 'principaluri';
2037
+        $fields[] = 'lastmodified';
2038
+        $fields[] = 'synctoken';
2039
+
2040
+        $query = $this->db->getQueryBuilder();
2041
+        $query->select($fields)
2042
+            ->from('calendarsubscriptions')
2043
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2044
+            ->orderBy('calendarorder', 'asc');
2045
+        $stmt = $query->execute();
2046
+
2047
+        $subscriptions = [];
2048
+        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
2049
+            $subscription = [
2050
+                'id' => $row['id'],
2051
+                'uri' => $row['uri'],
2052
+                'principaluri' => $row['principaluri'],
2053
+                'source' => $row['source'],
2054
+                'lastmodified' => $row['lastmodified'],
2055
+
2056
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
2057
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
2058
+            ];
2059
+
2060
+            foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
2061
+                if (!is_null($row[$dbName])) {
2062
+                    $subscription[$xmlName] = $row[$dbName];
2063
+                }
2064
+            }
2065
+
2066
+            $subscriptions[] = $subscription;
2067
+        }
2068
+
2069
+        return $subscriptions;
2070
+    }
2071
+
2072
+    /**
2073
+     * Creates a new subscription for a principal.
2074
+     *
2075
+     * If the creation was a success, an id must be returned that can be used to reference
2076
+     * this subscription in other methods, such as updateSubscription.
2077
+     *
2078
+     * @param string $principalUri
2079
+     * @param string $uri
2080
+     * @param array $properties
2081
+     * @return mixed
2082
+     */
2083
+    public function createSubscription($principalUri, $uri, array $properties) {
2084
+        if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
2085
+            throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
2086
+        }
2087
+
2088
+        $values = [
2089
+            'principaluri' => $principalUri,
2090
+            'uri' => $uri,
2091
+            'source' => $properties['{http://calendarserver.org/ns/}source']->getHref(),
2092
+            'lastmodified' => time(),
2093
+        ];
2094
+
2095
+        $propertiesBoolean = ['striptodos', 'stripalarms', 'stripattachments'];
2096
+
2097
+        foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
2098
+            if (array_key_exists($xmlName, $properties)) {
2099
+                $values[$dbName] = $properties[$xmlName];
2100
+                if (in_array($dbName, $propertiesBoolean)) {
2101
+                    $values[$dbName] = true;
2102
+                }
2103
+            }
2104
+        }
2105
+
2106
+        $valuesToInsert = [];
2107
+
2108
+        $query = $this->db->getQueryBuilder();
2109
+
2110
+        foreach (array_keys($values) as $name) {
2111
+            $valuesToInsert[$name] = $query->createNamedParameter($values[$name]);
2112
+        }
2113
+
2114
+        $query->insert('calendarsubscriptions')
2115
+            ->values($valuesToInsert)
2116
+            ->execute();
2117
+
2118
+        $subscriptionId = $this->db->lastInsertId('*PREFIX*calendarsubscriptions');
2119
+
2120
+        $subscriptionRow = $this->getSubscriptionById($subscriptionId);
2121
+        $this->dispatcher->dispatchTyped(new SubscriptionCreatedEvent((int)$subscriptionId, $subscriptionRow));
2122
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createSubscription', new GenericEvent(
2123
+            '\OCA\DAV\CalDAV\CalDavBackend::createSubscription',
2124
+            [
2125
+                'subscriptionId' => $subscriptionId,
2126
+                'subscriptionData' => $subscriptionRow,
2127
+            ]));
2128
+
2129
+        return $subscriptionId;
2130
+    }
2131
+
2132
+    /**
2133
+     * Updates a subscription
2134
+     *
2135
+     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
2136
+     * To do the actual updates, you must tell this object which properties
2137
+     * you're going to process with the handle() method.
2138
+     *
2139
+     * Calling the handle method is like telling the PropPatch object "I
2140
+     * promise I can handle updating this property".
2141
+     *
2142
+     * Read the PropPatch documentation for more info and examples.
2143
+     *
2144
+     * @param mixed $subscriptionId
2145
+     * @param PropPatch $propPatch
2146
+     * @return void
2147
+     */
2148
+    public function updateSubscription($subscriptionId, PropPatch $propPatch) {
2149
+        $supportedProperties = array_keys($this->subscriptionPropertyMap);
2150
+        $supportedProperties[] = '{http://calendarserver.org/ns/}source';
2151
+
2152
+        $propPatch->handle($supportedProperties, function ($mutations) use ($subscriptionId) {
2153
+            $newValues = [];
2154
+
2155
+            foreach ($mutations as $propertyName => $propertyValue) {
2156
+                if ($propertyName === '{http://calendarserver.org/ns/}source') {
2157
+                    $newValues['source'] = $propertyValue->getHref();
2158
+                } else {
2159
+                    $fieldName = $this->subscriptionPropertyMap[$propertyName];
2160
+                    $newValues[$fieldName] = $propertyValue;
2161
+                }
2162
+            }
2163
+
2164
+            $query = $this->db->getQueryBuilder();
2165
+            $query->update('calendarsubscriptions')
2166
+                ->set('lastmodified', $query->createNamedParameter(time()));
2167
+            foreach ($newValues as $fieldName => $value) {
2168
+                $query->set($fieldName, $query->createNamedParameter($value));
2169
+            }
2170
+            $query->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
2171
+                ->execute();
2172
+
2173
+            $subscriptionRow = $this->getSubscriptionById($subscriptionId);
2174
+            $this->dispatcher->dispatchTyped(new SubscriptionUpdatedEvent((int)$subscriptionId, $subscriptionRow, [], $mutations));
2175
+            $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateSubscription', new GenericEvent(
2176
+                '\OCA\DAV\CalDAV\CalDavBackend::updateSubscription',
2177
+                [
2178
+                    'subscriptionId' => $subscriptionId,
2179
+                    'subscriptionData' => $subscriptionRow,
2180
+                    'propertyMutations' => $mutations,
2181
+                ]));
2182
+
2183
+            return true;
2184
+        });
2185
+    }
2186
+
2187
+    /**
2188
+     * Deletes a subscription.
2189
+     *
2190
+     * @param mixed $subscriptionId
2191
+     * @return void
2192
+     */
2193
+    public function deleteSubscription($subscriptionId) {
2194
+        $subscriptionRow = $this->getSubscriptionById($subscriptionId);
2195
+
2196
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription', new GenericEvent(
2197
+            '\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription',
2198
+            [
2199
+                'subscriptionId' => $subscriptionId,
2200
+                'subscriptionData' => $this->getSubscriptionById($subscriptionId),
2201
+            ]));
2202
+
2203
+        $query = $this->db->getQueryBuilder();
2204
+        $query->delete('calendarsubscriptions')
2205
+            ->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
2206
+            ->execute();
2207
+
2208
+        $query = $this->db->getQueryBuilder();
2209
+        $query->delete('calendarobjects')
2210
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2211
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2212
+            ->execute();
2213
+
2214
+        $query->delete('calendarchanges')
2215
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2216
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2217
+            ->execute();
2218
+
2219
+        $query->delete($this->dbObjectPropertiesTable)
2220
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2221
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2222
+            ->execute();
2223
+
2224
+        if ($subscriptionRow) {
2225
+            $this->dispatcher->dispatchTyped(new SubscriptionDeletedEvent((int)$subscriptionId, $subscriptionRow, []));
2226
+        }
2227
+    }
2228
+
2229
+    /**
2230
+     * Returns a single scheduling object for the inbox collection.
2231
+     *
2232
+     * The returned array should contain the following elements:
2233
+     *   * uri - A unique basename for the object. This will be used to
2234
+     *           construct a full uri.
2235
+     *   * calendardata - The iCalendar object
2236
+     *   * lastmodified - The last modification date. Can be an int for a unix
2237
+     *                    timestamp, or a PHP DateTime object.
2238
+     *   * etag - A unique token that must change if the object changed.
2239
+     *   * size - The size of the object, in bytes.
2240
+     *
2241
+     * @param string $principalUri
2242
+     * @param string $objectUri
2243
+     * @return array
2244
+     */
2245
+    public function getSchedulingObject($principalUri, $objectUri) {
2246
+        $query = $this->db->getQueryBuilder();
2247
+        $stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
2248
+            ->from('schedulingobjects')
2249
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2250
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
2251
+            ->execute();
2252
+
2253
+        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
2254
+
2255
+        if (!$row) {
2256
+            return null;
2257
+        }
2258
+
2259
+        return [
2260
+            'uri' => $row['uri'],
2261
+            'calendardata' => $row['calendardata'],
2262
+            'lastmodified' => $row['lastmodified'],
2263
+            'etag' => '"' . $row['etag'] . '"',
2264
+            'size' => (int)$row['size'],
2265
+        ];
2266
+    }
2267
+
2268
+    /**
2269
+     * Returns all scheduling objects for the inbox collection.
2270
+     *
2271
+     * These objects should be returned as an array. Every item in the array
2272
+     * should follow the same structure as returned from getSchedulingObject.
2273
+     *
2274
+     * The main difference is that 'calendardata' is optional.
2275
+     *
2276
+     * @param string $principalUri
2277
+     * @return array
2278
+     */
2279
+    public function getSchedulingObjects($principalUri) {
2280
+        $query = $this->db->getQueryBuilder();
2281
+        $stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
2282
+                ->from('schedulingobjects')
2283
+                ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2284
+                ->execute();
2285
+
2286
+        $result = [];
2287
+        foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
2288
+            $result[] = [
2289
+                'calendardata' => $row['calendardata'],
2290
+                'uri' => $row['uri'],
2291
+                'lastmodified' => $row['lastmodified'],
2292
+                'etag' => '"' . $row['etag'] . '"',
2293
+                'size' => (int)$row['size'],
2294
+            ];
2295
+        }
2296
+
2297
+        return $result;
2298
+    }
2299
+
2300
+    /**
2301
+     * Deletes a scheduling object from the inbox collection.
2302
+     *
2303
+     * @param string $principalUri
2304
+     * @param string $objectUri
2305
+     * @return void
2306
+     */
2307
+    public function deleteSchedulingObject($principalUri, $objectUri) {
2308
+        $query = $this->db->getQueryBuilder();
2309
+        $query->delete('schedulingobjects')
2310
+                ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2311
+                ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
2312
+                ->execute();
2313
+    }
2314
+
2315
+    /**
2316
+     * Creates a new scheduling object. This should land in a users' inbox.
2317
+     *
2318
+     * @param string $principalUri
2319
+     * @param string $objectUri
2320
+     * @param string $objectData
2321
+     * @return void
2322
+     */
2323
+    public function createSchedulingObject($principalUri, $objectUri, $objectData) {
2324
+        $query = $this->db->getQueryBuilder();
2325
+        $query->insert('schedulingobjects')
2326
+            ->values([
2327
+                'principaluri' => $query->createNamedParameter($principalUri),
2328
+                'calendardata' => $query->createNamedParameter($objectData, IQueryBuilder::PARAM_LOB),
2329
+                'uri' => $query->createNamedParameter($objectUri),
2330
+                'lastmodified' => $query->createNamedParameter(time()),
2331
+                'etag' => $query->createNamedParameter(md5($objectData)),
2332
+                'size' => $query->createNamedParameter(strlen($objectData))
2333
+            ])
2334
+            ->execute();
2335
+    }
2336
+
2337
+    /**
2338
+     * Adds a change record to the calendarchanges table.
2339
+     *
2340
+     * @param mixed $calendarId
2341
+     * @param string $objectUri
2342
+     * @param int $operation 1 = add, 2 = modify, 3 = delete.
2343
+     * @param int $calendarType
2344
+     * @return void
2345
+     */
2346
+    protected function addChange($calendarId, $objectUri, $operation, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2347
+        $table = $calendarType === self::CALENDAR_TYPE_CALENDAR ? 'calendars': 'calendarsubscriptions';
2348
+
2349
+        $query = $this->db->getQueryBuilder();
2350
+        $query->select('synctoken')
2351
+            ->from($table)
2352
+            ->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
2353
+        $result = $query->execute();
2354
+        $syncToken = (int)$result->fetchColumn();
2355
+        $result->closeCursor();
2356
+
2357
+        $query = $this->db->getQueryBuilder();
2358
+        $query->insert('calendarchanges')
2359
+            ->values([
2360
+                'uri' => $query->createNamedParameter($objectUri),
2361
+                'synctoken' => $query->createNamedParameter($syncToken),
2362
+                'calendarid' => $query->createNamedParameter($calendarId),
2363
+                'operation' => $query->createNamedParameter($operation),
2364
+                'calendartype' => $query->createNamedParameter($calendarType),
2365
+            ])
2366
+            ->execute();
2367
+
2368
+        $stmt = $this->db->prepare("UPDATE `*PREFIX*$table` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?");
2369
+        $stmt->execute([
2370
+            $calendarId
2371
+        ]);
2372
+    }
2373
+
2374
+    /**
2375
+     * Parses some information from calendar objects, used for optimized
2376
+     * calendar-queries.
2377
+     *
2378
+     * Returns an array with the following keys:
2379
+     *   * etag - An md5 checksum of the object without the quotes.
2380
+     *   * size - Size of the object in bytes
2381
+     *   * componentType - VEVENT, VTODO or VJOURNAL
2382
+     *   * firstOccurence
2383
+     *   * lastOccurence
2384
+     *   * uid - value of the UID property
2385
+     *
2386
+     * @param string $calendarData
2387
+     * @return array
2388
+     */
2389
+    public function getDenormalizedData($calendarData) {
2390
+        $vObject = Reader::read($calendarData);
2391
+        $vEvents = [];
2392
+        $componentType = null;
2393
+        $component = null;
2394
+        $firstOccurrence = null;
2395
+        $lastOccurrence = null;
2396
+        $uid = null;
2397
+        $classification = self::CLASSIFICATION_PUBLIC;
2398
+        $hasDTSTART = false;
2399
+        foreach ($vObject->getComponents() as $component) {
2400
+            if ($component->name !== 'VTIMEZONE') {
2401
+                // Finding all VEVENTs, and track them
2402
+                if ($component->name === 'VEVENT') {
2403
+                    array_push($vEvents, $component);
2404
+                    if ($component->DTSTART) {
2405
+                        $hasDTSTART = true;
2406
+                    }
2407
+                }
2408
+                // Track first component type and uid
2409
+                if ($uid === null) {
2410
+                    $componentType = $component->name;
2411
+                    $uid = (string)$component->UID;
2412
+                }
2413
+            }
2414
+        }
2415
+        if (!$componentType) {
2416
+            throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
2417
+        }
2418
+
2419
+        if ($hasDTSTART) {
2420
+            $component = $vEvents[0];
2421
+
2422
+            // Finding the last occurrence is a bit harder
2423
+            if (!isset($component->RRULE) && count($vEvents) === 1) {
2424
+                $firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp();
2425
+                if (isset($component->DTEND)) {
2426
+                    $lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp();
2427
+                } elseif (isset($component->DURATION)) {
2428
+                    $endDate = clone $component->DTSTART->getDateTime();
2429
+                    $endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
2430
+                    $lastOccurrence = $endDate->getTimeStamp();
2431
+                } elseif (!$component->DTSTART->hasTime()) {
2432
+                    $endDate = clone $component->DTSTART->getDateTime();
2433
+                    $endDate->modify('+1 day');
2434
+                    $lastOccurrence = $endDate->getTimeStamp();
2435
+                } else {
2436
+                    $lastOccurrence = $firstOccurrence;
2437
+                }
2438
+            } else {
2439
+                $it = new EventIterator($vEvents);
2440
+                $maxDate = new DateTime(self::MAX_DATE);
2441
+                $firstOccurrence = $it->getDtStart()->getTimestamp();
2442
+                if ($it->isInfinite()) {
2443
+                    $lastOccurrence = $maxDate->getTimestamp();
2444
+                } else {
2445
+                    $end = $it->getDtEnd();
2446
+                    while ($it->valid() && $end < $maxDate) {
2447
+                        $end = $it->getDtEnd();
2448
+                        $it->next();
2449
+                    }
2450
+                    $lastOccurrence = $end->getTimestamp();
2451
+                }
2452
+            }
2453
+        }
2454
+
2455
+        if ($component->CLASS) {
2456
+            $classification = CalDavBackend::CLASSIFICATION_PRIVATE;
2457
+            switch ($component->CLASS->getValue()) {
2458
+                case 'PUBLIC':
2459
+                    $classification = CalDavBackend::CLASSIFICATION_PUBLIC;
2460
+                    break;
2461
+                case 'CONFIDENTIAL':
2462
+                    $classification = CalDavBackend::CLASSIFICATION_CONFIDENTIAL;
2463
+                    break;
2464
+            }
2465
+        }
2466
+        return [
2467
+            'etag' => md5($calendarData),
2468
+            'size' => strlen($calendarData),
2469
+            'componentType' => $componentType,
2470
+            'firstOccurence' => is_null($firstOccurrence) ? null : max(0, $firstOccurrence),
2471
+            'lastOccurence' => $lastOccurrence,
2472
+            'uid' => $uid,
2473
+            'classification' => $classification
2474
+        ];
2475
+    }
2476
+
2477
+    /**
2478
+     * @param $cardData
2479
+     * @return bool|string
2480
+     */
2481
+    private function readBlob($cardData) {
2482
+        if (is_resource($cardData)) {
2483
+            return stream_get_contents($cardData);
2484
+        }
2485
+
2486
+        return $cardData;
2487
+    }
2488
+
2489
+    /**
2490
+     * @param IShareable $shareable
2491
+     * @param array $add
2492
+     * @param array $remove
2493
+     */
2494
+    public function updateShares($shareable, $add, $remove) {
2495
+        $calendarId = $shareable->getResourceId();
2496
+        $calendarRow = $this->getCalendarById($calendarId);
2497
+        $oldShares = $this->getShares($calendarId);
2498
+
2499
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateShares', new GenericEvent(
2500
+            '\OCA\DAV\CalDAV\CalDavBackend::updateShares',
2501
+            [
2502
+                'calendarId' => $calendarId,
2503
+                'calendarData' => $calendarRow,
2504
+                'shares' => $oldShares,
2505
+                'add' => $add,
2506
+                'remove' => $remove,
2507
+            ]));
2508
+        $this->calendarSharingBackend->updateShares($shareable, $add, $remove);
2509
+
2510
+        $this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent((int)$calendarId, $calendarRow, $oldShares, $add, $remove));
2511
+    }
2512
+
2513
+    /**
2514
+     * @param int $resourceId
2515
+     * @param int $calendarType
2516
+     * @return array
2517
+     */
2518
+    public function getShares($resourceId, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2519
+        return $this->calendarSharingBackend->getShares($resourceId);
2520
+    }
2521
+
2522
+    /**
2523
+     * @param boolean $value
2524
+     * @param \OCA\DAV\CalDAV\Calendar $calendar
2525
+     * @return string|null
2526
+     */
2527
+    public function setPublishStatus($value, $calendar) {
2528
+        $calendarId = $calendar->getResourceId();
2529
+        $calendarData = $this->getCalendarById($calendarId);
2530
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::publishCalendar', new GenericEvent(
2531
+            '\OCA\DAV\CalDAV\CalDavBackend::updateShares',
2532
+            [
2533
+                'calendarId' => $calendarId,
2534
+                'calendarData' => $calendarData,
2535
+                'public' => $value,
2536
+            ]));
2537
+
2538
+        $query = $this->db->getQueryBuilder();
2539
+        if ($value) {
2540
+            $publicUri = $this->random->generate(16, ISecureRandom::CHAR_HUMAN_READABLE);
2541
+            $query->insert('dav_shares')
2542
+                ->values([
2543
+                    'principaluri' => $query->createNamedParameter($calendar->getPrincipalURI()),
2544
+                    'type' => $query->createNamedParameter('calendar'),
2545
+                    'access' => $query->createNamedParameter(self::ACCESS_PUBLIC),
2546
+                    'resourceid' => $query->createNamedParameter($calendar->getResourceId()),
2547
+                    'publicuri' => $query->createNamedParameter($publicUri)
2548
+                ]);
2549
+            $query->execute();
2550
+
2551
+            $this->dispatcher->dispatchTyped(new CalendarPublishedEvent((int)$calendarId, $calendarData, $publicUri));
2552
+            return $publicUri;
2553
+        }
2554
+        $query->delete('dav_shares')
2555
+            ->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
2556
+            ->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)));
2557
+        $query->execute();
2558
+
2559
+        $this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent((int)$calendarId, $calendarData));
2560
+        return null;
2561
+    }
2562
+
2563
+    /**
2564
+     * @param \OCA\DAV\CalDAV\Calendar $calendar
2565
+     * @return mixed
2566
+     */
2567
+    public function getPublishStatus($calendar) {
2568
+        $query = $this->db->getQueryBuilder();
2569
+        $result = $query->select('publicuri')
2570
+            ->from('dav_shares')
2571
+            ->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
2572
+            ->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
2573
+            ->execute();
2574
+
2575
+        $row = $result->fetch();
2576
+        $result->closeCursor();
2577
+        return $row ? reset($row) : false;
2578
+    }
2579
+
2580
+    /**
2581
+     * @param int $resourceId
2582
+     * @param array $acl
2583
+     * @return array
2584
+     */
2585
+    public function applyShareAcl($resourceId, $acl) {
2586
+        return $this->calendarSharingBackend->applyShareAcl($resourceId, $acl);
2587
+    }
2588
+
2589
+
2590
+
2591
+    /**
2592
+     * update properties table
2593
+     *
2594
+     * @param int $calendarId
2595
+     * @param string $objectUri
2596
+     * @param string $calendarData
2597
+     * @param int $calendarType
2598
+     */
2599
+    public function updateProperties($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2600
+        $objectId = $this->getCalendarObjectId($calendarId, $objectUri, $calendarType);
2601
+
2602
+        try {
2603
+            $vCalendar = $this->readCalendarData($calendarData);
2604
+        } catch (\Exception $ex) {
2605
+            return;
2606
+        }
2607
+
2608
+        $this->purgeProperties($calendarId, $objectId);
2609
+
2610
+        $query = $this->db->getQueryBuilder();
2611
+        $query->insert($this->dbObjectPropertiesTable)
2612
+            ->values(
2613
+                [
2614
+                    'calendarid' => $query->createNamedParameter($calendarId),
2615
+                    'calendartype' => $query->createNamedParameter($calendarType),
2616
+                    'objectid' => $query->createNamedParameter($objectId),
2617
+                    'name' => $query->createParameter('name'),
2618
+                    'parameter' => $query->createParameter('parameter'),
2619
+                    'value' => $query->createParameter('value'),
2620
+                ]
2621
+            );
2622
+
2623
+        $indexComponents = ['VEVENT', 'VJOURNAL', 'VTODO'];
2624
+        foreach ($vCalendar->getComponents() as $component) {
2625
+            if (!in_array($component->name, $indexComponents)) {
2626
+                continue;
2627
+            }
2628
+
2629
+            foreach ($component->children() as $property) {
2630
+                if (in_array($property->name, self::$indexProperties)) {
2631
+                    $value = $property->getValue();
2632
+                    // is this a shitty db?
2633
+                    if (!$this->db->supports4ByteText()) {
2634
+                        $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
2635
+                    }
2636
+                    $value = mb_substr($value, 0, 254);
2637
+
2638
+                    $query->setParameter('name', $property->name);
2639
+                    $query->setParameter('parameter', null);
2640
+                    $query->setParameter('value', $value);
2641
+                    $query->execute();
2642
+                }
2643
+
2644
+                if (array_key_exists($property->name, self::$indexParameters)) {
2645
+                    $parameters = $property->parameters();
2646
+                    $indexedParametersForProperty = self::$indexParameters[$property->name];
2647
+
2648
+                    foreach ($parameters as $key => $value) {
2649
+                        if (in_array($key, $indexedParametersForProperty)) {
2650
+                            // is this a shitty db?
2651
+                            if ($this->db->supports4ByteText()) {
2652
+                                $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
2653
+                            }
2654
+
2655
+                            $query->setParameter('name', $property->name);
2656
+                            $query->setParameter('parameter', mb_substr($key, 0, 254));
2657
+                            $query->setParameter('value', mb_substr($value, 0, 254));
2658
+                            $query->execute();
2659
+                        }
2660
+                    }
2661
+                }
2662
+            }
2663
+        }
2664
+    }
2665
+
2666
+    /**
2667
+     * deletes all birthday calendars
2668
+     */
2669
+    public function deleteAllBirthdayCalendars() {
2670
+        $query = $this->db->getQueryBuilder();
2671
+        $result = $query->select(['id'])->from('calendars')
2672
+            ->where($query->expr()->eq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)))
2673
+            ->execute();
2674
+
2675
+        $ids = $result->fetchAll();
2676
+        foreach ($ids as $id) {
2677
+            $this->deleteCalendar($id['id']);
2678
+        }
2679
+    }
2680
+
2681
+    /**
2682
+     * @param $subscriptionId
2683
+     */
2684
+    public function purgeAllCachedEventsForSubscription($subscriptionId) {
2685
+        $query = $this->db->getQueryBuilder();
2686
+        $query->select('uri')
2687
+            ->from('calendarobjects')
2688
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2689
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
2690
+        $stmt = $query->execute();
2691
+
2692
+        $uris = [];
2693
+        foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
2694
+            $uris[] = $row['uri'];
2695
+        }
2696
+        $stmt->closeCursor();
2697
+
2698
+        $query = $this->db->getQueryBuilder();
2699
+        $query->delete('calendarobjects')
2700
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2701
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2702
+            ->execute();
2703
+
2704
+        $query->delete('calendarchanges')
2705
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2706
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2707
+            ->execute();
2708
+
2709
+        $query->delete($this->dbObjectPropertiesTable)
2710
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2711
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2712
+            ->execute();
2713
+
2714
+        foreach ($uris as $uri) {
2715
+            $this->addChange($subscriptionId, $uri, 3, self::CALENDAR_TYPE_SUBSCRIPTION);
2716
+        }
2717
+    }
2718
+
2719
+    /**
2720
+     * Move a calendar from one user to another
2721
+     *
2722
+     * @param string $uriName
2723
+     * @param string $uriOrigin
2724
+     * @param string $uriDestination
2725
+     */
2726
+    public function moveCalendar($uriName, $uriOrigin, $uriDestination) {
2727
+        $query = $this->db->getQueryBuilder();
2728
+        $query->update('calendars')
2729
+            ->set('principaluri', $query->createNamedParameter($uriDestination))
2730
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($uriOrigin)))
2731
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($uriName)))
2732
+            ->execute();
2733
+    }
2734
+
2735
+    /**
2736
+     * read VCalendar data into a VCalendar object
2737
+     *
2738
+     * @param string $objectData
2739
+     * @return VCalendar
2740
+     */
2741
+    protected function readCalendarData($objectData) {
2742
+        return Reader::read($objectData);
2743
+    }
2744
+
2745
+    /**
2746
+     * delete all properties from a given calendar object
2747
+     *
2748
+     * @param int $calendarId
2749
+     * @param int $objectId
2750
+     */
2751
+    protected function purgeProperties($calendarId, $objectId) {
2752
+        $query = $this->db->getQueryBuilder();
2753
+        $query->delete($this->dbObjectPropertiesTable)
2754
+            ->where($query->expr()->eq('objectid', $query->createNamedParameter($objectId)))
2755
+            ->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
2756
+        $query->execute();
2757
+    }
2758
+
2759
+    /**
2760
+     * get ID from a given calendar object
2761
+     *
2762
+     * @param int $calendarId
2763
+     * @param string $uri
2764
+     * @param int $calendarType
2765
+     * @return int
2766
+     */
2767
+    protected function getCalendarObjectId($calendarId, $uri, $calendarType):int {
2768
+        $query = $this->db->getQueryBuilder();
2769
+        $query->select('id')
2770
+            ->from('calendarobjects')
2771
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
2772
+            ->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
2773
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
2774
+
2775
+        $result = $query->execute();
2776
+        $objectIds = $result->fetch();
2777
+        $result->closeCursor();
2778
+
2779
+        if (!isset($objectIds['id'])) {
2780
+            throw new \InvalidArgumentException('Calendarobject does not exists: ' . $uri);
2781
+        }
2782
+
2783
+        return (int)$objectIds['id'];
2784
+    }
2785
+
2786
+    /**
2787
+     * return legacy endpoint principal name to new principal name
2788
+     *
2789
+     * @param $principalUri
2790
+     * @param $toV2
2791
+     * @return string
2792
+     */
2793
+    private function convertPrincipal($principalUri, $toV2) {
2794
+        if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
2795
+            list(, $name) = Uri\split($principalUri);
2796
+            if ($toV2 === true) {
2797
+                return "principals/users/$name";
2798
+            }
2799
+            return "principals/$name";
2800
+        }
2801
+        return $principalUri;
2802
+    }
2803
+
2804
+    /**
2805
+     * adds information about an owner to the calendar data
2806
+     *
2807
+     * @param $calendarInfo
2808
+     */
2809
+    private function addOwnerPrincipal(&$calendarInfo) {
2810
+        $ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
2811
+        $displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
2812
+        if (isset($calendarInfo[$ownerPrincipalKey])) {
2813
+            $uri = $calendarInfo[$ownerPrincipalKey];
2814
+        } else {
2815
+            $uri = $calendarInfo['principaluri'];
2816
+        }
2817
+
2818
+        $principalInformation = $this->principalBackend->getPrincipalByPath($uri);
2819
+        if (isset($principalInformation['{DAV:}displayname'])) {
2820
+            $calendarInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
2821
+        }
2822
+    }
2823 2823
 }
Please login to merge, or discard this patch.
Spacing   +116 added lines, -116 removed lines patch added patch discarded remove patch
@@ -250,7 +250,7 @@  discard block
 block discarded – undo
250 250
 		}
251 251
 
252 252
 		$result = $query->execute();
253
-		$column = (int)$result->fetchColumn();
253
+		$column = (int) $result->fetchColumn();
254 254
 		$result->closeCursor();
255 255
 		return $column;
256 256
 	}
@@ -302,18 +302,18 @@  discard block
 block discarded – undo
302 302
 		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
303 303
 			$components = [];
304 304
 			if ($row['components']) {
305
-				$components = explode(',',$row['components']);
305
+				$components = explode(',', $row['components']);
306 306
 			}
307 307
 
308 308
 			$calendar = [
309 309
 				'id' => $row['id'],
310 310
 				'uri' => $row['uri'],
311 311
 				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
312
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
313
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
314
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
315
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
316
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
312
+				'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
313
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
314
+				'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
315
+				'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
316
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
317 317
 			];
318 318
 
319 319
 			foreach ($this->propertyMap as $xmlName => $dbName) {
@@ -333,7 +333,7 @@  discard block
 block discarded – undo
333 333
 		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
334 334
 		$principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
335 335
 
336
-		$principals = array_map(function ($principal) {
336
+		$principals = array_map(function($principal) {
337 337
 			return urldecode($principal);
338 338
 		}, $principals);
339 339
 		$principals[] = $principalUri;
@@ -356,7 +356,7 @@  discard block
 block discarded – undo
356 356
 			->setParameter('principaluri', $principals, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
357 357
 			->execute();
358 358
 
359
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
359
+		$readOnlyPropertyName = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}read-only';
360 360
 		while ($row = $result->fetch()) {
361 361
 			if ($row['principaluri'] === $principalUri) {
362 362
 				continue;
@@ -376,21 +376,21 @@  discard block
 block discarded – undo
376 376
 			}
377 377
 
378 378
 			list(, $name) = Uri\split($row['principaluri']);
379
-			$uri = $row['uri'] . '_shared_by_' . $name;
380
-			$row['displayname'] = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
379
+			$uri = $row['uri'].'_shared_by_'.$name;
380
+			$row['displayname'] = $row['displayname'].' ('.$this->getUserDisplayName($name).')';
381 381
 			$components = [];
382 382
 			if ($row['components']) {
383
-				$components = explode(',',$row['components']);
383
+				$components = explode(',', $row['components']);
384 384
 			}
385 385
 			$calendar = [
386 386
 				'id' => $row['id'],
387 387
 				'uri' => $uri,
388 388
 				'principaluri' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
389
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
390
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
391
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
392
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp('transparent'),
393
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
389
+				'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
390
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
391
+				'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
392
+				'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp('transparent'),
393
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
394 394
 				$readOnlyPropertyName => $readOnly,
395 395
 			];
396 396
 
@@ -430,16 +430,16 @@  discard block
 block discarded – undo
430 430
 		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
431 431
 			$components = [];
432 432
 			if ($row['components']) {
433
-				$components = explode(',',$row['components']);
433
+				$components = explode(',', $row['components']);
434 434
 			}
435 435
 			$calendar = [
436 436
 				'id' => $row['id'],
437 437
 				'uri' => $row['uri'],
438 438
 				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
439
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
440
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
441
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
442
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
439
+				'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
440
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
441
+				'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
442
+				'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
443 443
 			];
444 444
 			foreach ($this->propertyMap as $xmlName => $dbName) {
445 445
 				$calendar[$xmlName] = $row[$dbName];
@@ -498,22 +498,22 @@  discard block
 block discarded – undo
498 498
 
499 499
 		while ($row = $result->fetch()) {
500 500
 			list(, $name) = Uri\split($row['principaluri']);
501
-			$row['displayname'] = $row['displayname'] . "($name)";
501
+			$row['displayname'] = $row['displayname']."($name)";
502 502
 			$components = [];
503 503
 			if ($row['components']) {
504
-				$components = explode(',',$row['components']);
504
+				$components = explode(',', $row['components']);
505 505
 			}
506 506
 			$calendar = [
507 507
 				'id' => $row['id'],
508 508
 				'uri' => $row['publicuri'],
509 509
 				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
510
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
511
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
512
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
513
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
514
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
515
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
516
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
510
+				'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
511
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
512
+				'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
513
+				'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
514
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
515
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}read-only' => (int) $row['access'] === Backend::ACCESS_READ,
516
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}public' => (int) $row['access'] === self::ACCESS_PUBLIC,
517 517
 			];
518 518
 
519 519
 			foreach ($this->propertyMap as $xmlName => $dbName) {
@@ -560,26 +560,26 @@  discard block
 block discarded – undo
560 560
 		$result->closeCursor();
561 561
 
562 562
 		if ($row === false) {
563
-			throw new NotFound('Node with name \'' . $uri . '\' could not be found');
563
+			throw new NotFound('Node with name \''.$uri.'\' could not be found');
564 564
 		}
565 565
 
566 566
 		list(, $name) = Uri\split($row['principaluri']);
567
-		$row['displayname'] = $row['displayname'] . ' ' . "($name)";
567
+		$row['displayname'] = $row['displayname'].' '."($name)";
568 568
 		$components = [];
569 569
 		if ($row['components']) {
570
-			$components = explode(',',$row['components']);
570
+			$components = explode(',', $row['components']);
571 571
 		}
572 572
 		$calendar = [
573 573
 			'id' => $row['id'],
574 574
 			'uri' => $row['publicuri'],
575 575
 			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
576
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
577
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
578
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
579
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
580
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
581
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
582
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
576
+			'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
577
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
578
+			'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
579
+			'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
580
+			'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
581
+			'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}read-only' => (int) $row['access'] === Backend::ACCESS_READ,
582
+			'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}public' => (int) $row['access'] === self::ACCESS_PUBLIC,
583 583
 		];
584 584
 
585 585
 		foreach ($this->propertyMap as $xmlName => $dbName) {
@@ -621,17 +621,17 @@  discard block
 block discarded – undo
621 621
 
622 622
 		$components = [];
623 623
 		if ($row['components']) {
624
-			$components = explode(',',$row['components']);
624
+			$components = explode(',', $row['components']);
625 625
 		}
626 626
 
627 627
 		$calendar = [
628 628
 			'id' => $row['id'],
629 629
 			'uri' => $row['uri'],
630 630
 			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
631
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
632
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
633
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
634
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
631
+			'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
632
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
633
+			'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
634
+			'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
635 635
 		];
636 636
 
637 637
 		foreach ($this->propertyMap as $xmlName => $dbName) {
@@ -671,17 +671,17 @@  discard block
 block discarded – undo
671 671
 
672 672
 		$components = [];
673 673
 		if ($row['components']) {
674
-			$components = explode(',',$row['components']);
674
+			$components = explode(',', $row['components']);
675 675
 		}
676 676
 
677 677
 		$calendar = [
678 678
 			'id' => $row['id'],
679 679
 			'uri' => $row['uri'],
680 680
 			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
681
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
682
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
683
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
684
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
681
+			'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
682
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
683
+			'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
684
+			'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
685 685
 		];
686 686
 
687 687
 		foreach ($this->propertyMap as $xmlName => $dbName) {
@@ -724,8 +724,8 @@  discard block
 block discarded – undo
724 724
 			'principaluri' => $row['principaluri'],
725 725
 			'source' => $row['source'],
726 726
 			'lastmodified' => $row['lastmodified'],
727
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
728
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
727
+			'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
728
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
729 729
 		];
730 730
 
731 731
 		foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
@@ -762,16 +762,16 @@  discard block
 block discarded – undo
762 762
 		$sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
763 763
 		if (isset($properties[$sccs])) {
764 764
 			if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) {
765
-				throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
765
+				throw new DAV\Exception('The '.$sccs.' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
766 766
 			}
767
-			$values['components'] = implode(',',$properties[$sccs]->getValue());
767
+			$values['components'] = implode(',', $properties[$sccs]->getValue());
768 768
 		} elseif (isset($properties['components'])) {
769 769
 			// Allow to provide components internally without having
770 770
 			// to create a SupportedCalendarComponentSet object
771 771
 			$values['components'] = $properties['components'];
772 772
 		}
773 773
 
774
-		$transp = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
774
+		$transp = '{'.Plugin::NS_CALDAV.'}schedule-calendar-transp';
775 775
 		if (isset($properties[$transp])) {
776 776
 			$values['transparent'] = (int) ($properties[$transp]->getValue() === 'transparent');
777 777
 		}
@@ -791,7 +791,7 @@  discard block
 block discarded – undo
791 791
 		$calendarId = $query->getLastInsertId();
792 792
 
793 793
 		$calendarData = $this->getCalendarById($calendarId);
794
-		$this->dispatcher->dispatchTyped(new CalendarCreatedEvent((int)$calendarId, $calendarData));
794
+		$this->dispatcher->dispatchTyped(new CalendarCreatedEvent((int) $calendarId, $calendarData));
795 795
 		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendar', new GenericEvent(
796 796
 			'\OCA\DAV\CalDAV\CalDavBackend::createCalendar',
797 797
 			[
@@ -820,13 +820,13 @@  discard block
 block discarded – undo
820 820
 	 */
821 821
 	public function updateCalendar($calendarId, PropPatch $propPatch) {
822 822
 		$supportedProperties = array_keys($this->propertyMap);
823
-		$supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
823
+		$supportedProperties[] = '{'.Plugin::NS_CALDAV.'}schedule-calendar-transp';
824 824
 
825
-		$propPatch->handle($supportedProperties, function ($mutations) use ($calendarId) {
825
+		$propPatch->handle($supportedProperties, function($mutations) use ($calendarId) {
826 826
 			$newValues = [];
827 827
 			foreach ($mutations as $propertyName => $propertyValue) {
828 828
 				switch ($propertyName) {
829
-					case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp':
829
+					case '{'.Plugin::NS_CALDAV.'}schedule-calendar-transp':
830 830
 						$fieldName = 'transparent';
831 831
 						$newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent');
832 832
 						break;
@@ -848,7 +848,7 @@  discard block
 block discarded – undo
848 848
 
849 849
 			$calendarData = $this->getCalendarById($calendarId);
850 850
 			$shares = $this->getShares($calendarId);
851
-			$this->dispatcher->dispatchTyped(new CalendarUpdatedEvent((int)$calendarId, $calendarData, $shares, $mutations));
851
+			$this->dispatcher->dispatchTyped(new CalendarUpdatedEvent((int) $calendarId, $calendarData, $shares, $mutations));
852 852
 			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendar', new GenericEvent(
853 853
 				'\OCA\DAV\CalDAV\CalDavBackend::updateCalendar',
854 854
 				[
@@ -898,7 +898,7 @@  discard block
 block discarded – undo
898 898
 			->execute();
899 899
 
900 900
 		if ($calendarData) {
901
-			$this->dispatcher->dispatchTyped(new CalendarDeletedEvent((int)$calendarId, $calendarData, $shares));
901
+			$this->dispatcher->dispatchTyped(new CalendarDeletedEvent((int) $calendarId, $calendarData, $shares));
902 902
 		}
903 903
 	}
904 904
 
@@ -958,11 +958,11 @@  discard block
 block discarded – undo
958 958
 				'id' => $row['id'],
959 959
 				'uri' => $row['uri'],
960 960
 				'lastmodified' => $row['lastmodified'],
961
-				'etag' => '"' . $row['etag'] . '"',
961
+				'etag' => '"'.$row['etag'].'"',
962 962
 				'calendarid' => $row['calendarid'],
963
-				'size' => (int)$row['size'],
963
+				'size' => (int) $row['size'],
964 964
 				'component' => strtolower($row['componenttype']),
965
-				'classification' => (int)$row['classification']
965
+				'classification' => (int) $row['classification']
966 966
 			];
967 967
 		}
968 968
 
@@ -1004,12 +1004,12 @@  discard block
 block discarded – undo
1004 1004
 			'id' => $row['id'],
1005 1005
 			'uri' => $row['uri'],
1006 1006
 			'lastmodified' => $row['lastmodified'],
1007
-			'etag' => '"' . $row['etag'] . '"',
1007
+			'etag' => '"'.$row['etag'].'"',
1008 1008
 			'calendarid' => $row['calendarid'],
1009
-			'size' => (int)$row['size'],
1009
+			'size' => (int) $row['size'],
1010 1010
 			'calendardata' => $this->readBlob($row['calendardata']),
1011 1011
 			'component' => strtolower($row['componenttype']),
1012
-			'classification' => (int)$row['classification']
1012
+			'classification' => (int) $row['classification']
1013 1013
 		];
1014 1014
 	}
1015 1015
 
@@ -1050,12 +1050,12 @@  discard block
 block discarded – undo
1050 1050
 					'id' => $row['id'],
1051 1051
 					'uri' => $row['uri'],
1052 1052
 					'lastmodified' => $row['lastmodified'],
1053
-					'etag' => '"' . $row['etag'] . '"',
1053
+					'etag' => '"'.$row['etag'].'"',
1054 1054
 					'calendarid' => $row['calendarid'],
1055
-					'size' => (int)$row['size'],
1055
+					'size' => (int) $row['size'],
1056 1056
 					'calendardata' => $this->readBlob($row['calendardata']),
1057 1057
 					'component' => strtolower($row['componenttype']),
1058
-					'classification' => (int)$row['classification']
1058
+					'classification' => (int) $row['classification']
1059 1059
 				];
1060 1060
 			}
1061 1061
 			$result->closeCursor();
@@ -1127,7 +1127,7 @@  discard block
 block discarded – undo
1127 1127
 			$calendarRow = $this->getCalendarById($calendarId);
1128 1128
 			$shares = $this->getShares($calendarId);
1129 1129
 
1130
-			$this->dispatcher->dispatchTyped(new CalendarObjectCreatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
1130
+			$this->dispatcher->dispatchTyped(new CalendarObjectCreatedEvent((int) $calendarId, $calendarRow, $shares, $objectRow));
1131 1131
 			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', new GenericEvent(
1132 1132
 				'\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject',
1133 1133
 				[
@@ -1140,7 +1140,7 @@  discard block
 block discarded – undo
1140 1140
 		} else {
1141 1141
 			$subscriptionRow = $this->getSubscriptionById($calendarId);
1142 1142
 
1143
-			$this->dispatcher->dispatchTyped(new CachedCalendarObjectCreatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
1143
+			$this->dispatcher->dispatchTyped(new CachedCalendarObjectCreatedEvent((int) $calendarId, $subscriptionRow, [], $objectRow));
1144 1144
 			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject', new GenericEvent(
1145 1145
 				'\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject',
1146 1146
 				[
@@ -1152,7 +1152,7 @@  discard block
 block discarded – undo
1152 1152
 			));
1153 1153
 		}
1154 1154
 
1155
-		return '"' . $extraData['etag'] . '"';
1155
+		return '"'.$extraData['etag'].'"';
1156 1156
 	}
1157 1157
 
1158 1158
 	/**
@@ -1201,7 +1201,7 @@  discard block
 block discarded – undo
1201 1201
 				$calendarRow = $this->getCalendarById($calendarId);
1202 1202
 				$shares = $this->getShares($calendarId);
1203 1203
 
1204
-				$this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
1204
+				$this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent((int) $calendarId, $calendarRow, $shares, $objectRow));
1205 1205
 				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', new GenericEvent(
1206 1206
 					'\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject',
1207 1207
 					[
@@ -1214,7 +1214,7 @@  discard block
 block discarded – undo
1214 1214
 			} else {
1215 1215
 				$subscriptionRow = $this->getSubscriptionById($calendarId);
1216 1216
 
1217
-				$this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
1217
+				$this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent((int) $calendarId, $subscriptionRow, [], $objectRow));
1218 1218
 				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject', new GenericEvent(
1219 1219
 					'\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject',
1220 1220
 					[
@@ -1227,7 +1227,7 @@  discard block
 block discarded – undo
1227 1227
 			}
1228 1228
 		}
1229 1229
 
1230
-		return '"' . $extraData['etag'] . '"';
1230
+		return '"'.$extraData['etag'].'"';
1231 1231
 	}
1232 1232
 
1233 1233
 	/**
@@ -1264,7 +1264,7 @@  discard block
 block discarded – undo
1264 1264
 				$calendarRow = $this->getCalendarById($calendarId);
1265 1265
 				$shares = $this->getShares($calendarId);
1266 1266
 
1267
-				$this->dispatcher->dispatchTyped(new CalendarObjectDeletedEvent((int)$calendarId, $calendarRow, $shares, $data));
1267
+				$this->dispatcher->dispatchTyped(new CalendarObjectDeletedEvent((int) $calendarId, $calendarRow, $shares, $data));
1268 1268
 				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', new GenericEvent(
1269 1269
 					'\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject',
1270 1270
 					[
@@ -1277,7 +1277,7 @@  discard block
 block discarded – undo
1277 1277
 			} else {
1278 1278
 				$subscriptionRow = $this->getSubscriptionById($calendarId);
1279 1279
 
1280
-				$this->dispatcher->dispatchTyped(new CachedCalendarObjectDeletedEvent((int)$calendarId, $subscriptionRow, [], $data));
1280
+				$this->dispatcher->dispatchTyped(new CachedCalendarObjectDeletedEvent((int) $calendarId, $subscriptionRow, [], $data));
1281 1281
 				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject', new GenericEvent(
1282 1282
 					'\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject',
1283 1283
 					[
@@ -1549,7 +1549,7 @@  discard block
 block discarded – undo
1549 1549
 
1550 1550
 		$result = [];
1551 1551
 		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1552
-			$path = $uriMapper[$row['calendarid']] . '/' . $row['uri'];
1552
+			$path = $uriMapper[$row['calendarid']].'/'.$row['uri'];
1553 1553
 			if (!in_array($path, $result)) {
1554 1554
 				$result[] = $path;
1555 1555
 			}
@@ -1597,8 +1597,8 @@  discard block
 block discarded – undo
1597 1597
 
1598 1598
 		if ($pattern !== '') {
1599 1599
 			$innerQuery->andWhere($innerQuery->expr()->iLike('op.value',
1600
-				$outerQuery->createNamedParameter('%' .
1601
-					$this->db->escapeLikeParameter($pattern) . '%')));
1600
+				$outerQuery->createNamedParameter('%'.
1601
+					$this->db->escapeLikeParameter($pattern).'%')));
1602 1602
 		}
1603 1603
 
1604 1604
 		$outerQuery->select('c.id', 'c.calendardata', 'c.componenttype', 'c.uid', 'c.uri')
@@ -1637,7 +1637,7 @@  discard block
 block discarded – undo
1637 1637
 		$result = $outerQuery->execute();
1638 1638
 		$calendarObjects = $result->fetchAll();
1639 1639
 
1640
-		return array_map(function ($o) {
1640
+		return array_map(function($o) {
1641 1641
 			$calendarData = Reader::read($o['calendardata']);
1642 1642
 			$comps = $calendarData->getComponents();
1643 1643
 			$objects = [];
@@ -1655,10 +1655,10 @@  discard block
 block discarded – undo
1655 1655
 				'type' => $o['componenttype'],
1656 1656
 				'uid' => $o['uid'],
1657 1657
 				'uri' => $o['uri'],
1658
-				'objects' => array_map(function ($c) {
1658
+				'objects' => array_map(function($c) {
1659 1659
 					return $this->transformSearchData($c);
1660 1660
 				}, $objects),
1661
-				'timezones' => array_map(function ($c) {
1661
+				'timezones' => array_map(function($c) {
1662 1662
 					return $this->transformSearchData($c);
1663 1663
 				}, $timezones),
1664 1664
 			];
@@ -1674,7 +1674,7 @@  discard block
 block discarded – undo
1674 1674
 		/** @var Component[] $subComponents */
1675 1675
 		$subComponents = $comp->getComponents();
1676 1676
 		/** @var Property[] $properties */
1677
-		$properties = array_filter($comp->children(), function ($c) {
1677
+		$properties = array_filter($comp->children(), function($c) {
1678 1678
 			return $c instanceof Property;
1679 1679
 		});
1680 1680
 		$validationRules = $comp->getValidationRules();
@@ -1752,7 +1752,7 @@  discard block
 block discarded – undo
1752 1752
 		$subscriptions = $this->getSubscriptionsForUser($principalUri);
1753 1753
 		foreach ($calendars as $calendar) {
1754 1754
 			$calendarAnd = $calendarObjectIdQuery->expr()->andX();
1755
-			$calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$calendar['id'])));
1755
+			$calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int) $calendar['id'])));
1756 1756
 			$calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1757 1757
 
1758 1758
 			// If it's shared, limit search to public events
@@ -1764,7 +1764,7 @@  discard block
 block discarded – undo
1764 1764
 		}
1765 1765
 		foreach ($subscriptions as $subscription) {
1766 1766
 			$subscriptionAnd = $calendarObjectIdQuery->expr()->andX();
1767
-			$subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$subscription['id'])));
1767
+			$subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int) $subscription['id'])));
1768 1768
 			$subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
1769 1769
 
1770 1770
 			// If it's shared, limit search to public events
@@ -1808,7 +1808,7 @@  discard block
 block discarded – undo
1808 1808
 			if (!$escapePattern) {
1809 1809
 				$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter($pattern)));
1810 1810
 			} else {
1811
-				$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
1811
+				$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter('%'.$this->db->escapeLikeParameter($pattern).'%')));
1812 1812
 			}
1813 1813
 		}
1814 1814
 
@@ -1822,7 +1822,7 @@  discard block
 block discarded – undo
1822 1822
 		$result = $calendarObjectIdQuery->execute();
1823 1823
 		$matches = $result->fetchAll();
1824 1824
 		$result->closeCursor();
1825
-		$matches = array_map(static function (array $match):int {
1825
+		$matches = array_map(static function(array $match):int {
1826 1826
 			return (int) $match['objectid'];
1827 1827
 		}, $matches);
1828 1828
 
@@ -1835,9 +1835,9 @@  discard block
 block discarded – undo
1835 1835
 		$calendarObjects = $result->fetchAll();
1836 1836
 		$result->closeCursor();
1837 1837
 
1838
-		return array_map(function (array $array): array {
1839
-			$array['calendarid'] = (int)$array['calendarid'];
1840
-			$array['calendartype'] = (int)$array['calendartype'];
1838
+		return array_map(function(array $array): array {
1839
+			$array['calendarid'] = (int) $array['calendarid'];
1840
+			$array['calendartype'] = (int) $array['calendartype'];
1841 1841
 			$array['calendardata'] = $this->readBlob($array['calendardata']);
1842 1842
 
1843 1843
 			return $array;
@@ -1874,7 +1874,7 @@  discard block
 block discarded – undo
1874 1874
 		$stmt = $query->execute();
1875 1875
 
1876 1876
 		if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1877
-			return $row['calendaruri'] . '/' . $row['objecturi'];
1877
+			return $row['calendaruri'].'/'.$row['objecturi'];
1878 1878
 		}
1879 1879
 
1880 1880
 		return null;
@@ -1940,7 +1940,7 @@  discard block
 block discarded – undo
1940 1940
 	public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1941 1941
 		// Current synctoken
1942 1942
 		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*calendars` WHERE `id` = ?');
1943
-		$stmt->execute([ $calendarId ]);
1943
+		$stmt->execute([$calendarId]);
1944 1944
 		$currentToken = $stmt->fetchColumn(0);
1945 1945
 
1946 1946
 		if (is_null($currentToken)) {
@@ -1957,7 +1957,7 @@  discard block
 block discarded – undo
1957 1957
 		if ($syncToken) {
1958 1958
 			$query = "SELECT `uri`, `operation` FROM `*PREFIX*calendarchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `calendarid` = ? AND `calendartype` = ? ORDER BY `synctoken`";
1959 1959
 			if ($limit > 0) {
1960
-				$query .= " LIMIT " . (int)$limit;
1960
+				$query .= " LIMIT ".(int) $limit;
1961 1961
 			}
1962 1962
 
1963 1963
 			// Fetching all changes
@@ -2053,8 +2053,8 @@  discard block
 block discarded – undo
2053 2053
 				'source' => $row['source'],
2054 2054
 				'lastmodified' => $row['lastmodified'],
2055 2055
 
2056
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
2057
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
2056
+				'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
2057
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
2058 2058
 			];
2059 2059
 
2060 2060
 			foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
@@ -2118,7 +2118,7 @@  discard block
 block discarded – undo
2118 2118
 		$subscriptionId = $this->db->lastInsertId('*PREFIX*calendarsubscriptions');
2119 2119
 
2120 2120
 		$subscriptionRow = $this->getSubscriptionById($subscriptionId);
2121
-		$this->dispatcher->dispatchTyped(new SubscriptionCreatedEvent((int)$subscriptionId, $subscriptionRow));
2121
+		$this->dispatcher->dispatchTyped(new SubscriptionCreatedEvent((int) $subscriptionId, $subscriptionRow));
2122 2122
 		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createSubscription', new GenericEvent(
2123 2123
 			'\OCA\DAV\CalDAV\CalDavBackend::createSubscription',
2124 2124
 			[
@@ -2149,7 +2149,7 @@  discard block
 block discarded – undo
2149 2149
 		$supportedProperties = array_keys($this->subscriptionPropertyMap);
2150 2150
 		$supportedProperties[] = '{http://calendarserver.org/ns/}source';
2151 2151
 
2152
-		$propPatch->handle($supportedProperties, function ($mutations) use ($subscriptionId) {
2152
+		$propPatch->handle($supportedProperties, function($mutations) use ($subscriptionId) {
2153 2153
 			$newValues = [];
2154 2154
 
2155 2155
 			foreach ($mutations as $propertyName => $propertyValue) {
@@ -2171,7 +2171,7 @@  discard block
 block discarded – undo
2171 2171
 				->execute();
2172 2172
 
2173 2173
 			$subscriptionRow = $this->getSubscriptionById($subscriptionId);
2174
-			$this->dispatcher->dispatchTyped(new SubscriptionUpdatedEvent((int)$subscriptionId, $subscriptionRow, [], $mutations));
2174
+			$this->dispatcher->dispatchTyped(new SubscriptionUpdatedEvent((int) $subscriptionId, $subscriptionRow, [], $mutations));
2175 2175
 			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateSubscription', new GenericEvent(
2176 2176
 				'\OCA\DAV\CalDAV\CalDavBackend::updateSubscription',
2177 2177
 				[
@@ -2222,7 +2222,7 @@  discard block
 block discarded – undo
2222 2222
 			->execute();
2223 2223
 
2224 2224
 		if ($subscriptionRow) {
2225
-			$this->dispatcher->dispatchTyped(new SubscriptionDeletedEvent((int)$subscriptionId, $subscriptionRow, []));
2225
+			$this->dispatcher->dispatchTyped(new SubscriptionDeletedEvent((int) $subscriptionId, $subscriptionRow, []));
2226 2226
 		}
2227 2227
 	}
2228 2228
 
@@ -2260,8 +2260,8 @@  discard block
 block discarded – undo
2260 2260
 			'uri' => $row['uri'],
2261 2261
 			'calendardata' => $row['calendardata'],
2262 2262
 			'lastmodified' => $row['lastmodified'],
2263
-			'etag' => '"' . $row['etag'] . '"',
2264
-			'size' => (int)$row['size'],
2263
+			'etag' => '"'.$row['etag'].'"',
2264
+			'size' => (int) $row['size'],
2265 2265
 		];
2266 2266
 	}
2267 2267
 
@@ -2289,8 +2289,8 @@  discard block
 block discarded – undo
2289 2289
 				'calendardata' => $row['calendardata'],
2290 2290
 				'uri' => $row['uri'],
2291 2291
 				'lastmodified' => $row['lastmodified'],
2292
-				'etag' => '"' . $row['etag'] . '"',
2293
-				'size' => (int)$row['size'],
2292
+				'etag' => '"'.$row['etag'].'"',
2293
+				'size' => (int) $row['size'],
2294 2294
 			];
2295 2295
 		}
2296 2296
 
@@ -2344,14 +2344,14 @@  discard block
 block discarded – undo
2344 2344
 	 * @return void
2345 2345
 	 */
2346 2346
 	protected function addChange($calendarId, $objectUri, $operation, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2347
-		$table = $calendarType === self::CALENDAR_TYPE_CALENDAR ? 'calendars': 'calendarsubscriptions';
2347
+		$table = $calendarType === self::CALENDAR_TYPE_CALENDAR ? 'calendars' : 'calendarsubscriptions';
2348 2348
 
2349 2349
 		$query = $this->db->getQueryBuilder();
2350 2350
 		$query->select('synctoken')
2351 2351
 			->from($table)
2352 2352
 			->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
2353 2353
 		$result = $query->execute();
2354
-		$syncToken = (int)$result->fetchColumn();
2354
+		$syncToken = (int) $result->fetchColumn();
2355 2355
 		$result->closeCursor();
2356 2356
 
2357 2357
 		$query = $this->db->getQueryBuilder();
@@ -2408,7 +2408,7 @@  discard block
 block discarded – undo
2408 2408
 				// Track first component type and uid
2409 2409
 				if ($uid === null) {
2410 2410
 					$componentType = $component->name;
2411
-					$uid = (string)$component->UID;
2411
+					$uid = (string) $component->UID;
2412 2412
 				}
2413 2413
 			}
2414 2414
 		}
@@ -2507,7 +2507,7 @@  discard block
 block discarded – undo
2507 2507
 			]));
2508 2508
 		$this->calendarSharingBackend->updateShares($shareable, $add, $remove);
2509 2509
 
2510
-		$this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent((int)$calendarId, $calendarRow, $oldShares, $add, $remove));
2510
+		$this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent((int) $calendarId, $calendarRow, $oldShares, $add, $remove));
2511 2511
 	}
2512 2512
 
2513 2513
 	/**
@@ -2548,7 +2548,7 @@  discard block
 block discarded – undo
2548 2548
 				]);
2549 2549
 			$query->execute();
2550 2550
 
2551
-			$this->dispatcher->dispatchTyped(new CalendarPublishedEvent((int)$calendarId, $calendarData, $publicUri));
2551
+			$this->dispatcher->dispatchTyped(new CalendarPublishedEvent((int) $calendarId, $calendarData, $publicUri));
2552 2552
 			return $publicUri;
2553 2553
 		}
2554 2554
 		$query->delete('dav_shares')
@@ -2556,7 +2556,7 @@  discard block
 block discarded – undo
2556 2556
 			->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)));
2557 2557
 		$query->execute();
2558 2558
 
2559
-		$this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent((int)$calendarId, $calendarData));
2559
+		$this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent((int) $calendarId, $calendarData));
2560 2560
 		return null;
2561 2561
 	}
2562 2562
 
@@ -2777,10 +2777,10 @@  discard block
 block discarded – undo
2777 2777
 		$result->closeCursor();
2778 2778
 
2779 2779
 		if (!isset($objectIds['id'])) {
2780
-			throw new \InvalidArgumentException('Calendarobject does not exists: ' . $uri);
2780
+			throw new \InvalidArgumentException('Calendarobject does not exists: '.$uri);
2781 2781
 		}
2782 2782
 
2783
-		return (int)$objectIds['id'];
2783
+		return (int) $objectIds['id'];
2784 2784
 	}
2785 2785
 
2786 2786
 	/**
@@ -2807,8 +2807,8 @@  discard block
 block discarded – undo
2807 2807
 	 * @param $calendarInfo
2808 2808
 	 */
2809 2809
 	private function addOwnerPrincipal(&$calendarInfo) {
2810
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
2811
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
2810
+		$ownerPrincipalKey = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal';
2811
+		$displaynameKey = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD.'}owner-displayname';
2812 2812
 		if (isset($calendarInfo[$ownerPrincipalKey])) {
2813 2813
 			$uri = $calendarInfo[$ownerPrincipalKey];
2814 2814
 		} else {
Please login to merge, or discard this patch.
apps/dav/lib/CardDAV/CardDavBackend.php 2 patches
Indentation   +1302 added lines, -1302 removed lines patch added patch discarded remove patch
@@ -62,1306 +62,1306 @@
 block discarded – undo
62 62
 use Symfony\Component\EventDispatcher\GenericEvent;
63 63
 
64 64
 class CardDavBackend implements BackendInterface, SyncSupport {
65
-	public const PERSONAL_ADDRESSBOOK_URI = 'contacts';
66
-	public const PERSONAL_ADDRESSBOOK_NAME = 'Contacts';
67
-
68
-	/** @var Principal */
69
-	private $principalBackend;
70
-
71
-	/** @var string */
72
-	private $dbCardsTable = 'cards';
73
-
74
-	/** @var string */
75
-	private $dbCardsPropertiesTable = 'cards_properties';
76
-
77
-	/** @var IDBConnection */
78
-	private $db;
79
-
80
-	/** @var Backend */
81
-	private $sharingBackend;
82
-
83
-	/** @var array properties to index */
84
-	public static $indexProperties = [
85
-		'BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME',
86
-		'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO', 'CLOUD'];
87
-
88
-	/**
89
-	 * @var string[] Map of uid => display name
90
-	 */
91
-	protected $userDisplayNames;
92
-
93
-	/** @var IUserManager */
94
-	private $userManager;
95
-
96
-	/** @var IEventDispatcher */
97
-	private $dispatcher;
98
-
99
-	/** @var EventDispatcherInterface */
100
-	private $legacyDispatcher;
101
-
102
-	private $etagCache = [];
103
-
104
-	/**
105
-	 * CardDavBackend constructor.
106
-	 *
107
-	 * @param IDBConnection $db
108
-	 * @param Principal $principalBackend
109
-	 * @param IUserManager $userManager
110
-	 * @param IGroupManager $groupManager
111
-	 * @param IEventDispatcher $dispatcher
112
-	 * @param EventDispatcherInterface $legacyDispatcher
113
-	 */
114
-	public function __construct(IDBConnection $db,
115
-								Principal $principalBackend,
116
-								IUserManager $userManager,
117
-								IGroupManager $groupManager,
118
-								IEventDispatcher $dispatcher,
119
-								EventDispatcherInterface $legacyDispatcher) {
120
-		$this->db = $db;
121
-		$this->principalBackend = $principalBackend;
122
-		$this->userManager = $userManager;
123
-		$this->dispatcher = $dispatcher;
124
-		$this->legacyDispatcher = $legacyDispatcher;
125
-		$this->sharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'addressbook');
126
-	}
127
-
128
-	/**
129
-	 * Return the number of address books for a principal
130
-	 *
131
-	 * @param $principalUri
132
-	 * @return int
133
-	 */
134
-	public function getAddressBooksForUserCount($principalUri) {
135
-		$principalUri = $this->convertPrincipal($principalUri, true);
136
-		$query = $this->db->getQueryBuilder();
137
-		$query->select($query->func()->count('*'))
138
-			->from('addressbooks')
139
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
140
-
141
-		$result = $query->execute();
142
-		$column = (int) $result->fetchColumn();
143
-		$result->closeCursor();
144
-		return $column;
145
-	}
146
-
147
-	/**
148
-	 * Returns the list of address books for a specific user.
149
-	 *
150
-	 * Every addressbook should have the following properties:
151
-	 *   id - an arbitrary unique id
152
-	 *   uri - the 'basename' part of the url
153
-	 *   principaluri - Same as the passed parameter
154
-	 *
155
-	 * Any additional clark-notation property may be passed besides this. Some
156
-	 * common ones are :
157
-	 *   {DAV:}displayname
158
-	 *   {urn:ietf:params:xml:ns:carddav}addressbook-description
159
-	 *   {http://calendarserver.org/ns/}getctag
160
-	 *
161
-	 * @param string $principalUri
162
-	 * @return array
163
-	 */
164
-	public function getAddressBooksForUser($principalUri) {
165
-		$principalUriOriginal = $principalUri;
166
-		$principalUri = $this->convertPrincipal($principalUri, true);
167
-		$query = $this->db->getQueryBuilder();
168
-		$query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
169
-			->from('addressbooks')
170
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
171
-
172
-		$addressBooks = [];
173
-
174
-		$result = $query->execute();
175
-		while ($row = $result->fetch()) {
176
-			$addressBooks[$row['id']] = [
177
-				'id' => $row['id'],
178
-				'uri' => $row['uri'],
179
-				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
180
-				'{DAV:}displayname' => $row['displayname'],
181
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
182
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
183
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
184
-			];
185
-
186
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
187
-		}
188
-		$result->closeCursor();
189
-
190
-		// query for shared addressbooks
191
-		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
192
-		$principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
193
-
194
-		$principals = array_map(function ($principal) {
195
-			return urldecode($principal);
196
-		}, $principals);
197
-		$principals[] = $principalUri;
198
-
199
-		$query = $this->db->getQueryBuilder();
200
-		$result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
201
-			->from('dav_shares', 's')
202
-			->join('s', 'addressbooks', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
203
-			->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
204
-			->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
205
-			->setParameter('type', 'addressbook')
206
-			->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
207
-			->execute();
208
-
209
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
210
-		while ($row = $result->fetch()) {
211
-			if ($row['principaluri'] === $principalUri) {
212
-				continue;
213
-			}
214
-
215
-			$readOnly = (int)$row['access'] === Backend::ACCESS_READ;
216
-			if (isset($addressBooks[$row['id']])) {
217
-				if ($readOnly) {
218
-					// New share can not have more permissions then the old one.
219
-					continue;
220
-				}
221
-				if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) &&
222
-					$addressBooks[$row['id']][$readOnlyPropertyName] === 0) {
223
-					// Old share is already read-write, no more permissions can be gained
224
-					continue;
225
-				}
226
-			}
227
-
228
-			list(, $name) = \Sabre\Uri\split($row['principaluri']);
229
-			$uri = $row['uri'] . '_shared_by_' . $name;
230
-			$displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
231
-
232
-			$addressBooks[$row['id']] = [
233
-				'id' => $row['id'],
234
-				'uri' => $uri,
235
-				'principaluri' => $principalUriOriginal,
236
-				'{DAV:}displayname' => $displayName,
237
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
238
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
239
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
240
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
241
-				$readOnlyPropertyName => $readOnly,
242
-			];
243
-
244
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
245
-		}
246
-		$result->closeCursor();
247
-
248
-		return array_values($addressBooks);
249
-	}
250
-
251
-	public function getUsersOwnAddressBooks($principalUri) {
252
-		$principalUri = $this->convertPrincipal($principalUri, true);
253
-		$query = $this->db->getQueryBuilder();
254
-		$query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
255
-			->from('addressbooks')
256
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
257
-
258
-		$addressBooks = [];
259
-
260
-		$result = $query->execute();
261
-		while ($row = $result->fetch()) {
262
-			$addressBooks[$row['id']] = [
263
-				'id' => $row['id'],
264
-				'uri' => $row['uri'],
265
-				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
266
-				'{DAV:}displayname' => $row['displayname'],
267
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
268
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
269
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
270
-			];
271
-
272
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
273
-		}
274
-		$result->closeCursor();
275
-
276
-		return array_values($addressBooks);
277
-	}
278
-
279
-	private function getUserDisplayName($uid) {
280
-		if (!isset($this->userDisplayNames[$uid])) {
281
-			$user = $this->userManager->get($uid);
282
-
283
-			if ($user instanceof IUser) {
284
-				$this->userDisplayNames[$uid] = $user->getDisplayName();
285
-			} else {
286
-				$this->userDisplayNames[$uid] = $uid;
287
-			}
288
-		}
289
-
290
-		return $this->userDisplayNames[$uid];
291
-	}
292
-
293
-	/**
294
-	 * @param int $addressBookId
295
-	 */
296
-	public function getAddressBookById($addressBookId) {
297
-		$query = $this->db->getQueryBuilder();
298
-		$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
299
-			->from('addressbooks')
300
-			->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
301
-			->execute();
302
-
303
-		$row = $result->fetch();
304
-		$result->closeCursor();
305
-		if ($row === false) {
306
-			return null;
307
-		}
308
-
309
-		$addressBook = [
310
-			'id' => $row['id'],
311
-			'uri' => $row['uri'],
312
-			'principaluri' => $row['principaluri'],
313
-			'{DAV:}displayname' => $row['displayname'],
314
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
315
-			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
316
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
317
-		];
318
-
319
-		$this->addOwnerPrincipal($addressBook);
320
-
321
-		return $addressBook;
322
-	}
323
-
324
-	/**
325
-	 * @param $addressBookUri
326
-	 * @return array|null
327
-	 */
328
-	public function getAddressBooksByUri($principal, $addressBookUri) {
329
-		$query = $this->db->getQueryBuilder();
330
-		$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
331
-			->from('addressbooks')
332
-			->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri)))
333
-			->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
334
-			->setMaxResults(1)
335
-			->execute();
336
-
337
-		$row = $result->fetch();
338
-		$result->closeCursor();
339
-		if ($row === false) {
340
-			return null;
341
-		}
342
-
343
-		$addressBook = [
344
-			'id' => $row['id'],
345
-			'uri' => $row['uri'],
346
-			'principaluri' => $row['principaluri'],
347
-			'{DAV:}displayname' => $row['displayname'],
348
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
349
-			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
350
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
351
-		];
352
-
353
-		$this->addOwnerPrincipal($addressBook);
354
-
355
-		return $addressBook;
356
-	}
357
-
358
-	/**
359
-	 * Updates properties for an address book.
360
-	 *
361
-	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
362
-	 * To do the actual updates, you must tell this object which properties
363
-	 * you're going to process with the handle() method.
364
-	 *
365
-	 * Calling the handle method is like telling the PropPatch object "I
366
-	 * promise I can handle updating this property".
367
-	 *
368
-	 * Read the PropPatch documentation for more info and examples.
369
-	 *
370
-	 * @param string $addressBookId
371
-	 * @param \Sabre\DAV\PropPatch $propPatch
372
-	 * @return void
373
-	 */
374
-	public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
375
-		$supportedProperties = [
376
-			'{DAV:}displayname',
377
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description',
378
-		];
379
-
380
-		$propPatch->handle($supportedProperties, function ($mutations) use ($addressBookId) {
381
-			$updates = [];
382
-			foreach ($mutations as $property => $newValue) {
383
-				switch ($property) {
384
-					case '{DAV:}displayname':
385
-						$updates['displayname'] = $newValue;
386
-						break;
387
-					case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
388
-						$updates['description'] = $newValue;
389
-						break;
390
-				}
391
-			}
392
-			$query = $this->db->getQueryBuilder();
393
-			$query->update('addressbooks');
394
-
395
-			foreach ($updates as $key => $value) {
396
-				$query->set($key, $query->createNamedParameter($value));
397
-			}
398
-			$query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
399
-				->execute();
400
-
401
-			$this->addChange($addressBookId, "", 2);
402
-
403
-			$addressBookRow = $this->getAddressBookById((int)$addressBookId);
404
-			$shares = $this->getShares($addressBookId);
405
-			$this->dispatcher->dispatchTyped(new AddressBookUpdatedEvent((int)$addressBookId, $addressBookRow, $shares, $mutations));
406
-
407
-			return true;
408
-		});
409
-	}
410
-
411
-	/**
412
-	 * Creates a new address book
413
-	 *
414
-	 * @param string $principalUri
415
-	 * @param string $url Just the 'basename' of the url.
416
-	 * @param array $properties
417
-	 * @return int
418
-	 * @throws BadRequest
419
-	 */
420
-	public function createAddressBook($principalUri, $url, array $properties) {
421
-		$values = [
422
-			'displayname' => null,
423
-			'description' => null,
424
-			'principaluri' => $principalUri,
425
-			'uri' => $url,
426
-			'synctoken' => 1
427
-		];
428
-
429
-		foreach ($properties as $property => $newValue) {
430
-			switch ($property) {
431
-				case '{DAV:}displayname':
432
-					$values['displayname'] = $newValue;
433
-					break;
434
-				case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
435
-					$values['description'] = $newValue;
436
-					break;
437
-				default:
438
-					throw new BadRequest('Unknown property: ' . $property);
439
-			}
440
-		}
441
-
442
-		// Fallback to make sure the displayname is set. Some clients may refuse
443
-		// to work with addressbooks not having a displayname.
444
-		if (is_null($values['displayname'])) {
445
-			$values['displayname'] = $url;
446
-		}
447
-
448
-		$query = $this->db->getQueryBuilder();
449
-		$query->insert('addressbooks')
450
-			->values([
451
-				'uri' => $query->createParameter('uri'),
452
-				'displayname' => $query->createParameter('displayname'),
453
-				'description' => $query->createParameter('description'),
454
-				'principaluri' => $query->createParameter('principaluri'),
455
-				'synctoken' => $query->createParameter('synctoken'),
456
-			])
457
-			->setParameters($values)
458
-			->execute();
459
-
460
-		$addressBookId = $query->getLastInsertId();
461
-		$addressBookRow = $this->getAddressBookById($addressBookId);
462
-		$this->dispatcher->dispatchTyped(new AddressBookCreatedEvent((int)$addressBookId, $addressBookRow));
463
-
464
-		return $addressBookId;
465
-	}
466
-
467
-	/**
468
-	 * Deletes an entire addressbook and all its contents
469
-	 *
470
-	 * @param mixed $addressBookId
471
-	 * @return void
472
-	 */
473
-	public function deleteAddressBook($addressBookId) {
474
-		$addressBookData = $this->getAddressBookById($addressBookId);
475
-		$shares = $this->getShares($addressBookId);
476
-
477
-		$query = $this->db->getQueryBuilder();
478
-		$query->delete($this->dbCardsTable)
479
-			->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
480
-			->setParameter('addressbookid', $addressBookId)
481
-			->execute();
482
-
483
-		$query->delete('addressbookchanges')
484
-			->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
485
-			->setParameter('addressbookid', $addressBookId)
486
-			->execute();
487
-
488
-		$query->delete('addressbooks')
489
-			->where($query->expr()->eq('id', $query->createParameter('id')))
490
-			->setParameter('id', $addressBookId)
491
-			->execute();
492
-
493
-		$this->sharingBackend->deleteAllShares($addressBookId);
494
-
495
-		$query->delete($this->dbCardsPropertiesTable)
496
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
497
-			->execute();
498
-
499
-		if ($addressBookData) {
500
-			$this->dispatcher->dispatchTyped(new AddressBookDeletedEvent((int) $addressBookId, $addressBookData, $shares));
501
-		}
502
-	}
503
-
504
-	/**
505
-	 * Returns all cards for a specific addressbook id.
506
-	 *
507
-	 * This method should return the following properties for each card:
508
-	 *   * carddata - raw vcard data
509
-	 *   * uri - Some unique url
510
-	 *   * lastmodified - A unix timestamp
511
-	 *
512
-	 * It's recommended to also return the following properties:
513
-	 *   * etag - A unique etag. This must change every time the card changes.
514
-	 *   * size - The size of the card in bytes.
515
-	 *
516
-	 * If these last two properties are provided, less time will be spent
517
-	 * calculating them. If they are specified, you can also ommit carddata.
518
-	 * This may speed up certain requests, especially with large cards.
519
-	 *
520
-	 * @param mixed $addressBookId
521
-	 * @return array
522
-	 */
523
-	public function getCards($addressBookId) {
524
-		$query = $this->db->getQueryBuilder();
525
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
526
-			->from($this->dbCardsTable)
527
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
528
-
529
-		$cards = [];
530
-
531
-		$result = $query->execute();
532
-		while ($row = $result->fetch()) {
533
-			$row['etag'] = '"' . $row['etag'] . '"';
534
-
535
-			$modified = false;
536
-			$row['carddata'] = $this->readBlob($row['carddata'], $modified);
537
-			if ($modified) {
538
-				$row['size'] = strlen($row['carddata']);
539
-			}
540
-
541
-			$cards[] = $row;
542
-		}
543
-		$result->closeCursor();
544
-
545
-		return $cards;
546
-	}
547
-
548
-	/**
549
-	 * Returns a specific card.
550
-	 *
551
-	 * The same set of properties must be returned as with getCards. The only
552
-	 * exception is that 'carddata' is absolutely required.
553
-	 *
554
-	 * If the card does not exist, you must return false.
555
-	 *
556
-	 * @param mixed $addressBookId
557
-	 * @param string $cardUri
558
-	 * @return array
559
-	 */
560
-	public function getCard($addressBookId, $cardUri) {
561
-		$query = $this->db->getQueryBuilder();
562
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
563
-			->from($this->dbCardsTable)
564
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
565
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
566
-			->setMaxResults(1);
567
-
568
-		$result = $query->execute();
569
-		$row = $result->fetch();
570
-		if (!$row) {
571
-			return false;
572
-		}
573
-		$row['etag'] = '"' . $row['etag'] . '"';
574
-
575
-		$modified = false;
576
-		$row['carddata'] = $this->readBlob($row['carddata'], $modified);
577
-		if ($modified) {
578
-			$row['size'] = strlen($row['carddata']);
579
-		}
580
-
581
-		return $row;
582
-	}
583
-
584
-	/**
585
-	 * Returns a list of cards.
586
-	 *
587
-	 * This method should work identical to getCard, but instead return all the
588
-	 * cards in the list as an array.
589
-	 *
590
-	 * If the backend supports this, it may allow for some speed-ups.
591
-	 *
592
-	 * @param mixed $addressBookId
593
-	 * @param string[] $uris
594
-	 * @return array
595
-	 */
596
-	public function getMultipleCards($addressBookId, array $uris) {
597
-		if (empty($uris)) {
598
-			return [];
599
-		}
600
-
601
-		$chunks = array_chunk($uris, 100);
602
-		$cards = [];
603
-
604
-		$query = $this->db->getQueryBuilder();
605
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
606
-			->from($this->dbCardsTable)
607
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
608
-			->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
609
-
610
-		foreach ($chunks as $uris) {
611
-			$query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
612
-			$result = $query->execute();
613
-
614
-			while ($row = $result->fetch()) {
615
-				$row['etag'] = '"' . $row['etag'] . '"';
616
-
617
-				$modified = false;
618
-				$row['carddata'] = $this->readBlob($row['carddata'], $modified);
619
-				if ($modified) {
620
-					$row['size'] = strlen($row['carddata']);
621
-				}
622
-
623
-				$cards[] = $row;
624
-			}
625
-			$result->closeCursor();
626
-		}
627
-		return $cards;
628
-	}
629
-
630
-	/**
631
-	 * Creates a new card.
632
-	 *
633
-	 * The addressbook id will be passed as the first argument. This is the
634
-	 * same id as it is returned from the getAddressBooksForUser method.
635
-	 *
636
-	 * The cardUri is a base uri, and doesn't include the full path. The
637
-	 * cardData argument is the vcard body, and is passed as a string.
638
-	 *
639
-	 * It is possible to return an ETag from this method. This ETag is for the
640
-	 * newly created resource, and must be enclosed with double quotes (that
641
-	 * is, the string itself must contain the double quotes).
642
-	 *
643
-	 * You should only return the ETag if you store the carddata as-is. If a
644
-	 * subsequent GET request on the same card does not have the same body,
645
-	 * byte-by-byte and you did return an ETag here, clients tend to get
646
-	 * confused.
647
-	 *
648
-	 * If you don't return an ETag, you can just return null.
649
-	 *
650
-	 * @param mixed $addressBookId
651
-	 * @param string $cardUri
652
-	 * @param string $cardData
653
-	 * @return string
654
-	 */
655
-	public function createCard($addressBookId, $cardUri, $cardData) {
656
-		$etag = md5($cardData);
657
-		$uid = $this->getUID($cardData);
658
-
659
-		$q = $this->db->getQueryBuilder();
660
-		$q->select('uid')
661
-			->from($this->dbCardsTable)
662
-			->where($q->expr()->eq('addressbookid', $q->createNamedParameter($addressBookId)))
663
-			->andWhere($q->expr()->eq('uid', $q->createNamedParameter($uid)))
664
-			->setMaxResults(1);
665
-		$result = $q->execute();
666
-		$count = (bool)$result->fetchColumn();
667
-		$result->closeCursor();
668
-		if ($count) {
669
-			throw new \Sabre\DAV\Exception\BadRequest('VCard object with uid already exists in this addressbook collection.');
670
-		}
671
-
672
-		$query = $this->db->getQueryBuilder();
673
-		$query->insert('cards')
674
-			->values([
675
-				'carddata' => $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB),
676
-				'uri' => $query->createNamedParameter($cardUri),
677
-				'lastmodified' => $query->createNamedParameter(time()),
678
-				'addressbookid' => $query->createNamedParameter($addressBookId),
679
-				'size' => $query->createNamedParameter(strlen($cardData)),
680
-				'etag' => $query->createNamedParameter($etag),
681
-				'uid' => $query->createNamedParameter($uid),
682
-			])
683
-			->execute();
684
-
685
-		$etagCacheKey = "$addressBookId#$cardUri";
686
-		$this->etagCache[$etagCacheKey] = $etag;
687
-
688
-		$this->addChange($addressBookId, $cardUri, 1);
689
-		$this->updateProperties($addressBookId, $cardUri, $cardData);
690
-
691
-		$addressBookData = $this->getAddressBookById($addressBookId);
692
-		$shares = $this->getShares($addressBookId);
693
-		$objectRow = $this->getCard($addressBookId, $cardUri);
694
-		$this->dispatcher->dispatchTyped(new CardCreatedEvent((int)$addressBookId, $addressBookData, $shares, $objectRow));
695
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
696
-			new GenericEvent(null, [
697
-				'addressBookId' => $addressBookId,
698
-				'cardUri' => $cardUri,
699
-				'cardData' => $cardData]));
700
-
701
-		return '"' . $etag . '"';
702
-	}
703
-
704
-	/**
705
-	 * Updates a card.
706
-	 *
707
-	 * The addressbook id will be passed as the first argument. This is the
708
-	 * same id as it is returned from the getAddressBooksForUser method.
709
-	 *
710
-	 * The cardUri is a base uri, and doesn't include the full path. The
711
-	 * cardData argument is the vcard body, and is passed as a string.
712
-	 *
713
-	 * It is possible to return an ETag from this method. This ETag should
714
-	 * match that of the updated resource, and must be enclosed with double
715
-	 * quotes (that is: the string itself must contain the actual quotes).
716
-	 *
717
-	 * You should only return the ETag if you store the carddata as-is. If a
718
-	 * subsequent GET request on the same card does not have the same body,
719
-	 * byte-by-byte and you did return an ETag here, clients tend to get
720
-	 * confused.
721
-	 *
722
-	 * If you don't return an ETag, you can just return null.
723
-	 *
724
-	 * @param mixed $addressBookId
725
-	 * @param string $cardUri
726
-	 * @param string $cardData
727
-	 * @return string
728
-	 */
729
-	public function updateCard($addressBookId, $cardUri, $cardData) {
730
-		$uid = $this->getUID($cardData);
731
-		$etag = md5($cardData);
732
-		$query = $this->db->getQueryBuilder();
733
-
734
-		// check for recently stored etag and stop if it is the same
735
-		$etagCacheKey = "$addressBookId#$cardUri";
736
-		if (isset($this->etagCache[$etagCacheKey]) && $this->etagCache[$etagCacheKey] === $etag) {
737
-			return '"' . $etag . '"';
738
-		}
739
-
740
-		$query->update($this->dbCardsTable)
741
-			->set('carddata', $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB))
742
-			->set('lastmodified', $query->createNamedParameter(time()))
743
-			->set('size', $query->createNamedParameter(strlen($cardData)))
744
-			->set('etag', $query->createNamedParameter($etag))
745
-			->set('uid', $query->createNamedParameter($uid))
746
-			->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
747
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
748
-			->execute();
749
-
750
-		$this->etagCache[$etagCacheKey] = $etag;
751
-
752
-		$this->addChange($addressBookId, $cardUri, 2);
753
-		$this->updateProperties($addressBookId, $cardUri, $cardData);
754
-
755
-		$addressBookData = $this->getAddressBookById($addressBookId);
756
-		$shares = $this->getShares($addressBookId);
757
-		$objectRow = $this->getCard($addressBookId, $cardUri);
758
-		$this->dispatcher->dispatchTyped(new CardUpdatedEvent((int)$addressBookId, $addressBookData, $shares, $objectRow));
759
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
760
-			new GenericEvent(null, [
761
-				'addressBookId' => $addressBookId,
762
-				'cardUri' => $cardUri,
763
-				'cardData' => $cardData]));
764
-
765
-		return '"' . $etag . '"';
766
-	}
767
-
768
-	/**
769
-	 * Deletes a card
770
-	 *
771
-	 * @param mixed $addressBookId
772
-	 * @param string $cardUri
773
-	 * @return bool
774
-	 */
775
-	public function deleteCard($addressBookId, $cardUri) {
776
-		$addressBookData = $this->getAddressBookById($addressBookId);
777
-		$shares = $this->getShares($addressBookId);
778
-		$objectRow = $this->getCard($addressBookId, $cardUri);
779
-
780
-		try {
781
-			$cardId = $this->getCardId($addressBookId, $cardUri);
782
-		} catch (\InvalidArgumentException $e) {
783
-			$cardId = null;
784
-		}
785
-		$query = $this->db->getQueryBuilder();
786
-		$ret = $query->delete($this->dbCardsTable)
787
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
788
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
789
-			->execute();
790
-
791
-		$this->addChange($addressBookId, $cardUri, 3);
792
-
793
-		if ($ret === 1) {
794
-			if ($cardId !== null) {
795
-				$this->dispatcher->dispatchTyped(new CardDeletedEvent((int)$addressBookId, $addressBookData, $shares, $objectRow));
796
-				$this->legacyDispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
797
-					new GenericEvent(null, [
798
-						'addressBookId' => $addressBookId,
799
-						'cardUri' => $cardUri]));
800
-
801
-				$this->purgeProperties($addressBookId, $cardId);
802
-			}
803
-			return true;
804
-		}
805
-
806
-		return false;
807
-	}
808
-
809
-	/**
810
-	 * The getChanges method returns all the changes that have happened, since
811
-	 * the specified syncToken in the specified address book.
812
-	 *
813
-	 * This function should return an array, such as the following:
814
-	 *
815
-	 * [
816
-	 *   'syncToken' => 'The current synctoken',
817
-	 *   'added'   => [
818
-	 *      'new.txt',
819
-	 *   ],
820
-	 *   'modified'   => [
821
-	 *      'modified.txt',
822
-	 *   ],
823
-	 *   'deleted' => [
824
-	 *      'foo.php.bak',
825
-	 *      'old.txt'
826
-	 *   ]
827
-	 * ];
828
-	 *
829
-	 * The returned syncToken property should reflect the *current* syncToken
830
-	 * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
831
-	 * property. This is needed here too, to ensure the operation is atomic.
832
-	 *
833
-	 * If the $syncToken argument is specified as null, this is an initial
834
-	 * sync, and all members should be reported.
835
-	 *
836
-	 * The modified property is an array of nodenames that have changed since
837
-	 * the last token.
838
-	 *
839
-	 * The deleted property is an array with nodenames, that have been deleted
840
-	 * from collection.
841
-	 *
842
-	 * The $syncLevel argument is basically the 'depth' of the report. If it's
843
-	 * 1, you only have to report changes that happened only directly in
844
-	 * immediate descendants. If it's 2, it should also include changes from
845
-	 * the nodes below the child collections. (grandchildren)
846
-	 *
847
-	 * The $limit argument allows a client to specify how many results should
848
-	 * be returned at most. If the limit is not specified, it should be treated
849
-	 * as infinite.
850
-	 *
851
-	 * If the limit (infinite or not) is higher than you're willing to return,
852
-	 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
853
-	 *
854
-	 * If the syncToken is expired (due to data cleanup) or unknown, you must
855
-	 * return null.
856
-	 *
857
-	 * The limit is 'suggestive'. You are free to ignore it.
858
-	 *
859
-	 * @param string $addressBookId
860
-	 * @param string $syncToken
861
-	 * @param int $syncLevel
862
-	 * @param int $limit
863
-	 * @return array
864
-	 */
865
-	public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
866
-		// Current synctoken
867
-		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
868
-		$stmt->execute([$addressBookId]);
869
-		$currentToken = $stmt->fetchColumn(0);
870
-
871
-		if (is_null($currentToken)) {
872
-			return null;
873
-		}
874
-
875
-		$result = [
876
-			'syncToken' => $currentToken,
877
-			'added' => [],
878
-			'modified' => [],
879
-			'deleted' => [],
880
-		];
881
-
882
-		if ($syncToken) {
883
-			$query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
884
-			if ($limit > 0) {
885
-				$query .= " LIMIT " . (int)$limit;
886
-			}
887
-
888
-			// Fetching all changes
889
-			$stmt = $this->db->prepare($query);
890
-			$stmt->execute([$syncToken, $currentToken, $addressBookId]);
891
-
892
-			$changes = [];
893
-
894
-			// This loop ensures that any duplicates are overwritten, only the
895
-			// last change on a node is relevant.
896
-			while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
897
-				$changes[$row['uri']] = $row['operation'];
898
-			}
899
-
900
-			foreach ($changes as $uri => $operation) {
901
-				switch ($operation) {
902
-					case 1:
903
-						$result['added'][] = $uri;
904
-						break;
905
-					case 2:
906
-						$result['modified'][] = $uri;
907
-						break;
908
-					case 3:
909
-						$result['deleted'][] = $uri;
910
-						break;
911
-				}
912
-			}
913
-		} else {
914
-			// No synctoken supplied, this is the initial sync.
915
-			$query = "SELECT `uri` FROM `*PREFIX*cards` WHERE `addressbookid` = ?";
916
-			$stmt = $this->db->prepare($query);
917
-			$stmt->execute([$addressBookId]);
918
-
919
-			$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
920
-		}
921
-		return $result;
922
-	}
923
-
924
-	/**
925
-	 * Adds a change record to the addressbookchanges table.
926
-	 *
927
-	 * @param mixed $addressBookId
928
-	 * @param string $objectUri
929
-	 * @param int $operation 1 = add, 2 = modify, 3 = delete
930
-	 * @return void
931
-	 */
932
-	protected function addChange($addressBookId, $objectUri, $operation) {
933
-		$sql = 'INSERT INTO `*PREFIX*addressbookchanges`(`uri`, `synctoken`, `addressbookid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*addressbooks` WHERE `id` = ?';
934
-		$stmt = $this->db->prepare($sql);
935
-		$stmt->execute([
936
-			$objectUri,
937
-			$addressBookId,
938
-			$operation,
939
-			$addressBookId
940
-		]);
941
-		$stmt = $this->db->prepare('UPDATE `*PREFIX*addressbooks` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
942
-		$stmt->execute([
943
-			$addressBookId
944
-		]);
945
-	}
946
-
947
-	/**
948
-	 * @param resource|string $cardData
949
-	 * @param bool $modified
950
-	 * @return string
951
-	 */
952
-	private function readBlob($cardData, &$modified = false) {
953
-		if (is_resource($cardData)) {
954
-			$cardData = stream_get_contents($cardData);
955
-		}
956
-
957
-		$cardDataArray = explode("\r\n", $cardData);
958
-
959
-		$cardDataFiltered = [];
960
-		$removingPhoto = false;
961
-		foreach ($cardDataArray as $line) {
962
-			if (strpos($line, 'PHOTO:data:') === 0
963
-				&& strpos($line, 'PHOTO:data:image/') !== 0) {
964
-				// Filter out PHOTO data of non-images
965
-				$removingPhoto = true;
966
-				$modified = true;
967
-				continue;
968
-			}
969
-
970
-			if ($removingPhoto) {
971
-				if (strpos($line, ' ') === 0) {
972
-					continue;
973
-				}
974
-				// No leading space means this is a new property
975
-				$removingPhoto = false;
976
-			}
977
-
978
-			$cardDataFiltered[] = $line;
979
-		}
980
-
981
-		return implode("\r\n", $cardDataFiltered);
982
-	}
983
-
984
-	/**
985
-	 * @param IShareable $shareable
986
-	 * @param string[] $add
987
-	 * @param string[] $remove
988
-	 */
989
-	public function updateShares(IShareable $shareable, $add, $remove) {
990
-		$addressBookId = $shareable->getResourceId();
991
-		$addressBookData = $this->getAddressBookById($addressBookId);
992
-		$oldShares = $this->getShares($addressBookId);
993
-
994
-		$this->sharingBackend->updateShares($shareable, $add, $remove);
995
-
996
-		$this->dispatcher->dispatchTyped(new AddressBookShareUpdatedEvent($addressBookId, $addressBookData, $oldShares, $add, $remove));
997
-	}
998
-
999
-	/**
1000
-	 * Search contacts in a specific address-book
1001
-	 *
1002
-	 * @param int $addressBookId
1003
-	 * @param string $pattern which should match within the $searchProperties
1004
-	 * @param array $searchProperties defines the properties within the query pattern should match
1005
-	 * @param array $options = array() to define the search behavior
1006
-	 *    - 'escape_like_param' - If set to false wildcards _ and % are not escaped, otherwise they are
1007
-	 *    - 'limit' - Set a numeric limit for the search results
1008
-	 *    - 'offset' - Set the offset for the limited search results
1009
-	 * @return array an array of contacts which are arrays of key-value-pairs
1010
-	 */
1011
-	public function search($addressBookId, $pattern, $searchProperties, $options = []): array {
1012
-		return $this->searchByAddressBookIds([$addressBookId], $pattern, $searchProperties, $options);
1013
-	}
1014
-
1015
-	/**
1016
-	 * Search contacts in all address-books accessible by a user
1017
-	 *
1018
-	 * @param string $principalUri
1019
-	 * @param string $pattern
1020
-	 * @param array $searchProperties
1021
-	 * @param array $options
1022
-	 * @return array
1023
-	 */
1024
-	public function searchPrincipalUri(string $principalUri,
1025
-									   string $pattern,
1026
-									   array $searchProperties,
1027
-									   array $options = []): array {
1028
-		$addressBookIds = array_map(static function ($row):int {
1029
-			return (int) $row['id'];
1030
-		}, $this->getAddressBooksForUser($principalUri));
1031
-
1032
-		return $this->searchByAddressBookIds($addressBookIds, $pattern, $searchProperties, $options);
1033
-	}
1034
-
1035
-	/**
1036
-	 * @param array $addressBookIds
1037
-	 * @param string $pattern
1038
-	 * @param array $searchProperties
1039
-	 * @param array $options
1040
-	 * @return array
1041
-	 */
1042
-	private function searchByAddressBookIds(array $addressBookIds,
1043
-											string $pattern,
1044
-											array $searchProperties,
1045
-											array $options = []): array {
1046
-		$escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false;
1047
-
1048
-		$query2 = $this->db->getQueryBuilder();
1049
-
1050
-		$addressBookOr = $query2->expr()->orX();
1051
-		foreach ($addressBookIds as $addressBookId) {
1052
-			$addressBookOr->add($query2->expr()->eq('cp.addressbookid', $query2->createNamedParameter($addressBookId)));
1053
-		}
1054
-
1055
-		if ($addressBookOr->count() === 0) {
1056
-			return [];
1057
-		}
1058
-
1059
-		$propertyOr = $query2->expr()->orX();
1060
-		foreach ($searchProperties as $property) {
1061
-			if ($escapePattern) {
1062
-				if ($property === 'EMAIL' && strpos($pattern, ' ') !== false) {
1063
-					// There can be no spaces in emails
1064
-					continue;
1065
-				}
1066
-
1067
-				if ($property === 'CLOUD' && preg_match('/[^a-zA-Z0-9 :_.@\/\-\']/', $pattern) === 1) {
1068
-					// There can be no chars in cloud ids which are not valid for user ids plus :/
1069
-					// worst case: CA61590A-BBBC-423E-84AF-E6DF01455A53@https://my.nxt/srv/
1070
-					continue;
1071
-				}
1072
-			}
1073
-
1074
-			$propertyOr->add($query2->expr()->eq('cp.name', $query2->createNamedParameter($property)));
1075
-		}
1076
-
1077
-		if ($propertyOr->count() === 0) {
1078
-			return [];
1079
-		}
1080
-
1081
-		$query2->selectDistinct('cp.cardid')
1082
-			->from($this->dbCardsPropertiesTable, 'cp')
1083
-			->andWhere($addressBookOr)
1084
-			->andWhere($propertyOr);
1085
-
1086
-		// No need for like when the pattern is empty
1087
-		if ('' !== $pattern) {
1088
-			if (!$escapePattern) {
1089
-				$query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter($pattern)));
1090
-			} else {
1091
-				$query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
1092
-			}
1093
-		}
1094
-
1095
-		if (isset($options['limit'])) {
1096
-			$query2->setMaxResults($options['limit']);
1097
-		}
1098
-		if (isset($options['offset'])) {
1099
-			$query2->setFirstResult($options['offset']);
1100
-		}
1101
-
1102
-		$result = $query2->execute();
1103
-		$matches = $result->fetchAll();
1104
-		$result->closeCursor();
1105
-		$matches = array_map(function ($match) {
1106
-			return (int)$match['cardid'];
1107
-		}, $matches);
1108
-
1109
-		$query = $this->db->getQueryBuilder();
1110
-		$query->select('c.addressbookid', 'c.carddata', 'c.uri')
1111
-			->from($this->dbCardsTable, 'c')
1112
-			->where($query->expr()->in('c.id', $query->createNamedParameter($matches, IQueryBuilder::PARAM_INT_ARRAY)));
1113
-
1114
-		$result = $query->execute();
1115
-		$cards = $result->fetchAll();
1116
-
1117
-		$result->closeCursor();
1118
-
1119
-		return array_map(function ($array) {
1120
-			$array['addressbookid'] = (int) $array['addressbookid'];
1121
-			$modified = false;
1122
-			$array['carddata'] = $this->readBlob($array['carddata'], $modified);
1123
-			if ($modified) {
1124
-				$array['size'] = strlen($array['carddata']);
1125
-			}
1126
-			return $array;
1127
-		}, $cards);
1128
-	}
1129
-
1130
-	/**
1131
-	 * @param int $bookId
1132
-	 * @param string $name
1133
-	 * @return array
1134
-	 */
1135
-	public function collectCardProperties($bookId, $name) {
1136
-		$query = $this->db->getQueryBuilder();
1137
-		$result = $query->selectDistinct('value')
1138
-			->from($this->dbCardsPropertiesTable)
1139
-			->where($query->expr()->eq('name', $query->createNamedParameter($name)))
1140
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($bookId)))
1141
-			->execute();
1142
-
1143
-		$all = $result->fetchAll(PDO::FETCH_COLUMN);
1144
-		$result->closeCursor();
1145
-
1146
-		return $all;
1147
-	}
1148
-
1149
-	/**
1150
-	 * get URI from a given contact
1151
-	 *
1152
-	 * @param int $id
1153
-	 * @return string
1154
-	 */
1155
-	public function getCardUri($id) {
1156
-		$query = $this->db->getQueryBuilder();
1157
-		$query->select('uri')->from($this->dbCardsTable)
1158
-			->where($query->expr()->eq('id', $query->createParameter('id')))
1159
-			->setParameter('id', $id);
1160
-
1161
-		$result = $query->execute();
1162
-		$uri = $result->fetch();
1163
-		$result->closeCursor();
1164
-
1165
-		if (!isset($uri['uri'])) {
1166
-			throw new \InvalidArgumentException('Card does not exists: ' . $id);
1167
-		}
1168
-
1169
-		return $uri['uri'];
1170
-	}
1171
-
1172
-	/**
1173
-	 * return contact with the given URI
1174
-	 *
1175
-	 * @param int $addressBookId
1176
-	 * @param string $uri
1177
-	 * @returns array
1178
-	 */
1179
-	public function getContact($addressBookId, $uri) {
1180
-		$result = [];
1181
-		$query = $this->db->getQueryBuilder();
1182
-		$query->select('*')->from($this->dbCardsTable)
1183
-			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1184
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1185
-		$queryResult = $query->execute();
1186
-		$contact = $queryResult->fetch();
1187
-		$queryResult->closeCursor();
1188
-
1189
-		if (is_array($contact)) {
1190
-			$modified = false;
1191
-			$contact['etag'] = '"' . $contact['etag'] . '"';
1192
-			$contact['carddata'] = $this->readBlob($contact['carddata'], $modified);
1193
-			if ($modified) {
1194
-				$contact['size'] = strlen($contact['carddata']);
1195
-			}
1196
-
1197
-			$result = $contact;
1198
-		}
1199
-
1200
-		return $result;
1201
-	}
1202
-
1203
-	/**
1204
-	 * Returns the list of people whom this address book is shared with.
1205
-	 *
1206
-	 * Every element in this array should have the following properties:
1207
-	 *   * href - Often a mailto: address
1208
-	 *   * commonName - Optional, for example a first + last name
1209
-	 *   * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
1210
-	 *   * readOnly - boolean
1211
-	 *   * summary - Optional, a description for the share
1212
-	 *
1213
-	 * @return array
1214
-	 */
1215
-	public function getShares($addressBookId) {
1216
-		return $this->sharingBackend->getShares($addressBookId);
1217
-	}
1218
-
1219
-	/**
1220
-	 * update properties table
1221
-	 *
1222
-	 * @param int $addressBookId
1223
-	 * @param string $cardUri
1224
-	 * @param string $vCardSerialized
1225
-	 */
1226
-	protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) {
1227
-		$cardId = $this->getCardId($addressBookId, $cardUri);
1228
-		$vCard = $this->readCard($vCardSerialized);
1229
-
1230
-		$this->purgeProperties($addressBookId, $cardId);
1231
-
1232
-		$query = $this->db->getQueryBuilder();
1233
-		$query->insert($this->dbCardsPropertiesTable)
1234
-			->values(
1235
-				[
1236
-					'addressbookid' => $query->createNamedParameter($addressBookId),
1237
-					'cardid' => $query->createNamedParameter($cardId),
1238
-					'name' => $query->createParameter('name'),
1239
-					'value' => $query->createParameter('value'),
1240
-					'preferred' => $query->createParameter('preferred')
1241
-				]
1242
-			);
1243
-
1244
-		foreach ($vCard->children() as $property) {
1245
-			if (!in_array($property->name, self::$indexProperties)) {
1246
-				continue;
1247
-			}
1248
-			$preferred = 0;
1249
-			foreach ($property->parameters as $parameter) {
1250
-				if ($parameter->name === 'TYPE' && strtoupper($parameter->getValue()) === 'PREF') {
1251
-					$preferred = 1;
1252
-					break;
1253
-				}
1254
-			}
1255
-			$query->setParameter('name', $property->name);
1256
-			$query->setParameter('value', mb_substr($property->getValue(), 0, 254));
1257
-			$query->setParameter('preferred', $preferred);
1258
-			$query->execute();
1259
-		}
1260
-	}
1261
-
1262
-	/**
1263
-	 * read vCard data into a vCard object
1264
-	 *
1265
-	 * @param string $cardData
1266
-	 * @return VCard
1267
-	 */
1268
-	protected function readCard($cardData) {
1269
-		return Reader::read($cardData);
1270
-	}
1271
-
1272
-	/**
1273
-	 * delete all properties from a given card
1274
-	 *
1275
-	 * @param int $addressBookId
1276
-	 * @param int $cardId
1277
-	 */
1278
-	protected function purgeProperties($addressBookId, $cardId) {
1279
-		$query = $this->db->getQueryBuilder();
1280
-		$query->delete($this->dbCardsPropertiesTable)
1281
-			->where($query->expr()->eq('cardid', $query->createNamedParameter($cardId)))
1282
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1283
-		$query->execute();
1284
-	}
1285
-
1286
-	/**
1287
-	 * get ID from a given contact
1288
-	 *
1289
-	 * @param int $addressBookId
1290
-	 * @param string $uri
1291
-	 * @return int
1292
-	 */
1293
-	protected function getCardId($addressBookId, $uri) {
1294
-		$query = $this->db->getQueryBuilder();
1295
-		$query->select('id')->from($this->dbCardsTable)
1296
-			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1297
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1298
-
1299
-		$result = $query->execute();
1300
-		$cardIds = $result->fetch();
1301
-		$result->closeCursor();
1302
-
1303
-		if (!isset($cardIds['id'])) {
1304
-			throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1305
-		}
1306
-
1307
-		return (int)$cardIds['id'];
1308
-	}
1309
-
1310
-	/**
1311
-	 * For shared address books the sharee is set in the ACL of the address book
1312
-	 *
1313
-	 * @param $addressBookId
1314
-	 * @param $acl
1315
-	 * @return array
1316
-	 */
1317
-	public function applyShareAcl($addressBookId, $acl) {
1318
-		return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
1319
-	}
1320
-
1321
-	private function convertPrincipal($principalUri, $toV2) {
1322
-		if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
1323
-			list(, $name) = \Sabre\Uri\split($principalUri);
1324
-			if ($toV2 === true) {
1325
-				return "principals/users/$name";
1326
-			}
1327
-			return "principals/$name";
1328
-		}
1329
-		return $principalUri;
1330
-	}
1331
-
1332
-	private function addOwnerPrincipal(&$addressbookInfo) {
1333
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1334
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1335
-		if (isset($addressbookInfo[$ownerPrincipalKey])) {
1336
-			$uri = $addressbookInfo[$ownerPrincipalKey];
1337
-		} else {
1338
-			$uri = $addressbookInfo['principaluri'];
1339
-		}
1340
-
1341
-		$principalInformation = $this->principalBackend->getPrincipalByPath($uri);
1342
-		if (isset($principalInformation['{DAV:}displayname'])) {
1343
-			$addressbookInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
1344
-		}
1345
-	}
1346
-
1347
-	/**
1348
-	 * Extract UID from vcard
1349
-	 *
1350
-	 * @param string $cardData the vcard raw data
1351
-	 * @return string the uid
1352
-	 * @throws BadRequest if no UID is available
1353
-	 */
1354
-	private function getUID($cardData) {
1355
-		if ($cardData != '') {
1356
-			$vCard = Reader::read($cardData);
1357
-			if ($vCard->UID) {
1358
-				$uid = $vCard->UID->getValue();
1359
-				return $uid;
1360
-			}
1361
-			// should already be handled, but just in case
1362
-			throw new BadRequest('vCards on CardDAV servers MUST have a UID property');
1363
-		}
1364
-		// should already be handled, but just in case
1365
-		throw new BadRequest('vCard can not be empty');
1366
-	}
65
+    public const PERSONAL_ADDRESSBOOK_URI = 'contacts';
66
+    public const PERSONAL_ADDRESSBOOK_NAME = 'Contacts';
67
+
68
+    /** @var Principal */
69
+    private $principalBackend;
70
+
71
+    /** @var string */
72
+    private $dbCardsTable = 'cards';
73
+
74
+    /** @var string */
75
+    private $dbCardsPropertiesTable = 'cards_properties';
76
+
77
+    /** @var IDBConnection */
78
+    private $db;
79
+
80
+    /** @var Backend */
81
+    private $sharingBackend;
82
+
83
+    /** @var array properties to index */
84
+    public static $indexProperties = [
85
+        'BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME',
86
+        'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO', 'CLOUD'];
87
+
88
+    /**
89
+     * @var string[] Map of uid => display name
90
+     */
91
+    protected $userDisplayNames;
92
+
93
+    /** @var IUserManager */
94
+    private $userManager;
95
+
96
+    /** @var IEventDispatcher */
97
+    private $dispatcher;
98
+
99
+    /** @var EventDispatcherInterface */
100
+    private $legacyDispatcher;
101
+
102
+    private $etagCache = [];
103
+
104
+    /**
105
+     * CardDavBackend constructor.
106
+     *
107
+     * @param IDBConnection $db
108
+     * @param Principal $principalBackend
109
+     * @param IUserManager $userManager
110
+     * @param IGroupManager $groupManager
111
+     * @param IEventDispatcher $dispatcher
112
+     * @param EventDispatcherInterface $legacyDispatcher
113
+     */
114
+    public function __construct(IDBConnection $db,
115
+                                Principal $principalBackend,
116
+                                IUserManager $userManager,
117
+                                IGroupManager $groupManager,
118
+                                IEventDispatcher $dispatcher,
119
+                                EventDispatcherInterface $legacyDispatcher) {
120
+        $this->db = $db;
121
+        $this->principalBackend = $principalBackend;
122
+        $this->userManager = $userManager;
123
+        $this->dispatcher = $dispatcher;
124
+        $this->legacyDispatcher = $legacyDispatcher;
125
+        $this->sharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'addressbook');
126
+    }
127
+
128
+    /**
129
+     * Return the number of address books for a principal
130
+     *
131
+     * @param $principalUri
132
+     * @return int
133
+     */
134
+    public function getAddressBooksForUserCount($principalUri) {
135
+        $principalUri = $this->convertPrincipal($principalUri, true);
136
+        $query = $this->db->getQueryBuilder();
137
+        $query->select($query->func()->count('*'))
138
+            ->from('addressbooks')
139
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
140
+
141
+        $result = $query->execute();
142
+        $column = (int) $result->fetchColumn();
143
+        $result->closeCursor();
144
+        return $column;
145
+    }
146
+
147
+    /**
148
+     * Returns the list of address books for a specific user.
149
+     *
150
+     * Every addressbook should have the following properties:
151
+     *   id - an arbitrary unique id
152
+     *   uri - the 'basename' part of the url
153
+     *   principaluri - Same as the passed parameter
154
+     *
155
+     * Any additional clark-notation property may be passed besides this. Some
156
+     * common ones are :
157
+     *   {DAV:}displayname
158
+     *   {urn:ietf:params:xml:ns:carddav}addressbook-description
159
+     *   {http://calendarserver.org/ns/}getctag
160
+     *
161
+     * @param string $principalUri
162
+     * @return array
163
+     */
164
+    public function getAddressBooksForUser($principalUri) {
165
+        $principalUriOriginal = $principalUri;
166
+        $principalUri = $this->convertPrincipal($principalUri, true);
167
+        $query = $this->db->getQueryBuilder();
168
+        $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
169
+            ->from('addressbooks')
170
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
171
+
172
+        $addressBooks = [];
173
+
174
+        $result = $query->execute();
175
+        while ($row = $result->fetch()) {
176
+            $addressBooks[$row['id']] = [
177
+                'id' => $row['id'],
178
+                'uri' => $row['uri'],
179
+                'principaluri' => $this->convertPrincipal($row['principaluri'], false),
180
+                '{DAV:}displayname' => $row['displayname'],
181
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
182
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
183
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
184
+            ];
185
+
186
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
187
+        }
188
+        $result->closeCursor();
189
+
190
+        // query for shared addressbooks
191
+        $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
192
+        $principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
193
+
194
+        $principals = array_map(function ($principal) {
195
+            return urldecode($principal);
196
+        }, $principals);
197
+        $principals[] = $principalUri;
198
+
199
+        $query = $this->db->getQueryBuilder();
200
+        $result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
201
+            ->from('dav_shares', 's')
202
+            ->join('s', 'addressbooks', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
203
+            ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
204
+            ->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
205
+            ->setParameter('type', 'addressbook')
206
+            ->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
207
+            ->execute();
208
+
209
+        $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
210
+        while ($row = $result->fetch()) {
211
+            if ($row['principaluri'] === $principalUri) {
212
+                continue;
213
+            }
214
+
215
+            $readOnly = (int)$row['access'] === Backend::ACCESS_READ;
216
+            if (isset($addressBooks[$row['id']])) {
217
+                if ($readOnly) {
218
+                    // New share can not have more permissions then the old one.
219
+                    continue;
220
+                }
221
+                if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) &&
222
+                    $addressBooks[$row['id']][$readOnlyPropertyName] === 0) {
223
+                    // Old share is already read-write, no more permissions can be gained
224
+                    continue;
225
+                }
226
+            }
227
+
228
+            list(, $name) = \Sabre\Uri\split($row['principaluri']);
229
+            $uri = $row['uri'] . '_shared_by_' . $name;
230
+            $displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
231
+
232
+            $addressBooks[$row['id']] = [
233
+                'id' => $row['id'],
234
+                'uri' => $uri,
235
+                'principaluri' => $principalUriOriginal,
236
+                '{DAV:}displayname' => $displayName,
237
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
238
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
239
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
240
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
241
+                $readOnlyPropertyName => $readOnly,
242
+            ];
243
+
244
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
245
+        }
246
+        $result->closeCursor();
247
+
248
+        return array_values($addressBooks);
249
+    }
250
+
251
+    public function getUsersOwnAddressBooks($principalUri) {
252
+        $principalUri = $this->convertPrincipal($principalUri, true);
253
+        $query = $this->db->getQueryBuilder();
254
+        $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
255
+            ->from('addressbooks')
256
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
257
+
258
+        $addressBooks = [];
259
+
260
+        $result = $query->execute();
261
+        while ($row = $result->fetch()) {
262
+            $addressBooks[$row['id']] = [
263
+                'id' => $row['id'],
264
+                'uri' => $row['uri'],
265
+                'principaluri' => $this->convertPrincipal($row['principaluri'], false),
266
+                '{DAV:}displayname' => $row['displayname'],
267
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
268
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
269
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
270
+            ];
271
+
272
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
273
+        }
274
+        $result->closeCursor();
275
+
276
+        return array_values($addressBooks);
277
+    }
278
+
279
+    private function getUserDisplayName($uid) {
280
+        if (!isset($this->userDisplayNames[$uid])) {
281
+            $user = $this->userManager->get($uid);
282
+
283
+            if ($user instanceof IUser) {
284
+                $this->userDisplayNames[$uid] = $user->getDisplayName();
285
+            } else {
286
+                $this->userDisplayNames[$uid] = $uid;
287
+            }
288
+        }
289
+
290
+        return $this->userDisplayNames[$uid];
291
+    }
292
+
293
+    /**
294
+     * @param int $addressBookId
295
+     */
296
+    public function getAddressBookById($addressBookId) {
297
+        $query = $this->db->getQueryBuilder();
298
+        $result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
299
+            ->from('addressbooks')
300
+            ->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
301
+            ->execute();
302
+
303
+        $row = $result->fetch();
304
+        $result->closeCursor();
305
+        if ($row === false) {
306
+            return null;
307
+        }
308
+
309
+        $addressBook = [
310
+            'id' => $row['id'],
311
+            'uri' => $row['uri'],
312
+            'principaluri' => $row['principaluri'],
313
+            '{DAV:}displayname' => $row['displayname'],
314
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
315
+            '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
316
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
317
+        ];
318
+
319
+        $this->addOwnerPrincipal($addressBook);
320
+
321
+        return $addressBook;
322
+    }
323
+
324
+    /**
325
+     * @param $addressBookUri
326
+     * @return array|null
327
+     */
328
+    public function getAddressBooksByUri($principal, $addressBookUri) {
329
+        $query = $this->db->getQueryBuilder();
330
+        $result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
331
+            ->from('addressbooks')
332
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri)))
333
+            ->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
334
+            ->setMaxResults(1)
335
+            ->execute();
336
+
337
+        $row = $result->fetch();
338
+        $result->closeCursor();
339
+        if ($row === false) {
340
+            return null;
341
+        }
342
+
343
+        $addressBook = [
344
+            'id' => $row['id'],
345
+            'uri' => $row['uri'],
346
+            'principaluri' => $row['principaluri'],
347
+            '{DAV:}displayname' => $row['displayname'],
348
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
349
+            '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
350
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
351
+        ];
352
+
353
+        $this->addOwnerPrincipal($addressBook);
354
+
355
+        return $addressBook;
356
+    }
357
+
358
+    /**
359
+     * Updates properties for an address book.
360
+     *
361
+     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
362
+     * To do the actual updates, you must tell this object which properties
363
+     * you're going to process with the handle() method.
364
+     *
365
+     * Calling the handle method is like telling the PropPatch object "I
366
+     * promise I can handle updating this property".
367
+     *
368
+     * Read the PropPatch documentation for more info and examples.
369
+     *
370
+     * @param string $addressBookId
371
+     * @param \Sabre\DAV\PropPatch $propPatch
372
+     * @return void
373
+     */
374
+    public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
375
+        $supportedProperties = [
376
+            '{DAV:}displayname',
377
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description',
378
+        ];
379
+
380
+        $propPatch->handle($supportedProperties, function ($mutations) use ($addressBookId) {
381
+            $updates = [];
382
+            foreach ($mutations as $property => $newValue) {
383
+                switch ($property) {
384
+                    case '{DAV:}displayname':
385
+                        $updates['displayname'] = $newValue;
386
+                        break;
387
+                    case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
388
+                        $updates['description'] = $newValue;
389
+                        break;
390
+                }
391
+            }
392
+            $query = $this->db->getQueryBuilder();
393
+            $query->update('addressbooks');
394
+
395
+            foreach ($updates as $key => $value) {
396
+                $query->set($key, $query->createNamedParameter($value));
397
+            }
398
+            $query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
399
+                ->execute();
400
+
401
+            $this->addChange($addressBookId, "", 2);
402
+
403
+            $addressBookRow = $this->getAddressBookById((int)$addressBookId);
404
+            $shares = $this->getShares($addressBookId);
405
+            $this->dispatcher->dispatchTyped(new AddressBookUpdatedEvent((int)$addressBookId, $addressBookRow, $shares, $mutations));
406
+
407
+            return true;
408
+        });
409
+    }
410
+
411
+    /**
412
+     * Creates a new address book
413
+     *
414
+     * @param string $principalUri
415
+     * @param string $url Just the 'basename' of the url.
416
+     * @param array $properties
417
+     * @return int
418
+     * @throws BadRequest
419
+     */
420
+    public function createAddressBook($principalUri, $url, array $properties) {
421
+        $values = [
422
+            'displayname' => null,
423
+            'description' => null,
424
+            'principaluri' => $principalUri,
425
+            'uri' => $url,
426
+            'synctoken' => 1
427
+        ];
428
+
429
+        foreach ($properties as $property => $newValue) {
430
+            switch ($property) {
431
+                case '{DAV:}displayname':
432
+                    $values['displayname'] = $newValue;
433
+                    break;
434
+                case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
435
+                    $values['description'] = $newValue;
436
+                    break;
437
+                default:
438
+                    throw new BadRequest('Unknown property: ' . $property);
439
+            }
440
+        }
441
+
442
+        // Fallback to make sure the displayname is set. Some clients may refuse
443
+        // to work with addressbooks not having a displayname.
444
+        if (is_null($values['displayname'])) {
445
+            $values['displayname'] = $url;
446
+        }
447
+
448
+        $query = $this->db->getQueryBuilder();
449
+        $query->insert('addressbooks')
450
+            ->values([
451
+                'uri' => $query->createParameter('uri'),
452
+                'displayname' => $query->createParameter('displayname'),
453
+                'description' => $query->createParameter('description'),
454
+                'principaluri' => $query->createParameter('principaluri'),
455
+                'synctoken' => $query->createParameter('synctoken'),
456
+            ])
457
+            ->setParameters($values)
458
+            ->execute();
459
+
460
+        $addressBookId = $query->getLastInsertId();
461
+        $addressBookRow = $this->getAddressBookById($addressBookId);
462
+        $this->dispatcher->dispatchTyped(new AddressBookCreatedEvent((int)$addressBookId, $addressBookRow));
463
+
464
+        return $addressBookId;
465
+    }
466
+
467
+    /**
468
+     * Deletes an entire addressbook and all its contents
469
+     *
470
+     * @param mixed $addressBookId
471
+     * @return void
472
+     */
473
+    public function deleteAddressBook($addressBookId) {
474
+        $addressBookData = $this->getAddressBookById($addressBookId);
475
+        $shares = $this->getShares($addressBookId);
476
+
477
+        $query = $this->db->getQueryBuilder();
478
+        $query->delete($this->dbCardsTable)
479
+            ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
480
+            ->setParameter('addressbookid', $addressBookId)
481
+            ->execute();
482
+
483
+        $query->delete('addressbookchanges')
484
+            ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
485
+            ->setParameter('addressbookid', $addressBookId)
486
+            ->execute();
487
+
488
+        $query->delete('addressbooks')
489
+            ->where($query->expr()->eq('id', $query->createParameter('id')))
490
+            ->setParameter('id', $addressBookId)
491
+            ->execute();
492
+
493
+        $this->sharingBackend->deleteAllShares($addressBookId);
494
+
495
+        $query->delete($this->dbCardsPropertiesTable)
496
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
497
+            ->execute();
498
+
499
+        if ($addressBookData) {
500
+            $this->dispatcher->dispatchTyped(new AddressBookDeletedEvent((int) $addressBookId, $addressBookData, $shares));
501
+        }
502
+    }
503
+
504
+    /**
505
+     * Returns all cards for a specific addressbook id.
506
+     *
507
+     * This method should return the following properties for each card:
508
+     *   * carddata - raw vcard data
509
+     *   * uri - Some unique url
510
+     *   * lastmodified - A unix timestamp
511
+     *
512
+     * It's recommended to also return the following properties:
513
+     *   * etag - A unique etag. This must change every time the card changes.
514
+     *   * size - The size of the card in bytes.
515
+     *
516
+     * If these last two properties are provided, less time will be spent
517
+     * calculating them. If they are specified, you can also ommit carddata.
518
+     * This may speed up certain requests, especially with large cards.
519
+     *
520
+     * @param mixed $addressBookId
521
+     * @return array
522
+     */
523
+    public function getCards($addressBookId) {
524
+        $query = $this->db->getQueryBuilder();
525
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
526
+            ->from($this->dbCardsTable)
527
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
528
+
529
+        $cards = [];
530
+
531
+        $result = $query->execute();
532
+        while ($row = $result->fetch()) {
533
+            $row['etag'] = '"' . $row['etag'] . '"';
534
+
535
+            $modified = false;
536
+            $row['carddata'] = $this->readBlob($row['carddata'], $modified);
537
+            if ($modified) {
538
+                $row['size'] = strlen($row['carddata']);
539
+            }
540
+
541
+            $cards[] = $row;
542
+        }
543
+        $result->closeCursor();
544
+
545
+        return $cards;
546
+    }
547
+
548
+    /**
549
+     * Returns a specific card.
550
+     *
551
+     * The same set of properties must be returned as with getCards. The only
552
+     * exception is that 'carddata' is absolutely required.
553
+     *
554
+     * If the card does not exist, you must return false.
555
+     *
556
+     * @param mixed $addressBookId
557
+     * @param string $cardUri
558
+     * @return array
559
+     */
560
+    public function getCard($addressBookId, $cardUri) {
561
+        $query = $this->db->getQueryBuilder();
562
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
563
+            ->from($this->dbCardsTable)
564
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
565
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
566
+            ->setMaxResults(1);
567
+
568
+        $result = $query->execute();
569
+        $row = $result->fetch();
570
+        if (!$row) {
571
+            return false;
572
+        }
573
+        $row['etag'] = '"' . $row['etag'] . '"';
574
+
575
+        $modified = false;
576
+        $row['carddata'] = $this->readBlob($row['carddata'], $modified);
577
+        if ($modified) {
578
+            $row['size'] = strlen($row['carddata']);
579
+        }
580
+
581
+        return $row;
582
+    }
583
+
584
+    /**
585
+     * Returns a list of cards.
586
+     *
587
+     * This method should work identical to getCard, but instead return all the
588
+     * cards in the list as an array.
589
+     *
590
+     * If the backend supports this, it may allow for some speed-ups.
591
+     *
592
+     * @param mixed $addressBookId
593
+     * @param string[] $uris
594
+     * @return array
595
+     */
596
+    public function getMultipleCards($addressBookId, array $uris) {
597
+        if (empty($uris)) {
598
+            return [];
599
+        }
600
+
601
+        $chunks = array_chunk($uris, 100);
602
+        $cards = [];
603
+
604
+        $query = $this->db->getQueryBuilder();
605
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
606
+            ->from($this->dbCardsTable)
607
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
608
+            ->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
609
+
610
+        foreach ($chunks as $uris) {
611
+            $query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
612
+            $result = $query->execute();
613
+
614
+            while ($row = $result->fetch()) {
615
+                $row['etag'] = '"' . $row['etag'] . '"';
616
+
617
+                $modified = false;
618
+                $row['carddata'] = $this->readBlob($row['carddata'], $modified);
619
+                if ($modified) {
620
+                    $row['size'] = strlen($row['carddata']);
621
+                }
622
+
623
+                $cards[] = $row;
624
+            }
625
+            $result->closeCursor();
626
+        }
627
+        return $cards;
628
+    }
629
+
630
+    /**
631
+     * Creates a new card.
632
+     *
633
+     * The addressbook id will be passed as the first argument. This is the
634
+     * same id as it is returned from the getAddressBooksForUser method.
635
+     *
636
+     * The cardUri is a base uri, and doesn't include the full path. The
637
+     * cardData argument is the vcard body, and is passed as a string.
638
+     *
639
+     * It is possible to return an ETag from this method. This ETag is for the
640
+     * newly created resource, and must be enclosed with double quotes (that
641
+     * is, the string itself must contain the double quotes).
642
+     *
643
+     * You should only return the ETag if you store the carddata as-is. If a
644
+     * subsequent GET request on the same card does not have the same body,
645
+     * byte-by-byte and you did return an ETag here, clients tend to get
646
+     * confused.
647
+     *
648
+     * If you don't return an ETag, you can just return null.
649
+     *
650
+     * @param mixed $addressBookId
651
+     * @param string $cardUri
652
+     * @param string $cardData
653
+     * @return string
654
+     */
655
+    public function createCard($addressBookId, $cardUri, $cardData) {
656
+        $etag = md5($cardData);
657
+        $uid = $this->getUID($cardData);
658
+
659
+        $q = $this->db->getQueryBuilder();
660
+        $q->select('uid')
661
+            ->from($this->dbCardsTable)
662
+            ->where($q->expr()->eq('addressbookid', $q->createNamedParameter($addressBookId)))
663
+            ->andWhere($q->expr()->eq('uid', $q->createNamedParameter($uid)))
664
+            ->setMaxResults(1);
665
+        $result = $q->execute();
666
+        $count = (bool)$result->fetchColumn();
667
+        $result->closeCursor();
668
+        if ($count) {
669
+            throw new \Sabre\DAV\Exception\BadRequest('VCard object with uid already exists in this addressbook collection.');
670
+        }
671
+
672
+        $query = $this->db->getQueryBuilder();
673
+        $query->insert('cards')
674
+            ->values([
675
+                'carddata' => $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB),
676
+                'uri' => $query->createNamedParameter($cardUri),
677
+                'lastmodified' => $query->createNamedParameter(time()),
678
+                'addressbookid' => $query->createNamedParameter($addressBookId),
679
+                'size' => $query->createNamedParameter(strlen($cardData)),
680
+                'etag' => $query->createNamedParameter($etag),
681
+                'uid' => $query->createNamedParameter($uid),
682
+            ])
683
+            ->execute();
684
+
685
+        $etagCacheKey = "$addressBookId#$cardUri";
686
+        $this->etagCache[$etagCacheKey] = $etag;
687
+
688
+        $this->addChange($addressBookId, $cardUri, 1);
689
+        $this->updateProperties($addressBookId, $cardUri, $cardData);
690
+
691
+        $addressBookData = $this->getAddressBookById($addressBookId);
692
+        $shares = $this->getShares($addressBookId);
693
+        $objectRow = $this->getCard($addressBookId, $cardUri);
694
+        $this->dispatcher->dispatchTyped(new CardCreatedEvent((int)$addressBookId, $addressBookData, $shares, $objectRow));
695
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
696
+            new GenericEvent(null, [
697
+                'addressBookId' => $addressBookId,
698
+                'cardUri' => $cardUri,
699
+                'cardData' => $cardData]));
700
+
701
+        return '"' . $etag . '"';
702
+    }
703
+
704
+    /**
705
+     * Updates a card.
706
+     *
707
+     * The addressbook id will be passed as the first argument. This is the
708
+     * same id as it is returned from the getAddressBooksForUser method.
709
+     *
710
+     * The cardUri is a base uri, and doesn't include the full path. The
711
+     * cardData argument is the vcard body, and is passed as a string.
712
+     *
713
+     * It is possible to return an ETag from this method. This ETag should
714
+     * match that of the updated resource, and must be enclosed with double
715
+     * quotes (that is: the string itself must contain the actual quotes).
716
+     *
717
+     * You should only return the ETag if you store the carddata as-is. If a
718
+     * subsequent GET request on the same card does not have the same body,
719
+     * byte-by-byte and you did return an ETag here, clients tend to get
720
+     * confused.
721
+     *
722
+     * If you don't return an ETag, you can just return null.
723
+     *
724
+     * @param mixed $addressBookId
725
+     * @param string $cardUri
726
+     * @param string $cardData
727
+     * @return string
728
+     */
729
+    public function updateCard($addressBookId, $cardUri, $cardData) {
730
+        $uid = $this->getUID($cardData);
731
+        $etag = md5($cardData);
732
+        $query = $this->db->getQueryBuilder();
733
+
734
+        // check for recently stored etag and stop if it is the same
735
+        $etagCacheKey = "$addressBookId#$cardUri";
736
+        if (isset($this->etagCache[$etagCacheKey]) && $this->etagCache[$etagCacheKey] === $etag) {
737
+            return '"' . $etag . '"';
738
+        }
739
+
740
+        $query->update($this->dbCardsTable)
741
+            ->set('carddata', $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB))
742
+            ->set('lastmodified', $query->createNamedParameter(time()))
743
+            ->set('size', $query->createNamedParameter(strlen($cardData)))
744
+            ->set('etag', $query->createNamedParameter($etag))
745
+            ->set('uid', $query->createNamedParameter($uid))
746
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
747
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
748
+            ->execute();
749
+
750
+        $this->etagCache[$etagCacheKey] = $etag;
751
+
752
+        $this->addChange($addressBookId, $cardUri, 2);
753
+        $this->updateProperties($addressBookId, $cardUri, $cardData);
754
+
755
+        $addressBookData = $this->getAddressBookById($addressBookId);
756
+        $shares = $this->getShares($addressBookId);
757
+        $objectRow = $this->getCard($addressBookId, $cardUri);
758
+        $this->dispatcher->dispatchTyped(new CardUpdatedEvent((int)$addressBookId, $addressBookData, $shares, $objectRow));
759
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
760
+            new GenericEvent(null, [
761
+                'addressBookId' => $addressBookId,
762
+                'cardUri' => $cardUri,
763
+                'cardData' => $cardData]));
764
+
765
+        return '"' . $etag . '"';
766
+    }
767
+
768
+    /**
769
+     * Deletes a card
770
+     *
771
+     * @param mixed $addressBookId
772
+     * @param string $cardUri
773
+     * @return bool
774
+     */
775
+    public function deleteCard($addressBookId, $cardUri) {
776
+        $addressBookData = $this->getAddressBookById($addressBookId);
777
+        $shares = $this->getShares($addressBookId);
778
+        $objectRow = $this->getCard($addressBookId, $cardUri);
779
+
780
+        try {
781
+            $cardId = $this->getCardId($addressBookId, $cardUri);
782
+        } catch (\InvalidArgumentException $e) {
783
+            $cardId = null;
784
+        }
785
+        $query = $this->db->getQueryBuilder();
786
+        $ret = $query->delete($this->dbCardsTable)
787
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
788
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
789
+            ->execute();
790
+
791
+        $this->addChange($addressBookId, $cardUri, 3);
792
+
793
+        if ($ret === 1) {
794
+            if ($cardId !== null) {
795
+                $this->dispatcher->dispatchTyped(new CardDeletedEvent((int)$addressBookId, $addressBookData, $shares, $objectRow));
796
+                $this->legacyDispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
797
+                    new GenericEvent(null, [
798
+                        'addressBookId' => $addressBookId,
799
+                        'cardUri' => $cardUri]));
800
+
801
+                $this->purgeProperties($addressBookId, $cardId);
802
+            }
803
+            return true;
804
+        }
805
+
806
+        return false;
807
+    }
808
+
809
+    /**
810
+     * The getChanges method returns all the changes that have happened, since
811
+     * the specified syncToken in the specified address book.
812
+     *
813
+     * This function should return an array, such as the following:
814
+     *
815
+     * [
816
+     *   'syncToken' => 'The current synctoken',
817
+     *   'added'   => [
818
+     *      'new.txt',
819
+     *   ],
820
+     *   'modified'   => [
821
+     *      'modified.txt',
822
+     *   ],
823
+     *   'deleted' => [
824
+     *      'foo.php.bak',
825
+     *      'old.txt'
826
+     *   ]
827
+     * ];
828
+     *
829
+     * The returned syncToken property should reflect the *current* syncToken
830
+     * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
831
+     * property. This is needed here too, to ensure the operation is atomic.
832
+     *
833
+     * If the $syncToken argument is specified as null, this is an initial
834
+     * sync, and all members should be reported.
835
+     *
836
+     * The modified property is an array of nodenames that have changed since
837
+     * the last token.
838
+     *
839
+     * The deleted property is an array with nodenames, that have been deleted
840
+     * from collection.
841
+     *
842
+     * The $syncLevel argument is basically the 'depth' of the report. If it's
843
+     * 1, you only have to report changes that happened only directly in
844
+     * immediate descendants. If it's 2, it should also include changes from
845
+     * the nodes below the child collections. (grandchildren)
846
+     *
847
+     * The $limit argument allows a client to specify how many results should
848
+     * be returned at most. If the limit is not specified, it should be treated
849
+     * as infinite.
850
+     *
851
+     * If the limit (infinite or not) is higher than you're willing to return,
852
+     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
853
+     *
854
+     * If the syncToken is expired (due to data cleanup) or unknown, you must
855
+     * return null.
856
+     *
857
+     * The limit is 'suggestive'. You are free to ignore it.
858
+     *
859
+     * @param string $addressBookId
860
+     * @param string $syncToken
861
+     * @param int $syncLevel
862
+     * @param int $limit
863
+     * @return array
864
+     */
865
+    public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
866
+        // Current synctoken
867
+        $stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
868
+        $stmt->execute([$addressBookId]);
869
+        $currentToken = $stmt->fetchColumn(0);
870
+
871
+        if (is_null($currentToken)) {
872
+            return null;
873
+        }
874
+
875
+        $result = [
876
+            'syncToken' => $currentToken,
877
+            'added' => [],
878
+            'modified' => [],
879
+            'deleted' => [],
880
+        ];
881
+
882
+        if ($syncToken) {
883
+            $query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
884
+            if ($limit > 0) {
885
+                $query .= " LIMIT " . (int)$limit;
886
+            }
887
+
888
+            // Fetching all changes
889
+            $stmt = $this->db->prepare($query);
890
+            $stmt->execute([$syncToken, $currentToken, $addressBookId]);
891
+
892
+            $changes = [];
893
+
894
+            // This loop ensures that any duplicates are overwritten, only the
895
+            // last change on a node is relevant.
896
+            while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
897
+                $changes[$row['uri']] = $row['operation'];
898
+            }
899
+
900
+            foreach ($changes as $uri => $operation) {
901
+                switch ($operation) {
902
+                    case 1:
903
+                        $result['added'][] = $uri;
904
+                        break;
905
+                    case 2:
906
+                        $result['modified'][] = $uri;
907
+                        break;
908
+                    case 3:
909
+                        $result['deleted'][] = $uri;
910
+                        break;
911
+                }
912
+            }
913
+        } else {
914
+            // No synctoken supplied, this is the initial sync.
915
+            $query = "SELECT `uri` FROM `*PREFIX*cards` WHERE `addressbookid` = ?";
916
+            $stmt = $this->db->prepare($query);
917
+            $stmt->execute([$addressBookId]);
918
+
919
+            $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
920
+        }
921
+        return $result;
922
+    }
923
+
924
+    /**
925
+     * Adds a change record to the addressbookchanges table.
926
+     *
927
+     * @param mixed $addressBookId
928
+     * @param string $objectUri
929
+     * @param int $operation 1 = add, 2 = modify, 3 = delete
930
+     * @return void
931
+     */
932
+    protected function addChange($addressBookId, $objectUri, $operation) {
933
+        $sql = 'INSERT INTO `*PREFIX*addressbookchanges`(`uri`, `synctoken`, `addressbookid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*addressbooks` WHERE `id` = ?';
934
+        $stmt = $this->db->prepare($sql);
935
+        $stmt->execute([
936
+            $objectUri,
937
+            $addressBookId,
938
+            $operation,
939
+            $addressBookId
940
+        ]);
941
+        $stmt = $this->db->prepare('UPDATE `*PREFIX*addressbooks` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
942
+        $stmt->execute([
943
+            $addressBookId
944
+        ]);
945
+    }
946
+
947
+    /**
948
+     * @param resource|string $cardData
949
+     * @param bool $modified
950
+     * @return string
951
+     */
952
+    private function readBlob($cardData, &$modified = false) {
953
+        if (is_resource($cardData)) {
954
+            $cardData = stream_get_contents($cardData);
955
+        }
956
+
957
+        $cardDataArray = explode("\r\n", $cardData);
958
+
959
+        $cardDataFiltered = [];
960
+        $removingPhoto = false;
961
+        foreach ($cardDataArray as $line) {
962
+            if (strpos($line, 'PHOTO:data:') === 0
963
+                && strpos($line, 'PHOTO:data:image/') !== 0) {
964
+                // Filter out PHOTO data of non-images
965
+                $removingPhoto = true;
966
+                $modified = true;
967
+                continue;
968
+            }
969
+
970
+            if ($removingPhoto) {
971
+                if (strpos($line, ' ') === 0) {
972
+                    continue;
973
+                }
974
+                // No leading space means this is a new property
975
+                $removingPhoto = false;
976
+            }
977
+
978
+            $cardDataFiltered[] = $line;
979
+        }
980
+
981
+        return implode("\r\n", $cardDataFiltered);
982
+    }
983
+
984
+    /**
985
+     * @param IShareable $shareable
986
+     * @param string[] $add
987
+     * @param string[] $remove
988
+     */
989
+    public function updateShares(IShareable $shareable, $add, $remove) {
990
+        $addressBookId = $shareable->getResourceId();
991
+        $addressBookData = $this->getAddressBookById($addressBookId);
992
+        $oldShares = $this->getShares($addressBookId);
993
+
994
+        $this->sharingBackend->updateShares($shareable, $add, $remove);
995
+
996
+        $this->dispatcher->dispatchTyped(new AddressBookShareUpdatedEvent($addressBookId, $addressBookData, $oldShares, $add, $remove));
997
+    }
998
+
999
+    /**
1000
+     * Search contacts in a specific address-book
1001
+     *
1002
+     * @param int $addressBookId
1003
+     * @param string $pattern which should match within the $searchProperties
1004
+     * @param array $searchProperties defines the properties within the query pattern should match
1005
+     * @param array $options = array() to define the search behavior
1006
+     *    - 'escape_like_param' - If set to false wildcards _ and % are not escaped, otherwise they are
1007
+     *    - 'limit' - Set a numeric limit for the search results
1008
+     *    - 'offset' - Set the offset for the limited search results
1009
+     * @return array an array of contacts which are arrays of key-value-pairs
1010
+     */
1011
+    public function search($addressBookId, $pattern, $searchProperties, $options = []): array {
1012
+        return $this->searchByAddressBookIds([$addressBookId], $pattern, $searchProperties, $options);
1013
+    }
1014
+
1015
+    /**
1016
+     * Search contacts in all address-books accessible by a user
1017
+     *
1018
+     * @param string $principalUri
1019
+     * @param string $pattern
1020
+     * @param array $searchProperties
1021
+     * @param array $options
1022
+     * @return array
1023
+     */
1024
+    public function searchPrincipalUri(string $principalUri,
1025
+                                        string $pattern,
1026
+                                        array $searchProperties,
1027
+                                        array $options = []): array {
1028
+        $addressBookIds = array_map(static function ($row):int {
1029
+            return (int) $row['id'];
1030
+        }, $this->getAddressBooksForUser($principalUri));
1031
+
1032
+        return $this->searchByAddressBookIds($addressBookIds, $pattern, $searchProperties, $options);
1033
+    }
1034
+
1035
+    /**
1036
+     * @param array $addressBookIds
1037
+     * @param string $pattern
1038
+     * @param array $searchProperties
1039
+     * @param array $options
1040
+     * @return array
1041
+     */
1042
+    private function searchByAddressBookIds(array $addressBookIds,
1043
+                                            string $pattern,
1044
+                                            array $searchProperties,
1045
+                                            array $options = []): array {
1046
+        $escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false;
1047
+
1048
+        $query2 = $this->db->getQueryBuilder();
1049
+
1050
+        $addressBookOr = $query2->expr()->orX();
1051
+        foreach ($addressBookIds as $addressBookId) {
1052
+            $addressBookOr->add($query2->expr()->eq('cp.addressbookid', $query2->createNamedParameter($addressBookId)));
1053
+        }
1054
+
1055
+        if ($addressBookOr->count() === 0) {
1056
+            return [];
1057
+        }
1058
+
1059
+        $propertyOr = $query2->expr()->orX();
1060
+        foreach ($searchProperties as $property) {
1061
+            if ($escapePattern) {
1062
+                if ($property === 'EMAIL' && strpos($pattern, ' ') !== false) {
1063
+                    // There can be no spaces in emails
1064
+                    continue;
1065
+                }
1066
+
1067
+                if ($property === 'CLOUD' && preg_match('/[^a-zA-Z0-9 :_.@\/\-\']/', $pattern) === 1) {
1068
+                    // There can be no chars in cloud ids which are not valid for user ids plus :/
1069
+                    // worst case: CA61590A-BBBC-423E-84AF-E6DF01455A53@https://my.nxt/srv/
1070
+                    continue;
1071
+                }
1072
+            }
1073
+
1074
+            $propertyOr->add($query2->expr()->eq('cp.name', $query2->createNamedParameter($property)));
1075
+        }
1076
+
1077
+        if ($propertyOr->count() === 0) {
1078
+            return [];
1079
+        }
1080
+
1081
+        $query2->selectDistinct('cp.cardid')
1082
+            ->from($this->dbCardsPropertiesTable, 'cp')
1083
+            ->andWhere($addressBookOr)
1084
+            ->andWhere($propertyOr);
1085
+
1086
+        // No need for like when the pattern is empty
1087
+        if ('' !== $pattern) {
1088
+            if (!$escapePattern) {
1089
+                $query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter($pattern)));
1090
+            } else {
1091
+                $query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
1092
+            }
1093
+        }
1094
+
1095
+        if (isset($options['limit'])) {
1096
+            $query2->setMaxResults($options['limit']);
1097
+        }
1098
+        if (isset($options['offset'])) {
1099
+            $query2->setFirstResult($options['offset']);
1100
+        }
1101
+
1102
+        $result = $query2->execute();
1103
+        $matches = $result->fetchAll();
1104
+        $result->closeCursor();
1105
+        $matches = array_map(function ($match) {
1106
+            return (int)$match['cardid'];
1107
+        }, $matches);
1108
+
1109
+        $query = $this->db->getQueryBuilder();
1110
+        $query->select('c.addressbookid', 'c.carddata', 'c.uri')
1111
+            ->from($this->dbCardsTable, 'c')
1112
+            ->where($query->expr()->in('c.id', $query->createNamedParameter($matches, IQueryBuilder::PARAM_INT_ARRAY)));
1113
+
1114
+        $result = $query->execute();
1115
+        $cards = $result->fetchAll();
1116
+
1117
+        $result->closeCursor();
1118
+
1119
+        return array_map(function ($array) {
1120
+            $array['addressbookid'] = (int) $array['addressbookid'];
1121
+            $modified = false;
1122
+            $array['carddata'] = $this->readBlob($array['carddata'], $modified);
1123
+            if ($modified) {
1124
+                $array['size'] = strlen($array['carddata']);
1125
+            }
1126
+            return $array;
1127
+        }, $cards);
1128
+    }
1129
+
1130
+    /**
1131
+     * @param int $bookId
1132
+     * @param string $name
1133
+     * @return array
1134
+     */
1135
+    public function collectCardProperties($bookId, $name) {
1136
+        $query = $this->db->getQueryBuilder();
1137
+        $result = $query->selectDistinct('value')
1138
+            ->from($this->dbCardsPropertiesTable)
1139
+            ->where($query->expr()->eq('name', $query->createNamedParameter($name)))
1140
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($bookId)))
1141
+            ->execute();
1142
+
1143
+        $all = $result->fetchAll(PDO::FETCH_COLUMN);
1144
+        $result->closeCursor();
1145
+
1146
+        return $all;
1147
+    }
1148
+
1149
+    /**
1150
+     * get URI from a given contact
1151
+     *
1152
+     * @param int $id
1153
+     * @return string
1154
+     */
1155
+    public function getCardUri($id) {
1156
+        $query = $this->db->getQueryBuilder();
1157
+        $query->select('uri')->from($this->dbCardsTable)
1158
+            ->where($query->expr()->eq('id', $query->createParameter('id')))
1159
+            ->setParameter('id', $id);
1160
+
1161
+        $result = $query->execute();
1162
+        $uri = $result->fetch();
1163
+        $result->closeCursor();
1164
+
1165
+        if (!isset($uri['uri'])) {
1166
+            throw new \InvalidArgumentException('Card does not exists: ' . $id);
1167
+        }
1168
+
1169
+        return $uri['uri'];
1170
+    }
1171
+
1172
+    /**
1173
+     * return contact with the given URI
1174
+     *
1175
+     * @param int $addressBookId
1176
+     * @param string $uri
1177
+     * @returns array
1178
+     */
1179
+    public function getContact($addressBookId, $uri) {
1180
+        $result = [];
1181
+        $query = $this->db->getQueryBuilder();
1182
+        $query->select('*')->from($this->dbCardsTable)
1183
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1184
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1185
+        $queryResult = $query->execute();
1186
+        $contact = $queryResult->fetch();
1187
+        $queryResult->closeCursor();
1188
+
1189
+        if (is_array($contact)) {
1190
+            $modified = false;
1191
+            $contact['etag'] = '"' . $contact['etag'] . '"';
1192
+            $contact['carddata'] = $this->readBlob($contact['carddata'], $modified);
1193
+            if ($modified) {
1194
+                $contact['size'] = strlen($contact['carddata']);
1195
+            }
1196
+
1197
+            $result = $contact;
1198
+        }
1199
+
1200
+        return $result;
1201
+    }
1202
+
1203
+    /**
1204
+     * Returns the list of people whom this address book is shared with.
1205
+     *
1206
+     * Every element in this array should have the following properties:
1207
+     *   * href - Often a mailto: address
1208
+     *   * commonName - Optional, for example a first + last name
1209
+     *   * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
1210
+     *   * readOnly - boolean
1211
+     *   * summary - Optional, a description for the share
1212
+     *
1213
+     * @return array
1214
+     */
1215
+    public function getShares($addressBookId) {
1216
+        return $this->sharingBackend->getShares($addressBookId);
1217
+    }
1218
+
1219
+    /**
1220
+     * update properties table
1221
+     *
1222
+     * @param int $addressBookId
1223
+     * @param string $cardUri
1224
+     * @param string $vCardSerialized
1225
+     */
1226
+    protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) {
1227
+        $cardId = $this->getCardId($addressBookId, $cardUri);
1228
+        $vCard = $this->readCard($vCardSerialized);
1229
+
1230
+        $this->purgeProperties($addressBookId, $cardId);
1231
+
1232
+        $query = $this->db->getQueryBuilder();
1233
+        $query->insert($this->dbCardsPropertiesTable)
1234
+            ->values(
1235
+                [
1236
+                    'addressbookid' => $query->createNamedParameter($addressBookId),
1237
+                    'cardid' => $query->createNamedParameter($cardId),
1238
+                    'name' => $query->createParameter('name'),
1239
+                    'value' => $query->createParameter('value'),
1240
+                    'preferred' => $query->createParameter('preferred')
1241
+                ]
1242
+            );
1243
+
1244
+        foreach ($vCard->children() as $property) {
1245
+            if (!in_array($property->name, self::$indexProperties)) {
1246
+                continue;
1247
+            }
1248
+            $preferred = 0;
1249
+            foreach ($property->parameters as $parameter) {
1250
+                if ($parameter->name === 'TYPE' && strtoupper($parameter->getValue()) === 'PREF') {
1251
+                    $preferred = 1;
1252
+                    break;
1253
+                }
1254
+            }
1255
+            $query->setParameter('name', $property->name);
1256
+            $query->setParameter('value', mb_substr($property->getValue(), 0, 254));
1257
+            $query->setParameter('preferred', $preferred);
1258
+            $query->execute();
1259
+        }
1260
+    }
1261
+
1262
+    /**
1263
+     * read vCard data into a vCard object
1264
+     *
1265
+     * @param string $cardData
1266
+     * @return VCard
1267
+     */
1268
+    protected function readCard($cardData) {
1269
+        return Reader::read($cardData);
1270
+    }
1271
+
1272
+    /**
1273
+     * delete all properties from a given card
1274
+     *
1275
+     * @param int $addressBookId
1276
+     * @param int $cardId
1277
+     */
1278
+    protected function purgeProperties($addressBookId, $cardId) {
1279
+        $query = $this->db->getQueryBuilder();
1280
+        $query->delete($this->dbCardsPropertiesTable)
1281
+            ->where($query->expr()->eq('cardid', $query->createNamedParameter($cardId)))
1282
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1283
+        $query->execute();
1284
+    }
1285
+
1286
+    /**
1287
+     * get ID from a given contact
1288
+     *
1289
+     * @param int $addressBookId
1290
+     * @param string $uri
1291
+     * @return int
1292
+     */
1293
+    protected function getCardId($addressBookId, $uri) {
1294
+        $query = $this->db->getQueryBuilder();
1295
+        $query->select('id')->from($this->dbCardsTable)
1296
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1297
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1298
+
1299
+        $result = $query->execute();
1300
+        $cardIds = $result->fetch();
1301
+        $result->closeCursor();
1302
+
1303
+        if (!isset($cardIds['id'])) {
1304
+            throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1305
+        }
1306
+
1307
+        return (int)$cardIds['id'];
1308
+    }
1309
+
1310
+    /**
1311
+     * For shared address books the sharee is set in the ACL of the address book
1312
+     *
1313
+     * @param $addressBookId
1314
+     * @param $acl
1315
+     * @return array
1316
+     */
1317
+    public function applyShareAcl($addressBookId, $acl) {
1318
+        return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
1319
+    }
1320
+
1321
+    private function convertPrincipal($principalUri, $toV2) {
1322
+        if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
1323
+            list(, $name) = \Sabre\Uri\split($principalUri);
1324
+            if ($toV2 === true) {
1325
+                return "principals/users/$name";
1326
+            }
1327
+            return "principals/$name";
1328
+        }
1329
+        return $principalUri;
1330
+    }
1331
+
1332
+    private function addOwnerPrincipal(&$addressbookInfo) {
1333
+        $ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1334
+        $displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1335
+        if (isset($addressbookInfo[$ownerPrincipalKey])) {
1336
+            $uri = $addressbookInfo[$ownerPrincipalKey];
1337
+        } else {
1338
+            $uri = $addressbookInfo['principaluri'];
1339
+        }
1340
+
1341
+        $principalInformation = $this->principalBackend->getPrincipalByPath($uri);
1342
+        if (isset($principalInformation['{DAV:}displayname'])) {
1343
+            $addressbookInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
1344
+        }
1345
+    }
1346
+
1347
+    /**
1348
+     * Extract UID from vcard
1349
+     *
1350
+     * @param string $cardData the vcard raw data
1351
+     * @return string the uid
1352
+     * @throws BadRequest if no UID is available
1353
+     */
1354
+    private function getUID($cardData) {
1355
+        if ($cardData != '') {
1356
+            $vCard = Reader::read($cardData);
1357
+            if ($vCard->UID) {
1358
+                $uid = $vCard->UID->getValue();
1359
+                return $uid;
1360
+            }
1361
+            // should already be handled, but just in case
1362
+            throw new BadRequest('vCards on CardDAV servers MUST have a UID property');
1363
+        }
1364
+        // should already be handled, but just in case
1365
+        throw new BadRequest('vCard can not be empty');
1366
+    }
1367 1367
 }
Please login to merge, or discard this patch.
Spacing   +41 added lines, -41 removed lines patch added patch discarded remove patch
@@ -178,7 +178,7 @@  discard block
 block discarded – undo
178 178
 				'uri' => $row['uri'],
179 179
 				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
180 180
 				'{DAV:}displayname' => $row['displayname'],
181
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
181
+				'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
182 182
 				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
183 183
 				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
184 184
 			];
@@ -191,7 +191,7 @@  discard block
 block discarded – undo
191 191
 		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
192 192
 		$principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
193 193
 
194
-		$principals = array_map(function ($principal) {
194
+		$principals = array_map(function($principal) {
195 195
 			return urldecode($principal);
196 196
 		}, $principals);
197 197
 		$principals[] = $principalUri;
@@ -206,13 +206,13 @@  discard block
 block discarded – undo
206 206
 			->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
207 207
 			->execute();
208 208
 
209
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
209
+		$readOnlyPropertyName = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}read-only';
210 210
 		while ($row = $result->fetch()) {
211 211
 			if ($row['principaluri'] === $principalUri) {
212 212
 				continue;
213 213
 			}
214 214
 
215
-			$readOnly = (int)$row['access'] === Backend::ACCESS_READ;
215
+			$readOnly = (int) $row['access'] === Backend::ACCESS_READ;
216 216
 			if (isset($addressBooks[$row['id']])) {
217 217
 				if ($readOnly) {
218 218
 					// New share can not have more permissions then the old one.
@@ -226,18 +226,18 @@  discard block
 block discarded – undo
226 226
 			}
227 227
 
228 228
 			list(, $name) = \Sabre\Uri\split($row['principaluri']);
229
-			$uri = $row['uri'] . '_shared_by_' . $name;
230
-			$displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
229
+			$uri = $row['uri'].'_shared_by_'.$name;
230
+			$displayName = $row['displayname'].' ('.$this->getUserDisplayName($name).')';
231 231
 
232 232
 			$addressBooks[$row['id']] = [
233 233
 				'id' => $row['id'],
234 234
 				'uri' => $uri,
235 235
 				'principaluri' => $principalUriOriginal,
236 236
 				'{DAV:}displayname' => $displayName,
237
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
237
+				'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
238 238
 				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
239 239
 				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
240
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
240
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $row['principaluri'],
241 241
 				$readOnlyPropertyName => $readOnly,
242 242
 			];
243 243
 
@@ -264,7 +264,7 @@  discard block
 block discarded – undo
264 264
 				'uri' => $row['uri'],
265 265
 				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
266 266
 				'{DAV:}displayname' => $row['displayname'],
267
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
267
+				'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
268 268
 				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
269 269
 				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
270 270
 			];
@@ -311,7 +311,7 @@  discard block
 block discarded – undo
311 311
 			'uri' => $row['uri'],
312 312
 			'principaluri' => $row['principaluri'],
313 313
 			'{DAV:}displayname' => $row['displayname'],
314
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
314
+			'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
315 315
 			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
316 316
 			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
317 317
 		];
@@ -345,7 +345,7 @@  discard block
 block discarded – undo
345 345
 			'uri' => $row['uri'],
346 346
 			'principaluri' => $row['principaluri'],
347 347
 			'{DAV:}displayname' => $row['displayname'],
348
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
348
+			'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
349 349
 			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
350 350
 			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
351 351
 		];
@@ -374,17 +374,17 @@  discard block
 block discarded – undo
374 374
 	public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
375 375
 		$supportedProperties = [
376 376
 			'{DAV:}displayname',
377
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description',
377
+			'{'.Plugin::NS_CARDDAV.'}addressbook-description',
378 378
 		];
379 379
 
380
-		$propPatch->handle($supportedProperties, function ($mutations) use ($addressBookId) {
380
+		$propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) {
381 381
 			$updates = [];
382 382
 			foreach ($mutations as $property => $newValue) {
383 383
 				switch ($property) {
384 384
 					case '{DAV:}displayname':
385 385
 						$updates['displayname'] = $newValue;
386 386
 						break;
387
-					case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
387
+					case '{'.Plugin::NS_CARDDAV.'}addressbook-description':
388 388
 						$updates['description'] = $newValue;
389 389
 						break;
390 390
 				}
@@ -400,9 +400,9 @@  discard block
 block discarded – undo
400 400
 
401 401
 			$this->addChange($addressBookId, "", 2);
402 402
 
403
-			$addressBookRow = $this->getAddressBookById((int)$addressBookId);
403
+			$addressBookRow = $this->getAddressBookById((int) $addressBookId);
404 404
 			$shares = $this->getShares($addressBookId);
405
-			$this->dispatcher->dispatchTyped(new AddressBookUpdatedEvent((int)$addressBookId, $addressBookRow, $shares, $mutations));
405
+			$this->dispatcher->dispatchTyped(new AddressBookUpdatedEvent((int) $addressBookId, $addressBookRow, $shares, $mutations));
406 406
 
407 407
 			return true;
408 408
 		});
@@ -431,11 +431,11 @@  discard block
 block discarded – undo
431 431
 				case '{DAV:}displayname':
432 432
 					$values['displayname'] = $newValue;
433 433
 					break;
434
-				case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
434
+				case '{'.Plugin::NS_CARDDAV.'}addressbook-description':
435 435
 					$values['description'] = $newValue;
436 436
 					break;
437 437
 				default:
438
-					throw new BadRequest('Unknown property: ' . $property);
438
+					throw new BadRequest('Unknown property: '.$property);
439 439
 			}
440 440
 		}
441 441
 
@@ -459,7 +459,7 @@  discard block
 block discarded – undo
459 459
 
460 460
 		$addressBookId = $query->getLastInsertId();
461 461
 		$addressBookRow = $this->getAddressBookById($addressBookId);
462
-		$this->dispatcher->dispatchTyped(new AddressBookCreatedEvent((int)$addressBookId, $addressBookRow));
462
+		$this->dispatcher->dispatchTyped(new AddressBookCreatedEvent((int) $addressBookId, $addressBookRow));
463 463
 
464 464
 		return $addressBookId;
465 465
 	}
@@ -530,7 +530,7 @@  discard block
 block discarded – undo
530 530
 
531 531
 		$result = $query->execute();
532 532
 		while ($row = $result->fetch()) {
533
-			$row['etag'] = '"' . $row['etag'] . '"';
533
+			$row['etag'] = '"'.$row['etag'].'"';
534 534
 
535 535
 			$modified = false;
536 536
 			$row['carddata'] = $this->readBlob($row['carddata'], $modified);
@@ -570,7 +570,7 @@  discard block
 block discarded – undo
570 570
 		if (!$row) {
571 571
 			return false;
572 572
 		}
573
-		$row['etag'] = '"' . $row['etag'] . '"';
573
+		$row['etag'] = '"'.$row['etag'].'"';
574 574
 
575 575
 		$modified = false;
576 576
 		$row['carddata'] = $this->readBlob($row['carddata'], $modified);
@@ -612,7 +612,7 @@  discard block
 block discarded – undo
612 612
 			$result = $query->execute();
613 613
 
614 614
 			while ($row = $result->fetch()) {
615
-				$row['etag'] = '"' . $row['etag'] . '"';
615
+				$row['etag'] = '"'.$row['etag'].'"';
616 616
 
617 617
 				$modified = false;
618 618
 				$row['carddata'] = $this->readBlob($row['carddata'], $modified);
@@ -663,7 +663,7 @@  discard block
 block discarded – undo
663 663
 			->andWhere($q->expr()->eq('uid', $q->createNamedParameter($uid)))
664 664
 			->setMaxResults(1);
665 665
 		$result = $q->execute();
666
-		$count = (bool)$result->fetchColumn();
666
+		$count = (bool) $result->fetchColumn();
667 667
 		$result->closeCursor();
668 668
 		if ($count) {
669 669
 			throw new \Sabre\DAV\Exception\BadRequest('VCard object with uid already exists in this addressbook collection.');
@@ -691,14 +691,14 @@  discard block
 block discarded – undo
691 691
 		$addressBookData = $this->getAddressBookById($addressBookId);
692 692
 		$shares = $this->getShares($addressBookId);
693 693
 		$objectRow = $this->getCard($addressBookId, $cardUri);
694
-		$this->dispatcher->dispatchTyped(new CardCreatedEvent((int)$addressBookId, $addressBookData, $shares, $objectRow));
694
+		$this->dispatcher->dispatchTyped(new CardCreatedEvent((int) $addressBookId, $addressBookData, $shares, $objectRow));
695 695
 		$this->legacyDispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
696 696
 			new GenericEvent(null, [
697 697
 				'addressBookId' => $addressBookId,
698 698
 				'cardUri' => $cardUri,
699 699
 				'cardData' => $cardData]));
700 700
 
701
-		return '"' . $etag . '"';
701
+		return '"'.$etag.'"';
702 702
 	}
703 703
 
704 704
 	/**
@@ -734,7 +734,7 @@  discard block
 block discarded – undo
734 734
 		// check for recently stored etag and stop if it is the same
735 735
 		$etagCacheKey = "$addressBookId#$cardUri";
736 736
 		if (isset($this->etagCache[$etagCacheKey]) && $this->etagCache[$etagCacheKey] === $etag) {
737
-			return '"' . $etag . '"';
737
+			return '"'.$etag.'"';
738 738
 		}
739 739
 
740 740
 		$query->update($this->dbCardsTable)
@@ -755,14 +755,14 @@  discard block
 block discarded – undo
755 755
 		$addressBookData = $this->getAddressBookById($addressBookId);
756 756
 		$shares = $this->getShares($addressBookId);
757 757
 		$objectRow = $this->getCard($addressBookId, $cardUri);
758
-		$this->dispatcher->dispatchTyped(new CardUpdatedEvent((int)$addressBookId, $addressBookData, $shares, $objectRow));
758
+		$this->dispatcher->dispatchTyped(new CardUpdatedEvent((int) $addressBookId, $addressBookData, $shares, $objectRow));
759 759
 		$this->legacyDispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
760 760
 			new GenericEvent(null, [
761 761
 				'addressBookId' => $addressBookId,
762 762
 				'cardUri' => $cardUri,
763 763
 				'cardData' => $cardData]));
764 764
 
765
-		return '"' . $etag . '"';
765
+		return '"'.$etag.'"';
766 766
 	}
767 767
 
768 768
 	/**
@@ -792,7 +792,7 @@  discard block
 block discarded – undo
792 792
 
793 793
 		if ($ret === 1) {
794 794
 			if ($cardId !== null) {
795
-				$this->dispatcher->dispatchTyped(new CardDeletedEvent((int)$addressBookId, $addressBookData, $shares, $objectRow));
795
+				$this->dispatcher->dispatchTyped(new CardDeletedEvent((int) $addressBookId, $addressBookData, $shares, $objectRow));
796 796
 				$this->legacyDispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
797 797
 					new GenericEvent(null, [
798 798
 						'addressBookId' => $addressBookId,
@@ -882,7 +882,7 @@  discard block
 block discarded – undo
882 882
 		if ($syncToken) {
883 883
 			$query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
884 884
 			if ($limit > 0) {
885
-				$query .= " LIMIT " . (int)$limit;
885
+				$query .= " LIMIT ".(int) $limit;
886 886
 			}
887 887
 
888 888
 			// Fetching all changes
@@ -1025,7 +1025,7 @@  discard block
 block discarded – undo
1025 1025
 									   string $pattern,
1026 1026
 									   array $searchProperties,
1027 1027
 									   array $options = []): array {
1028
-		$addressBookIds = array_map(static function ($row):int {
1028
+		$addressBookIds = array_map(static function($row):int {
1029 1029
 			return (int) $row['id'];
1030 1030
 		}, $this->getAddressBooksForUser($principalUri));
1031 1031
 
@@ -1088,7 +1088,7 @@  discard block
 block discarded – undo
1088 1088
 			if (!$escapePattern) {
1089 1089
 				$query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter($pattern)));
1090 1090
 			} else {
1091
-				$query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
1091
+				$query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter('%'.$this->db->escapeLikeParameter($pattern).'%')));
1092 1092
 			}
1093 1093
 		}
1094 1094
 
@@ -1102,8 +1102,8 @@  discard block
 block discarded – undo
1102 1102
 		$result = $query2->execute();
1103 1103
 		$matches = $result->fetchAll();
1104 1104
 		$result->closeCursor();
1105
-		$matches = array_map(function ($match) {
1106
-			return (int)$match['cardid'];
1105
+		$matches = array_map(function($match) {
1106
+			return (int) $match['cardid'];
1107 1107
 		}, $matches);
1108 1108
 
1109 1109
 		$query = $this->db->getQueryBuilder();
@@ -1116,7 +1116,7 @@  discard block
 block discarded – undo
1116 1116
 
1117 1117
 		$result->closeCursor();
1118 1118
 
1119
-		return array_map(function ($array) {
1119
+		return array_map(function($array) {
1120 1120
 			$array['addressbookid'] = (int) $array['addressbookid'];
1121 1121
 			$modified = false;
1122 1122
 			$array['carddata'] = $this->readBlob($array['carddata'], $modified);
@@ -1163,7 +1163,7 @@  discard block
 block discarded – undo
1163 1163
 		$result->closeCursor();
1164 1164
 
1165 1165
 		if (!isset($uri['uri'])) {
1166
-			throw new \InvalidArgumentException('Card does not exists: ' . $id);
1166
+			throw new \InvalidArgumentException('Card does not exists: '.$id);
1167 1167
 		}
1168 1168
 
1169 1169
 		return $uri['uri'];
@@ -1188,7 +1188,7 @@  discard block
 block discarded – undo
1188 1188
 
1189 1189
 		if (is_array($contact)) {
1190 1190
 			$modified = false;
1191
-			$contact['etag'] = '"' . $contact['etag'] . '"';
1191
+			$contact['etag'] = '"'.$contact['etag'].'"';
1192 1192
 			$contact['carddata'] = $this->readBlob($contact['carddata'], $modified);
1193 1193
 			if ($modified) {
1194 1194
 				$contact['size'] = strlen($contact['carddata']);
@@ -1301,10 +1301,10 @@  discard block
 block discarded – undo
1301 1301
 		$result->closeCursor();
1302 1302
 
1303 1303
 		if (!isset($cardIds['id'])) {
1304
-			throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1304
+			throw new \InvalidArgumentException('Card does not exists: '.$uri);
1305 1305
 		}
1306 1306
 
1307
-		return (int)$cardIds['id'];
1307
+		return (int) $cardIds['id'];
1308 1308
 	}
1309 1309
 
1310 1310
 	/**
@@ -1330,8 +1330,8 @@  discard block
 block discarded – undo
1330 1330
 	}
1331 1331
 
1332 1332
 	private function addOwnerPrincipal(&$addressbookInfo) {
1333
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1334
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1333
+		$ownerPrincipalKey = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal';
1334
+		$displaynameKey = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD.'}owner-displayname';
1335 1335
 		if (isset($addressbookInfo[$ownerPrincipalKey])) {
1336 1336
 			$uri = $addressbookInfo[$ownerPrincipalKey];
1337 1337
 		} else {
Please login to merge, or discard this patch.
apps/federatedfilesharing/lib/FederatedShareProvider.php 2 patches
Indentation   +1061 added lines, -1061 removed lines patch added patch discarded remove patch
@@ -60,1075 +60,1075 @@
 block discarded – undo
60 60
  * @package OCA\FederatedFileSharing
61 61
  */
62 62
 class FederatedShareProvider implements IShareProvider {
63
-	public const SHARE_TYPE_REMOTE = 6;
64
-
65
-	/** @var IDBConnection */
66
-	private $dbConnection;
67
-
68
-	/** @var AddressHandler */
69
-	private $addressHandler;
70
-
71
-	/** @var Notifications */
72
-	private $notifications;
73
-
74
-	/** @var TokenHandler */
75
-	private $tokenHandler;
76
-
77
-	/** @var IL10N */
78
-	private $l;
79
-
80
-	/** @var ILogger */
81
-	private $logger;
82
-
83
-	/** @var IRootFolder */
84
-	private $rootFolder;
85
-
86
-	/** @var IConfig */
87
-	private $config;
88
-
89
-	/** @var string */
90
-	private $externalShareTable = 'share_external';
91
-
92
-	/** @var IUserManager */
93
-	private $userManager;
94
-
95
-	/** @var ICloudIdManager */
96
-	private $cloudIdManager;
97
-
98
-	/** @var \OCP\GlobalScale\IConfig */
99
-	private $gsConfig;
100
-
101
-	/** @var ICloudFederationProviderManager */
102
-	private $cloudFederationProviderManager;
103
-
104
-	/** @var array list of supported share types */
105
-	private $supportedShareType = [IShare::TYPE_REMOTE_GROUP, IShare::TYPE_REMOTE, IShare::TYPE_CIRCLE];
106
-
107
-	/**
108
-	 * DefaultShareProvider constructor.
109
-	 *
110
-	 * @param IDBConnection $connection
111
-	 * @param AddressHandler $addressHandler
112
-	 * @param Notifications $notifications
113
-	 * @param TokenHandler $tokenHandler
114
-	 * @param IL10N $l10n
115
-	 * @param ILogger $logger
116
-	 * @param IRootFolder $rootFolder
117
-	 * @param IConfig $config
118
-	 * @param IUserManager $userManager
119
-	 * @param ICloudIdManager $cloudIdManager
120
-	 * @param \OCP\GlobalScale\IConfig $globalScaleConfig
121
-	 * @param ICloudFederationProviderManager $cloudFederationProviderManager
122
-	 */
123
-	public function __construct(
124
-			IDBConnection $connection,
125
-			AddressHandler $addressHandler,
126
-			Notifications $notifications,
127
-			TokenHandler $tokenHandler,
128
-			IL10N $l10n,
129
-			ILogger $logger,
130
-			IRootFolder $rootFolder,
131
-			IConfig $config,
132
-			IUserManager $userManager,
133
-			ICloudIdManager $cloudIdManager,
134
-			\OCP\GlobalScale\IConfig $globalScaleConfig,
135
-			ICloudFederationProviderManager $cloudFederationProviderManager
136
-	) {
137
-		$this->dbConnection = $connection;
138
-		$this->addressHandler = $addressHandler;
139
-		$this->notifications = $notifications;
140
-		$this->tokenHandler = $tokenHandler;
141
-		$this->l = $l10n;
142
-		$this->logger = $logger;
143
-		$this->rootFolder = $rootFolder;
144
-		$this->config = $config;
145
-		$this->userManager = $userManager;
146
-		$this->cloudIdManager = $cloudIdManager;
147
-		$this->gsConfig = $globalScaleConfig;
148
-		$this->cloudFederationProviderManager = $cloudFederationProviderManager;
149
-	}
150
-
151
-	/**
152
-	 * Return the identifier of this provider.
153
-	 *
154
-	 * @return string Containing only [a-zA-Z0-9]
155
-	 */
156
-	public function identifier() {
157
-		return 'ocFederatedSharing';
158
-	}
159
-
160
-	/**
161
-	 * Share a path
162
-	 *
163
-	 * @param IShare $share
164
-	 * @return IShare The share object
165
-	 * @throws ShareNotFound
166
-	 * @throws \Exception
167
-	 */
168
-	public function create(IShare $share) {
169
-		$shareWith = $share->getSharedWith();
170
-		$itemSource = $share->getNodeId();
171
-		$itemType = $share->getNodeType();
172
-		$permissions = $share->getPermissions();
173
-		$sharedBy = $share->getSharedBy();
174
-		$shareType = $share->getShareType();
175
-
176
-		if ($shareType === IShare::TYPE_REMOTE_GROUP &&
177
-			!$this->isOutgoingServer2serverGroupShareEnabled()
178
-		) {
179
-			$message = 'It is not allowed to send federated group shares from this server.';
180
-			$message_t = $this->l->t('It is not allowed to send federated group shares from this server.');
181
-			$this->logger->debug($message, ['app' => 'Federated File Sharing']);
182
-			throw new \Exception($message_t);
183
-		}
184
-
185
-		/*
63
+    public const SHARE_TYPE_REMOTE = 6;
64
+
65
+    /** @var IDBConnection */
66
+    private $dbConnection;
67
+
68
+    /** @var AddressHandler */
69
+    private $addressHandler;
70
+
71
+    /** @var Notifications */
72
+    private $notifications;
73
+
74
+    /** @var TokenHandler */
75
+    private $tokenHandler;
76
+
77
+    /** @var IL10N */
78
+    private $l;
79
+
80
+    /** @var ILogger */
81
+    private $logger;
82
+
83
+    /** @var IRootFolder */
84
+    private $rootFolder;
85
+
86
+    /** @var IConfig */
87
+    private $config;
88
+
89
+    /** @var string */
90
+    private $externalShareTable = 'share_external';
91
+
92
+    /** @var IUserManager */
93
+    private $userManager;
94
+
95
+    /** @var ICloudIdManager */
96
+    private $cloudIdManager;
97
+
98
+    /** @var \OCP\GlobalScale\IConfig */
99
+    private $gsConfig;
100
+
101
+    /** @var ICloudFederationProviderManager */
102
+    private $cloudFederationProviderManager;
103
+
104
+    /** @var array list of supported share types */
105
+    private $supportedShareType = [IShare::TYPE_REMOTE_GROUP, IShare::TYPE_REMOTE, IShare::TYPE_CIRCLE];
106
+
107
+    /**
108
+     * DefaultShareProvider constructor.
109
+     *
110
+     * @param IDBConnection $connection
111
+     * @param AddressHandler $addressHandler
112
+     * @param Notifications $notifications
113
+     * @param TokenHandler $tokenHandler
114
+     * @param IL10N $l10n
115
+     * @param ILogger $logger
116
+     * @param IRootFolder $rootFolder
117
+     * @param IConfig $config
118
+     * @param IUserManager $userManager
119
+     * @param ICloudIdManager $cloudIdManager
120
+     * @param \OCP\GlobalScale\IConfig $globalScaleConfig
121
+     * @param ICloudFederationProviderManager $cloudFederationProviderManager
122
+     */
123
+    public function __construct(
124
+            IDBConnection $connection,
125
+            AddressHandler $addressHandler,
126
+            Notifications $notifications,
127
+            TokenHandler $tokenHandler,
128
+            IL10N $l10n,
129
+            ILogger $logger,
130
+            IRootFolder $rootFolder,
131
+            IConfig $config,
132
+            IUserManager $userManager,
133
+            ICloudIdManager $cloudIdManager,
134
+            \OCP\GlobalScale\IConfig $globalScaleConfig,
135
+            ICloudFederationProviderManager $cloudFederationProviderManager
136
+    ) {
137
+        $this->dbConnection = $connection;
138
+        $this->addressHandler = $addressHandler;
139
+        $this->notifications = $notifications;
140
+        $this->tokenHandler = $tokenHandler;
141
+        $this->l = $l10n;
142
+        $this->logger = $logger;
143
+        $this->rootFolder = $rootFolder;
144
+        $this->config = $config;
145
+        $this->userManager = $userManager;
146
+        $this->cloudIdManager = $cloudIdManager;
147
+        $this->gsConfig = $globalScaleConfig;
148
+        $this->cloudFederationProviderManager = $cloudFederationProviderManager;
149
+    }
150
+
151
+    /**
152
+     * Return the identifier of this provider.
153
+     *
154
+     * @return string Containing only [a-zA-Z0-9]
155
+     */
156
+    public function identifier() {
157
+        return 'ocFederatedSharing';
158
+    }
159
+
160
+    /**
161
+     * Share a path
162
+     *
163
+     * @param IShare $share
164
+     * @return IShare The share object
165
+     * @throws ShareNotFound
166
+     * @throws \Exception
167
+     */
168
+    public function create(IShare $share) {
169
+        $shareWith = $share->getSharedWith();
170
+        $itemSource = $share->getNodeId();
171
+        $itemType = $share->getNodeType();
172
+        $permissions = $share->getPermissions();
173
+        $sharedBy = $share->getSharedBy();
174
+        $shareType = $share->getShareType();
175
+
176
+        if ($shareType === IShare::TYPE_REMOTE_GROUP &&
177
+            !$this->isOutgoingServer2serverGroupShareEnabled()
178
+        ) {
179
+            $message = 'It is not allowed to send federated group shares from this server.';
180
+            $message_t = $this->l->t('It is not allowed to send federated group shares from this server.');
181
+            $this->logger->debug($message, ['app' => 'Federated File Sharing']);
182
+            throw new \Exception($message_t);
183
+        }
184
+
185
+        /*
186 186
 		 * Check if file is not already shared with the remote user
187 187
 		 */
188
-		$alreadyShared = $this->getSharedWith($shareWith, IShare::TYPE_REMOTE, $share->getNode(), 1, 0);
189
-		$alreadySharedGroup = $this->getSharedWith($shareWith, IShare::TYPE_REMOTE_GROUP, $share->getNode(), 1, 0);
190
-		if (!empty($alreadyShared) || !empty($alreadySharedGroup)) {
191
-			$message = 'Sharing %1$s failed, because this item is already shared with %2$s';
192
-			$message_t = $this->l->t('Sharing %1$s failed, because this item is already shared with %2$s', [$share->getNode()->getName(), $shareWith]);
193
-			$this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'Federated File Sharing']);
194
-			throw new \Exception($message_t);
195
-		}
196
-
197
-
198
-		// don't allow federated shares if source and target server are the same
199
-		$cloudId = $this->cloudIdManager->resolveCloudId($shareWith);
200
-		$currentServer = $this->addressHandler->generateRemoteURL();
201
-		$currentUser = $sharedBy;
202
-		if ($this->addressHandler->compareAddresses($cloudId->getUser(), $cloudId->getRemote(), $currentUser, $currentServer)) {
203
-			$message = 'Not allowed to create a federated share with the same user.';
204
-			$message_t = $this->l->t('Not allowed to create a federated share with the same user');
205
-			$this->logger->debug($message, ['app' => 'Federated File Sharing']);
206
-			throw new \Exception($message_t);
207
-		}
208
-
209
-
210
-		$share->setSharedWith($cloudId->getId());
211
-
212
-		try {
213
-			$remoteShare = $this->getShareFromExternalShareTable($share);
214
-		} catch (ShareNotFound $e) {
215
-			$remoteShare = null;
216
-		}
217
-
218
-		if ($remoteShare) {
219
-			try {
220
-				$ownerCloudId = $this->cloudIdManager->getCloudId($remoteShare['owner'], $remoteShare['remote']);
221
-				$shareId = $this->addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $ownerCloudId->getId(), $permissions, 'tmp_token_' . time(), $shareType);
222
-				$share->setId($shareId);
223
-				list($token, $remoteId) = $this->askOwnerToReShare($shareWith, $share, $shareId);
224
-				// remote share was create successfully if we get a valid token as return
225
-				$send = is_string($token) && $token !== '';
226
-			} catch (\Exception $e) {
227
-				// fall back to old re-share behavior if the remote server
228
-				// doesn't support flat re-shares (was introduced with Nextcloud 9.1)
229
-				$this->removeShareFromTable($share);
230
-				$shareId = $this->createFederatedShare($share);
231
-			}
232
-			if ($send) {
233
-				$this->updateSuccessfulReshare($shareId, $token);
234
-				$this->storeRemoteId($shareId, $remoteId);
235
-			} else {
236
-				$this->removeShareFromTable($share);
237
-				$message_t = $this->l->t('File is already shared with %s', [$shareWith]);
238
-				throw new \Exception($message_t);
239
-			}
240
-		} else {
241
-			$shareId = $this->createFederatedShare($share);
242
-		}
243
-
244
-		$data = $this->getRawShare($shareId);
245
-		return $this->createShareObject($data);
246
-	}
247
-
248
-	/**
249
-	 * create federated share and inform the recipient
250
-	 *
251
-	 * @param IShare $share
252
-	 * @return int
253
-	 * @throws ShareNotFound
254
-	 * @throws \Exception
255
-	 */
256
-	protected function createFederatedShare(IShare $share) {
257
-		$token = $this->tokenHandler->generateToken();
258
-		$shareId = $this->addShareToDB(
259
-			$share->getNodeId(),
260
-			$share->getNodeType(),
261
-			$share->getSharedWith(),
262
-			$share->getSharedBy(),
263
-			$share->getShareOwner(),
264
-			$share->getPermissions(),
265
-			$token,
266
-			$share->getShareType()
267
-		);
268
-
269
-		$failure = false;
270
-
271
-		try {
272
-			$sharedByFederatedId = $share->getSharedBy();
273
-			if ($this->userManager->userExists($sharedByFederatedId)) {
274
-				$cloudId = $this->cloudIdManager->getCloudId($sharedByFederatedId, $this->addressHandler->generateRemoteURL());
275
-				$sharedByFederatedId = $cloudId->getId();
276
-			}
277
-			$ownerCloudId = $this->cloudIdManager->getCloudId($share->getShareOwner(), $this->addressHandler->generateRemoteURL());
278
-			$send = $this->notifications->sendRemoteShare(
279
-				$token,
280
-				$share->getSharedWith(),
281
-				$share->getNode()->getName(),
282
-				$shareId,
283
-				$share->getShareOwner(),
284
-				$ownerCloudId->getId(),
285
-				$share->getSharedBy(),
286
-				$sharedByFederatedId,
287
-				$share->getShareType()
288
-			);
289
-
290
-			if ($send === false) {
291
-				$failure = true;
292
-			}
293
-		} catch (\Exception $e) {
294
-			$this->logger->logException($e, [
295
-				'message' => 'Failed to notify remote server of federated share, removing share.',
296
-				'level' => ILogger::ERROR,
297
-				'app' => 'federatedfilesharing',
298
-			]);
299
-			$failure = true;
300
-		}
301
-
302
-		if ($failure) {
303
-			$this->removeShareFromTableById($shareId);
304
-			$message_t = $this->l->t('Sharing %1$s failed, could not find %2$s, maybe the server is currently unreachable or uses a self-signed certificate.',
305
-				[$share->getNode()->getName(), $share->getSharedWith()]);
306
-			throw new \Exception($message_t);
307
-		}
308
-
309
-		return $shareId;
310
-	}
311
-
312
-	/**
313
-	 * @param string $shareWith
314
-	 * @param IShare $share
315
-	 * @param string $shareId internal share Id
316
-	 * @return array
317
-	 * @throws \Exception
318
-	 */
319
-	protected function askOwnerToReShare($shareWith, IShare $share, $shareId) {
320
-		$remoteShare = $this->getShareFromExternalShareTable($share);
321
-		$token = $remoteShare['share_token'];
322
-		$remoteId = $remoteShare['remote_id'];
323
-		$remote = $remoteShare['remote'];
324
-
325
-		list($token, $remoteId) = $this->notifications->requestReShare(
326
-			$token,
327
-			$remoteId,
328
-			$shareId,
329
-			$remote,
330
-			$shareWith,
331
-			$share->getPermissions(),
332
-			$share->getNode()->getName()
333
-		);
334
-
335
-		return [$token, $remoteId];
336
-	}
337
-
338
-	/**
339
-	 * get federated share from the share_external table but exclude mounted link shares
340
-	 *
341
-	 * @param IShare $share
342
-	 * @return array
343
-	 * @throws ShareNotFound
344
-	 */
345
-	protected function getShareFromExternalShareTable(IShare $share) {
346
-		$query = $this->dbConnection->getQueryBuilder();
347
-		$query->select('*')->from($this->externalShareTable)
348
-			->where($query->expr()->eq('user', $query->createNamedParameter($share->getShareOwner())))
349
-			->andWhere($query->expr()->eq('mountpoint', $query->createNamedParameter($share->getTarget())));
350
-		$qResult = $query->execute();
351
-		$result = $qResult->fetchAll();
352
-		$qResult->closeCursor();
353
-
354
-		if (isset($result[0]) && (int)$result[0]['remote_id'] > 0) {
355
-			return $result[0];
356
-		}
357
-
358
-		throw new ShareNotFound('share not found in share_external table');
359
-	}
360
-
361
-	/**
362
-	 * add share to the database and return the ID
363
-	 *
364
-	 * @param int $itemSource
365
-	 * @param string $itemType
366
-	 * @param string $shareWith
367
-	 * @param string $sharedBy
368
-	 * @param string $uidOwner
369
-	 * @param int $permissions
370
-	 * @param string $token
371
-	 * @param int $shareType
372
-	 * @return int
373
-	 */
374
-	private function addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token, $shareType) {
375
-		$qb = $this->dbConnection->getQueryBuilder();
376
-		$qb->insert('share')
377
-			->setValue('share_type', $qb->createNamedParameter($shareType))
378
-			->setValue('item_type', $qb->createNamedParameter($itemType))
379
-			->setValue('item_source', $qb->createNamedParameter($itemSource))
380
-			->setValue('file_source', $qb->createNamedParameter($itemSource))
381
-			->setValue('share_with', $qb->createNamedParameter($shareWith))
382
-			->setValue('uid_owner', $qb->createNamedParameter($uidOwner))
383
-			->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
384
-			->setValue('permissions', $qb->createNamedParameter($permissions))
385
-			->setValue('token', $qb->createNamedParameter($token))
386
-			->setValue('stime', $qb->createNamedParameter(time()));
387
-
388
-		/*
188
+        $alreadyShared = $this->getSharedWith($shareWith, IShare::TYPE_REMOTE, $share->getNode(), 1, 0);
189
+        $alreadySharedGroup = $this->getSharedWith($shareWith, IShare::TYPE_REMOTE_GROUP, $share->getNode(), 1, 0);
190
+        if (!empty($alreadyShared) || !empty($alreadySharedGroup)) {
191
+            $message = 'Sharing %1$s failed, because this item is already shared with %2$s';
192
+            $message_t = $this->l->t('Sharing %1$s failed, because this item is already shared with %2$s', [$share->getNode()->getName(), $shareWith]);
193
+            $this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'Federated File Sharing']);
194
+            throw new \Exception($message_t);
195
+        }
196
+
197
+
198
+        // don't allow federated shares if source and target server are the same
199
+        $cloudId = $this->cloudIdManager->resolveCloudId($shareWith);
200
+        $currentServer = $this->addressHandler->generateRemoteURL();
201
+        $currentUser = $sharedBy;
202
+        if ($this->addressHandler->compareAddresses($cloudId->getUser(), $cloudId->getRemote(), $currentUser, $currentServer)) {
203
+            $message = 'Not allowed to create a federated share with the same user.';
204
+            $message_t = $this->l->t('Not allowed to create a federated share with the same user');
205
+            $this->logger->debug($message, ['app' => 'Federated File Sharing']);
206
+            throw new \Exception($message_t);
207
+        }
208
+
209
+
210
+        $share->setSharedWith($cloudId->getId());
211
+
212
+        try {
213
+            $remoteShare = $this->getShareFromExternalShareTable($share);
214
+        } catch (ShareNotFound $e) {
215
+            $remoteShare = null;
216
+        }
217
+
218
+        if ($remoteShare) {
219
+            try {
220
+                $ownerCloudId = $this->cloudIdManager->getCloudId($remoteShare['owner'], $remoteShare['remote']);
221
+                $shareId = $this->addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $ownerCloudId->getId(), $permissions, 'tmp_token_' . time(), $shareType);
222
+                $share->setId($shareId);
223
+                list($token, $remoteId) = $this->askOwnerToReShare($shareWith, $share, $shareId);
224
+                // remote share was create successfully if we get a valid token as return
225
+                $send = is_string($token) && $token !== '';
226
+            } catch (\Exception $e) {
227
+                // fall back to old re-share behavior if the remote server
228
+                // doesn't support flat re-shares (was introduced with Nextcloud 9.1)
229
+                $this->removeShareFromTable($share);
230
+                $shareId = $this->createFederatedShare($share);
231
+            }
232
+            if ($send) {
233
+                $this->updateSuccessfulReshare($shareId, $token);
234
+                $this->storeRemoteId($shareId, $remoteId);
235
+            } else {
236
+                $this->removeShareFromTable($share);
237
+                $message_t = $this->l->t('File is already shared with %s', [$shareWith]);
238
+                throw new \Exception($message_t);
239
+            }
240
+        } else {
241
+            $shareId = $this->createFederatedShare($share);
242
+        }
243
+
244
+        $data = $this->getRawShare($shareId);
245
+        return $this->createShareObject($data);
246
+    }
247
+
248
+    /**
249
+     * create federated share and inform the recipient
250
+     *
251
+     * @param IShare $share
252
+     * @return int
253
+     * @throws ShareNotFound
254
+     * @throws \Exception
255
+     */
256
+    protected function createFederatedShare(IShare $share) {
257
+        $token = $this->tokenHandler->generateToken();
258
+        $shareId = $this->addShareToDB(
259
+            $share->getNodeId(),
260
+            $share->getNodeType(),
261
+            $share->getSharedWith(),
262
+            $share->getSharedBy(),
263
+            $share->getShareOwner(),
264
+            $share->getPermissions(),
265
+            $token,
266
+            $share->getShareType()
267
+        );
268
+
269
+        $failure = false;
270
+
271
+        try {
272
+            $sharedByFederatedId = $share->getSharedBy();
273
+            if ($this->userManager->userExists($sharedByFederatedId)) {
274
+                $cloudId = $this->cloudIdManager->getCloudId($sharedByFederatedId, $this->addressHandler->generateRemoteURL());
275
+                $sharedByFederatedId = $cloudId->getId();
276
+            }
277
+            $ownerCloudId = $this->cloudIdManager->getCloudId($share->getShareOwner(), $this->addressHandler->generateRemoteURL());
278
+            $send = $this->notifications->sendRemoteShare(
279
+                $token,
280
+                $share->getSharedWith(),
281
+                $share->getNode()->getName(),
282
+                $shareId,
283
+                $share->getShareOwner(),
284
+                $ownerCloudId->getId(),
285
+                $share->getSharedBy(),
286
+                $sharedByFederatedId,
287
+                $share->getShareType()
288
+            );
289
+
290
+            if ($send === false) {
291
+                $failure = true;
292
+            }
293
+        } catch (\Exception $e) {
294
+            $this->logger->logException($e, [
295
+                'message' => 'Failed to notify remote server of federated share, removing share.',
296
+                'level' => ILogger::ERROR,
297
+                'app' => 'federatedfilesharing',
298
+            ]);
299
+            $failure = true;
300
+        }
301
+
302
+        if ($failure) {
303
+            $this->removeShareFromTableById($shareId);
304
+            $message_t = $this->l->t('Sharing %1$s failed, could not find %2$s, maybe the server is currently unreachable or uses a self-signed certificate.',
305
+                [$share->getNode()->getName(), $share->getSharedWith()]);
306
+            throw new \Exception($message_t);
307
+        }
308
+
309
+        return $shareId;
310
+    }
311
+
312
+    /**
313
+     * @param string $shareWith
314
+     * @param IShare $share
315
+     * @param string $shareId internal share Id
316
+     * @return array
317
+     * @throws \Exception
318
+     */
319
+    protected function askOwnerToReShare($shareWith, IShare $share, $shareId) {
320
+        $remoteShare = $this->getShareFromExternalShareTable($share);
321
+        $token = $remoteShare['share_token'];
322
+        $remoteId = $remoteShare['remote_id'];
323
+        $remote = $remoteShare['remote'];
324
+
325
+        list($token, $remoteId) = $this->notifications->requestReShare(
326
+            $token,
327
+            $remoteId,
328
+            $shareId,
329
+            $remote,
330
+            $shareWith,
331
+            $share->getPermissions(),
332
+            $share->getNode()->getName()
333
+        );
334
+
335
+        return [$token, $remoteId];
336
+    }
337
+
338
+    /**
339
+     * get federated share from the share_external table but exclude mounted link shares
340
+     *
341
+     * @param IShare $share
342
+     * @return array
343
+     * @throws ShareNotFound
344
+     */
345
+    protected function getShareFromExternalShareTable(IShare $share) {
346
+        $query = $this->dbConnection->getQueryBuilder();
347
+        $query->select('*')->from($this->externalShareTable)
348
+            ->where($query->expr()->eq('user', $query->createNamedParameter($share->getShareOwner())))
349
+            ->andWhere($query->expr()->eq('mountpoint', $query->createNamedParameter($share->getTarget())));
350
+        $qResult = $query->execute();
351
+        $result = $qResult->fetchAll();
352
+        $qResult->closeCursor();
353
+
354
+        if (isset($result[0]) && (int)$result[0]['remote_id'] > 0) {
355
+            return $result[0];
356
+        }
357
+
358
+        throw new ShareNotFound('share not found in share_external table');
359
+    }
360
+
361
+    /**
362
+     * add share to the database and return the ID
363
+     *
364
+     * @param int $itemSource
365
+     * @param string $itemType
366
+     * @param string $shareWith
367
+     * @param string $sharedBy
368
+     * @param string $uidOwner
369
+     * @param int $permissions
370
+     * @param string $token
371
+     * @param int $shareType
372
+     * @return int
373
+     */
374
+    private function addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token, $shareType) {
375
+        $qb = $this->dbConnection->getQueryBuilder();
376
+        $qb->insert('share')
377
+            ->setValue('share_type', $qb->createNamedParameter($shareType))
378
+            ->setValue('item_type', $qb->createNamedParameter($itemType))
379
+            ->setValue('item_source', $qb->createNamedParameter($itemSource))
380
+            ->setValue('file_source', $qb->createNamedParameter($itemSource))
381
+            ->setValue('share_with', $qb->createNamedParameter($shareWith))
382
+            ->setValue('uid_owner', $qb->createNamedParameter($uidOwner))
383
+            ->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
384
+            ->setValue('permissions', $qb->createNamedParameter($permissions))
385
+            ->setValue('token', $qb->createNamedParameter($token))
386
+            ->setValue('stime', $qb->createNamedParameter(time()));
387
+
388
+        /*
389 389
 		 * Added to fix https://github.com/owncloud/core/issues/22215
390 390
 		 * Can be removed once we get rid of ajax/share.php
391 391
 		 */
392
-		$qb->setValue('file_target', $qb->createNamedParameter(''));
393
-
394
-		$qb->execute();
395
-		$id = $qb->getLastInsertId();
396
-
397
-		return (int)$id;
398
-	}
399
-
400
-	/**
401
-	 * Update a share
402
-	 *
403
-	 * @param IShare $share
404
-	 * @return IShare The share object
405
-	 */
406
-	public function update(IShare $share) {
407
-		/*
392
+        $qb->setValue('file_target', $qb->createNamedParameter(''));
393
+
394
+        $qb->execute();
395
+        $id = $qb->getLastInsertId();
396
+
397
+        return (int)$id;
398
+    }
399
+
400
+    /**
401
+     * Update a share
402
+     *
403
+     * @param IShare $share
404
+     * @return IShare The share object
405
+     */
406
+    public function update(IShare $share) {
407
+        /*
408 408
 		 * We allow updating the permissions of federated shares
409 409
 		 */
410
-		$qb = $this->dbConnection->getQueryBuilder();
411
-		$qb->update('share')
412
-				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
413
-				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
414
-				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
415
-				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
416
-				->execute();
417
-
418
-		// send the updated permission to the owner/initiator, if they are not the same
419
-		if ($share->getShareOwner() !== $share->getSharedBy()) {
420
-			$this->sendPermissionUpdate($share);
421
-		}
422
-
423
-		return $share;
424
-	}
425
-
426
-	/**
427
-	 * send the updated permission to the owner/initiator, if they are not the same
428
-	 *
429
-	 * @param IShare $share
430
-	 * @throws ShareNotFound
431
-	 * @throws \OC\HintException
432
-	 */
433
-	protected function sendPermissionUpdate(IShare $share) {
434
-		$remoteId = $this->getRemoteId($share);
435
-		// if the local user is the owner we send the permission change to the initiator
436
-		if ($this->userManager->userExists($share->getShareOwner())) {
437
-			list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
438
-		} else { // ... if not we send the permission change to the owner
439
-			list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner());
440
-		}
441
-		$this->notifications->sendPermissionChange($remote, $remoteId, $share->getToken(), $share->getPermissions());
442
-	}
443
-
444
-
445
-	/**
446
-	 * update successful reShare with the correct token
447
-	 *
448
-	 * @param int $shareId
449
-	 * @param string $token
450
-	 */
451
-	protected function updateSuccessfulReShare($shareId, $token) {
452
-		$query = $this->dbConnection->getQueryBuilder();
453
-		$query->update('share')
454
-			->where($query->expr()->eq('id', $query->createNamedParameter($shareId)))
455
-			->set('token', $query->createNamedParameter($token))
456
-			->execute();
457
-	}
458
-
459
-	/**
460
-	 * store remote ID in federated reShare table
461
-	 *
462
-	 * @param $shareId
463
-	 * @param $remoteId
464
-	 */
465
-	public function storeRemoteId($shareId, $remoteId) {
466
-		$query = $this->dbConnection->getQueryBuilder();
467
-		$query->insert('federated_reshares')
468
-			->values(
469
-				[
470
-					'share_id' => $query->createNamedParameter($shareId),
471
-					'remote_id' => $query->createNamedParameter($remoteId),
472
-				]
473
-			);
474
-		$query->execute();
475
-	}
476
-
477
-	/**
478
-	 * get share ID on remote server for federated re-shares
479
-	 *
480
-	 * @param IShare $share
481
-	 * @return int
482
-	 * @throws ShareNotFound
483
-	 */
484
-	public function getRemoteId(IShare $share) {
485
-		$query = $this->dbConnection->getQueryBuilder();
486
-		$query->select('remote_id')->from('federated_reshares')
487
-			->where($query->expr()->eq('share_id', $query->createNamedParameter((int)$share->getId())));
488
-		$result = $query->execute();
489
-		$data = $result->fetch();
490
-		$result->closeCursor();
491
-
492
-		if (!is_array($data) || !isset($data['remote_id'])) {
493
-			throw new ShareNotFound();
494
-		}
495
-
496
-		return (int)$data['remote_id'];
497
-	}
498
-
499
-	/**
500
-	 * @inheritdoc
501
-	 */
502
-	public function move(IShare $share, $recipient) {
503
-		/*
410
+        $qb = $this->dbConnection->getQueryBuilder();
411
+        $qb->update('share')
412
+                ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
413
+                ->set('permissions', $qb->createNamedParameter($share->getPermissions()))
414
+                ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
415
+                ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
416
+                ->execute();
417
+
418
+        // send the updated permission to the owner/initiator, if they are not the same
419
+        if ($share->getShareOwner() !== $share->getSharedBy()) {
420
+            $this->sendPermissionUpdate($share);
421
+        }
422
+
423
+        return $share;
424
+    }
425
+
426
+    /**
427
+     * send the updated permission to the owner/initiator, if they are not the same
428
+     *
429
+     * @param IShare $share
430
+     * @throws ShareNotFound
431
+     * @throws \OC\HintException
432
+     */
433
+    protected function sendPermissionUpdate(IShare $share) {
434
+        $remoteId = $this->getRemoteId($share);
435
+        // if the local user is the owner we send the permission change to the initiator
436
+        if ($this->userManager->userExists($share->getShareOwner())) {
437
+            list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
438
+        } else { // ... if not we send the permission change to the owner
439
+            list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner());
440
+        }
441
+        $this->notifications->sendPermissionChange($remote, $remoteId, $share->getToken(), $share->getPermissions());
442
+    }
443
+
444
+
445
+    /**
446
+     * update successful reShare with the correct token
447
+     *
448
+     * @param int $shareId
449
+     * @param string $token
450
+     */
451
+    protected function updateSuccessfulReShare($shareId, $token) {
452
+        $query = $this->dbConnection->getQueryBuilder();
453
+        $query->update('share')
454
+            ->where($query->expr()->eq('id', $query->createNamedParameter($shareId)))
455
+            ->set('token', $query->createNamedParameter($token))
456
+            ->execute();
457
+    }
458
+
459
+    /**
460
+     * store remote ID in federated reShare table
461
+     *
462
+     * @param $shareId
463
+     * @param $remoteId
464
+     */
465
+    public function storeRemoteId($shareId, $remoteId) {
466
+        $query = $this->dbConnection->getQueryBuilder();
467
+        $query->insert('federated_reshares')
468
+            ->values(
469
+                [
470
+                    'share_id' => $query->createNamedParameter($shareId),
471
+                    'remote_id' => $query->createNamedParameter($remoteId),
472
+                ]
473
+            );
474
+        $query->execute();
475
+    }
476
+
477
+    /**
478
+     * get share ID on remote server for federated re-shares
479
+     *
480
+     * @param IShare $share
481
+     * @return int
482
+     * @throws ShareNotFound
483
+     */
484
+    public function getRemoteId(IShare $share) {
485
+        $query = $this->dbConnection->getQueryBuilder();
486
+        $query->select('remote_id')->from('federated_reshares')
487
+            ->where($query->expr()->eq('share_id', $query->createNamedParameter((int)$share->getId())));
488
+        $result = $query->execute();
489
+        $data = $result->fetch();
490
+        $result->closeCursor();
491
+
492
+        if (!is_array($data) || !isset($data['remote_id'])) {
493
+            throw new ShareNotFound();
494
+        }
495
+
496
+        return (int)$data['remote_id'];
497
+    }
498
+
499
+    /**
500
+     * @inheritdoc
501
+     */
502
+    public function move(IShare $share, $recipient) {
503
+        /*
504 504
 		 * This function does nothing yet as it is just for outgoing
505 505
 		 * federated shares.
506 506
 		 */
507
-		return $share;
508
-	}
509
-
510
-	/**
511
-	 * Get all children of this share
512
-	 *
513
-	 * @param IShare $parent
514
-	 * @return IShare[]
515
-	 */
516
-	public function getChildren(IShare $parent) {
517
-		$children = [];
518
-
519
-		$qb = $this->dbConnection->getQueryBuilder();
520
-		$qb->select('*')
521
-			->from('share')
522
-			->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
523
-			->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)))
524
-			->orderBy('id');
525
-
526
-		$cursor = $qb->execute();
527
-		while ($data = $cursor->fetch()) {
528
-			$children[] = $this->createShareObject($data);
529
-		}
530
-		$cursor->closeCursor();
531
-
532
-		return $children;
533
-	}
534
-
535
-	/**
536
-	 * Delete a share (owner unShares the file)
537
-	 *
538
-	 * @param IShare $share
539
-	 * @throws ShareNotFound
540
-	 * @throws \OC\HintException
541
-	 */
542
-	public function delete(IShare $share) {
543
-		list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedWith());
544
-
545
-		// if the local user is the owner we can send the unShare request directly...
546
-		if ($this->userManager->userExists($share->getShareOwner())) {
547
-			$this->notifications->sendRemoteUnShare($remote, $share->getId(), $share->getToken());
548
-			$this->revokeShare($share, true);
549
-		} else { // ... if not we need to correct ID for the unShare request
550
-			$remoteId = $this->getRemoteId($share);
551
-			$this->notifications->sendRemoteUnShare($remote, $remoteId, $share->getToken());
552
-			$this->revokeShare($share, false);
553
-		}
554
-
555
-		// only remove the share when all messages are send to not lose information
556
-		// about the share to early
557
-		$this->removeShareFromTable($share);
558
-	}
559
-
560
-	/**
561
-	 * in case of a re-share we need to send the other use (initiator or owner)
562
-	 * a message that the file was unshared
563
-	 *
564
-	 * @param IShare $share
565
-	 * @param bool $isOwner the user can either be the owner or the user who re-sahred it
566
-	 * @throws ShareNotFound
567
-	 * @throws \OC\HintException
568
-	 */
569
-	protected function revokeShare($share, $isOwner) {
570
-		if ($this->userManager->userExists($share->getShareOwner()) && $this->userManager->userExists($share->getSharedBy())) {
571
-			// If both the owner and the initiator of the share are local users we don't have to notify anybody else
572
-			return;
573
-		}
574
-
575
-		// also send a unShare request to the initiator, if this is a different user than the owner
576
-		if ($share->getShareOwner() !== $share->getSharedBy()) {
577
-			if ($isOwner) {
578
-				list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
579
-			} else {
580
-				list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner());
581
-			}
582
-			$remoteId = $this->getRemoteId($share);
583
-			$this->notifications->sendRevokeShare($remote, $remoteId, $share->getToken());
584
-		}
585
-	}
586
-
587
-	/**
588
-	 * remove share from table
589
-	 *
590
-	 * @param IShare $share
591
-	 */
592
-	public function removeShareFromTable(IShare $share) {
593
-		$this->removeShareFromTableById($share->getId());
594
-	}
595
-
596
-	/**
597
-	 * remove share from table
598
-	 *
599
-	 * @param string $shareId
600
-	 */
601
-	private function removeShareFromTableById($shareId) {
602
-		$qb = $this->dbConnection->getQueryBuilder();
603
-		$qb->delete('share')
604
-			->where($qb->expr()->eq('id', $qb->createNamedParameter($shareId)))
605
-			->andWhere($qb->expr()->neq('share_type', $qb->createNamedParameter(IShare::TYPE_CIRCLE)));
606
-		$qb->execute();
607
-
608
-		$qb->delete('federated_reshares')
609
-			->where($qb->expr()->eq('share_id', $qb->createNamedParameter($shareId)));
610
-		$qb->execute();
611
-	}
612
-
613
-	/**
614
-	 * @inheritdoc
615
-	 */
616
-	public function deleteFromSelf(IShare $share, $recipient) {
617
-		// nothing to do here. Technically deleteFromSelf in the context of federated
618
-		// shares is a umount of an external storage. This is handled here
619
-		// apps/files_sharing/lib/external/manager.php
620
-		// TODO move this code over to this app
621
-	}
622
-
623
-	public function restore(IShare $share, string $recipient): IShare {
624
-		throw new GenericShareException('not implemented');
625
-	}
626
-
627
-
628
-	public function getSharesInFolder($userId, Folder $node, $reshares) {
629
-		$qb = $this->dbConnection->getQueryBuilder();
630
-		$qb->select('*')
631
-			->from('share', 's')
632
-			->andWhere($qb->expr()->orX(
633
-				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
634
-				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
635
-			))
636
-			->andWhere(
637
-				$qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_REMOTE))
638
-			);
639
-
640
-		/**
641
-		 * Reshares for this user are shares where they are the owner.
642
-		 */
643
-		if ($reshares === false) {
644
-			$qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
645
-		} else {
646
-			$qb->andWhere(
647
-				$qb->expr()->orX(
648
-					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
649
-					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
650
-				)
651
-			);
652
-		}
653
-
654
-		$qb->innerJoin('s', 'filecache' ,'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
655
-		$qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())));
656
-
657
-		$qb->orderBy('id');
658
-
659
-		$cursor = $qb->execute();
660
-		$shares = [];
661
-		while ($data = $cursor->fetch()) {
662
-			$shares[$data['fileid']][] = $this->createShareObject($data);
663
-		}
664
-		$cursor->closeCursor();
665
-
666
-		return $shares;
667
-	}
668
-
669
-	/**
670
-	 * @inheritdoc
671
-	 */
672
-	public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
673
-		$qb = $this->dbConnection->getQueryBuilder();
674
-		$qb->select('*')
675
-			->from('share');
676
-
677
-		$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter($shareType)));
678
-
679
-		/**
680
-		 * Reshares for this user are shares where they are the owner.
681
-		 */
682
-		if ($reshares === false) {
683
-			//Special case for old shares created via the web UI
684
-			$or1 = $qb->expr()->andX(
685
-				$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
686
-				$qb->expr()->isNull('uid_initiator')
687
-			);
688
-
689
-			$qb->andWhere(
690
-				$qb->expr()->orX(
691
-					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
692
-					$or1
693
-				)
694
-			);
695
-		} else {
696
-			$qb->andWhere(
697
-				$qb->expr()->orX(
698
-					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
699
-					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
700
-				)
701
-			);
702
-		}
703
-
704
-		if ($node !== null) {
705
-			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
706
-		}
707
-
708
-		if ($limit !== -1) {
709
-			$qb->setMaxResults($limit);
710
-		}
711
-
712
-		$qb->setFirstResult($offset);
713
-		$qb->orderBy('id');
714
-
715
-		$cursor = $qb->execute();
716
-		$shares = [];
717
-		while ($data = $cursor->fetch()) {
718
-			$shares[] = $this->createShareObject($data);
719
-		}
720
-		$cursor->closeCursor();
721
-
722
-		return $shares;
723
-	}
724
-
725
-	/**
726
-	 * @inheritdoc
727
-	 */
728
-	public function getShareById($id, $recipientId = null) {
729
-		$qb = $this->dbConnection->getQueryBuilder();
730
-
731
-		$qb->select('*')
732
-			->from('share')
733
-			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
734
-			->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)));
735
-
736
-		$cursor = $qb->execute();
737
-		$data = $cursor->fetch();
738
-		$cursor->closeCursor();
739
-
740
-		if ($data === false) {
741
-			throw new ShareNotFound('Can not find share with ID: ' . $id);
742
-		}
743
-
744
-		try {
745
-			$share = $this->createShareObject($data);
746
-		} catch (InvalidShare $e) {
747
-			throw new ShareNotFound();
748
-		}
749
-
750
-		return $share;
751
-	}
752
-
753
-	/**
754
-	 * Get shares for a given path
755
-	 *
756
-	 * @param \OCP\Files\Node $path
757
-	 * @return IShare[]
758
-	 */
759
-	public function getSharesByPath(Node $path) {
760
-		$qb = $this->dbConnection->getQueryBuilder();
761
-
762
-		// get federated user shares
763
-		$cursor = $qb->select('*')
764
-			->from('share')
765
-			->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
766
-			->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)))
767
-			->execute();
768
-
769
-		$shares = [];
770
-		while ($data = $cursor->fetch()) {
771
-			$shares[] = $this->createShareObject($data);
772
-		}
773
-		$cursor->closeCursor();
774
-
775
-		return $shares;
776
-	}
777
-
778
-	/**
779
-	 * @inheritdoc
780
-	 */
781
-	public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
782
-		/** @var IShare[] $shares */
783
-		$shares = [];
784
-
785
-		//Get shares directly with this user
786
-		$qb = $this->dbConnection->getQueryBuilder();
787
-		$qb->select('*')
788
-			->from('share');
789
-
790
-		// Order by id
791
-		$qb->orderBy('id');
792
-
793
-		// Set limit and offset
794
-		if ($limit !== -1) {
795
-			$qb->setMaxResults($limit);
796
-		}
797
-		$qb->setFirstResult($offset);
798
-
799
-		$qb->where($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)));
800
-		$qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)));
801
-
802
-		// Filter by node if provided
803
-		if ($node !== null) {
804
-			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
805
-		}
806
-
807
-		$cursor = $qb->execute();
808
-
809
-		while ($data = $cursor->fetch()) {
810
-			$shares[] = $this->createShareObject($data);
811
-		}
812
-		$cursor->closeCursor();
813
-
814
-
815
-		return $shares;
816
-	}
817
-
818
-	/**
819
-	 * Get a share by token
820
-	 *
821
-	 * @param string $token
822
-	 * @return IShare
823
-	 * @throws ShareNotFound
824
-	 */
825
-	public function getShareByToken($token) {
826
-		$qb = $this->dbConnection->getQueryBuilder();
827
-
828
-		$cursor = $qb->select('*')
829
-			->from('share')
830
-			->where($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)))
831
-			->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
832
-			->execute();
833
-
834
-		$data = $cursor->fetch();
835
-
836
-		if ($data === false) {
837
-			throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
838
-		}
839
-
840
-		try {
841
-			$share = $this->createShareObject($data);
842
-		} catch (InvalidShare $e) {
843
-			throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
844
-		}
845
-
846
-		return $share;
847
-	}
848
-
849
-	/**
850
-	 * get database row of a give share
851
-	 *
852
-	 * @param $id
853
-	 * @return array
854
-	 * @throws ShareNotFound
855
-	 */
856
-	private function getRawShare($id) {
857
-
858
-		// Now fetch the inserted share and create a complete share object
859
-		$qb = $this->dbConnection->getQueryBuilder();
860
-		$qb->select('*')
861
-			->from('share')
862
-			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
863
-
864
-		$cursor = $qb->execute();
865
-		$data = $cursor->fetch();
866
-		$cursor->closeCursor();
867
-
868
-		if ($data === false) {
869
-			throw new ShareNotFound;
870
-		}
871
-
872
-		return $data;
873
-	}
874
-
875
-	/**
876
-	 * Create a share object from an database row
877
-	 *
878
-	 * @param array $data
879
-	 * @return IShare
880
-	 * @throws InvalidShare
881
-	 * @throws ShareNotFound
882
-	 */
883
-	private function createShareObject($data) {
884
-		$share = new Share($this->rootFolder, $this->userManager);
885
-		$share->setId((int)$data['id'])
886
-			->setShareType((int)$data['share_type'])
887
-			->setPermissions((int)$data['permissions'])
888
-			->setTarget($data['file_target'])
889
-			->setMailSend((bool)$data['mail_send'])
890
-			->setToken($data['token']);
891
-
892
-		$shareTime = new \DateTime();
893
-		$shareTime->setTimestamp((int)$data['stime']);
894
-		$share->setShareTime($shareTime);
895
-		$share->setSharedWith($data['share_with']);
896
-
897
-		if ($data['uid_initiator'] !== null) {
898
-			$share->setShareOwner($data['uid_owner']);
899
-			$share->setSharedBy($data['uid_initiator']);
900
-		} else {
901
-			//OLD SHARE
902
-			$share->setSharedBy($data['uid_owner']);
903
-			$path = $this->getNode($share->getSharedBy(), (int)$data['file_source']);
904
-
905
-			$owner = $path->getOwner();
906
-			$share->setShareOwner($owner->getUID());
907
-		}
908
-
909
-		$share->setNodeId((int)$data['file_source']);
910
-		$share->setNodeType($data['item_type']);
911
-
912
-		$share->setProviderId($this->identifier());
913
-
914
-		return $share;
915
-	}
916
-
917
-	/**
918
-	 * Get the node with file $id for $user
919
-	 *
920
-	 * @param string $userId
921
-	 * @param int $id
922
-	 * @return \OCP\Files\File|\OCP\Files\Folder
923
-	 * @throws InvalidShare
924
-	 */
925
-	private function getNode($userId, $id) {
926
-		try {
927
-			$userFolder = $this->rootFolder->getUserFolder($userId);
928
-		} catch (NotFoundException $e) {
929
-			throw new InvalidShare();
930
-		}
931
-
932
-		$nodes = $userFolder->getById($id);
933
-
934
-		if (empty($nodes)) {
935
-			throw new InvalidShare();
936
-		}
937
-
938
-		return $nodes[0];
939
-	}
940
-
941
-	/**
942
-	 * A user is deleted from the system
943
-	 * So clean up the relevant shares.
944
-	 *
945
-	 * @param string $uid
946
-	 * @param int $shareType
947
-	 */
948
-	public function userDeleted($uid, $shareType) {
949
-		//TODO: probabaly a good idea to send unshare info to remote servers
950
-
951
-		$qb = $this->dbConnection->getQueryBuilder();
952
-
953
-		$qb->delete('share')
954
-			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_REMOTE)))
955
-			->andWhere($qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)))
956
-			->execute();
957
-	}
958
-
959
-	/**
960
-	 * This provider does not handle groups
961
-	 *
962
-	 * @param string $gid
963
-	 */
964
-	public function groupDeleted($gid) {
965
-		// We don't handle groups here
966
-	}
967
-
968
-	/**
969
-	 * This provider does not handle groups
970
-	 *
971
-	 * @param string $uid
972
-	 * @param string $gid
973
-	 */
974
-	public function userDeletedFromGroup($uid, $gid) {
975
-		// We don't handle groups here
976
-	}
977
-
978
-	/**
979
-	 * check if users from other Nextcloud instances are allowed to mount public links share by this instance
980
-	 *
981
-	 * @return bool
982
-	 */
983
-	public function isOutgoingServer2serverShareEnabled() {
984
-		if ($this->gsConfig->onlyInternalFederation()) {
985
-			return false;
986
-		}
987
-		$result = $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes');
988
-		return ($result === 'yes');
989
-	}
990
-
991
-	/**
992
-	 * check if users are allowed to mount public links from other Nextclouds
993
-	 *
994
-	 * @return bool
995
-	 */
996
-	public function isIncomingServer2serverShareEnabled() {
997
-		if ($this->gsConfig->onlyInternalFederation()) {
998
-			return false;
999
-		}
1000
-		$result = $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes');
1001
-		return ($result === 'yes');
1002
-	}
1003
-
1004
-
1005
-	/**
1006
-	 * check if users from other Nextcloud instances are allowed to send federated group shares
1007
-	 *
1008
-	 * @return bool
1009
-	 */
1010
-	public function isOutgoingServer2serverGroupShareEnabled() {
1011
-		if ($this->gsConfig->onlyInternalFederation()) {
1012
-			return false;
1013
-		}
1014
-		$result = $this->config->getAppValue('files_sharing', 'outgoing_server2server_group_share_enabled', 'no');
1015
-		return ($result === 'yes');
1016
-	}
1017
-
1018
-	/**
1019
-	 * check if users are allowed to receive federated group shares
1020
-	 *
1021
-	 * @return bool
1022
-	 */
1023
-	public function isIncomingServer2serverGroupShareEnabled() {
1024
-		if ($this->gsConfig->onlyInternalFederation()) {
1025
-			return false;
1026
-		}
1027
-		$result = $this->config->getAppValue('files_sharing', 'incoming_server2server_group_share_enabled', 'no');
1028
-		return ($result === 'yes');
1029
-	}
1030
-
1031
-	/**
1032
-	 * check if federated group sharing is supported, therefore the OCM API need to be enabled
1033
-	 *
1034
-	 * @return bool
1035
-	 */
1036
-	public function isFederatedGroupSharingSupported() {
1037
-		return $this->cloudFederationProviderManager->isReady();
1038
-	}
1039
-
1040
-	/**
1041
-	 * Check if querying sharees on the lookup server is enabled
1042
-	 *
1043
-	 * @return bool
1044
-	 */
1045
-	public function isLookupServerQueriesEnabled() {
1046
-		// in a global scale setup we should always query the lookup server
1047
-		if ($this->gsConfig->isGlobalScaleEnabled()) {
1048
-			return true;
1049
-		}
1050
-		$result = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'yes');
1051
-		return ($result === 'yes');
1052
-	}
1053
-
1054
-
1055
-	/**
1056
-	 * Check if it is allowed to publish user specific data to the lookup server
1057
-	 *
1058
-	 * @return bool
1059
-	 */
1060
-	public function isLookupServerUploadEnabled() {
1061
-		// in a global scale setup the admin is responsible to keep the lookup server up-to-date
1062
-		if ($this->gsConfig->isGlobalScaleEnabled()) {
1063
-			return false;
1064
-		}
1065
-		$result = $this->config->getAppValue('files_sharing', 'lookupServerUploadEnabled', 'yes');
1066
-		return ($result === 'yes');
1067
-	}
1068
-
1069
-	/**
1070
-	 * @inheritdoc
1071
-	 */
1072
-	public function getAccessList($nodes, $currentAccess) {
1073
-		$ids = [];
1074
-		foreach ($nodes as $node) {
1075
-			$ids[] = $node->getId();
1076
-		}
1077
-
1078
-		$qb = $this->dbConnection->getQueryBuilder();
1079
-		$qb->select('share_with', 'token', 'file_source')
1080
-			->from('share')
1081
-			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_REMOTE)))
1082
-			->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)))
1083
-			->andWhere($qb->expr()->orX(
1084
-				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
1085
-				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
1086
-			));
1087
-		$cursor = $qb->execute();
1088
-
1089
-		if ($currentAccess === false) {
1090
-			$remote = $cursor->fetch() !== false;
1091
-			$cursor->closeCursor();
1092
-
1093
-			return ['remote' => $remote];
1094
-		}
1095
-
1096
-		$remote = [];
1097
-		while ($row = $cursor->fetch()) {
1098
-			$remote[$row['share_with']] = [
1099
-				'node_id' => $row['file_source'],
1100
-				'token' => $row['token'],
1101
-			];
1102
-		}
1103
-		$cursor->closeCursor();
1104
-
1105
-		return ['remote' => $remote];
1106
-	}
1107
-
1108
-	public function getAllShares(): iterable {
1109
-		$qb = $this->dbConnection->getQueryBuilder();
1110
-
1111
-		$qb->select('*')
1112
-			->from('share')
1113
-			->where(
1114
-				$qb->expr()->orX(
1115
-					$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share\IShare::TYPE_REMOTE)),
1116
-					$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share\IShare::TYPE_REMOTE_GROUP))
1117
-				)
1118
-			);
1119
-
1120
-		$cursor = $qb->execute();
1121
-		while ($data = $cursor->fetch()) {
1122
-			try {
1123
-				$share = $this->createShareObject($data);
1124
-			} catch (InvalidShare $e) {
1125
-				continue;
1126
-			} catch (ShareNotFound $e) {
1127
-				continue;
1128
-			}
1129
-
1130
-			yield $share;
1131
-		}
1132
-		$cursor->closeCursor();
1133
-	}
507
+        return $share;
508
+    }
509
+
510
+    /**
511
+     * Get all children of this share
512
+     *
513
+     * @param IShare $parent
514
+     * @return IShare[]
515
+     */
516
+    public function getChildren(IShare $parent) {
517
+        $children = [];
518
+
519
+        $qb = $this->dbConnection->getQueryBuilder();
520
+        $qb->select('*')
521
+            ->from('share')
522
+            ->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
523
+            ->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)))
524
+            ->orderBy('id');
525
+
526
+        $cursor = $qb->execute();
527
+        while ($data = $cursor->fetch()) {
528
+            $children[] = $this->createShareObject($data);
529
+        }
530
+        $cursor->closeCursor();
531
+
532
+        return $children;
533
+    }
534
+
535
+    /**
536
+     * Delete a share (owner unShares the file)
537
+     *
538
+     * @param IShare $share
539
+     * @throws ShareNotFound
540
+     * @throws \OC\HintException
541
+     */
542
+    public function delete(IShare $share) {
543
+        list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedWith());
544
+
545
+        // if the local user is the owner we can send the unShare request directly...
546
+        if ($this->userManager->userExists($share->getShareOwner())) {
547
+            $this->notifications->sendRemoteUnShare($remote, $share->getId(), $share->getToken());
548
+            $this->revokeShare($share, true);
549
+        } else { // ... if not we need to correct ID for the unShare request
550
+            $remoteId = $this->getRemoteId($share);
551
+            $this->notifications->sendRemoteUnShare($remote, $remoteId, $share->getToken());
552
+            $this->revokeShare($share, false);
553
+        }
554
+
555
+        // only remove the share when all messages are send to not lose information
556
+        // about the share to early
557
+        $this->removeShareFromTable($share);
558
+    }
559
+
560
+    /**
561
+     * in case of a re-share we need to send the other use (initiator or owner)
562
+     * a message that the file was unshared
563
+     *
564
+     * @param IShare $share
565
+     * @param bool $isOwner the user can either be the owner or the user who re-sahred it
566
+     * @throws ShareNotFound
567
+     * @throws \OC\HintException
568
+     */
569
+    protected function revokeShare($share, $isOwner) {
570
+        if ($this->userManager->userExists($share->getShareOwner()) && $this->userManager->userExists($share->getSharedBy())) {
571
+            // If both the owner and the initiator of the share are local users we don't have to notify anybody else
572
+            return;
573
+        }
574
+
575
+        // also send a unShare request to the initiator, if this is a different user than the owner
576
+        if ($share->getShareOwner() !== $share->getSharedBy()) {
577
+            if ($isOwner) {
578
+                list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
579
+            } else {
580
+                list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner());
581
+            }
582
+            $remoteId = $this->getRemoteId($share);
583
+            $this->notifications->sendRevokeShare($remote, $remoteId, $share->getToken());
584
+        }
585
+    }
586
+
587
+    /**
588
+     * remove share from table
589
+     *
590
+     * @param IShare $share
591
+     */
592
+    public function removeShareFromTable(IShare $share) {
593
+        $this->removeShareFromTableById($share->getId());
594
+    }
595
+
596
+    /**
597
+     * remove share from table
598
+     *
599
+     * @param string $shareId
600
+     */
601
+    private function removeShareFromTableById($shareId) {
602
+        $qb = $this->dbConnection->getQueryBuilder();
603
+        $qb->delete('share')
604
+            ->where($qb->expr()->eq('id', $qb->createNamedParameter($shareId)))
605
+            ->andWhere($qb->expr()->neq('share_type', $qb->createNamedParameter(IShare::TYPE_CIRCLE)));
606
+        $qb->execute();
607
+
608
+        $qb->delete('federated_reshares')
609
+            ->where($qb->expr()->eq('share_id', $qb->createNamedParameter($shareId)));
610
+        $qb->execute();
611
+    }
612
+
613
+    /**
614
+     * @inheritdoc
615
+     */
616
+    public function deleteFromSelf(IShare $share, $recipient) {
617
+        // nothing to do here. Technically deleteFromSelf in the context of federated
618
+        // shares is a umount of an external storage. This is handled here
619
+        // apps/files_sharing/lib/external/manager.php
620
+        // TODO move this code over to this app
621
+    }
622
+
623
+    public function restore(IShare $share, string $recipient): IShare {
624
+        throw new GenericShareException('not implemented');
625
+    }
626
+
627
+
628
+    public function getSharesInFolder($userId, Folder $node, $reshares) {
629
+        $qb = $this->dbConnection->getQueryBuilder();
630
+        $qb->select('*')
631
+            ->from('share', 's')
632
+            ->andWhere($qb->expr()->orX(
633
+                $qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
634
+                $qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
635
+            ))
636
+            ->andWhere(
637
+                $qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_REMOTE))
638
+            );
639
+
640
+        /**
641
+         * Reshares for this user are shares where they are the owner.
642
+         */
643
+        if ($reshares === false) {
644
+            $qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
645
+        } else {
646
+            $qb->andWhere(
647
+                $qb->expr()->orX(
648
+                    $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
649
+                    $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
650
+                )
651
+            );
652
+        }
653
+
654
+        $qb->innerJoin('s', 'filecache' ,'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
655
+        $qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())));
656
+
657
+        $qb->orderBy('id');
658
+
659
+        $cursor = $qb->execute();
660
+        $shares = [];
661
+        while ($data = $cursor->fetch()) {
662
+            $shares[$data['fileid']][] = $this->createShareObject($data);
663
+        }
664
+        $cursor->closeCursor();
665
+
666
+        return $shares;
667
+    }
668
+
669
+    /**
670
+     * @inheritdoc
671
+     */
672
+    public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
673
+        $qb = $this->dbConnection->getQueryBuilder();
674
+        $qb->select('*')
675
+            ->from('share');
676
+
677
+        $qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter($shareType)));
678
+
679
+        /**
680
+         * Reshares for this user are shares where they are the owner.
681
+         */
682
+        if ($reshares === false) {
683
+            //Special case for old shares created via the web UI
684
+            $or1 = $qb->expr()->andX(
685
+                $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
686
+                $qb->expr()->isNull('uid_initiator')
687
+            );
688
+
689
+            $qb->andWhere(
690
+                $qb->expr()->orX(
691
+                    $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
692
+                    $or1
693
+                )
694
+            );
695
+        } else {
696
+            $qb->andWhere(
697
+                $qb->expr()->orX(
698
+                    $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
699
+                    $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
700
+                )
701
+            );
702
+        }
703
+
704
+        if ($node !== null) {
705
+            $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
706
+        }
707
+
708
+        if ($limit !== -1) {
709
+            $qb->setMaxResults($limit);
710
+        }
711
+
712
+        $qb->setFirstResult($offset);
713
+        $qb->orderBy('id');
714
+
715
+        $cursor = $qb->execute();
716
+        $shares = [];
717
+        while ($data = $cursor->fetch()) {
718
+            $shares[] = $this->createShareObject($data);
719
+        }
720
+        $cursor->closeCursor();
721
+
722
+        return $shares;
723
+    }
724
+
725
+    /**
726
+     * @inheritdoc
727
+     */
728
+    public function getShareById($id, $recipientId = null) {
729
+        $qb = $this->dbConnection->getQueryBuilder();
730
+
731
+        $qb->select('*')
732
+            ->from('share')
733
+            ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
734
+            ->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)));
735
+
736
+        $cursor = $qb->execute();
737
+        $data = $cursor->fetch();
738
+        $cursor->closeCursor();
739
+
740
+        if ($data === false) {
741
+            throw new ShareNotFound('Can not find share with ID: ' . $id);
742
+        }
743
+
744
+        try {
745
+            $share = $this->createShareObject($data);
746
+        } catch (InvalidShare $e) {
747
+            throw new ShareNotFound();
748
+        }
749
+
750
+        return $share;
751
+    }
752
+
753
+    /**
754
+     * Get shares for a given path
755
+     *
756
+     * @param \OCP\Files\Node $path
757
+     * @return IShare[]
758
+     */
759
+    public function getSharesByPath(Node $path) {
760
+        $qb = $this->dbConnection->getQueryBuilder();
761
+
762
+        // get federated user shares
763
+        $cursor = $qb->select('*')
764
+            ->from('share')
765
+            ->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
766
+            ->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)))
767
+            ->execute();
768
+
769
+        $shares = [];
770
+        while ($data = $cursor->fetch()) {
771
+            $shares[] = $this->createShareObject($data);
772
+        }
773
+        $cursor->closeCursor();
774
+
775
+        return $shares;
776
+    }
777
+
778
+    /**
779
+     * @inheritdoc
780
+     */
781
+    public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
782
+        /** @var IShare[] $shares */
783
+        $shares = [];
784
+
785
+        //Get shares directly with this user
786
+        $qb = $this->dbConnection->getQueryBuilder();
787
+        $qb->select('*')
788
+            ->from('share');
789
+
790
+        // Order by id
791
+        $qb->orderBy('id');
792
+
793
+        // Set limit and offset
794
+        if ($limit !== -1) {
795
+            $qb->setMaxResults($limit);
796
+        }
797
+        $qb->setFirstResult($offset);
798
+
799
+        $qb->where($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)));
800
+        $qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)));
801
+
802
+        // Filter by node if provided
803
+        if ($node !== null) {
804
+            $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
805
+        }
806
+
807
+        $cursor = $qb->execute();
808
+
809
+        while ($data = $cursor->fetch()) {
810
+            $shares[] = $this->createShareObject($data);
811
+        }
812
+        $cursor->closeCursor();
813
+
814
+
815
+        return $shares;
816
+    }
817
+
818
+    /**
819
+     * Get a share by token
820
+     *
821
+     * @param string $token
822
+     * @return IShare
823
+     * @throws ShareNotFound
824
+     */
825
+    public function getShareByToken($token) {
826
+        $qb = $this->dbConnection->getQueryBuilder();
827
+
828
+        $cursor = $qb->select('*')
829
+            ->from('share')
830
+            ->where($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)))
831
+            ->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
832
+            ->execute();
833
+
834
+        $data = $cursor->fetch();
835
+
836
+        if ($data === false) {
837
+            throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
838
+        }
839
+
840
+        try {
841
+            $share = $this->createShareObject($data);
842
+        } catch (InvalidShare $e) {
843
+            throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
844
+        }
845
+
846
+        return $share;
847
+    }
848
+
849
+    /**
850
+     * get database row of a give share
851
+     *
852
+     * @param $id
853
+     * @return array
854
+     * @throws ShareNotFound
855
+     */
856
+    private function getRawShare($id) {
857
+
858
+        // Now fetch the inserted share and create a complete share object
859
+        $qb = $this->dbConnection->getQueryBuilder();
860
+        $qb->select('*')
861
+            ->from('share')
862
+            ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
863
+
864
+        $cursor = $qb->execute();
865
+        $data = $cursor->fetch();
866
+        $cursor->closeCursor();
867
+
868
+        if ($data === false) {
869
+            throw new ShareNotFound;
870
+        }
871
+
872
+        return $data;
873
+    }
874
+
875
+    /**
876
+     * Create a share object from an database row
877
+     *
878
+     * @param array $data
879
+     * @return IShare
880
+     * @throws InvalidShare
881
+     * @throws ShareNotFound
882
+     */
883
+    private function createShareObject($data) {
884
+        $share = new Share($this->rootFolder, $this->userManager);
885
+        $share->setId((int)$data['id'])
886
+            ->setShareType((int)$data['share_type'])
887
+            ->setPermissions((int)$data['permissions'])
888
+            ->setTarget($data['file_target'])
889
+            ->setMailSend((bool)$data['mail_send'])
890
+            ->setToken($data['token']);
891
+
892
+        $shareTime = new \DateTime();
893
+        $shareTime->setTimestamp((int)$data['stime']);
894
+        $share->setShareTime($shareTime);
895
+        $share->setSharedWith($data['share_with']);
896
+
897
+        if ($data['uid_initiator'] !== null) {
898
+            $share->setShareOwner($data['uid_owner']);
899
+            $share->setSharedBy($data['uid_initiator']);
900
+        } else {
901
+            //OLD SHARE
902
+            $share->setSharedBy($data['uid_owner']);
903
+            $path = $this->getNode($share->getSharedBy(), (int)$data['file_source']);
904
+
905
+            $owner = $path->getOwner();
906
+            $share->setShareOwner($owner->getUID());
907
+        }
908
+
909
+        $share->setNodeId((int)$data['file_source']);
910
+        $share->setNodeType($data['item_type']);
911
+
912
+        $share->setProviderId($this->identifier());
913
+
914
+        return $share;
915
+    }
916
+
917
+    /**
918
+     * Get the node with file $id for $user
919
+     *
920
+     * @param string $userId
921
+     * @param int $id
922
+     * @return \OCP\Files\File|\OCP\Files\Folder
923
+     * @throws InvalidShare
924
+     */
925
+    private function getNode($userId, $id) {
926
+        try {
927
+            $userFolder = $this->rootFolder->getUserFolder($userId);
928
+        } catch (NotFoundException $e) {
929
+            throw new InvalidShare();
930
+        }
931
+
932
+        $nodes = $userFolder->getById($id);
933
+
934
+        if (empty($nodes)) {
935
+            throw new InvalidShare();
936
+        }
937
+
938
+        return $nodes[0];
939
+    }
940
+
941
+    /**
942
+     * A user is deleted from the system
943
+     * So clean up the relevant shares.
944
+     *
945
+     * @param string $uid
946
+     * @param int $shareType
947
+     */
948
+    public function userDeleted($uid, $shareType) {
949
+        //TODO: probabaly a good idea to send unshare info to remote servers
950
+
951
+        $qb = $this->dbConnection->getQueryBuilder();
952
+
953
+        $qb->delete('share')
954
+            ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_REMOTE)))
955
+            ->andWhere($qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)))
956
+            ->execute();
957
+    }
958
+
959
+    /**
960
+     * This provider does not handle groups
961
+     *
962
+     * @param string $gid
963
+     */
964
+    public function groupDeleted($gid) {
965
+        // We don't handle groups here
966
+    }
967
+
968
+    /**
969
+     * This provider does not handle groups
970
+     *
971
+     * @param string $uid
972
+     * @param string $gid
973
+     */
974
+    public function userDeletedFromGroup($uid, $gid) {
975
+        // We don't handle groups here
976
+    }
977
+
978
+    /**
979
+     * check if users from other Nextcloud instances are allowed to mount public links share by this instance
980
+     *
981
+     * @return bool
982
+     */
983
+    public function isOutgoingServer2serverShareEnabled() {
984
+        if ($this->gsConfig->onlyInternalFederation()) {
985
+            return false;
986
+        }
987
+        $result = $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes');
988
+        return ($result === 'yes');
989
+    }
990
+
991
+    /**
992
+     * check if users are allowed to mount public links from other Nextclouds
993
+     *
994
+     * @return bool
995
+     */
996
+    public function isIncomingServer2serverShareEnabled() {
997
+        if ($this->gsConfig->onlyInternalFederation()) {
998
+            return false;
999
+        }
1000
+        $result = $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes');
1001
+        return ($result === 'yes');
1002
+    }
1003
+
1004
+
1005
+    /**
1006
+     * check if users from other Nextcloud instances are allowed to send federated group shares
1007
+     *
1008
+     * @return bool
1009
+     */
1010
+    public function isOutgoingServer2serverGroupShareEnabled() {
1011
+        if ($this->gsConfig->onlyInternalFederation()) {
1012
+            return false;
1013
+        }
1014
+        $result = $this->config->getAppValue('files_sharing', 'outgoing_server2server_group_share_enabled', 'no');
1015
+        return ($result === 'yes');
1016
+    }
1017
+
1018
+    /**
1019
+     * check if users are allowed to receive federated group shares
1020
+     *
1021
+     * @return bool
1022
+     */
1023
+    public function isIncomingServer2serverGroupShareEnabled() {
1024
+        if ($this->gsConfig->onlyInternalFederation()) {
1025
+            return false;
1026
+        }
1027
+        $result = $this->config->getAppValue('files_sharing', 'incoming_server2server_group_share_enabled', 'no');
1028
+        return ($result === 'yes');
1029
+    }
1030
+
1031
+    /**
1032
+     * check if federated group sharing is supported, therefore the OCM API need to be enabled
1033
+     *
1034
+     * @return bool
1035
+     */
1036
+    public function isFederatedGroupSharingSupported() {
1037
+        return $this->cloudFederationProviderManager->isReady();
1038
+    }
1039
+
1040
+    /**
1041
+     * Check if querying sharees on the lookup server is enabled
1042
+     *
1043
+     * @return bool
1044
+     */
1045
+    public function isLookupServerQueriesEnabled() {
1046
+        // in a global scale setup we should always query the lookup server
1047
+        if ($this->gsConfig->isGlobalScaleEnabled()) {
1048
+            return true;
1049
+        }
1050
+        $result = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'yes');
1051
+        return ($result === 'yes');
1052
+    }
1053
+
1054
+
1055
+    /**
1056
+     * Check if it is allowed to publish user specific data to the lookup server
1057
+     *
1058
+     * @return bool
1059
+     */
1060
+    public function isLookupServerUploadEnabled() {
1061
+        // in a global scale setup the admin is responsible to keep the lookup server up-to-date
1062
+        if ($this->gsConfig->isGlobalScaleEnabled()) {
1063
+            return false;
1064
+        }
1065
+        $result = $this->config->getAppValue('files_sharing', 'lookupServerUploadEnabled', 'yes');
1066
+        return ($result === 'yes');
1067
+    }
1068
+
1069
+    /**
1070
+     * @inheritdoc
1071
+     */
1072
+    public function getAccessList($nodes, $currentAccess) {
1073
+        $ids = [];
1074
+        foreach ($nodes as $node) {
1075
+            $ids[] = $node->getId();
1076
+        }
1077
+
1078
+        $qb = $this->dbConnection->getQueryBuilder();
1079
+        $qb->select('share_with', 'token', 'file_source')
1080
+            ->from('share')
1081
+            ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_REMOTE)))
1082
+            ->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)))
1083
+            ->andWhere($qb->expr()->orX(
1084
+                $qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
1085
+                $qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
1086
+            ));
1087
+        $cursor = $qb->execute();
1088
+
1089
+        if ($currentAccess === false) {
1090
+            $remote = $cursor->fetch() !== false;
1091
+            $cursor->closeCursor();
1092
+
1093
+            return ['remote' => $remote];
1094
+        }
1095
+
1096
+        $remote = [];
1097
+        while ($row = $cursor->fetch()) {
1098
+            $remote[$row['share_with']] = [
1099
+                'node_id' => $row['file_source'],
1100
+                'token' => $row['token'],
1101
+            ];
1102
+        }
1103
+        $cursor->closeCursor();
1104
+
1105
+        return ['remote' => $remote];
1106
+    }
1107
+
1108
+    public function getAllShares(): iterable {
1109
+        $qb = $this->dbConnection->getQueryBuilder();
1110
+
1111
+        $qb->select('*')
1112
+            ->from('share')
1113
+            ->where(
1114
+                $qb->expr()->orX(
1115
+                    $qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share\IShare::TYPE_REMOTE)),
1116
+                    $qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share\IShare::TYPE_REMOTE_GROUP))
1117
+                )
1118
+            );
1119
+
1120
+        $cursor = $qb->execute();
1121
+        while ($data = $cursor->fetch()) {
1122
+            try {
1123
+                $share = $this->createShareObject($data);
1124
+            } catch (InvalidShare $e) {
1125
+                continue;
1126
+            } catch (ShareNotFound $e) {
1127
+                continue;
1128
+            }
1129
+
1130
+            yield $share;
1131
+        }
1132
+        $cursor->closeCursor();
1133
+    }
1134 1134
 }
Please login to merge, or discard this patch.
Spacing   +14 added lines, -14 removed lines patch added patch discarded remove patch
@@ -218,7 +218,7 @@  discard block
 block discarded – undo
218 218
 		if ($remoteShare) {
219 219
 			try {
220 220
 				$ownerCloudId = $this->cloudIdManager->getCloudId($remoteShare['owner'], $remoteShare['remote']);
221
-				$shareId = $this->addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $ownerCloudId->getId(), $permissions, 'tmp_token_' . time(), $shareType);
221
+				$shareId = $this->addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $ownerCloudId->getId(), $permissions, 'tmp_token_'.time(), $shareType);
222 222
 				$share->setId($shareId);
223 223
 				list($token, $remoteId) = $this->askOwnerToReShare($shareWith, $share, $shareId);
224 224
 				// remote share was create successfully if we get a valid token as return
@@ -351,7 +351,7 @@  discard block
 block discarded – undo
351 351
 		$result = $qResult->fetchAll();
352 352
 		$qResult->closeCursor();
353 353
 
354
-		if (isset($result[0]) && (int)$result[0]['remote_id'] > 0) {
354
+		if (isset($result[0]) && (int) $result[0]['remote_id'] > 0) {
355 355
 			return $result[0];
356 356
 		}
357 357
 
@@ -394,7 +394,7 @@  discard block
 block discarded – undo
394 394
 		$qb->execute();
395 395
 		$id = $qb->getLastInsertId();
396 396
 
397
-		return (int)$id;
397
+		return (int) $id;
398 398
 	}
399 399
 
400 400
 	/**
@@ -484,7 +484,7 @@  discard block
 block discarded – undo
484 484
 	public function getRemoteId(IShare $share) {
485 485
 		$query = $this->dbConnection->getQueryBuilder();
486 486
 		$query->select('remote_id')->from('federated_reshares')
487
-			->where($query->expr()->eq('share_id', $query->createNamedParameter((int)$share->getId())));
487
+			->where($query->expr()->eq('share_id', $query->createNamedParameter((int) $share->getId())));
488 488
 		$result = $query->execute();
489 489
 		$data = $result->fetch();
490 490
 		$result->closeCursor();
@@ -493,7 +493,7 @@  discard block
 block discarded – undo
493 493
 			throw new ShareNotFound();
494 494
 		}
495 495
 
496
-		return (int)$data['remote_id'];
496
+		return (int) $data['remote_id'];
497 497
 	}
498 498
 
499 499
 	/**
@@ -651,7 +651,7 @@  discard block
 block discarded – undo
651 651
 			);
652 652
 		}
653 653
 
654
-		$qb->innerJoin('s', 'filecache' ,'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
654
+		$qb->innerJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
655 655
 		$qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())));
656 656
 
657 657
 		$qb->orderBy('id');
@@ -738,7 +738,7 @@  discard block
 block discarded – undo
738 738
 		$cursor->closeCursor();
739 739
 
740 740
 		if ($data === false) {
741
-			throw new ShareNotFound('Can not find share with ID: ' . $id);
741
+			throw new ShareNotFound('Can not find share with ID: '.$id);
742 742
 		}
743 743
 
744 744
 		try {
@@ -882,15 +882,15 @@  discard block
 block discarded – undo
882 882
 	 */
883 883
 	private function createShareObject($data) {
884 884
 		$share = new Share($this->rootFolder, $this->userManager);
885
-		$share->setId((int)$data['id'])
886
-			->setShareType((int)$data['share_type'])
887
-			->setPermissions((int)$data['permissions'])
885
+		$share->setId((int) $data['id'])
886
+			->setShareType((int) $data['share_type'])
887
+			->setPermissions((int) $data['permissions'])
888 888
 			->setTarget($data['file_target'])
889
-			->setMailSend((bool)$data['mail_send'])
889
+			->setMailSend((bool) $data['mail_send'])
890 890
 			->setToken($data['token']);
891 891
 
892 892
 		$shareTime = new \DateTime();
893
-		$shareTime->setTimestamp((int)$data['stime']);
893
+		$shareTime->setTimestamp((int) $data['stime']);
894 894
 		$share->setShareTime($shareTime);
895 895
 		$share->setSharedWith($data['share_with']);
896 896
 
@@ -900,13 +900,13 @@  discard block
 block discarded – undo
900 900
 		} else {
901 901
 			//OLD SHARE
902 902
 			$share->setSharedBy($data['uid_owner']);
903
-			$path = $this->getNode($share->getSharedBy(), (int)$data['file_source']);
903
+			$path = $this->getNode($share->getSharedBy(), (int) $data['file_source']);
904 904
 
905 905
 			$owner = $path->getOwner();
906 906
 			$share->setShareOwner($owner->getUID());
907 907
 		}
908 908
 
909
-		$share->setNodeId((int)$data['file_source']);
909
+		$share->setNodeId((int) $data['file_source']);
910 910
 		$share->setNodeType($data['item_type']);
911 911
 
912 912
 		$share->setProviderId($this->identifier());
Please login to merge, or discard this patch.
apps/files_trashbin/lib/Trashbin.php 2 patches
Indentation   +1063 added lines, -1063 removed lines patch added patch discarded remove patch
@@ -63,1067 +63,1067 @@
 block discarded – undo
63 63
 
64 64
 class Trashbin {
65 65
 
66
-	// unit: percentage; 50% of available disk space/quota
67
-	public const DEFAULTMAXSIZE = 50;
68
-
69
-	/**
70
-	 * Whether versions have already be rescanned during this PHP request
71
-	 *
72
-	 * @var bool
73
-	 */
74
-	private static $scannedVersions = false;
75
-
76
-	/**
77
-	 * Ensure we don't need to scan the file during the move to trash
78
-	 * by triggering the scan in the pre-hook
79
-	 *
80
-	 * @param array $params
81
-	 */
82
-	public static function ensureFileScannedHook($params) {
83
-		try {
84
-			self::getUidAndFilename($params['path']);
85
-		} catch (NotFoundException $e) {
86
-			// nothing to scan for non existing files
87
-		}
88
-	}
89
-
90
-	/**
91
-	 * get the UID of the owner of the file and the path to the file relative to
92
-	 * owners files folder
93
-	 *
94
-	 * @param string $filename
95
-	 * @return array
96
-	 * @throws \OC\User\NoUserException
97
-	 */
98
-	public static function getUidAndFilename($filename) {
99
-		$uid = Filesystem::getOwner($filename);
100
-		$userManager = \OC::$server->getUserManager();
101
-		// if the user with the UID doesn't exists, e.g. because the UID points
102
-		// to a remote user with a federated cloud ID we use the current logged-in
103
-		// user. We need a valid local user to move the file to the right trash bin
104
-		if (!$userManager->userExists($uid)) {
105
-			$uid = User::getUser();
106
-		}
107
-		if (!$uid) {
108
-			// no owner, usually because of share link from ext storage
109
-			return [null, null];
110
-		}
111
-		Filesystem::initMountPoints($uid);
112
-		if ($uid !== User::getUser()) {
113
-			$info = Filesystem::getFileInfo($filename);
114
-			$ownerView = new View('/' . $uid . '/files');
115
-			try {
116
-				$filename = $ownerView->getPath($info['fileid']);
117
-			} catch (NotFoundException $e) {
118
-				$filename = null;
119
-			}
120
-		}
121
-		return [$uid, $filename];
122
-	}
123
-
124
-	/**
125
-	 * get original location of files for user
126
-	 *
127
-	 * @param string $user
128
-	 * @return array (filename => array (timestamp => original location))
129
-	 */
130
-	public static function getLocations($user) {
131
-		$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
132
-		$query->select('id', 'timestamp', 'location')
133
-			->from('files_trash')
134
-			->where($query->expr()->eq('user', $query->createNamedParameter($user)));
135
-		$result = $query->execute();
136
-		$array = [];
137
-		while ($row = $result->fetch()) {
138
-			if (isset($array[$row['id']])) {
139
-				$array[$row['id']][$row['timestamp']] = $row['location'];
140
-			} else {
141
-				$array[$row['id']] = [$row['timestamp'] => $row['location']];
142
-			}
143
-		}
144
-		$result->closeCursor();
145
-		return $array;
146
-	}
147
-
148
-	/**
149
-	 * get original location of file
150
-	 *
151
-	 * @param string $user
152
-	 * @param string $filename
153
-	 * @param string $timestamp
154
-	 * @return string original location
155
-	 */
156
-	public static function getLocation($user, $filename, $timestamp) {
157
-		$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
158
-		$query->select('location')
159
-			->from('files_trash')
160
-			->where($query->expr()->eq('user', $query->createNamedParameter($user)))
161
-			->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
162
-			->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
163
-
164
-		$result = $query->execute();
165
-		$row = $result->fetch();
166
-		$result->closeCursor();
167
-
168
-		if (isset($row['location'])) {
169
-			return $row['location'];
170
-		} else {
171
-			return false;
172
-		}
173
-	}
174
-
175
-	private static function setUpTrash($user) {
176
-		$view = new View('/' . $user);
177
-		if (!$view->is_dir('files_trashbin')) {
178
-			$view->mkdir('files_trashbin');
179
-		}
180
-		if (!$view->is_dir('files_trashbin/files')) {
181
-			$view->mkdir('files_trashbin/files');
182
-		}
183
-		if (!$view->is_dir('files_trashbin/versions')) {
184
-			$view->mkdir('files_trashbin/versions');
185
-		}
186
-		if (!$view->is_dir('files_trashbin/keys')) {
187
-			$view->mkdir('files_trashbin/keys');
188
-		}
189
-	}
190
-
191
-
192
-	/**
193
-	 * copy file to owners trash
194
-	 *
195
-	 * @param string $sourcePath
196
-	 * @param string $owner
197
-	 * @param string $targetPath
198
-	 * @param $user
199
-	 * @param integer $timestamp
200
-	 */
201
-	private static function copyFilesToUser($sourcePath, $owner, $targetPath, $user, $timestamp) {
202
-		self::setUpTrash($owner);
203
-
204
-		$targetFilename = basename($targetPath);
205
-		$targetLocation = dirname($targetPath);
206
-
207
-		$sourceFilename = basename($sourcePath);
208
-
209
-		$view = new View('/');
210
-
211
-		$target = $user . '/files_trashbin/files/' . $targetFilename . '.d' . $timestamp;
212
-		$source = $owner . '/files_trashbin/files/' . $sourceFilename . '.d' . $timestamp;
213
-		$free = $view->free_space($target);
214
-		$isUnknownOrUnlimitedFreeSpace = $free < 0;
215
-		$isEnoughFreeSpaceLeft = $view->filesize($source) < $free;
216
-		if ($isUnknownOrUnlimitedFreeSpace || $isEnoughFreeSpaceLeft) {
217
-			self::copy_recursive($source, $target, $view);
218
-		}
219
-
220
-
221
-		if ($view->file_exists($target)) {
222
-			$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
223
-			$query->insert('files_trash')
224
-				->setValue('id', $query->createNamedParameter($targetFilename))
225
-				->setValue('timestamp', $query->createNamedParameter($timestamp))
226
-				->setValue('location', $query->createNamedParameter($targetLocation))
227
-				->setValue('user', $query->createNamedParameter($user));
228
-			$result = $query->execute();
229
-			if (!$result) {
230
-				\OC::$server->getLogger()->error('trash bin database couldn\'t be updated for the files owner', ['app' => 'files_trashbin']);
231
-			}
232
-		}
233
-	}
234
-
235
-
236
-	/**
237
-	 * move file to the trash bin
238
-	 *
239
-	 * @param string $file_path path to the deleted file/directory relative to the files root directory
240
-	 * @param bool $ownerOnly delete for owner only (if file gets moved out of a shared folder)
241
-	 *
242
-	 * @return bool
243
-	 */
244
-	public static function move2trash($file_path, $ownerOnly = false) {
245
-		// get the user for which the filesystem is setup
246
-		$root = Filesystem::getRoot();
247
-		[, $user] = explode('/', $root);
248
-		[$owner, $ownerPath] = self::getUidAndFilename($file_path);
249
-
250
-		// if no owner found (ex: ext storage + share link), will use the current user's trashbin then
251
-		if (is_null($owner)) {
252
-			$owner = $user;
253
-			$ownerPath = $file_path;
254
-		}
255
-
256
-		$ownerView = new View('/' . $owner);
257
-		// file has been deleted in between
258
-		if (is_null($ownerPath) || $ownerPath === '' || !$ownerView->file_exists('/files/' . $ownerPath)) {
259
-			return true;
260
-		}
261
-
262
-		self::setUpTrash($user);
263
-		if ($owner !== $user) {
264
-			// also setup for owner
265
-			self::setUpTrash($owner);
266
-		}
267
-
268
-		$path_parts = pathinfo($ownerPath);
269
-
270
-		$filename = $path_parts['basename'];
271
-		$location = $path_parts['dirname'];
272
-		/** @var ITimeFactory $timeFactory */
273
-		$timeFactory = \OC::$server->query(ITimeFactory::class);
274
-		$timestamp = $timeFactory->getTime();
275
-
276
-		$lockingProvider = \OC::$server->getLockingProvider();
277
-
278
-		// disable proxy to prevent recursive calls
279
-		$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
280
-		$gotLock = false;
281
-
282
-		while (!$gotLock) {
283
-			try {
284
-				/** @var \OC\Files\Storage\Storage $trashStorage */
285
-				[$trashStorage, $trashInternalPath] = $ownerView->resolvePath($trashPath);
286
-
287
-				$trashStorage->acquireLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
288
-				$gotLock = true;
289
-			} catch (LockedException $e) {
290
-				// a file with the same name is being deleted concurrently
291
-				// nudge the timestamp a bit to resolve the conflict
292
-
293
-				$timestamp = $timestamp + 1;
294
-
295
-				$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
296
-			}
297
-		}
298
-
299
-		/** @var \OC\Files\Storage\Storage $sourceStorage */
300
-		[$sourceStorage, $sourceInternalPath] = $ownerView->resolvePath('/files/' . $ownerPath);
301
-
302
-
303
-		if ($trashStorage->file_exists($trashInternalPath)) {
304
-			$trashStorage->unlink($trashInternalPath);
305
-		}
306
-
307
-		$config = \OC::$server->getConfig();
308
-		$systemTrashbinSize = (int)$config->getAppValue('files_trashbin', 'trashbin_size', '-1');
309
-		$userTrashbinSize = (int)$config->getUserValue($owner, 'files_trashbin', 'trashbin_size', '-1');
310
-		$configuredTrashbinSize = ($userTrashbinSize < 0) ? $systemTrashbinSize : $userTrashbinSize;
311
-		if ($configuredTrashbinSize >= 0 && $sourceStorage->filesize($sourceInternalPath) >= $configuredTrashbinSize) {
312
-			return false;
313
-		}
314
-
315
-		$trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
316
-
317
-		try {
318
-			$moveSuccessful = true;
319
-
320
-			// when moving within the same object store, the cache update done above is enough to move the file
321
-			if (!($trashStorage->instanceOfStorage(ObjectStoreStorage::class) && $trashStorage->getId() === $sourceStorage->getId())) {
322
-				$trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
323
-			}
324
-		} catch (\OCA\Files_Trashbin\Exceptions\CopyRecursiveException $e) {
325
-			$moveSuccessful = false;
326
-			if ($trashStorage->file_exists($trashInternalPath)) {
327
-				$trashStorage->unlink($trashInternalPath);
328
-			}
329
-			\OC::$server->getLogger()->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']);
330
-		}
331
-
332
-		if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
333
-			if ($sourceStorage->is_dir($sourceInternalPath)) {
334
-				$sourceStorage->rmdir($sourceInternalPath);
335
-			} else {
336
-				$sourceStorage->unlink($sourceInternalPath);
337
-			}
338
-
339
-			if ($sourceStorage->file_exists($sourceInternalPath)) {
340
-				// undo the cache move
341
-				$sourceStorage->getUpdater()->renameFromStorage($trashStorage, $trashInternalPath, $sourceInternalPath);
342
-			} else {
343
-				$trashStorage->getUpdater()->remove($trashInternalPath);
344
-			}
345
-			return false;
346
-		}
347
-
348
-		if ($moveSuccessful) {
349
-			$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
350
-			$query->insert('files_trash')
351
-				->setValue('id', $query->createNamedParameter($filename))
352
-				->setValue('timestamp', $query->createNamedParameter($timestamp))
353
-				->setValue('location', $query->createNamedParameter($location))
354
-				->setValue('user', $query->createNamedParameter($owner));
355
-			$result = $query->execute();
356
-			if (!$result) {
357
-				\OC::$server->getLogger()->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']);
358
-			}
359
-			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path),
360
-				'trashPath' => Filesystem::normalizePath($filename . '.d' . $timestamp)]);
361
-
362
-			self::retainVersions($filename, $owner, $ownerPath, $timestamp);
363
-
364
-			// if owner !== user we need to also add a copy to the users trash
365
-			if ($user !== $owner && $ownerOnly === false) {
366
-				self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp);
367
-			}
368
-		}
369
-
370
-		$trashStorage->releaseLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
371
-
372
-		self::scheduleExpire($user);
373
-
374
-		// if owner !== user we also need to update the owners trash size
375
-		if ($owner !== $user) {
376
-			self::scheduleExpire($owner);
377
-		}
378
-
379
-		return $moveSuccessful;
380
-	}
381
-
382
-	/**
383
-	 * Move file versions to trash so that they can be restored later
384
-	 *
385
-	 * @param string $filename of deleted file
386
-	 * @param string $owner owner user id
387
-	 * @param string $ownerPath path relative to the owner's home storage
388
-	 * @param integer $timestamp when the file was deleted
389
-	 */
390
-	private static function retainVersions($filename, $owner, $ownerPath, $timestamp) {
391
-		if (\OCP\App::isEnabled('files_versions') && !empty($ownerPath)) {
392
-			$user = User::getUser();
393
-			$rootView = new View('/');
394
-
395
-			if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
396
-				if ($owner !== $user) {
397
-					self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . basename($ownerPath) . '.d' . $timestamp, $rootView);
398
-				}
399
-				self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp);
400
-			} elseif ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) {
401
-				foreach ($versions as $v) {
402
-					if ($owner !== $user) {
403
-						self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . $v['name'] . '.v' . $v['version'] . '.d' . $timestamp);
404
-					}
405
-					self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp);
406
-				}
407
-			}
408
-		}
409
-	}
410
-
411
-	/**
412
-	 * Move a file or folder on storage level
413
-	 *
414
-	 * @param View $view
415
-	 * @param string $source
416
-	 * @param string $target
417
-	 * @return bool
418
-	 */
419
-	private static function move(View $view, $source, $target) {
420
-		/** @var \OC\Files\Storage\Storage $sourceStorage */
421
-		[$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
422
-		/** @var \OC\Files\Storage\Storage $targetStorage */
423
-		[$targetStorage, $targetInternalPath] = $view->resolvePath($target);
424
-		/** @var \OC\Files\Storage\Storage $ownerTrashStorage */
425
-
426
-		$result = $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
427
-		if ($result) {
428
-			$targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
429
-		}
430
-		return $result;
431
-	}
432
-
433
-	/**
434
-	 * Copy a file or folder on storage level
435
-	 *
436
-	 * @param View $view
437
-	 * @param string $source
438
-	 * @param string $target
439
-	 * @return bool
440
-	 */
441
-	private static function copy(View $view, $source, $target) {
442
-		/** @var \OC\Files\Storage\Storage $sourceStorage */
443
-		[$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
444
-		/** @var \OC\Files\Storage\Storage $targetStorage */
445
-		[$targetStorage, $targetInternalPath] = $view->resolvePath($target);
446
-		/** @var \OC\Files\Storage\Storage $ownerTrashStorage */
447
-
448
-		$result = $targetStorage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
449
-		if ($result) {
450
-			$targetStorage->getUpdater()->update($targetInternalPath);
451
-		}
452
-		return $result;
453
-	}
454
-
455
-	/**
456
-	 * Restore a file or folder from trash bin
457
-	 *
458
-	 * @param string $file path to the deleted file/folder relative to "files_trashbin/files/",
459
-	 * including the timestamp suffix ".d12345678"
460
-	 * @param string $filename name of the file/folder
461
-	 * @param int $timestamp time when the file/folder was deleted
462
-	 *
463
-	 * @return bool true on success, false otherwise
464
-	 */
465
-	public static function restore($file, $filename, $timestamp) {
466
-		$user = User::getUser();
467
-		$view = new View('/' . $user);
468
-
469
-		$location = '';
470
-		if ($timestamp) {
471
-			$location = self::getLocation($user, $filename, $timestamp);
472
-			if ($location === false) {
473
-				\OC::$server->getLogger()->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']);
474
-			} else {
475
-				// if location no longer exists, restore file in the root directory
476
-				if ($location !== '/' &&
477
-					(!$view->is_dir('files/' . $location) ||
478
-						!$view->isCreatable('files/' . $location))
479
-				) {
480
-					$location = '';
481
-				}
482
-			}
483
-		}
484
-
485
-		// we need a  extension in case a file/dir with the same name already exists
486
-		$uniqueFilename = self::getUniqueFilename($location, $filename, $view);
487
-
488
-		$source = Filesystem::normalizePath('files_trashbin/files/' . $file);
489
-		$target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
490
-		if (!$view->file_exists($source)) {
491
-			return false;
492
-		}
493
-		$mtime = $view->filemtime($source);
494
-
495
-		// restore file
496
-		if (!$view->isCreatable(dirname($target))) {
497
-			throw new NotPermittedException("Can't restore trash item because the target folder is not writable");
498
-		}
499
-		$restoreResult = $view->rename($source, $target);
500
-
501
-		// handle the restore result
502
-		if ($restoreResult) {
503
-			$fakeRoot = $view->getRoot();
504
-			$view->chroot('/' . $user . '/files');
505
-			$view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
506
-			$view->chroot($fakeRoot);
507
-			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename),
508
-				'trashPath' => Filesystem::normalizePath($file)]);
509
-
510
-			self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
511
-
512
-			if ($timestamp) {
513
-				$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
514
-				$query->delete('files_trash')
515
-					->where($query->expr()->eq('user', $query->createNamedParameter($user)))
516
-					->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
517
-					->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
518
-				$query->execute();
519
-			}
520
-
521
-			return true;
522
-		}
523
-
524
-		return false;
525
-	}
526
-
527
-	/**
528
-	 * restore versions from trash bin
529
-	 *
530
-	 * @param View $view file view
531
-	 * @param string $file complete path to file
532
-	 * @param string $filename name of file once it was deleted
533
-	 * @param string $uniqueFilename new file name to restore the file without overwriting existing files
534
-	 * @param string $location location if file
535
-	 * @param int $timestamp deletion time
536
-	 * @return false|null
537
-	 */
538
-	private static function restoreVersions(View $view, $file, $filename, $uniqueFilename, $location, $timestamp) {
539
-		if (\OCP\App::isEnabled('files_versions')) {
540
-			$user = User::getUser();
541
-			$rootView = new View('/');
542
-
543
-			$target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
544
-
545
-			[$owner, $ownerPath] = self::getUidAndFilename($target);
546
-
547
-			// file has been deleted in between
548
-			if (empty($ownerPath)) {
549
-				return false;
550
-			}
551
-
552
-			if ($timestamp) {
553
-				$versionedFile = $filename;
554
-			} else {
555
-				$versionedFile = $file;
556
-			}
557
-
558
-			if ($view->is_dir('/files_trashbin/versions/' . $file)) {
559
-				$rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
560
-			} elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
561
-				foreach ($versions as $v) {
562
-					if ($timestamp) {
563
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
564
-					} else {
565
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
566
-					}
567
-				}
568
-			}
569
-		}
570
-	}
571
-
572
-	/**
573
-	 * delete all files from the trash
574
-	 */
575
-	public static function deleteAll() {
576
-		$user = User::getUser();
577
-		$userRoot = \OC::$server->getUserFolder($user)->getParent();
578
-		$view = new View('/' . $user);
579
-		$fileInfos = $view->getDirectoryContent('files_trashbin/files');
580
-
581
-		try {
582
-			$trash = $userRoot->get('files_trashbin');
583
-		} catch (NotFoundException $e) {
584
-			return false;
585
-		}
586
-
587
-		// Array to store the relative path in (after the file is deleted, the view won't be able to relativise the path anymore)
588
-		$filePaths = [];
589
-		foreach ($fileInfos as $fileInfo) {
590
-			$filePaths[] = $view->getRelativePath($fileInfo->getPath());
591
-		}
592
-		unset($fileInfos); // save memory
593
-
594
-		// Bulk PreDelete-Hook
595
-		\OC_Hook::emit('\OCP\Trashbin', 'preDeleteAll', ['paths' => $filePaths]);
596
-
597
-		// Single-File Hooks
598
-		foreach ($filePaths as $path) {
599
-			self::emitTrashbinPreDelete($path);
600
-		}
601
-
602
-		// actual file deletion
603
-		$trash->delete();
604
-
605
-		$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
606
-		$query->delete('files_trash')
607
-			->where($query->expr()->eq('user', $query->createNamedParameter($user)));
608
-		$query->execute();
609
-
610
-		// Bulk PostDelete-Hook
611
-		\OC_Hook::emit('\OCP\Trashbin', 'deleteAll', ['paths' => $filePaths]);
612
-
613
-		// Single-File Hooks
614
-		foreach ($filePaths as $path) {
615
-			self::emitTrashbinPostDelete($path);
616
-		}
617
-
618
-		$trash = $userRoot->newFolder('files_trashbin');
619
-		$trash->newFolder('files');
620
-
621
-		return true;
622
-	}
623
-
624
-	/**
625
-	 * wrapper function to emit the 'preDelete' hook of \OCP\Trashbin before a file is deleted
626
-	 *
627
-	 * @param string $path
628
-	 */
629
-	protected static function emitTrashbinPreDelete($path) {
630
-		\OC_Hook::emit('\OCP\Trashbin', 'preDelete', ['path' => $path]);
631
-	}
632
-
633
-	/**
634
-	 * wrapper function to emit the 'delete' hook of \OCP\Trashbin after a file has been deleted
635
-	 *
636
-	 * @param string $path
637
-	 */
638
-	protected static function emitTrashbinPostDelete($path) {
639
-		\OC_Hook::emit('\OCP\Trashbin', 'delete', ['path' => $path]);
640
-	}
641
-
642
-	/**
643
-	 * delete file from trash bin permanently
644
-	 *
645
-	 * @param string $filename path to the file
646
-	 * @param string $user
647
-	 * @param int $timestamp of deletion time
648
-	 *
649
-	 * @return int size of deleted files
650
-	 */
651
-	public static function delete($filename, $user, $timestamp = null) {
652
-		$userRoot = \OC::$server->getUserFolder($user)->getParent();
653
-		$view = new View('/' . $user);
654
-		$size = 0;
655
-
656
-		if ($timestamp) {
657
-			$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
658
-			$query->delete('files_trash')
659
-				->where($query->expr()->eq('user', $query->createNamedParameter($user)))
660
-				->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
661
-				->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
662
-			$query->execute();
663
-
664
-			$file = $filename . '.d' . $timestamp;
665
-		} else {
666
-			$file = $filename;
667
-		}
668
-
669
-		$size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
670
-
671
-		try {
672
-			$node = $userRoot->get('/files_trashbin/files/' . $file);
673
-		} catch (NotFoundException $e) {
674
-			return $size;
675
-		}
676
-
677
-		if ($node instanceof Folder) {
678
-			$size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
679
-		} elseif ($node instanceof File) {
680
-			$size += $view->filesize('/files_trashbin/files/' . $file);
681
-		}
682
-
683
-		self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
684
-		$node->delete();
685
-		self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
686
-
687
-		return $size;
688
-	}
689
-
690
-	/**
691
-	 * @param View $view
692
-	 * @param string $file
693
-	 * @param string $filename
694
-	 * @param integer|null $timestamp
695
-	 * @param string $user
696
-	 * @return int
697
-	 */
698
-	private static function deleteVersions(View $view, $file, $filename, $timestamp, $user) {
699
-		$size = 0;
700
-		if (\OCP\App::isEnabled('files_versions')) {
701
-			if ($view->is_dir('files_trashbin/versions/' . $file)) {
702
-				$size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
703
-				$view->unlink('files_trashbin/versions/' . $file);
704
-			} elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
705
-				foreach ($versions as $v) {
706
-					if ($timestamp) {
707
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
708
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
709
-					} else {
710
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
711
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
712
-					}
713
-				}
714
-			}
715
-		}
716
-		return $size;
717
-	}
718
-
719
-	/**
720
-	 * check to see whether a file exists in trashbin
721
-	 *
722
-	 * @param string $filename path to the file
723
-	 * @param int $timestamp of deletion time
724
-	 * @return bool true if file exists, otherwise false
725
-	 */
726
-	public static function file_exists($filename, $timestamp = null) {
727
-		$user = User::getUser();
728
-		$view = new View('/' . $user);
729
-
730
-		if ($timestamp) {
731
-			$filename = $filename . '.d' . $timestamp;
732
-		}
733
-
734
-		$target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
735
-		return $view->file_exists($target);
736
-	}
737
-
738
-	/**
739
-	 * deletes used space for trash bin in db if user was deleted
740
-	 *
741
-	 * @param string $uid id of deleted user
742
-	 * @return bool result of db delete operation
743
-	 */
744
-	public static function deleteUser($uid) {
745
-		$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
746
-		$query->delete('files_trash')
747
-			->where($query->expr()->eq('user', $query->createNamedParameter($uid)));
748
-		return (bool) $query->execute();
749
-	}
750
-
751
-	/**
752
-	 * calculate remaining free space for trash bin
753
-	 *
754
-	 * @param integer $trashbinSize current size of the trash bin
755
-	 * @param string $user
756
-	 * @return int available free space for trash bin
757
-	 */
758
-	private static function calculateFreeSpace($trashbinSize, $user) {
759
-		$config = \OC::$server->getConfig();
760
-		$userTrashbinSize = (int)$config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
761
-		if ($userTrashbinSize > -1) {
762
-			return $userTrashbinSize - $trashbinSize;
763
-		}
764
-		$systemTrashbinSize = (int)$config->getAppValue('files_trashbin', 'trashbin_size', '-1');
765
-		if ($systemTrashbinSize > -1) {
766
-			return $systemTrashbinSize - $trashbinSize;
767
-		}
768
-
769
-		$softQuota = true;
770
-		$userObject = \OC::$server->getUserManager()->get($user);
771
-		if (is_null($userObject)) {
772
-			return 0;
773
-		}
774
-		$quota = $userObject->getQuota();
775
-		if ($quota === null || $quota === 'none') {
776
-			$quota = Filesystem::free_space('/');
777
-			$softQuota = false;
778
-			// inf or unknown free space
779
-			if ($quota < 0) {
780
-				$quota = PHP_INT_MAX;
781
-			}
782
-		} else {
783
-			$quota = \OCP\Util::computerFileSize($quota);
784
-		}
785
-
786
-		// calculate available space for trash bin
787
-		// subtract size of files and current trash bin size from quota
788
-		if ($softQuota) {
789
-			$userFolder = \OC::$server->getUserFolder($user);
790
-			if (is_null($userFolder)) {
791
-				return 0;
792
-			}
793
-			$free = $quota - $userFolder->getSize(false); // remaining free space for user
794
-			if ($free > 0) {
795
-				$availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $trashbinSize; // how much space can be used for versions
796
-			} else {
797
-				$availableSpace = $free - $trashbinSize;
798
-			}
799
-		} else {
800
-			$availableSpace = $quota;
801
-		}
802
-
803
-		return $availableSpace;
804
-	}
805
-
806
-	/**
807
-	 * resize trash bin if necessary after a new file was added to Nextcloud
808
-	 *
809
-	 * @param string $user user id
810
-	 */
811
-	public static function resizeTrash($user) {
812
-		$size = self::getTrashbinSize($user);
813
-
814
-		$freeSpace = self::calculateFreeSpace($size, $user);
815
-
816
-		if ($freeSpace < 0) {
817
-			self::scheduleExpire($user);
818
-		}
819
-	}
820
-
821
-	/**
822
-	 * clean up the trash bin
823
-	 *
824
-	 * @param string $user
825
-	 */
826
-	public static function expire($user) {
827
-		$trashBinSize = self::getTrashbinSize($user);
828
-		$availableSpace = self::calculateFreeSpace($trashBinSize, $user);
829
-
830
-		$dirContent = Helper::getTrashFiles('/', $user, 'mtime');
831
-
832
-		// delete all files older then $retention_obligation
833
-		[$delSize, $count] = self::deleteExpiredFiles($dirContent, $user);
834
-
835
-		$availableSpace += $delSize;
836
-
837
-		// delete files from trash until we meet the trash bin size limit again
838
-		self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace);
839
-	}
840
-
841
-	/**
842
-	 * @param string $user
843
-	 */
844
-	private static function scheduleExpire($user) {
845
-		// let the admin disable auto expire
846
-		/** @var Application $application */
847
-		$application = \OC::$server->query(Application::class);
848
-		$expiration = $application->getContainer()->query('Expiration');
849
-		if ($expiration->isEnabled()) {
850
-			\OC::$server->getCommandBus()->push(new Expire($user));
851
-		}
852
-	}
853
-
854
-	/**
855
-	 * if the size limit for the trash bin is reached, we delete the oldest
856
-	 * files in the trash bin until we meet the limit again
857
-	 *
858
-	 * @param array $files
859
-	 * @param string $user
860
-	 * @param int $availableSpace available disc space
861
-	 * @return int size of deleted files
862
-	 */
863
-	protected static function deleteFiles($files, $user, $availableSpace) {
864
-		/** @var Application $application */
865
-		$application = \OC::$server->query(Application::class);
866
-		$expiration = $application->getContainer()->query('Expiration');
867
-		$size = 0;
868
-
869
-		if ($availableSpace < 0) {
870
-			foreach ($files as $file) {
871
-				if ($availableSpace < 0 && $expiration->isExpired($file['mtime'], true)) {
872
-					$tmp = self::delete($file['name'], $user, $file['mtime']);
873
-					\OC::$server->getLogger()->info('remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota)', ['app' => 'files_trashbin']);
874
-					$availableSpace += $tmp;
875
-					$size += $tmp;
876
-				} else {
877
-					break;
878
-				}
879
-			}
880
-		}
881
-		return $size;
882
-	}
883
-
884
-	/**
885
-	 * delete files older then max storage time
886
-	 *
887
-	 * @param array $files list of files sorted by mtime
888
-	 * @param string $user
889
-	 * @return integer[] size of deleted files and number of deleted files
890
-	 */
891
-	public static function deleteExpiredFiles($files, $user) {
892
-		/** @var Expiration $expiration */
893
-		$expiration = \OC::$server->query(Expiration::class);
894
-		$size = 0;
895
-		$count = 0;
896
-		foreach ($files as $file) {
897
-			$timestamp = $file['mtime'];
898
-			$filename = $file['name'];
899
-			if ($expiration->isExpired($timestamp)) {
900
-				try {
901
-					$size += self::delete($filename, $user, $timestamp);
902
-					$count++;
903
-				} catch (\OCP\Files\NotPermittedException $e) {
904
-					\OC::$server->getLogger()->logException($e, ['app' => 'files_trashbin', 'level' => \OCP\ILogger::WARN, 'message' => 'Removing "' . $filename . '" from trashbin failed.']);
905
-				}
906
-				\OC::$server->getLogger()->info(
907
-					'Remove "' . $filename . '" from trashbin because it exceeds max retention obligation term.',
908
-					['app' => 'files_trashbin']
909
-				);
910
-			} else {
911
-				break;
912
-			}
913
-		}
914
-
915
-		return [$size, $count];
916
-	}
917
-
918
-	/**
919
-	 * recursive copy to copy a whole directory
920
-	 *
921
-	 * @param string $source source path, relative to the users files directory
922
-	 * @param string $destination destination path relative to the users root directoy
923
-	 * @param View $view file view for the users root directory
924
-	 * @return int
925
-	 * @throws Exceptions\CopyRecursiveException
926
-	 */
927
-	private static function copy_recursive($source, $destination, View $view) {
928
-		$size = 0;
929
-		if ($view->is_dir($source)) {
930
-			$view->mkdir($destination);
931
-			$view->touch($destination, $view->filemtime($source));
932
-			foreach ($view->getDirectoryContent($source) as $i) {
933
-				$pathDir = $source . '/' . $i['name'];
934
-				if ($view->is_dir($pathDir)) {
935
-					$size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
936
-				} else {
937
-					$size += $view->filesize($pathDir);
938
-					$result = $view->copy($pathDir, $destination . '/' . $i['name']);
939
-					if (!$result) {
940
-						throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
941
-					}
942
-					$view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
943
-				}
944
-			}
945
-		} else {
946
-			$size += $view->filesize($source);
947
-			$result = $view->copy($source, $destination);
948
-			if (!$result) {
949
-				throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
950
-			}
951
-			$view->touch($destination, $view->filemtime($source));
952
-		}
953
-		return $size;
954
-	}
955
-
956
-	/**
957
-	 * find all versions which belong to the file we want to restore
958
-	 *
959
-	 * @param string $filename name of the file which should be restored
960
-	 * @param int $timestamp timestamp when the file was deleted
961
-	 * @return array
962
-	 */
963
-	private static function getVersionsFromTrash($filename, $timestamp, $user) {
964
-		$view = new View('/' . $user . '/files_trashbin/versions');
965
-		$versions = [];
966
-
967
-		/** @var \OC\Files\Storage\Storage $storage */
968
-		[$storage,] = $view->resolvePath('/');
969
-
970
-		//force rescan of versions, local storage may not have updated the cache
971
-		if (!self::$scannedVersions) {
972
-			$storage->getScanner()->scan('files_trashbin/versions');
973
-			self::$scannedVersions = true;
974
-		}
975
-
976
-		$pattern = \OC::$server->getDatabaseConnection()->escapeLikeParameter(basename($filename));
977
-		if ($timestamp) {
978
-			// fetch for old versions
979
-			$escapedTimestamp = \OC::$server->getDatabaseConnection()->escapeLikeParameter($timestamp);
980
-			$pattern .= '.v%.d' . $escapedTimestamp;
981
-			$offset = -strlen($escapedTimestamp) - 2;
982
-		} else {
983
-			$pattern .= '.v%';
984
-		}
985
-
986
-		// Manually fetch all versions from the file cache to be able to filter them by their parent
987
-		$cache = $storage->getCache('');
988
-		$query = new CacheQueryBuilder(
989
-			\OC::$server->getDatabaseConnection(),
990
-			\OC::$server->getSystemConfig(),
991
-			\OC::$server->getLogger(),
992
-			$cache
993
-		);
994
-		$normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/'. $filename)), '/');
995
-		$parentId = $cache->getId($normalizedParentPath);
996
-		if ($parentId === -1) {
997
-			return [];
998
-		}
999
-
1000
-		$query->selectFileCache()
1001
-			->whereStorageId()
1002
-			->andWhere($query->expr()->eq('parent', $query->createNamedParameter($parentId)))
1003
-			->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern)));
1004
-
1005
-		$result = $query->execute();
1006
-		$entries = $result->fetchAll();
1007
-		$result->closeCursor();
1008
-
1009
-		/** @var CacheEntry[] $matches */
1010
-		$matches = array_map(function (array $data) {
1011
-			return Cache::cacheEntryFromData($data, \OC::$server->getMimeTypeLoader());
1012
-		}, $entries);
1013
-
1014
-		foreach ($matches as $ma) {
1015
-			if ($timestamp) {
1016
-				$parts = explode('.v', substr($ma['path'], 0, $offset));
1017
-				$versions[] = end($parts);
1018
-			} else {
1019
-				$parts = explode('.v', $ma['path']);
1020
-				$versions[] = end($parts);
1021
-			}
1022
-		}
1023
-
1024
-		return $versions;
1025
-	}
1026
-
1027
-	/**
1028
-	 * find unique extension for restored file if a file with the same name already exists
1029
-	 *
1030
-	 * @param string $location where the file should be restored
1031
-	 * @param string $filename name of the file
1032
-	 * @param View $view filesystem view relative to users root directory
1033
-	 * @return string with unique extension
1034
-	 */
1035
-	private static function getUniqueFilename($location, $filename, View $view) {
1036
-		$ext = pathinfo($filename, PATHINFO_EXTENSION);
1037
-		$name = pathinfo($filename, PATHINFO_FILENAME);
1038
-		$l = \OC::$server->getL10N('files_trashbin');
1039
-
1040
-		$location = '/' . trim($location, '/');
1041
-
1042
-		// if extension is not empty we set a dot in front of it
1043
-		if ($ext !== '') {
1044
-			$ext = '.' . $ext;
1045
-		}
1046
-
1047
-		if ($view->file_exists('files' . $location . '/' . $filename)) {
1048
-			$i = 2;
1049
-			$uniqueName = $name . " (" . $l->t("restored") . ")" . $ext;
1050
-			while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
1051
-				$uniqueName = $name . " (" . $l->t("restored") . " " . $i . ")" . $ext;
1052
-				$i++;
1053
-			}
1054
-
1055
-			return $uniqueName;
1056
-		}
1057
-
1058
-		return $filename;
1059
-	}
1060
-
1061
-	/**
1062
-	 * get the size from a given root folder
1063
-	 *
1064
-	 * @param View $view file view on the root folder
1065
-	 * @return integer size of the folder
1066
-	 */
1067
-	private static function calculateSize($view) {
1068
-		$root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
1069
-		if (!file_exists($root)) {
1070
-			return 0;
1071
-		}
1072
-		$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), \RecursiveIteratorIterator::CHILD_FIRST);
1073
-		$size = 0;
1074
-
1075
-		/**
1076
-		 * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
1077
-		 * This bug is fixed in PHP 5.5.9 or before
1078
-		 * See #8376
1079
-		 */
1080
-		$iterator->rewind();
1081
-		while ($iterator->valid()) {
1082
-			$path = $iterator->current();
1083
-			$relpath = substr($path, strlen($root) - 1);
1084
-			if (!$view->is_dir($relpath)) {
1085
-				$size += $view->filesize($relpath);
1086
-			}
1087
-			$iterator->next();
1088
-		}
1089
-		return $size;
1090
-	}
1091
-
1092
-	/**
1093
-	 * get current size of trash bin from a given user
1094
-	 *
1095
-	 * @param string $user user who owns the trash bin
1096
-	 * @return integer trash bin size
1097
-	 */
1098
-	private static function getTrashbinSize($user) {
1099
-		$view = new View('/' . $user);
1100
-		$fileInfo = $view->getFileInfo('/files_trashbin');
1101
-		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
1102
-	}
1103
-
1104
-	/**
1105
-	 * check if trash bin is empty for a given user
1106
-	 *
1107
-	 * @param string $user
1108
-	 * @return bool
1109
-	 */
1110
-	public static function isEmpty($user) {
1111
-		$view = new View('/' . $user . '/files_trashbin');
1112
-		if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
1113
-			while ($file = readdir($dh)) {
1114
-				if (!Filesystem::isIgnoredDir($file)) {
1115
-					return false;
1116
-				}
1117
-			}
1118
-		}
1119
-		return true;
1120
-	}
1121
-
1122
-	/**
1123
-	 * @param $path
1124
-	 * @return string
1125
-	 */
1126
-	public static function preview_icon($path) {
1127
-		return \OC::$server->getURLGenerator()->linkToRoute('core_ajax_trashbin_preview', ['x' => 32, 'y' => 32, 'file' => $path]);
1128
-	}
66
+    // unit: percentage; 50% of available disk space/quota
67
+    public const DEFAULTMAXSIZE = 50;
68
+
69
+    /**
70
+     * Whether versions have already be rescanned during this PHP request
71
+     *
72
+     * @var bool
73
+     */
74
+    private static $scannedVersions = false;
75
+
76
+    /**
77
+     * Ensure we don't need to scan the file during the move to trash
78
+     * by triggering the scan in the pre-hook
79
+     *
80
+     * @param array $params
81
+     */
82
+    public static function ensureFileScannedHook($params) {
83
+        try {
84
+            self::getUidAndFilename($params['path']);
85
+        } catch (NotFoundException $e) {
86
+            // nothing to scan for non existing files
87
+        }
88
+    }
89
+
90
+    /**
91
+     * get the UID of the owner of the file and the path to the file relative to
92
+     * owners files folder
93
+     *
94
+     * @param string $filename
95
+     * @return array
96
+     * @throws \OC\User\NoUserException
97
+     */
98
+    public static function getUidAndFilename($filename) {
99
+        $uid = Filesystem::getOwner($filename);
100
+        $userManager = \OC::$server->getUserManager();
101
+        // if the user with the UID doesn't exists, e.g. because the UID points
102
+        // to a remote user with a federated cloud ID we use the current logged-in
103
+        // user. We need a valid local user to move the file to the right trash bin
104
+        if (!$userManager->userExists($uid)) {
105
+            $uid = User::getUser();
106
+        }
107
+        if (!$uid) {
108
+            // no owner, usually because of share link from ext storage
109
+            return [null, null];
110
+        }
111
+        Filesystem::initMountPoints($uid);
112
+        if ($uid !== User::getUser()) {
113
+            $info = Filesystem::getFileInfo($filename);
114
+            $ownerView = new View('/' . $uid . '/files');
115
+            try {
116
+                $filename = $ownerView->getPath($info['fileid']);
117
+            } catch (NotFoundException $e) {
118
+                $filename = null;
119
+            }
120
+        }
121
+        return [$uid, $filename];
122
+    }
123
+
124
+    /**
125
+     * get original location of files for user
126
+     *
127
+     * @param string $user
128
+     * @return array (filename => array (timestamp => original location))
129
+     */
130
+    public static function getLocations($user) {
131
+        $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
132
+        $query->select('id', 'timestamp', 'location')
133
+            ->from('files_trash')
134
+            ->where($query->expr()->eq('user', $query->createNamedParameter($user)));
135
+        $result = $query->execute();
136
+        $array = [];
137
+        while ($row = $result->fetch()) {
138
+            if (isset($array[$row['id']])) {
139
+                $array[$row['id']][$row['timestamp']] = $row['location'];
140
+            } else {
141
+                $array[$row['id']] = [$row['timestamp'] => $row['location']];
142
+            }
143
+        }
144
+        $result->closeCursor();
145
+        return $array;
146
+    }
147
+
148
+    /**
149
+     * get original location of file
150
+     *
151
+     * @param string $user
152
+     * @param string $filename
153
+     * @param string $timestamp
154
+     * @return string original location
155
+     */
156
+    public static function getLocation($user, $filename, $timestamp) {
157
+        $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
158
+        $query->select('location')
159
+            ->from('files_trash')
160
+            ->where($query->expr()->eq('user', $query->createNamedParameter($user)))
161
+            ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
162
+            ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
163
+
164
+        $result = $query->execute();
165
+        $row = $result->fetch();
166
+        $result->closeCursor();
167
+
168
+        if (isset($row['location'])) {
169
+            return $row['location'];
170
+        } else {
171
+            return false;
172
+        }
173
+    }
174
+
175
+    private static function setUpTrash($user) {
176
+        $view = new View('/' . $user);
177
+        if (!$view->is_dir('files_trashbin')) {
178
+            $view->mkdir('files_trashbin');
179
+        }
180
+        if (!$view->is_dir('files_trashbin/files')) {
181
+            $view->mkdir('files_trashbin/files');
182
+        }
183
+        if (!$view->is_dir('files_trashbin/versions')) {
184
+            $view->mkdir('files_trashbin/versions');
185
+        }
186
+        if (!$view->is_dir('files_trashbin/keys')) {
187
+            $view->mkdir('files_trashbin/keys');
188
+        }
189
+    }
190
+
191
+
192
+    /**
193
+     * copy file to owners trash
194
+     *
195
+     * @param string $sourcePath
196
+     * @param string $owner
197
+     * @param string $targetPath
198
+     * @param $user
199
+     * @param integer $timestamp
200
+     */
201
+    private static function copyFilesToUser($sourcePath, $owner, $targetPath, $user, $timestamp) {
202
+        self::setUpTrash($owner);
203
+
204
+        $targetFilename = basename($targetPath);
205
+        $targetLocation = dirname($targetPath);
206
+
207
+        $sourceFilename = basename($sourcePath);
208
+
209
+        $view = new View('/');
210
+
211
+        $target = $user . '/files_trashbin/files/' . $targetFilename . '.d' . $timestamp;
212
+        $source = $owner . '/files_trashbin/files/' . $sourceFilename . '.d' . $timestamp;
213
+        $free = $view->free_space($target);
214
+        $isUnknownOrUnlimitedFreeSpace = $free < 0;
215
+        $isEnoughFreeSpaceLeft = $view->filesize($source) < $free;
216
+        if ($isUnknownOrUnlimitedFreeSpace || $isEnoughFreeSpaceLeft) {
217
+            self::copy_recursive($source, $target, $view);
218
+        }
219
+
220
+
221
+        if ($view->file_exists($target)) {
222
+            $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
223
+            $query->insert('files_trash')
224
+                ->setValue('id', $query->createNamedParameter($targetFilename))
225
+                ->setValue('timestamp', $query->createNamedParameter($timestamp))
226
+                ->setValue('location', $query->createNamedParameter($targetLocation))
227
+                ->setValue('user', $query->createNamedParameter($user));
228
+            $result = $query->execute();
229
+            if (!$result) {
230
+                \OC::$server->getLogger()->error('trash bin database couldn\'t be updated for the files owner', ['app' => 'files_trashbin']);
231
+            }
232
+        }
233
+    }
234
+
235
+
236
+    /**
237
+     * move file to the trash bin
238
+     *
239
+     * @param string $file_path path to the deleted file/directory relative to the files root directory
240
+     * @param bool $ownerOnly delete for owner only (if file gets moved out of a shared folder)
241
+     *
242
+     * @return bool
243
+     */
244
+    public static function move2trash($file_path, $ownerOnly = false) {
245
+        // get the user for which the filesystem is setup
246
+        $root = Filesystem::getRoot();
247
+        [, $user] = explode('/', $root);
248
+        [$owner, $ownerPath] = self::getUidAndFilename($file_path);
249
+
250
+        // if no owner found (ex: ext storage + share link), will use the current user's trashbin then
251
+        if (is_null($owner)) {
252
+            $owner = $user;
253
+            $ownerPath = $file_path;
254
+        }
255
+
256
+        $ownerView = new View('/' . $owner);
257
+        // file has been deleted in between
258
+        if (is_null($ownerPath) || $ownerPath === '' || !$ownerView->file_exists('/files/' . $ownerPath)) {
259
+            return true;
260
+        }
261
+
262
+        self::setUpTrash($user);
263
+        if ($owner !== $user) {
264
+            // also setup for owner
265
+            self::setUpTrash($owner);
266
+        }
267
+
268
+        $path_parts = pathinfo($ownerPath);
269
+
270
+        $filename = $path_parts['basename'];
271
+        $location = $path_parts['dirname'];
272
+        /** @var ITimeFactory $timeFactory */
273
+        $timeFactory = \OC::$server->query(ITimeFactory::class);
274
+        $timestamp = $timeFactory->getTime();
275
+
276
+        $lockingProvider = \OC::$server->getLockingProvider();
277
+
278
+        // disable proxy to prevent recursive calls
279
+        $trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
280
+        $gotLock = false;
281
+
282
+        while (!$gotLock) {
283
+            try {
284
+                /** @var \OC\Files\Storage\Storage $trashStorage */
285
+                [$trashStorage, $trashInternalPath] = $ownerView->resolvePath($trashPath);
286
+
287
+                $trashStorage->acquireLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
288
+                $gotLock = true;
289
+            } catch (LockedException $e) {
290
+                // a file with the same name is being deleted concurrently
291
+                // nudge the timestamp a bit to resolve the conflict
292
+
293
+                $timestamp = $timestamp + 1;
294
+
295
+                $trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
296
+            }
297
+        }
298
+
299
+        /** @var \OC\Files\Storage\Storage $sourceStorage */
300
+        [$sourceStorage, $sourceInternalPath] = $ownerView->resolvePath('/files/' . $ownerPath);
301
+
302
+
303
+        if ($trashStorage->file_exists($trashInternalPath)) {
304
+            $trashStorage->unlink($trashInternalPath);
305
+        }
306
+
307
+        $config = \OC::$server->getConfig();
308
+        $systemTrashbinSize = (int)$config->getAppValue('files_trashbin', 'trashbin_size', '-1');
309
+        $userTrashbinSize = (int)$config->getUserValue($owner, 'files_trashbin', 'trashbin_size', '-1');
310
+        $configuredTrashbinSize = ($userTrashbinSize < 0) ? $systemTrashbinSize : $userTrashbinSize;
311
+        if ($configuredTrashbinSize >= 0 && $sourceStorage->filesize($sourceInternalPath) >= $configuredTrashbinSize) {
312
+            return false;
313
+        }
314
+
315
+        $trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
316
+
317
+        try {
318
+            $moveSuccessful = true;
319
+
320
+            // when moving within the same object store, the cache update done above is enough to move the file
321
+            if (!($trashStorage->instanceOfStorage(ObjectStoreStorage::class) && $trashStorage->getId() === $sourceStorage->getId())) {
322
+                $trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
323
+            }
324
+        } catch (\OCA\Files_Trashbin\Exceptions\CopyRecursiveException $e) {
325
+            $moveSuccessful = false;
326
+            if ($trashStorage->file_exists($trashInternalPath)) {
327
+                $trashStorage->unlink($trashInternalPath);
328
+            }
329
+            \OC::$server->getLogger()->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']);
330
+        }
331
+
332
+        if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
333
+            if ($sourceStorage->is_dir($sourceInternalPath)) {
334
+                $sourceStorage->rmdir($sourceInternalPath);
335
+            } else {
336
+                $sourceStorage->unlink($sourceInternalPath);
337
+            }
338
+
339
+            if ($sourceStorage->file_exists($sourceInternalPath)) {
340
+                // undo the cache move
341
+                $sourceStorage->getUpdater()->renameFromStorage($trashStorage, $trashInternalPath, $sourceInternalPath);
342
+            } else {
343
+                $trashStorage->getUpdater()->remove($trashInternalPath);
344
+            }
345
+            return false;
346
+        }
347
+
348
+        if ($moveSuccessful) {
349
+            $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
350
+            $query->insert('files_trash')
351
+                ->setValue('id', $query->createNamedParameter($filename))
352
+                ->setValue('timestamp', $query->createNamedParameter($timestamp))
353
+                ->setValue('location', $query->createNamedParameter($location))
354
+                ->setValue('user', $query->createNamedParameter($owner));
355
+            $result = $query->execute();
356
+            if (!$result) {
357
+                \OC::$server->getLogger()->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']);
358
+            }
359
+            \OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path),
360
+                'trashPath' => Filesystem::normalizePath($filename . '.d' . $timestamp)]);
361
+
362
+            self::retainVersions($filename, $owner, $ownerPath, $timestamp);
363
+
364
+            // if owner !== user we need to also add a copy to the users trash
365
+            if ($user !== $owner && $ownerOnly === false) {
366
+                self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp);
367
+            }
368
+        }
369
+
370
+        $trashStorage->releaseLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
371
+
372
+        self::scheduleExpire($user);
373
+
374
+        // if owner !== user we also need to update the owners trash size
375
+        if ($owner !== $user) {
376
+            self::scheduleExpire($owner);
377
+        }
378
+
379
+        return $moveSuccessful;
380
+    }
381
+
382
+    /**
383
+     * Move file versions to trash so that they can be restored later
384
+     *
385
+     * @param string $filename of deleted file
386
+     * @param string $owner owner user id
387
+     * @param string $ownerPath path relative to the owner's home storage
388
+     * @param integer $timestamp when the file was deleted
389
+     */
390
+    private static function retainVersions($filename, $owner, $ownerPath, $timestamp) {
391
+        if (\OCP\App::isEnabled('files_versions') && !empty($ownerPath)) {
392
+            $user = User::getUser();
393
+            $rootView = new View('/');
394
+
395
+            if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
396
+                if ($owner !== $user) {
397
+                    self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . basename($ownerPath) . '.d' . $timestamp, $rootView);
398
+                }
399
+                self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp);
400
+            } elseif ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) {
401
+                foreach ($versions as $v) {
402
+                    if ($owner !== $user) {
403
+                        self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . $v['name'] . '.v' . $v['version'] . '.d' . $timestamp);
404
+                    }
405
+                    self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp);
406
+                }
407
+            }
408
+        }
409
+    }
410
+
411
+    /**
412
+     * Move a file or folder on storage level
413
+     *
414
+     * @param View $view
415
+     * @param string $source
416
+     * @param string $target
417
+     * @return bool
418
+     */
419
+    private static function move(View $view, $source, $target) {
420
+        /** @var \OC\Files\Storage\Storage $sourceStorage */
421
+        [$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
422
+        /** @var \OC\Files\Storage\Storage $targetStorage */
423
+        [$targetStorage, $targetInternalPath] = $view->resolvePath($target);
424
+        /** @var \OC\Files\Storage\Storage $ownerTrashStorage */
425
+
426
+        $result = $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
427
+        if ($result) {
428
+            $targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
429
+        }
430
+        return $result;
431
+    }
432
+
433
+    /**
434
+     * Copy a file or folder on storage level
435
+     *
436
+     * @param View $view
437
+     * @param string $source
438
+     * @param string $target
439
+     * @return bool
440
+     */
441
+    private static function copy(View $view, $source, $target) {
442
+        /** @var \OC\Files\Storage\Storage $sourceStorage */
443
+        [$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
444
+        /** @var \OC\Files\Storage\Storage $targetStorage */
445
+        [$targetStorage, $targetInternalPath] = $view->resolvePath($target);
446
+        /** @var \OC\Files\Storage\Storage $ownerTrashStorage */
447
+
448
+        $result = $targetStorage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
449
+        if ($result) {
450
+            $targetStorage->getUpdater()->update($targetInternalPath);
451
+        }
452
+        return $result;
453
+    }
454
+
455
+    /**
456
+     * Restore a file or folder from trash bin
457
+     *
458
+     * @param string $file path to the deleted file/folder relative to "files_trashbin/files/",
459
+     * including the timestamp suffix ".d12345678"
460
+     * @param string $filename name of the file/folder
461
+     * @param int $timestamp time when the file/folder was deleted
462
+     *
463
+     * @return bool true on success, false otherwise
464
+     */
465
+    public static function restore($file, $filename, $timestamp) {
466
+        $user = User::getUser();
467
+        $view = new View('/' . $user);
468
+
469
+        $location = '';
470
+        if ($timestamp) {
471
+            $location = self::getLocation($user, $filename, $timestamp);
472
+            if ($location === false) {
473
+                \OC::$server->getLogger()->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']);
474
+            } else {
475
+                // if location no longer exists, restore file in the root directory
476
+                if ($location !== '/' &&
477
+                    (!$view->is_dir('files/' . $location) ||
478
+                        !$view->isCreatable('files/' . $location))
479
+                ) {
480
+                    $location = '';
481
+                }
482
+            }
483
+        }
484
+
485
+        // we need a  extension in case a file/dir with the same name already exists
486
+        $uniqueFilename = self::getUniqueFilename($location, $filename, $view);
487
+
488
+        $source = Filesystem::normalizePath('files_trashbin/files/' . $file);
489
+        $target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
490
+        if (!$view->file_exists($source)) {
491
+            return false;
492
+        }
493
+        $mtime = $view->filemtime($source);
494
+
495
+        // restore file
496
+        if (!$view->isCreatable(dirname($target))) {
497
+            throw new NotPermittedException("Can't restore trash item because the target folder is not writable");
498
+        }
499
+        $restoreResult = $view->rename($source, $target);
500
+
501
+        // handle the restore result
502
+        if ($restoreResult) {
503
+            $fakeRoot = $view->getRoot();
504
+            $view->chroot('/' . $user . '/files');
505
+            $view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
506
+            $view->chroot($fakeRoot);
507
+            \OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename),
508
+                'trashPath' => Filesystem::normalizePath($file)]);
509
+
510
+            self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
511
+
512
+            if ($timestamp) {
513
+                $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
514
+                $query->delete('files_trash')
515
+                    ->where($query->expr()->eq('user', $query->createNamedParameter($user)))
516
+                    ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
517
+                    ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
518
+                $query->execute();
519
+            }
520
+
521
+            return true;
522
+        }
523
+
524
+        return false;
525
+    }
526
+
527
+    /**
528
+     * restore versions from trash bin
529
+     *
530
+     * @param View $view file view
531
+     * @param string $file complete path to file
532
+     * @param string $filename name of file once it was deleted
533
+     * @param string $uniqueFilename new file name to restore the file without overwriting existing files
534
+     * @param string $location location if file
535
+     * @param int $timestamp deletion time
536
+     * @return false|null
537
+     */
538
+    private static function restoreVersions(View $view, $file, $filename, $uniqueFilename, $location, $timestamp) {
539
+        if (\OCP\App::isEnabled('files_versions')) {
540
+            $user = User::getUser();
541
+            $rootView = new View('/');
542
+
543
+            $target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
544
+
545
+            [$owner, $ownerPath] = self::getUidAndFilename($target);
546
+
547
+            // file has been deleted in between
548
+            if (empty($ownerPath)) {
549
+                return false;
550
+            }
551
+
552
+            if ($timestamp) {
553
+                $versionedFile = $filename;
554
+            } else {
555
+                $versionedFile = $file;
556
+            }
557
+
558
+            if ($view->is_dir('/files_trashbin/versions/' . $file)) {
559
+                $rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
560
+            } elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
561
+                foreach ($versions as $v) {
562
+                    if ($timestamp) {
563
+                        $rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
564
+                    } else {
565
+                        $rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
566
+                    }
567
+                }
568
+            }
569
+        }
570
+    }
571
+
572
+    /**
573
+     * delete all files from the trash
574
+     */
575
+    public static function deleteAll() {
576
+        $user = User::getUser();
577
+        $userRoot = \OC::$server->getUserFolder($user)->getParent();
578
+        $view = new View('/' . $user);
579
+        $fileInfos = $view->getDirectoryContent('files_trashbin/files');
580
+
581
+        try {
582
+            $trash = $userRoot->get('files_trashbin');
583
+        } catch (NotFoundException $e) {
584
+            return false;
585
+        }
586
+
587
+        // Array to store the relative path in (after the file is deleted, the view won't be able to relativise the path anymore)
588
+        $filePaths = [];
589
+        foreach ($fileInfos as $fileInfo) {
590
+            $filePaths[] = $view->getRelativePath($fileInfo->getPath());
591
+        }
592
+        unset($fileInfos); // save memory
593
+
594
+        // Bulk PreDelete-Hook
595
+        \OC_Hook::emit('\OCP\Trashbin', 'preDeleteAll', ['paths' => $filePaths]);
596
+
597
+        // Single-File Hooks
598
+        foreach ($filePaths as $path) {
599
+            self::emitTrashbinPreDelete($path);
600
+        }
601
+
602
+        // actual file deletion
603
+        $trash->delete();
604
+
605
+        $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
606
+        $query->delete('files_trash')
607
+            ->where($query->expr()->eq('user', $query->createNamedParameter($user)));
608
+        $query->execute();
609
+
610
+        // Bulk PostDelete-Hook
611
+        \OC_Hook::emit('\OCP\Trashbin', 'deleteAll', ['paths' => $filePaths]);
612
+
613
+        // Single-File Hooks
614
+        foreach ($filePaths as $path) {
615
+            self::emitTrashbinPostDelete($path);
616
+        }
617
+
618
+        $trash = $userRoot->newFolder('files_trashbin');
619
+        $trash->newFolder('files');
620
+
621
+        return true;
622
+    }
623
+
624
+    /**
625
+     * wrapper function to emit the 'preDelete' hook of \OCP\Trashbin before a file is deleted
626
+     *
627
+     * @param string $path
628
+     */
629
+    protected static function emitTrashbinPreDelete($path) {
630
+        \OC_Hook::emit('\OCP\Trashbin', 'preDelete', ['path' => $path]);
631
+    }
632
+
633
+    /**
634
+     * wrapper function to emit the 'delete' hook of \OCP\Trashbin after a file has been deleted
635
+     *
636
+     * @param string $path
637
+     */
638
+    protected static function emitTrashbinPostDelete($path) {
639
+        \OC_Hook::emit('\OCP\Trashbin', 'delete', ['path' => $path]);
640
+    }
641
+
642
+    /**
643
+     * delete file from trash bin permanently
644
+     *
645
+     * @param string $filename path to the file
646
+     * @param string $user
647
+     * @param int $timestamp of deletion time
648
+     *
649
+     * @return int size of deleted files
650
+     */
651
+    public static function delete($filename, $user, $timestamp = null) {
652
+        $userRoot = \OC::$server->getUserFolder($user)->getParent();
653
+        $view = new View('/' . $user);
654
+        $size = 0;
655
+
656
+        if ($timestamp) {
657
+            $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
658
+            $query->delete('files_trash')
659
+                ->where($query->expr()->eq('user', $query->createNamedParameter($user)))
660
+                ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
661
+                ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
662
+            $query->execute();
663
+
664
+            $file = $filename . '.d' . $timestamp;
665
+        } else {
666
+            $file = $filename;
667
+        }
668
+
669
+        $size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
670
+
671
+        try {
672
+            $node = $userRoot->get('/files_trashbin/files/' . $file);
673
+        } catch (NotFoundException $e) {
674
+            return $size;
675
+        }
676
+
677
+        if ($node instanceof Folder) {
678
+            $size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
679
+        } elseif ($node instanceof File) {
680
+            $size += $view->filesize('/files_trashbin/files/' . $file);
681
+        }
682
+
683
+        self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
684
+        $node->delete();
685
+        self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
686
+
687
+        return $size;
688
+    }
689
+
690
+    /**
691
+     * @param View $view
692
+     * @param string $file
693
+     * @param string $filename
694
+     * @param integer|null $timestamp
695
+     * @param string $user
696
+     * @return int
697
+     */
698
+    private static function deleteVersions(View $view, $file, $filename, $timestamp, $user) {
699
+        $size = 0;
700
+        if (\OCP\App::isEnabled('files_versions')) {
701
+            if ($view->is_dir('files_trashbin/versions/' . $file)) {
702
+                $size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
703
+                $view->unlink('files_trashbin/versions/' . $file);
704
+            } elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
705
+                foreach ($versions as $v) {
706
+                    if ($timestamp) {
707
+                        $size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
708
+                        $view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
709
+                    } else {
710
+                        $size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
711
+                        $view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
712
+                    }
713
+                }
714
+            }
715
+        }
716
+        return $size;
717
+    }
718
+
719
+    /**
720
+     * check to see whether a file exists in trashbin
721
+     *
722
+     * @param string $filename path to the file
723
+     * @param int $timestamp of deletion time
724
+     * @return bool true if file exists, otherwise false
725
+     */
726
+    public static function file_exists($filename, $timestamp = null) {
727
+        $user = User::getUser();
728
+        $view = new View('/' . $user);
729
+
730
+        if ($timestamp) {
731
+            $filename = $filename . '.d' . $timestamp;
732
+        }
733
+
734
+        $target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
735
+        return $view->file_exists($target);
736
+    }
737
+
738
+    /**
739
+     * deletes used space for trash bin in db if user was deleted
740
+     *
741
+     * @param string $uid id of deleted user
742
+     * @return bool result of db delete operation
743
+     */
744
+    public static function deleteUser($uid) {
745
+        $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
746
+        $query->delete('files_trash')
747
+            ->where($query->expr()->eq('user', $query->createNamedParameter($uid)));
748
+        return (bool) $query->execute();
749
+    }
750
+
751
+    /**
752
+     * calculate remaining free space for trash bin
753
+     *
754
+     * @param integer $trashbinSize current size of the trash bin
755
+     * @param string $user
756
+     * @return int available free space for trash bin
757
+     */
758
+    private static function calculateFreeSpace($trashbinSize, $user) {
759
+        $config = \OC::$server->getConfig();
760
+        $userTrashbinSize = (int)$config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
761
+        if ($userTrashbinSize > -1) {
762
+            return $userTrashbinSize - $trashbinSize;
763
+        }
764
+        $systemTrashbinSize = (int)$config->getAppValue('files_trashbin', 'trashbin_size', '-1');
765
+        if ($systemTrashbinSize > -1) {
766
+            return $systemTrashbinSize - $trashbinSize;
767
+        }
768
+
769
+        $softQuota = true;
770
+        $userObject = \OC::$server->getUserManager()->get($user);
771
+        if (is_null($userObject)) {
772
+            return 0;
773
+        }
774
+        $quota = $userObject->getQuota();
775
+        if ($quota === null || $quota === 'none') {
776
+            $quota = Filesystem::free_space('/');
777
+            $softQuota = false;
778
+            // inf or unknown free space
779
+            if ($quota < 0) {
780
+                $quota = PHP_INT_MAX;
781
+            }
782
+        } else {
783
+            $quota = \OCP\Util::computerFileSize($quota);
784
+        }
785
+
786
+        // calculate available space for trash bin
787
+        // subtract size of files and current trash bin size from quota
788
+        if ($softQuota) {
789
+            $userFolder = \OC::$server->getUserFolder($user);
790
+            if (is_null($userFolder)) {
791
+                return 0;
792
+            }
793
+            $free = $quota - $userFolder->getSize(false); // remaining free space for user
794
+            if ($free > 0) {
795
+                $availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $trashbinSize; // how much space can be used for versions
796
+            } else {
797
+                $availableSpace = $free - $trashbinSize;
798
+            }
799
+        } else {
800
+            $availableSpace = $quota;
801
+        }
802
+
803
+        return $availableSpace;
804
+    }
805
+
806
+    /**
807
+     * resize trash bin if necessary after a new file was added to Nextcloud
808
+     *
809
+     * @param string $user user id
810
+     */
811
+    public static function resizeTrash($user) {
812
+        $size = self::getTrashbinSize($user);
813
+
814
+        $freeSpace = self::calculateFreeSpace($size, $user);
815
+
816
+        if ($freeSpace < 0) {
817
+            self::scheduleExpire($user);
818
+        }
819
+    }
820
+
821
+    /**
822
+     * clean up the trash bin
823
+     *
824
+     * @param string $user
825
+     */
826
+    public static function expire($user) {
827
+        $trashBinSize = self::getTrashbinSize($user);
828
+        $availableSpace = self::calculateFreeSpace($trashBinSize, $user);
829
+
830
+        $dirContent = Helper::getTrashFiles('/', $user, 'mtime');
831
+
832
+        // delete all files older then $retention_obligation
833
+        [$delSize, $count] = self::deleteExpiredFiles($dirContent, $user);
834
+
835
+        $availableSpace += $delSize;
836
+
837
+        // delete files from trash until we meet the trash bin size limit again
838
+        self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace);
839
+    }
840
+
841
+    /**
842
+     * @param string $user
843
+     */
844
+    private static function scheduleExpire($user) {
845
+        // let the admin disable auto expire
846
+        /** @var Application $application */
847
+        $application = \OC::$server->query(Application::class);
848
+        $expiration = $application->getContainer()->query('Expiration');
849
+        if ($expiration->isEnabled()) {
850
+            \OC::$server->getCommandBus()->push(new Expire($user));
851
+        }
852
+    }
853
+
854
+    /**
855
+     * if the size limit for the trash bin is reached, we delete the oldest
856
+     * files in the trash bin until we meet the limit again
857
+     *
858
+     * @param array $files
859
+     * @param string $user
860
+     * @param int $availableSpace available disc space
861
+     * @return int size of deleted files
862
+     */
863
+    protected static function deleteFiles($files, $user, $availableSpace) {
864
+        /** @var Application $application */
865
+        $application = \OC::$server->query(Application::class);
866
+        $expiration = $application->getContainer()->query('Expiration');
867
+        $size = 0;
868
+
869
+        if ($availableSpace < 0) {
870
+            foreach ($files as $file) {
871
+                if ($availableSpace < 0 && $expiration->isExpired($file['mtime'], true)) {
872
+                    $tmp = self::delete($file['name'], $user, $file['mtime']);
873
+                    \OC::$server->getLogger()->info('remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota)', ['app' => 'files_trashbin']);
874
+                    $availableSpace += $tmp;
875
+                    $size += $tmp;
876
+                } else {
877
+                    break;
878
+                }
879
+            }
880
+        }
881
+        return $size;
882
+    }
883
+
884
+    /**
885
+     * delete files older then max storage time
886
+     *
887
+     * @param array $files list of files sorted by mtime
888
+     * @param string $user
889
+     * @return integer[] size of deleted files and number of deleted files
890
+     */
891
+    public static function deleteExpiredFiles($files, $user) {
892
+        /** @var Expiration $expiration */
893
+        $expiration = \OC::$server->query(Expiration::class);
894
+        $size = 0;
895
+        $count = 0;
896
+        foreach ($files as $file) {
897
+            $timestamp = $file['mtime'];
898
+            $filename = $file['name'];
899
+            if ($expiration->isExpired($timestamp)) {
900
+                try {
901
+                    $size += self::delete($filename, $user, $timestamp);
902
+                    $count++;
903
+                } catch (\OCP\Files\NotPermittedException $e) {
904
+                    \OC::$server->getLogger()->logException($e, ['app' => 'files_trashbin', 'level' => \OCP\ILogger::WARN, 'message' => 'Removing "' . $filename . '" from trashbin failed.']);
905
+                }
906
+                \OC::$server->getLogger()->info(
907
+                    'Remove "' . $filename . '" from trashbin because it exceeds max retention obligation term.',
908
+                    ['app' => 'files_trashbin']
909
+                );
910
+            } else {
911
+                break;
912
+            }
913
+        }
914
+
915
+        return [$size, $count];
916
+    }
917
+
918
+    /**
919
+     * recursive copy to copy a whole directory
920
+     *
921
+     * @param string $source source path, relative to the users files directory
922
+     * @param string $destination destination path relative to the users root directoy
923
+     * @param View $view file view for the users root directory
924
+     * @return int
925
+     * @throws Exceptions\CopyRecursiveException
926
+     */
927
+    private static function copy_recursive($source, $destination, View $view) {
928
+        $size = 0;
929
+        if ($view->is_dir($source)) {
930
+            $view->mkdir($destination);
931
+            $view->touch($destination, $view->filemtime($source));
932
+            foreach ($view->getDirectoryContent($source) as $i) {
933
+                $pathDir = $source . '/' . $i['name'];
934
+                if ($view->is_dir($pathDir)) {
935
+                    $size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
936
+                } else {
937
+                    $size += $view->filesize($pathDir);
938
+                    $result = $view->copy($pathDir, $destination . '/' . $i['name']);
939
+                    if (!$result) {
940
+                        throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
941
+                    }
942
+                    $view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
943
+                }
944
+            }
945
+        } else {
946
+            $size += $view->filesize($source);
947
+            $result = $view->copy($source, $destination);
948
+            if (!$result) {
949
+                throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
950
+            }
951
+            $view->touch($destination, $view->filemtime($source));
952
+        }
953
+        return $size;
954
+    }
955
+
956
+    /**
957
+     * find all versions which belong to the file we want to restore
958
+     *
959
+     * @param string $filename name of the file which should be restored
960
+     * @param int $timestamp timestamp when the file was deleted
961
+     * @return array
962
+     */
963
+    private static function getVersionsFromTrash($filename, $timestamp, $user) {
964
+        $view = new View('/' . $user . '/files_trashbin/versions');
965
+        $versions = [];
966
+
967
+        /** @var \OC\Files\Storage\Storage $storage */
968
+        [$storage,] = $view->resolvePath('/');
969
+
970
+        //force rescan of versions, local storage may not have updated the cache
971
+        if (!self::$scannedVersions) {
972
+            $storage->getScanner()->scan('files_trashbin/versions');
973
+            self::$scannedVersions = true;
974
+        }
975
+
976
+        $pattern = \OC::$server->getDatabaseConnection()->escapeLikeParameter(basename($filename));
977
+        if ($timestamp) {
978
+            // fetch for old versions
979
+            $escapedTimestamp = \OC::$server->getDatabaseConnection()->escapeLikeParameter($timestamp);
980
+            $pattern .= '.v%.d' . $escapedTimestamp;
981
+            $offset = -strlen($escapedTimestamp) - 2;
982
+        } else {
983
+            $pattern .= '.v%';
984
+        }
985
+
986
+        // Manually fetch all versions from the file cache to be able to filter them by their parent
987
+        $cache = $storage->getCache('');
988
+        $query = new CacheQueryBuilder(
989
+            \OC::$server->getDatabaseConnection(),
990
+            \OC::$server->getSystemConfig(),
991
+            \OC::$server->getLogger(),
992
+            $cache
993
+        );
994
+        $normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/'. $filename)), '/');
995
+        $parentId = $cache->getId($normalizedParentPath);
996
+        if ($parentId === -1) {
997
+            return [];
998
+        }
999
+
1000
+        $query->selectFileCache()
1001
+            ->whereStorageId()
1002
+            ->andWhere($query->expr()->eq('parent', $query->createNamedParameter($parentId)))
1003
+            ->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern)));
1004
+
1005
+        $result = $query->execute();
1006
+        $entries = $result->fetchAll();
1007
+        $result->closeCursor();
1008
+
1009
+        /** @var CacheEntry[] $matches */
1010
+        $matches = array_map(function (array $data) {
1011
+            return Cache::cacheEntryFromData($data, \OC::$server->getMimeTypeLoader());
1012
+        }, $entries);
1013
+
1014
+        foreach ($matches as $ma) {
1015
+            if ($timestamp) {
1016
+                $parts = explode('.v', substr($ma['path'], 0, $offset));
1017
+                $versions[] = end($parts);
1018
+            } else {
1019
+                $parts = explode('.v', $ma['path']);
1020
+                $versions[] = end($parts);
1021
+            }
1022
+        }
1023
+
1024
+        return $versions;
1025
+    }
1026
+
1027
+    /**
1028
+     * find unique extension for restored file if a file with the same name already exists
1029
+     *
1030
+     * @param string $location where the file should be restored
1031
+     * @param string $filename name of the file
1032
+     * @param View $view filesystem view relative to users root directory
1033
+     * @return string with unique extension
1034
+     */
1035
+    private static function getUniqueFilename($location, $filename, View $view) {
1036
+        $ext = pathinfo($filename, PATHINFO_EXTENSION);
1037
+        $name = pathinfo($filename, PATHINFO_FILENAME);
1038
+        $l = \OC::$server->getL10N('files_trashbin');
1039
+
1040
+        $location = '/' . trim($location, '/');
1041
+
1042
+        // if extension is not empty we set a dot in front of it
1043
+        if ($ext !== '') {
1044
+            $ext = '.' . $ext;
1045
+        }
1046
+
1047
+        if ($view->file_exists('files' . $location . '/' . $filename)) {
1048
+            $i = 2;
1049
+            $uniqueName = $name . " (" . $l->t("restored") . ")" . $ext;
1050
+            while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
1051
+                $uniqueName = $name . " (" . $l->t("restored") . " " . $i . ")" . $ext;
1052
+                $i++;
1053
+            }
1054
+
1055
+            return $uniqueName;
1056
+        }
1057
+
1058
+        return $filename;
1059
+    }
1060
+
1061
+    /**
1062
+     * get the size from a given root folder
1063
+     *
1064
+     * @param View $view file view on the root folder
1065
+     * @return integer size of the folder
1066
+     */
1067
+    private static function calculateSize($view) {
1068
+        $root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
1069
+        if (!file_exists($root)) {
1070
+            return 0;
1071
+        }
1072
+        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), \RecursiveIteratorIterator::CHILD_FIRST);
1073
+        $size = 0;
1074
+
1075
+        /**
1076
+         * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
1077
+         * This bug is fixed in PHP 5.5.9 or before
1078
+         * See #8376
1079
+         */
1080
+        $iterator->rewind();
1081
+        while ($iterator->valid()) {
1082
+            $path = $iterator->current();
1083
+            $relpath = substr($path, strlen($root) - 1);
1084
+            if (!$view->is_dir($relpath)) {
1085
+                $size += $view->filesize($relpath);
1086
+            }
1087
+            $iterator->next();
1088
+        }
1089
+        return $size;
1090
+    }
1091
+
1092
+    /**
1093
+     * get current size of trash bin from a given user
1094
+     *
1095
+     * @param string $user user who owns the trash bin
1096
+     * @return integer trash bin size
1097
+     */
1098
+    private static function getTrashbinSize($user) {
1099
+        $view = new View('/' . $user);
1100
+        $fileInfo = $view->getFileInfo('/files_trashbin');
1101
+        return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
1102
+    }
1103
+
1104
+    /**
1105
+     * check if trash bin is empty for a given user
1106
+     *
1107
+     * @param string $user
1108
+     * @return bool
1109
+     */
1110
+    public static function isEmpty($user) {
1111
+        $view = new View('/' . $user . '/files_trashbin');
1112
+        if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
1113
+            while ($file = readdir($dh)) {
1114
+                if (!Filesystem::isIgnoredDir($file)) {
1115
+                    return false;
1116
+                }
1117
+            }
1118
+        }
1119
+        return true;
1120
+    }
1121
+
1122
+    /**
1123
+     * @param $path
1124
+     * @return string
1125
+     */
1126
+    public static function preview_icon($path) {
1127
+        return \OC::$server->getURLGenerator()->linkToRoute('core_ajax_trashbin_preview', ['x' => 32, 'y' => 32, 'file' => $path]);
1128
+    }
1129 1129
 }
Please login to merge, or discard this patch.
Spacing   +73 added lines, -73 removed lines patch added patch discarded remove patch
@@ -111,7 +111,7 @@  discard block
 block discarded – undo
111 111
 		Filesystem::initMountPoints($uid);
112 112
 		if ($uid !== User::getUser()) {
113 113
 			$info = Filesystem::getFileInfo($filename);
114
-			$ownerView = new View('/' . $uid . '/files');
114
+			$ownerView = new View('/'.$uid.'/files');
115 115
 			try {
116 116
 				$filename = $ownerView->getPath($info['fileid']);
117 117
 			} catch (NotFoundException $e) {
@@ -173,7 +173,7 @@  discard block
 block discarded – undo
173 173
 	}
174 174
 
175 175
 	private static function setUpTrash($user) {
176
-		$view = new View('/' . $user);
176
+		$view = new View('/'.$user);
177 177
 		if (!$view->is_dir('files_trashbin')) {
178 178
 			$view->mkdir('files_trashbin');
179 179
 		}
@@ -208,8 +208,8 @@  discard block
 block discarded – undo
208 208
 
209 209
 		$view = new View('/');
210 210
 
211
-		$target = $user . '/files_trashbin/files/' . $targetFilename . '.d' . $timestamp;
212
-		$source = $owner . '/files_trashbin/files/' . $sourceFilename . '.d' . $timestamp;
211
+		$target = $user.'/files_trashbin/files/'.$targetFilename.'.d'.$timestamp;
212
+		$source = $owner.'/files_trashbin/files/'.$sourceFilename.'.d'.$timestamp;
213 213
 		$free = $view->free_space($target);
214 214
 		$isUnknownOrUnlimitedFreeSpace = $free < 0;
215 215
 		$isEnoughFreeSpaceLeft = $view->filesize($source) < $free;
@@ -253,9 +253,9 @@  discard block
 block discarded – undo
253 253
 			$ownerPath = $file_path;
254 254
 		}
255 255
 
256
-		$ownerView = new View('/' . $owner);
256
+		$ownerView = new View('/'.$owner);
257 257
 		// file has been deleted in between
258
-		if (is_null($ownerPath) || $ownerPath === '' || !$ownerView->file_exists('/files/' . $ownerPath)) {
258
+		if (is_null($ownerPath) || $ownerPath === '' || !$ownerView->file_exists('/files/'.$ownerPath)) {
259 259
 			return true;
260 260
 		}
261 261
 
@@ -276,7 +276,7 @@  discard block
 block discarded – undo
276 276
 		$lockingProvider = \OC::$server->getLockingProvider();
277 277
 
278 278
 		// disable proxy to prevent recursive calls
279
-		$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
279
+		$trashPath = '/files_trashbin/files/'.$filename.'.d'.$timestamp;
280 280
 		$gotLock = false;
281 281
 
282 282
 		while (!$gotLock) {
@@ -292,12 +292,12 @@  discard block
 block discarded – undo
292 292
 
293 293
 				$timestamp = $timestamp + 1;
294 294
 
295
-				$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
295
+				$trashPath = '/files_trashbin/files/'.$filename.'.d'.$timestamp;
296 296
 			}
297 297
 		}
298 298
 
299 299
 		/** @var \OC\Files\Storage\Storage $sourceStorage */
300
-		[$sourceStorage, $sourceInternalPath] = $ownerView->resolvePath('/files/' . $ownerPath);
300
+		[$sourceStorage, $sourceInternalPath] = $ownerView->resolvePath('/files/'.$ownerPath);
301 301
 
302 302
 
303 303
 		if ($trashStorage->file_exists($trashInternalPath)) {
@@ -305,8 +305,8 @@  discard block
 block discarded – undo
305 305
 		}
306 306
 
307 307
 		$config = \OC::$server->getConfig();
308
-		$systemTrashbinSize = (int)$config->getAppValue('files_trashbin', 'trashbin_size', '-1');
309
-		$userTrashbinSize = (int)$config->getUserValue($owner, 'files_trashbin', 'trashbin_size', '-1');
308
+		$systemTrashbinSize = (int) $config->getAppValue('files_trashbin', 'trashbin_size', '-1');
309
+		$userTrashbinSize = (int) $config->getUserValue($owner, 'files_trashbin', 'trashbin_size', '-1');
310 310
 		$configuredTrashbinSize = ($userTrashbinSize < 0) ? $systemTrashbinSize : $userTrashbinSize;
311 311
 		if ($configuredTrashbinSize >= 0 && $sourceStorage->filesize($sourceInternalPath) >= $configuredTrashbinSize) {
312 312
 			return false;
@@ -326,7 +326,7 @@  discard block
 block discarded – undo
326 326
 			if ($trashStorage->file_exists($trashInternalPath)) {
327 327
 				$trashStorage->unlink($trashInternalPath);
328 328
 			}
329
-			\OC::$server->getLogger()->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']);
329
+			\OC::$server->getLogger()->error('Couldn\'t move '.$file_path.' to the trash bin', ['app' => 'files_trashbin']);
330 330
 		}
331 331
 
332 332
 		if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
@@ -357,7 +357,7 @@  discard block
 block discarded – undo
357 357
 				\OC::$server->getLogger()->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']);
358 358
 			}
359 359
 			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path),
360
-				'trashPath' => Filesystem::normalizePath($filename . '.d' . $timestamp)]);
360
+				'trashPath' => Filesystem::normalizePath($filename.'.d'.$timestamp)]);
361 361
 
362 362
 			self::retainVersions($filename, $owner, $ownerPath, $timestamp);
363 363
 
@@ -392,17 +392,17 @@  discard block
 block discarded – undo
392 392
 			$user = User::getUser();
393 393
 			$rootView = new View('/');
394 394
 
395
-			if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
395
+			if ($rootView->is_dir($owner.'/files_versions/'.$ownerPath)) {
396 396
 				if ($owner !== $user) {
397
-					self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . basename($ownerPath) . '.d' . $timestamp, $rootView);
397
+					self::copy_recursive($owner.'/files_versions/'.$ownerPath, $owner.'/files_trashbin/versions/'.basename($ownerPath).'.d'.$timestamp, $rootView);
398 398
 				}
399
-				self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp);
399
+				self::move($rootView, $owner.'/files_versions/'.$ownerPath, $user.'/files_trashbin/versions/'.$filename.'.d'.$timestamp);
400 400
 			} elseif ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) {
401 401
 				foreach ($versions as $v) {
402 402
 					if ($owner !== $user) {
403
-						self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . $v['name'] . '.v' . $v['version'] . '.d' . $timestamp);
403
+						self::copy($rootView, $owner.'/files_versions'.$v['path'].'.v'.$v['version'], $owner.'/files_trashbin/versions/'.$v['name'].'.v'.$v['version'].'.d'.$timestamp);
404 404
 					}
405
-					self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp);
405
+					self::move($rootView, $owner.'/files_versions'.$v['path'].'.v'.$v['version'], $user.'/files_trashbin/versions/'.$filename.'.v'.$v['version'].'.d'.$timestamp);
406 406
 				}
407 407
 			}
408 408
 		}
@@ -464,18 +464,18 @@  discard block
 block discarded – undo
464 464
 	 */
465 465
 	public static function restore($file, $filename, $timestamp) {
466 466
 		$user = User::getUser();
467
-		$view = new View('/' . $user);
467
+		$view = new View('/'.$user);
468 468
 
469 469
 		$location = '';
470 470
 		if ($timestamp) {
471 471
 			$location = self::getLocation($user, $filename, $timestamp);
472 472
 			if ($location === false) {
473
-				\OC::$server->getLogger()->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']);
473
+				\OC::$server->getLogger()->error('trash bin database inconsistent! ($user: '.$user.' $filename: '.$filename.', $timestamp: '.$timestamp.')', ['app' => 'files_trashbin']);
474 474
 			} else {
475 475
 				// if location no longer exists, restore file in the root directory
476 476
 				if ($location !== '/' &&
477
-					(!$view->is_dir('files/' . $location) ||
478
-						!$view->isCreatable('files/' . $location))
477
+					(!$view->is_dir('files/'.$location) ||
478
+						!$view->isCreatable('files/'.$location))
479 479
 				) {
480 480
 					$location = '';
481 481
 				}
@@ -485,8 +485,8 @@  discard block
 block discarded – undo
485 485
 		// we need a  extension in case a file/dir with the same name already exists
486 486
 		$uniqueFilename = self::getUniqueFilename($location, $filename, $view);
487 487
 
488
-		$source = Filesystem::normalizePath('files_trashbin/files/' . $file);
489
-		$target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
488
+		$source = Filesystem::normalizePath('files_trashbin/files/'.$file);
489
+		$target = Filesystem::normalizePath('files/'.$location.'/'.$uniqueFilename);
490 490
 		if (!$view->file_exists($source)) {
491 491
 			return false;
492 492
 		}
@@ -501,10 +501,10 @@  discard block
 block discarded – undo
501 501
 		// handle the restore result
502 502
 		if ($restoreResult) {
503 503
 			$fakeRoot = $view->getRoot();
504
-			$view->chroot('/' . $user . '/files');
505
-			$view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
504
+			$view->chroot('/'.$user.'/files');
505
+			$view->touch('/'.$location.'/'.$uniqueFilename, $mtime);
506 506
 			$view->chroot($fakeRoot);
507
-			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename),
507
+			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/'.$location.'/'.$uniqueFilename),
508 508
 				'trashPath' => Filesystem::normalizePath($file)]);
509 509
 
510 510
 			self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
@@ -540,7 +540,7 @@  discard block
 block discarded – undo
540 540
 			$user = User::getUser();
541 541
 			$rootView = new View('/');
542 542
 
543
-			$target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
543
+			$target = Filesystem::normalizePath('/'.$location.'/'.$uniqueFilename);
544 544
 
545 545
 			[$owner, $ownerPath] = self::getUidAndFilename($target);
546 546
 
@@ -555,14 +555,14 @@  discard block
 block discarded – undo
555 555
 				$versionedFile = $file;
556 556
 			}
557 557
 
558
-			if ($view->is_dir('/files_trashbin/versions/' . $file)) {
559
-				$rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
558
+			if ($view->is_dir('/files_trashbin/versions/'.$file)) {
559
+				$rootView->rename(Filesystem::normalizePath($user.'/files_trashbin/versions/'.$file), Filesystem::normalizePath($owner.'/files_versions/'.$ownerPath));
560 560
 			} elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
561 561
 				foreach ($versions as $v) {
562 562
 					if ($timestamp) {
563
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
563
+						$rootView->rename($user.'/files_trashbin/versions/'.$versionedFile.'.v'.$v.'.d'.$timestamp, $owner.'/files_versions/'.$ownerPath.'.v'.$v);
564 564
 					} else {
565
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
565
+						$rootView->rename($user.'/files_trashbin/versions/'.$versionedFile.'.v'.$v, $owner.'/files_versions/'.$ownerPath.'.v'.$v);
566 566
 					}
567 567
 				}
568 568
 			}
@@ -575,7 +575,7 @@  discard block
 block discarded – undo
575 575
 	public static function deleteAll() {
576 576
 		$user = User::getUser();
577 577
 		$userRoot = \OC::$server->getUserFolder($user)->getParent();
578
-		$view = new View('/' . $user);
578
+		$view = new View('/'.$user);
579 579
 		$fileInfos = $view->getDirectoryContent('files_trashbin/files');
580 580
 
581 581
 		try {
@@ -650,7 +650,7 @@  discard block
 block discarded – undo
650 650
 	 */
651 651
 	public static function delete($filename, $user, $timestamp = null) {
652 652
 		$userRoot = \OC::$server->getUserFolder($user)->getParent();
653
-		$view = new View('/' . $user);
653
+		$view = new View('/'.$user);
654 654
 		$size = 0;
655 655
 
656 656
 		if ($timestamp) {
@@ -661,7 +661,7 @@  discard block
 block discarded – undo
661 661
 				->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
662 662
 			$query->execute();
663 663
 
664
-			$file = $filename . '.d' . $timestamp;
664
+			$file = $filename.'.d'.$timestamp;
665 665
 		} else {
666 666
 			$file = $filename;
667 667
 		}
@@ -669,20 +669,20 @@  discard block
 block discarded – undo
669 669
 		$size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
670 670
 
671 671
 		try {
672
-			$node = $userRoot->get('/files_trashbin/files/' . $file);
672
+			$node = $userRoot->get('/files_trashbin/files/'.$file);
673 673
 		} catch (NotFoundException $e) {
674 674
 			return $size;
675 675
 		}
676 676
 
677 677
 		if ($node instanceof Folder) {
678
-			$size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
678
+			$size += self::calculateSize(new View('/'.$user.'/files_trashbin/files/'.$file));
679 679
 		} elseif ($node instanceof File) {
680
-			$size += $view->filesize('/files_trashbin/files/' . $file);
680
+			$size += $view->filesize('/files_trashbin/files/'.$file);
681 681
 		}
682 682
 
683
-		self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
683
+		self::emitTrashbinPreDelete('/files_trashbin/files/'.$file);
684 684
 		$node->delete();
685
-		self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
685
+		self::emitTrashbinPostDelete('/files_trashbin/files/'.$file);
686 686
 
687 687
 		return $size;
688 688
 	}
@@ -698,17 +698,17 @@  discard block
 block discarded – undo
698 698
 	private static function deleteVersions(View $view, $file, $filename, $timestamp, $user) {
699 699
 		$size = 0;
700 700
 		if (\OCP\App::isEnabled('files_versions')) {
701
-			if ($view->is_dir('files_trashbin/versions/' . $file)) {
702
-				$size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
703
-				$view->unlink('files_trashbin/versions/' . $file);
701
+			if ($view->is_dir('files_trashbin/versions/'.$file)) {
702
+				$size += self::calculateSize(new View('/'.$user.'/files_trashbin/versions/'.$file));
703
+				$view->unlink('files_trashbin/versions/'.$file);
704 704
 			} elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
705 705
 				foreach ($versions as $v) {
706 706
 					if ($timestamp) {
707
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
708
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
707
+						$size += $view->filesize('/files_trashbin/versions/'.$filename.'.v'.$v.'.d'.$timestamp);
708
+						$view->unlink('/files_trashbin/versions/'.$filename.'.v'.$v.'.d'.$timestamp);
709 709
 					} else {
710
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
711
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
710
+						$size += $view->filesize('/files_trashbin/versions/'.$filename.'.v'.$v);
711
+						$view->unlink('/files_trashbin/versions/'.$filename.'.v'.$v);
712 712
 					}
713 713
 				}
714 714
 			}
@@ -725,13 +725,13 @@  discard block
 block discarded – undo
725 725
 	 */
726 726
 	public static function file_exists($filename, $timestamp = null) {
727 727
 		$user = User::getUser();
728
-		$view = new View('/' . $user);
728
+		$view = new View('/'.$user);
729 729
 
730 730
 		if ($timestamp) {
731
-			$filename = $filename . '.d' . $timestamp;
731
+			$filename = $filename.'.d'.$timestamp;
732 732
 		}
733 733
 
734
-		$target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
734
+		$target = Filesystem::normalizePath('files_trashbin/files/'.$filename);
735 735
 		return $view->file_exists($target);
736 736
 	}
737 737
 
@@ -757,11 +757,11 @@  discard block
 block discarded – undo
757 757
 	 */
758 758
 	private static function calculateFreeSpace($trashbinSize, $user) {
759 759
 		$config = \OC::$server->getConfig();
760
-		$userTrashbinSize = (int)$config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
760
+		$userTrashbinSize = (int) $config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
761 761
 		if ($userTrashbinSize > -1) {
762 762
 			return $userTrashbinSize - $trashbinSize;
763 763
 		}
764
-		$systemTrashbinSize = (int)$config->getAppValue('files_trashbin', 'trashbin_size', '-1');
764
+		$systemTrashbinSize = (int) $config->getAppValue('files_trashbin', 'trashbin_size', '-1');
765 765
 		if ($systemTrashbinSize > -1) {
766 766
 			return $systemTrashbinSize - $trashbinSize;
767 767
 		}
@@ -870,7 +870,7 @@  discard block
 block discarded – undo
870 870
 			foreach ($files as $file) {
871 871
 				if ($availableSpace < 0 && $expiration->isExpired($file['mtime'], true)) {
872 872
 					$tmp = self::delete($file['name'], $user, $file['mtime']);
873
-					\OC::$server->getLogger()->info('remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota)', ['app' => 'files_trashbin']);
873
+					\OC::$server->getLogger()->info('remove "'.$file['name'].'" ('.$tmp.'B) to meet the limit of trash bin size (50% of available quota)', ['app' => 'files_trashbin']);
874 874
 					$availableSpace += $tmp;
875 875
 					$size += $tmp;
876 876
 				} else {
@@ -901,10 +901,10 @@  discard block
 block discarded – undo
901 901
 					$size += self::delete($filename, $user, $timestamp);
902 902
 					$count++;
903 903
 				} catch (\OCP\Files\NotPermittedException $e) {
904
-					\OC::$server->getLogger()->logException($e, ['app' => 'files_trashbin', 'level' => \OCP\ILogger::WARN, 'message' => 'Removing "' . $filename . '" from trashbin failed.']);
904
+					\OC::$server->getLogger()->logException($e, ['app' => 'files_trashbin', 'level' => \OCP\ILogger::WARN, 'message' => 'Removing "'.$filename.'" from trashbin failed.']);
905 905
 				}
906 906
 				\OC::$server->getLogger()->info(
907
-					'Remove "' . $filename . '" from trashbin because it exceeds max retention obligation term.',
907
+					'Remove "'.$filename.'" from trashbin because it exceeds max retention obligation term.',
908 908
 					['app' => 'files_trashbin']
909 909
 				);
910 910
 			} else {
@@ -930,16 +930,16 @@  discard block
 block discarded – undo
930 930
 			$view->mkdir($destination);
931 931
 			$view->touch($destination, $view->filemtime($source));
932 932
 			foreach ($view->getDirectoryContent($source) as $i) {
933
-				$pathDir = $source . '/' . $i['name'];
933
+				$pathDir = $source.'/'.$i['name'];
934 934
 				if ($view->is_dir($pathDir)) {
935
-					$size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
935
+					$size += self::copy_recursive($pathDir, $destination.'/'.$i['name'], $view);
936 936
 				} else {
937 937
 					$size += $view->filesize($pathDir);
938
-					$result = $view->copy($pathDir, $destination . '/' . $i['name']);
938
+					$result = $view->copy($pathDir, $destination.'/'.$i['name']);
939 939
 					if (!$result) {
940 940
 						throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
941 941
 					}
942
-					$view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
942
+					$view->touch($destination.'/'.$i['name'], $view->filemtime($pathDir));
943 943
 				}
944 944
 			}
945 945
 		} else {
@@ -961,11 +961,11 @@  discard block
 block discarded – undo
961 961
 	 * @return array
962 962
 	 */
963 963
 	private static function getVersionsFromTrash($filename, $timestamp, $user) {
964
-		$view = new View('/' . $user . '/files_trashbin/versions');
964
+		$view = new View('/'.$user.'/files_trashbin/versions');
965 965
 		$versions = [];
966 966
 
967 967
 		/** @var \OC\Files\Storage\Storage $storage */
968
-		[$storage,] = $view->resolvePath('/');
968
+		[$storage, ] = $view->resolvePath('/');
969 969
 
970 970
 		//force rescan of versions, local storage may not have updated the cache
971 971
 		if (!self::$scannedVersions) {
@@ -977,7 +977,7 @@  discard block
 block discarded – undo
977 977
 		if ($timestamp) {
978 978
 			// fetch for old versions
979 979
 			$escapedTimestamp = \OC::$server->getDatabaseConnection()->escapeLikeParameter($timestamp);
980
-			$pattern .= '.v%.d' . $escapedTimestamp;
980
+			$pattern .= '.v%.d'.$escapedTimestamp;
981 981
 			$offset = -strlen($escapedTimestamp) - 2;
982 982
 		} else {
983 983
 			$pattern .= '.v%';
@@ -991,7 +991,7 @@  discard block
 block discarded – undo
991 991
 			\OC::$server->getLogger(),
992 992
 			$cache
993 993
 		);
994
-		$normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/'. $filename)), '/');
994
+		$normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/'.$filename)), '/');
995 995
 		$parentId = $cache->getId($normalizedParentPath);
996 996
 		if ($parentId === -1) {
997 997
 			return [];
@@ -1007,7 +1007,7 @@  discard block
 block discarded – undo
1007 1007
 		$result->closeCursor();
1008 1008
 
1009 1009
 		/** @var CacheEntry[] $matches */
1010
-		$matches = array_map(function (array $data) {
1010
+		$matches = array_map(function(array $data) {
1011 1011
 			return Cache::cacheEntryFromData($data, \OC::$server->getMimeTypeLoader());
1012 1012
 		}, $entries);
1013 1013
 
@@ -1037,18 +1037,18 @@  discard block
 block discarded – undo
1037 1037
 		$name = pathinfo($filename, PATHINFO_FILENAME);
1038 1038
 		$l = \OC::$server->getL10N('files_trashbin');
1039 1039
 
1040
-		$location = '/' . trim($location, '/');
1040
+		$location = '/'.trim($location, '/');
1041 1041
 
1042 1042
 		// if extension is not empty we set a dot in front of it
1043 1043
 		if ($ext !== '') {
1044
-			$ext = '.' . $ext;
1044
+			$ext = '.'.$ext;
1045 1045
 		}
1046 1046
 
1047
-		if ($view->file_exists('files' . $location . '/' . $filename)) {
1047
+		if ($view->file_exists('files'.$location.'/'.$filename)) {
1048 1048
 			$i = 2;
1049
-			$uniqueName = $name . " (" . $l->t("restored") . ")" . $ext;
1050
-			while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
1051
-				$uniqueName = $name . " (" . $l->t("restored") . " " . $i . ")" . $ext;
1049
+			$uniqueName = $name." (".$l->t("restored").")".$ext;
1050
+			while ($view->file_exists('files'.$location.'/'.$uniqueName)) {
1051
+				$uniqueName = $name." (".$l->t("restored")." ".$i.")".$ext;
1052 1052
 				$i++;
1053 1053
 			}
1054 1054
 
@@ -1065,7 +1065,7 @@  discard block
 block discarded – undo
1065 1065
 	 * @return integer size of the folder
1066 1066
 	 */
1067 1067
 	private static function calculateSize($view) {
1068
-		$root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
1068
+		$root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data').$view->getAbsolutePath('');
1069 1069
 		if (!file_exists($root)) {
1070 1070
 			return 0;
1071 1071
 		}
@@ -1096,7 +1096,7 @@  discard block
 block discarded – undo
1096 1096
 	 * @return integer trash bin size
1097 1097
 	 */
1098 1098
 	private static function getTrashbinSize($user) {
1099
-		$view = new View('/' . $user);
1099
+		$view = new View('/'.$user);
1100 1100
 		$fileInfo = $view->getFileInfo('/files_trashbin');
1101 1101
 		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
1102 1102
 	}
@@ -1108,7 +1108,7 @@  discard block
 block discarded – undo
1108 1108
 	 * @return bool
1109 1109
 	 */
1110 1110
 	public static function isEmpty($user) {
1111
-		$view = new View('/' . $user . '/files_trashbin');
1111
+		$view = new View('/'.$user.'/files_trashbin');
1112 1112
 		if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
1113 1113
 			while ($file = readdir($dh)) {
1114 1114
 				if (!Filesystem::isIgnoredDir($file)) {
Please login to merge, or discard this patch.