Passed
Push — master ( aeb32e...81302f )
by Christoph
15:20 queued 10s
created
core/Command/Db/AddMissingColumns.php 1 patch
Indentation   +58 added lines, -58 removed lines patch added patch discarded remove patch
@@ -45,62 +45,62 @@
 block discarded – undo
45 45
  */
46 46
 class AddMissingColumns extends Command {
47 47
 
48
-	/** @var Connection */
49
-	private $connection;
50
-
51
-	/** @var EventDispatcherInterface */
52
-	private $dispatcher;
53
-
54
-	public function __construct(Connection $connection, EventDispatcherInterface $dispatcher) {
55
-		parent::__construct();
56
-
57
-		$this->connection = $connection;
58
-		$this->dispatcher = $dispatcher;
59
-	}
60
-
61
-	protected function configure() {
62
-		$this
63
-			->setName('db:add-missing-columns')
64
-			->setDescription('Add missing optional columns to the database tables');
65
-	}
66
-
67
-	protected function execute(InputInterface $input, OutputInterface $output): int {
68
-		$this->addCoreColumns($output);
69
-
70
-		// Dispatch event so apps can also update columns if needed
71
-		$event = new GenericEvent($output);
72
-		$this->dispatcher->dispatch(IDBConnection::ADD_MISSING_COLUMNS_EVENT, $event);
73
-		return 0;
74
-	}
75
-
76
-	/**
77
-	 * add missing indices to the share table
78
-	 *
79
-	 * @param OutputInterface $output
80
-	 * @throws \Doctrine\DBAL\Schema\SchemaException
81
-	 */
82
-	private function addCoreColumns(OutputInterface $output) {
83
-		$output->writeln('<info>Check columns of the comments table.</info>');
84
-
85
-		$schema = new SchemaWrapper($this->connection);
86
-		$updated = false;
87
-
88
-		if ($schema->hasTable('comments')) {
89
-			$table = $schema->getTable('comments');
90
-			if (!$table->hasColumn('reference_id')) {
91
-				$output->writeln('<info>Adding additional reference_id column to the comments table, this can take some time...</info>');
92
-				$table->addColumn('reference_id', 'string', [
93
-					'notnull' => false,
94
-					'length' => 64,
95
-				]);
96
-				$this->connection->migrateToSchema($schema->getWrappedSchema());
97
-				$updated = true;
98
-				$output->writeln('<info>Comments table updated successfully.</info>');
99
-			}
100
-		}
101
-
102
-		if (!$updated) {
103
-			$output->writeln('<info>Done.</info>');
104
-		}
105
-	}
48
+    /** @var Connection */
49
+    private $connection;
50
+
51
+    /** @var EventDispatcherInterface */
52
+    private $dispatcher;
53
+
54
+    public function __construct(Connection $connection, EventDispatcherInterface $dispatcher) {
55
+        parent::__construct();
56
+
57
+        $this->connection = $connection;
58
+        $this->dispatcher = $dispatcher;
59
+    }
60
+
61
+    protected function configure() {
62
+        $this
63
+            ->setName('db:add-missing-columns')
64
+            ->setDescription('Add missing optional columns to the database tables');
65
+    }
66
+
67
+    protected function execute(InputInterface $input, OutputInterface $output): int {
68
+        $this->addCoreColumns($output);
69
+
70
+        // Dispatch event so apps can also update columns if needed
71
+        $event = new GenericEvent($output);
72
+        $this->dispatcher->dispatch(IDBConnection::ADD_MISSING_COLUMNS_EVENT, $event);
73
+        return 0;
74
+    }
75
+
76
+    /**
77
+     * add missing indices to the share table
78
+     *
79
+     * @param OutputInterface $output
80
+     * @throws \Doctrine\DBAL\Schema\SchemaException
81
+     */
82
+    private function addCoreColumns(OutputInterface $output) {
83
+        $output->writeln('<info>Check columns of the comments table.</info>');
84
+
85
+        $schema = new SchemaWrapper($this->connection);
86
+        $updated = false;
87
+
88
+        if ($schema->hasTable('comments')) {
89
+            $table = $schema->getTable('comments');
90
+            if (!$table->hasColumn('reference_id')) {
91
+                $output->writeln('<info>Adding additional reference_id column to the comments table, this can take some time...</info>');
92
+                $table->addColumn('reference_id', 'string', [
93
+                    'notnull' => false,
94
+                    'length' => 64,
95
+                ]);
96
+                $this->connection->migrateToSchema($schema->getWrappedSchema());
97
+                $updated = true;
98
+                $output->writeln('<info>Comments table updated successfully.</info>');
99
+            }
100
+        }
101
+
102
+        if (!$updated) {
103
+            $output->writeln('<info>Done.</info>');
104
+        }
105
+    }
106 106
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Mapping/AbstractMapping.php 2 patches
Indentation   +335 added lines, -335 removed lines patch added patch discarded remove patch
@@ -37,367 +37,367 @@
 block discarded – undo
37 37
  * @package OCA\User_LDAP\Mapping
38 38
  */
39 39
 abstract class AbstractMapping {
40
-	/**
41
-	 * @var \OCP\IDBConnection $dbc
42
-	 */
43
-	protected $dbc;
44
-
45
-	/**
46
-	 * returns the DB table name which holds the mappings
47
-	 *
48
-	 * @return string
49
-	 */
50
-	abstract protected function getTableName(bool $includePrefix = true);
51
-
52
-	/**
53
-	 * @param \OCP\IDBConnection $dbc
54
-	 */
55
-	public function __construct(\OCP\IDBConnection $dbc) {
56
-		$this->dbc = $dbc;
57
-	}
58
-
59
-	/** @var array caches Names (value) by DN (key) */
60
-	protected $cache = [];
61
-
62
-	/**
63
-	 * checks whether a provided string represents an existing table col
64
-	 *
65
-	 * @param string $col
66
-	 * @return bool
67
-	 */
68
-	public function isColNameValid($col) {
69
-		switch ($col) {
70
-			case 'ldap_dn':
71
-			case 'owncloud_name':
72
-			case 'directory_uuid':
73
-				return true;
74
-			default:
75
-				return false;
76
-		}
77
-	}
78
-
79
-	/**
80
-	 * Gets the value of one column based on a provided value of another column
81
-	 *
82
-	 * @param string $fetchCol
83
-	 * @param string $compareCol
84
-	 * @param string $search
85
-	 * @return string|false
86
-	 * @throws \Exception
87
-	 */
88
-	protected function getXbyY($fetchCol, $compareCol, $search) {
89
-		if (!$this->isColNameValid($fetchCol)) {
90
-			//this is used internally only, but we don't want to risk
91
-			//having SQL injection at all.
92
-			throw new \Exception('Invalid Column Name');
93
-		}
94
-		$query = $this->dbc->prepare('
40
+    /**
41
+     * @var \OCP\IDBConnection $dbc
42
+     */
43
+    protected $dbc;
44
+
45
+    /**
46
+     * returns the DB table name which holds the mappings
47
+     *
48
+     * @return string
49
+     */
50
+    abstract protected function getTableName(bool $includePrefix = true);
51
+
52
+    /**
53
+     * @param \OCP\IDBConnection $dbc
54
+     */
55
+    public function __construct(\OCP\IDBConnection $dbc) {
56
+        $this->dbc = $dbc;
57
+    }
58
+
59
+    /** @var array caches Names (value) by DN (key) */
60
+    protected $cache = [];
61
+
62
+    /**
63
+     * checks whether a provided string represents an existing table col
64
+     *
65
+     * @param string $col
66
+     * @return bool
67
+     */
68
+    public function isColNameValid($col) {
69
+        switch ($col) {
70
+            case 'ldap_dn':
71
+            case 'owncloud_name':
72
+            case 'directory_uuid':
73
+                return true;
74
+            default:
75
+                return false;
76
+        }
77
+    }
78
+
79
+    /**
80
+     * Gets the value of one column based on a provided value of another column
81
+     *
82
+     * @param string $fetchCol
83
+     * @param string $compareCol
84
+     * @param string $search
85
+     * @return string|false
86
+     * @throws \Exception
87
+     */
88
+    protected function getXbyY($fetchCol, $compareCol, $search) {
89
+        if (!$this->isColNameValid($fetchCol)) {
90
+            //this is used internally only, but we don't want to risk
91
+            //having SQL injection at all.
92
+            throw new \Exception('Invalid Column Name');
93
+        }
94
+        $query = $this->dbc->prepare('
95 95
 			SELECT `' . $fetchCol . '`
96 96
 			FROM `' . $this->getTableName() . '`
97 97
 			WHERE `' . $compareCol . '` = ?
98 98
 		');
99 99
 
100
-		try {
101
-			$res = $query->execute([$search]);
102
-			$data = $res->fetchOne();
103
-			$res->closeCursor();
104
-			return $data;
105
-		} catch (Exception $e) {
106
-			return false;
107
-		}
108
-	}
109
-
110
-	/**
111
-	 * Performs a DELETE or UPDATE query to the database.
112
-	 *
113
-	 * @param IPreparedStatement $statement
114
-	 * @param array $parameters
115
-	 * @return bool true if at least one row was modified, false otherwise
116
-	 */
117
-	protected function modify(IPreparedStatement $statement, $parameters) {
118
-		try {
119
-			$result = $statement->execute($parameters);
120
-			$updated = $result->rowCount() > 0;
121
-			$result->closeCursor();
122
-			return $updated;
123
-		} catch (Exception $e) {
124
-			return false;
125
-		}
126
-	}
127
-
128
-	/**
129
-	 * Gets the LDAP DN based on the provided name.
130
-	 * Replaces Access::ocname2dn
131
-	 *
132
-	 * @param string $name
133
-	 * @return string|false
134
-	 */
135
-	public function getDNByName($name) {
136
-		$dn = array_search($name, $this->cache);
137
-		if ($dn === false && ($dn = $this->getXbyY('ldap_dn', 'owncloud_name', $name)) !== false) {
138
-			$this->cache[$dn] = $name;
139
-		}
140
-		return $dn;
141
-	}
142
-
143
-	/**
144
-	 * Updates the DN based on the given UUID
145
-	 *
146
-	 * @param string $fdn
147
-	 * @param string $uuid
148
-	 * @return bool
149
-	 */
150
-	public function setDNbyUUID($fdn, $uuid) {
151
-		$oldDn = $this->getDnByUUID($uuid);
152
-		$statement = $this->dbc->prepare('
100
+        try {
101
+            $res = $query->execute([$search]);
102
+            $data = $res->fetchOne();
103
+            $res->closeCursor();
104
+            return $data;
105
+        } catch (Exception $e) {
106
+            return false;
107
+        }
108
+    }
109
+
110
+    /**
111
+     * Performs a DELETE or UPDATE query to the database.
112
+     *
113
+     * @param IPreparedStatement $statement
114
+     * @param array $parameters
115
+     * @return bool true if at least one row was modified, false otherwise
116
+     */
117
+    protected function modify(IPreparedStatement $statement, $parameters) {
118
+        try {
119
+            $result = $statement->execute($parameters);
120
+            $updated = $result->rowCount() > 0;
121
+            $result->closeCursor();
122
+            return $updated;
123
+        } catch (Exception $e) {
124
+            return false;
125
+        }
126
+    }
127
+
128
+    /**
129
+     * Gets the LDAP DN based on the provided name.
130
+     * Replaces Access::ocname2dn
131
+     *
132
+     * @param string $name
133
+     * @return string|false
134
+     */
135
+    public function getDNByName($name) {
136
+        $dn = array_search($name, $this->cache);
137
+        if ($dn === false && ($dn = $this->getXbyY('ldap_dn', 'owncloud_name', $name)) !== false) {
138
+            $this->cache[$dn] = $name;
139
+        }
140
+        return $dn;
141
+    }
142
+
143
+    /**
144
+     * Updates the DN based on the given UUID
145
+     *
146
+     * @param string $fdn
147
+     * @param string $uuid
148
+     * @return bool
149
+     */
150
+    public function setDNbyUUID($fdn, $uuid) {
151
+        $oldDn = $this->getDnByUUID($uuid);
152
+        $statement = $this->dbc->prepare('
153 153
 			UPDATE `' . $this->getTableName() . '`
154 154
 			SET `ldap_dn` = ?
155 155
 			WHERE `directory_uuid` = ?
156 156
 		');
157 157
 
158
-		$r = $this->modify($statement, [$fdn, $uuid]);
159
-
160
-		if ($r && is_string($oldDn) && isset($this->cache[$oldDn])) {
161
-			$this->cache[$fdn] = $this->cache[$oldDn];
162
-			unset($this->cache[$oldDn]);
163
-		}
164
-
165
-		return $r;
166
-	}
167
-
168
-	/**
169
-	 * Updates the UUID based on the given DN
170
-	 *
171
-	 * required by Migration/UUIDFix
172
-	 *
173
-	 * @param $uuid
174
-	 * @param $fdn
175
-	 * @return bool
176
-	 */
177
-	public function setUUIDbyDN($uuid, $fdn) {
178
-		$statement = $this->dbc->prepare('
158
+        $r = $this->modify($statement, [$fdn, $uuid]);
159
+
160
+        if ($r && is_string($oldDn) && isset($this->cache[$oldDn])) {
161
+            $this->cache[$fdn] = $this->cache[$oldDn];
162
+            unset($this->cache[$oldDn]);
163
+        }
164
+
165
+        return $r;
166
+    }
167
+
168
+    /**
169
+     * Updates the UUID based on the given DN
170
+     *
171
+     * required by Migration/UUIDFix
172
+     *
173
+     * @param $uuid
174
+     * @param $fdn
175
+     * @return bool
176
+     */
177
+    public function setUUIDbyDN($uuid, $fdn) {
178
+        $statement = $this->dbc->prepare('
179 179
 			UPDATE `' . $this->getTableName() . '`
180 180
 			SET `directory_uuid` = ?
181 181
 			WHERE `ldap_dn` = ?
182 182
 		');
183 183
 
184
-		unset($this->cache[$fdn]);
185
-
186
-		return $this->modify($statement, [$uuid, $fdn]);
187
-	}
188
-
189
-	/**
190
-	 * Gets the name based on the provided LDAP DN.
191
-	 *
192
-	 * @param string $fdn
193
-	 * @return string|false
194
-	 */
195
-	public function getNameByDN($fdn) {
196
-		if (!isset($this->cache[$fdn])) {
197
-			$this->cache[$fdn] = $this->getXbyY('owncloud_name', 'ldap_dn', $fdn);
198
-		}
199
-		return $this->cache[$fdn];
200
-	}
201
-
202
-	public function getListOfIdsByDn(array $fdns): array {
203
-		$qb = $this->dbc->getQueryBuilder();
204
-		$qb->select('owncloud_name', 'ldap_dn')
205
-			->from($this->getTableName(false))
206
-			->where($qb->expr()->in('ldap_dn', $qb->createNamedParameter($fdns, QueryBuilder::PARAM_STR_ARRAY)));
207
-		$stmt = $qb->execute();
208
-
209
-		$results = $stmt->fetchAll(\Doctrine\DBAL\FetchMode::ASSOCIATIVE);
210
-		foreach ($results as $key => $entry) {
211
-			unset($results[$key]);
212
-			$results[$entry['ldap_dn']] = $entry['owncloud_name'];
213
-			$this->cache[$entry['ldap_dn']] = $entry['owncloud_name'];
214
-		}
215
-
216
-		return $results;
217
-	}
218
-
219
-	/**
220
-	 * Searches mapped names by the giving string in the name column
221
-	 *
222
-	 * @param string $search
223
-	 * @param string $prefixMatch
224
-	 * @param string $postfixMatch
225
-	 * @return string[]
226
-	 */
227
-	public function getNamesBySearch($search, $prefixMatch = "", $postfixMatch = "") {
228
-		$statement = $this->dbc->prepare('
184
+        unset($this->cache[$fdn]);
185
+
186
+        return $this->modify($statement, [$uuid, $fdn]);
187
+    }
188
+
189
+    /**
190
+     * Gets the name based on the provided LDAP DN.
191
+     *
192
+     * @param string $fdn
193
+     * @return string|false
194
+     */
195
+    public function getNameByDN($fdn) {
196
+        if (!isset($this->cache[$fdn])) {
197
+            $this->cache[$fdn] = $this->getXbyY('owncloud_name', 'ldap_dn', $fdn);
198
+        }
199
+        return $this->cache[$fdn];
200
+    }
201
+
202
+    public function getListOfIdsByDn(array $fdns): array {
203
+        $qb = $this->dbc->getQueryBuilder();
204
+        $qb->select('owncloud_name', 'ldap_dn')
205
+            ->from($this->getTableName(false))
206
+            ->where($qb->expr()->in('ldap_dn', $qb->createNamedParameter($fdns, QueryBuilder::PARAM_STR_ARRAY)));
207
+        $stmt = $qb->execute();
208
+
209
+        $results = $stmt->fetchAll(\Doctrine\DBAL\FetchMode::ASSOCIATIVE);
210
+        foreach ($results as $key => $entry) {
211
+            unset($results[$key]);
212
+            $results[$entry['ldap_dn']] = $entry['owncloud_name'];
213
+            $this->cache[$entry['ldap_dn']] = $entry['owncloud_name'];
214
+        }
215
+
216
+        return $results;
217
+    }
218
+
219
+    /**
220
+     * Searches mapped names by the giving string in the name column
221
+     *
222
+     * @param string $search
223
+     * @param string $prefixMatch
224
+     * @param string $postfixMatch
225
+     * @return string[]
226
+     */
227
+    public function getNamesBySearch($search, $prefixMatch = "", $postfixMatch = "") {
228
+        $statement = $this->dbc->prepare('
229 229
 			SELECT `owncloud_name`
230 230
 			FROM `' . $this->getTableName() . '`
231 231
 			WHERE `owncloud_name` LIKE ?
232 232
 		');
233 233
 
234
-		try {
235
-			$res = $statement->execute([$prefixMatch . $this->dbc->escapeLikeParameter($search) . $postfixMatch]);
236
-		} catch (Exception $e) {
237
-			return [];
238
-		}
239
-		$names = [];
240
-		while ($row = $res->fetch()) {
241
-			$names[] = $row['owncloud_name'];
242
-		}
243
-		return $names;
244
-	}
245
-
246
-	/**
247
-	 * Gets the name based on the provided LDAP UUID.
248
-	 *
249
-	 * @param string $uuid
250
-	 * @return string|false
251
-	 */
252
-	public function getNameByUUID($uuid) {
253
-		return $this->getXbyY('owncloud_name', 'directory_uuid', $uuid);
254
-	}
255
-
256
-	public function getDnByUUID($uuid) {
257
-		return $this->getXbyY('ldap_dn', 'directory_uuid', $uuid);
258
-	}
259
-
260
-	/**
261
-	 * Gets the UUID based on the provided LDAP DN
262
-	 *
263
-	 * @param string $dn
264
-	 * @return false|string
265
-	 * @throws \Exception
266
-	 */
267
-	public function getUUIDByDN($dn) {
268
-		return $this->getXbyY('directory_uuid', 'ldap_dn', $dn);
269
-	}
270
-
271
-	/**
272
-	 * gets a piece of the mapping list
273
-	 *
274
-	 * @param int $offset
275
-	 * @param int $limit
276
-	 * @return array
277
-	 */
278
-	public function getList($offset = null, $limit = null) {
279
-		$query = $this->dbc->prepare('
234
+        try {
235
+            $res = $statement->execute([$prefixMatch . $this->dbc->escapeLikeParameter($search) . $postfixMatch]);
236
+        } catch (Exception $e) {
237
+            return [];
238
+        }
239
+        $names = [];
240
+        while ($row = $res->fetch()) {
241
+            $names[] = $row['owncloud_name'];
242
+        }
243
+        return $names;
244
+    }
245
+
246
+    /**
247
+     * Gets the name based on the provided LDAP UUID.
248
+     *
249
+     * @param string $uuid
250
+     * @return string|false
251
+     */
252
+    public function getNameByUUID($uuid) {
253
+        return $this->getXbyY('owncloud_name', 'directory_uuid', $uuid);
254
+    }
255
+
256
+    public function getDnByUUID($uuid) {
257
+        return $this->getXbyY('ldap_dn', 'directory_uuid', $uuid);
258
+    }
259
+
260
+    /**
261
+     * Gets the UUID based on the provided LDAP DN
262
+     *
263
+     * @param string $dn
264
+     * @return false|string
265
+     * @throws \Exception
266
+     */
267
+    public function getUUIDByDN($dn) {
268
+        return $this->getXbyY('directory_uuid', 'ldap_dn', $dn);
269
+    }
270
+
271
+    /**
272
+     * gets a piece of the mapping list
273
+     *
274
+     * @param int $offset
275
+     * @param int $limit
276
+     * @return array
277
+     */
278
+    public function getList($offset = null, $limit = null) {
279
+        $query = $this->dbc->prepare('
280 280
 			SELECT
281 281
 				`ldap_dn` AS `dn`,
282 282
 				`owncloud_name` AS `name`,
283 283
 				`directory_uuid` AS `uuid`
284 284
 			FROM `' . $this->getTableName() . '`',
285
-			$limit,
286
-			$offset
287
-		);
288
-
289
-		$query->execute();
290
-		return $query->fetchAll();
291
-	}
292
-
293
-	/**
294
-	 * attempts to map the given entry
295
-	 *
296
-	 * @param string $fdn fully distinguished name (from LDAP)
297
-	 * @param string $name
298
-	 * @param string $uuid a unique identifier as used in LDAP
299
-	 * @return bool
300
-	 */
301
-	public function map($fdn, $name, $uuid) {
302
-		if (mb_strlen($fdn) > 255) {
303
-			\OC::$server->getLogger()->error(
304
-				'Cannot map, because the DN exceeds 255 characters: {dn}',
305
-				[
306
-					'app' => 'user_ldap',
307
-					'dn' => $fdn,
308
-				]
309
-			);
310
-			return false;
311
-		}
312
-
313
-		$row = [
314
-			'ldap_dn' => $fdn,
315
-			'owncloud_name' => $name,
316
-			'directory_uuid' => $uuid
317
-		];
318
-
319
-		try {
320
-			$result = $this->dbc->insertIfNotExist($this->getTableName(), $row);
321
-			if ((bool)$result === true) {
322
-				$this->cache[$fdn] = $name;
323
-			}
324
-			// insertIfNotExist returns values as int
325
-			return (bool)$result;
326
-		} catch (\Exception $e) {
327
-			return false;
328
-		}
329
-	}
330
-
331
-	/**
332
-	 * removes a mapping based on the owncloud_name of the entry
333
-	 *
334
-	 * @param string $name
335
-	 * @return bool
336
-	 */
337
-	public function unmap($name) {
338
-		$statement = $this->dbc->prepare('
285
+            $limit,
286
+            $offset
287
+        );
288
+
289
+        $query->execute();
290
+        return $query->fetchAll();
291
+    }
292
+
293
+    /**
294
+     * attempts to map the given entry
295
+     *
296
+     * @param string $fdn fully distinguished name (from LDAP)
297
+     * @param string $name
298
+     * @param string $uuid a unique identifier as used in LDAP
299
+     * @return bool
300
+     */
301
+    public function map($fdn, $name, $uuid) {
302
+        if (mb_strlen($fdn) > 255) {
303
+            \OC::$server->getLogger()->error(
304
+                'Cannot map, because the DN exceeds 255 characters: {dn}',
305
+                [
306
+                    'app' => 'user_ldap',
307
+                    'dn' => $fdn,
308
+                ]
309
+            );
310
+            return false;
311
+        }
312
+
313
+        $row = [
314
+            'ldap_dn' => $fdn,
315
+            'owncloud_name' => $name,
316
+            'directory_uuid' => $uuid
317
+        ];
318
+
319
+        try {
320
+            $result = $this->dbc->insertIfNotExist($this->getTableName(), $row);
321
+            if ((bool)$result === true) {
322
+                $this->cache[$fdn] = $name;
323
+            }
324
+            // insertIfNotExist returns values as int
325
+            return (bool)$result;
326
+        } catch (\Exception $e) {
327
+            return false;
328
+        }
329
+    }
330
+
331
+    /**
332
+     * removes a mapping based on the owncloud_name of the entry
333
+     *
334
+     * @param string $name
335
+     * @return bool
336
+     */
337
+    public function unmap($name) {
338
+        $statement = $this->dbc->prepare('
339 339
 			DELETE FROM `' . $this->getTableName() . '`
340 340
 			WHERE `owncloud_name` = ?');
341 341
 
342
-		return $this->modify($statement, [$name]);
343
-	}
344
-
345
-	/**
346
-	 * Truncates the mapping table
347
-	 *
348
-	 * @return bool
349
-	 */
350
-	public function clear() {
351
-		$sql = $this->dbc
352
-			->getDatabasePlatform()
353
-			->getTruncateTableSQL('`' . $this->getTableName() . '`');
354
-		try {
355
-			$this->dbc->executeQuery($sql);
356
-
357
-			return true;
358
-		} catch (Exception $e) {
359
-			return false;
360
-		}
361
-	}
362
-
363
-	/**
364
-	 * clears the mapping table one by one and executing a callback with
365
-	 * each row's id (=owncloud_name col)
366
-	 *
367
-	 * @param callable $preCallback
368
-	 * @param callable $postCallback
369
-	 * @return bool true on success, false when at least one row was not
370
-	 * deleted
371
-	 */
372
-	public function clearCb(callable $preCallback, callable $postCallback): bool {
373
-		$picker = $this->dbc->getQueryBuilder();
374
-		$picker->select('owncloud_name')
375
-			->from($this->getTableName());
376
-		$cursor = $picker->execute();
377
-		$result = true;
378
-		while ($id = $cursor->fetchOne()) {
379
-			$preCallback($id);
380
-			if ($isUnmapped = $this->unmap($id)) {
381
-				$postCallback($id);
382
-			}
383
-			$result &= $isUnmapped;
384
-		}
385
-		$cursor->closeCursor();
386
-		return $result;
387
-	}
388
-
389
-	/**
390
-	 * returns the number of entries in the mappings table
391
-	 *
392
-	 * @return int
393
-	 */
394
-	public function count() {
395
-		$qb = $this->dbc->getQueryBuilder();
396
-		$query = $qb->select($qb->func()->count('ldap_dn'))
397
-			->from($this->getTableName());
398
-		$res = $query->execute();
399
-		$count = $res->fetchOne();
400
-		$res->closeCursor();
401
-		return (int)$count;
402
-	}
342
+        return $this->modify($statement, [$name]);
343
+    }
344
+
345
+    /**
346
+     * Truncates the mapping table
347
+     *
348
+     * @return bool
349
+     */
350
+    public function clear() {
351
+        $sql = $this->dbc
352
+            ->getDatabasePlatform()
353
+            ->getTruncateTableSQL('`' . $this->getTableName() . '`');
354
+        try {
355
+            $this->dbc->executeQuery($sql);
356
+
357
+            return true;
358
+        } catch (Exception $e) {
359
+            return false;
360
+        }
361
+    }
362
+
363
+    /**
364
+     * clears the mapping table one by one and executing a callback with
365
+     * each row's id (=owncloud_name col)
366
+     *
367
+     * @param callable $preCallback
368
+     * @param callable $postCallback
369
+     * @return bool true on success, false when at least one row was not
370
+     * deleted
371
+     */
372
+    public function clearCb(callable $preCallback, callable $postCallback): bool {
373
+        $picker = $this->dbc->getQueryBuilder();
374
+        $picker->select('owncloud_name')
375
+            ->from($this->getTableName());
376
+        $cursor = $picker->execute();
377
+        $result = true;
378
+        while ($id = $cursor->fetchOne()) {
379
+            $preCallback($id);
380
+            if ($isUnmapped = $this->unmap($id)) {
381
+                $postCallback($id);
382
+            }
383
+            $result &= $isUnmapped;
384
+        }
385
+        $cursor->closeCursor();
386
+        return $result;
387
+    }
388
+
389
+    /**
390
+     * returns the number of entries in the mappings table
391
+     *
392
+     * @return int
393
+     */
394
+    public function count() {
395
+        $qb = $this->dbc->getQueryBuilder();
396
+        $query = $qb->select($qb->func()->count('ldap_dn'))
397
+            ->from($this->getTableName());
398
+        $res = $query->execute();
399
+        $count = $res->fetchOne();
400
+        $res->closeCursor();
401
+        return (int)$count;
402
+    }
403 403
 }
Please login to merge, or discard this patch.
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -92,9 +92,9 @@  discard block
 block discarded – undo
92 92
 			throw new \Exception('Invalid Column Name');
93 93
 		}
94 94
 		$query = $this->dbc->prepare('
95
-			SELECT `' . $fetchCol . '`
96
-			FROM `' . $this->getTableName() . '`
97
-			WHERE `' . $compareCol . '` = ?
95
+			SELECT `' . $fetchCol.'`
96
+			FROM `' . $this->getTableName().'`
97
+			WHERE `' . $compareCol.'` = ?
98 98
 		');
99 99
 
100 100
 		try {
@@ -150,7 +150,7 @@  discard block
 block discarded – undo
150 150
 	public function setDNbyUUID($fdn, $uuid) {
151 151
 		$oldDn = $this->getDnByUUID($uuid);
152 152
 		$statement = $this->dbc->prepare('
153
-			UPDATE `' . $this->getTableName() . '`
153
+			UPDATE `' . $this->getTableName().'`
154 154
 			SET `ldap_dn` = ?
155 155
 			WHERE `directory_uuid` = ?
156 156
 		');
@@ -176,7 +176,7 @@  discard block
 block discarded – undo
176 176
 	 */
177 177
 	public function setUUIDbyDN($uuid, $fdn) {
178 178
 		$statement = $this->dbc->prepare('
179
-			UPDATE `' . $this->getTableName() . '`
179
+			UPDATE `' . $this->getTableName().'`
180 180
 			SET `directory_uuid` = ?
181 181
 			WHERE `ldap_dn` = ?
182 182
 		');
@@ -227,12 +227,12 @@  discard block
 block discarded – undo
227 227
 	public function getNamesBySearch($search, $prefixMatch = "", $postfixMatch = "") {
228 228
 		$statement = $this->dbc->prepare('
229 229
 			SELECT `owncloud_name`
230
-			FROM `' . $this->getTableName() . '`
230
+			FROM `' . $this->getTableName().'`
231 231
 			WHERE `owncloud_name` LIKE ?
232 232
 		');
233 233
 
234 234
 		try {
235
-			$res = $statement->execute([$prefixMatch . $this->dbc->escapeLikeParameter($search) . $postfixMatch]);
235
+			$res = $statement->execute([$prefixMatch.$this->dbc->escapeLikeParameter($search).$postfixMatch]);
236 236
 		} catch (Exception $e) {
237 237
 			return [];
238 238
 		}
@@ -281,7 +281,7 @@  discard block
 block discarded – undo
281 281
 				`ldap_dn` AS `dn`,
282 282
 				`owncloud_name` AS `name`,
283 283
 				`directory_uuid` AS `uuid`
284
-			FROM `' . $this->getTableName() . '`',
284
+			FROM `' . $this->getTableName().'`',
285 285
 			$limit,
286 286
 			$offset
287 287
 		);
@@ -318,11 +318,11 @@  discard block
 block discarded – undo
318 318
 
319 319
 		try {
320 320
 			$result = $this->dbc->insertIfNotExist($this->getTableName(), $row);
321
-			if ((bool)$result === true) {
321
+			if ((bool) $result === true) {
322 322
 				$this->cache[$fdn] = $name;
323 323
 			}
324 324
 			// insertIfNotExist returns values as int
325
-			return (bool)$result;
325
+			return (bool) $result;
326 326
 		} catch (\Exception $e) {
327 327
 			return false;
328 328
 		}
@@ -336,7 +336,7 @@  discard block
 block discarded – undo
336 336
 	 */
337 337
 	public function unmap($name) {
338 338
 		$statement = $this->dbc->prepare('
339
-			DELETE FROM `' . $this->getTableName() . '`
339
+			DELETE FROM `' . $this->getTableName().'`
340 340
 			WHERE `owncloud_name` = ?');
341 341
 
342 342
 		return $this->modify($statement, [$name]);
@@ -350,7 +350,7 @@  discard block
 block discarded – undo
350 350
 	public function clear() {
351 351
 		$sql = $this->dbc
352 352
 			->getDatabasePlatform()
353
-			->getTruncateTableSQL('`' . $this->getTableName() . '`');
353
+			->getTruncateTableSQL('`'.$this->getTableName().'`');
354 354
 		try {
355 355
 			$this->dbc->executeQuery($sql);
356 356
 
@@ -398,6 +398,6 @@  discard block
 block discarded – undo
398 398
 		$res = $query->execute();
399 399
 		$count = $res->fetchOne();
400 400
 		$res->closeCursor();
401
-		return (int)$count;
401
+		return (int) $count;
402 402
 	}
403 403
 }
Please login to merge, or discard this patch.
apps/contactsinteraction/lib/Db/CardSearchDao.php 1 patch
Indentation   +55 added lines, -55 removed lines patch added patch discarded remove patch
@@ -33,64 +33,64 @@
 block discarded – undo
33 33
 
34 34
 class CardSearchDao {
35 35
 
36
-	/** @var IDBConnection */
37
-	private $db;
36
+    /** @var IDBConnection */
37
+    private $db;
38 38
 
39
-	public function __construct(IDBConnection $db) {
40
-		$this->db = $db;
41
-	}
39
+    public function __construct(IDBConnection $db) {
40
+        $this->db = $db;
41
+    }
42 42
 
43
-	public function findExisting(IUser $user,
44
-								 ?string $uid,
45
-								 ?string $email,
46
-								 ?string $cloudId): ?string {
47
-		$addressbooksQuery = $this->db->getQueryBuilder();
48
-		$cardQuery = $this->db->getQueryBuilder();
49
-		$propQuery = $this->db->getQueryBuilder();
43
+    public function findExisting(IUser $user,
44
+                                 ?string $uid,
45
+                                 ?string $email,
46
+                                 ?string $cloudId): ?string {
47
+        $addressbooksQuery = $this->db->getQueryBuilder();
48
+        $cardQuery = $this->db->getQueryBuilder();
49
+        $propQuery = $this->db->getQueryBuilder();
50 50
 
51
-		$propOr = $propQuery->expr()->orX();
52
-		if ($uid !== null) {
53
-			$propOr->add($propQuery->expr()->andX(
54
-				$propQuery->expr()->eq('name', $cardQuery->createNamedParameter('UID')),
55
-				$propQuery->expr()->eq('value', $cardQuery->createNamedParameter($uid))
56
-			));
57
-		}
58
-		if ($email !== null) {
59
-			$propOr->add($propQuery->expr()->andX(
60
-				$propQuery->expr()->eq('name', $cardQuery->createNamedParameter('EMAIL')),
61
-				$propQuery->expr()->eq('value', $cardQuery->createNamedParameter($email))
62
-			));
63
-		}
64
-		if ($cloudId !== null) {
65
-			$propOr->add($propQuery->expr()->andX(
66
-				$propQuery->expr()->eq('name', $cardQuery->createNamedParameter('CLOUD')),
67
-				$propQuery->expr()->eq('value', $cardQuery->createNamedParameter($cloudId))
68
-			));
69
-		}
70
-		$addressbooksQuery->selectDistinct('id')
71
-			->from('addressbooks')
72
-			->where($addressbooksQuery->expr()->eq('principaluri', $cardQuery->createNamedParameter("principals/users/" . $user->getUID())));
73
-		$propQuery->selectDistinct('cardid')
74
-			->from('cards_properties')
75
-			->where($propQuery->expr()->in('addressbookid', $propQuery->createFunction($addressbooksQuery->getSQL()), IQueryBuilder::PARAM_INT_ARRAY))
76
-			->andWhere($propOr)
77
-			->groupBy('cardid');
78
-		$cardQuery->select('carddata')
79
-			->from('cards')
80
-			->where($cardQuery->expr()->in('id', $cardQuery->createFunction($propQuery->getSQL()), IQueryBuilder::PARAM_INT_ARRAY))
81
-			->andWhere($cardQuery->expr()->in('addressbookid', $cardQuery->createFunction($addressbooksQuery->getSQL()), IQueryBuilder::PARAM_INT_ARRAY))
82
-			->setMaxResults(1);
83
-		$result = $cardQuery->execute();
84
-		/** @var string|resource|false $card */
85
-		$card = $result->fetchOne();
51
+        $propOr = $propQuery->expr()->orX();
52
+        if ($uid !== null) {
53
+            $propOr->add($propQuery->expr()->andX(
54
+                $propQuery->expr()->eq('name', $cardQuery->createNamedParameter('UID')),
55
+                $propQuery->expr()->eq('value', $cardQuery->createNamedParameter($uid))
56
+            ));
57
+        }
58
+        if ($email !== null) {
59
+            $propOr->add($propQuery->expr()->andX(
60
+                $propQuery->expr()->eq('name', $cardQuery->createNamedParameter('EMAIL')),
61
+                $propQuery->expr()->eq('value', $cardQuery->createNamedParameter($email))
62
+            ));
63
+        }
64
+        if ($cloudId !== null) {
65
+            $propOr->add($propQuery->expr()->andX(
66
+                $propQuery->expr()->eq('name', $cardQuery->createNamedParameter('CLOUD')),
67
+                $propQuery->expr()->eq('value', $cardQuery->createNamedParameter($cloudId))
68
+            ));
69
+        }
70
+        $addressbooksQuery->selectDistinct('id')
71
+            ->from('addressbooks')
72
+            ->where($addressbooksQuery->expr()->eq('principaluri', $cardQuery->createNamedParameter("principals/users/" . $user->getUID())));
73
+        $propQuery->selectDistinct('cardid')
74
+            ->from('cards_properties')
75
+            ->where($propQuery->expr()->in('addressbookid', $propQuery->createFunction($addressbooksQuery->getSQL()), IQueryBuilder::PARAM_INT_ARRAY))
76
+            ->andWhere($propOr)
77
+            ->groupBy('cardid');
78
+        $cardQuery->select('carddata')
79
+            ->from('cards')
80
+            ->where($cardQuery->expr()->in('id', $cardQuery->createFunction($propQuery->getSQL()), IQueryBuilder::PARAM_INT_ARRAY))
81
+            ->andWhere($cardQuery->expr()->in('addressbookid', $cardQuery->createFunction($addressbooksQuery->getSQL()), IQueryBuilder::PARAM_INT_ARRAY))
82
+            ->setMaxResults(1);
83
+        $result = $cardQuery->execute();
84
+        /** @var string|resource|false $card */
85
+        $card = $result->fetchOne();
86 86
 
87
-		if ($card === false) {
88
-			return null;
89
-		}
90
-		if (is_resource($card)) {
91
-			return stream_get_contents($card);
92
-		}
87
+        if ($card === false) {
88
+            return null;
89
+        }
90
+        if (is_resource($card)) {
91
+            return stream_get_contents($card);
92
+        }
93 93
 
94
-		return $card;
95
-	}
94
+        return $card;
95
+    }
96 96
 }
Please login to merge, or discard this patch.
apps/dav/lib/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJob.php 2 patches
Indentation   +400 added lines, -400 removed lines patch added patch discarded remove patch
@@ -37,404 +37,404 @@
 block discarded – undo
37 37
 
38 38
 class UpdateCalendarResourcesRoomsBackgroundJob extends TimedJob {
39 39
 
40
-	/** @var IResourceManager */
41
-	private $resourceManager;
42
-
43
-	/** @var IRoomManager */
44
-	private $roomManager;
45
-
46
-	/** @var IDBConnection */
47
-	private $dbConnection;
48
-
49
-	/** @var CalDavBackend */
50
-	private $calDavBackend;
51
-
52
-	/**
53
-	 * UpdateCalendarResourcesRoomsBackgroundJob constructor.
54
-	 *
55
-	 * @param IResourceManager $resourceManager
56
-	 * @param IRoomManager $roomManager
57
-	 * @param IDBConnection $dbConnection
58
-	 * @param CalDavBackend $calDavBackend
59
-	 */
60
-	public function __construct(IResourceManager $resourceManager,
61
-								IRoomManager $roomManager,
62
-								IDBConnection $dbConnection,
63
-								CalDavBackend $calDavBackend) {
64
-		$this->resourceManager = $resourceManager;
65
-		$this->roomManager = $roomManager;
66
-		$this->dbConnection = $dbConnection;
67
-		$this->calDavBackend = $calDavBackend;
68
-
69
-		// run once an hour
70
-		$this->setInterval(60 * 60);
71
-	}
72
-
73
-	/**
74
-	 * @param $argument
75
-	 */
76
-	public function run($argument):void {
77
-		$this->runForBackend(
78
-			$this->resourceManager,
79
-			'calendar_resources',
80
-			'calendar_resources_md',
81
-			'resource_id',
82
-			'principals/calendar-resources'
83
-		);
84
-		$this->runForBackend(
85
-			$this->roomManager,
86
-			'calendar_rooms',
87
-			'calendar_rooms_md',
88
-			'room_id',
89
-			'principals/calendar-rooms'
90
-		);
91
-	}
92
-
93
-	/**
94
-	 * Run background-job for one specific backendManager
95
-	 * either ResourceManager or RoomManager
96
-	 *
97
-	 * @param IResourceManager|IRoomManager $backendManager
98
-	 * @param string $dbTable
99
-	 * @param string $dbTableMetadata
100
-	 * @param string $foreignKey
101
-	 * @param string $principalPrefix
102
-	 */
103
-	private function runForBackend($backendManager,
104
-								   string $dbTable,
105
-								   string $dbTableMetadata,
106
-								   string $foreignKey,
107
-								   string $principalPrefix):void {
108
-		$backends = $backendManager->getBackends();
109
-
110
-		foreach ($backends as $backend) {
111
-			$backendId = $backend->getBackendIdentifier();
112
-
113
-			try {
114
-				if ($backend instanceof IResourceBackend) {
115
-					$list = $backend->listAllResources();
116
-				} else {
117
-					$list = $backend->listAllRooms();
118
-				}
119
-			} catch (BackendTemporarilyUnavailableException $ex) {
120
-				continue;
121
-			}
122
-
123
-			$cachedList = $this->getAllCachedByBackend($dbTable, $backendId);
124
-			$newIds = array_diff($list, $cachedList);
125
-			$deletedIds = array_diff($cachedList, $list);
126
-			$editedIds = array_intersect($list, $cachedList);
127
-
128
-			foreach ($newIds as $newId) {
129
-				try {
130
-					if ($backend instanceof IResourceBackend) {
131
-						$resource = $backend->getResource($newId);
132
-					} else {
133
-						$resource = $backend->getRoom($newId);
134
-					}
135
-
136
-					$metadata = [];
137
-					if ($resource instanceof IMetadataProvider) {
138
-						$metadata = $this->getAllMetadataOfBackend($resource);
139
-					}
140
-				} catch (BackendTemporarilyUnavailableException $ex) {
141
-					continue;
142
-				}
143
-
144
-				$id = $this->addToCache($dbTable, $backendId, $resource);
145
-				$this->addMetadataToCache($dbTableMetadata, $foreignKey, $id, $metadata);
146
-				// we don't create the calendar here, it is created lazily
147
-				// when an event is actually scheduled with this resource / room
148
-			}
149
-
150
-			foreach ($deletedIds as $deletedId) {
151
-				$id = $this->getIdForBackendAndResource($dbTable, $backendId, $deletedId);
152
-				$this->deleteFromCache($dbTable, $id);
153
-				$this->deleteMetadataFromCache($dbTableMetadata, $foreignKey, $id);
154
-
155
-				$principalName = implode('-', [$backendId, $deletedId]);
156
-				$this->deleteCalendarDataForResource($principalPrefix, $principalName);
157
-			}
158
-
159
-			foreach ($editedIds as $editedId) {
160
-				$id = $this->getIdForBackendAndResource($dbTable, $backendId, $editedId);
161
-
162
-				try {
163
-					if ($backend instanceof IResourceBackend) {
164
-						$resource = $backend->getResource($editedId);
165
-					} else {
166
-						$resource = $backend->getRoom($editedId);
167
-					}
168
-
169
-					$metadata = [];
170
-					if ($resource instanceof IMetadataProvider) {
171
-						$metadata = $this->getAllMetadataOfBackend($resource);
172
-					}
173
-				} catch (BackendTemporarilyUnavailableException $ex) {
174
-					continue;
175
-				}
176
-
177
-				$this->updateCache($dbTable, $id, $resource);
178
-
179
-				if ($resource instanceof IMetadataProvider) {
180
-					$cachedMetadata = $this->getAllMetadataOfCache($dbTableMetadata, $foreignKey, $id);
181
-					$this->updateMetadataCache($dbTableMetadata, $foreignKey, $id, $metadata, $cachedMetadata);
182
-				}
183
-			}
184
-		}
185
-	}
186
-
187
-	/**
188
-	 * add entry to cache that exists remotely but not yet in cache
189
-	 *
190
-	 * @param string $table
191
-	 * @param string $backendId
192
-	 * @param IResource|IRoom $remote
193
-	 * @return int Insert id
194
-	 */
195
-	private function addToCache(string $table,
196
-								string $backendId,
197
-								$remote):int {
198
-		$query = $this->dbConnection->getQueryBuilder();
199
-		$query->insert($table)
200
-			->values([
201
-				'backend_id' => $query->createNamedParameter($backendId),
202
-				'resource_id' => $query->createNamedParameter($remote->getId()),
203
-				'email' => $query->createNamedParameter($remote->getEMail()),
204
-				'displayname' => $query->createNamedParameter($remote->getDisplayName()),
205
-				'group_restrictions' => $query->createNamedParameter(
206
-					$this->serializeGroupRestrictions(
207
-						$remote->getGroupRestrictions()
208
-					))
209
-			])
210
-			->execute();
211
-		return $query->getLastInsertId();
212
-	}
213
-
214
-	/**
215
-	 * @param string $table
216
-	 * @param string $foreignKey
217
-	 * @param int $foreignId
218
-	 * @param array $metadata
219
-	 */
220
-	private function addMetadataToCache(string $table,
221
-										string $foreignKey,
222
-										int $foreignId,
223
-										array $metadata):void {
224
-		foreach ($metadata as $key => $value) {
225
-			$query = $this->dbConnection->getQueryBuilder();
226
-			$query->insert($table)
227
-				->values([
228
-					$foreignKey => $query->createNamedParameter($foreignId),
229
-					'key' => $query->createNamedParameter($key),
230
-					'value' => $query->createNamedParameter($value),
231
-				])
232
-				->execute();
233
-		}
234
-	}
235
-
236
-	/**
237
-	 * delete entry from cache that does not exist anymore remotely
238
-	 *
239
-	 * @param string $table
240
-	 * @param int $id
241
-	 */
242
-	private function deleteFromCache(string $table,
243
-									 int $id):void {
244
-		$query = $this->dbConnection->getQueryBuilder();
245
-		$query->delete($table)
246
-			->where($query->expr()->eq('id', $query->createNamedParameter($id)))
247
-			->execute();
248
-	}
249
-
250
-	/**
251
-	 * @param string $table
252
-	 * @param string $foreignKey
253
-	 * @param int $id
254
-	 */
255
-	private function deleteMetadataFromCache(string $table,
256
-											 string $foreignKey,
257
-											 int $id):void {
258
-		$query = $this->dbConnection->getQueryBuilder();
259
-		$query->delete($table)
260
-			->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
261
-			->execute();
262
-	}
263
-
264
-	/**
265
-	 * update an existing entry in cache
266
-	 *
267
-	 * @param string $table
268
-	 * @param int $id
269
-	 * @param IResource|IRoom $remote
270
-	 */
271
-	private function updateCache(string $table,
272
-								 int $id,
273
-								 $remote):void {
274
-		$query = $this->dbConnection->getQueryBuilder();
275
-		$query->update($table)
276
-			->set('email', $query->createNamedParameter($remote->getEMail()))
277
-			->set('displayname', $query->createNamedParameter($remote->getDisplayName()))
278
-			->set('group_restrictions', $query->createNamedParameter(
279
-				$this->serializeGroupRestrictions(
280
-					$remote->getGroupRestrictions()
281
-				)))
282
-			->where($query->expr()->eq('id', $query->createNamedParameter($id)))
283
-			->execute();
284
-	}
285
-
286
-	/**
287
-	 * @param string $dbTable
288
-	 * @param string $foreignKey
289
-	 * @param int $id
290
-	 * @param array $metadata
291
-	 * @param array $cachedMetadata
292
-	 */
293
-	private function updateMetadataCache(string $dbTable,
294
-										 string $foreignKey,
295
-										 int $id,
296
-										 array $metadata,
297
-										 array $cachedMetadata):void {
298
-		$newMetadata = array_diff_key($metadata, $cachedMetadata);
299
-		$deletedMetadata = array_diff_key($cachedMetadata, $metadata);
300
-
301
-		foreach ($newMetadata as $key => $value) {
302
-			$query = $this->dbConnection->getQueryBuilder();
303
-			$query->insert($dbTable)
304
-				->values([
305
-					$foreignKey => $query->createNamedParameter($id),
306
-					'key' => $query->createNamedParameter($key),
307
-					'value' => $query->createNamedParameter($value),
308
-				])
309
-				->execute();
310
-		}
311
-
312
-		foreach ($deletedMetadata as $key => $value) {
313
-			$query = $this->dbConnection->getQueryBuilder();
314
-			$query->delete($dbTable)
315
-				->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
316
-				->andWhere($query->expr()->eq('key', $query->createNamedParameter($key)))
317
-				->execute();
318
-		}
319
-
320
-		$existingKeys = array_keys(array_intersect_key($metadata, $cachedMetadata));
321
-		foreach ($existingKeys as $existingKey) {
322
-			if ($metadata[$existingKey] !== $cachedMetadata[$existingKey]) {
323
-				$query = $this->dbConnection->getQueryBuilder();
324
-				$query->update($dbTable)
325
-					->set('value', $query->createNamedParameter($metadata[$existingKey]))
326
-					->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
327
-					->andWhere($query->expr()->eq('key', $query->createNamedParameter($existingKey)))
328
-					->execute();
329
-			}
330
-		}
331
-	}
332
-
333
-	/**
334
-	 * serialize array of group restrictions to store them in database
335
-	 *
336
-	 * @param array $groups
337
-	 * @return string
338
-	 */
339
-	private function serializeGroupRestrictions(array $groups):string {
340
-		return \json_encode($groups);
341
-	}
342
-
343
-	/**
344
-	 * Gets all metadata of a backend
345
-	 *
346
-	 * @param IResource|IRoom $resource
347
-	 * @return array
348
-	 */
349
-	private function getAllMetadataOfBackend($resource):array {
350
-		if (!($resource instanceof IMetadataProvider)) {
351
-			return [];
352
-		}
353
-
354
-		$keys = $resource->getAllAvailableMetadataKeys();
355
-		$metadata = [];
356
-		foreach ($keys as $key) {
357
-			$metadata[$key] = $resource->getMetadataForKey($key);
358
-		}
359
-
360
-		return $metadata;
361
-	}
362
-
363
-	/**
364
-	 * @param string $table
365
-	 * @param string $foreignKey
366
-	 * @param int $id
367
-	 * @return array
368
-	 */
369
-	private function getAllMetadataOfCache(string $table,
370
-										   string $foreignKey,
371
-										   int $id):array {
372
-		$query = $this->dbConnection->getQueryBuilder();
373
-		$query->select(['key', 'value'])
374
-			->from($table)
375
-			->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)));
376
-		$stmt = $query->execute();
377
-		$rows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
378
-
379
-		$metadata = [];
380
-		foreach ($rows as $row) {
381
-			$metadata[$row['key']] = $row['value'];
382
-		}
383
-
384
-		return $metadata;
385
-	}
386
-
387
-	/**
388
-	 * Gets all cached rooms / resources by backend
389
-	 *
390
-	 * @param $tableName
391
-	 * @param $backendId
392
-	 * @return array
393
-	 */
394
-	private function getAllCachedByBackend(string $tableName,
395
-										   string $backendId):array {
396
-		$query = $this->dbConnection->getQueryBuilder();
397
-		$query->select('resource_id')
398
-			->from($tableName)
399
-			->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)));
400
-		$stmt = $query->execute();
401
-
402
-		return array_map(function ($row) {
403
-			return $row['resource_id'];
404
-		}, $stmt->fetchAll());
405
-	}
406
-
407
-	/**
408
-	 * @param $principalPrefix
409
-	 * @param $principalUri
410
-	 */
411
-	private function deleteCalendarDataForResource(string $principalPrefix,
412
-												   string $principalUri):void {
413
-		$calendar = $this->calDavBackend->getCalendarByUri(
414
-			implode('/', [$principalPrefix, $principalUri]),
415
-			CalDavBackend::RESOURCE_BOOKING_CALENDAR_URI);
416
-
417
-		if ($calendar !== null) {
418
-			$this->calDavBackend->deleteCalendar($calendar['id']);
419
-		}
420
-	}
421
-
422
-	/**
423
-	 * @param $table
424
-	 * @param $backendId
425
-	 * @param $resourceId
426
-	 * @return int
427
-	 */
428
-	private function getIdForBackendAndResource(string $table,
429
-												string $backendId,
430
-												string $resourceId):int {
431
-		$query = $this->dbConnection->getQueryBuilder();
432
-		$query->select('id')
433
-			->from($table)
434
-			->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)))
435
-			->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resourceId)));
436
-		$stmt = $query->execute();
437
-
438
-		return $stmt->fetch()['id'];
439
-	}
40
+    /** @var IResourceManager */
41
+    private $resourceManager;
42
+
43
+    /** @var IRoomManager */
44
+    private $roomManager;
45
+
46
+    /** @var IDBConnection */
47
+    private $dbConnection;
48
+
49
+    /** @var CalDavBackend */
50
+    private $calDavBackend;
51
+
52
+    /**
53
+     * UpdateCalendarResourcesRoomsBackgroundJob constructor.
54
+     *
55
+     * @param IResourceManager $resourceManager
56
+     * @param IRoomManager $roomManager
57
+     * @param IDBConnection $dbConnection
58
+     * @param CalDavBackend $calDavBackend
59
+     */
60
+    public function __construct(IResourceManager $resourceManager,
61
+                                IRoomManager $roomManager,
62
+                                IDBConnection $dbConnection,
63
+                                CalDavBackend $calDavBackend) {
64
+        $this->resourceManager = $resourceManager;
65
+        $this->roomManager = $roomManager;
66
+        $this->dbConnection = $dbConnection;
67
+        $this->calDavBackend = $calDavBackend;
68
+
69
+        // run once an hour
70
+        $this->setInterval(60 * 60);
71
+    }
72
+
73
+    /**
74
+     * @param $argument
75
+     */
76
+    public function run($argument):void {
77
+        $this->runForBackend(
78
+            $this->resourceManager,
79
+            'calendar_resources',
80
+            'calendar_resources_md',
81
+            'resource_id',
82
+            'principals/calendar-resources'
83
+        );
84
+        $this->runForBackend(
85
+            $this->roomManager,
86
+            'calendar_rooms',
87
+            'calendar_rooms_md',
88
+            'room_id',
89
+            'principals/calendar-rooms'
90
+        );
91
+    }
92
+
93
+    /**
94
+     * Run background-job for one specific backendManager
95
+     * either ResourceManager or RoomManager
96
+     *
97
+     * @param IResourceManager|IRoomManager $backendManager
98
+     * @param string $dbTable
99
+     * @param string $dbTableMetadata
100
+     * @param string $foreignKey
101
+     * @param string $principalPrefix
102
+     */
103
+    private function runForBackend($backendManager,
104
+                                    string $dbTable,
105
+                                    string $dbTableMetadata,
106
+                                    string $foreignKey,
107
+                                    string $principalPrefix):void {
108
+        $backends = $backendManager->getBackends();
109
+
110
+        foreach ($backends as $backend) {
111
+            $backendId = $backend->getBackendIdentifier();
112
+
113
+            try {
114
+                if ($backend instanceof IResourceBackend) {
115
+                    $list = $backend->listAllResources();
116
+                } else {
117
+                    $list = $backend->listAllRooms();
118
+                }
119
+            } catch (BackendTemporarilyUnavailableException $ex) {
120
+                continue;
121
+            }
122
+
123
+            $cachedList = $this->getAllCachedByBackend($dbTable, $backendId);
124
+            $newIds = array_diff($list, $cachedList);
125
+            $deletedIds = array_diff($cachedList, $list);
126
+            $editedIds = array_intersect($list, $cachedList);
127
+
128
+            foreach ($newIds as $newId) {
129
+                try {
130
+                    if ($backend instanceof IResourceBackend) {
131
+                        $resource = $backend->getResource($newId);
132
+                    } else {
133
+                        $resource = $backend->getRoom($newId);
134
+                    }
135
+
136
+                    $metadata = [];
137
+                    if ($resource instanceof IMetadataProvider) {
138
+                        $metadata = $this->getAllMetadataOfBackend($resource);
139
+                    }
140
+                } catch (BackendTemporarilyUnavailableException $ex) {
141
+                    continue;
142
+                }
143
+
144
+                $id = $this->addToCache($dbTable, $backendId, $resource);
145
+                $this->addMetadataToCache($dbTableMetadata, $foreignKey, $id, $metadata);
146
+                // we don't create the calendar here, it is created lazily
147
+                // when an event is actually scheduled with this resource / room
148
+            }
149
+
150
+            foreach ($deletedIds as $deletedId) {
151
+                $id = $this->getIdForBackendAndResource($dbTable, $backendId, $deletedId);
152
+                $this->deleteFromCache($dbTable, $id);
153
+                $this->deleteMetadataFromCache($dbTableMetadata, $foreignKey, $id);
154
+
155
+                $principalName = implode('-', [$backendId, $deletedId]);
156
+                $this->deleteCalendarDataForResource($principalPrefix, $principalName);
157
+            }
158
+
159
+            foreach ($editedIds as $editedId) {
160
+                $id = $this->getIdForBackendAndResource($dbTable, $backendId, $editedId);
161
+
162
+                try {
163
+                    if ($backend instanceof IResourceBackend) {
164
+                        $resource = $backend->getResource($editedId);
165
+                    } else {
166
+                        $resource = $backend->getRoom($editedId);
167
+                    }
168
+
169
+                    $metadata = [];
170
+                    if ($resource instanceof IMetadataProvider) {
171
+                        $metadata = $this->getAllMetadataOfBackend($resource);
172
+                    }
173
+                } catch (BackendTemporarilyUnavailableException $ex) {
174
+                    continue;
175
+                }
176
+
177
+                $this->updateCache($dbTable, $id, $resource);
178
+
179
+                if ($resource instanceof IMetadataProvider) {
180
+                    $cachedMetadata = $this->getAllMetadataOfCache($dbTableMetadata, $foreignKey, $id);
181
+                    $this->updateMetadataCache($dbTableMetadata, $foreignKey, $id, $metadata, $cachedMetadata);
182
+                }
183
+            }
184
+        }
185
+    }
186
+
187
+    /**
188
+     * add entry to cache that exists remotely but not yet in cache
189
+     *
190
+     * @param string $table
191
+     * @param string $backendId
192
+     * @param IResource|IRoom $remote
193
+     * @return int Insert id
194
+     */
195
+    private function addToCache(string $table,
196
+                                string $backendId,
197
+                                $remote):int {
198
+        $query = $this->dbConnection->getQueryBuilder();
199
+        $query->insert($table)
200
+            ->values([
201
+                'backend_id' => $query->createNamedParameter($backendId),
202
+                'resource_id' => $query->createNamedParameter($remote->getId()),
203
+                'email' => $query->createNamedParameter($remote->getEMail()),
204
+                'displayname' => $query->createNamedParameter($remote->getDisplayName()),
205
+                'group_restrictions' => $query->createNamedParameter(
206
+                    $this->serializeGroupRestrictions(
207
+                        $remote->getGroupRestrictions()
208
+                    ))
209
+            ])
210
+            ->execute();
211
+        return $query->getLastInsertId();
212
+    }
213
+
214
+    /**
215
+     * @param string $table
216
+     * @param string $foreignKey
217
+     * @param int $foreignId
218
+     * @param array $metadata
219
+     */
220
+    private function addMetadataToCache(string $table,
221
+                                        string $foreignKey,
222
+                                        int $foreignId,
223
+                                        array $metadata):void {
224
+        foreach ($metadata as $key => $value) {
225
+            $query = $this->dbConnection->getQueryBuilder();
226
+            $query->insert($table)
227
+                ->values([
228
+                    $foreignKey => $query->createNamedParameter($foreignId),
229
+                    'key' => $query->createNamedParameter($key),
230
+                    'value' => $query->createNamedParameter($value),
231
+                ])
232
+                ->execute();
233
+        }
234
+    }
235
+
236
+    /**
237
+     * delete entry from cache that does not exist anymore remotely
238
+     *
239
+     * @param string $table
240
+     * @param int $id
241
+     */
242
+    private function deleteFromCache(string $table,
243
+                                        int $id):void {
244
+        $query = $this->dbConnection->getQueryBuilder();
245
+        $query->delete($table)
246
+            ->where($query->expr()->eq('id', $query->createNamedParameter($id)))
247
+            ->execute();
248
+    }
249
+
250
+    /**
251
+     * @param string $table
252
+     * @param string $foreignKey
253
+     * @param int $id
254
+     */
255
+    private function deleteMetadataFromCache(string $table,
256
+                                                string $foreignKey,
257
+                                                int $id):void {
258
+        $query = $this->dbConnection->getQueryBuilder();
259
+        $query->delete($table)
260
+            ->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
261
+            ->execute();
262
+    }
263
+
264
+    /**
265
+     * update an existing entry in cache
266
+     *
267
+     * @param string $table
268
+     * @param int $id
269
+     * @param IResource|IRoom $remote
270
+     */
271
+    private function updateCache(string $table,
272
+                                    int $id,
273
+                                    $remote):void {
274
+        $query = $this->dbConnection->getQueryBuilder();
275
+        $query->update($table)
276
+            ->set('email', $query->createNamedParameter($remote->getEMail()))
277
+            ->set('displayname', $query->createNamedParameter($remote->getDisplayName()))
278
+            ->set('group_restrictions', $query->createNamedParameter(
279
+                $this->serializeGroupRestrictions(
280
+                    $remote->getGroupRestrictions()
281
+                )))
282
+            ->where($query->expr()->eq('id', $query->createNamedParameter($id)))
283
+            ->execute();
284
+    }
285
+
286
+    /**
287
+     * @param string $dbTable
288
+     * @param string $foreignKey
289
+     * @param int $id
290
+     * @param array $metadata
291
+     * @param array $cachedMetadata
292
+     */
293
+    private function updateMetadataCache(string $dbTable,
294
+                                            string $foreignKey,
295
+                                            int $id,
296
+                                            array $metadata,
297
+                                            array $cachedMetadata):void {
298
+        $newMetadata = array_diff_key($metadata, $cachedMetadata);
299
+        $deletedMetadata = array_diff_key($cachedMetadata, $metadata);
300
+
301
+        foreach ($newMetadata as $key => $value) {
302
+            $query = $this->dbConnection->getQueryBuilder();
303
+            $query->insert($dbTable)
304
+                ->values([
305
+                    $foreignKey => $query->createNamedParameter($id),
306
+                    'key' => $query->createNamedParameter($key),
307
+                    'value' => $query->createNamedParameter($value),
308
+                ])
309
+                ->execute();
310
+        }
311
+
312
+        foreach ($deletedMetadata as $key => $value) {
313
+            $query = $this->dbConnection->getQueryBuilder();
314
+            $query->delete($dbTable)
315
+                ->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
316
+                ->andWhere($query->expr()->eq('key', $query->createNamedParameter($key)))
317
+                ->execute();
318
+        }
319
+
320
+        $existingKeys = array_keys(array_intersect_key($metadata, $cachedMetadata));
321
+        foreach ($existingKeys as $existingKey) {
322
+            if ($metadata[$existingKey] !== $cachedMetadata[$existingKey]) {
323
+                $query = $this->dbConnection->getQueryBuilder();
324
+                $query->update($dbTable)
325
+                    ->set('value', $query->createNamedParameter($metadata[$existingKey]))
326
+                    ->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
327
+                    ->andWhere($query->expr()->eq('key', $query->createNamedParameter($existingKey)))
328
+                    ->execute();
329
+            }
330
+        }
331
+    }
332
+
333
+    /**
334
+     * serialize array of group restrictions to store them in database
335
+     *
336
+     * @param array $groups
337
+     * @return string
338
+     */
339
+    private function serializeGroupRestrictions(array $groups):string {
340
+        return \json_encode($groups);
341
+    }
342
+
343
+    /**
344
+     * Gets all metadata of a backend
345
+     *
346
+     * @param IResource|IRoom $resource
347
+     * @return array
348
+     */
349
+    private function getAllMetadataOfBackend($resource):array {
350
+        if (!($resource instanceof IMetadataProvider)) {
351
+            return [];
352
+        }
353
+
354
+        $keys = $resource->getAllAvailableMetadataKeys();
355
+        $metadata = [];
356
+        foreach ($keys as $key) {
357
+            $metadata[$key] = $resource->getMetadataForKey($key);
358
+        }
359
+
360
+        return $metadata;
361
+    }
362
+
363
+    /**
364
+     * @param string $table
365
+     * @param string $foreignKey
366
+     * @param int $id
367
+     * @return array
368
+     */
369
+    private function getAllMetadataOfCache(string $table,
370
+                                            string $foreignKey,
371
+                                            int $id):array {
372
+        $query = $this->dbConnection->getQueryBuilder();
373
+        $query->select(['key', 'value'])
374
+            ->from($table)
375
+            ->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)));
376
+        $stmt = $query->execute();
377
+        $rows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
378
+
379
+        $metadata = [];
380
+        foreach ($rows as $row) {
381
+            $metadata[$row['key']] = $row['value'];
382
+        }
383
+
384
+        return $metadata;
385
+    }
386
+
387
+    /**
388
+     * Gets all cached rooms / resources by backend
389
+     *
390
+     * @param $tableName
391
+     * @param $backendId
392
+     * @return array
393
+     */
394
+    private function getAllCachedByBackend(string $tableName,
395
+                                            string $backendId):array {
396
+        $query = $this->dbConnection->getQueryBuilder();
397
+        $query->select('resource_id')
398
+            ->from($tableName)
399
+            ->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)));
400
+        $stmt = $query->execute();
401
+
402
+        return array_map(function ($row) {
403
+            return $row['resource_id'];
404
+        }, $stmt->fetchAll());
405
+    }
406
+
407
+    /**
408
+     * @param $principalPrefix
409
+     * @param $principalUri
410
+     */
411
+    private function deleteCalendarDataForResource(string $principalPrefix,
412
+                                                    string $principalUri):void {
413
+        $calendar = $this->calDavBackend->getCalendarByUri(
414
+            implode('/', [$principalPrefix, $principalUri]),
415
+            CalDavBackend::RESOURCE_BOOKING_CALENDAR_URI);
416
+
417
+        if ($calendar !== null) {
418
+            $this->calDavBackend->deleteCalendar($calendar['id']);
419
+        }
420
+    }
421
+
422
+    /**
423
+     * @param $table
424
+     * @param $backendId
425
+     * @param $resourceId
426
+     * @return int
427
+     */
428
+    private function getIdForBackendAndResource(string $table,
429
+                                                string $backendId,
430
+                                                string $resourceId):int {
431
+        $query = $this->dbConnection->getQueryBuilder();
432
+        $query->select('id')
433
+            ->from($table)
434
+            ->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)))
435
+            ->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resourceId)));
436
+        $stmt = $query->execute();
437
+
438
+        return $stmt->fetch()['id'];
439
+    }
440 440
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -399,7 +399,7 @@
 block discarded – undo
399 399
 			->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)));
400 400
 		$stmt = $query->execute();
401 401
 
402
-		return array_map(function ($row) {
402
+		return array_map(function($row) {
403 403
 			return $row['resource_id'];
404 404
 		}, $stmt->fetchAll());
405 405
 	}
Please login to merge, or discard this patch.
apps/dav/lib/CardDAV/CardDavBackend.php 2 patches
Indentation   +1300 added lines, -1300 removed lines patch added patch discarded remove patch
@@ -62,1304 +62,1304 @@
 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',
87
-		'CLOUD', 'X-SOCIALPROFILE'];
88
-
89
-	/**
90
-	 * @var string[] Map of uid => display name
91
-	 */
92
-	protected $userDisplayNames;
93
-
94
-	/** @var IUserManager */
95
-	private $userManager;
96
-
97
-	/** @var IEventDispatcher */
98
-	private $dispatcher;
99
-
100
-	/** @var EventDispatcherInterface */
101
-	private $legacyDispatcher;
102
-
103
-	private $etagCache = [];
104
-
105
-	/**
106
-	 * CardDavBackend constructor.
107
-	 *
108
-	 * @param IDBConnection $db
109
-	 * @param Principal $principalBackend
110
-	 * @param IUserManager $userManager
111
-	 * @param IGroupManager $groupManager
112
-	 * @param IEventDispatcher $dispatcher
113
-	 * @param EventDispatcherInterface $legacyDispatcher
114
-	 */
115
-	public function __construct(IDBConnection $db,
116
-								Principal $principalBackend,
117
-								IUserManager $userManager,
118
-								IGroupManager $groupManager,
119
-								IEventDispatcher $dispatcher,
120
-								EventDispatcherInterface $legacyDispatcher) {
121
-		$this->db = $db;
122
-		$this->principalBackend = $principalBackend;
123
-		$this->userManager = $userManager;
124
-		$this->dispatcher = $dispatcher;
125
-		$this->legacyDispatcher = $legacyDispatcher;
126
-		$this->sharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'addressbook');
127
-	}
128
-
129
-	/**
130
-	 * Return the number of address books for a principal
131
-	 *
132
-	 * @param $principalUri
133
-	 * @return int
134
-	 */
135
-	public function getAddressBooksForUserCount($principalUri) {
136
-		$principalUri = $this->convertPrincipal($principalUri, true);
137
-		$query = $this->db->getQueryBuilder();
138
-		$query->select($query->func()->count('*'))
139
-			->from('addressbooks')
140
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
141
-
142
-		$result = $query->execute();
143
-		$column = (int) $result->fetchOne();
144
-		$result->closeCursor();
145
-		return $column;
146
-	}
147
-
148
-	/**
149
-	 * Returns the list of address books for a specific user.
150
-	 *
151
-	 * Every addressbook should have the following properties:
152
-	 *   id - an arbitrary unique id
153
-	 *   uri - the 'basename' part of the url
154
-	 *   principaluri - Same as the passed parameter
155
-	 *
156
-	 * Any additional clark-notation property may be passed besides this. Some
157
-	 * common ones are :
158
-	 *   {DAV:}displayname
159
-	 *   {urn:ietf:params:xml:ns:carddav}addressbook-description
160
-	 *   {http://calendarserver.org/ns/}getctag
161
-	 *
162
-	 * @param string $principalUri
163
-	 * @return array
164
-	 */
165
-	public function getAddressBooksForUser($principalUri) {
166
-		$principalUriOriginal = $principalUri;
167
-		$principalUri = $this->convertPrincipal($principalUri, true);
168
-		$query = $this->db->getQueryBuilder();
169
-		$query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
170
-			->from('addressbooks')
171
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
172
-
173
-		$addressBooks = [];
174
-
175
-		$result = $query->execute();
176
-		while ($row = $result->fetch()) {
177
-			$addressBooks[$row['id']] = [
178
-				'id' => $row['id'],
179
-				'uri' => $row['uri'],
180
-				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
181
-				'{DAV:}displayname' => $row['displayname'],
182
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
183
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
184
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
185
-			];
186
-
187
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
188
-		}
189
-		$result->closeCursor();
190
-
191
-		// query for shared addressbooks
192
-		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
193
-		$principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
194
-
195
-		$principals[] = $principalUri;
196
-
197
-		$query = $this->db->getQueryBuilder();
198
-		$result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
199
-			->from('dav_shares', 's')
200
-			->join('s', 'addressbooks', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
201
-			->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
202
-			->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
203
-			->setParameter('type', 'addressbook')
204
-			->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
205
-			->execute();
206
-
207
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
208
-		while ($row = $result->fetch()) {
209
-			if ($row['principaluri'] === $principalUri) {
210
-				continue;
211
-			}
212
-
213
-			$readOnly = (int)$row['access'] === Backend::ACCESS_READ;
214
-			if (isset($addressBooks[$row['id']])) {
215
-				if ($readOnly) {
216
-					// New share can not have more permissions then the old one.
217
-					continue;
218
-				}
219
-				if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) &&
220
-					$addressBooks[$row['id']][$readOnlyPropertyName] === 0) {
221
-					// Old share is already read-write, no more permissions can be gained
222
-					continue;
223
-				}
224
-			}
225
-
226
-			list(, $name) = \Sabre\Uri\split($row['principaluri']);
227
-			$uri = $row['uri'] . '_shared_by_' . $name;
228
-			$displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
229
-
230
-			$addressBooks[$row['id']] = [
231
-				'id' => $row['id'],
232
-				'uri' => $uri,
233
-				'principaluri' => $principalUriOriginal,
234
-				'{DAV:}displayname' => $displayName,
235
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
236
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
237
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
238
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
239
-				$readOnlyPropertyName => $readOnly,
240
-			];
241
-
242
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
243
-		}
244
-		$result->closeCursor();
245
-
246
-		return array_values($addressBooks);
247
-	}
248
-
249
-	public function getUsersOwnAddressBooks($principalUri) {
250
-		$principalUri = $this->convertPrincipal($principalUri, true);
251
-		$query = $this->db->getQueryBuilder();
252
-		$query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
253
-			->from('addressbooks')
254
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
255
-
256
-		$addressBooks = [];
257
-
258
-		$result = $query->execute();
259
-		while ($row = $result->fetch()) {
260
-			$addressBooks[$row['id']] = [
261
-				'id' => $row['id'],
262
-				'uri' => $row['uri'],
263
-				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
264
-				'{DAV:}displayname' => $row['displayname'],
265
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
266
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
267
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
268
-			];
269
-
270
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
271
-		}
272
-		$result->closeCursor();
273
-
274
-		return array_values($addressBooks);
275
-	}
276
-
277
-	private function getUserDisplayName($uid) {
278
-		if (!isset($this->userDisplayNames[$uid])) {
279
-			$user = $this->userManager->get($uid);
280
-
281
-			if ($user instanceof IUser) {
282
-				$this->userDisplayNames[$uid] = $user->getDisplayName();
283
-			} else {
284
-				$this->userDisplayNames[$uid] = $uid;
285
-			}
286
-		}
287
-
288
-		return $this->userDisplayNames[$uid];
289
-	}
290
-
291
-	/**
292
-	 * @param int $addressBookId
293
-	 */
294
-	public function getAddressBookById($addressBookId) {
295
-		$query = $this->db->getQueryBuilder();
296
-		$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
297
-			->from('addressbooks')
298
-			->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
299
-			->execute();
300
-
301
-		$row = $result->fetch();
302
-		$result->closeCursor();
303
-		if ($row === false) {
304
-			return null;
305
-		}
306
-
307
-		$addressBook = [
308
-			'id' => $row['id'],
309
-			'uri' => $row['uri'],
310
-			'principaluri' => $row['principaluri'],
311
-			'{DAV:}displayname' => $row['displayname'],
312
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
313
-			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
314
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
315
-		];
316
-
317
-		$this->addOwnerPrincipal($addressBook);
318
-
319
-		return $addressBook;
320
-	}
321
-
322
-	/**
323
-	 * @param $addressBookUri
324
-	 * @return array|null
325
-	 */
326
-	public function getAddressBooksByUri($principal, $addressBookUri) {
327
-		$query = $this->db->getQueryBuilder();
328
-		$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
329
-			->from('addressbooks')
330
-			->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri)))
331
-			->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
332
-			->setMaxResults(1)
333
-			->execute();
334
-
335
-		$row = $result->fetch();
336
-		$result->closeCursor();
337
-		if ($row === false) {
338
-			return null;
339
-		}
340
-
341
-		$addressBook = [
342
-			'id' => $row['id'],
343
-			'uri' => $row['uri'],
344
-			'principaluri' => $row['principaluri'],
345
-			'{DAV:}displayname' => $row['displayname'],
346
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
347
-			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
348
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
349
-		];
350
-
351
-		$this->addOwnerPrincipal($addressBook);
352
-
353
-		return $addressBook;
354
-	}
355
-
356
-	/**
357
-	 * Updates properties for an address book.
358
-	 *
359
-	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
360
-	 * To do the actual updates, you must tell this object which properties
361
-	 * you're going to process with the handle() method.
362
-	 *
363
-	 * Calling the handle method is like telling the PropPatch object "I
364
-	 * promise I can handle updating this property".
365
-	 *
366
-	 * Read the PropPatch documentation for more info and examples.
367
-	 *
368
-	 * @param string $addressBookId
369
-	 * @param \Sabre\DAV\PropPatch $propPatch
370
-	 * @return void
371
-	 */
372
-	public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
373
-		$supportedProperties = [
374
-			'{DAV:}displayname',
375
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description',
376
-		];
377
-
378
-		$propPatch->handle($supportedProperties, function ($mutations) use ($addressBookId) {
379
-			$updates = [];
380
-			foreach ($mutations as $property => $newValue) {
381
-				switch ($property) {
382
-					case '{DAV:}displayname':
383
-						$updates['displayname'] = $newValue;
384
-						break;
385
-					case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
386
-						$updates['description'] = $newValue;
387
-						break;
388
-				}
389
-			}
390
-			$query = $this->db->getQueryBuilder();
391
-			$query->update('addressbooks');
392
-
393
-			foreach ($updates as $key => $value) {
394
-				$query->set($key, $query->createNamedParameter($value));
395
-			}
396
-			$query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
397
-				->execute();
398
-
399
-			$this->addChange($addressBookId, "", 2);
400
-
401
-			$addressBookRow = $this->getAddressBookById((int)$addressBookId);
402
-			$shares = $this->getShares($addressBookId);
403
-			$this->dispatcher->dispatchTyped(new AddressBookUpdatedEvent((int)$addressBookId, $addressBookRow, $shares, $mutations));
404
-
405
-			return true;
406
-		});
407
-	}
408
-
409
-	/**
410
-	 * Creates a new address book
411
-	 *
412
-	 * @param string $principalUri
413
-	 * @param string $url Just the 'basename' of the url.
414
-	 * @param array $properties
415
-	 * @return int
416
-	 * @throws BadRequest
417
-	 */
418
-	public function createAddressBook($principalUri, $url, array $properties) {
419
-		$values = [
420
-			'displayname' => null,
421
-			'description' => null,
422
-			'principaluri' => $principalUri,
423
-			'uri' => $url,
424
-			'synctoken' => 1
425
-		];
426
-
427
-		foreach ($properties as $property => $newValue) {
428
-			switch ($property) {
429
-				case '{DAV:}displayname':
430
-					$values['displayname'] = $newValue;
431
-					break;
432
-				case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
433
-					$values['description'] = $newValue;
434
-					break;
435
-				default:
436
-					throw new BadRequest('Unknown property: ' . $property);
437
-			}
438
-		}
439
-
440
-		// Fallback to make sure the displayname is set. Some clients may refuse
441
-		// to work with addressbooks not having a displayname.
442
-		if (is_null($values['displayname'])) {
443
-			$values['displayname'] = $url;
444
-		}
445
-
446
-		$query = $this->db->getQueryBuilder();
447
-		$query->insert('addressbooks')
448
-			->values([
449
-				'uri' => $query->createParameter('uri'),
450
-				'displayname' => $query->createParameter('displayname'),
451
-				'description' => $query->createParameter('description'),
452
-				'principaluri' => $query->createParameter('principaluri'),
453
-				'synctoken' => $query->createParameter('synctoken'),
454
-			])
455
-			->setParameters($values)
456
-			->execute();
457
-
458
-		$addressBookId = $query->getLastInsertId();
459
-		$addressBookRow = $this->getAddressBookById($addressBookId);
460
-		$this->dispatcher->dispatchTyped(new AddressBookCreatedEvent((int)$addressBookId, $addressBookRow));
461
-
462
-		return $addressBookId;
463
-	}
464
-
465
-	/**
466
-	 * Deletes an entire addressbook and all its contents
467
-	 *
468
-	 * @param mixed $addressBookId
469
-	 * @return void
470
-	 */
471
-	public function deleteAddressBook($addressBookId) {
472
-		$addressBookData = $this->getAddressBookById($addressBookId);
473
-		$shares = $this->getShares($addressBookId);
474
-
475
-		$query = $this->db->getQueryBuilder();
476
-		$query->delete($this->dbCardsTable)
477
-			->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
478
-			->setParameter('addressbookid', $addressBookId)
479
-			->execute();
480
-
481
-		$query->delete('addressbookchanges')
482
-			->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
483
-			->setParameter('addressbookid', $addressBookId)
484
-			->execute();
485
-
486
-		$query->delete('addressbooks')
487
-			->where($query->expr()->eq('id', $query->createParameter('id')))
488
-			->setParameter('id', $addressBookId)
489
-			->execute();
490
-
491
-		$this->sharingBackend->deleteAllShares($addressBookId);
492
-
493
-		$query->delete($this->dbCardsPropertiesTable)
494
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
495
-			->execute();
496
-
497
-		if ($addressBookData) {
498
-			$this->dispatcher->dispatchTyped(new AddressBookDeletedEvent((int) $addressBookId, $addressBookData, $shares));
499
-		}
500
-	}
501
-
502
-	/**
503
-	 * Returns all cards for a specific addressbook id.
504
-	 *
505
-	 * This method should return the following properties for each card:
506
-	 *   * carddata - raw vcard data
507
-	 *   * uri - Some unique url
508
-	 *   * lastmodified - A unix timestamp
509
-	 *
510
-	 * It's recommended to also return the following properties:
511
-	 *   * etag - A unique etag. This must change every time the card changes.
512
-	 *   * size - The size of the card in bytes.
513
-	 *
514
-	 * If these last two properties are provided, less time will be spent
515
-	 * calculating them. If they are specified, you can also ommit carddata.
516
-	 * This may speed up certain requests, especially with large cards.
517
-	 *
518
-	 * @param mixed $addressBookId
519
-	 * @return array
520
-	 */
521
-	public function getCards($addressBookId) {
522
-		$query = $this->db->getQueryBuilder();
523
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
524
-			->from($this->dbCardsTable)
525
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
526
-
527
-		$cards = [];
528
-
529
-		$result = $query->execute();
530
-		while ($row = $result->fetch()) {
531
-			$row['etag'] = '"' . $row['etag'] . '"';
532
-
533
-			$modified = false;
534
-			$row['carddata'] = $this->readBlob($row['carddata'], $modified);
535
-			if ($modified) {
536
-				$row['size'] = strlen($row['carddata']);
537
-			}
538
-
539
-			$cards[] = $row;
540
-		}
541
-		$result->closeCursor();
542
-
543
-		return $cards;
544
-	}
545
-
546
-	/**
547
-	 * Returns a specific card.
548
-	 *
549
-	 * The same set of properties must be returned as with getCards. The only
550
-	 * exception is that 'carddata' is absolutely required.
551
-	 *
552
-	 * If the card does not exist, you must return false.
553
-	 *
554
-	 * @param mixed $addressBookId
555
-	 * @param string $cardUri
556
-	 * @return array
557
-	 */
558
-	public function getCard($addressBookId, $cardUri) {
559
-		$query = $this->db->getQueryBuilder();
560
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
561
-			->from($this->dbCardsTable)
562
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
563
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
564
-			->setMaxResults(1);
565
-
566
-		$result = $query->execute();
567
-		$row = $result->fetch();
568
-		if (!$row) {
569
-			return false;
570
-		}
571
-		$row['etag'] = '"' . $row['etag'] . '"';
572
-
573
-		$modified = false;
574
-		$row['carddata'] = $this->readBlob($row['carddata'], $modified);
575
-		if ($modified) {
576
-			$row['size'] = strlen($row['carddata']);
577
-		}
578
-
579
-		return $row;
580
-	}
581
-
582
-	/**
583
-	 * Returns a list of cards.
584
-	 *
585
-	 * This method should work identical to getCard, but instead return all the
586
-	 * cards in the list as an array.
587
-	 *
588
-	 * If the backend supports this, it may allow for some speed-ups.
589
-	 *
590
-	 * @param mixed $addressBookId
591
-	 * @param string[] $uris
592
-	 * @return array
593
-	 */
594
-	public function getMultipleCards($addressBookId, array $uris) {
595
-		if (empty($uris)) {
596
-			return [];
597
-		}
598
-
599
-		$chunks = array_chunk($uris, 100);
600
-		$cards = [];
601
-
602
-		$query = $this->db->getQueryBuilder();
603
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
604
-			->from($this->dbCardsTable)
605
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
606
-			->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
607
-
608
-		foreach ($chunks as $uris) {
609
-			$query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
610
-			$result = $query->execute();
611
-
612
-			while ($row = $result->fetch()) {
613
-				$row['etag'] = '"' . $row['etag'] . '"';
614
-
615
-				$modified = false;
616
-				$row['carddata'] = $this->readBlob($row['carddata'], $modified);
617
-				if ($modified) {
618
-					$row['size'] = strlen($row['carddata']);
619
-				}
620
-
621
-				$cards[] = $row;
622
-			}
623
-			$result->closeCursor();
624
-		}
625
-		return $cards;
626
-	}
627
-
628
-	/**
629
-	 * Creates a new card.
630
-	 *
631
-	 * The addressbook id will be passed as the first argument. This is the
632
-	 * same id as it is returned from the getAddressBooksForUser method.
633
-	 *
634
-	 * The cardUri is a base uri, and doesn't include the full path. The
635
-	 * cardData argument is the vcard body, and is passed as a string.
636
-	 *
637
-	 * It is possible to return an ETag from this method. This ETag is for the
638
-	 * newly created resource, and must be enclosed with double quotes (that
639
-	 * is, the string itself must contain the double quotes).
640
-	 *
641
-	 * You should only return the ETag if you store the carddata as-is. If a
642
-	 * subsequent GET request on the same card does not have the same body,
643
-	 * byte-by-byte and you did return an ETag here, clients tend to get
644
-	 * confused.
645
-	 *
646
-	 * If you don't return an ETag, you can just return null.
647
-	 *
648
-	 * @param mixed $addressBookId
649
-	 * @param string $cardUri
650
-	 * @param string $cardData
651
-	 * @return string
652
-	 */
653
-	public function createCard($addressBookId, $cardUri, $cardData) {
654
-		$etag = md5($cardData);
655
-		$uid = $this->getUID($cardData);
656
-
657
-		$q = $this->db->getQueryBuilder();
658
-		$q->select('uid')
659
-			->from($this->dbCardsTable)
660
-			->where($q->expr()->eq('addressbookid', $q->createNamedParameter($addressBookId)))
661
-			->andWhere($q->expr()->eq('uid', $q->createNamedParameter($uid)))
662
-			->setMaxResults(1);
663
-		$result = $q->execute();
664
-		$count = (bool)$result->fetchOne();
665
-		$result->closeCursor();
666
-		if ($count) {
667
-			throw new \Sabre\DAV\Exception\BadRequest('VCard object with uid already exists in this addressbook collection.');
668
-		}
669
-
670
-		$query = $this->db->getQueryBuilder();
671
-		$query->insert('cards')
672
-			->values([
673
-				'carddata' => $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB),
674
-				'uri' => $query->createNamedParameter($cardUri),
675
-				'lastmodified' => $query->createNamedParameter(time()),
676
-				'addressbookid' => $query->createNamedParameter($addressBookId),
677
-				'size' => $query->createNamedParameter(strlen($cardData)),
678
-				'etag' => $query->createNamedParameter($etag),
679
-				'uid' => $query->createNamedParameter($uid),
680
-			])
681
-			->execute();
682
-
683
-		$etagCacheKey = "$addressBookId#$cardUri";
684
-		$this->etagCache[$etagCacheKey] = $etag;
685
-
686
-		$this->addChange($addressBookId, $cardUri, 1);
687
-		$this->updateProperties($addressBookId, $cardUri, $cardData);
688
-
689
-		$addressBookData = $this->getAddressBookById($addressBookId);
690
-		$shares = $this->getShares($addressBookId);
691
-		$objectRow = $this->getCard($addressBookId, $cardUri);
692
-		$this->dispatcher->dispatchTyped(new CardCreatedEvent((int)$addressBookId, $addressBookData, $shares, $objectRow));
693
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
694
-			new GenericEvent(null, [
695
-				'addressBookId' => $addressBookId,
696
-				'cardUri' => $cardUri,
697
-				'cardData' => $cardData]));
698
-
699
-		return '"' . $etag . '"';
700
-	}
701
-
702
-	/**
703
-	 * Updates a card.
704
-	 *
705
-	 * The addressbook id will be passed as the first argument. This is the
706
-	 * same id as it is returned from the getAddressBooksForUser method.
707
-	 *
708
-	 * The cardUri is a base uri, and doesn't include the full path. The
709
-	 * cardData argument is the vcard body, and is passed as a string.
710
-	 *
711
-	 * It is possible to return an ETag from this method. This ETag should
712
-	 * match that of the updated resource, and must be enclosed with double
713
-	 * quotes (that is: the string itself must contain the actual quotes).
714
-	 *
715
-	 * You should only return the ETag if you store the carddata as-is. If a
716
-	 * subsequent GET request on the same card does not have the same body,
717
-	 * byte-by-byte and you did return an ETag here, clients tend to get
718
-	 * confused.
719
-	 *
720
-	 * If you don't return an ETag, you can just return null.
721
-	 *
722
-	 * @param mixed $addressBookId
723
-	 * @param string $cardUri
724
-	 * @param string $cardData
725
-	 * @return string
726
-	 */
727
-	public function updateCard($addressBookId, $cardUri, $cardData) {
728
-		$uid = $this->getUID($cardData);
729
-		$etag = md5($cardData);
730
-		$query = $this->db->getQueryBuilder();
731
-
732
-		// check for recently stored etag and stop if it is the same
733
-		$etagCacheKey = "$addressBookId#$cardUri";
734
-		if (isset($this->etagCache[$etagCacheKey]) && $this->etagCache[$etagCacheKey] === $etag) {
735
-			return '"' . $etag . '"';
736
-		}
737
-
738
-		$query->update($this->dbCardsTable)
739
-			->set('carddata', $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB))
740
-			->set('lastmodified', $query->createNamedParameter(time()))
741
-			->set('size', $query->createNamedParameter(strlen($cardData)))
742
-			->set('etag', $query->createNamedParameter($etag))
743
-			->set('uid', $query->createNamedParameter($uid))
744
-			->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
745
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
746
-			->execute();
747
-
748
-		$this->etagCache[$etagCacheKey] = $etag;
749
-
750
-		$this->addChange($addressBookId, $cardUri, 2);
751
-		$this->updateProperties($addressBookId, $cardUri, $cardData);
752
-
753
-		$addressBookData = $this->getAddressBookById($addressBookId);
754
-		$shares = $this->getShares($addressBookId);
755
-		$objectRow = $this->getCard($addressBookId, $cardUri);
756
-		$this->dispatcher->dispatchTyped(new CardUpdatedEvent((int)$addressBookId, $addressBookData, $shares, $objectRow));
757
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
758
-			new GenericEvent(null, [
759
-				'addressBookId' => $addressBookId,
760
-				'cardUri' => $cardUri,
761
-				'cardData' => $cardData]));
762
-
763
-		return '"' . $etag . '"';
764
-	}
765
-
766
-	/**
767
-	 * Deletes a card
768
-	 *
769
-	 * @param mixed $addressBookId
770
-	 * @param string $cardUri
771
-	 * @return bool
772
-	 */
773
-	public function deleteCard($addressBookId, $cardUri) {
774
-		$addressBookData = $this->getAddressBookById($addressBookId);
775
-		$shares = $this->getShares($addressBookId);
776
-		$objectRow = $this->getCard($addressBookId, $cardUri);
777
-
778
-		try {
779
-			$cardId = $this->getCardId($addressBookId, $cardUri);
780
-		} catch (\InvalidArgumentException $e) {
781
-			$cardId = null;
782
-		}
783
-		$query = $this->db->getQueryBuilder();
784
-		$ret = $query->delete($this->dbCardsTable)
785
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
786
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
787
-			->execute();
788
-
789
-		$this->addChange($addressBookId, $cardUri, 3);
790
-
791
-		if ($ret === 1) {
792
-			if ($cardId !== null) {
793
-				$this->dispatcher->dispatchTyped(new CardDeletedEvent((int)$addressBookId, $addressBookData, $shares, $objectRow));
794
-				$this->legacyDispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
795
-					new GenericEvent(null, [
796
-						'addressBookId' => $addressBookId,
797
-						'cardUri' => $cardUri]));
798
-
799
-				$this->purgeProperties($addressBookId, $cardId);
800
-			}
801
-			return true;
802
-		}
803
-
804
-		return false;
805
-	}
806
-
807
-	/**
808
-	 * The getChanges method returns all the changes that have happened, since
809
-	 * the specified syncToken in the specified address book.
810
-	 *
811
-	 * This function should return an array, such as the following:
812
-	 *
813
-	 * [
814
-	 *   'syncToken' => 'The current synctoken',
815
-	 *   'added'   => [
816
-	 *      'new.txt',
817
-	 *   ],
818
-	 *   'modified'   => [
819
-	 *      'modified.txt',
820
-	 *   ],
821
-	 *   'deleted' => [
822
-	 *      'foo.php.bak',
823
-	 *      'old.txt'
824
-	 *   ]
825
-	 * ];
826
-	 *
827
-	 * The returned syncToken property should reflect the *current* syncToken
828
-	 * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
829
-	 * property. This is needed here too, to ensure the operation is atomic.
830
-	 *
831
-	 * If the $syncToken argument is specified as null, this is an initial
832
-	 * sync, and all members should be reported.
833
-	 *
834
-	 * The modified property is an array of nodenames that have changed since
835
-	 * the last token.
836
-	 *
837
-	 * The deleted property is an array with nodenames, that have been deleted
838
-	 * from collection.
839
-	 *
840
-	 * The $syncLevel argument is basically the 'depth' of the report. If it's
841
-	 * 1, you only have to report changes that happened only directly in
842
-	 * immediate descendants. If it's 2, it should also include changes from
843
-	 * the nodes below the child collections. (grandchildren)
844
-	 *
845
-	 * The $limit argument allows a client to specify how many results should
846
-	 * be returned at most. If the limit is not specified, it should be treated
847
-	 * as infinite.
848
-	 *
849
-	 * If the limit (infinite or not) is higher than you're willing to return,
850
-	 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
851
-	 *
852
-	 * If the syncToken is expired (due to data cleanup) or unknown, you must
853
-	 * return null.
854
-	 *
855
-	 * The limit is 'suggestive'. You are free to ignore it.
856
-	 *
857
-	 * @param string $addressBookId
858
-	 * @param string $syncToken
859
-	 * @param int $syncLevel
860
-	 * @param int $limit
861
-	 * @return array
862
-	 */
863
-	public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
864
-		// Current synctoken
865
-		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
866
-		$stmt->execute([$addressBookId]);
867
-		$currentToken = $stmt->fetchOne();
868
-
869
-		if (is_null($currentToken)) {
870
-			return null;
871
-		}
872
-
873
-		$result = [
874
-			'syncToken' => $currentToken,
875
-			'added' => [],
876
-			'modified' => [],
877
-			'deleted' => [],
878
-		];
879
-
880
-		if ($syncToken) {
881
-			$query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
882
-			if ($limit > 0) {
883
-				$query .= " LIMIT " . (int)$limit;
884
-			}
885
-
886
-			// Fetching all changes
887
-			$stmt = $this->db->prepare($query);
888
-			$stmt->execute([$syncToken, $currentToken, $addressBookId]);
889
-
890
-			$changes = [];
891
-
892
-			// This loop ensures that any duplicates are overwritten, only the
893
-			// last change on a node is relevant.
894
-			while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
895
-				$changes[$row['uri']] = $row['operation'];
896
-			}
897
-
898
-			foreach ($changes as $uri => $operation) {
899
-				switch ($operation) {
900
-					case 1:
901
-						$result['added'][] = $uri;
902
-						break;
903
-					case 2:
904
-						$result['modified'][] = $uri;
905
-						break;
906
-					case 3:
907
-						$result['deleted'][] = $uri;
908
-						break;
909
-				}
910
-			}
911
-		} else {
912
-			// No synctoken supplied, this is the initial sync.
913
-			$query = "SELECT `uri` FROM `*PREFIX*cards` WHERE `addressbookid` = ?";
914
-			$stmt = $this->db->prepare($query);
915
-			$stmt->execute([$addressBookId]);
916
-
917
-			$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
918
-		}
919
-		return $result;
920
-	}
921
-
922
-	/**
923
-	 * Adds a change record to the addressbookchanges table.
924
-	 *
925
-	 * @param mixed $addressBookId
926
-	 * @param string $objectUri
927
-	 * @param int $operation 1 = add, 2 = modify, 3 = delete
928
-	 * @return void
929
-	 */
930
-	protected function addChange($addressBookId, $objectUri, $operation) {
931
-		$sql = 'INSERT INTO `*PREFIX*addressbookchanges`(`uri`, `synctoken`, `addressbookid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*addressbooks` WHERE `id` = ?';
932
-		$stmt = $this->db->prepare($sql);
933
-		$stmt->execute([
934
-			$objectUri,
935
-			$addressBookId,
936
-			$operation,
937
-			$addressBookId
938
-		]);
939
-		$stmt = $this->db->prepare('UPDATE `*PREFIX*addressbooks` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
940
-		$stmt->execute([
941
-			$addressBookId
942
-		]);
943
-	}
944
-
945
-	/**
946
-	 * @param resource|string $cardData
947
-	 * @param bool $modified
948
-	 * @return string
949
-	 */
950
-	private function readBlob($cardData, &$modified = false) {
951
-		if (is_resource($cardData)) {
952
-			$cardData = stream_get_contents($cardData);
953
-		}
954
-
955
-		$cardDataArray = explode("\r\n", $cardData);
956
-
957
-		$cardDataFiltered = [];
958
-		$removingPhoto = false;
959
-		foreach ($cardDataArray as $line) {
960
-			if (strpos($line, 'PHOTO:data:') === 0
961
-				&& strpos($line, 'PHOTO:data:image/') !== 0) {
962
-				// Filter out PHOTO data of non-images
963
-				$removingPhoto = true;
964
-				$modified = true;
965
-				continue;
966
-			}
967
-
968
-			if ($removingPhoto) {
969
-				if (strpos($line, ' ') === 0) {
970
-					continue;
971
-				}
972
-				// No leading space means this is a new property
973
-				$removingPhoto = false;
974
-			}
975
-
976
-			$cardDataFiltered[] = $line;
977
-		}
978
-
979
-		return implode("\r\n", $cardDataFiltered);
980
-	}
981
-
982
-	/**
983
-	 * @param IShareable $shareable
984
-	 * @param string[] $add
985
-	 * @param string[] $remove
986
-	 */
987
-	public function updateShares(IShareable $shareable, $add, $remove) {
988
-		$addressBookId = $shareable->getResourceId();
989
-		$addressBookData = $this->getAddressBookById($addressBookId);
990
-		$oldShares = $this->getShares($addressBookId);
991
-
992
-		$this->sharingBackend->updateShares($shareable, $add, $remove);
993
-
994
-		$this->dispatcher->dispatchTyped(new AddressBookShareUpdatedEvent($addressBookId, $addressBookData, $oldShares, $add, $remove));
995
-	}
996
-
997
-	/**
998
-	 * Search contacts in a specific address-book
999
-	 *
1000
-	 * @param int $addressBookId
1001
-	 * @param string $pattern which should match within the $searchProperties
1002
-	 * @param array $searchProperties defines the properties within the query pattern should match
1003
-	 * @param array $options = array() to define the search behavior
1004
-	 *    - 'escape_like_param' - If set to false wildcards _ and % are not escaped, otherwise they are
1005
-	 *    - 'limit' - Set a numeric limit for the search results
1006
-	 *    - 'offset' - Set the offset for the limited search results
1007
-	 * @return array an array of contacts which are arrays of key-value-pairs
1008
-	 */
1009
-	public function search($addressBookId, $pattern, $searchProperties, $options = []): array {
1010
-		return $this->searchByAddressBookIds([$addressBookId], $pattern, $searchProperties, $options);
1011
-	}
1012
-
1013
-	/**
1014
-	 * Search contacts in all address-books accessible by a user
1015
-	 *
1016
-	 * @param string $principalUri
1017
-	 * @param string $pattern
1018
-	 * @param array $searchProperties
1019
-	 * @param array $options
1020
-	 * @return array
1021
-	 */
1022
-	public function searchPrincipalUri(string $principalUri,
1023
-									   string $pattern,
1024
-									   array $searchProperties,
1025
-									   array $options = []): array {
1026
-		$addressBookIds = array_map(static function ($row):int {
1027
-			return (int) $row['id'];
1028
-		}, $this->getAddressBooksForUser($principalUri));
1029
-
1030
-		return $this->searchByAddressBookIds($addressBookIds, $pattern, $searchProperties, $options);
1031
-	}
1032
-
1033
-	/**
1034
-	 * @param array $addressBookIds
1035
-	 * @param string $pattern
1036
-	 * @param array $searchProperties
1037
-	 * @param array $options
1038
-	 * @return array
1039
-	 */
1040
-	private function searchByAddressBookIds(array $addressBookIds,
1041
-											string $pattern,
1042
-											array $searchProperties,
1043
-											array $options = []): array {
1044
-		$escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false;
1045
-
1046
-		$query2 = $this->db->getQueryBuilder();
1047
-
1048
-		$addressBookOr = $query2->expr()->orX();
1049
-		foreach ($addressBookIds as $addressBookId) {
1050
-			$addressBookOr->add($query2->expr()->eq('cp.addressbookid', $query2->createNamedParameter($addressBookId)));
1051
-		}
1052
-
1053
-		if ($addressBookOr->count() === 0) {
1054
-			return [];
1055
-		}
1056
-
1057
-		$propertyOr = $query2->expr()->orX();
1058
-		foreach ($searchProperties as $property) {
1059
-			if ($escapePattern) {
1060
-				if ($property === 'EMAIL' && strpos($pattern, ' ') !== false) {
1061
-					// There can be no spaces in emails
1062
-					continue;
1063
-				}
1064
-
1065
-				if ($property === 'CLOUD' && preg_match('/[^a-zA-Z0-9 :_.@\/\-\']/', $pattern) === 1) {
1066
-					// There can be no chars in cloud ids which are not valid for user ids plus :/
1067
-					// worst case: CA61590A-BBBC-423E-84AF-E6DF01455A53@https://my.nxt/srv/
1068
-					continue;
1069
-				}
1070
-			}
1071
-
1072
-			$propertyOr->add($query2->expr()->eq('cp.name', $query2->createNamedParameter($property)));
1073
-		}
1074
-
1075
-		if ($propertyOr->count() === 0) {
1076
-			return [];
1077
-		}
1078
-
1079
-		$query2->selectDistinct('cp.cardid')
1080
-			->from($this->dbCardsPropertiesTable, 'cp')
1081
-			->andWhere($addressBookOr)
1082
-			->andWhere($propertyOr);
1083
-
1084
-		// No need for like when the pattern is empty
1085
-		if ('' !== $pattern) {
1086
-			if (!$escapePattern) {
1087
-				$query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter($pattern)));
1088
-			} else {
1089
-				$query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
1090
-			}
1091
-		}
1092
-
1093
-		if (isset($options['limit'])) {
1094
-			$query2->setMaxResults($options['limit']);
1095
-		}
1096
-		if (isset($options['offset'])) {
1097
-			$query2->setFirstResult($options['offset']);
1098
-		}
1099
-
1100
-		$result = $query2->execute();
1101
-		$matches = $result->fetchAll();
1102
-		$result->closeCursor();
1103
-		$matches = array_map(function ($match) {
1104
-			return (int)$match['cardid'];
1105
-		}, $matches);
1106
-
1107
-		$query = $this->db->getQueryBuilder();
1108
-		$query->select('c.addressbookid', 'c.carddata', 'c.uri')
1109
-			->from($this->dbCardsTable, 'c')
1110
-			->where($query->expr()->in('c.id', $query->createNamedParameter($matches, IQueryBuilder::PARAM_INT_ARRAY)));
1111
-
1112
-		$result = $query->execute();
1113
-		$cards = $result->fetchAll();
1114
-
1115
-		$result->closeCursor();
1116
-
1117
-		return array_map(function ($array) {
1118
-			$array['addressbookid'] = (int) $array['addressbookid'];
1119
-			$modified = false;
1120
-			$array['carddata'] = $this->readBlob($array['carddata'], $modified);
1121
-			if ($modified) {
1122
-				$array['size'] = strlen($array['carddata']);
1123
-			}
1124
-			return $array;
1125
-		}, $cards);
1126
-	}
1127
-
1128
-	/**
1129
-	 * @param int $bookId
1130
-	 * @param string $name
1131
-	 * @return array
1132
-	 */
1133
-	public function collectCardProperties($bookId, $name) {
1134
-		$query = $this->db->getQueryBuilder();
1135
-		$result = $query->selectDistinct('value')
1136
-			->from($this->dbCardsPropertiesTable)
1137
-			->where($query->expr()->eq('name', $query->createNamedParameter($name)))
1138
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($bookId)))
1139
-			->execute();
1140
-
1141
-		$all = $result->fetchAll(PDO::FETCH_COLUMN);
1142
-		$result->closeCursor();
1143
-
1144
-		return $all;
1145
-	}
1146
-
1147
-	/**
1148
-	 * get URI from a given contact
1149
-	 *
1150
-	 * @param int $id
1151
-	 * @return string
1152
-	 */
1153
-	public function getCardUri($id) {
1154
-		$query = $this->db->getQueryBuilder();
1155
-		$query->select('uri')->from($this->dbCardsTable)
1156
-			->where($query->expr()->eq('id', $query->createParameter('id')))
1157
-			->setParameter('id', $id);
1158
-
1159
-		$result = $query->execute();
1160
-		$uri = $result->fetch();
1161
-		$result->closeCursor();
1162
-
1163
-		if (!isset($uri['uri'])) {
1164
-			throw new \InvalidArgumentException('Card does not exists: ' . $id);
1165
-		}
1166
-
1167
-		return $uri['uri'];
1168
-	}
1169
-
1170
-	/**
1171
-	 * return contact with the given URI
1172
-	 *
1173
-	 * @param int $addressBookId
1174
-	 * @param string $uri
1175
-	 * @returns array
1176
-	 */
1177
-	public function getContact($addressBookId, $uri) {
1178
-		$result = [];
1179
-		$query = $this->db->getQueryBuilder();
1180
-		$query->select('*')->from($this->dbCardsTable)
1181
-			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1182
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1183
-		$queryResult = $query->execute();
1184
-		$contact = $queryResult->fetch();
1185
-		$queryResult->closeCursor();
1186
-
1187
-		if (is_array($contact)) {
1188
-			$modified = false;
1189
-			$contact['etag'] = '"' . $contact['etag'] . '"';
1190
-			$contact['carddata'] = $this->readBlob($contact['carddata'], $modified);
1191
-			if ($modified) {
1192
-				$contact['size'] = strlen($contact['carddata']);
1193
-			}
1194
-
1195
-			$result = $contact;
1196
-		}
1197
-
1198
-		return $result;
1199
-	}
1200
-
1201
-	/**
1202
-	 * Returns the list of people whom this address book is shared with.
1203
-	 *
1204
-	 * Every element in this array should have the following properties:
1205
-	 *   * href - Often a mailto: address
1206
-	 *   * commonName - Optional, for example a first + last name
1207
-	 *   * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
1208
-	 *   * readOnly - boolean
1209
-	 *   * summary - Optional, a description for the share
1210
-	 *
1211
-	 * @return array
1212
-	 */
1213
-	public function getShares($addressBookId) {
1214
-		return $this->sharingBackend->getShares($addressBookId);
1215
-	}
1216
-
1217
-	/**
1218
-	 * update properties table
1219
-	 *
1220
-	 * @param int $addressBookId
1221
-	 * @param string $cardUri
1222
-	 * @param string $vCardSerialized
1223
-	 */
1224
-	protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) {
1225
-		$cardId = $this->getCardId($addressBookId, $cardUri);
1226
-		$vCard = $this->readCard($vCardSerialized);
1227
-
1228
-		$this->purgeProperties($addressBookId, $cardId);
1229
-
1230
-		$query = $this->db->getQueryBuilder();
1231
-		$query->insert($this->dbCardsPropertiesTable)
1232
-			->values(
1233
-				[
1234
-					'addressbookid' => $query->createNamedParameter($addressBookId),
1235
-					'cardid' => $query->createNamedParameter($cardId),
1236
-					'name' => $query->createParameter('name'),
1237
-					'value' => $query->createParameter('value'),
1238
-					'preferred' => $query->createParameter('preferred')
1239
-				]
1240
-			);
1241
-
1242
-		foreach ($vCard->children() as $property) {
1243
-			if (!in_array($property->name, self::$indexProperties)) {
1244
-				continue;
1245
-			}
1246
-			$preferred = 0;
1247
-			foreach ($property->parameters as $parameter) {
1248
-				if ($parameter->name === 'TYPE' && strtoupper($parameter->getValue()) === 'PREF') {
1249
-					$preferred = 1;
1250
-					break;
1251
-				}
1252
-			}
1253
-			$query->setParameter('name', $property->name);
1254
-			$query->setParameter('value', mb_substr($property->getValue(), 0, 254));
1255
-			$query->setParameter('preferred', $preferred);
1256
-			$query->execute();
1257
-		}
1258
-	}
1259
-
1260
-	/**
1261
-	 * read vCard data into a vCard object
1262
-	 *
1263
-	 * @param string $cardData
1264
-	 * @return VCard
1265
-	 */
1266
-	protected function readCard($cardData) {
1267
-		return Reader::read($cardData);
1268
-	}
1269
-
1270
-	/**
1271
-	 * delete all properties from a given card
1272
-	 *
1273
-	 * @param int $addressBookId
1274
-	 * @param int $cardId
1275
-	 */
1276
-	protected function purgeProperties($addressBookId, $cardId) {
1277
-		$query = $this->db->getQueryBuilder();
1278
-		$query->delete($this->dbCardsPropertiesTable)
1279
-			->where($query->expr()->eq('cardid', $query->createNamedParameter($cardId)))
1280
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1281
-		$query->execute();
1282
-	}
1283
-
1284
-	/**
1285
-	 * get ID from a given contact
1286
-	 *
1287
-	 * @param int $addressBookId
1288
-	 * @param string $uri
1289
-	 * @return int
1290
-	 */
1291
-	protected function getCardId($addressBookId, $uri) {
1292
-		$query = $this->db->getQueryBuilder();
1293
-		$query->select('id')->from($this->dbCardsTable)
1294
-			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1295
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1296
-
1297
-		$result = $query->execute();
1298
-		$cardIds = $result->fetch();
1299
-		$result->closeCursor();
1300
-
1301
-		if (!isset($cardIds['id'])) {
1302
-			throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1303
-		}
1304
-
1305
-		return (int)$cardIds['id'];
1306
-	}
1307
-
1308
-	/**
1309
-	 * For shared address books the sharee is set in the ACL of the address book
1310
-	 *
1311
-	 * @param $addressBookId
1312
-	 * @param $acl
1313
-	 * @return array
1314
-	 */
1315
-	public function applyShareAcl($addressBookId, $acl) {
1316
-		return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
1317
-	}
1318
-
1319
-	private function convertPrincipal($principalUri, $toV2) {
1320
-		if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
1321
-			list(, $name) = \Sabre\Uri\split($principalUri);
1322
-			if ($toV2 === true) {
1323
-				return "principals/users/$name";
1324
-			}
1325
-			return "principals/$name";
1326
-		}
1327
-		return $principalUri;
1328
-	}
1329
-
1330
-	private function addOwnerPrincipal(&$addressbookInfo) {
1331
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1332
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1333
-		if (isset($addressbookInfo[$ownerPrincipalKey])) {
1334
-			$uri = $addressbookInfo[$ownerPrincipalKey];
1335
-		} else {
1336
-			$uri = $addressbookInfo['principaluri'];
1337
-		}
1338
-
1339
-		$principalInformation = $this->principalBackend->getPrincipalByPath($uri);
1340
-		if (isset($principalInformation['{DAV:}displayname'])) {
1341
-			$addressbookInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
1342
-		}
1343
-	}
1344
-
1345
-	/**
1346
-	 * Extract UID from vcard
1347
-	 *
1348
-	 * @param string $cardData the vcard raw data
1349
-	 * @return string the uid
1350
-	 * @throws BadRequest if no UID is available
1351
-	 */
1352
-	private function getUID($cardData) {
1353
-		if ($cardData != '') {
1354
-			$vCard = Reader::read($cardData);
1355
-			if ($vCard->UID) {
1356
-				$uid = $vCard->UID->getValue();
1357
-				return $uid;
1358
-			}
1359
-			// should already be handled, but just in case
1360
-			throw new BadRequest('vCards on CardDAV servers MUST have a UID property');
1361
-		}
1362
-		// should already be handled, but just in case
1363
-		throw new BadRequest('vCard can not be empty');
1364
-	}
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',
87
+        'CLOUD', 'X-SOCIALPROFILE'];
88
+
89
+    /**
90
+     * @var string[] Map of uid => display name
91
+     */
92
+    protected $userDisplayNames;
93
+
94
+    /** @var IUserManager */
95
+    private $userManager;
96
+
97
+    /** @var IEventDispatcher */
98
+    private $dispatcher;
99
+
100
+    /** @var EventDispatcherInterface */
101
+    private $legacyDispatcher;
102
+
103
+    private $etagCache = [];
104
+
105
+    /**
106
+     * CardDavBackend constructor.
107
+     *
108
+     * @param IDBConnection $db
109
+     * @param Principal $principalBackend
110
+     * @param IUserManager $userManager
111
+     * @param IGroupManager $groupManager
112
+     * @param IEventDispatcher $dispatcher
113
+     * @param EventDispatcherInterface $legacyDispatcher
114
+     */
115
+    public function __construct(IDBConnection $db,
116
+                                Principal $principalBackend,
117
+                                IUserManager $userManager,
118
+                                IGroupManager $groupManager,
119
+                                IEventDispatcher $dispatcher,
120
+                                EventDispatcherInterface $legacyDispatcher) {
121
+        $this->db = $db;
122
+        $this->principalBackend = $principalBackend;
123
+        $this->userManager = $userManager;
124
+        $this->dispatcher = $dispatcher;
125
+        $this->legacyDispatcher = $legacyDispatcher;
126
+        $this->sharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'addressbook');
127
+    }
128
+
129
+    /**
130
+     * Return the number of address books for a principal
131
+     *
132
+     * @param $principalUri
133
+     * @return int
134
+     */
135
+    public function getAddressBooksForUserCount($principalUri) {
136
+        $principalUri = $this->convertPrincipal($principalUri, true);
137
+        $query = $this->db->getQueryBuilder();
138
+        $query->select($query->func()->count('*'))
139
+            ->from('addressbooks')
140
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
141
+
142
+        $result = $query->execute();
143
+        $column = (int) $result->fetchOne();
144
+        $result->closeCursor();
145
+        return $column;
146
+    }
147
+
148
+    /**
149
+     * Returns the list of address books for a specific user.
150
+     *
151
+     * Every addressbook should have the following properties:
152
+     *   id - an arbitrary unique id
153
+     *   uri - the 'basename' part of the url
154
+     *   principaluri - Same as the passed parameter
155
+     *
156
+     * Any additional clark-notation property may be passed besides this. Some
157
+     * common ones are :
158
+     *   {DAV:}displayname
159
+     *   {urn:ietf:params:xml:ns:carddav}addressbook-description
160
+     *   {http://calendarserver.org/ns/}getctag
161
+     *
162
+     * @param string $principalUri
163
+     * @return array
164
+     */
165
+    public function getAddressBooksForUser($principalUri) {
166
+        $principalUriOriginal = $principalUri;
167
+        $principalUri = $this->convertPrincipal($principalUri, true);
168
+        $query = $this->db->getQueryBuilder();
169
+        $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
170
+            ->from('addressbooks')
171
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
172
+
173
+        $addressBooks = [];
174
+
175
+        $result = $query->execute();
176
+        while ($row = $result->fetch()) {
177
+            $addressBooks[$row['id']] = [
178
+                'id' => $row['id'],
179
+                'uri' => $row['uri'],
180
+                'principaluri' => $this->convertPrincipal($row['principaluri'], false),
181
+                '{DAV:}displayname' => $row['displayname'],
182
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
183
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
184
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
185
+            ];
186
+
187
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
188
+        }
189
+        $result->closeCursor();
190
+
191
+        // query for shared addressbooks
192
+        $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
193
+        $principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
194
+
195
+        $principals[] = $principalUri;
196
+
197
+        $query = $this->db->getQueryBuilder();
198
+        $result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
199
+            ->from('dav_shares', 's')
200
+            ->join('s', 'addressbooks', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
201
+            ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
202
+            ->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
203
+            ->setParameter('type', 'addressbook')
204
+            ->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
205
+            ->execute();
206
+
207
+        $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
208
+        while ($row = $result->fetch()) {
209
+            if ($row['principaluri'] === $principalUri) {
210
+                continue;
211
+            }
212
+
213
+            $readOnly = (int)$row['access'] === Backend::ACCESS_READ;
214
+            if (isset($addressBooks[$row['id']])) {
215
+                if ($readOnly) {
216
+                    // New share can not have more permissions then the old one.
217
+                    continue;
218
+                }
219
+                if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) &&
220
+                    $addressBooks[$row['id']][$readOnlyPropertyName] === 0) {
221
+                    // Old share is already read-write, no more permissions can be gained
222
+                    continue;
223
+                }
224
+            }
225
+
226
+            list(, $name) = \Sabre\Uri\split($row['principaluri']);
227
+            $uri = $row['uri'] . '_shared_by_' . $name;
228
+            $displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
229
+
230
+            $addressBooks[$row['id']] = [
231
+                'id' => $row['id'],
232
+                'uri' => $uri,
233
+                'principaluri' => $principalUriOriginal,
234
+                '{DAV:}displayname' => $displayName,
235
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
236
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
237
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
238
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
239
+                $readOnlyPropertyName => $readOnly,
240
+            ];
241
+
242
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
243
+        }
244
+        $result->closeCursor();
245
+
246
+        return array_values($addressBooks);
247
+    }
248
+
249
+    public function getUsersOwnAddressBooks($principalUri) {
250
+        $principalUri = $this->convertPrincipal($principalUri, true);
251
+        $query = $this->db->getQueryBuilder();
252
+        $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
253
+            ->from('addressbooks')
254
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
255
+
256
+        $addressBooks = [];
257
+
258
+        $result = $query->execute();
259
+        while ($row = $result->fetch()) {
260
+            $addressBooks[$row['id']] = [
261
+                'id' => $row['id'],
262
+                'uri' => $row['uri'],
263
+                'principaluri' => $this->convertPrincipal($row['principaluri'], false),
264
+                '{DAV:}displayname' => $row['displayname'],
265
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
266
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
267
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
268
+            ];
269
+
270
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
271
+        }
272
+        $result->closeCursor();
273
+
274
+        return array_values($addressBooks);
275
+    }
276
+
277
+    private function getUserDisplayName($uid) {
278
+        if (!isset($this->userDisplayNames[$uid])) {
279
+            $user = $this->userManager->get($uid);
280
+
281
+            if ($user instanceof IUser) {
282
+                $this->userDisplayNames[$uid] = $user->getDisplayName();
283
+            } else {
284
+                $this->userDisplayNames[$uid] = $uid;
285
+            }
286
+        }
287
+
288
+        return $this->userDisplayNames[$uid];
289
+    }
290
+
291
+    /**
292
+     * @param int $addressBookId
293
+     */
294
+    public function getAddressBookById($addressBookId) {
295
+        $query = $this->db->getQueryBuilder();
296
+        $result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
297
+            ->from('addressbooks')
298
+            ->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
299
+            ->execute();
300
+
301
+        $row = $result->fetch();
302
+        $result->closeCursor();
303
+        if ($row === false) {
304
+            return null;
305
+        }
306
+
307
+        $addressBook = [
308
+            'id' => $row['id'],
309
+            'uri' => $row['uri'],
310
+            'principaluri' => $row['principaluri'],
311
+            '{DAV:}displayname' => $row['displayname'],
312
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
313
+            '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
314
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
315
+        ];
316
+
317
+        $this->addOwnerPrincipal($addressBook);
318
+
319
+        return $addressBook;
320
+    }
321
+
322
+    /**
323
+     * @param $addressBookUri
324
+     * @return array|null
325
+     */
326
+    public function getAddressBooksByUri($principal, $addressBookUri) {
327
+        $query = $this->db->getQueryBuilder();
328
+        $result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
329
+            ->from('addressbooks')
330
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri)))
331
+            ->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
332
+            ->setMaxResults(1)
333
+            ->execute();
334
+
335
+        $row = $result->fetch();
336
+        $result->closeCursor();
337
+        if ($row === false) {
338
+            return null;
339
+        }
340
+
341
+        $addressBook = [
342
+            'id' => $row['id'],
343
+            'uri' => $row['uri'],
344
+            'principaluri' => $row['principaluri'],
345
+            '{DAV:}displayname' => $row['displayname'],
346
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
347
+            '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
348
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
349
+        ];
350
+
351
+        $this->addOwnerPrincipal($addressBook);
352
+
353
+        return $addressBook;
354
+    }
355
+
356
+    /**
357
+     * Updates properties for an address book.
358
+     *
359
+     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
360
+     * To do the actual updates, you must tell this object which properties
361
+     * you're going to process with the handle() method.
362
+     *
363
+     * Calling the handle method is like telling the PropPatch object "I
364
+     * promise I can handle updating this property".
365
+     *
366
+     * Read the PropPatch documentation for more info and examples.
367
+     *
368
+     * @param string $addressBookId
369
+     * @param \Sabre\DAV\PropPatch $propPatch
370
+     * @return void
371
+     */
372
+    public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
373
+        $supportedProperties = [
374
+            '{DAV:}displayname',
375
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description',
376
+        ];
377
+
378
+        $propPatch->handle($supportedProperties, function ($mutations) use ($addressBookId) {
379
+            $updates = [];
380
+            foreach ($mutations as $property => $newValue) {
381
+                switch ($property) {
382
+                    case '{DAV:}displayname':
383
+                        $updates['displayname'] = $newValue;
384
+                        break;
385
+                    case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
386
+                        $updates['description'] = $newValue;
387
+                        break;
388
+                }
389
+            }
390
+            $query = $this->db->getQueryBuilder();
391
+            $query->update('addressbooks');
392
+
393
+            foreach ($updates as $key => $value) {
394
+                $query->set($key, $query->createNamedParameter($value));
395
+            }
396
+            $query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
397
+                ->execute();
398
+
399
+            $this->addChange($addressBookId, "", 2);
400
+
401
+            $addressBookRow = $this->getAddressBookById((int)$addressBookId);
402
+            $shares = $this->getShares($addressBookId);
403
+            $this->dispatcher->dispatchTyped(new AddressBookUpdatedEvent((int)$addressBookId, $addressBookRow, $shares, $mutations));
404
+
405
+            return true;
406
+        });
407
+    }
408
+
409
+    /**
410
+     * Creates a new address book
411
+     *
412
+     * @param string $principalUri
413
+     * @param string $url Just the 'basename' of the url.
414
+     * @param array $properties
415
+     * @return int
416
+     * @throws BadRequest
417
+     */
418
+    public function createAddressBook($principalUri, $url, array $properties) {
419
+        $values = [
420
+            'displayname' => null,
421
+            'description' => null,
422
+            'principaluri' => $principalUri,
423
+            'uri' => $url,
424
+            'synctoken' => 1
425
+        ];
426
+
427
+        foreach ($properties as $property => $newValue) {
428
+            switch ($property) {
429
+                case '{DAV:}displayname':
430
+                    $values['displayname'] = $newValue;
431
+                    break;
432
+                case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
433
+                    $values['description'] = $newValue;
434
+                    break;
435
+                default:
436
+                    throw new BadRequest('Unknown property: ' . $property);
437
+            }
438
+        }
439
+
440
+        // Fallback to make sure the displayname is set. Some clients may refuse
441
+        // to work with addressbooks not having a displayname.
442
+        if (is_null($values['displayname'])) {
443
+            $values['displayname'] = $url;
444
+        }
445
+
446
+        $query = $this->db->getQueryBuilder();
447
+        $query->insert('addressbooks')
448
+            ->values([
449
+                'uri' => $query->createParameter('uri'),
450
+                'displayname' => $query->createParameter('displayname'),
451
+                'description' => $query->createParameter('description'),
452
+                'principaluri' => $query->createParameter('principaluri'),
453
+                'synctoken' => $query->createParameter('synctoken'),
454
+            ])
455
+            ->setParameters($values)
456
+            ->execute();
457
+
458
+        $addressBookId = $query->getLastInsertId();
459
+        $addressBookRow = $this->getAddressBookById($addressBookId);
460
+        $this->dispatcher->dispatchTyped(new AddressBookCreatedEvent((int)$addressBookId, $addressBookRow));
461
+
462
+        return $addressBookId;
463
+    }
464
+
465
+    /**
466
+     * Deletes an entire addressbook and all its contents
467
+     *
468
+     * @param mixed $addressBookId
469
+     * @return void
470
+     */
471
+    public function deleteAddressBook($addressBookId) {
472
+        $addressBookData = $this->getAddressBookById($addressBookId);
473
+        $shares = $this->getShares($addressBookId);
474
+
475
+        $query = $this->db->getQueryBuilder();
476
+        $query->delete($this->dbCardsTable)
477
+            ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
478
+            ->setParameter('addressbookid', $addressBookId)
479
+            ->execute();
480
+
481
+        $query->delete('addressbookchanges')
482
+            ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
483
+            ->setParameter('addressbookid', $addressBookId)
484
+            ->execute();
485
+
486
+        $query->delete('addressbooks')
487
+            ->where($query->expr()->eq('id', $query->createParameter('id')))
488
+            ->setParameter('id', $addressBookId)
489
+            ->execute();
490
+
491
+        $this->sharingBackend->deleteAllShares($addressBookId);
492
+
493
+        $query->delete($this->dbCardsPropertiesTable)
494
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
495
+            ->execute();
496
+
497
+        if ($addressBookData) {
498
+            $this->dispatcher->dispatchTyped(new AddressBookDeletedEvent((int) $addressBookId, $addressBookData, $shares));
499
+        }
500
+    }
501
+
502
+    /**
503
+     * Returns all cards for a specific addressbook id.
504
+     *
505
+     * This method should return the following properties for each card:
506
+     *   * carddata - raw vcard data
507
+     *   * uri - Some unique url
508
+     *   * lastmodified - A unix timestamp
509
+     *
510
+     * It's recommended to also return the following properties:
511
+     *   * etag - A unique etag. This must change every time the card changes.
512
+     *   * size - The size of the card in bytes.
513
+     *
514
+     * If these last two properties are provided, less time will be spent
515
+     * calculating them. If they are specified, you can also ommit carddata.
516
+     * This may speed up certain requests, especially with large cards.
517
+     *
518
+     * @param mixed $addressBookId
519
+     * @return array
520
+     */
521
+    public function getCards($addressBookId) {
522
+        $query = $this->db->getQueryBuilder();
523
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
524
+            ->from($this->dbCardsTable)
525
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
526
+
527
+        $cards = [];
528
+
529
+        $result = $query->execute();
530
+        while ($row = $result->fetch()) {
531
+            $row['etag'] = '"' . $row['etag'] . '"';
532
+
533
+            $modified = false;
534
+            $row['carddata'] = $this->readBlob($row['carddata'], $modified);
535
+            if ($modified) {
536
+                $row['size'] = strlen($row['carddata']);
537
+            }
538
+
539
+            $cards[] = $row;
540
+        }
541
+        $result->closeCursor();
542
+
543
+        return $cards;
544
+    }
545
+
546
+    /**
547
+     * Returns a specific card.
548
+     *
549
+     * The same set of properties must be returned as with getCards. The only
550
+     * exception is that 'carddata' is absolutely required.
551
+     *
552
+     * If the card does not exist, you must return false.
553
+     *
554
+     * @param mixed $addressBookId
555
+     * @param string $cardUri
556
+     * @return array
557
+     */
558
+    public function getCard($addressBookId, $cardUri) {
559
+        $query = $this->db->getQueryBuilder();
560
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
561
+            ->from($this->dbCardsTable)
562
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
563
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
564
+            ->setMaxResults(1);
565
+
566
+        $result = $query->execute();
567
+        $row = $result->fetch();
568
+        if (!$row) {
569
+            return false;
570
+        }
571
+        $row['etag'] = '"' . $row['etag'] . '"';
572
+
573
+        $modified = false;
574
+        $row['carddata'] = $this->readBlob($row['carddata'], $modified);
575
+        if ($modified) {
576
+            $row['size'] = strlen($row['carddata']);
577
+        }
578
+
579
+        return $row;
580
+    }
581
+
582
+    /**
583
+     * Returns a list of cards.
584
+     *
585
+     * This method should work identical to getCard, but instead return all the
586
+     * cards in the list as an array.
587
+     *
588
+     * If the backend supports this, it may allow for some speed-ups.
589
+     *
590
+     * @param mixed $addressBookId
591
+     * @param string[] $uris
592
+     * @return array
593
+     */
594
+    public function getMultipleCards($addressBookId, array $uris) {
595
+        if (empty($uris)) {
596
+            return [];
597
+        }
598
+
599
+        $chunks = array_chunk($uris, 100);
600
+        $cards = [];
601
+
602
+        $query = $this->db->getQueryBuilder();
603
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
604
+            ->from($this->dbCardsTable)
605
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
606
+            ->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
607
+
608
+        foreach ($chunks as $uris) {
609
+            $query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
610
+            $result = $query->execute();
611
+
612
+            while ($row = $result->fetch()) {
613
+                $row['etag'] = '"' . $row['etag'] . '"';
614
+
615
+                $modified = false;
616
+                $row['carddata'] = $this->readBlob($row['carddata'], $modified);
617
+                if ($modified) {
618
+                    $row['size'] = strlen($row['carddata']);
619
+                }
620
+
621
+                $cards[] = $row;
622
+            }
623
+            $result->closeCursor();
624
+        }
625
+        return $cards;
626
+    }
627
+
628
+    /**
629
+     * Creates a new card.
630
+     *
631
+     * The addressbook id will be passed as the first argument. This is the
632
+     * same id as it is returned from the getAddressBooksForUser method.
633
+     *
634
+     * The cardUri is a base uri, and doesn't include the full path. The
635
+     * cardData argument is the vcard body, and is passed as a string.
636
+     *
637
+     * It is possible to return an ETag from this method. This ETag is for the
638
+     * newly created resource, and must be enclosed with double quotes (that
639
+     * is, the string itself must contain the double quotes).
640
+     *
641
+     * You should only return the ETag if you store the carddata as-is. If a
642
+     * subsequent GET request on the same card does not have the same body,
643
+     * byte-by-byte and you did return an ETag here, clients tend to get
644
+     * confused.
645
+     *
646
+     * If you don't return an ETag, you can just return null.
647
+     *
648
+     * @param mixed $addressBookId
649
+     * @param string $cardUri
650
+     * @param string $cardData
651
+     * @return string
652
+     */
653
+    public function createCard($addressBookId, $cardUri, $cardData) {
654
+        $etag = md5($cardData);
655
+        $uid = $this->getUID($cardData);
656
+
657
+        $q = $this->db->getQueryBuilder();
658
+        $q->select('uid')
659
+            ->from($this->dbCardsTable)
660
+            ->where($q->expr()->eq('addressbookid', $q->createNamedParameter($addressBookId)))
661
+            ->andWhere($q->expr()->eq('uid', $q->createNamedParameter($uid)))
662
+            ->setMaxResults(1);
663
+        $result = $q->execute();
664
+        $count = (bool)$result->fetchOne();
665
+        $result->closeCursor();
666
+        if ($count) {
667
+            throw new \Sabre\DAV\Exception\BadRequest('VCard object with uid already exists in this addressbook collection.');
668
+        }
669
+
670
+        $query = $this->db->getQueryBuilder();
671
+        $query->insert('cards')
672
+            ->values([
673
+                'carddata' => $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB),
674
+                'uri' => $query->createNamedParameter($cardUri),
675
+                'lastmodified' => $query->createNamedParameter(time()),
676
+                'addressbookid' => $query->createNamedParameter($addressBookId),
677
+                'size' => $query->createNamedParameter(strlen($cardData)),
678
+                'etag' => $query->createNamedParameter($etag),
679
+                'uid' => $query->createNamedParameter($uid),
680
+            ])
681
+            ->execute();
682
+
683
+        $etagCacheKey = "$addressBookId#$cardUri";
684
+        $this->etagCache[$etagCacheKey] = $etag;
685
+
686
+        $this->addChange($addressBookId, $cardUri, 1);
687
+        $this->updateProperties($addressBookId, $cardUri, $cardData);
688
+
689
+        $addressBookData = $this->getAddressBookById($addressBookId);
690
+        $shares = $this->getShares($addressBookId);
691
+        $objectRow = $this->getCard($addressBookId, $cardUri);
692
+        $this->dispatcher->dispatchTyped(new CardCreatedEvent((int)$addressBookId, $addressBookData, $shares, $objectRow));
693
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
694
+            new GenericEvent(null, [
695
+                'addressBookId' => $addressBookId,
696
+                'cardUri' => $cardUri,
697
+                'cardData' => $cardData]));
698
+
699
+        return '"' . $etag . '"';
700
+    }
701
+
702
+    /**
703
+     * Updates a card.
704
+     *
705
+     * The addressbook id will be passed as the first argument. This is the
706
+     * same id as it is returned from the getAddressBooksForUser method.
707
+     *
708
+     * The cardUri is a base uri, and doesn't include the full path. The
709
+     * cardData argument is the vcard body, and is passed as a string.
710
+     *
711
+     * It is possible to return an ETag from this method. This ETag should
712
+     * match that of the updated resource, and must be enclosed with double
713
+     * quotes (that is: the string itself must contain the actual quotes).
714
+     *
715
+     * You should only return the ETag if you store the carddata as-is. If a
716
+     * subsequent GET request on the same card does not have the same body,
717
+     * byte-by-byte and you did return an ETag here, clients tend to get
718
+     * confused.
719
+     *
720
+     * If you don't return an ETag, you can just return null.
721
+     *
722
+     * @param mixed $addressBookId
723
+     * @param string $cardUri
724
+     * @param string $cardData
725
+     * @return string
726
+     */
727
+    public function updateCard($addressBookId, $cardUri, $cardData) {
728
+        $uid = $this->getUID($cardData);
729
+        $etag = md5($cardData);
730
+        $query = $this->db->getQueryBuilder();
731
+
732
+        // check for recently stored etag and stop if it is the same
733
+        $etagCacheKey = "$addressBookId#$cardUri";
734
+        if (isset($this->etagCache[$etagCacheKey]) && $this->etagCache[$etagCacheKey] === $etag) {
735
+            return '"' . $etag . '"';
736
+        }
737
+
738
+        $query->update($this->dbCardsTable)
739
+            ->set('carddata', $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB))
740
+            ->set('lastmodified', $query->createNamedParameter(time()))
741
+            ->set('size', $query->createNamedParameter(strlen($cardData)))
742
+            ->set('etag', $query->createNamedParameter($etag))
743
+            ->set('uid', $query->createNamedParameter($uid))
744
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
745
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
746
+            ->execute();
747
+
748
+        $this->etagCache[$etagCacheKey] = $etag;
749
+
750
+        $this->addChange($addressBookId, $cardUri, 2);
751
+        $this->updateProperties($addressBookId, $cardUri, $cardData);
752
+
753
+        $addressBookData = $this->getAddressBookById($addressBookId);
754
+        $shares = $this->getShares($addressBookId);
755
+        $objectRow = $this->getCard($addressBookId, $cardUri);
756
+        $this->dispatcher->dispatchTyped(new CardUpdatedEvent((int)$addressBookId, $addressBookData, $shares, $objectRow));
757
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
758
+            new GenericEvent(null, [
759
+                'addressBookId' => $addressBookId,
760
+                'cardUri' => $cardUri,
761
+                'cardData' => $cardData]));
762
+
763
+        return '"' . $etag . '"';
764
+    }
765
+
766
+    /**
767
+     * Deletes a card
768
+     *
769
+     * @param mixed $addressBookId
770
+     * @param string $cardUri
771
+     * @return bool
772
+     */
773
+    public function deleteCard($addressBookId, $cardUri) {
774
+        $addressBookData = $this->getAddressBookById($addressBookId);
775
+        $shares = $this->getShares($addressBookId);
776
+        $objectRow = $this->getCard($addressBookId, $cardUri);
777
+
778
+        try {
779
+            $cardId = $this->getCardId($addressBookId, $cardUri);
780
+        } catch (\InvalidArgumentException $e) {
781
+            $cardId = null;
782
+        }
783
+        $query = $this->db->getQueryBuilder();
784
+        $ret = $query->delete($this->dbCardsTable)
785
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
786
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
787
+            ->execute();
788
+
789
+        $this->addChange($addressBookId, $cardUri, 3);
790
+
791
+        if ($ret === 1) {
792
+            if ($cardId !== null) {
793
+                $this->dispatcher->dispatchTyped(new CardDeletedEvent((int)$addressBookId, $addressBookData, $shares, $objectRow));
794
+                $this->legacyDispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
795
+                    new GenericEvent(null, [
796
+                        'addressBookId' => $addressBookId,
797
+                        'cardUri' => $cardUri]));
798
+
799
+                $this->purgeProperties($addressBookId, $cardId);
800
+            }
801
+            return true;
802
+        }
803
+
804
+        return false;
805
+    }
806
+
807
+    /**
808
+     * The getChanges method returns all the changes that have happened, since
809
+     * the specified syncToken in the specified address book.
810
+     *
811
+     * This function should return an array, such as the following:
812
+     *
813
+     * [
814
+     *   'syncToken' => 'The current synctoken',
815
+     *   'added'   => [
816
+     *      'new.txt',
817
+     *   ],
818
+     *   'modified'   => [
819
+     *      'modified.txt',
820
+     *   ],
821
+     *   'deleted' => [
822
+     *      'foo.php.bak',
823
+     *      'old.txt'
824
+     *   ]
825
+     * ];
826
+     *
827
+     * The returned syncToken property should reflect the *current* syncToken
828
+     * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
829
+     * property. This is needed here too, to ensure the operation is atomic.
830
+     *
831
+     * If the $syncToken argument is specified as null, this is an initial
832
+     * sync, and all members should be reported.
833
+     *
834
+     * The modified property is an array of nodenames that have changed since
835
+     * the last token.
836
+     *
837
+     * The deleted property is an array with nodenames, that have been deleted
838
+     * from collection.
839
+     *
840
+     * The $syncLevel argument is basically the 'depth' of the report. If it's
841
+     * 1, you only have to report changes that happened only directly in
842
+     * immediate descendants. If it's 2, it should also include changes from
843
+     * the nodes below the child collections. (grandchildren)
844
+     *
845
+     * The $limit argument allows a client to specify how many results should
846
+     * be returned at most. If the limit is not specified, it should be treated
847
+     * as infinite.
848
+     *
849
+     * If the limit (infinite or not) is higher than you're willing to return,
850
+     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
851
+     *
852
+     * If the syncToken is expired (due to data cleanup) or unknown, you must
853
+     * return null.
854
+     *
855
+     * The limit is 'suggestive'. You are free to ignore it.
856
+     *
857
+     * @param string $addressBookId
858
+     * @param string $syncToken
859
+     * @param int $syncLevel
860
+     * @param int $limit
861
+     * @return array
862
+     */
863
+    public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
864
+        // Current synctoken
865
+        $stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
866
+        $stmt->execute([$addressBookId]);
867
+        $currentToken = $stmt->fetchOne();
868
+
869
+        if (is_null($currentToken)) {
870
+            return null;
871
+        }
872
+
873
+        $result = [
874
+            'syncToken' => $currentToken,
875
+            'added' => [],
876
+            'modified' => [],
877
+            'deleted' => [],
878
+        ];
879
+
880
+        if ($syncToken) {
881
+            $query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
882
+            if ($limit > 0) {
883
+                $query .= " LIMIT " . (int)$limit;
884
+            }
885
+
886
+            // Fetching all changes
887
+            $stmt = $this->db->prepare($query);
888
+            $stmt->execute([$syncToken, $currentToken, $addressBookId]);
889
+
890
+            $changes = [];
891
+
892
+            // This loop ensures that any duplicates are overwritten, only the
893
+            // last change on a node is relevant.
894
+            while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
895
+                $changes[$row['uri']] = $row['operation'];
896
+            }
897
+
898
+            foreach ($changes as $uri => $operation) {
899
+                switch ($operation) {
900
+                    case 1:
901
+                        $result['added'][] = $uri;
902
+                        break;
903
+                    case 2:
904
+                        $result['modified'][] = $uri;
905
+                        break;
906
+                    case 3:
907
+                        $result['deleted'][] = $uri;
908
+                        break;
909
+                }
910
+            }
911
+        } else {
912
+            // No synctoken supplied, this is the initial sync.
913
+            $query = "SELECT `uri` FROM `*PREFIX*cards` WHERE `addressbookid` = ?";
914
+            $stmt = $this->db->prepare($query);
915
+            $stmt->execute([$addressBookId]);
916
+
917
+            $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
918
+        }
919
+        return $result;
920
+    }
921
+
922
+    /**
923
+     * Adds a change record to the addressbookchanges table.
924
+     *
925
+     * @param mixed $addressBookId
926
+     * @param string $objectUri
927
+     * @param int $operation 1 = add, 2 = modify, 3 = delete
928
+     * @return void
929
+     */
930
+    protected function addChange($addressBookId, $objectUri, $operation) {
931
+        $sql = 'INSERT INTO `*PREFIX*addressbookchanges`(`uri`, `synctoken`, `addressbookid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*addressbooks` WHERE `id` = ?';
932
+        $stmt = $this->db->prepare($sql);
933
+        $stmt->execute([
934
+            $objectUri,
935
+            $addressBookId,
936
+            $operation,
937
+            $addressBookId
938
+        ]);
939
+        $stmt = $this->db->prepare('UPDATE `*PREFIX*addressbooks` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
940
+        $stmt->execute([
941
+            $addressBookId
942
+        ]);
943
+    }
944
+
945
+    /**
946
+     * @param resource|string $cardData
947
+     * @param bool $modified
948
+     * @return string
949
+     */
950
+    private function readBlob($cardData, &$modified = false) {
951
+        if (is_resource($cardData)) {
952
+            $cardData = stream_get_contents($cardData);
953
+        }
954
+
955
+        $cardDataArray = explode("\r\n", $cardData);
956
+
957
+        $cardDataFiltered = [];
958
+        $removingPhoto = false;
959
+        foreach ($cardDataArray as $line) {
960
+            if (strpos($line, 'PHOTO:data:') === 0
961
+                && strpos($line, 'PHOTO:data:image/') !== 0) {
962
+                // Filter out PHOTO data of non-images
963
+                $removingPhoto = true;
964
+                $modified = true;
965
+                continue;
966
+            }
967
+
968
+            if ($removingPhoto) {
969
+                if (strpos($line, ' ') === 0) {
970
+                    continue;
971
+                }
972
+                // No leading space means this is a new property
973
+                $removingPhoto = false;
974
+            }
975
+
976
+            $cardDataFiltered[] = $line;
977
+        }
978
+
979
+        return implode("\r\n", $cardDataFiltered);
980
+    }
981
+
982
+    /**
983
+     * @param IShareable $shareable
984
+     * @param string[] $add
985
+     * @param string[] $remove
986
+     */
987
+    public function updateShares(IShareable $shareable, $add, $remove) {
988
+        $addressBookId = $shareable->getResourceId();
989
+        $addressBookData = $this->getAddressBookById($addressBookId);
990
+        $oldShares = $this->getShares($addressBookId);
991
+
992
+        $this->sharingBackend->updateShares($shareable, $add, $remove);
993
+
994
+        $this->dispatcher->dispatchTyped(new AddressBookShareUpdatedEvent($addressBookId, $addressBookData, $oldShares, $add, $remove));
995
+    }
996
+
997
+    /**
998
+     * Search contacts in a specific address-book
999
+     *
1000
+     * @param int $addressBookId
1001
+     * @param string $pattern which should match within the $searchProperties
1002
+     * @param array $searchProperties defines the properties within the query pattern should match
1003
+     * @param array $options = array() to define the search behavior
1004
+     *    - 'escape_like_param' - If set to false wildcards _ and % are not escaped, otherwise they are
1005
+     *    - 'limit' - Set a numeric limit for the search results
1006
+     *    - 'offset' - Set the offset for the limited search results
1007
+     * @return array an array of contacts which are arrays of key-value-pairs
1008
+     */
1009
+    public function search($addressBookId, $pattern, $searchProperties, $options = []): array {
1010
+        return $this->searchByAddressBookIds([$addressBookId], $pattern, $searchProperties, $options);
1011
+    }
1012
+
1013
+    /**
1014
+     * Search contacts in all address-books accessible by a user
1015
+     *
1016
+     * @param string $principalUri
1017
+     * @param string $pattern
1018
+     * @param array $searchProperties
1019
+     * @param array $options
1020
+     * @return array
1021
+     */
1022
+    public function searchPrincipalUri(string $principalUri,
1023
+                                        string $pattern,
1024
+                                        array $searchProperties,
1025
+                                        array $options = []): array {
1026
+        $addressBookIds = array_map(static function ($row):int {
1027
+            return (int) $row['id'];
1028
+        }, $this->getAddressBooksForUser($principalUri));
1029
+
1030
+        return $this->searchByAddressBookIds($addressBookIds, $pattern, $searchProperties, $options);
1031
+    }
1032
+
1033
+    /**
1034
+     * @param array $addressBookIds
1035
+     * @param string $pattern
1036
+     * @param array $searchProperties
1037
+     * @param array $options
1038
+     * @return array
1039
+     */
1040
+    private function searchByAddressBookIds(array $addressBookIds,
1041
+                                            string $pattern,
1042
+                                            array $searchProperties,
1043
+                                            array $options = []): array {
1044
+        $escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false;
1045
+
1046
+        $query2 = $this->db->getQueryBuilder();
1047
+
1048
+        $addressBookOr = $query2->expr()->orX();
1049
+        foreach ($addressBookIds as $addressBookId) {
1050
+            $addressBookOr->add($query2->expr()->eq('cp.addressbookid', $query2->createNamedParameter($addressBookId)));
1051
+        }
1052
+
1053
+        if ($addressBookOr->count() === 0) {
1054
+            return [];
1055
+        }
1056
+
1057
+        $propertyOr = $query2->expr()->orX();
1058
+        foreach ($searchProperties as $property) {
1059
+            if ($escapePattern) {
1060
+                if ($property === 'EMAIL' && strpos($pattern, ' ') !== false) {
1061
+                    // There can be no spaces in emails
1062
+                    continue;
1063
+                }
1064
+
1065
+                if ($property === 'CLOUD' && preg_match('/[^a-zA-Z0-9 :_.@\/\-\']/', $pattern) === 1) {
1066
+                    // There can be no chars in cloud ids which are not valid for user ids plus :/
1067
+                    // worst case: CA61590A-BBBC-423E-84AF-E6DF01455A53@https://my.nxt/srv/
1068
+                    continue;
1069
+                }
1070
+            }
1071
+
1072
+            $propertyOr->add($query2->expr()->eq('cp.name', $query2->createNamedParameter($property)));
1073
+        }
1074
+
1075
+        if ($propertyOr->count() === 0) {
1076
+            return [];
1077
+        }
1078
+
1079
+        $query2->selectDistinct('cp.cardid')
1080
+            ->from($this->dbCardsPropertiesTable, 'cp')
1081
+            ->andWhere($addressBookOr)
1082
+            ->andWhere($propertyOr);
1083
+
1084
+        // No need for like when the pattern is empty
1085
+        if ('' !== $pattern) {
1086
+            if (!$escapePattern) {
1087
+                $query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter($pattern)));
1088
+            } else {
1089
+                $query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
1090
+            }
1091
+        }
1092
+
1093
+        if (isset($options['limit'])) {
1094
+            $query2->setMaxResults($options['limit']);
1095
+        }
1096
+        if (isset($options['offset'])) {
1097
+            $query2->setFirstResult($options['offset']);
1098
+        }
1099
+
1100
+        $result = $query2->execute();
1101
+        $matches = $result->fetchAll();
1102
+        $result->closeCursor();
1103
+        $matches = array_map(function ($match) {
1104
+            return (int)$match['cardid'];
1105
+        }, $matches);
1106
+
1107
+        $query = $this->db->getQueryBuilder();
1108
+        $query->select('c.addressbookid', 'c.carddata', 'c.uri')
1109
+            ->from($this->dbCardsTable, 'c')
1110
+            ->where($query->expr()->in('c.id', $query->createNamedParameter($matches, IQueryBuilder::PARAM_INT_ARRAY)));
1111
+
1112
+        $result = $query->execute();
1113
+        $cards = $result->fetchAll();
1114
+
1115
+        $result->closeCursor();
1116
+
1117
+        return array_map(function ($array) {
1118
+            $array['addressbookid'] = (int) $array['addressbookid'];
1119
+            $modified = false;
1120
+            $array['carddata'] = $this->readBlob($array['carddata'], $modified);
1121
+            if ($modified) {
1122
+                $array['size'] = strlen($array['carddata']);
1123
+            }
1124
+            return $array;
1125
+        }, $cards);
1126
+    }
1127
+
1128
+    /**
1129
+     * @param int $bookId
1130
+     * @param string $name
1131
+     * @return array
1132
+     */
1133
+    public function collectCardProperties($bookId, $name) {
1134
+        $query = $this->db->getQueryBuilder();
1135
+        $result = $query->selectDistinct('value')
1136
+            ->from($this->dbCardsPropertiesTable)
1137
+            ->where($query->expr()->eq('name', $query->createNamedParameter($name)))
1138
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($bookId)))
1139
+            ->execute();
1140
+
1141
+        $all = $result->fetchAll(PDO::FETCH_COLUMN);
1142
+        $result->closeCursor();
1143
+
1144
+        return $all;
1145
+    }
1146
+
1147
+    /**
1148
+     * get URI from a given contact
1149
+     *
1150
+     * @param int $id
1151
+     * @return string
1152
+     */
1153
+    public function getCardUri($id) {
1154
+        $query = $this->db->getQueryBuilder();
1155
+        $query->select('uri')->from($this->dbCardsTable)
1156
+            ->where($query->expr()->eq('id', $query->createParameter('id')))
1157
+            ->setParameter('id', $id);
1158
+
1159
+        $result = $query->execute();
1160
+        $uri = $result->fetch();
1161
+        $result->closeCursor();
1162
+
1163
+        if (!isset($uri['uri'])) {
1164
+            throw new \InvalidArgumentException('Card does not exists: ' . $id);
1165
+        }
1166
+
1167
+        return $uri['uri'];
1168
+    }
1169
+
1170
+    /**
1171
+     * return contact with the given URI
1172
+     *
1173
+     * @param int $addressBookId
1174
+     * @param string $uri
1175
+     * @returns array
1176
+     */
1177
+    public function getContact($addressBookId, $uri) {
1178
+        $result = [];
1179
+        $query = $this->db->getQueryBuilder();
1180
+        $query->select('*')->from($this->dbCardsTable)
1181
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1182
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1183
+        $queryResult = $query->execute();
1184
+        $contact = $queryResult->fetch();
1185
+        $queryResult->closeCursor();
1186
+
1187
+        if (is_array($contact)) {
1188
+            $modified = false;
1189
+            $contact['etag'] = '"' . $contact['etag'] . '"';
1190
+            $contact['carddata'] = $this->readBlob($contact['carddata'], $modified);
1191
+            if ($modified) {
1192
+                $contact['size'] = strlen($contact['carddata']);
1193
+            }
1194
+
1195
+            $result = $contact;
1196
+        }
1197
+
1198
+        return $result;
1199
+    }
1200
+
1201
+    /**
1202
+     * Returns the list of people whom this address book is shared with.
1203
+     *
1204
+     * Every element in this array should have the following properties:
1205
+     *   * href - Often a mailto: address
1206
+     *   * commonName - Optional, for example a first + last name
1207
+     *   * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
1208
+     *   * readOnly - boolean
1209
+     *   * summary - Optional, a description for the share
1210
+     *
1211
+     * @return array
1212
+     */
1213
+    public function getShares($addressBookId) {
1214
+        return $this->sharingBackend->getShares($addressBookId);
1215
+    }
1216
+
1217
+    /**
1218
+     * update properties table
1219
+     *
1220
+     * @param int $addressBookId
1221
+     * @param string $cardUri
1222
+     * @param string $vCardSerialized
1223
+     */
1224
+    protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) {
1225
+        $cardId = $this->getCardId($addressBookId, $cardUri);
1226
+        $vCard = $this->readCard($vCardSerialized);
1227
+
1228
+        $this->purgeProperties($addressBookId, $cardId);
1229
+
1230
+        $query = $this->db->getQueryBuilder();
1231
+        $query->insert($this->dbCardsPropertiesTable)
1232
+            ->values(
1233
+                [
1234
+                    'addressbookid' => $query->createNamedParameter($addressBookId),
1235
+                    'cardid' => $query->createNamedParameter($cardId),
1236
+                    'name' => $query->createParameter('name'),
1237
+                    'value' => $query->createParameter('value'),
1238
+                    'preferred' => $query->createParameter('preferred')
1239
+                ]
1240
+            );
1241
+
1242
+        foreach ($vCard->children() as $property) {
1243
+            if (!in_array($property->name, self::$indexProperties)) {
1244
+                continue;
1245
+            }
1246
+            $preferred = 0;
1247
+            foreach ($property->parameters as $parameter) {
1248
+                if ($parameter->name === 'TYPE' && strtoupper($parameter->getValue()) === 'PREF') {
1249
+                    $preferred = 1;
1250
+                    break;
1251
+                }
1252
+            }
1253
+            $query->setParameter('name', $property->name);
1254
+            $query->setParameter('value', mb_substr($property->getValue(), 0, 254));
1255
+            $query->setParameter('preferred', $preferred);
1256
+            $query->execute();
1257
+        }
1258
+    }
1259
+
1260
+    /**
1261
+     * read vCard data into a vCard object
1262
+     *
1263
+     * @param string $cardData
1264
+     * @return VCard
1265
+     */
1266
+    protected function readCard($cardData) {
1267
+        return Reader::read($cardData);
1268
+    }
1269
+
1270
+    /**
1271
+     * delete all properties from a given card
1272
+     *
1273
+     * @param int $addressBookId
1274
+     * @param int $cardId
1275
+     */
1276
+    protected function purgeProperties($addressBookId, $cardId) {
1277
+        $query = $this->db->getQueryBuilder();
1278
+        $query->delete($this->dbCardsPropertiesTable)
1279
+            ->where($query->expr()->eq('cardid', $query->createNamedParameter($cardId)))
1280
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1281
+        $query->execute();
1282
+    }
1283
+
1284
+    /**
1285
+     * get ID from a given contact
1286
+     *
1287
+     * @param int $addressBookId
1288
+     * @param string $uri
1289
+     * @return int
1290
+     */
1291
+    protected function getCardId($addressBookId, $uri) {
1292
+        $query = $this->db->getQueryBuilder();
1293
+        $query->select('id')->from($this->dbCardsTable)
1294
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1295
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1296
+
1297
+        $result = $query->execute();
1298
+        $cardIds = $result->fetch();
1299
+        $result->closeCursor();
1300
+
1301
+        if (!isset($cardIds['id'])) {
1302
+            throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1303
+        }
1304
+
1305
+        return (int)$cardIds['id'];
1306
+    }
1307
+
1308
+    /**
1309
+     * For shared address books the sharee is set in the ACL of the address book
1310
+     *
1311
+     * @param $addressBookId
1312
+     * @param $acl
1313
+     * @return array
1314
+     */
1315
+    public function applyShareAcl($addressBookId, $acl) {
1316
+        return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
1317
+    }
1318
+
1319
+    private function convertPrincipal($principalUri, $toV2) {
1320
+        if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
1321
+            list(, $name) = \Sabre\Uri\split($principalUri);
1322
+            if ($toV2 === true) {
1323
+                return "principals/users/$name";
1324
+            }
1325
+            return "principals/$name";
1326
+        }
1327
+        return $principalUri;
1328
+    }
1329
+
1330
+    private function addOwnerPrincipal(&$addressbookInfo) {
1331
+        $ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1332
+        $displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1333
+        if (isset($addressbookInfo[$ownerPrincipalKey])) {
1334
+            $uri = $addressbookInfo[$ownerPrincipalKey];
1335
+        } else {
1336
+            $uri = $addressbookInfo['principaluri'];
1337
+        }
1338
+
1339
+        $principalInformation = $this->principalBackend->getPrincipalByPath($uri);
1340
+        if (isset($principalInformation['{DAV:}displayname'])) {
1341
+            $addressbookInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
1342
+        }
1343
+    }
1344
+
1345
+    /**
1346
+     * Extract UID from vcard
1347
+     *
1348
+     * @param string $cardData the vcard raw data
1349
+     * @return string the uid
1350
+     * @throws BadRequest if no UID is available
1351
+     */
1352
+    private function getUID($cardData) {
1353
+        if ($cardData != '') {
1354
+            $vCard = Reader::read($cardData);
1355
+            if ($vCard->UID) {
1356
+                $uid = $vCard->UID->getValue();
1357
+                return $uid;
1358
+            }
1359
+            // should already be handled, but just in case
1360
+            throw new BadRequest('vCards on CardDAV servers MUST have a UID property');
1361
+        }
1362
+        // should already be handled, but just in case
1363
+        throw new BadRequest('vCard can not be empty');
1364
+    }
1365 1365
 }
Please login to merge, or discard this patch.
Spacing   +40 added lines, -40 removed lines patch added patch discarded remove patch
@@ -179,7 +179,7 @@  discard block
 block discarded – undo
179 179
 				'uri' => $row['uri'],
180 180
 				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
181 181
 				'{DAV:}displayname' => $row['displayname'],
182
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
182
+				'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
183 183
 				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
184 184
 				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
185 185
 			];
@@ -204,13 +204,13 @@  discard block
 block discarded – undo
204 204
 			->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
205 205
 			->execute();
206 206
 
207
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
207
+		$readOnlyPropertyName = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}read-only';
208 208
 		while ($row = $result->fetch()) {
209 209
 			if ($row['principaluri'] === $principalUri) {
210 210
 				continue;
211 211
 			}
212 212
 
213
-			$readOnly = (int)$row['access'] === Backend::ACCESS_READ;
213
+			$readOnly = (int) $row['access'] === Backend::ACCESS_READ;
214 214
 			if (isset($addressBooks[$row['id']])) {
215 215
 				if ($readOnly) {
216 216
 					// New share can not have more permissions then the old one.
@@ -224,18 +224,18 @@  discard block
 block discarded – undo
224 224
 			}
225 225
 
226 226
 			list(, $name) = \Sabre\Uri\split($row['principaluri']);
227
-			$uri = $row['uri'] . '_shared_by_' . $name;
228
-			$displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
227
+			$uri = $row['uri'].'_shared_by_'.$name;
228
+			$displayName = $row['displayname'].' ('.$this->getUserDisplayName($name).')';
229 229
 
230 230
 			$addressBooks[$row['id']] = [
231 231
 				'id' => $row['id'],
232 232
 				'uri' => $uri,
233 233
 				'principaluri' => $principalUriOriginal,
234 234
 				'{DAV:}displayname' => $displayName,
235
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
235
+				'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
236 236
 				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
237 237
 				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
238
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
238
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $row['principaluri'],
239 239
 				$readOnlyPropertyName => $readOnly,
240 240
 			];
241 241
 
@@ -262,7 +262,7 @@  discard block
 block discarded – undo
262 262
 				'uri' => $row['uri'],
263 263
 				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
264 264
 				'{DAV:}displayname' => $row['displayname'],
265
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
265
+				'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
266 266
 				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
267 267
 				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
268 268
 			];
@@ -309,7 +309,7 @@  discard block
 block discarded – undo
309 309
 			'uri' => $row['uri'],
310 310
 			'principaluri' => $row['principaluri'],
311 311
 			'{DAV:}displayname' => $row['displayname'],
312
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
312
+			'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
313 313
 			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
314 314
 			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
315 315
 		];
@@ -343,7 +343,7 @@  discard block
 block discarded – undo
343 343
 			'uri' => $row['uri'],
344 344
 			'principaluri' => $row['principaluri'],
345 345
 			'{DAV:}displayname' => $row['displayname'],
346
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
346
+			'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
347 347
 			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
348 348
 			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
349 349
 		];
@@ -372,17 +372,17 @@  discard block
 block discarded – undo
372 372
 	public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
373 373
 		$supportedProperties = [
374 374
 			'{DAV:}displayname',
375
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description',
375
+			'{'.Plugin::NS_CARDDAV.'}addressbook-description',
376 376
 		];
377 377
 
378
-		$propPatch->handle($supportedProperties, function ($mutations) use ($addressBookId) {
378
+		$propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) {
379 379
 			$updates = [];
380 380
 			foreach ($mutations as $property => $newValue) {
381 381
 				switch ($property) {
382 382
 					case '{DAV:}displayname':
383 383
 						$updates['displayname'] = $newValue;
384 384
 						break;
385
-					case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
385
+					case '{'.Plugin::NS_CARDDAV.'}addressbook-description':
386 386
 						$updates['description'] = $newValue;
387 387
 						break;
388 388
 				}
@@ -398,9 +398,9 @@  discard block
 block discarded – undo
398 398
 
399 399
 			$this->addChange($addressBookId, "", 2);
400 400
 
401
-			$addressBookRow = $this->getAddressBookById((int)$addressBookId);
401
+			$addressBookRow = $this->getAddressBookById((int) $addressBookId);
402 402
 			$shares = $this->getShares($addressBookId);
403
-			$this->dispatcher->dispatchTyped(new AddressBookUpdatedEvent((int)$addressBookId, $addressBookRow, $shares, $mutations));
403
+			$this->dispatcher->dispatchTyped(new AddressBookUpdatedEvent((int) $addressBookId, $addressBookRow, $shares, $mutations));
404 404
 
405 405
 			return true;
406 406
 		});
@@ -429,11 +429,11 @@  discard block
 block discarded – undo
429 429
 				case '{DAV:}displayname':
430 430
 					$values['displayname'] = $newValue;
431 431
 					break;
432
-				case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
432
+				case '{'.Plugin::NS_CARDDAV.'}addressbook-description':
433 433
 					$values['description'] = $newValue;
434 434
 					break;
435 435
 				default:
436
-					throw new BadRequest('Unknown property: ' . $property);
436
+					throw new BadRequest('Unknown property: '.$property);
437 437
 			}
438 438
 		}
439 439
 
@@ -457,7 +457,7 @@  discard block
 block discarded – undo
457 457
 
458 458
 		$addressBookId = $query->getLastInsertId();
459 459
 		$addressBookRow = $this->getAddressBookById($addressBookId);
460
-		$this->dispatcher->dispatchTyped(new AddressBookCreatedEvent((int)$addressBookId, $addressBookRow));
460
+		$this->dispatcher->dispatchTyped(new AddressBookCreatedEvent((int) $addressBookId, $addressBookRow));
461 461
 
462 462
 		return $addressBookId;
463 463
 	}
@@ -528,7 +528,7 @@  discard block
 block discarded – undo
528 528
 
529 529
 		$result = $query->execute();
530 530
 		while ($row = $result->fetch()) {
531
-			$row['etag'] = '"' . $row['etag'] . '"';
531
+			$row['etag'] = '"'.$row['etag'].'"';
532 532
 
533 533
 			$modified = false;
534 534
 			$row['carddata'] = $this->readBlob($row['carddata'], $modified);
@@ -568,7 +568,7 @@  discard block
 block discarded – undo
568 568
 		if (!$row) {
569 569
 			return false;
570 570
 		}
571
-		$row['etag'] = '"' . $row['etag'] . '"';
571
+		$row['etag'] = '"'.$row['etag'].'"';
572 572
 
573 573
 		$modified = false;
574 574
 		$row['carddata'] = $this->readBlob($row['carddata'], $modified);
@@ -610,7 +610,7 @@  discard block
 block discarded – undo
610 610
 			$result = $query->execute();
611 611
 
612 612
 			while ($row = $result->fetch()) {
613
-				$row['etag'] = '"' . $row['etag'] . '"';
613
+				$row['etag'] = '"'.$row['etag'].'"';
614 614
 
615 615
 				$modified = false;
616 616
 				$row['carddata'] = $this->readBlob($row['carddata'], $modified);
@@ -661,7 +661,7 @@  discard block
 block discarded – undo
661 661
 			->andWhere($q->expr()->eq('uid', $q->createNamedParameter($uid)))
662 662
 			->setMaxResults(1);
663 663
 		$result = $q->execute();
664
-		$count = (bool)$result->fetchOne();
664
+		$count = (bool) $result->fetchOne();
665 665
 		$result->closeCursor();
666 666
 		if ($count) {
667 667
 			throw new \Sabre\DAV\Exception\BadRequest('VCard object with uid already exists in this addressbook collection.');
@@ -689,14 +689,14 @@  discard block
 block discarded – undo
689 689
 		$addressBookData = $this->getAddressBookById($addressBookId);
690 690
 		$shares = $this->getShares($addressBookId);
691 691
 		$objectRow = $this->getCard($addressBookId, $cardUri);
692
-		$this->dispatcher->dispatchTyped(new CardCreatedEvent((int)$addressBookId, $addressBookData, $shares, $objectRow));
692
+		$this->dispatcher->dispatchTyped(new CardCreatedEvent((int) $addressBookId, $addressBookData, $shares, $objectRow));
693 693
 		$this->legacyDispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
694 694
 			new GenericEvent(null, [
695 695
 				'addressBookId' => $addressBookId,
696 696
 				'cardUri' => $cardUri,
697 697
 				'cardData' => $cardData]));
698 698
 
699
-		return '"' . $etag . '"';
699
+		return '"'.$etag.'"';
700 700
 	}
701 701
 
702 702
 	/**
@@ -732,7 +732,7 @@  discard block
 block discarded – undo
732 732
 		// check for recently stored etag and stop if it is the same
733 733
 		$etagCacheKey = "$addressBookId#$cardUri";
734 734
 		if (isset($this->etagCache[$etagCacheKey]) && $this->etagCache[$etagCacheKey] === $etag) {
735
-			return '"' . $etag . '"';
735
+			return '"'.$etag.'"';
736 736
 		}
737 737
 
738 738
 		$query->update($this->dbCardsTable)
@@ -753,14 +753,14 @@  discard block
 block discarded – undo
753 753
 		$addressBookData = $this->getAddressBookById($addressBookId);
754 754
 		$shares = $this->getShares($addressBookId);
755 755
 		$objectRow = $this->getCard($addressBookId, $cardUri);
756
-		$this->dispatcher->dispatchTyped(new CardUpdatedEvent((int)$addressBookId, $addressBookData, $shares, $objectRow));
756
+		$this->dispatcher->dispatchTyped(new CardUpdatedEvent((int) $addressBookId, $addressBookData, $shares, $objectRow));
757 757
 		$this->legacyDispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
758 758
 			new GenericEvent(null, [
759 759
 				'addressBookId' => $addressBookId,
760 760
 				'cardUri' => $cardUri,
761 761
 				'cardData' => $cardData]));
762 762
 
763
-		return '"' . $etag . '"';
763
+		return '"'.$etag.'"';
764 764
 	}
765 765
 
766 766
 	/**
@@ -790,7 +790,7 @@  discard block
 block discarded – undo
790 790
 
791 791
 		if ($ret === 1) {
792 792
 			if ($cardId !== null) {
793
-				$this->dispatcher->dispatchTyped(new CardDeletedEvent((int)$addressBookId, $addressBookData, $shares, $objectRow));
793
+				$this->dispatcher->dispatchTyped(new CardDeletedEvent((int) $addressBookId, $addressBookData, $shares, $objectRow));
794 794
 				$this->legacyDispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
795 795
 					new GenericEvent(null, [
796 796
 						'addressBookId' => $addressBookId,
@@ -880,7 +880,7 @@  discard block
 block discarded – undo
880 880
 		if ($syncToken) {
881 881
 			$query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
882 882
 			if ($limit > 0) {
883
-				$query .= " LIMIT " . (int)$limit;
883
+				$query .= " LIMIT ".(int) $limit;
884 884
 			}
885 885
 
886 886
 			// Fetching all changes
@@ -1023,7 +1023,7 @@  discard block
 block discarded – undo
1023 1023
 									   string $pattern,
1024 1024
 									   array $searchProperties,
1025 1025
 									   array $options = []): array {
1026
-		$addressBookIds = array_map(static function ($row):int {
1026
+		$addressBookIds = array_map(static function($row):int {
1027 1027
 			return (int) $row['id'];
1028 1028
 		}, $this->getAddressBooksForUser($principalUri));
1029 1029
 
@@ -1086,7 +1086,7 @@  discard block
 block discarded – undo
1086 1086
 			if (!$escapePattern) {
1087 1087
 				$query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter($pattern)));
1088 1088
 			} else {
1089
-				$query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
1089
+				$query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter('%'.$this->db->escapeLikeParameter($pattern).'%')));
1090 1090
 			}
1091 1091
 		}
1092 1092
 
@@ -1100,8 +1100,8 @@  discard block
 block discarded – undo
1100 1100
 		$result = $query2->execute();
1101 1101
 		$matches = $result->fetchAll();
1102 1102
 		$result->closeCursor();
1103
-		$matches = array_map(function ($match) {
1104
-			return (int)$match['cardid'];
1103
+		$matches = array_map(function($match) {
1104
+			return (int) $match['cardid'];
1105 1105
 		}, $matches);
1106 1106
 
1107 1107
 		$query = $this->db->getQueryBuilder();
@@ -1114,7 +1114,7 @@  discard block
 block discarded – undo
1114 1114
 
1115 1115
 		$result->closeCursor();
1116 1116
 
1117
-		return array_map(function ($array) {
1117
+		return array_map(function($array) {
1118 1118
 			$array['addressbookid'] = (int) $array['addressbookid'];
1119 1119
 			$modified = false;
1120 1120
 			$array['carddata'] = $this->readBlob($array['carddata'], $modified);
@@ -1161,7 +1161,7 @@  discard block
 block discarded – undo
1161 1161
 		$result->closeCursor();
1162 1162
 
1163 1163
 		if (!isset($uri['uri'])) {
1164
-			throw new \InvalidArgumentException('Card does not exists: ' . $id);
1164
+			throw new \InvalidArgumentException('Card does not exists: '.$id);
1165 1165
 		}
1166 1166
 
1167 1167
 		return $uri['uri'];
@@ -1186,7 +1186,7 @@  discard block
 block discarded – undo
1186 1186
 
1187 1187
 		if (is_array($contact)) {
1188 1188
 			$modified = false;
1189
-			$contact['etag'] = '"' . $contact['etag'] . '"';
1189
+			$contact['etag'] = '"'.$contact['etag'].'"';
1190 1190
 			$contact['carddata'] = $this->readBlob($contact['carddata'], $modified);
1191 1191
 			if ($modified) {
1192 1192
 				$contact['size'] = strlen($contact['carddata']);
@@ -1299,10 +1299,10 @@  discard block
 block discarded – undo
1299 1299
 		$result->closeCursor();
1300 1300
 
1301 1301
 		if (!isset($cardIds['id'])) {
1302
-			throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1302
+			throw new \InvalidArgumentException('Card does not exists: '.$uri);
1303 1303
 		}
1304 1304
 
1305
-		return (int)$cardIds['id'];
1305
+		return (int) $cardIds['id'];
1306 1306
 	}
1307 1307
 
1308 1308
 	/**
@@ -1328,8 +1328,8 @@  discard block
 block discarded – undo
1328 1328
 	}
1329 1329
 
1330 1330
 	private function addOwnerPrincipal(&$addressbookInfo) {
1331
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1332
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1331
+		$ownerPrincipalKey = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal';
1332
+		$displaynameKey = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD.'}owner-displayname';
1333 1333
 		if (isset($addressbookInfo[$ownerPrincipalKey])) {
1334 1334
 			$uri = $addressbookInfo[$ownerPrincipalKey];
1335 1335
 		} else {
Please login to merge, or discard this patch.
apps/dav/lib/CalDAV/CalDavBackend.php 2 patches
Indentation   +2748 added lines, -2748 removed lines patch added patch discarded remove patch
@@ -96,2752 +96,2752 @@
 block discarded – undo
96 96
  * @package OCA\DAV\CalDAV
97 97
  */
98 98
 class CalDavBackend extends AbstractBackend implements SyncSupport, SubscriptionSupport, SchedulingSupport {
99
-	public const CALENDAR_TYPE_CALENDAR = 0;
100
-	public const CALENDAR_TYPE_SUBSCRIPTION = 1;
101
-
102
-	public const PERSONAL_CALENDAR_URI = 'personal';
103
-	public const PERSONAL_CALENDAR_NAME = 'Personal';
104
-
105
-	public const RESOURCE_BOOKING_CALENDAR_URI = 'calendar';
106
-	public const RESOURCE_BOOKING_CALENDAR_NAME = 'Calendar';
107
-
108
-	/**
109
-	 * We need to specify a max date, because we need to stop *somewhere*
110
-	 *
111
-	 * On 32 bit system the maximum for a signed integer is 2147483647, so
112
-	 * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
113
-	 * in 2038-01-19 to avoid problems when the date is converted
114
-	 * to a unix timestamp.
115
-	 */
116
-	public const MAX_DATE = '2038-01-01';
117
-
118
-	public const ACCESS_PUBLIC = 4;
119
-	public const CLASSIFICATION_PUBLIC = 0;
120
-	public const CLASSIFICATION_PRIVATE = 1;
121
-	public const CLASSIFICATION_CONFIDENTIAL = 2;
122
-
123
-	/**
124
-	 * List of CalDAV properties, and how they map to database field names
125
-	 * Add your own properties by simply adding on to this array.
126
-	 *
127
-	 * Note that only string-based properties are supported here.
128
-	 *
129
-	 * @var array
130
-	 */
131
-	public $propertyMap = [
132
-		'{DAV:}displayname' => 'displayname',
133
-		'{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
134
-		'{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone',
135
-		'{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
136
-		'{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
137
-	];
138
-
139
-	/**
140
-	 * List of subscription properties, and how they map to database field names.
141
-	 *
142
-	 * @var array
143
-	 */
144
-	public $subscriptionPropertyMap = [
145
-		'{DAV:}displayname' => 'displayname',
146
-		'{http://apple.com/ns/ical/}refreshrate' => 'refreshrate',
147
-		'{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
148
-		'{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
149
-		'{http://calendarserver.org/ns/}subscribed-strip-todos' => 'striptodos',
150
-		'{http://calendarserver.org/ns/}subscribed-strip-alarms' => 'stripalarms',
151
-		'{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
152
-	];
153
-
154
-	/** @var array properties to index */
155
-	public static $indexProperties = ['CATEGORIES', 'COMMENT', 'DESCRIPTION',
156
-		'LOCATION', 'RESOURCES', 'STATUS', 'SUMMARY', 'ATTENDEE', 'CONTACT',
157
-		'ORGANIZER'];
158
-
159
-	/** @var array parameters to index */
160
-	public static $indexParameters = [
161
-		'ATTENDEE' => ['CN'],
162
-		'ORGANIZER' => ['CN'],
163
-	];
164
-
165
-	/**
166
-	 * @var string[] Map of uid => display name
167
-	 */
168
-	protected $userDisplayNames;
169
-
170
-	/** @var IDBConnection */
171
-	private $db;
172
-
173
-	/** @var Backend */
174
-	private $calendarSharingBackend;
175
-
176
-	/** @var Principal */
177
-	private $principalBackend;
178
-
179
-	/** @var IUserManager */
180
-	private $userManager;
181
-
182
-	/** @var ISecureRandom */
183
-	private $random;
184
-
185
-	/** @var ILogger */
186
-	private $logger;
187
-
188
-	/** @var IEventDispatcher */
189
-	private $dispatcher;
190
-
191
-	/** @var EventDispatcherInterface */
192
-	private $legacyDispatcher;
193
-
194
-	/** @var bool */
195
-	private $legacyEndpoint;
196
-
197
-	/** @var string */
198
-	private $dbObjectPropertiesTable = 'calendarobjects_props';
199
-
200
-	/**
201
-	 * CalDavBackend constructor.
202
-	 *
203
-	 * @param IDBConnection $db
204
-	 * @param Principal $principalBackend
205
-	 * @param IUserManager $userManager
206
-	 * @param IGroupManager $groupManager
207
-	 * @param ISecureRandom $random
208
-	 * @param ILogger $logger
209
-	 * @param IEventDispatcher $dispatcher
210
-	 * @param EventDispatcherInterface $legacyDispatcher
211
-	 * @param bool $legacyEndpoint
212
-	 */
213
-	public function __construct(IDBConnection $db,
214
-								Principal $principalBackend,
215
-								IUserManager $userManager,
216
-								IGroupManager $groupManager,
217
-								ISecureRandom $random,
218
-								ILogger $logger,
219
-								IEventDispatcher $dispatcher,
220
-								EventDispatcherInterface $legacyDispatcher,
221
-								bool $legacyEndpoint = false) {
222
-		$this->db = $db;
223
-		$this->principalBackend = $principalBackend;
224
-		$this->userManager = $userManager;
225
-		$this->calendarSharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'calendar');
226
-		$this->random = $random;
227
-		$this->logger = $logger;
228
-		$this->dispatcher = $dispatcher;
229
-		$this->legacyDispatcher = $legacyDispatcher;
230
-		$this->legacyEndpoint = $legacyEndpoint;
231
-	}
232
-
233
-	/**
234
-	 * Return the number of calendars for a principal
235
-	 *
236
-	 * By default this excludes the automatically generated birthday calendar
237
-	 *
238
-	 * @param $principalUri
239
-	 * @param bool $excludeBirthday
240
-	 * @return int
241
-	 */
242
-	public function getCalendarsForUserCount($principalUri, $excludeBirthday = true) {
243
-		$principalUri = $this->convertPrincipal($principalUri, true);
244
-		$query = $this->db->getQueryBuilder();
245
-		$query->select($query->func()->count('*'))
246
-			->from('calendars');
247
-
248
-		if ($principalUri === '') {
249
-			$query->where($query->expr()->emptyString('principaluri'));
250
-		} else {
251
-			$query->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
252
-		}
253
-
254
-		if ($excludeBirthday) {
255
-			$query->andWhere($query->expr()->neq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)));
256
-		}
257
-
258
-		$result = $query->execute();
259
-		$column = (int)$result->fetchOne();
260
-		$result->closeCursor();
261
-		return $column;
262
-	}
263
-
264
-	/**
265
-	 * Returns a list of calendars for a principal.
266
-	 *
267
-	 * Every project is an array with the following keys:
268
-	 *  * id, a unique id that will be used by other functions to modify the
269
-	 *    calendar. This can be the same as the uri or a database key.
270
-	 *  * uri, which the basename of the uri with which the calendar is
271
-	 *    accessed.
272
-	 *  * principaluri. The owner of the calendar. Almost always the same as
273
-	 *    principalUri passed to this method.
274
-	 *
275
-	 * Furthermore it can contain webdav properties in clark notation. A very
276
-	 * common one is '{DAV:}displayname'.
277
-	 *
278
-	 * Many clients also require:
279
-	 * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
280
-	 * For this property, you can just return an instance of
281
-	 * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
282
-	 *
283
-	 * If you return {http://sabredav.org/ns}read-only and set the value to 1,
284
-	 * ACL will automatically be put in read-only mode.
285
-	 *
286
-	 * @param string $principalUri
287
-	 * @return array
288
-	 */
289
-	public function getCalendarsForUser($principalUri) {
290
-		$principalUriOriginal = $principalUri;
291
-		$principalUri = $this->convertPrincipal($principalUri, true);
292
-		$fields = array_values($this->propertyMap);
293
-		$fields[] = 'id';
294
-		$fields[] = 'uri';
295
-		$fields[] = 'synctoken';
296
-		$fields[] = 'components';
297
-		$fields[] = 'principaluri';
298
-		$fields[] = 'transparent';
299
-
300
-		// Making fields a comma-delimited list
301
-		$query = $this->db->getQueryBuilder();
302
-		$query->select($fields)
303
-			->from('calendars')
304
-			->orderBy('calendarorder', 'ASC');
305
-
306
-		if ($principalUri === '') {
307
-			$query->where($query->expr()->emptyString('principaluri'));
308
-		} else {
309
-			$query->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
310
-		}
311
-
312
-		$result = $query->execute();
313
-
314
-		$calendars = [];
315
-		while ($row = $result->fetch()) {
316
-			$row['principaluri'] = (string) $row['principaluri'];
317
-			$components = [];
318
-			if ($row['components']) {
319
-				$components = explode(',',$row['components']);
320
-			}
321
-
322
-			$calendar = [
323
-				'id' => $row['id'],
324
-				'uri' => $row['uri'],
325
-				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
326
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
327
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
328
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
329
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
330
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
331
-			];
332
-
333
-			foreach ($this->propertyMap as $xmlName => $dbName) {
334
-				$calendar[$xmlName] = $row[$dbName];
335
-			}
336
-
337
-			$this->addOwnerPrincipal($calendar);
338
-
339
-			if (!isset($calendars[$calendar['id']])) {
340
-				$calendars[$calendar['id']] = $calendar;
341
-			}
342
-		}
343
-		$result->closeCursor();
344
-
345
-		// query for shared calendars
346
-		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
347
-		$principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
348
-
349
-		$principals[] = $principalUri;
350
-
351
-		$fields = array_values($this->propertyMap);
352
-		$fields[] = 'a.id';
353
-		$fields[] = 'a.uri';
354
-		$fields[] = 'a.synctoken';
355
-		$fields[] = 'a.components';
356
-		$fields[] = 'a.principaluri';
357
-		$fields[] = 'a.transparent';
358
-		$fields[] = 's.access';
359
-		$query = $this->db->getQueryBuilder();
360
-		$query->select($fields)
361
-			->from('dav_shares', 's')
362
-			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
363
-			->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
364
-			->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
365
-			->setParameter('type', 'calendar')
366
-			->setParameter('principaluri', $principals, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY);
367
-
368
-		$result	= $query->execute();
369
-
370
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
371
-		while ($row = $result->fetch()) {
372
-			$row['principaluri'] = (string) $row['principaluri'];
373
-			if ($row['principaluri'] === $principalUri) {
374
-				continue;
375
-			}
376
-
377
-			$readOnly = (int) $row['access'] === Backend::ACCESS_READ;
378
-			if (isset($calendars[$row['id']])) {
379
-				if ($readOnly) {
380
-					// New share can not have more permissions then the old one.
381
-					continue;
382
-				}
383
-				if (isset($calendars[$row['id']][$readOnlyPropertyName]) &&
384
-					$calendars[$row['id']][$readOnlyPropertyName] === 0) {
385
-					// Old share is already read-write, no more permissions can be gained
386
-					continue;
387
-				}
388
-			}
389
-
390
-			list(, $name) = Uri\split($row['principaluri']);
391
-			$uri = $row['uri'] . '_shared_by_' . $name;
392
-			$row['displayname'] = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
393
-			$components = [];
394
-			if ($row['components']) {
395
-				$components = explode(',',$row['components']);
396
-			}
397
-			$calendar = [
398
-				'id' => $row['id'],
399
-				'uri' => $uri,
400
-				'principaluri' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
401
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
402
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
403
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
404
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp('transparent'),
405
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
406
-				$readOnlyPropertyName => $readOnly,
407
-			];
408
-
409
-			foreach ($this->propertyMap as $xmlName => $dbName) {
410
-				$calendar[$xmlName] = $row[$dbName];
411
-			}
412
-
413
-			$this->addOwnerPrincipal($calendar);
414
-
415
-			$calendars[$calendar['id']] = $calendar;
416
-		}
417
-		$result->closeCursor();
418
-
419
-		return array_values($calendars);
420
-	}
421
-
422
-	/**
423
-	 * @param $principalUri
424
-	 * @return array
425
-	 */
426
-	public function getUsersOwnCalendars($principalUri) {
427
-		$principalUri = $this->convertPrincipal($principalUri, true);
428
-		$fields = array_values($this->propertyMap);
429
-		$fields[] = 'id';
430
-		$fields[] = 'uri';
431
-		$fields[] = 'synctoken';
432
-		$fields[] = 'components';
433
-		$fields[] = 'principaluri';
434
-		$fields[] = 'transparent';
435
-		// Making fields a comma-delimited list
436
-		$query = $this->db->getQueryBuilder();
437
-		$query->select($fields)->from('calendars')
438
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
439
-			->orderBy('calendarorder', 'ASC');
440
-		$stmt = $query->execute();
441
-		$calendars = [];
442
-		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
443
-			$row['principaluri'] = (string) $row['principaluri'];
444
-			$components = [];
445
-			if ($row['components']) {
446
-				$components = explode(',',$row['components']);
447
-			}
448
-			$calendar = [
449
-				'id' => $row['id'],
450
-				'uri' => $row['uri'],
451
-				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
452
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
453
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
454
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
455
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
456
-			];
457
-			foreach ($this->propertyMap as $xmlName => $dbName) {
458
-				$calendar[$xmlName] = $row[$dbName];
459
-			}
460
-
461
-			$this->addOwnerPrincipal($calendar);
462
-
463
-			if (!isset($calendars[$calendar['id']])) {
464
-				$calendars[$calendar['id']] = $calendar;
465
-			}
466
-		}
467
-		$stmt->closeCursor();
468
-		return array_values($calendars);
469
-	}
470
-
471
-
472
-	/**
473
-	 * @param $uid
474
-	 * @return string
475
-	 */
476
-	private function getUserDisplayName($uid) {
477
-		if (!isset($this->userDisplayNames[$uid])) {
478
-			$user = $this->userManager->get($uid);
479
-
480
-			if ($user instanceof IUser) {
481
-				$this->userDisplayNames[$uid] = $user->getDisplayName();
482
-			} else {
483
-				$this->userDisplayNames[$uid] = $uid;
484
-			}
485
-		}
486
-
487
-		return $this->userDisplayNames[$uid];
488
-	}
489
-
490
-	/**
491
-	 * @return array
492
-	 */
493
-	public function getPublicCalendars() {
494
-		$fields = array_values($this->propertyMap);
495
-		$fields[] = 'a.id';
496
-		$fields[] = 'a.uri';
497
-		$fields[] = 'a.synctoken';
498
-		$fields[] = 'a.components';
499
-		$fields[] = 'a.principaluri';
500
-		$fields[] = 'a.transparent';
501
-		$fields[] = 's.access';
502
-		$fields[] = 's.publicuri';
503
-		$calendars = [];
504
-		$query = $this->db->getQueryBuilder();
505
-		$result = $query->select($fields)
506
-			->from('dav_shares', 's')
507
-			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
508
-			->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
509
-			->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
510
-			->execute();
511
-
512
-		while ($row = $result->fetch()) {
513
-			$row['principaluri'] = (string) $row['principaluri'];
514
-			list(, $name) = Uri\split($row['principaluri']);
515
-			$row['displayname'] = $row['displayname'] . "($name)";
516
-			$components = [];
517
-			if ($row['components']) {
518
-				$components = explode(',',$row['components']);
519
-			}
520
-			$calendar = [
521
-				'id' => $row['id'],
522
-				'uri' => $row['publicuri'],
523
-				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
524
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
525
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
526
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
527
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
528
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
529
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
530
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
531
-			];
532
-
533
-			foreach ($this->propertyMap as $xmlName => $dbName) {
534
-				$calendar[$xmlName] = $row[$dbName];
535
-			}
536
-
537
-			$this->addOwnerPrincipal($calendar);
538
-
539
-			if (!isset($calendars[$calendar['id']])) {
540
-				$calendars[$calendar['id']] = $calendar;
541
-			}
542
-		}
543
-		$result->closeCursor();
544
-
545
-		return array_values($calendars);
546
-	}
547
-
548
-	/**
549
-	 * @param string $uri
550
-	 * @return array
551
-	 * @throws NotFound
552
-	 */
553
-	public function getPublicCalendar($uri) {
554
-		$fields = array_values($this->propertyMap);
555
-		$fields[] = 'a.id';
556
-		$fields[] = 'a.uri';
557
-		$fields[] = 'a.synctoken';
558
-		$fields[] = 'a.components';
559
-		$fields[] = 'a.principaluri';
560
-		$fields[] = 'a.transparent';
561
-		$fields[] = 's.access';
562
-		$fields[] = 's.publicuri';
563
-		$query = $this->db->getQueryBuilder();
564
-		$result = $query->select($fields)
565
-			->from('dav_shares', 's')
566
-			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
567
-			->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
568
-			->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
569
-			->andWhere($query->expr()->eq('s.publicuri', $query->createNamedParameter($uri)))
570
-			->execute();
571
-
572
-		$row = $result->fetch(\PDO::FETCH_ASSOC);
573
-
574
-		$result->closeCursor();
575
-
576
-		if ($row === false) {
577
-			throw new NotFound('Node with name \'' . $uri . '\' could not be found');
578
-		}
579
-
580
-		$row['principaluri'] = (string) $row['principaluri'];
581
-		list(, $name) = Uri\split($row['principaluri']);
582
-		$row['displayname'] = $row['displayname'] . ' ' . "($name)";
583
-		$components = [];
584
-		if ($row['components']) {
585
-			$components = explode(',',$row['components']);
586
-		}
587
-		$calendar = [
588
-			'id' => $row['id'],
589
-			'uri' => $row['publicuri'],
590
-			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
591
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
592
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
593
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
594
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
595
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
596
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
597
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
598
-		];
599
-
600
-		foreach ($this->propertyMap as $xmlName => $dbName) {
601
-			$calendar[$xmlName] = $row[$dbName];
602
-		}
603
-
604
-		$this->addOwnerPrincipal($calendar);
605
-
606
-		return $calendar;
607
-	}
608
-
609
-	/**
610
-	 * @param string $principal
611
-	 * @param string $uri
612
-	 * @return array|null
613
-	 */
614
-	public function getCalendarByUri($principal, $uri) {
615
-		$fields = array_values($this->propertyMap);
616
-		$fields[] = 'id';
617
-		$fields[] = 'uri';
618
-		$fields[] = 'synctoken';
619
-		$fields[] = 'components';
620
-		$fields[] = 'principaluri';
621
-		$fields[] = 'transparent';
622
-
623
-		// Making fields a comma-delimited list
624
-		$query = $this->db->getQueryBuilder();
625
-		$query->select($fields)->from('calendars')
626
-			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
627
-			->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
628
-			->setMaxResults(1);
629
-		$stmt = $query->execute();
630
-
631
-		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
632
-		$stmt->closeCursor();
633
-		if ($row === false) {
634
-			return null;
635
-		}
636
-
637
-		$row['principaluri'] = (string) $row['principaluri'];
638
-		$components = [];
639
-		if ($row['components']) {
640
-			$components = explode(',',$row['components']);
641
-		}
642
-
643
-		$calendar = [
644
-			'id' => $row['id'],
645
-			'uri' => $row['uri'],
646
-			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
647
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
648
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
649
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
650
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
651
-		];
652
-
653
-		foreach ($this->propertyMap as $xmlName => $dbName) {
654
-			$calendar[$xmlName] = $row[$dbName];
655
-		}
656
-
657
-		$this->addOwnerPrincipal($calendar);
658
-
659
-		return $calendar;
660
-	}
661
-
662
-	/**
663
-	 * @param $calendarId
664
-	 * @return array|null
665
-	 */
666
-	public function getCalendarById($calendarId) {
667
-		$fields = array_values($this->propertyMap);
668
-		$fields[] = 'id';
669
-		$fields[] = 'uri';
670
-		$fields[] = 'synctoken';
671
-		$fields[] = 'components';
672
-		$fields[] = 'principaluri';
673
-		$fields[] = 'transparent';
674
-
675
-		// Making fields a comma-delimited list
676
-		$query = $this->db->getQueryBuilder();
677
-		$query->select($fields)->from('calendars')
678
-			->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)))
679
-			->setMaxResults(1);
680
-		$stmt = $query->execute();
681
-
682
-		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
683
-		$stmt->closeCursor();
684
-		if ($row === false) {
685
-			return null;
686
-		}
687
-
688
-		$row['principaluri'] = (string) $row['principaluri'];
689
-		$components = [];
690
-		if ($row['components']) {
691
-			$components = explode(',',$row['components']);
692
-		}
693
-
694
-		$calendar = [
695
-			'id' => $row['id'],
696
-			'uri' => $row['uri'],
697
-			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
698
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
699
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
700
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
701
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
702
-		];
703
-
704
-		foreach ($this->propertyMap as $xmlName => $dbName) {
705
-			$calendar[$xmlName] = $row[$dbName];
706
-		}
707
-
708
-		$this->addOwnerPrincipal($calendar);
709
-
710
-		return $calendar;
711
-	}
712
-
713
-	/**
714
-	 * @param $subscriptionId
715
-	 */
716
-	public function getSubscriptionById($subscriptionId) {
717
-		$fields = array_values($this->subscriptionPropertyMap);
718
-		$fields[] = 'id';
719
-		$fields[] = 'uri';
720
-		$fields[] = 'source';
721
-		$fields[] = 'synctoken';
722
-		$fields[] = 'principaluri';
723
-		$fields[] = 'lastmodified';
724
-
725
-		$query = $this->db->getQueryBuilder();
726
-		$query->select($fields)
727
-			->from('calendarsubscriptions')
728
-			->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
729
-			->orderBy('calendarorder', 'asc');
730
-		$stmt = $query->execute();
731
-
732
-		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
733
-		$stmt->closeCursor();
734
-		if ($row === false) {
735
-			return null;
736
-		}
737
-
738
-		$row['principaluri'] = (string) $row['principaluri'];
739
-		$subscription = [
740
-			'id' => $row['id'],
741
-			'uri' => $row['uri'],
742
-			'principaluri' => $row['principaluri'],
743
-			'source' => $row['source'],
744
-			'lastmodified' => $row['lastmodified'],
745
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
746
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
747
-		];
748
-
749
-		foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
750
-			if (!is_null($row[$dbName])) {
751
-				$subscription[$xmlName] = $row[$dbName];
752
-			}
753
-		}
754
-
755
-		return $subscription;
756
-	}
757
-
758
-	/**
759
-	 * Creates a new calendar for a principal.
760
-	 *
761
-	 * If the creation was a success, an id must be returned that can be used to reference
762
-	 * this calendar in other methods, such as updateCalendar.
763
-	 *
764
-	 * @param string $principalUri
765
-	 * @param string $calendarUri
766
-	 * @param array $properties
767
-	 * @return int
768
-	 */
769
-	public function createCalendar($principalUri, $calendarUri, array $properties) {
770
-		$values = [
771
-			'principaluri' => $this->convertPrincipal($principalUri, true),
772
-			'uri' => $calendarUri,
773
-			'synctoken' => 1,
774
-			'transparent' => 0,
775
-			'components' => 'VEVENT,VTODO',
776
-			'displayname' => $calendarUri
777
-		];
778
-
779
-		// Default value
780
-		$sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
781
-		if (isset($properties[$sccs])) {
782
-			if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) {
783
-				throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
784
-			}
785
-			$values['components'] = implode(',',$properties[$sccs]->getValue());
786
-		} elseif (isset($properties['components'])) {
787
-			// Allow to provide components internally without having
788
-			// to create a SupportedCalendarComponentSet object
789
-			$values['components'] = $properties['components'];
790
-		}
791
-
792
-		$transp = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
793
-		if (isset($properties[$transp])) {
794
-			$values['transparent'] = (int) ($properties[$transp]->getValue() === 'transparent');
795
-		}
796
-
797
-		foreach ($this->propertyMap as $xmlName => $dbName) {
798
-			if (isset($properties[$xmlName])) {
799
-				$values[$dbName] = $properties[$xmlName];
800
-			}
801
-		}
802
-
803
-		$query = $this->db->getQueryBuilder();
804
-		$query->insert('calendars');
805
-		foreach ($values as $column => $value) {
806
-			$query->setValue($column, $query->createNamedParameter($value));
807
-		}
808
-		$query->execute();
809
-		$calendarId = $query->getLastInsertId();
810
-
811
-		$calendarData = $this->getCalendarById($calendarId);
812
-		$this->dispatcher->dispatchTyped(new CalendarCreatedEvent((int)$calendarId, $calendarData));
813
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendar', new GenericEvent(
814
-			'\OCA\DAV\CalDAV\CalDavBackend::createCalendar',
815
-			[
816
-				'calendarId' => $calendarId,
817
-				'calendarData' => $calendarData,
818
-			]));
819
-
820
-		return $calendarId;
821
-	}
822
-
823
-	/**
824
-	 * Updates properties for a calendar.
825
-	 *
826
-	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
827
-	 * To do the actual updates, you must tell this object which properties
828
-	 * you're going to process with the handle() method.
829
-	 *
830
-	 * Calling the handle method is like telling the PropPatch object "I
831
-	 * promise I can handle updating this property".
832
-	 *
833
-	 * Read the PropPatch documentation for more info and examples.
834
-	 *
835
-	 * @param mixed $calendarId
836
-	 * @param PropPatch $propPatch
837
-	 * @return void
838
-	 */
839
-	public function updateCalendar($calendarId, PropPatch $propPatch) {
840
-		$supportedProperties = array_keys($this->propertyMap);
841
-		$supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
842
-
843
-		$propPatch->handle($supportedProperties, function ($mutations) use ($calendarId) {
844
-			$newValues = [];
845
-			foreach ($mutations as $propertyName => $propertyValue) {
846
-				switch ($propertyName) {
847
-					case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp':
848
-						$fieldName = 'transparent';
849
-						$newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent');
850
-						break;
851
-					default:
852
-						$fieldName = $this->propertyMap[$propertyName];
853
-						$newValues[$fieldName] = $propertyValue;
854
-						break;
855
-				}
856
-			}
857
-			$query = $this->db->getQueryBuilder();
858
-			$query->update('calendars');
859
-			foreach ($newValues as $fieldName => $value) {
860
-				$query->set($fieldName, $query->createNamedParameter($value));
861
-			}
862
-			$query->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
863
-			$query->execute();
864
-
865
-			$this->addChange($calendarId, "", 2);
866
-
867
-			$calendarData = $this->getCalendarById($calendarId);
868
-			$shares = $this->getShares($calendarId);
869
-			$this->dispatcher->dispatchTyped(new CalendarUpdatedEvent((int)$calendarId, $calendarData, $shares, $mutations));
870
-			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendar', new GenericEvent(
871
-				'\OCA\DAV\CalDAV\CalDavBackend::updateCalendar',
872
-				[
873
-					'calendarId' => $calendarId,
874
-					'calendarData' => $calendarData,
875
-					'shares' => $shares,
876
-					'propertyMutations' => $mutations,
877
-				]));
878
-
879
-			return true;
880
-		});
881
-	}
882
-
883
-	/**
884
-	 * Delete a calendar and all it's objects
885
-	 *
886
-	 * @param mixed $calendarId
887
-	 * @return void
888
-	 */
889
-	public function deleteCalendar($calendarId) {
890
-		$calendarData = $this->getCalendarById($calendarId);
891
-		$shares = $this->getShares($calendarId);
892
-
893
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar', new GenericEvent(
894
-			'\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar',
895
-			[
896
-				'calendarId' => $calendarId,
897
-				'calendarData' => $calendarData,
898
-				'shares' => $shares,
899
-			]));
900
-
901
-		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `calendartype` = ?');
902
-		$stmt->execute([$calendarId, self::CALENDAR_TYPE_CALENDAR]);
903
-
904
-		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendars` WHERE `id` = ?');
905
-		$stmt->execute([$calendarId]);
906
-
907
-		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarchanges` WHERE `calendarid` = ? AND `calendartype` = ?');
908
-		$stmt->execute([$calendarId, self::CALENDAR_TYPE_CALENDAR]);
909
-
910
-		$this->calendarSharingBackend->deleteAllShares($calendarId);
911
-
912
-		$query = $this->db->getQueryBuilder();
913
-		$query->delete($this->dbObjectPropertiesTable)
914
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
915
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)))
916
-			->execute();
917
-
918
-		if ($calendarData) {
919
-			$this->dispatcher->dispatchTyped(new CalendarDeletedEvent((int)$calendarId, $calendarData, $shares));
920
-		}
921
-	}
922
-
923
-	/**
924
-	 * Delete all of an user's shares
925
-	 *
926
-	 * @param string $principaluri
927
-	 * @return void
928
-	 */
929
-	public function deleteAllSharesByUser($principaluri) {
930
-		$this->calendarSharingBackend->deleteAllSharesByUser($principaluri);
931
-	}
932
-
933
-	/**
934
-	 * Returns all calendar objects within a calendar.
935
-	 *
936
-	 * Every item contains an array with the following keys:
937
-	 *   * calendardata - The iCalendar-compatible calendar data
938
-	 *   * uri - a unique key which will be used to construct the uri. This can
939
-	 *     be any arbitrary string, but making sure it ends with '.ics' is a
940
-	 *     good idea. This is only the basename, or filename, not the full
941
-	 *     path.
942
-	 *   * lastmodified - a timestamp of the last modification time
943
-	 *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
944
-	 *   '"abcdef"')
945
-	 *   * size - The size of the calendar objects, in bytes.
946
-	 *   * component - optional, a string containing the type of object, such
947
-	 *     as 'vevent' or 'vtodo'. If specified, this will be used to populate
948
-	 *     the Content-Type header.
949
-	 *
950
-	 * Note that the etag is optional, but it's highly encouraged to return for
951
-	 * speed reasons.
952
-	 *
953
-	 * The calendardata is also optional. If it's not returned
954
-	 * 'getCalendarObject' will be called later, which *is* expected to return
955
-	 * calendardata.
956
-	 *
957
-	 * If neither etag or size are specified, the calendardata will be
958
-	 * used/fetched to determine these numbers. If both are specified the
959
-	 * amount of times this is needed is reduced by a great degree.
960
-	 *
961
-	 * @param mixed $calendarId
962
-	 * @param int $calendarType
963
-	 * @return array
964
-	 */
965
-	public function getCalendarObjects($calendarId, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
966
-		$query = $this->db->getQueryBuilder();
967
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'componenttype', 'classification'])
968
-			->from('calendarobjects')
969
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
970
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
971
-		$stmt = $query->execute();
972
-
973
-		$result = [];
974
-		foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
975
-			$result[] = [
976
-				'id' => $row['id'],
977
-				'uri' => $row['uri'],
978
-				'lastmodified' => $row['lastmodified'],
979
-				'etag' => '"' . $row['etag'] . '"',
980
-				'calendarid' => $row['calendarid'],
981
-				'size' => (int)$row['size'],
982
-				'component' => strtolower($row['componenttype']),
983
-				'classification' => (int)$row['classification']
984
-			];
985
-		}
986
-		$stmt->closeCursor();
987
-
988
-		return $result;
989
-	}
990
-
991
-	/**
992
-	 * Returns information from a single calendar object, based on it's object
993
-	 * uri.
994
-	 *
995
-	 * The object uri is only the basename, or filename and not a full path.
996
-	 *
997
-	 * The returned array must have the same keys as getCalendarObjects. The
998
-	 * 'calendardata' object is required here though, while it's not required
999
-	 * for getCalendarObjects.
1000
-	 *
1001
-	 * This method must return null if the object did not exist.
1002
-	 *
1003
-	 * @param mixed $calendarId
1004
-	 * @param string $objectUri
1005
-	 * @param int $calendarType
1006
-	 * @return array|null
1007
-	 */
1008
-	public function getCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1009
-		$query = $this->db->getQueryBuilder();
1010
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
1011
-			->from('calendarobjects')
1012
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1013
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1014
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
1015
-		$stmt = $query->execute();
1016
-		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
1017
-		$stmt->closeCursor();
1018
-
1019
-		if (!$row) {
1020
-			return null;
1021
-		}
1022
-
1023
-		return [
1024
-			'id' => $row['id'],
1025
-			'uri' => $row['uri'],
1026
-			'lastmodified' => $row['lastmodified'],
1027
-			'etag' => '"' . $row['etag'] . '"',
1028
-			'calendarid' => $row['calendarid'],
1029
-			'size' => (int)$row['size'],
1030
-			'calendardata' => $this->readBlob($row['calendardata']),
1031
-			'component' => strtolower($row['componenttype']),
1032
-			'classification' => (int)$row['classification']
1033
-		];
1034
-	}
1035
-
1036
-	/**
1037
-	 * Returns a list of calendar objects.
1038
-	 *
1039
-	 * This method should work identical to getCalendarObject, but instead
1040
-	 * return all the calendar objects in the list as an array.
1041
-	 *
1042
-	 * If the backend supports this, it may allow for some speed-ups.
1043
-	 *
1044
-	 * @param mixed $calendarId
1045
-	 * @param string[] $uris
1046
-	 * @param int $calendarType
1047
-	 * @return array
1048
-	 */
1049
-	public function getMultipleCalendarObjects($calendarId, array $uris, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
1050
-		if (empty($uris)) {
1051
-			return [];
1052
-		}
1053
-
1054
-		$chunks = array_chunk($uris, 100);
1055
-		$objects = [];
1056
-
1057
-		$query = $this->db->getQueryBuilder();
1058
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
1059
-			->from('calendarobjects')
1060
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1061
-			->andWhere($query->expr()->in('uri', $query->createParameter('uri')))
1062
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
1063
-
1064
-		foreach ($chunks as $uris) {
1065
-			$query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
1066
-			$result = $query->execute();
1067
-
1068
-			while ($row = $result->fetch()) {
1069
-				$objects[] = [
1070
-					'id' => $row['id'],
1071
-					'uri' => $row['uri'],
1072
-					'lastmodified' => $row['lastmodified'],
1073
-					'etag' => '"' . $row['etag'] . '"',
1074
-					'calendarid' => $row['calendarid'],
1075
-					'size' => (int)$row['size'],
1076
-					'calendardata' => $this->readBlob($row['calendardata']),
1077
-					'component' => strtolower($row['componenttype']),
1078
-					'classification' => (int)$row['classification']
1079
-				];
1080
-			}
1081
-			$result->closeCursor();
1082
-		}
1083
-
1084
-		return $objects;
1085
-	}
1086
-
1087
-	/**
1088
-	 * Creates a new calendar object.
1089
-	 *
1090
-	 * The object uri is only the basename, or filename and not a full path.
1091
-	 *
1092
-	 * It is possible return an etag from this function, which will be used in
1093
-	 * the response to this PUT request. Note that the ETag must be surrounded
1094
-	 * by double-quotes.
1095
-	 *
1096
-	 * However, you should only really return this ETag if you don't mangle the
1097
-	 * calendar-data. If the result of a subsequent GET to this object is not
1098
-	 * the exact same as this request body, you should omit the ETag.
1099
-	 *
1100
-	 * @param mixed $calendarId
1101
-	 * @param string $objectUri
1102
-	 * @param string $calendarData
1103
-	 * @param int $calendarType
1104
-	 * @return string
1105
-	 */
1106
-	public function createCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1107
-		$extraData = $this->getDenormalizedData($calendarData);
1108
-
1109
-		$q = $this->db->getQueryBuilder();
1110
-		$q->select($q->func()->count('*'))
1111
-			->from('calendarobjects')
1112
-			->where($q->expr()->eq('calendarid', $q->createNamedParameter($calendarId)))
1113
-			->andWhere($q->expr()->eq('uid', $q->createNamedParameter($extraData['uid'])))
1114
-			->andWhere($q->expr()->eq('calendartype', $q->createNamedParameter($calendarType)));
1115
-
1116
-		$result = $q->execute();
1117
-		$count = (int) $result->fetchOne();
1118
-		$result->closeCursor();
1119
-
1120
-		if ($count !== 0) {
1121
-			throw new \Sabre\DAV\Exception\BadRequest('Calendar object with uid already exists in this calendar collection.');
1122
-		}
1123
-
1124
-		$query = $this->db->getQueryBuilder();
1125
-		$query->insert('calendarobjects')
1126
-			->values([
1127
-				'calendarid' => $query->createNamedParameter($calendarId),
1128
-				'uri' => $query->createNamedParameter($objectUri),
1129
-				'calendardata' => $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB),
1130
-				'lastmodified' => $query->createNamedParameter(time()),
1131
-				'etag' => $query->createNamedParameter($extraData['etag']),
1132
-				'size' => $query->createNamedParameter($extraData['size']),
1133
-				'componenttype' => $query->createNamedParameter($extraData['componentType']),
1134
-				'firstoccurence' => $query->createNamedParameter($extraData['firstOccurence']),
1135
-				'lastoccurence' => $query->createNamedParameter($extraData['lastOccurence']),
1136
-				'classification' => $query->createNamedParameter($extraData['classification']),
1137
-				'uid' => $query->createNamedParameter($extraData['uid']),
1138
-				'calendartype' => $query->createNamedParameter($calendarType),
1139
-			])
1140
-			->execute();
1141
-
1142
-		$this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType);
1143
-		$this->addChange($calendarId, $objectUri, 1, $calendarType);
1144
-
1145
-		$objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1146
-		if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1147
-			$calendarRow = $this->getCalendarById($calendarId);
1148
-			$shares = $this->getShares($calendarId);
1149
-
1150
-			$this->dispatcher->dispatchTyped(new CalendarObjectCreatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
1151
-			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', new GenericEvent(
1152
-				'\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject',
1153
-				[
1154
-					'calendarId' => $calendarId,
1155
-					'calendarData' => $calendarRow,
1156
-					'shares' => $shares,
1157
-					'objectData' => $objectRow,
1158
-				]
1159
-			));
1160
-		} else {
1161
-			$subscriptionRow = $this->getSubscriptionById($calendarId);
1162
-
1163
-			$this->dispatcher->dispatchTyped(new CachedCalendarObjectCreatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
1164
-			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject', new GenericEvent(
1165
-				'\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject',
1166
-				[
1167
-					'subscriptionId' => $calendarId,
1168
-					'calendarData' => $subscriptionRow,
1169
-					'shares' => [],
1170
-					'objectData' => $objectRow,
1171
-				]
1172
-			));
1173
-		}
1174
-
1175
-		return '"' . $extraData['etag'] . '"';
1176
-	}
1177
-
1178
-	/**
1179
-	 * Updates an existing calendarobject, based on it's uri.
1180
-	 *
1181
-	 * The object uri is only the basename, or filename and not a full path.
1182
-	 *
1183
-	 * It is possible return an etag from this function, which will be used in
1184
-	 * the response to this PUT request. Note that the ETag must be surrounded
1185
-	 * by double-quotes.
1186
-	 *
1187
-	 * However, you should only really return this ETag if you don't mangle the
1188
-	 * calendar-data. If the result of a subsequent GET to this object is not
1189
-	 * the exact same as this request body, you should omit the ETag.
1190
-	 *
1191
-	 * @param mixed $calendarId
1192
-	 * @param string $objectUri
1193
-	 * @param string $calendarData
1194
-	 * @param int $calendarType
1195
-	 * @return string
1196
-	 */
1197
-	public function updateCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1198
-		$extraData = $this->getDenormalizedData($calendarData);
1199
-		$query = $this->db->getQueryBuilder();
1200
-		$query->update('calendarobjects')
1201
-				->set('calendardata', $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB))
1202
-				->set('lastmodified', $query->createNamedParameter(time()))
1203
-				->set('etag', $query->createNamedParameter($extraData['etag']))
1204
-				->set('size', $query->createNamedParameter($extraData['size']))
1205
-				->set('componenttype', $query->createNamedParameter($extraData['componentType']))
1206
-				->set('firstoccurence', $query->createNamedParameter($extraData['firstOccurence']))
1207
-				->set('lastoccurence', $query->createNamedParameter($extraData['lastOccurence']))
1208
-				->set('classification', $query->createNamedParameter($extraData['classification']))
1209
-				->set('uid', $query->createNamedParameter($extraData['uid']))
1210
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1211
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1212
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)))
1213
-			->execute();
1214
-
1215
-		$this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType);
1216
-		$this->addChange($calendarId, $objectUri, 2, $calendarType);
1217
-
1218
-		$objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1219
-		if (is_array($objectRow)) {
1220
-			if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1221
-				$calendarRow = $this->getCalendarById($calendarId);
1222
-				$shares = $this->getShares($calendarId);
1223
-
1224
-				$this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
1225
-				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', new GenericEvent(
1226
-					'\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject',
1227
-					[
1228
-						'calendarId' => $calendarId,
1229
-						'calendarData' => $calendarRow,
1230
-						'shares' => $shares,
1231
-						'objectData' => $objectRow,
1232
-					]
1233
-				));
1234
-			} else {
1235
-				$subscriptionRow = $this->getSubscriptionById($calendarId);
1236
-
1237
-				$this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
1238
-				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject', new GenericEvent(
1239
-					'\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject',
1240
-					[
1241
-						'subscriptionId' => $calendarId,
1242
-						'calendarData' => $subscriptionRow,
1243
-						'shares' => [],
1244
-						'objectData' => $objectRow,
1245
-					]
1246
-				));
1247
-			}
1248
-		}
1249
-
1250
-		return '"' . $extraData['etag'] . '"';
1251
-	}
1252
-
1253
-	/**
1254
-	 * @param int $calendarObjectId
1255
-	 * @param int $classification
1256
-	 */
1257
-	public function setClassification($calendarObjectId, $classification) {
1258
-		if (!in_array($classification, [
1259
-			self::CLASSIFICATION_PUBLIC, self::CLASSIFICATION_PRIVATE, self::CLASSIFICATION_CONFIDENTIAL
1260
-		])) {
1261
-			throw new \InvalidArgumentException();
1262
-		}
1263
-		$query = $this->db->getQueryBuilder();
1264
-		$query->update('calendarobjects')
1265
-			->set('classification', $query->createNamedParameter($classification))
1266
-			->where($query->expr()->eq('id', $query->createNamedParameter($calendarObjectId)))
1267
-			->execute();
1268
-	}
1269
-
1270
-	/**
1271
-	 * Deletes an existing calendar object.
1272
-	 *
1273
-	 * The object uri is only the basename, or filename and not a full path.
1274
-	 *
1275
-	 * @param mixed $calendarId
1276
-	 * @param string $objectUri
1277
-	 * @param int $calendarType
1278
-	 * @return void
1279
-	 */
1280
-	public function deleteCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1281
-		$data = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1282
-		if (is_array($data)) {
1283
-			if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1284
-				$calendarRow = $this->getCalendarById($calendarId);
1285
-				$shares = $this->getShares($calendarId);
1286
-
1287
-				$this->dispatcher->dispatchTyped(new CalendarObjectDeletedEvent((int)$calendarId, $calendarRow, $shares, $data));
1288
-				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', new GenericEvent(
1289
-					'\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject',
1290
-					[
1291
-						'calendarId' => $calendarId,
1292
-						'calendarData' => $calendarRow,
1293
-						'shares' => $shares,
1294
-						'objectData' => $data,
1295
-					]
1296
-				));
1297
-			} else {
1298
-				$subscriptionRow = $this->getSubscriptionById($calendarId);
1299
-
1300
-				$this->dispatcher->dispatchTyped(new CachedCalendarObjectDeletedEvent((int)$calendarId, $subscriptionRow, [], $data));
1301
-				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject', new GenericEvent(
1302
-					'\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject',
1303
-					[
1304
-						'subscriptionId' => $calendarId,
1305
-						'calendarData' => $subscriptionRow,
1306
-						'shares' => [],
1307
-						'objectData' => $data,
1308
-					]
1309
-				));
1310
-			}
1311
-		}
1312
-
1313
-		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ? AND `calendartype` = ?');
1314
-		$stmt->execute([$calendarId, $objectUri, $calendarType]);
1315
-
1316
-		if (is_array($data)) {
1317
-			$this->purgeProperties($calendarId, $data['id'], $calendarType);
1318
-		}
1319
-
1320
-		$this->addChange($calendarId, $objectUri, 3, $calendarType);
1321
-	}
1322
-
1323
-	/**
1324
-	 * Performs a calendar-query on the contents of this calendar.
1325
-	 *
1326
-	 * The calendar-query is defined in RFC4791 : CalDAV. Using the
1327
-	 * calendar-query it is possible for a client to request a specific set of
1328
-	 * object, based on contents of iCalendar properties, date-ranges and
1329
-	 * iCalendar component types (VTODO, VEVENT).
1330
-	 *
1331
-	 * This method should just return a list of (relative) urls that match this
1332
-	 * query.
1333
-	 *
1334
-	 * The list of filters are specified as an array. The exact array is
1335
-	 * documented by Sabre\CalDAV\CalendarQueryParser.
1336
-	 *
1337
-	 * Note that it is extremely likely that getCalendarObject for every path
1338
-	 * returned from this method will be called almost immediately after. You
1339
-	 * may want to anticipate this to speed up these requests.
1340
-	 *
1341
-	 * This method provides a default implementation, which parses *all* the
1342
-	 * iCalendar objects in the specified calendar.
1343
-	 *
1344
-	 * This default may well be good enough for personal use, and calendars
1345
-	 * that aren't very large. But if you anticipate high usage, big calendars
1346
-	 * or high loads, you are strongly advised to optimize certain paths.
1347
-	 *
1348
-	 * The best way to do so is override this method and to optimize
1349
-	 * specifically for 'common filters'.
1350
-	 *
1351
-	 * Requests that are extremely common are:
1352
-	 *   * requests for just VEVENTS
1353
-	 *   * requests for just VTODO
1354
-	 *   * requests with a time-range-filter on either VEVENT or VTODO.
1355
-	 *
1356
-	 * ..and combinations of these requests. It may not be worth it to try to
1357
-	 * handle every possible situation and just rely on the (relatively
1358
-	 * easy to use) CalendarQueryValidator to handle the rest.
1359
-	 *
1360
-	 * Note that especially time-range-filters may be difficult to parse. A
1361
-	 * time-range filter specified on a VEVENT must for instance also handle
1362
-	 * recurrence rules correctly.
1363
-	 * A good example of how to interprete all these filters can also simply
1364
-	 * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
1365
-	 * as possible, so it gives you a good idea on what type of stuff you need
1366
-	 * to think of.
1367
-	 *
1368
-	 * @param mixed $calendarId
1369
-	 * @param array $filters
1370
-	 * @param int $calendarType
1371
-	 * @return array
1372
-	 */
1373
-	public function calendarQuery($calendarId, array $filters, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
1374
-		$componentType = null;
1375
-		$requirePostFilter = true;
1376
-		$timeRange = null;
1377
-
1378
-		// if no filters were specified, we don't need to filter after a query
1379
-		if (!$filters['prop-filters'] && !$filters['comp-filters']) {
1380
-			$requirePostFilter = false;
1381
-		}
1382
-
1383
-		// Figuring out if there's a component filter
1384
-		if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
1385
-			$componentType = $filters['comp-filters'][0]['name'];
1386
-
1387
-			// Checking if we need post-filters
1388
-			if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
1389
-				$requirePostFilter = false;
1390
-			}
1391
-			// There was a time-range filter
1392
-			if ($componentType === 'VEVENT' && isset($filters['comp-filters'][0]['time-range']) && is_array($filters['comp-filters'][0]['time-range'])) {
1393
-				$timeRange = $filters['comp-filters'][0]['time-range'];
1394
-
1395
-				// If start time OR the end time is not specified, we can do a
1396
-				// 100% accurate mysql query.
1397
-				if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
1398
-					$requirePostFilter = false;
1399
-				}
1400
-			}
1401
-		}
1402
-		$columns = ['uri'];
1403
-		if ($requirePostFilter) {
1404
-			$columns = ['uri', 'calendardata'];
1405
-		}
1406
-		$query = $this->db->getQueryBuilder();
1407
-		$query->select($columns)
1408
-			->from('calendarobjects')
1409
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1410
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
1411
-
1412
-		if ($componentType) {
1413
-			$query->andWhere($query->expr()->eq('componenttype', $query->createNamedParameter($componentType)));
1414
-		}
1415
-
1416
-		if ($timeRange && $timeRange['start']) {
1417
-			$query->andWhere($query->expr()->gt('lastoccurence', $query->createNamedParameter($timeRange['start']->getTimeStamp())));
1418
-		}
1419
-		if ($timeRange && $timeRange['end']) {
1420
-			$query->andWhere($query->expr()->lt('firstoccurence', $query->createNamedParameter($timeRange['end']->getTimeStamp())));
1421
-		}
1422
-
1423
-		$stmt = $query->execute();
1424
-
1425
-		$result = [];
1426
-		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1427
-			if ($requirePostFilter) {
1428
-				// validateFilterForObject will parse the calendar data
1429
-				// catch parsing errors
1430
-				try {
1431
-					$matches = $this->validateFilterForObject($row, $filters);
1432
-				} catch (ParseException $ex) {
1433
-					$this->logger->logException($ex, [
1434
-						'app' => 'dav',
1435
-						'message' => 'Caught parsing exception for calendar data. This usually indicates invalid calendar data. calendar-id:'.$calendarId.' uri:'.$row['uri']
1436
-					]);
1437
-					continue;
1438
-				} catch (InvalidDataException $ex) {
1439
-					$this->logger->logException($ex, [
1440
-						'app' => 'dav',
1441
-						'message' => 'Caught invalid data exception for calendar data. This usually indicates invalid calendar data. calendar-id:'.$calendarId.' uri:'.$row['uri']
1442
-					]);
1443
-					continue;
1444
-				}
1445
-
1446
-				if (!$matches) {
1447
-					continue;
1448
-				}
1449
-			}
1450
-			$result[] = $row['uri'];
1451
-		}
1452
-
1453
-		return $result;
1454
-	}
1455
-
1456
-	/**
1457
-	 * custom Nextcloud search extension for CalDAV
1458
-	 *
1459
-	 * TODO - this should optionally cover cached calendar objects as well
1460
-	 *
1461
-	 * @param string $principalUri
1462
-	 * @param array $filters
1463
-	 * @param integer|null $limit
1464
-	 * @param integer|null $offset
1465
-	 * @return array
1466
-	 */
1467
-	public function calendarSearch($principalUri, array $filters, $limit = null, $offset = null) {
1468
-		$calendars = $this->getCalendarsForUser($principalUri);
1469
-		$ownCalendars = [];
1470
-		$sharedCalendars = [];
1471
-
1472
-		$uriMapper = [];
1473
-
1474
-		foreach ($calendars as $calendar) {
1475
-			if ($calendar['{http://owncloud.org/ns}owner-principal'] === $principalUri) {
1476
-				$ownCalendars[] = $calendar['id'];
1477
-			} else {
1478
-				$sharedCalendars[] = $calendar['id'];
1479
-			}
1480
-			$uriMapper[$calendar['id']] = $calendar['uri'];
1481
-		}
1482
-		if (count($ownCalendars) === 0 && count($sharedCalendars) === 0) {
1483
-			return [];
1484
-		}
1485
-
1486
-		$query = $this->db->getQueryBuilder();
1487
-		// Calendar id expressions
1488
-		$calendarExpressions = [];
1489
-		foreach ($ownCalendars as $id) {
1490
-			$calendarExpressions[] = $query->expr()->andX(
1491
-				$query->expr()->eq('c.calendarid',
1492
-					$query->createNamedParameter($id)),
1493
-				$query->expr()->eq('c.calendartype',
1494
-						$query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1495
-		}
1496
-		foreach ($sharedCalendars as $id) {
1497
-			$calendarExpressions[] = $query->expr()->andX(
1498
-				$query->expr()->eq('c.calendarid',
1499
-					$query->createNamedParameter($id)),
1500
-				$query->expr()->eq('c.classification',
1501
-					$query->createNamedParameter(self::CLASSIFICATION_PUBLIC)),
1502
-				$query->expr()->eq('c.calendartype',
1503
-					$query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1504
-		}
1505
-
1506
-		if (count($calendarExpressions) === 1) {
1507
-			$calExpr = $calendarExpressions[0];
1508
-		} else {
1509
-			$calExpr = call_user_func_array([$query->expr(), 'orX'], $calendarExpressions);
1510
-		}
1511
-
1512
-		// Component expressions
1513
-		$compExpressions = [];
1514
-		foreach ($filters['comps'] as $comp) {
1515
-			$compExpressions[] = $query->expr()
1516
-				->eq('c.componenttype', $query->createNamedParameter($comp));
1517
-		}
1518
-
1519
-		if (count($compExpressions) === 1) {
1520
-			$compExpr = $compExpressions[0];
1521
-		} else {
1522
-			$compExpr = call_user_func_array([$query->expr(), 'orX'], $compExpressions);
1523
-		}
1524
-
1525
-		if (!isset($filters['props'])) {
1526
-			$filters['props'] = [];
1527
-		}
1528
-		if (!isset($filters['params'])) {
1529
-			$filters['params'] = [];
1530
-		}
1531
-
1532
-		$propParamExpressions = [];
1533
-		foreach ($filters['props'] as $prop) {
1534
-			$propParamExpressions[] = $query->expr()->andX(
1535
-				$query->expr()->eq('i.name', $query->createNamedParameter($prop)),
1536
-				$query->expr()->isNull('i.parameter')
1537
-			);
1538
-		}
1539
-		foreach ($filters['params'] as $param) {
1540
-			$propParamExpressions[] = $query->expr()->andX(
1541
-				$query->expr()->eq('i.name', $query->createNamedParameter($param['property'])),
1542
-				$query->expr()->eq('i.parameter', $query->createNamedParameter($param['parameter']))
1543
-			);
1544
-		}
1545
-
1546
-		if (count($propParamExpressions) === 1) {
1547
-			$propParamExpr = $propParamExpressions[0];
1548
-		} else {
1549
-			$propParamExpr = call_user_func_array([$query->expr(), 'orX'], $propParamExpressions);
1550
-		}
1551
-
1552
-		$query->select(['c.calendarid', 'c.uri'])
1553
-			->from($this->dbObjectPropertiesTable, 'i')
1554
-			->join('i', 'calendarobjects', 'c', $query->expr()->eq('i.objectid', 'c.id'))
1555
-			->where($calExpr)
1556
-			->andWhere($compExpr)
1557
-			->andWhere($propParamExpr)
1558
-			->andWhere($query->expr()->iLike('i.value',
1559
-				$query->createNamedParameter('%'.$this->db->escapeLikeParameter($filters['search-term']).'%')));
1560
-
1561
-		if ($offset) {
1562
-			$query->setFirstResult($offset);
1563
-		}
1564
-		if ($limit) {
1565
-			$query->setMaxResults($limit);
1566
-		}
1567
-
1568
-		$stmt = $query->execute();
1569
-
1570
-		$result = [];
1571
-		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1572
-			$path = $uriMapper[$row['calendarid']] . '/' . $row['uri'];
1573
-			if (!in_array($path, $result)) {
1574
-				$result[] = $path;
1575
-			}
1576
-		}
1577
-
1578
-		return $result;
1579
-	}
1580
-
1581
-	/**
1582
-	 * used for Nextcloud's calendar API
1583
-	 *
1584
-	 * @param array $calendarInfo
1585
-	 * @param string $pattern
1586
-	 * @param array $searchProperties
1587
-	 * @param array $options
1588
-	 * @param integer|null $limit
1589
-	 * @param integer|null $offset
1590
-	 *
1591
-	 * @return array
1592
-	 */
1593
-	public function search(array $calendarInfo, $pattern, array $searchProperties,
1594
-						   array $options, $limit, $offset) {
1595
-		$outerQuery = $this->db->getQueryBuilder();
1596
-		$innerQuery = $this->db->getQueryBuilder();
1597
-
1598
-		$innerQuery->selectDistinct('op.objectid')
1599
-			->from($this->dbObjectPropertiesTable, 'op')
1600
-			->andWhere($innerQuery->expr()->eq('op.calendarid',
1601
-				$outerQuery->createNamedParameter($calendarInfo['id'])))
1602
-			->andWhere($innerQuery->expr()->eq('op.calendartype',
1603
-				$outerQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1604
-
1605
-		// only return public items for shared calendars for now
1606
-		if ($calendarInfo['principaluri'] !== $calendarInfo['{http://owncloud.org/ns}owner-principal']) {
1607
-			$innerQuery->andWhere($innerQuery->expr()->eq('c.classification',
1608
-				$outerQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1609
-		}
1610
-
1611
-		$or = $innerQuery->expr()->orX();
1612
-		foreach ($searchProperties as $searchProperty) {
1613
-			$or->add($innerQuery->expr()->eq('op.name',
1614
-				$outerQuery->createNamedParameter($searchProperty)));
1615
-		}
1616
-		$innerQuery->andWhere($or);
1617
-
1618
-		if ($pattern !== '') {
1619
-			$innerQuery->andWhere($innerQuery->expr()->iLike('op.value',
1620
-				$outerQuery->createNamedParameter('%' .
1621
-					$this->db->escapeLikeParameter($pattern) . '%')));
1622
-		}
1623
-
1624
-		$outerQuery->select('c.id', 'c.calendardata', 'c.componenttype', 'c.uid', 'c.uri')
1625
-			->from('calendarobjects', 'c');
1626
-
1627
-		if (isset($options['timerange'])) {
1628
-			if (isset($options['timerange']['start']) && $options['timerange']['start'] instanceof DateTime) {
1629
-				$outerQuery->andWhere($outerQuery->expr()->gt('lastoccurence',
1630
-					$outerQuery->createNamedParameter($options['timerange']['start']->getTimeStamp())));
1631
-			}
1632
-			if (isset($options['timerange']['end']) && $options['timerange']['end'] instanceof DateTime) {
1633
-				$outerQuery->andWhere($outerQuery->expr()->lt('firstoccurence',
1634
-					$outerQuery->createNamedParameter($options['timerange']['end']->getTimeStamp())));
1635
-			}
1636
-		}
1637
-
1638
-		if (isset($options['types'])) {
1639
-			$or = $outerQuery->expr()->orX();
1640
-			foreach ($options['types'] as $type) {
1641
-				$or->add($outerQuery->expr()->eq('componenttype',
1642
-					$outerQuery->createNamedParameter($type)));
1643
-			}
1644
-			$outerQuery->andWhere($or);
1645
-		}
1646
-
1647
-		$outerQuery->andWhere($outerQuery->expr()->in('c.id',
1648
-			$outerQuery->createFunction($innerQuery->getSQL())));
1649
-
1650
-		if ($offset) {
1651
-			$outerQuery->setFirstResult($offset);
1652
-		}
1653
-		if ($limit) {
1654
-			$outerQuery->setMaxResults($limit);
1655
-		}
1656
-
1657
-		$result = $outerQuery->execute();
1658
-		$calendarObjects = $result->fetchAll();
1659
-
1660
-		return array_map(function ($o) {
1661
-			$calendarData = Reader::read($o['calendardata']);
1662
-			$comps = $calendarData->getComponents();
1663
-			$objects = [];
1664
-			$timezones = [];
1665
-			foreach ($comps as $comp) {
1666
-				if ($comp instanceof VTimeZone) {
1667
-					$timezones[] = $comp;
1668
-				} else {
1669
-					$objects[] = $comp;
1670
-				}
1671
-			}
1672
-
1673
-			return [
1674
-				'id' => $o['id'],
1675
-				'type' => $o['componenttype'],
1676
-				'uid' => $o['uid'],
1677
-				'uri' => $o['uri'],
1678
-				'objects' => array_map(function ($c) {
1679
-					return $this->transformSearchData($c);
1680
-				}, $objects),
1681
-				'timezones' => array_map(function ($c) {
1682
-					return $this->transformSearchData($c);
1683
-				}, $timezones),
1684
-			];
1685
-		}, $calendarObjects);
1686
-	}
1687
-
1688
-	/**
1689
-	 * @param Component $comp
1690
-	 * @return array
1691
-	 */
1692
-	private function transformSearchData(Component $comp) {
1693
-		$data = [];
1694
-		/** @var Component[] $subComponents */
1695
-		$subComponents = $comp->getComponents();
1696
-		/** @var Property[] $properties */
1697
-		$properties = array_filter($comp->children(), function ($c) {
1698
-			return $c instanceof Property;
1699
-		});
1700
-		$validationRules = $comp->getValidationRules();
1701
-
1702
-		foreach ($subComponents as $subComponent) {
1703
-			$name = $subComponent->name;
1704
-			if (!isset($data[$name])) {
1705
-				$data[$name] = [];
1706
-			}
1707
-			$data[$name][] = $this->transformSearchData($subComponent);
1708
-		}
1709
-
1710
-		foreach ($properties as $property) {
1711
-			$name = $property->name;
1712
-			if (!isset($validationRules[$name])) {
1713
-				$validationRules[$name] = '*';
1714
-			}
1715
-
1716
-			$rule = $validationRules[$property->name];
1717
-			if ($rule === '+' || $rule === '*') { // multiple
1718
-				if (!isset($data[$name])) {
1719
-					$data[$name] = [];
1720
-				}
1721
-
1722
-				$data[$name][] = $this->transformSearchProperty($property);
1723
-			} else { // once
1724
-				$data[$name] = $this->transformSearchProperty($property);
1725
-			}
1726
-		}
1727
-
1728
-		return $data;
1729
-	}
1730
-
1731
-	/**
1732
-	 * @param Property $prop
1733
-	 * @return array
1734
-	 */
1735
-	private function transformSearchProperty(Property $prop) {
1736
-		// No need to check Date, as it extends DateTime
1737
-		if ($prop instanceof Property\ICalendar\DateTime) {
1738
-			$value = $prop->getDateTime();
1739
-		} else {
1740
-			$value = $prop->getValue();
1741
-		}
1742
-
1743
-		return [
1744
-			$value,
1745
-			$prop->parameters()
1746
-		];
1747
-	}
1748
-
1749
-	/**
1750
-	 * @param string $principalUri
1751
-	 * @param string $pattern
1752
-	 * @param array $componentTypes
1753
-	 * @param array $searchProperties
1754
-	 * @param array $searchParameters
1755
-	 * @param array $options
1756
-	 * @return array
1757
-	 */
1758
-	public function searchPrincipalUri(string $principalUri,
1759
-									   string $pattern,
1760
-									   array $componentTypes,
1761
-									   array $searchProperties,
1762
-									   array $searchParameters,
1763
-									   array $options = []): array {
1764
-		$escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false;
1765
-
1766
-		$calendarObjectIdQuery = $this->db->getQueryBuilder();
1767
-		$calendarOr = $calendarObjectIdQuery->expr()->orX();
1768
-		$searchOr = $calendarObjectIdQuery->expr()->orX();
1769
-
1770
-		// Fetch calendars and subscription
1771
-		$calendars = $this->getCalendarsForUser($principalUri);
1772
-		$subscriptions = $this->getSubscriptionsForUser($principalUri);
1773
-		foreach ($calendars as $calendar) {
1774
-			$calendarAnd = $calendarObjectIdQuery->expr()->andX();
1775
-			$calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$calendar['id'])));
1776
-			$calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1777
-
1778
-			// If it's shared, limit search to public events
1779
-			if (isset($calendar['{http://owncloud.org/ns}owner-principal'])
1780
-				&& $calendar['principaluri'] !== $calendar['{http://owncloud.org/ns}owner-principal']) {
1781
-				$calendarAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1782
-			}
1783
-
1784
-			$calendarOr->add($calendarAnd);
1785
-		}
1786
-		foreach ($subscriptions as $subscription) {
1787
-			$subscriptionAnd = $calendarObjectIdQuery->expr()->andX();
1788
-			$subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$subscription['id'])));
1789
-			$subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
1790
-
1791
-			// If it's shared, limit search to public events
1792
-			if (isset($subscription['{http://owncloud.org/ns}owner-principal'])
1793
-				&& $subscription['principaluri'] !== $subscription['{http://owncloud.org/ns}owner-principal']) {
1794
-				$subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1795
-			}
1796
-
1797
-			$calendarOr->add($subscriptionAnd);
1798
-		}
1799
-
1800
-		foreach ($searchProperties as $property) {
1801
-			$propertyAnd = $calendarObjectIdQuery->expr()->andX();
1802
-			$propertyAnd->add($calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)));
1803
-			$propertyAnd->add($calendarObjectIdQuery->expr()->isNull('cob.parameter'));
1804
-
1805
-			$searchOr->add($propertyAnd);
1806
-		}
1807
-		foreach ($searchParameters as $property => $parameter) {
1808
-			$parameterAnd = $calendarObjectIdQuery->expr()->andX();
1809
-			$parameterAnd->add($calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)));
1810
-			$parameterAnd->add($calendarObjectIdQuery->expr()->eq('cob.parameter', $calendarObjectIdQuery->createNamedParameter($parameter, IQueryBuilder::PARAM_STR_ARRAY)));
1811
-
1812
-			$searchOr->add($parameterAnd);
1813
-		}
1814
-
1815
-		if ($calendarOr->count() === 0) {
1816
-			return [];
1817
-		}
1818
-		if ($searchOr->count() === 0) {
1819
-			return [];
1820
-		}
1821
-
1822
-		$calendarObjectIdQuery->selectDistinct('cob.objectid')
1823
-			->from($this->dbObjectPropertiesTable, 'cob')
1824
-			->leftJoin('cob', 'calendarobjects', 'co', $calendarObjectIdQuery->expr()->eq('co.id', 'cob.objectid'))
1825
-			->andWhere($calendarObjectIdQuery->expr()->in('co.componenttype', $calendarObjectIdQuery->createNamedParameter($componentTypes, IQueryBuilder::PARAM_STR_ARRAY)))
1826
-			->andWhere($calendarOr)
1827
-			->andWhere($searchOr);
1828
-
1829
-		if ('' !== $pattern) {
1830
-			if (!$escapePattern) {
1831
-				$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter($pattern)));
1832
-			} else {
1833
-				$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
1834
-			}
1835
-		}
1836
-
1837
-		if (isset($options['limit'])) {
1838
-			$calendarObjectIdQuery->setMaxResults($options['limit']);
1839
-		}
1840
-		if (isset($options['offset'])) {
1841
-			$calendarObjectIdQuery->setFirstResult($options['offset']);
1842
-		}
1843
-
1844
-		$result = $calendarObjectIdQuery->execute();
1845
-		$matches = $result->fetchAll();
1846
-		$result->closeCursor();
1847
-		$matches = array_map(static function (array $match):int {
1848
-			return (int) $match['objectid'];
1849
-		}, $matches);
1850
-
1851
-		$query = $this->db->getQueryBuilder();
1852
-		$query->select('calendardata', 'uri', 'calendarid', 'calendartype')
1853
-			->from('calendarobjects')
1854
-			->where($query->expr()->in('id', $query->createNamedParameter($matches, IQueryBuilder::PARAM_INT_ARRAY)));
1855
-
1856
-		$result = $query->execute();
1857
-		$calendarObjects = $result->fetchAll();
1858
-		$result->closeCursor();
1859
-
1860
-		return array_map(function (array $array): array {
1861
-			$array['calendarid'] = (int)$array['calendarid'];
1862
-			$array['calendartype'] = (int)$array['calendartype'];
1863
-			$array['calendardata'] = $this->readBlob($array['calendardata']);
1864
-
1865
-			return $array;
1866
-		}, $calendarObjects);
1867
-	}
1868
-
1869
-	/**
1870
-	 * Searches through all of a users calendars and calendar objects to find
1871
-	 * an object with a specific UID.
1872
-	 *
1873
-	 * This method should return the path to this object, relative to the
1874
-	 * calendar home, so this path usually only contains two parts:
1875
-	 *
1876
-	 * calendarpath/objectpath.ics
1877
-	 *
1878
-	 * If the uid is not found, return null.
1879
-	 *
1880
-	 * This method should only consider * objects that the principal owns, so
1881
-	 * any calendars owned by other principals that also appear in this
1882
-	 * collection should be ignored.
1883
-	 *
1884
-	 * @param string $principalUri
1885
-	 * @param string $uid
1886
-	 * @return string|null
1887
-	 */
1888
-	public function getCalendarObjectByUID($principalUri, $uid) {
1889
-		$query = $this->db->getQueryBuilder();
1890
-		$query->selectAlias('c.uri', 'calendaruri')->selectAlias('co.uri', 'objecturi')
1891
-			->from('calendarobjects', 'co')
1892
-			->leftJoin('co', 'calendars', 'c', $query->expr()->eq('co.calendarid', 'c.id'))
1893
-			->where($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri)))
1894
-			->andWhere($query->expr()->eq('co.uid', $query->createNamedParameter($uid)));
1895
-
1896
-		$stmt = $query->execute();
1897
-
1898
-		if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1899
-			return $row['calendaruri'] . '/' . $row['objecturi'];
1900
-		}
1901
-
1902
-		return null;
1903
-	}
1904
-
1905
-	/**
1906
-	 * The getChanges method returns all the changes that have happened, since
1907
-	 * the specified syncToken in the specified calendar.
1908
-	 *
1909
-	 * This function should return an array, such as the following:
1910
-	 *
1911
-	 * [
1912
-	 *   'syncToken' => 'The current synctoken',
1913
-	 *   'added'   => [
1914
-	 *      'new.txt',
1915
-	 *   ],
1916
-	 *   'modified'   => [
1917
-	 *      'modified.txt',
1918
-	 *   ],
1919
-	 *   'deleted' => [
1920
-	 *      'foo.php.bak',
1921
-	 *      'old.txt'
1922
-	 *   ]
1923
-	 * );
1924
-	 *
1925
-	 * The returned syncToken property should reflect the *current* syncToken
1926
-	 * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
1927
-	 * property This is * needed here too, to ensure the operation is atomic.
1928
-	 *
1929
-	 * If the $syncToken argument is specified as null, this is an initial
1930
-	 * sync, and all members should be reported.
1931
-	 *
1932
-	 * The modified property is an array of nodenames that have changed since
1933
-	 * the last token.
1934
-	 *
1935
-	 * The deleted property is an array with nodenames, that have been deleted
1936
-	 * from collection.
1937
-	 *
1938
-	 * The $syncLevel argument is basically the 'depth' of the report. If it's
1939
-	 * 1, you only have to report changes that happened only directly in
1940
-	 * immediate descendants. If it's 2, it should also include changes from
1941
-	 * the nodes below the child collections. (grandchildren)
1942
-	 *
1943
-	 * The $limit argument allows a client to specify how many results should
1944
-	 * be returned at most. If the limit is not specified, it should be treated
1945
-	 * as infinite.
1946
-	 *
1947
-	 * If the limit (infinite or not) is higher than you're willing to return,
1948
-	 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
1949
-	 *
1950
-	 * If the syncToken is expired (due to data cleanup) or unknown, you must
1951
-	 * return null.
1952
-	 *
1953
-	 * The limit is 'suggestive'. You are free to ignore it.
1954
-	 *
1955
-	 * @param string $calendarId
1956
-	 * @param string $syncToken
1957
-	 * @param int $syncLevel
1958
-	 * @param int $limit
1959
-	 * @param int $calendarType
1960
-	 * @return array
1961
-	 */
1962
-	public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1963
-		// Current synctoken
1964
-		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*calendars` WHERE `id` = ?');
1965
-		$stmt->execute([ $calendarId ]);
1966
-		$currentToken = $stmt->fetchOne();
1967
-
1968
-		if (is_null($currentToken)) {
1969
-			return null;
1970
-		}
1971
-
1972
-		$result = [
1973
-			'syncToken' => $currentToken,
1974
-			'added' => [],
1975
-			'modified' => [],
1976
-			'deleted' => [],
1977
-		];
1978
-
1979
-		if ($syncToken) {
1980
-			$query = "SELECT `uri`, `operation` FROM `*PREFIX*calendarchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `calendarid` = ? AND `calendartype` = ? ORDER BY `synctoken`";
1981
-			if ($limit > 0) {
1982
-				$query .= " LIMIT " . (int)$limit;
1983
-			}
1984
-
1985
-			// Fetching all changes
1986
-			$stmt = $this->db->prepare($query);
1987
-			$stmt->execute([$syncToken, $currentToken, $calendarId, $calendarType]);
1988
-
1989
-			$changes = [];
1990
-
1991
-			// This loop ensures that any duplicates are overwritten, only the
1992
-			// last change on a node is relevant.
1993
-			while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1994
-				$changes[$row['uri']] = $row['operation'];
1995
-			}
1996
-
1997
-			foreach ($changes as $uri => $operation) {
1998
-				switch ($operation) {
1999
-					case 1:
2000
-						$result['added'][] = $uri;
2001
-						break;
2002
-					case 2:
2003
-						$result['modified'][] = $uri;
2004
-						break;
2005
-					case 3:
2006
-						$result['deleted'][] = $uri;
2007
-						break;
2008
-				}
2009
-			}
2010
-		} else {
2011
-			// No synctoken supplied, this is the initial sync.
2012
-			$query = "SELECT `uri` FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `calendartype` = ?";
2013
-			$stmt = $this->db->prepare($query);
2014
-			$stmt->execute([$calendarId, $calendarType]);
2015
-
2016
-			$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
2017
-		}
2018
-		return $result;
2019
-	}
2020
-
2021
-	/**
2022
-	 * Returns a list of subscriptions for a principal.
2023
-	 *
2024
-	 * Every subscription is an array with the following keys:
2025
-	 *  * id, a unique id that will be used by other functions to modify the
2026
-	 *    subscription. This can be the same as the uri or a database key.
2027
-	 *  * uri. This is just the 'base uri' or 'filename' of the subscription.
2028
-	 *  * principaluri. The owner of the subscription. Almost always the same as
2029
-	 *    principalUri passed to this method.
2030
-	 *
2031
-	 * Furthermore, all the subscription info must be returned too:
2032
-	 *
2033
-	 * 1. {DAV:}displayname
2034
-	 * 2. {http://apple.com/ns/ical/}refreshrate
2035
-	 * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
2036
-	 *    should not be stripped).
2037
-	 * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
2038
-	 *    should not be stripped).
2039
-	 * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
2040
-	 *    attachments should not be stripped).
2041
-	 * 6. {http://calendarserver.org/ns/}source (Must be a
2042
-	 *     Sabre\DAV\Property\Href).
2043
-	 * 7. {http://apple.com/ns/ical/}calendar-color
2044
-	 * 8. {http://apple.com/ns/ical/}calendar-order
2045
-	 * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
2046
-	 *    (should just be an instance of
2047
-	 *    Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
2048
-	 *    default components).
2049
-	 *
2050
-	 * @param string $principalUri
2051
-	 * @return array
2052
-	 */
2053
-	public function getSubscriptionsForUser($principalUri) {
2054
-		$fields = array_values($this->subscriptionPropertyMap);
2055
-		$fields[] = 'id';
2056
-		$fields[] = 'uri';
2057
-		$fields[] = 'source';
2058
-		$fields[] = 'principaluri';
2059
-		$fields[] = 'lastmodified';
2060
-		$fields[] = 'synctoken';
2061
-
2062
-		$query = $this->db->getQueryBuilder();
2063
-		$query->select($fields)
2064
-			->from('calendarsubscriptions')
2065
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2066
-			->orderBy('calendarorder', 'asc');
2067
-		$stmt = $query->execute();
2068
-
2069
-		$subscriptions = [];
2070
-		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
2071
-			$subscription = [
2072
-				'id' => $row['id'],
2073
-				'uri' => $row['uri'],
2074
-				'principaluri' => $row['principaluri'],
2075
-				'source' => $row['source'],
2076
-				'lastmodified' => $row['lastmodified'],
2077
-
2078
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
2079
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
2080
-			];
2081
-
2082
-			foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
2083
-				if (!is_null($row[$dbName])) {
2084
-					$subscription[$xmlName] = $row[$dbName];
2085
-				}
2086
-			}
2087
-
2088
-			$subscriptions[] = $subscription;
2089
-		}
2090
-
2091
-		return $subscriptions;
2092
-	}
2093
-
2094
-	/**
2095
-	 * Creates a new subscription for a principal.
2096
-	 *
2097
-	 * If the creation was a success, an id must be returned that can be used to reference
2098
-	 * this subscription in other methods, such as updateSubscription.
2099
-	 *
2100
-	 * @param string $principalUri
2101
-	 * @param string $uri
2102
-	 * @param array $properties
2103
-	 * @return mixed
2104
-	 */
2105
-	public function createSubscription($principalUri, $uri, array $properties) {
2106
-		if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
2107
-			throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
2108
-		}
2109
-
2110
-		$values = [
2111
-			'principaluri' => $principalUri,
2112
-			'uri' => $uri,
2113
-			'source' => $properties['{http://calendarserver.org/ns/}source']->getHref(),
2114
-			'lastmodified' => time(),
2115
-		];
2116
-
2117
-		$propertiesBoolean = ['striptodos', 'stripalarms', 'stripattachments'];
2118
-
2119
-		foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
2120
-			if (array_key_exists($xmlName, $properties)) {
2121
-				$values[$dbName] = $properties[$xmlName];
2122
-				if (in_array($dbName, $propertiesBoolean)) {
2123
-					$values[$dbName] = true;
2124
-				}
2125
-			}
2126
-		}
2127
-
2128
-		$valuesToInsert = [];
2129
-
2130
-		$query = $this->db->getQueryBuilder();
2131
-
2132
-		foreach (array_keys($values) as $name) {
2133
-			$valuesToInsert[$name] = $query->createNamedParameter($values[$name]);
2134
-		}
2135
-
2136
-		$query->insert('calendarsubscriptions')
2137
-			->values($valuesToInsert)
2138
-			->execute();
2139
-
2140
-		$subscriptionId = $this->db->lastInsertId('*PREFIX*calendarsubscriptions');
2141
-
2142
-		$subscriptionRow = $this->getSubscriptionById($subscriptionId);
2143
-		$this->dispatcher->dispatchTyped(new SubscriptionCreatedEvent((int)$subscriptionId, $subscriptionRow));
2144
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createSubscription', new GenericEvent(
2145
-			'\OCA\DAV\CalDAV\CalDavBackend::createSubscription',
2146
-			[
2147
-				'subscriptionId' => $subscriptionId,
2148
-				'subscriptionData' => $subscriptionRow,
2149
-			]));
2150
-
2151
-		return $subscriptionId;
2152
-	}
2153
-
2154
-	/**
2155
-	 * Updates a subscription
2156
-	 *
2157
-	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
2158
-	 * To do the actual updates, you must tell this object which properties
2159
-	 * you're going to process with the handle() method.
2160
-	 *
2161
-	 * Calling the handle method is like telling the PropPatch object "I
2162
-	 * promise I can handle updating this property".
2163
-	 *
2164
-	 * Read the PropPatch documentation for more info and examples.
2165
-	 *
2166
-	 * @param mixed $subscriptionId
2167
-	 * @param PropPatch $propPatch
2168
-	 * @return void
2169
-	 */
2170
-	public function updateSubscription($subscriptionId, PropPatch $propPatch) {
2171
-		$supportedProperties = array_keys($this->subscriptionPropertyMap);
2172
-		$supportedProperties[] = '{http://calendarserver.org/ns/}source';
2173
-
2174
-		$propPatch->handle($supportedProperties, function ($mutations) use ($subscriptionId) {
2175
-			$newValues = [];
2176
-
2177
-			foreach ($mutations as $propertyName => $propertyValue) {
2178
-				if ($propertyName === '{http://calendarserver.org/ns/}source') {
2179
-					$newValues['source'] = $propertyValue->getHref();
2180
-				} else {
2181
-					$fieldName = $this->subscriptionPropertyMap[$propertyName];
2182
-					$newValues[$fieldName] = $propertyValue;
2183
-				}
2184
-			}
2185
-
2186
-			$query = $this->db->getQueryBuilder();
2187
-			$query->update('calendarsubscriptions')
2188
-				->set('lastmodified', $query->createNamedParameter(time()));
2189
-			foreach ($newValues as $fieldName => $value) {
2190
-				$query->set($fieldName, $query->createNamedParameter($value));
2191
-			}
2192
-			$query->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
2193
-				->execute();
2194
-
2195
-			$subscriptionRow = $this->getSubscriptionById($subscriptionId);
2196
-			$this->dispatcher->dispatchTyped(new SubscriptionUpdatedEvent((int)$subscriptionId, $subscriptionRow, [], $mutations));
2197
-			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateSubscription', new GenericEvent(
2198
-				'\OCA\DAV\CalDAV\CalDavBackend::updateSubscription',
2199
-				[
2200
-					'subscriptionId' => $subscriptionId,
2201
-					'subscriptionData' => $subscriptionRow,
2202
-					'propertyMutations' => $mutations,
2203
-				]));
2204
-
2205
-			return true;
2206
-		});
2207
-	}
2208
-
2209
-	/**
2210
-	 * Deletes a subscription.
2211
-	 *
2212
-	 * @param mixed $subscriptionId
2213
-	 * @return void
2214
-	 */
2215
-	public function deleteSubscription($subscriptionId) {
2216
-		$subscriptionRow = $this->getSubscriptionById($subscriptionId);
2217
-
2218
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription', new GenericEvent(
2219
-			'\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription',
2220
-			[
2221
-				'subscriptionId' => $subscriptionId,
2222
-				'subscriptionData' => $this->getSubscriptionById($subscriptionId),
2223
-			]));
2224
-
2225
-		$query = $this->db->getQueryBuilder();
2226
-		$query->delete('calendarsubscriptions')
2227
-			->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
2228
-			->execute();
2229
-
2230
-		$query = $this->db->getQueryBuilder();
2231
-		$query->delete('calendarobjects')
2232
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2233
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2234
-			->execute();
2235
-
2236
-		$query->delete('calendarchanges')
2237
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2238
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2239
-			->execute();
2240
-
2241
-		$query->delete($this->dbObjectPropertiesTable)
2242
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2243
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2244
-			->execute();
2245
-
2246
-		if ($subscriptionRow) {
2247
-			$this->dispatcher->dispatchTyped(new SubscriptionDeletedEvent((int)$subscriptionId, $subscriptionRow, []));
2248
-		}
2249
-	}
2250
-
2251
-	/**
2252
-	 * Returns a single scheduling object for the inbox collection.
2253
-	 *
2254
-	 * The returned array should contain the following elements:
2255
-	 *   * uri - A unique basename for the object. This will be used to
2256
-	 *           construct a full uri.
2257
-	 *   * calendardata - The iCalendar object
2258
-	 *   * lastmodified - The last modification date. Can be an int for a unix
2259
-	 *                    timestamp, or a PHP DateTime object.
2260
-	 *   * etag - A unique token that must change if the object changed.
2261
-	 *   * size - The size of the object, in bytes.
2262
-	 *
2263
-	 * @param string $principalUri
2264
-	 * @param string $objectUri
2265
-	 * @return array
2266
-	 */
2267
-	public function getSchedulingObject($principalUri, $objectUri) {
2268
-		$query = $this->db->getQueryBuilder();
2269
-		$stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
2270
-			->from('schedulingobjects')
2271
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2272
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
2273
-			->execute();
2274
-
2275
-		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
2276
-
2277
-		if (!$row) {
2278
-			return null;
2279
-		}
2280
-
2281
-		return [
2282
-			'uri' => $row['uri'],
2283
-			'calendardata' => $row['calendardata'],
2284
-			'lastmodified' => $row['lastmodified'],
2285
-			'etag' => '"' . $row['etag'] . '"',
2286
-			'size' => (int)$row['size'],
2287
-		];
2288
-	}
2289
-
2290
-	/**
2291
-	 * Returns all scheduling objects for the inbox collection.
2292
-	 *
2293
-	 * These objects should be returned as an array. Every item in the array
2294
-	 * should follow the same structure as returned from getSchedulingObject.
2295
-	 *
2296
-	 * The main difference is that 'calendardata' is optional.
2297
-	 *
2298
-	 * @param string $principalUri
2299
-	 * @return array
2300
-	 */
2301
-	public function getSchedulingObjects($principalUri) {
2302
-		$query = $this->db->getQueryBuilder();
2303
-		$stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
2304
-				->from('schedulingobjects')
2305
-				->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2306
-				->execute();
2307
-
2308
-		$result = [];
2309
-		foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
2310
-			$result[] = [
2311
-				'calendardata' => $row['calendardata'],
2312
-				'uri' => $row['uri'],
2313
-				'lastmodified' => $row['lastmodified'],
2314
-				'etag' => '"' . $row['etag'] . '"',
2315
-				'size' => (int)$row['size'],
2316
-			];
2317
-		}
2318
-
2319
-		return $result;
2320
-	}
2321
-
2322
-	/**
2323
-	 * Deletes a scheduling object from the inbox collection.
2324
-	 *
2325
-	 * @param string $principalUri
2326
-	 * @param string $objectUri
2327
-	 * @return void
2328
-	 */
2329
-	public function deleteSchedulingObject($principalUri, $objectUri) {
2330
-		$query = $this->db->getQueryBuilder();
2331
-		$query->delete('schedulingobjects')
2332
-				->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2333
-				->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
2334
-				->execute();
2335
-	}
2336
-
2337
-	/**
2338
-	 * Creates a new scheduling object. This should land in a users' inbox.
2339
-	 *
2340
-	 * @param string $principalUri
2341
-	 * @param string $objectUri
2342
-	 * @param string $objectData
2343
-	 * @return void
2344
-	 */
2345
-	public function createSchedulingObject($principalUri, $objectUri, $objectData) {
2346
-		$query = $this->db->getQueryBuilder();
2347
-		$query->insert('schedulingobjects')
2348
-			->values([
2349
-				'principaluri' => $query->createNamedParameter($principalUri),
2350
-				'calendardata' => $query->createNamedParameter($objectData, IQueryBuilder::PARAM_LOB),
2351
-				'uri' => $query->createNamedParameter($objectUri),
2352
-				'lastmodified' => $query->createNamedParameter(time()),
2353
-				'etag' => $query->createNamedParameter(md5($objectData)),
2354
-				'size' => $query->createNamedParameter(strlen($objectData))
2355
-			])
2356
-			->execute();
2357
-	}
2358
-
2359
-	/**
2360
-	 * Adds a change record to the calendarchanges table.
2361
-	 *
2362
-	 * @param mixed $calendarId
2363
-	 * @param string $objectUri
2364
-	 * @param int $operation 1 = add, 2 = modify, 3 = delete.
2365
-	 * @param int $calendarType
2366
-	 * @return void
2367
-	 */
2368
-	protected function addChange($calendarId, $objectUri, $operation, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2369
-		$table = $calendarType === self::CALENDAR_TYPE_CALENDAR ? 'calendars': 'calendarsubscriptions';
2370
-
2371
-		$query = $this->db->getQueryBuilder();
2372
-		$query->select('synctoken')
2373
-			->from($table)
2374
-			->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
2375
-		$result = $query->execute();
2376
-		$syncToken = (int)$result->fetchOne();
2377
-		$result->closeCursor();
2378
-
2379
-		$query = $this->db->getQueryBuilder();
2380
-		$query->insert('calendarchanges')
2381
-			->values([
2382
-				'uri' => $query->createNamedParameter($objectUri),
2383
-				'synctoken' => $query->createNamedParameter($syncToken),
2384
-				'calendarid' => $query->createNamedParameter($calendarId),
2385
-				'operation' => $query->createNamedParameter($operation),
2386
-				'calendartype' => $query->createNamedParameter($calendarType),
2387
-			])
2388
-			->execute();
2389
-
2390
-		$stmt = $this->db->prepare("UPDATE `*PREFIX*$table` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?");
2391
-		$stmt->execute([
2392
-			$calendarId
2393
-		]);
2394
-	}
2395
-
2396
-	/**
2397
-	 * Parses some information from calendar objects, used for optimized
2398
-	 * calendar-queries.
2399
-	 *
2400
-	 * Returns an array with the following keys:
2401
-	 *   * etag - An md5 checksum of the object without the quotes.
2402
-	 *   * size - Size of the object in bytes
2403
-	 *   * componentType - VEVENT, VTODO or VJOURNAL
2404
-	 *   * firstOccurence
2405
-	 *   * lastOccurence
2406
-	 *   * uid - value of the UID property
2407
-	 *
2408
-	 * @param string $calendarData
2409
-	 * @return array
2410
-	 */
2411
-	public function getDenormalizedData($calendarData) {
2412
-		$vObject = Reader::read($calendarData);
2413
-		$vEvents = [];
2414
-		$componentType = null;
2415
-		$component = null;
2416
-		$firstOccurrence = null;
2417
-		$lastOccurrence = null;
2418
-		$uid = null;
2419
-		$classification = self::CLASSIFICATION_PUBLIC;
2420
-		$hasDTSTART = false;
2421
-		foreach ($vObject->getComponents() as $component) {
2422
-			if ($component->name !== 'VTIMEZONE') {
2423
-				// Finding all VEVENTs, and track them
2424
-				if ($component->name === 'VEVENT') {
2425
-					array_push($vEvents, $component);
2426
-					if ($component->DTSTART) {
2427
-						$hasDTSTART = true;
2428
-					}
2429
-				}
2430
-				// Track first component type and uid
2431
-				if ($uid === null) {
2432
-					$componentType = $component->name;
2433
-					$uid = (string)$component->UID;
2434
-				}
2435
-			}
2436
-		}
2437
-		if (!$componentType) {
2438
-			throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
2439
-		}
2440
-
2441
-		if ($hasDTSTART) {
2442
-			$component = $vEvents[0];
2443
-
2444
-			// Finding the last occurrence is a bit harder
2445
-			if (!isset($component->RRULE) && count($vEvents) === 1) {
2446
-				$firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp();
2447
-				if (isset($component->DTEND)) {
2448
-					$lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp();
2449
-				} elseif (isset($component->DURATION)) {
2450
-					$endDate = clone $component->DTSTART->getDateTime();
2451
-					$endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
2452
-					$lastOccurrence = $endDate->getTimeStamp();
2453
-				} elseif (!$component->DTSTART->hasTime()) {
2454
-					$endDate = clone $component->DTSTART->getDateTime();
2455
-					$endDate->modify('+1 day');
2456
-					$lastOccurrence = $endDate->getTimeStamp();
2457
-				} else {
2458
-					$lastOccurrence = $firstOccurrence;
2459
-				}
2460
-			} else {
2461
-				$it = new EventIterator($vEvents);
2462
-				$maxDate = new DateTime(self::MAX_DATE);
2463
-				$firstOccurrence = $it->getDtStart()->getTimestamp();
2464
-				if ($it->isInfinite()) {
2465
-					$lastOccurrence = $maxDate->getTimestamp();
2466
-				} else {
2467
-					$end = $it->getDtEnd();
2468
-					while ($it->valid() && $end < $maxDate) {
2469
-						$end = $it->getDtEnd();
2470
-						$it->next();
2471
-					}
2472
-					$lastOccurrence = $end->getTimestamp();
2473
-				}
2474
-			}
2475
-		}
2476
-
2477
-		if ($component->CLASS) {
2478
-			$classification = CalDavBackend::CLASSIFICATION_PRIVATE;
2479
-			switch ($component->CLASS->getValue()) {
2480
-				case 'PUBLIC':
2481
-					$classification = CalDavBackend::CLASSIFICATION_PUBLIC;
2482
-					break;
2483
-				case 'CONFIDENTIAL':
2484
-					$classification = CalDavBackend::CLASSIFICATION_CONFIDENTIAL;
2485
-					break;
2486
-			}
2487
-		}
2488
-		return [
2489
-			'etag' => md5($calendarData),
2490
-			'size' => strlen($calendarData),
2491
-			'componentType' => $componentType,
2492
-			'firstOccurence' => is_null($firstOccurrence) ? null : max(0, $firstOccurrence),
2493
-			'lastOccurence' => $lastOccurrence,
2494
-			'uid' => $uid,
2495
-			'classification' => $classification
2496
-		];
2497
-	}
2498
-
2499
-	/**
2500
-	 * @param $cardData
2501
-	 * @return bool|string
2502
-	 */
2503
-	private function readBlob($cardData) {
2504
-		if (is_resource($cardData)) {
2505
-			return stream_get_contents($cardData);
2506
-		}
2507
-
2508
-		return $cardData;
2509
-	}
2510
-
2511
-	/**
2512
-	 * @param IShareable $shareable
2513
-	 * @param array $add
2514
-	 * @param array $remove
2515
-	 */
2516
-	public function updateShares($shareable, $add, $remove) {
2517
-		$calendarId = $shareable->getResourceId();
2518
-		$calendarRow = $this->getCalendarById($calendarId);
2519
-		$oldShares = $this->getShares($calendarId);
2520
-
2521
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateShares', new GenericEvent(
2522
-			'\OCA\DAV\CalDAV\CalDavBackend::updateShares',
2523
-			[
2524
-				'calendarId' => $calendarId,
2525
-				'calendarData' => $calendarRow,
2526
-				'shares' => $oldShares,
2527
-				'add' => $add,
2528
-				'remove' => $remove,
2529
-			]));
2530
-		$this->calendarSharingBackend->updateShares($shareable, $add, $remove);
2531
-
2532
-		$this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent((int)$calendarId, $calendarRow, $oldShares, $add, $remove));
2533
-	}
2534
-
2535
-	/**
2536
-	 * @param int $resourceId
2537
-	 * @param int $calendarType
2538
-	 * @return array
2539
-	 */
2540
-	public function getShares($resourceId, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2541
-		return $this->calendarSharingBackend->getShares($resourceId);
2542
-	}
2543
-
2544
-	/**
2545
-	 * @param boolean $value
2546
-	 * @param \OCA\DAV\CalDAV\Calendar $calendar
2547
-	 * @return string|null
2548
-	 */
2549
-	public function setPublishStatus($value, $calendar) {
2550
-		$calendarId = $calendar->getResourceId();
2551
-		$calendarData = $this->getCalendarById($calendarId);
2552
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::publishCalendar', new GenericEvent(
2553
-			'\OCA\DAV\CalDAV\CalDavBackend::updateShares',
2554
-			[
2555
-				'calendarId' => $calendarId,
2556
-				'calendarData' => $calendarData,
2557
-				'public' => $value,
2558
-			]));
2559
-
2560
-		$query = $this->db->getQueryBuilder();
2561
-		if ($value) {
2562
-			$publicUri = $this->random->generate(16, ISecureRandom::CHAR_HUMAN_READABLE);
2563
-			$query->insert('dav_shares')
2564
-				->values([
2565
-					'principaluri' => $query->createNamedParameter($calendar->getPrincipalURI()),
2566
-					'type' => $query->createNamedParameter('calendar'),
2567
-					'access' => $query->createNamedParameter(self::ACCESS_PUBLIC),
2568
-					'resourceid' => $query->createNamedParameter($calendar->getResourceId()),
2569
-					'publicuri' => $query->createNamedParameter($publicUri)
2570
-				]);
2571
-			$query->execute();
2572
-
2573
-			$this->dispatcher->dispatchTyped(new CalendarPublishedEvent((int)$calendarId, $calendarData, $publicUri));
2574
-			return $publicUri;
2575
-		}
2576
-		$query->delete('dav_shares')
2577
-			->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
2578
-			->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)));
2579
-		$query->execute();
2580
-
2581
-		$this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent((int)$calendarId, $calendarData));
2582
-		return null;
2583
-	}
2584
-
2585
-	/**
2586
-	 * @param \OCA\DAV\CalDAV\Calendar $calendar
2587
-	 * @return mixed
2588
-	 */
2589
-	public function getPublishStatus($calendar) {
2590
-		$query = $this->db->getQueryBuilder();
2591
-		$result = $query->select('publicuri')
2592
-			->from('dav_shares')
2593
-			->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
2594
-			->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
2595
-			->execute();
2596
-
2597
-		$row = $result->fetch();
2598
-		$result->closeCursor();
2599
-		return $row ? reset($row) : false;
2600
-	}
2601
-
2602
-	/**
2603
-	 * @param int $resourceId
2604
-	 * @param array $acl
2605
-	 * @return array
2606
-	 */
2607
-	public function applyShareAcl($resourceId, $acl) {
2608
-		return $this->calendarSharingBackend->applyShareAcl($resourceId, $acl);
2609
-	}
2610
-
2611
-
2612
-
2613
-	/**
2614
-	 * update properties table
2615
-	 *
2616
-	 * @param int $calendarId
2617
-	 * @param string $objectUri
2618
-	 * @param string $calendarData
2619
-	 * @param int $calendarType
2620
-	 */
2621
-	public function updateProperties($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2622
-		$objectId = $this->getCalendarObjectId($calendarId, $objectUri, $calendarType);
2623
-
2624
-		try {
2625
-			$vCalendar = $this->readCalendarData($calendarData);
2626
-		} catch (\Exception $ex) {
2627
-			return;
2628
-		}
2629
-
2630
-		$this->purgeProperties($calendarId, $objectId);
2631
-
2632
-		$query = $this->db->getQueryBuilder();
2633
-		$query->insert($this->dbObjectPropertiesTable)
2634
-			->values(
2635
-				[
2636
-					'calendarid' => $query->createNamedParameter($calendarId),
2637
-					'calendartype' => $query->createNamedParameter($calendarType),
2638
-					'objectid' => $query->createNamedParameter($objectId),
2639
-					'name' => $query->createParameter('name'),
2640
-					'parameter' => $query->createParameter('parameter'),
2641
-					'value' => $query->createParameter('value'),
2642
-				]
2643
-			);
2644
-
2645
-		$indexComponents = ['VEVENT', 'VJOURNAL', 'VTODO'];
2646
-		foreach ($vCalendar->getComponents() as $component) {
2647
-			if (!in_array($component->name, $indexComponents)) {
2648
-				continue;
2649
-			}
2650
-
2651
-			foreach ($component->children() as $property) {
2652
-				if (in_array($property->name, self::$indexProperties)) {
2653
-					$value = $property->getValue();
2654
-					// is this a shitty db?
2655
-					if (!$this->db->supports4ByteText()) {
2656
-						$value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
2657
-					}
2658
-					$value = mb_substr($value, 0, 254);
2659
-
2660
-					$query->setParameter('name', $property->name);
2661
-					$query->setParameter('parameter', null);
2662
-					$query->setParameter('value', $value);
2663
-					$query->execute();
2664
-				}
2665
-
2666
-				if (array_key_exists($property->name, self::$indexParameters)) {
2667
-					$parameters = $property->parameters();
2668
-					$indexedParametersForProperty = self::$indexParameters[$property->name];
2669
-
2670
-					foreach ($parameters as $key => $value) {
2671
-						if (in_array($key, $indexedParametersForProperty)) {
2672
-							// is this a shitty db?
2673
-							if ($this->db->supports4ByteText()) {
2674
-								$value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
2675
-							}
2676
-
2677
-							$query->setParameter('name', $property->name);
2678
-							$query->setParameter('parameter', mb_substr($key, 0, 254));
2679
-							$query->setParameter('value', mb_substr($value, 0, 254));
2680
-							$query->execute();
2681
-						}
2682
-					}
2683
-				}
2684
-			}
2685
-		}
2686
-	}
2687
-
2688
-	/**
2689
-	 * deletes all birthday calendars
2690
-	 */
2691
-	public function deleteAllBirthdayCalendars() {
2692
-		$query = $this->db->getQueryBuilder();
2693
-		$result = $query->select(['id'])->from('calendars')
2694
-			->where($query->expr()->eq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)))
2695
-			->execute();
2696
-
2697
-		$ids = $result->fetchAll();
2698
-		foreach ($ids as $id) {
2699
-			$this->deleteCalendar($id['id']);
2700
-		}
2701
-	}
2702
-
2703
-	/**
2704
-	 * @param $subscriptionId
2705
-	 */
2706
-	public function purgeAllCachedEventsForSubscription($subscriptionId) {
2707
-		$query = $this->db->getQueryBuilder();
2708
-		$query->select('uri')
2709
-			->from('calendarobjects')
2710
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2711
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
2712
-		$stmt = $query->execute();
2713
-
2714
-		$uris = [];
2715
-		foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
2716
-			$uris[] = $row['uri'];
2717
-		}
2718
-		$stmt->closeCursor();
2719
-
2720
-		$query = $this->db->getQueryBuilder();
2721
-		$query->delete('calendarobjects')
2722
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2723
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2724
-			->execute();
2725
-
2726
-		$query->delete('calendarchanges')
2727
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2728
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2729
-			->execute();
2730
-
2731
-		$query->delete($this->dbObjectPropertiesTable)
2732
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2733
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2734
-			->execute();
2735
-
2736
-		foreach ($uris as $uri) {
2737
-			$this->addChange($subscriptionId, $uri, 3, self::CALENDAR_TYPE_SUBSCRIPTION);
2738
-		}
2739
-	}
2740
-
2741
-	/**
2742
-	 * Move a calendar from one user to another
2743
-	 *
2744
-	 * @param string $uriName
2745
-	 * @param string $uriOrigin
2746
-	 * @param string $uriDestination
2747
-	 * @param string $newUriName (optional) the new uriName
2748
-	 */
2749
-	public function moveCalendar($uriName, $uriOrigin, $uriDestination, $newUriName = null) {
2750
-		$query = $this->db->getQueryBuilder();
2751
-		$query->update('calendars')
2752
-			->set('principaluri', $query->createNamedParameter($uriDestination))
2753
-			->set('uri', $query->createNamedParameter($newUriName ?: $uriName))
2754
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($uriOrigin)))
2755
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($uriName)))
2756
-			->execute();
2757
-	}
2758
-
2759
-	/**
2760
-	 * read VCalendar data into a VCalendar object
2761
-	 *
2762
-	 * @param string $objectData
2763
-	 * @return VCalendar
2764
-	 */
2765
-	protected function readCalendarData($objectData) {
2766
-		return Reader::read($objectData);
2767
-	}
2768
-
2769
-	/**
2770
-	 * delete all properties from a given calendar object
2771
-	 *
2772
-	 * @param int $calendarId
2773
-	 * @param int $objectId
2774
-	 */
2775
-	protected function purgeProperties($calendarId, $objectId) {
2776
-		$query = $this->db->getQueryBuilder();
2777
-		$query->delete($this->dbObjectPropertiesTable)
2778
-			->where($query->expr()->eq('objectid', $query->createNamedParameter($objectId)))
2779
-			->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
2780
-		$query->execute();
2781
-	}
2782
-
2783
-	/**
2784
-	 * get ID from a given calendar object
2785
-	 *
2786
-	 * @param int $calendarId
2787
-	 * @param string $uri
2788
-	 * @param int $calendarType
2789
-	 * @return int
2790
-	 */
2791
-	protected function getCalendarObjectId($calendarId, $uri, $calendarType):int {
2792
-		$query = $this->db->getQueryBuilder();
2793
-		$query->select('id')
2794
-			->from('calendarobjects')
2795
-			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
2796
-			->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
2797
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
2798
-
2799
-		$result = $query->execute();
2800
-		$objectIds = $result->fetch();
2801
-		$result->closeCursor();
2802
-
2803
-		if (!isset($objectIds['id'])) {
2804
-			throw new \InvalidArgumentException('Calendarobject does not exists: ' . $uri);
2805
-		}
2806
-
2807
-		return (int)$objectIds['id'];
2808
-	}
2809
-
2810
-	/**
2811
-	 * return legacy endpoint principal name to new principal name
2812
-	 *
2813
-	 * @param $principalUri
2814
-	 * @param $toV2
2815
-	 * @return string
2816
-	 */
2817
-	private function convertPrincipal($principalUri, $toV2) {
2818
-		if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
2819
-			list(, $name) = Uri\split($principalUri);
2820
-			if ($toV2 === true) {
2821
-				return "principals/users/$name";
2822
-			}
2823
-			return "principals/$name";
2824
-		}
2825
-		return $principalUri;
2826
-	}
2827
-
2828
-	/**
2829
-	 * adds information about an owner to the calendar data
2830
-	 *
2831
-	 * @param $calendarInfo
2832
-	 */
2833
-	private function addOwnerPrincipal(&$calendarInfo) {
2834
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
2835
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
2836
-		if (isset($calendarInfo[$ownerPrincipalKey])) {
2837
-			$uri = $calendarInfo[$ownerPrincipalKey];
2838
-		} else {
2839
-			$uri = $calendarInfo['principaluri'];
2840
-		}
2841
-
2842
-		$principalInformation = $this->principalBackend->getPrincipalByPath($uri);
2843
-		if (isset($principalInformation['{DAV:}displayname'])) {
2844
-			$calendarInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
2845
-		}
2846
-	}
99
+    public const CALENDAR_TYPE_CALENDAR = 0;
100
+    public const CALENDAR_TYPE_SUBSCRIPTION = 1;
101
+
102
+    public const PERSONAL_CALENDAR_URI = 'personal';
103
+    public const PERSONAL_CALENDAR_NAME = 'Personal';
104
+
105
+    public const RESOURCE_BOOKING_CALENDAR_URI = 'calendar';
106
+    public const RESOURCE_BOOKING_CALENDAR_NAME = 'Calendar';
107
+
108
+    /**
109
+     * We need to specify a max date, because we need to stop *somewhere*
110
+     *
111
+     * On 32 bit system the maximum for a signed integer is 2147483647, so
112
+     * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
113
+     * in 2038-01-19 to avoid problems when the date is converted
114
+     * to a unix timestamp.
115
+     */
116
+    public const MAX_DATE = '2038-01-01';
117
+
118
+    public const ACCESS_PUBLIC = 4;
119
+    public const CLASSIFICATION_PUBLIC = 0;
120
+    public const CLASSIFICATION_PRIVATE = 1;
121
+    public const CLASSIFICATION_CONFIDENTIAL = 2;
122
+
123
+    /**
124
+     * List of CalDAV properties, and how they map to database field names
125
+     * Add your own properties by simply adding on to this array.
126
+     *
127
+     * Note that only string-based properties are supported here.
128
+     *
129
+     * @var array
130
+     */
131
+    public $propertyMap = [
132
+        '{DAV:}displayname' => 'displayname',
133
+        '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
134
+        '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone',
135
+        '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
136
+        '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
137
+    ];
138
+
139
+    /**
140
+     * List of subscription properties, and how they map to database field names.
141
+     *
142
+     * @var array
143
+     */
144
+    public $subscriptionPropertyMap = [
145
+        '{DAV:}displayname' => 'displayname',
146
+        '{http://apple.com/ns/ical/}refreshrate' => 'refreshrate',
147
+        '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
148
+        '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
149
+        '{http://calendarserver.org/ns/}subscribed-strip-todos' => 'striptodos',
150
+        '{http://calendarserver.org/ns/}subscribed-strip-alarms' => 'stripalarms',
151
+        '{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
152
+    ];
153
+
154
+    /** @var array properties to index */
155
+    public static $indexProperties = ['CATEGORIES', 'COMMENT', 'DESCRIPTION',
156
+        'LOCATION', 'RESOURCES', 'STATUS', 'SUMMARY', 'ATTENDEE', 'CONTACT',
157
+        'ORGANIZER'];
158
+
159
+    /** @var array parameters to index */
160
+    public static $indexParameters = [
161
+        'ATTENDEE' => ['CN'],
162
+        'ORGANIZER' => ['CN'],
163
+    ];
164
+
165
+    /**
166
+     * @var string[] Map of uid => display name
167
+     */
168
+    protected $userDisplayNames;
169
+
170
+    /** @var IDBConnection */
171
+    private $db;
172
+
173
+    /** @var Backend */
174
+    private $calendarSharingBackend;
175
+
176
+    /** @var Principal */
177
+    private $principalBackend;
178
+
179
+    /** @var IUserManager */
180
+    private $userManager;
181
+
182
+    /** @var ISecureRandom */
183
+    private $random;
184
+
185
+    /** @var ILogger */
186
+    private $logger;
187
+
188
+    /** @var IEventDispatcher */
189
+    private $dispatcher;
190
+
191
+    /** @var EventDispatcherInterface */
192
+    private $legacyDispatcher;
193
+
194
+    /** @var bool */
195
+    private $legacyEndpoint;
196
+
197
+    /** @var string */
198
+    private $dbObjectPropertiesTable = 'calendarobjects_props';
199
+
200
+    /**
201
+     * CalDavBackend constructor.
202
+     *
203
+     * @param IDBConnection $db
204
+     * @param Principal $principalBackend
205
+     * @param IUserManager $userManager
206
+     * @param IGroupManager $groupManager
207
+     * @param ISecureRandom $random
208
+     * @param ILogger $logger
209
+     * @param IEventDispatcher $dispatcher
210
+     * @param EventDispatcherInterface $legacyDispatcher
211
+     * @param bool $legacyEndpoint
212
+     */
213
+    public function __construct(IDBConnection $db,
214
+                                Principal $principalBackend,
215
+                                IUserManager $userManager,
216
+                                IGroupManager $groupManager,
217
+                                ISecureRandom $random,
218
+                                ILogger $logger,
219
+                                IEventDispatcher $dispatcher,
220
+                                EventDispatcherInterface $legacyDispatcher,
221
+                                bool $legacyEndpoint = false) {
222
+        $this->db = $db;
223
+        $this->principalBackend = $principalBackend;
224
+        $this->userManager = $userManager;
225
+        $this->calendarSharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'calendar');
226
+        $this->random = $random;
227
+        $this->logger = $logger;
228
+        $this->dispatcher = $dispatcher;
229
+        $this->legacyDispatcher = $legacyDispatcher;
230
+        $this->legacyEndpoint = $legacyEndpoint;
231
+    }
232
+
233
+    /**
234
+     * Return the number of calendars for a principal
235
+     *
236
+     * By default this excludes the automatically generated birthday calendar
237
+     *
238
+     * @param $principalUri
239
+     * @param bool $excludeBirthday
240
+     * @return int
241
+     */
242
+    public function getCalendarsForUserCount($principalUri, $excludeBirthday = true) {
243
+        $principalUri = $this->convertPrincipal($principalUri, true);
244
+        $query = $this->db->getQueryBuilder();
245
+        $query->select($query->func()->count('*'))
246
+            ->from('calendars');
247
+
248
+        if ($principalUri === '') {
249
+            $query->where($query->expr()->emptyString('principaluri'));
250
+        } else {
251
+            $query->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
252
+        }
253
+
254
+        if ($excludeBirthday) {
255
+            $query->andWhere($query->expr()->neq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)));
256
+        }
257
+
258
+        $result = $query->execute();
259
+        $column = (int)$result->fetchOne();
260
+        $result->closeCursor();
261
+        return $column;
262
+    }
263
+
264
+    /**
265
+     * Returns a list of calendars for a principal.
266
+     *
267
+     * Every project is an array with the following keys:
268
+     *  * id, a unique id that will be used by other functions to modify the
269
+     *    calendar. This can be the same as the uri or a database key.
270
+     *  * uri, which the basename of the uri with which the calendar is
271
+     *    accessed.
272
+     *  * principaluri. The owner of the calendar. Almost always the same as
273
+     *    principalUri passed to this method.
274
+     *
275
+     * Furthermore it can contain webdav properties in clark notation. A very
276
+     * common one is '{DAV:}displayname'.
277
+     *
278
+     * Many clients also require:
279
+     * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
280
+     * For this property, you can just return an instance of
281
+     * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
282
+     *
283
+     * If you return {http://sabredav.org/ns}read-only and set the value to 1,
284
+     * ACL will automatically be put in read-only mode.
285
+     *
286
+     * @param string $principalUri
287
+     * @return array
288
+     */
289
+    public function getCalendarsForUser($principalUri) {
290
+        $principalUriOriginal = $principalUri;
291
+        $principalUri = $this->convertPrincipal($principalUri, true);
292
+        $fields = array_values($this->propertyMap);
293
+        $fields[] = 'id';
294
+        $fields[] = 'uri';
295
+        $fields[] = 'synctoken';
296
+        $fields[] = 'components';
297
+        $fields[] = 'principaluri';
298
+        $fields[] = 'transparent';
299
+
300
+        // Making fields a comma-delimited list
301
+        $query = $this->db->getQueryBuilder();
302
+        $query->select($fields)
303
+            ->from('calendars')
304
+            ->orderBy('calendarorder', 'ASC');
305
+
306
+        if ($principalUri === '') {
307
+            $query->where($query->expr()->emptyString('principaluri'));
308
+        } else {
309
+            $query->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
310
+        }
311
+
312
+        $result = $query->execute();
313
+
314
+        $calendars = [];
315
+        while ($row = $result->fetch()) {
316
+            $row['principaluri'] = (string) $row['principaluri'];
317
+            $components = [];
318
+            if ($row['components']) {
319
+                $components = explode(',',$row['components']);
320
+            }
321
+
322
+            $calendar = [
323
+                'id' => $row['id'],
324
+                'uri' => $row['uri'],
325
+                'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
326
+                '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
327
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
328
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
329
+                '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
330
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
331
+            ];
332
+
333
+            foreach ($this->propertyMap as $xmlName => $dbName) {
334
+                $calendar[$xmlName] = $row[$dbName];
335
+            }
336
+
337
+            $this->addOwnerPrincipal($calendar);
338
+
339
+            if (!isset($calendars[$calendar['id']])) {
340
+                $calendars[$calendar['id']] = $calendar;
341
+            }
342
+        }
343
+        $result->closeCursor();
344
+
345
+        // query for shared calendars
346
+        $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
347
+        $principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
348
+
349
+        $principals[] = $principalUri;
350
+
351
+        $fields = array_values($this->propertyMap);
352
+        $fields[] = 'a.id';
353
+        $fields[] = 'a.uri';
354
+        $fields[] = 'a.synctoken';
355
+        $fields[] = 'a.components';
356
+        $fields[] = 'a.principaluri';
357
+        $fields[] = 'a.transparent';
358
+        $fields[] = 's.access';
359
+        $query = $this->db->getQueryBuilder();
360
+        $query->select($fields)
361
+            ->from('dav_shares', 's')
362
+            ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
363
+            ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
364
+            ->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
365
+            ->setParameter('type', 'calendar')
366
+            ->setParameter('principaluri', $principals, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY);
367
+
368
+        $result	= $query->execute();
369
+
370
+        $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
371
+        while ($row = $result->fetch()) {
372
+            $row['principaluri'] = (string) $row['principaluri'];
373
+            if ($row['principaluri'] === $principalUri) {
374
+                continue;
375
+            }
376
+
377
+            $readOnly = (int) $row['access'] === Backend::ACCESS_READ;
378
+            if (isset($calendars[$row['id']])) {
379
+                if ($readOnly) {
380
+                    // New share can not have more permissions then the old one.
381
+                    continue;
382
+                }
383
+                if (isset($calendars[$row['id']][$readOnlyPropertyName]) &&
384
+                    $calendars[$row['id']][$readOnlyPropertyName] === 0) {
385
+                    // Old share is already read-write, no more permissions can be gained
386
+                    continue;
387
+                }
388
+            }
389
+
390
+            list(, $name) = Uri\split($row['principaluri']);
391
+            $uri = $row['uri'] . '_shared_by_' . $name;
392
+            $row['displayname'] = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
393
+            $components = [];
394
+            if ($row['components']) {
395
+                $components = explode(',',$row['components']);
396
+            }
397
+            $calendar = [
398
+                'id' => $row['id'],
399
+                'uri' => $uri,
400
+                'principaluri' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
401
+                '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
402
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
403
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
404
+                '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp('transparent'),
405
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
406
+                $readOnlyPropertyName => $readOnly,
407
+            ];
408
+
409
+            foreach ($this->propertyMap as $xmlName => $dbName) {
410
+                $calendar[$xmlName] = $row[$dbName];
411
+            }
412
+
413
+            $this->addOwnerPrincipal($calendar);
414
+
415
+            $calendars[$calendar['id']] = $calendar;
416
+        }
417
+        $result->closeCursor();
418
+
419
+        return array_values($calendars);
420
+    }
421
+
422
+    /**
423
+     * @param $principalUri
424
+     * @return array
425
+     */
426
+    public function getUsersOwnCalendars($principalUri) {
427
+        $principalUri = $this->convertPrincipal($principalUri, true);
428
+        $fields = array_values($this->propertyMap);
429
+        $fields[] = 'id';
430
+        $fields[] = 'uri';
431
+        $fields[] = 'synctoken';
432
+        $fields[] = 'components';
433
+        $fields[] = 'principaluri';
434
+        $fields[] = 'transparent';
435
+        // Making fields a comma-delimited list
436
+        $query = $this->db->getQueryBuilder();
437
+        $query->select($fields)->from('calendars')
438
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
439
+            ->orderBy('calendarorder', 'ASC');
440
+        $stmt = $query->execute();
441
+        $calendars = [];
442
+        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
443
+            $row['principaluri'] = (string) $row['principaluri'];
444
+            $components = [];
445
+            if ($row['components']) {
446
+                $components = explode(',',$row['components']);
447
+            }
448
+            $calendar = [
449
+                'id' => $row['id'],
450
+                'uri' => $row['uri'],
451
+                'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
452
+                '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
453
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
454
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
455
+                '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
456
+            ];
457
+            foreach ($this->propertyMap as $xmlName => $dbName) {
458
+                $calendar[$xmlName] = $row[$dbName];
459
+            }
460
+
461
+            $this->addOwnerPrincipal($calendar);
462
+
463
+            if (!isset($calendars[$calendar['id']])) {
464
+                $calendars[$calendar['id']] = $calendar;
465
+            }
466
+        }
467
+        $stmt->closeCursor();
468
+        return array_values($calendars);
469
+    }
470
+
471
+
472
+    /**
473
+     * @param $uid
474
+     * @return string
475
+     */
476
+    private function getUserDisplayName($uid) {
477
+        if (!isset($this->userDisplayNames[$uid])) {
478
+            $user = $this->userManager->get($uid);
479
+
480
+            if ($user instanceof IUser) {
481
+                $this->userDisplayNames[$uid] = $user->getDisplayName();
482
+            } else {
483
+                $this->userDisplayNames[$uid] = $uid;
484
+            }
485
+        }
486
+
487
+        return $this->userDisplayNames[$uid];
488
+    }
489
+
490
+    /**
491
+     * @return array
492
+     */
493
+    public function getPublicCalendars() {
494
+        $fields = array_values($this->propertyMap);
495
+        $fields[] = 'a.id';
496
+        $fields[] = 'a.uri';
497
+        $fields[] = 'a.synctoken';
498
+        $fields[] = 'a.components';
499
+        $fields[] = 'a.principaluri';
500
+        $fields[] = 'a.transparent';
501
+        $fields[] = 's.access';
502
+        $fields[] = 's.publicuri';
503
+        $calendars = [];
504
+        $query = $this->db->getQueryBuilder();
505
+        $result = $query->select($fields)
506
+            ->from('dav_shares', 's')
507
+            ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
508
+            ->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
509
+            ->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
510
+            ->execute();
511
+
512
+        while ($row = $result->fetch()) {
513
+            $row['principaluri'] = (string) $row['principaluri'];
514
+            list(, $name) = Uri\split($row['principaluri']);
515
+            $row['displayname'] = $row['displayname'] . "($name)";
516
+            $components = [];
517
+            if ($row['components']) {
518
+                $components = explode(',',$row['components']);
519
+            }
520
+            $calendar = [
521
+                'id' => $row['id'],
522
+                'uri' => $row['publicuri'],
523
+                'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
524
+                '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
525
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
526
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
527
+                '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
528
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
529
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
530
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
531
+            ];
532
+
533
+            foreach ($this->propertyMap as $xmlName => $dbName) {
534
+                $calendar[$xmlName] = $row[$dbName];
535
+            }
536
+
537
+            $this->addOwnerPrincipal($calendar);
538
+
539
+            if (!isset($calendars[$calendar['id']])) {
540
+                $calendars[$calendar['id']] = $calendar;
541
+            }
542
+        }
543
+        $result->closeCursor();
544
+
545
+        return array_values($calendars);
546
+    }
547
+
548
+    /**
549
+     * @param string $uri
550
+     * @return array
551
+     * @throws NotFound
552
+     */
553
+    public function getPublicCalendar($uri) {
554
+        $fields = array_values($this->propertyMap);
555
+        $fields[] = 'a.id';
556
+        $fields[] = 'a.uri';
557
+        $fields[] = 'a.synctoken';
558
+        $fields[] = 'a.components';
559
+        $fields[] = 'a.principaluri';
560
+        $fields[] = 'a.transparent';
561
+        $fields[] = 's.access';
562
+        $fields[] = 's.publicuri';
563
+        $query = $this->db->getQueryBuilder();
564
+        $result = $query->select($fields)
565
+            ->from('dav_shares', 's')
566
+            ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
567
+            ->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
568
+            ->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
569
+            ->andWhere($query->expr()->eq('s.publicuri', $query->createNamedParameter($uri)))
570
+            ->execute();
571
+
572
+        $row = $result->fetch(\PDO::FETCH_ASSOC);
573
+
574
+        $result->closeCursor();
575
+
576
+        if ($row === false) {
577
+            throw new NotFound('Node with name \'' . $uri . '\' could not be found');
578
+        }
579
+
580
+        $row['principaluri'] = (string) $row['principaluri'];
581
+        list(, $name) = Uri\split($row['principaluri']);
582
+        $row['displayname'] = $row['displayname'] . ' ' . "($name)";
583
+        $components = [];
584
+        if ($row['components']) {
585
+            $components = explode(',',$row['components']);
586
+        }
587
+        $calendar = [
588
+            'id' => $row['id'],
589
+            'uri' => $row['publicuri'],
590
+            'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
591
+            '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
592
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
593
+            '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
594
+            '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
595
+            '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
596
+            '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
597
+            '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
598
+        ];
599
+
600
+        foreach ($this->propertyMap as $xmlName => $dbName) {
601
+            $calendar[$xmlName] = $row[$dbName];
602
+        }
603
+
604
+        $this->addOwnerPrincipal($calendar);
605
+
606
+        return $calendar;
607
+    }
608
+
609
+    /**
610
+     * @param string $principal
611
+     * @param string $uri
612
+     * @return array|null
613
+     */
614
+    public function getCalendarByUri($principal, $uri) {
615
+        $fields = array_values($this->propertyMap);
616
+        $fields[] = 'id';
617
+        $fields[] = 'uri';
618
+        $fields[] = 'synctoken';
619
+        $fields[] = 'components';
620
+        $fields[] = 'principaluri';
621
+        $fields[] = 'transparent';
622
+
623
+        // Making fields a comma-delimited list
624
+        $query = $this->db->getQueryBuilder();
625
+        $query->select($fields)->from('calendars')
626
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
627
+            ->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
628
+            ->setMaxResults(1);
629
+        $stmt = $query->execute();
630
+
631
+        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
632
+        $stmt->closeCursor();
633
+        if ($row === false) {
634
+            return null;
635
+        }
636
+
637
+        $row['principaluri'] = (string) $row['principaluri'];
638
+        $components = [];
639
+        if ($row['components']) {
640
+            $components = explode(',',$row['components']);
641
+        }
642
+
643
+        $calendar = [
644
+            'id' => $row['id'],
645
+            'uri' => $row['uri'],
646
+            'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
647
+            '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
648
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
649
+            '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
650
+            '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
651
+        ];
652
+
653
+        foreach ($this->propertyMap as $xmlName => $dbName) {
654
+            $calendar[$xmlName] = $row[$dbName];
655
+        }
656
+
657
+        $this->addOwnerPrincipal($calendar);
658
+
659
+        return $calendar;
660
+    }
661
+
662
+    /**
663
+     * @param $calendarId
664
+     * @return array|null
665
+     */
666
+    public function getCalendarById($calendarId) {
667
+        $fields = array_values($this->propertyMap);
668
+        $fields[] = 'id';
669
+        $fields[] = 'uri';
670
+        $fields[] = 'synctoken';
671
+        $fields[] = 'components';
672
+        $fields[] = 'principaluri';
673
+        $fields[] = 'transparent';
674
+
675
+        // Making fields a comma-delimited list
676
+        $query = $this->db->getQueryBuilder();
677
+        $query->select($fields)->from('calendars')
678
+            ->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)))
679
+            ->setMaxResults(1);
680
+        $stmt = $query->execute();
681
+
682
+        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
683
+        $stmt->closeCursor();
684
+        if ($row === false) {
685
+            return null;
686
+        }
687
+
688
+        $row['principaluri'] = (string) $row['principaluri'];
689
+        $components = [];
690
+        if ($row['components']) {
691
+            $components = explode(',',$row['components']);
692
+        }
693
+
694
+        $calendar = [
695
+            'id' => $row['id'],
696
+            'uri' => $row['uri'],
697
+            'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
698
+            '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
699
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
700
+            '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
701
+            '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
702
+        ];
703
+
704
+        foreach ($this->propertyMap as $xmlName => $dbName) {
705
+            $calendar[$xmlName] = $row[$dbName];
706
+        }
707
+
708
+        $this->addOwnerPrincipal($calendar);
709
+
710
+        return $calendar;
711
+    }
712
+
713
+    /**
714
+     * @param $subscriptionId
715
+     */
716
+    public function getSubscriptionById($subscriptionId) {
717
+        $fields = array_values($this->subscriptionPropertyMap);
718
+        $fields[] = 'id';
719
+        $fields[] = 'uri';
720
+        $fields[] = 'source';
721
+        $fields[] = 'synctoken';
722
+        $fields[] = 'principaluri';
723
+        $fields[] = 'lastmodified';
724
+
725
+        $query = $this->db->getQueryBuilder();
726
+        $query->select($fields)
727
+            ->from('calendarsubscriptions')
728
+            ->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
729
+            ->orderBy('calendarorder', 'asc');
730
+        $stmt = $query->execute();
731
+
732
+        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
733
+        $stmt->closeCursor();
734
+        if ($row === false) {
735
+            return null;
736
+        }
737
+
738
+        $row['principaluri'] = (string) $row['principaluri'];
739
+        $subscription = [
740
+            'id' => $row['id'],
741
+            'uri' => $row['uri'],
742
+            'principaluri' => $row['principaluri'],
743
+            'source' => $row['source'],
744
+            'lastmodified' => $row['lastmodified'],
745
+            '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
746
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
747
+        ];
748
+
749
+        foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
750
+            if (!is_null($row[$dbName])) {
751
+                $subscription[$xmlName] = $row[$dbName];
752
+            }
753
+        }
754
+
755
+        return $subscription;
756
+    }
757
+
758
+    /**
759
+     * Creates a new calendar for a principal.
760
+     *
761
+     * If the creation was a success, an id must be returned that can be used to reference
762
+     * this calendar in other methods, such as updateCalendar.
763
+     *
764
+     * @param string $principalUri
765
+     * @param string $calendarUri
766
+     * @param array $properties
767
+     * @return int
768
+     */
769
+    public function createCalendar($principalUri, $calendarUri, array $properties) {
770
+        $values = [
771
+            'principaluri' => $this->convertPrincipal($principalUri, true),
772
+            'uri' => $calendarUri,
773
+            'synctoken' => 1,
774
+            'transparent' => 0,
775
+            'components' => 'VEVENT,VTODO',
776
+            'displayname' => $calendarUri
777
+        ];
778
+
779
+        // Default value
780
+        $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
781
+        if (isset($properties[$sccs])) {
782
+            if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) {
783
+                throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
784
+            }
785
+            $values['components'] = implode(',',$properties[$sccs]->getValue());
786
+        } elseif (isset($properties['components'])) {
787
+            // Allow to provide components internally without having
788
+            // to create a SupportedCalendarComponentSet object
789
+            $values['components'] = $properties['components'];
790
+        }
791
+
792
+        $transp = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
793
+        if (isset($properties[$transp])) {
794
+            $values['transparent'] = (int) ($properties[$transp]->getValue() === 'transparent');
795
+        }
796
+
797
+        foreach ($this->propertyMap as $xmlName => $dbName) {
798
+            if (isset($properties[$xmlName])) {
799
+                $values[$dbName] = $properties[$xmlName];
800
+            }
801
+        }
802
+
803
+        $query = $this->db->getQueryBuilder();
804
+        $query->insert('calendars');
805
+        foreach ($values as $column => $value) {
806
+            $query->setValue($column, $query->createNamedParameter($value));
807
+        }
808
+        $query->execute();
809
+        $calendarId = $query->getLastInsertId();
810
+
811
+        $calendarData = $this->getCalendarById($calendarId);
812
+        $this->dispatcher->dispatchTyped(new CalendarCreatedEvent((int)$calendarId, $calendarData));
813
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendar', new GenericEvent(
814
+            '\OCA\DAV\CalDAV\CalDavBackend::createCalendar',
815
+            [
816
+                'calendarId' => $calendarId,
817
+                'calendarData' => $calendarData,
818
+            ]));
819
+
820
+        return $calendarId;
821
+    }
822
+
823
+    /**
824
+     * Updates properties for a calendar.
825
+     *
826
+     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
827
+     * To do the actual updates, you must tell this object which properties
828
+     * you're going to process with the handle() method.
829
+     *
830
+     * Calling the handle method is like telling the PropPatch object "I
831
+     * promise I can handle updating this property".
832
+     *
833
+     * Read the PropPatch documentation for more info and examples.
834
+     *
835
+     * @param mixed $calendarId
836
+     * @param PropPatch $propPatch
837
+     * @return void
838
+     */
839
+    public function updateCalendar($calendarId, PropPatch $propPatch) {
840
+        $supportedProperties = array_keys($this->propertyMap);
841
+        $supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
842
+
843
+        $propPatch->handle($supportedProperties, function ($mutations) use ($calendarId) {
844
+            $newValues = [];
845
+            foreach ($mutations as $propertyName => $propertyValue) {
846
+                switch ($propertyName) {
847
+                    case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp':
848
+                        $fieldName = 'transparent';
849
+                        $newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent');
850
+                        break;
851
+                    default:
852
+                        $fieldName = $this->propertyMap[$propertyName];
853
+                        $newValues[$fieldName] = $propertyValue;
854
+                        break;
855
+                }
856
+            }
857
+            $query = $this->db->getQueryBuilder();
858
+            $query->update('calendars');
859
+            foreach ($newValues as $fieldName => $value) {
860
+                $query->set($fieldName, $query->createNamedParameter($value));
861
+            }
862
+            $query->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
863
+            $query->execute();
864
+
865
+            $this->addChange($calendarId, "", 2);
866
+
867
+            $calendarData = $this->getCalendarById($calendarId);
868
+            $shares = $this->getShares($calendarId);
869
+            $this->dispatcher->dispatchTyped(new CalendarUpdatedEvent((int)$calendarId, $calendarData, $shares, $mutations));
870
+            $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendar', new GenericEvent(
871
+                '\OCA\DAV\CalDAV\CalDavBackend::updateCalendar',
872
+                [
873
+                    'calendarId' => $calendarId,
874
+                    'calendarData' => $calendarData,
875
+                    'shares' => $shares,
876
+                    'propertyMutations' => $mutations,
877
+                ]));
878
+
879
+            return true;
880
+        });
881
+    }
882
+
883
+    /**
884
+     * Delete a calendar and all it's objects
885
+     *
886
+     * @param mixed $calendarId
887
+     * @return void
888
+     */
889
+    public function deleteCalendar($calendarId) {
890
+        $calendarData = $this->getCalendarById($calendarId);
891
+        $shares = $this->getShares($calendarId);
892
+
893
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar', new GenericEvent(
894
+            '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar',
895
+            [
896
+                'calendarId' => $calendarId,
897
+                'calendarData' => $calendarData,
898
+                'shares' => $shares,
899
+            ]));
900
+
901
+        $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `calendartype` = ?');
902
+        $stmt->execute([$calendarId, self::CALENDAR_TYPE_CALENDAR]);
903
+
904
+        $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendars` WHERE `id` = ?');
905
+        $stmt->execute([$calendarId]);
906
+
907
+        $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarchanges` WHERE `calendarid` = ? AND `calendartype` = ?');
908
+        $stmt->execute([$calendarId, self::CALENDAR_TYPE_CALENDAR]);
909
+
910
+        $this->calendarSharingBackend->deleteAllShares($calendarId);
911
+
912
+        $query = $this->db->getQueryBuilder();
913
+        $query->delete($this->dbObjectPropertiesTable)
914
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
915
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)))
916
+            ->execute();
917
+
918
+        if ($calendarData) {
919
+            $this->dispatcher->dispatchTyped(new CalendarDeletedEvent((int)$calendarId, $calendarData, $shares));
920
+        }
921
+    }
922
+
923
+    /**
924
+     * Delete all of an user's shares
925
+     *
926
+     * @param string $principaluri
927
+     * @return void
928
+     */
929
+    public function deleteAllSharesByUser($principaluri) {
930
+        $this->calendarSharingBackend->deleteAllSharesByUser($principaluri);
931
+    }
932
+
933
+    /**
934
+     * Returns all calendar objects within a calendar.
935
+     *
936
+     * Every item contains an array with the following keys:
937
+     *   * calendardata - The iCalendar-compatible calendar data
938
+     *   * uri - a unique key which will be used to construct the uri. This can
939
+     *     be any arbitrary string, but making sure it ends with '.ics' is a
940
+     *     good idea. This is only the basename, or filename, not the full
941
+     *     path.
942
+     *   * lastmodified - a timestamp of the last modification time
943
+     *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
944
+     *   '"abcdef"')
945
+     *   * size - The size of the calendar objects, in bytes.
946
+     *   * component - optional, a string containing the type of object, such
947
+     *     as 'vevent' or 'vtodo'. If specified, this will be used to populate
948
+     *     the Content-Type header.
949
+     *
950
+     * Note that the etag is optional, but it's highly encouraged to return for
951
+     * speed reasons.
952
+     *
953
+     * The calendardata is also optional. If it's not returned
954
+     * 'getCalendarObject' will be called later, which *is* expected to return
955
+     * calendardata.
956
+     *
957
+     * If neither etag or size are specified, the calendardata will be
958
+     * used/fetched to determine these numbers. If both are specified the
959
+     * amount of times this is needed is reduced by a great degree.
960
+     *
961
+     * @param mixed $calendarId
962
+     * @param int $calendarType
963
+     * @return array
964
+     */
965
+    public function getCalendarObjects($calendarId, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
966
+        $query = $this->db->getQueryBuilder();
967
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'componenttype', 'classification'])
968
+            ->from('calendarobjects')
969
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
970
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
971
+        $stmt = $query->execute();
972
+
973
+        $result = [];
974
+        foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
975
+            $result[] = [
976
+                'id' => $row['id'],
977
+                'uri' => $row['uri'],
978
+                'lastmodified' => $row['lastmodified'],
979
+                'etag' => '"' . $row['etag'] . '"',
980
+                'calendarid' => $row['calendarid'],
981
+                'size' => (int)$row['size'],
982
+                'component' => strtolower($row['componenttype']),
983
+                'classification' => (int)$row['classification']
984
+            ];
985
+        }
986
+        $stmt->closeCursor();
987
+
988
+        return $result;
989
+    }
990
+
991
+    /**
992
+     * Returns information from a single calendar object, based on it's object
993
+     * uri.
994
+     *
995
+     * The object uri is only the basename, or filename and not a full path.
996
+     *
997
+     * The returned array must have the same keys as getCalendarObjects. The
998
+     * 'calendardata' object is required here though, while it's not required
999
+     * for getCalendarObjects.
1000
+     *
1001
+     * This method must return null if the object did not exist.
1002
+     *
1003
+     * @param mixed $calendarId
1004
+     * @param string $objectUri
1005
+     * @param int $calendarType
1006
+     * @return array|null
1007
+     */
1008
+    public function getCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1009
+        $query = $this->db->getQueryBuilder();
1010
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
1011
+            ->from('calendarobjects')
1012
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1013
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1014
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
1015
+        $stmt = $query->execute();
1016
+        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
1017
+        $stmt->closeCursor();
1018
+
1019
+        if (!$row) {
1020
+            return null;
1021
+        }
1022
+
1023
+        return [
1024
+            'id' => $row['id'],
1025
+            'uri' => $row['uri'],
1026
+            'lastmodified' => $row['lastmodified'],
1027
+            'etag' => '"' . $row['etag'] . '"',
1028
+            'calendarid' => $row['calendarid'],
1029
+            'size' => (int)$row['size'],
1030
+            'calendardata' => $this->readBlob($row['calendardata']),
1031
+            'component' => strtolower($row['componenttype']),
1032
+            'classification' => (int)$row['classification']
1033
+        ];
1034
+    }
1035
+
1036
+    /**
1037
+     * Returns a list of calendar objects.
1038
+     *
1039
+     * This method should work identical to getCalendarObject, but instead
1040
+     * return all the calendar objects in the list as an array.
1041
+     *
1042
+     * If the backend supports this, it may allow for some speed-ups.
1043
+     *
1044
+     * @param mixed $calendarId
1045
+     * @param string[] $uris
1046
+     * @param int $calendarType
1047
+     * @return array
1048
+     */
1049
+    public function getMultipleCalendarObjects($calendarId, array $uris, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
1050
+        if (empty($uris)) {
1051
+            return [];
1052
+        }
1053
+
1054
+        $chunks = array_chunk($uris, 100);
1055
+        $objects = [];
1056
+
1057
+        $query = $this->db->getQueryBuilder();
1058
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
1059
+            ->from('calendarobjects')
1060
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1061
+            ->andWhere($query->expr()->in('uri', $query->createParameter('uri')))
1062
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
1063
+
1064
+        foreach ($chunks as $uris) {
1065
+            $query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
1066
+            $result = $query->execute();
1067
+
1068
+            while ($row = $result->fetch()) {
1069
+                $objects[] = [
1070
+                    'id' => $row['id'],
1071
+                    'uri' => $row['uri'],
1072
+                    'lastmodified' => $row['lastmodified'],
1073
+                    'etag' => '"' . $row['etag'] . '"',
1074
+                    'calendarid' => $row['calendarid'],
1075
+                    'size' => (int)$row['size'],
1076
+                    'calendardata' => $this->readBlob($row['calendardata']),
1077
+                    'component' => strtolower($row['componenttype']),
1078
+                    'classification' => (int)$row['classification']
1079
+                ];
1080
+            }
1081
+            $result->closeCursor();
1082
+        }
1083
+
1084
+        return $objects;
1085
+    }
1086
+
1087
+    /**
1088
+     * Creates a new calendar object.
1089
+     *
1090
+     * The object uri is only the basename, or filename and not a full path.
1091
+     *
1092
+     * It is possible return an etag from this function, which will be used in
1093
+     * the response to this PUT request. Note that the ETag must be surrounded
1094
+     * by double-quotes.
1095
+     *
1096
+     * However, you should only really return this ETag if you don't mangle the
1097
+     * calendar-data. If the result of a subsequent GET to this object is not
1098
+     * the exact same as this request body, you should omit the ETag.
1099
+     *
1100
+     * @param mixed $calendarId
1101
+     * @param string $objectUri
1102
+     * @param string $calendarData
1103
+     * @param int $calendarType
1104
+     * @return string
1105
+     */
1106
+    public function createCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1107
+        $extraData = $this->getDenormalizedData($calendarData);
1108
+
1109
+        $q = $this->db->getQueryBuilder();
1110
+        $q->select($q->func()->count('*'))
1111
+            ->from('calendarobjects')
1112
+            ->where($q->expr()->eq('calendarid', $q->createNamedParameter($calendarId)))
1113
+            ->andWhere($q->expr()->eq('uid', $q->createNamedParameter($extraData['uid'])))
1114
+            ->andWhere($q->expr()->eq('calendartype', $q->createNamedParameter($calendarType)));
1115
+
1116
+        $result = $q->execute();
1117
+        $count = (int) $result->fetchOne();
1118
+        $result->closeCursor();
1119
+
1120
+        if ($count !== 0) {
1121
+            throw new \Sabre\DAV\Exception\BadRequest('Calendar object with uid already exists in this calendar collection.');
1122
+        }
1123
+
1124
+        $query = $this->db->getQueryBuilder();
1125
+        $query->insert('calendarobjects')
1126
+            ->values([
1127
+                'calendarid' => $query->createNamedParameter($calendarId),
1128
+                'uri' => $query->createNamedParameter($objectUri),
1129
+                'calendardata' => $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB),
1130
+                'lastmodified' => $query->createNamedParameter(time()),
1131
+                'etag' => $query->createNamedParameter($extraData['etag']),
1132
+                'size' => $query->createNamedParameter($extraData['size']),
1133
+                'componenttype' => $query->createNamedParameter($extraData['componentType']),
1134
+                'firstoccurence' => $query->createNamedParameter($extraData['firstOccurence']),
1135
+                'lastoccurence' => $query->createNamedParameter($extraData['lastOccurence']),
1136
+                'classification' => $query->createNamedParameter($extraData['classification']),
1137
+                'uid' => $query->createNamedParameter($extraData['uid']),
1138
+                'calendartype' => $query->createNamedParameter($calendarType),
1139
+            ])
1140
+            ->execute();
1141
+
1142
+        $this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType);
1143
+        $this->addChange($calendarId, $objectUri, 1, $calendarType);
1144
+
1145
+        $objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1146
+        if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1147
+            $calendarRow = $this->getCalendarById($calendarId);
1148
+            $shares = $this->getShares($calendarId);
1149
+
1150
+            $this->dispatcher->dispatchTyped(new CalendarObjectCreatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
1151
+            $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', new GenericEvent(
1152
+                '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject',
1153
+                [
1154
+                    'calendarId' => $calendarId,
1155
+                    'calendarData' => $calendarRow,
1156
+                    'shares' => $shares,
1157
+                    'objectData' => $objectRow,
1158
+                ]
1159
+            ));
1160
+        } else {
1161
+            $subscriptionRow = $this->getSubscriptionById($calendarId);
1162
+
1163
+            $this->dispatcher->dispatchTyped(new CachedCalendarObjectCreatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
1164
+            $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject', new GenericEvent(
1165
+                '\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject',
1166
+                [
1167
+                    'subscriptionId' => $calendarId,
1168
+                    'calendarData' => $subscriptionRow,
1169
+                    'shares' => [],
1170
+                    'objectData' => $objectRow,
1171
+                ]
1172
+            ));
1173
+        }
1174
+
1175
+        return '"' . $extraData['etag'] . '"';
1176
+    }
1177
+
1178
+    /**
1179
+     * Updates an existing calendarobject, based on it's uri.
1180
+     *
1181
+     * The object uri is only the basename, or filename and not a full path.
1182
+     *
1183
+     * It is possible return an etag from this function, which will be used in
1184
+     * the response to this PUT request. Note that the ETag must be surrounded
1185
+     * by double-quotes.
1186
+     *
1187
+     * However, you should only really return this ETag if you don't mangle the
1188
+     * calendar-data. If the result of a subsequent GET to this object is not
1189
+     * the exact same as this request body, you should omit the ETag.
1190
+     *
1191
+     * @param mixed $calendarId
1192
+     * @param string $objectUri
1193
+     * @param string $calendarData
1194
+     * @param int $calendarType
1195
+     * @return string
1196
+     */
1197
+    public function updateCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1198
+        $extraData = $this->getDenormalizedData($calendarData);
1199
+        $query = $this->db->getQueryBuilder();
1200
+        $query->update('calendarobjects')
1201
+                ->set('calendardata', $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB))
1202
+                ->set('lastmodified', $query->createNamedParameter(time()))
1203
+                ->set('etag', $query->createNamedParameter($extraData['etag']))
1204
+                ->set('size', $query->createNamedParameter($extraData['size']))
1205
+                ->set('componenttype', $query->createNamedParameter($extraData['componentType']))
1206
+                ->set('firstoccurence', $query->createNamedParameter($extraData['firstOccurence']))
1207
+                ->set('lastoccurence', $query->createNamedParameter($extraData['lastOccurence']))
1208
+                ->set('classification', $query->createNamedParameter($extraData['classification']))
1209
+                ->set('uid', $query->createNamedParameter($extraData['uid']))
1210
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1211
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1212
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)))
1213
+            ->execute();
1214
+
1215
+        $this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType);
1216
+        $this->addChange($calendarId, $objectUri, 2, $calendarType);
1217
+
1218
+        $objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1219
+        if (is_array($objectRow)) {
1220
+            if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1221
+                $calendarRow = $this->getCalendarById($calendarId);
1222
+                $shares = $this->getShares($calendarId);
1223
+
1224
+                $this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
1225
+                $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', new GenericEvent(
1226
+                    '\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject',
1227
+                    [
1228
+                        'calendarId' => $calendarId,
1229
+                        'calendarData' => $calendarRow,
1230
+                        'shares' => $shares,
1231
+                        'objectData' => $objectRow,
1232
+                    ]
1233
+                ));
1234
+            } else {
1235
+                $subscriptionRow = $this->getSubscriptionById($calendarId);
1236
+
1237
+                $this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
1238
+                $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject', new GenericEvent(
1239
+                    '\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject',
1240
+                    [
1241
+                        'subscriptionId' => $calendarId,
1242
+                        'calendarData' => $subscriptionRow,
1243
+                        'shares' => [],
1244
+                        'objectData' => $objectRow,
1245
+                    ]
1246
+                ));
1247
+            }
1248
+        }
1249
+
1250
+        return '"' . $extraData['etag'] . '"';
1251
+    }
1252
+
1253
+    /**
1254
+     * @param int $calendarObjectId
1255
+     * @param int $classification
1256
+     */
1257
+    public function setClassification($calendarObjectId, $classification) {
1258
+        if (!in_array($classification, [
1259
+            self::CLASSIFICATION_PUBLIC, self::CLASSIFICATION_PRIVATE, self::CLASSIFICATION_CONFIDENTIAL
1260
+        ])) {
1261
+            throw new \InvalidArgumentException();
1262
+        }
1263
+        $query = $this->db->getQueryBuilder();
1264
+        $query->update('calendarobjects')
1265
+            ->set('classification', $query->createNamedParameter($classification))
1266
+            ->where($query->expr()->eq('id', $query->createNamedParameter($calendarObjectId)))
1267
+            ->execute();
1268
+    }
1269
+
1270
+    /**
1271
+     * Deletes an existing calendar object.
1272
+     *
1273
+     * The object uri is only the basename, or filename and not a full path.
1274
+     *
1275
+     * @param mixed $calendarId
1276
+     * @param string $objectUri
1277
+     * @param int $calendarType
1278
+     * @return void
1279
+     */
1280
+    public function deleteCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1281
+        $data = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1282
+        if (is_array($data)) {
1283
+            if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1284
+                $calendarRow = $this->getCalendarById($calendarId);
1285
+                $shares = $this->getShares($calendarId);
1286
+
1287
+                $this->dispatcher->dispatchTyped(new CalendarObjectDeletedEvent((int)$calendarId, $calendarRow, $shares, $data));
1288
+                $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', new GenericEvent(
1289
+                    '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject',
1290
+                    [
1291
+                        'calendarId' => $calendarId,
1292
+                        'calendarData' => $calendarRow,
1293
+                        'shares' => $shares,
1294
+                        'objectData' => $data,
1295
+                    ]
1296
+                ));
1297
+            } else {
1298
+                $subscriptionRow = $this->getSubscriptionById($calendarId);
1299
+
1300
+                $this->dispatcher->dispatchTyped(new CachedCalendarObjectDeletedEvent((int)$calendarId, $subscriptionRow, [], $data));
1301
+                $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject', new GenericEvent(
1302
+                    '\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject',
1303
+                    [
1304
+                        'subscriptionId' => $calendarId,
1305
+                        'calendarData' => $subscriptionRow,
1306
+                        'shares' => [],
1307
+                        'objectData' => $data,
1308
+                    ]
1309
+                ));
1310
+            }
1311
+        }
1312
+
1313
+        $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ? AND `calendartype` = ?');
1314
+        $stmt->execute([$calendarId, $objectUri, $calendarType]);
1315
+
1316
+        if (is_array($data)) {
1317
+            $this->purgeProperties($calendarId, $data['id'], $calendarType);
1318
+        }
1319
+
1320
+        $this->addChange($calendarId, $objectUri, 3, $calendarType);
1321
+    }
1322
+
1323
+    /**
1324
+     * Performs a calendar-query on the contents of this calendar.
1325
+     *
1326
+     * The calendar-query is defined in RFC4791 : CalDAV. Using the
1327
+     * calendar-query it is possible for a client to request a specific set of
1328
+     * object, based on contents of iCalendar properties, date-ranges and
1329
+     * iCalendar component types (VTODO, VEVENT).
1330
+     *
1331
+     * This method should just return a list of (relative) urls that match this
1332
+     * query.
1333
+     *
1334
+     * The list of filters are specified as an array. The exact array is
1335
+     * documented by Sabre\CalDAV\CalendarQueryParser.
1336
+     *
1337
+     * Note that it is extremely likely that getCalendarObject for every path
1338
+     * returned from this method will be called almost immediately after. You
1339
+     * may want to anticipate this to speed up these requests.
1340
+     *
1341
+     * This method provides a default implementation, which parses *all* the
1342
+     * iCalendar objects in the specified calendar.
1343
+     *
1344
+     * This default may well be good enough for personal use, and calendars
1345
+     * that aren't very large. But if you anticipate high usage, big calendars
1346
+     * or high loads, you are strongly advised to optimize certain paths.
1347
+     *
1348
+     * The best way to do so is override this method and to optimize
1349
+     * specifically for 'common filters'.
1350
+     *
1351
+     * Requests that are extremely common are:
1352
+     *   * requests for just VEVENTS
1353
+     *   * requests for just VTODO
1354
+     *   * requests with a time-range-filter on either VEVENT or VTODO.
1355
+     *
1356
+     * ..and combinations of these requests. It may not be worth it to try to
1357
+     * handle every possible situation and just rely on the (relatively
1358
+     * easy to use) CalendarQueryValidator to handle the rest.
1359
+     *
1360
+     * Note that especially time-range-filters may be difficult to parse. A
1361
+     * time-range filter specified on a VEVENT must for instance also handle
1362
+     * recurrence rules correctly.
1363
+     * A good example of how to interprete all these filters can also simply
1364
+     * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
1365
+     * as possible, so it gives you a good idea on what type of stuff you need
1366
+     * to think of.
1367
+     *
1368
+     * @param mixed $calendarId
1369
+     * @param array $filters
1370
+     * @param int $calendarType
1371
+     * @return array
1372
+     */
1373
+    public function calendarQuery($calendarId, array $filters, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
1374
+        $componentType = null;
1375
+        $requirePostFilter = true;
1376
+        $timeRange = null;
1377
+
1378
+        // if no filters were specified, we don't need to filter after a query
1379
+        if (!$filters['prop-filters'] && !$filters['comp-filters']) {
1380
+            $requirePostFilter = false;
1381
+        }
1382
+
1383
+        // Figuring out if there's a component filter
1384
+        if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
1385
+            $componentType = $filters['comp-filters'][0]['name'];
1386
+
1387
+            // Checking if we need post-filters
1388
+            if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
1389
+                $requirePostFilter = false;
1390
+            }
1391
+            // There was a time-range filter
1392
+            if ($componentType === 'VEVENT' && isset($filters['comp-filters'][0]['time-range']) && is_array($filters['comp-filters'][0]['time-range'])) {
1393
+                $timeRange = $filters['comp-filters'][0]['time-range'];
1394
+
1395
+                // If start time OR the end time is not specified, we can do a
1396
+                // 100% accurate mysql query.
1397
+                if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
1398
+                    $requirePostFilter = false;
1399
+                }
1400
+            }
1401
+        }
1402
+        $columns = ['uri'];
1403
+        if ($requirePostFilter) {
1404
+            $columns = ['uri', 'calendardata'];
1405
+        }
1406
+        $query = $this->db->getQueryBuilder();
1407
+        $query->select($columns)
1408
+            ->from('calendarobjects')
1409
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1410
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
1411
+
1412
+        if ($componentType) {
1413
+            $query->andWhere($query->expr()->eq('componenttype', $query->createNamedParameter($componentType)));
1414
+        }
1415
+
1416
+        if ($timeRange && $timeRange['start']) {
1417
+            $query->andWhere($query->expr()->gt('lastoccurence', $query->createNamedParameter($timeRange['start']->getTimeStamp())));
1418
+        }
1419
+        if ($timeRange && $timeRange['end']) {
1420
+            $query->andWhere($query->expr()->lt('firstoccurence', $query->createNamedParameter($timeRange['end']->getTimeStamp())));
1421
+        }
1422
+
1423
+        $stmt = $query->execute();
1424
+
1425
+        $result = [];
1426
+        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1427
+            if ($requirePostFilter) {
1428
+                // validateFilterForObject will parse the calendar data
1429
+                // catch parsing errors
1430
+                try {
1431
+                    $matches = $this->validateFilterForObject($row, $filters);
1432
+                } catch (ParseException $ex) {
1433
+                    $this->logger->logException($ex, [
1434
+                        'app' => 'dav',
1435
+                        'message' => 'Caught parsing exception for calendar data. This usually indicates invalid calendar data. calendar-id:'.$calendarId.' uri:'.$row['uri']
1436
+                    ]);
1437
+                    continue;
1438
+                } catch (InvalidDataException $ex) {
1439
+                    $this->logger->logException($ex, [
1440
+                        'app' => 'dav',
1441
+                        'message' => 'Caught invalid data exception for calendar data. This usually indicates invalid calendar data. calendar-id:'.$calendarId.' uri:'.$row['uri']
1442
+                    ]);
1443
+                    continue;
1444
+                }
1445
+
1446
+                if (!$matches) {
1447
+                    continue;
1448
+                }
1449
+            }
1450
+            $result[] = $row['uri'];
1451
+        }
1452
+
1453
+        return $result;
1454
+    }
1455
+
1456
+    /**
1457
+     * custom Nextcloud search extension for CalDAV
1458
+     *
1459
+     * TODO - this should optionally cover cached calendar objects as well
1460
+     *
1461
+     * @param string $principalUri
1462
+     * @param array $filters
1463
+     * @param integer|null $limit
1464
+     * @param integer|null $offset
1465
+     * @return array
1466
+     */
1467
+    public function calendarSearch($principalUri, array $filters, $limit = null, $offset = null) {
1468
+        $calendars = $this->getCalendarsForUser($principalUri);
1469
+        $ownCalendars = [];
1470
+        $sharedCalendars = [];
1471
+
1472
+        $uriMapper = [];
1473
+
1474
+        foreach ($calendars as $calendar) {
1475
+            if ($calendar['{http://owncloud.org/ns}owner-principal'] === $principalUri) {
1476
+                $ownCalendars[] = $calendar['id'];
1477
+            } else {
1478
+                $sharedCalendars[] = $calendar['id'];
1479
+            }
1480
+            $uriMapper[$calendar['id']] = $calendar['uri'];
1481
+        }
1482
+        if (count($ownCalendars) === 0 && count($sharedCalendars) === 0) {
1483
+            return [];
1484
+        }
1485
+
1486
+        $query = $this->db->getQueryBuilder();
1487
+        // Calendar id expressions
1488
+        $calendarExpressions = [];
1489
+        foreach ($ownCalendars as $id) {
1490
+            $calendarExpressions[] = $query->expr()->andX(
1491
+                $query->expr()->eq('c.calendarid',
1492
+                    $query->createNamedParameter($id)),
1493
+                $query->expr()->eq('c.calendartype',
1494
+                        $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1495
+        }
1496
+        foreach ($sharedCalendars as $id) {
1497
+            $calendarExpressions[] = $query->expr()->andX(
1498
+                $query->expr()->eq('c.calendarid',
1499
+                    $query->createNamedParameter($id)),
1500
+                $query->expr()->eq('c.classification',
1501
+                    $query->createNamedParameter(self::CLASSIFICATION_PUBLIC)),
1502
+                $query->expr()->eq('c.calendartype',
1503
+                    $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1504
+        }
1505
+
1506
+        if (count($calendarExpressions) === 1) {
1507
+            $calExpr = $calendarExpressions[0];
1508
+        } else {
1509
+            $calExpr = call_user_func_array([$query->expr(), 'orX'], $calendarExpressions);
1510
+        }
1511
+
1512
+        // Component expressions
1513
+        $compExpressions = [];
1514
+        foreach ($filters['comps'] as $comp) {
1515
+            $compExpressions[] = $query->expr()
1516
+                ->eq('c.componenttype', $query->createNamedParameter($comp));
1517
+        }
1518
+
1519
+        if (count($compExpressions) === 1) {
1520
+            $compExpr = $compExpressions[0];
1521
+        } else {
1522
+            $compExpr = call_user_func_array([$query->expr(), 'orX'], $compExpressions);
1523
+        }
1524
+
1525
+        if (!isset($filters['props'])) {
1526
+            $filters['props'] = [];
1527
+        }
1528
+        if (!isset($filters['params'])) {
1529
+            $filters['params'] = [];
1530
+        }
1531
+
1532
+        $propParamExpressions = [];
1533
+        foreach ($filters['props'] as $prop) {
1534
+            $propParamExpressions[] = $query->expr()->andX(
1535
+                $query->expr()->eq('i.name', $query->createNamedParameter($prop)),
1536
+                $query->expr()->isNull('i.parameter')
1537
+            );
1538
+        }
1539
+        foreach ($filters['params'] as $param) {
1540
+            $propParamExpressions[] = $query->expr()->andX(
1541
+                $query->expr()->eq('i.name', $query->createNamedParameter($param['property'])),
1542
+                $query->expr()->eq('i.parameter', $query->createNamedParameter($param['parameter']))
1543
+            );
1544
+        }
1545
+
1546
+        if (count($propParamExpressions) === 1) {
1547
+            $propParamExpr = $propParamExpressions[0];
1548
+        } else {
1549
+            $propParamExpr = call_user_func_array([$query->expr(), 'orX'], $propParamExpressions);
1550
+        }
1551
+
1552
+        $query->select(['c.calendarid', 'c.uri'])
1553
+            ->from($this->dbObjectPropertiesTable, 'i')
1554
+            ->join('i', 'calendarobjects', 'c', $query->expr()->eq('i.objectid', 'c.id'))
1555
+            ->where($calExpr)
1556
+            ->andWhere($compExpr)
1557
+            ->andWhere($propParamExpr)
1558
+            ->andWhere($query->expr()->iLike('i.value',
1559
+                $query->createNamedParameter('%'.$this->db->escapeLikeParameter($filters['search-term']).'%')));
1560
+
1561
+        if ($offset) {
1562
+            $query->setFirstResult($offset);
1563
+        }
1564
+        if ($limit) {
1565
+            $query->setMaxResults($limit);
1566
+        }
1567
+
1568
+        $stmt = $query->execute();
1569
+
1570
+        $result = [];
1571
+        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1572
+            $path = $uriMapper[$row['calendarid']] . '/' . $row['uri'];
1573
+            if (!in_array($path, $result)) {
1574
+                $result[] = $path;
1575
+            }
1576
+        }
1577
+
1578
+        return $result;
1579
+    }
1580
+
1581
+    /**
1582
+     * used for Nextcloud's calendar API
1583
+     *
1584
+     * @param array $calendarInfo
1585
+     * @param string $pattern
1586
+     * @param array $searchProperties
1587
+     * @param array $options
1588
+     * @param integer|null $limit
1589
+     * @param integer|null $offset
1590
+     *
1591
+     * @return array
1592
+     */
1593
+    public function search(array $calendarInfo, $pattern, array $searchProperties,
1594
+                            array $options, $limit, $offset) {
1595
+        $outerQuery = $this->db->getQueryBuilder();
1596
+        $innerQuery = $this->db->getQueryBuilder();
1597
+
1598
+        $innerQuery->selectDistinct('op.objectid')
1599
+            ->from($this->dbObjectPropertiesTable, 'op')
1600
+            ->andWhere($innerQuery->expr()->eq('op.calendarid',
1601
+                $outerQuery->createNamedParameter($calendarInfo['id'])))
1602
+            ->andWhere($innerQuery->expr()->eq('op.calendartype',
1603
+                $outerQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1604
+
1605
+        // only return public items for shared calendars for now
1606
+        if ($calendarInfo['principaluri'] !== $calendarInfo['{http://owncloud.org/ns}owner-principal']) {
1607
+            $innerQuery->andWhere($innerQuery->expr()->eq('c.classification',
1608
+                $outerQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1609
+        }
1610
+
1611
+        $or = $innerQuery->expr()->orX();
1612
+        foreach ($searchProperties as $searchProperty) {
1613
+            $or->add($innerQuery->expr()->eq('op.name',
1614
+                $outerQuery->createNamedParameter($searchProperty)));
1615
+        }
1616
+        $innerQuery->andWhere($or);
1617
+
1618
+        if ($pattern !== '') {
1619
+            $innerQuery->andWhere($innerQuery->expr()->iLike('op.value',
1620
+                $outerQuery->createNamedParameter('%' .
1621
+                    $this->db->escapeLikeParameter($pattern) . '%')));
1622
+        }
1623
+
1624
+        $outerQuery->select('c.id', 'c.calendardata', 'c.componenttype', 'c.uid', 'c.uri')
1625
+            ->from('calendarobjects', 'c');
1626
+
1627
+        if (isset($options['timerange'])) {
1628
+            if (isset($options['timerange']['start']) && $options['timerange']['start'] instanceof DateTime) {
1629
+                $outerQuery->andWhere($outerQuery->expr()->gt('lastoccurence',
1630
+                    $outerQuery->createNamedParameter($options['timerange']['start']->getTimeStamp())));
1631
+            }
1632
+            if (isset($options['timerange']['end']) && $options['timerange']['end'] instanceof DateTime) {
1633
+                $outerQuery->andWhere($outerQuery->expr()->lt('firstoccurence',
1634
+                    $outerQuery->createNamedParameter($options['timerange']['end']->getTimeStamp())));
1635
+            }
1636
+        }
1637
+
1638
+        if (isset($options['types'])) {
1639
+            $or = $outerQuery->expr()->orX();
1640
+            foreach ($options['types'] as $type) {
1641
+                $or->add($outerQuery->expr()->eq('componenttype',
1642
+                    $outerQuery->createNamedParameter($type)));
1643
+            }
1644
+            $outerQuery->andWhere($or);
1645
+        }
1646
+
1647
+        $outerQuery->andWhere($outerQuery->expr()->in('c.id',
1648
+            $outerQuery->createFunction($innerQuery->getSQL())));
1649
+
1650
+        if ($offset) {
1651
+            $outerQuery->setFirstResult($offset);
1652
+        }
1653
+        if ($limit) {
1654
+            $outerQuery->setMaxResults($limit);
1655
+        }
1656
+
1657
+        $result = $outerQuery->execute();
1658
+        $calendarObjects = $result->fetchAll();
1659
+
1660
+        return array_map(function ($o) {
1661
+            $calendarData = Reader::read($o['calendardata']);
1662
+            $comps = $calendarData->getComponents();
1663
+            $objects = [];
1664
+            $timezones = [];
1665
+            foreach ($comps as $comp) {
1666
+                if ($comp instanceof VTimeZone) {
1667
+                    $timezones[] = $comp;
1668
+                } else {
1669
+                    $objects[] = $comp;
1670
+                }
1671
+            }
1672
+
1673
+            return [
1674
+                'id' => $o['id'],
1675
+                'type' => $o['componenttype'],
1676
+                'uid' => $o['uid'],
1677
+                'uri' => $o['uri'],
1678
+                'objects' => array_map(function ($c) {
1679
+                    return $this->transformSearchData($c);
1680
+                }, $objects),
1681
+                'timezones' => array_map(function ($c) {
1682
+                    return $this->transformSearchData($c);
1683
+                }, $timezones),
1684
+            ];
1685
+        }, $calendarObjects);
1686
+    }
1687
+
1688
+    /**
1689
+     * @param Component $comp
1690
+     * @return array
1691
+     */
1692
+    private function transformSearchData(Component $comp) {
1693
+        $data = [];
1694
+        /** @var Component[] $subComponents */
1695
+        $subComponents = $comp->getComponents();
1696
+        /** @var Property[] $properties */
1697
+        $properties = array_filter($comp->children(), function ($c) {
1698
+            return $c instanceof Property;
1699
+        });
1700
+        $validationRules = $comp->getValidationRules();
1701
+
1702
+        foreach ($subComponents as $subComponent) {
1703
+            $name = $subComponent->name;
1704
+            if (!isset($data[$name])) {
1705
+                $data[$name] = [];
1706
+            }
1707
+            $data[$name][] = $this->transformSearchData($subComponent);
1708
+        }
1709
+
1710
+        foreach ($properties as $property) {
1711
+            $name = $property->name;
1712
+            if (!isset($validationRules[$name])) {
1713
+                $validationRules[$name] = '*';
1714
+            }
1715
+
1716
+            $rule = $validationRules[$property->name];
1717
+            if ($rule === '+' || $rule === '*') { // multiple
1718
+                if (!isset($data[$name])) {
1719
+                    $data[$name] = [];
1720
+                }
1721
+
1722
+                $data[$name][] = $this->transformSearchProperty($property);
1723
+            } else { // once
1724
+                $data[$name] = $this->transformSearchProperty($property);
1725
+            }
1726
+        }
1727
+
1728
+        return $data;
1729
+    }
1730
+
1731
+    /**
1732
+     * @param Property $prop
1733
+     * @return array
1734
+     */
1735
+    private function transformSearchProperty(Property $prop) {
1736
+        // No need to check Date, as it extends DateTime
1737
+        if ($prop instanceof Property\ICalendar\DateTime) {
1738
+            $value = $prop->getDateTime();
1739
+        } else {
1740
+            $value = $prop->getValue();
1741
+        }
1742
+
1743
+        return [
1744
+            $value,
1745
+            $prop->parameters()
1746
+        ];
1747
+    }
1748
+
1749
+    /**
1750
+     * @param string $principalUri
1751
+     * @param string $pattern
1752
+     * @param array $componentTypes
1753
+     * @param array $searchProperties
1754
+     * @param array $searchParameters
1755
+     * @param array $options
1756
+     * @return array
1757
+     */
1758
+    public function searchPrincipalUri(string $principalUri,
1759
+                                        string $pattern,
1760
+                                        array $componentTypes,
1761
+                                        array $searchProperties,
1762
+                                        array $searchParameters,
1763
+                                        array $options = []): array {
1764
+        $escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false;
1765
+
1766
+        $calendarObjectIdQuery = $this->db->getQueryBuilder();
1767
+        $calendarOr = $calendarObjectIdQuery->expr()->orX();
1768
+        $searchOr = $calendarObjectIdQuery->expr()->orX();
1769
+
1770
+        // Fetch calendars and subscription
1771
+        $calendars = $this->getCalendarsForUser($principalUri);
1772
+        $subscriptions = $this->getSubscriptionsForUser($principalUri);
1773
+        foreach ($calendars as $calendar) {
1774
+            $calendarAnd = $calendarObjectIdQuery->expr()->andX();
1775
+            $calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$calendar['id'])));
1776
+            $calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1777
+
1778
+            // If it's shared, limit search to public events
1779
+            if (isset($calendar['{http://owncloud.org/ns}owner-principal'])
1780
+                && $calendar['principaluri'] !== $calendar['{http://owncloud.org/ns}owner-principal']) {
1781
+                $calendarAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1782
+            }
1783
+
1784
+            $calendarOr->add($calendarAnd);
1785
+        }
1786
+        foreach ($subscriptions as $subscription) {
1787
+            $subscriptionAnd = $calendarObjectIdQuery->expr()->andX();
1788
+            $subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$subscription['id'])));
1789
+            $subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
1790
+
1791
+            // If it's shared, limit search to public events
1792
+            if (isset($subscription['{http://owncloud.org/ns}owner-principal'])
1793
+                && $subscription['principaluri'] !== $subscription['{http://owncloud.org/ns}owner-principal']) {
1794
+                $subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1795
+            }
1796
+
1797
+            $calendarOr->add($subscriptionAnd);
1798
+        }
1799
+
1800
+        foreach ($searchProperties as $property) {
1801
+            $propertyAnd = $calendarObjectIdQuery->expr()->andX();
1802
+            $propertyAnd->add($calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)));
1803
+            $propertyAnd->add($calendarObjectIdQuery->expr()->isNull('cob.parameter'));
1804
+
1805
+            $searchOr->add($propertyAnd);
1806
+        }
1807
+        foreach ($searchParameters as $property => $parameter) {
1808
+            $parameterAnd = $calendarObjectIdQuery->expr()->andX();
1809
+            $parameterAnd->add($calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)));
1810
+            $parameterAnd->add($calendarObjectIdQuery->expr()->eq('cob.parameter', $calendarObjectIdQuery->createNamedParameter($parameter, IQueryBuilder::PARAM_STR_ARRAY)));
1811
+
1812
+            $searchOr->add($parameterAnd);
1813
+        }
1814
+
1815
+        if ($calendarOr->count() === 0) {
1816
+            return [];
1817
+        }
1818
+        if ($searchOr->count() === 0) {
1819
+            return [];
1820
+        }
1821
+
1822
+        $calendarObjectIdQuery->selectDistinct('cob.objectid')
1823
+            ->from($this->dbObjectPropertiesTable, 'cob')
1824
+            ->leftJoin('cob', 'calendarobjects', 'co', $calendarObjectIdQuery->expr()->eq('co.id', 'cob.objectid'))
1825
+            ->andWhere($calendarObjectIdQuery->expr()->in('co.componenttype', $calendarObjectIdQuery->createNamedParameter($componentTypes, IQueryBuilder::PARAM_STR_ARRAY)))
1826
+            ->andWhere($calendarOr)
1827
+            ->andWhere($searchOr);
1828
+
1829
+        if ('' !== $pattern) {
1830
+            if (!$escapePattern) {
1831
+                $calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter($pattern)));
1832
+            } else {
1833
+                $calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
1834
+            }
1835
+        }
1836
+
1837
+        if (isset($options['limit'])) {
1838
+            $calendarObjectIdQuery->setMaxResults($options['limit']);
1839
+        }
1840
+        if (isset($options['offset'])) {
1841
+            $calendarObjectIdQuery->setFirstResult($options['offset']);
1842
+        }
1843
+
1844
+        $result = $calendarObjectIdQuery->execute();
1845
+        $matches = $result->fetchAll();
1846
+        $result->closeCursor();
1847
+        $matches = array_map(static function (array $match):int {
1848
+            return (int) $match['objectid'];
1849
+        }, $matches);
1850
+
1851
+        $query = $this->db->getQueryBuilder();
1852
+        $query->select('calendardata', 'uri', 'calendarid', 'calendartype')
1853
+            ->from('calendarobjects')
1854
+            ->where($query->expr()->in('id', $query->createNamedParameter($matches, IQueryBuilder::PARAM_INT_ARRAY)));
1855
+
1856
+        $result = $query->execute();
1857
+        $calendarObjects = $result->fetchAll();
1858
+        $result->closeCursor();
1859
+
1860
+        return array_map(function (array $array): array {
1861
+            $array['calendarid'] = (int)$array['calendarid'];
1862
+            $array['calendartype'] = (int)$array['calendartype'];
1863
+            $array['calendardata'] = $this->readBlob($array['calendardata']);
1864
+
1865
+            return $array;
1866
+        }, $calendarObjects);
1867
+    }
1868
+
1869
+    /**
1870
+     * Searches through all of a users calendars and calendar objects to find
1871
+     * an object with a specific UID.
1872
+     *
1873
+     * This method should return the path to this object, relative to the
1874
+     * calendar home, so this path usually only contains two parts:
1875
+     *
1876
+     * calendarpath/objectpath.ics
1877
+     *
1878
+     * If the uid is not found, return null.
1879
+     *
1880
+     * This method should only consider * objects that the principal owns, so
1881
+     * any calendars owned by other principals that also appear in this
1882
+     * collection should be ignored.
1883
+     *
1884
+     * @param string $principalUri
1885
+     * @param string $uid
1886
+     * @return string|null
1887
+     */
1888
+    public function getCalendarObjectByUID($principalUri, $uid) {
1889
+        $query = $this->db->getQueryBuilder();
1890
+        $query->selectAlias('c.uri', 'calendaruri')->selectAlias('co.uri', 'objecturi')
1891
+            ->from('calendarobjects', 'co')
1892
+            ->leftJoin('co', 'calendars', 'c', $query->expr()->eq('co.calendarid', 'c.id'))
1893
+            ->where($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri)))
1894
+            ->andWhere($query->expr()->eq('co.uid', $query->createNamedParameter($uid)));
1895
+
1896
+        $stmt = $query->execute();
1897
+
1898
+        if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1899
+            return $row['calendaruri'] . '/' . $row['objecturi'];
1900
+        }
1901
+
1902
+        return null;
1903
+    }
1904
+
1905
+    /**
1906
+     * The getChanges method returns all the changes that have happened, since
1907
+     * the specified syncToken in the specified calendar.
1908
+     *
1909
+     * This function should return an array, such as the following:
1910
+     *
1911
+     * [
1912
+     *   'syncToken' => 'The current synctoken',
1913
+     *   'added'   => [
1914
+     *      'new.txt',
1915
+     *   ],
1916
+     *   'modified'   => [
1917
+     *      'modified.txt',
1918
+     *   ],
1919
+     *   'deleted' => [
1920
+     *      'foo.php.bak',
1921
+     *      'old.txt'
1922
+     *   ]
1923
+     * );
1924
+     *
1925
+     * The returned syncToken property should reflect the *current* syncToken
1926
+     * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
1927
+     * property This is * needed here too, to ensure the operation is atomic.
1928
+     *
1929
+     * If the $syncToken argument is specified as null, this is an initial
1930
+     * sync, and all members should be reported.
1931
+     *
1932
+     * The modified property is an array of nodenames that have changed since
1933
+     * the last token.
1934
+     *
1935
+     * The deleted property is an array with nodenames, that have been deleted
1936
+     * from collection.
1937
+     *
1938
+     * The $syncLevel argument is basically the 'depth' of the report. If it's
1939
+     * 1, you only have to report changes that happened only directly in
1940
+     * immediate descendants. If it's 2, it should also include changes from
1941
+     * the nodes below the child collections. (grandchildren)
1942
+     *
1943
+     * The $limit argument allows a client to specify how many results should
1944
+     * be returned at most. If the limit is not specified, it should be treated
1945
+     * as infinite.
1946
+     *
1947
+     * If the limit (infinite or not) is higher than you're willing to return,
1948
+     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
1949
+     *
1950
+     * If the syncToken is expired (due to data cleanup) or unknown, you must
1951
+     * return null.
1952
+     *
1953
+     * The limit is 'suggestive'. You are free to ignore it.
1954
+     *
1955
+     * @param string $calendarId
1956
+     * @param string $syncToken
1957
+     * @param int $syncLevel
1958
+     * @param int $limit
1959
+     * @param int $calendarType
1960
+     * @return array
1961
+     */
1962
+    public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1963
+        // Current synctoken
1964
+        $stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*calendars` WHERE `id` = ?');
1965
+        $stmt->execute([ $calendarId ]);
1966
+        $currentToken = $stmt->fetchOne();
1967
+
1968
+        if (is_null($currentToken)) {
1969
+            return null;
1970
+        }
1971
+
1972
+        $result = [
1973
+            'syncToken' => $currentToken,
1974
+            'added' => [],
1975
+            'modified' => [],
1976
+            'deleted' => [],
1977
+        ];
1978
+
1979
+        if ($syncToken) {
1980
+            $query = "SELECT `uri`, `operation` FROM `*PREFIX*calendarchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `calendarid` = ? AND `calendartype` = ? ORDER BY `synctoken`";
1981
+            if ($limit > 0) {
1982
+                $query .= " LIMIT " . (int)$limit;
1983
+            }
1984
+
1985
+            // Fetching all changes
1986
+            $stmt = $this->db->prepare($query);
1987
+            $stmt->execute([$syncToken, $currentToken, $calendarId, $calendarType]);
1988
+
1989
+            $changes = [];
1990
+
1991
+            // This loop ensures that any duplicates are overwritten, only the
1992
+            // last change on a node is relevant.
1993
+            while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1994
+                $changes[$row['uri']] = $row['operation'];
1995
+            }
1996
+
1997
+            foreach ($changes as $uri => $operation) {
1998
+                switch ($operation) {
1999
+                    case 1:
2000
+                        $result['added'][] = $uri;
2001
+                        break;
2002
+                    case 2:
2003
+                        $result['modified'][] = $uri;
2004
+                        break;
2005
+                    case 3:
2006
+                        $result['deleted'][] = $uri;
2007
+                        break;
2008
+                }
2009
+            }
2010
+        } else {
2011
+            // No synctoken supplied, this is the initial sync.
2012
+            $query = "SELECT `uri` FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `calendartype` = ?";
2013
+            $stmt = $this->db->prepare($query);
2014
+            $stmt->execute([$calendarId, $calendarType]);
2015
+
2016
+            $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
2017
+        }
2018
+        return $result;
2019
+    }
2020
+
2021
+    /**
2022
+     * Returns a list of subscriptions for a principal.
2023
+     *
2024
+     * Every subscription is an array with the following keys:
2025
+     *  * id, a unique id that will be used by other functions to modify the
2026
+     *    subscription. This can be the same as the uri or a database key.
2027
+     *  * uri. This is just the 'base uri' or 'filename' of the subscription.
2028
+     *  * principaluri. The owner of the subscription. Almost always the same as
2029
+     *    principalUri passed to this method.
2030
+     *
2031
+     * Furthermore, all the subscription info must be returned too:
2032
+     *
2033
+     * 1. {DAV:}displayname
2034
+     * 2. {http://apple.com/ns/ical/}refreshrate
2035
+     * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
2036
+     *    should not be stripped).
2037
+     * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
2038
+     *    should not be stripped).
2039
+     * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
2040
+     *    attachments should not be stripped).
2041
+     * 6. {http://calendarserver.org/ns/}source (Must be a
2042
+     *     Sabre\DAV\Property\Href).
2043
+     * 7. {http://apple.com/ns/ical/}calendar-color
2044
+     * 8. {http://apple.com/ns/ical/}calendar-order
2045
+     * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
2046
+     *    (should just be an instance of
2047
+     *    Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
2048
+     *    default components).
2049
+     *
2050
+     * @param string $principalUri
2051
+     * @return array
2052
+     */
2053
+    public function getSubscriptionsForUser($principalUri) {
2054
+        $fields = array_values($this->subscriptionPropertyMap);
2055
+        $fields[] = 'id';
2056
+        $fields[] = 'uri';
2057
+        $fields[] = 'source';
2058
+        $fields[] = 'principaluri';
2059
+        $fields[] = 'lastmodified';
2060
+        $fields[] = 'synctoken';
2061
+
2062
+        $query = $this->db->getQueryBuilder();
2063
+        $query->select($fields)
2064
+            ->from('calendarsubscriptions')
2065
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2066
+            ->orderBy('calendarorder', 'asc');
2067
+        $stmt = $query->execute();
2068
+
2069
+        $subscriptions = [];
2070
+        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
2071
+            $subscription = [
2072
+                'id' => $row['id'],
2073
+                'uri' => $row['uri'],
2074
+                'principaluri' => $row['principaluri'],
2075
+                'source' => $row['source'],
2076
+                'lastmodified' => $row['lastmodified'],
2077
+
2078
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
2079
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
2080
+            ];
2081
+
2082
+            foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
2083
+                if (!is_null($row[$dbName])) {
2084
+                    $subscription[$xmlName] = $row[$dbName];
2085
+                }
2086
+            }
2087
+
2088
+            $subscriptions[] = $subscription;
2089
+        }
2090
+
2091
+        return $subscriptions;
2092
+    }
2093
+
2094
+    /**
2095
+     * Creates a new subscription for a principal.
2096
+     *
2097
+     * If the creation was a success, an id must be returned that can be used to reference
2098
+     * this subscription in other methods, such as updateSubscription.
2099
+     *
2100
+     * @param string $principalUri
2101
+     * @param string $uri
2102
+     * @param array $properties
2103
+     * @return mixed
2104
+     */
2105
+    public function createSubscription($principalUri, $uri, array $properties) {
2106
+        if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
2107
+            throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
2108
+        }
2109
+
2110
+        $values = [
2111
+            'principaluri' => $principalUri,
2112
+            'uri' => $uri,
2113
+            'source' => $properties['{http://calendarserver.org/ns/}source']->getHref(),
2114
+            'lastmodified' => time(),
2115
+        ];
2116
+
2117
+        $propertiesBoolean = ['striptodos', 'stripalarms', 'stripattachments'];
2118
+
2119
+        foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
2120
+            if (array_key_exists($xmlName, $properties)) {
2121
+                $values[$dbName] = $properties[$xmlName];
2122
+                if (in_array($dbName, $propertiesBoolean)) {
2123
+                    $values[$dbName] = true;
2124
+                }
2125
+            }
2126
+        }
2127
+
2128
+        $valuesToInsert = [];
2129
+
2130
+        $query = $this->db->getQueryBuilder();
2131
+
2132
+        foreach (array_keys($values) as $name) {
2133
+            $valuesToInsert[$name] = $query->createNamedParameter($values[$name]);
2134
+        }
2135
+
2136
+        $query->insert('calendarsubscriptions')
2137
+            ->values($valuesToInsert)
2138
+            ->execute();
2139
+
2140
+        $subscriptionId = $this->db->lastInsertId('*PREFIX*calendarsubscriptions');
2141
+
2142
+        $subscriptionRow = $this->getSubscriptionById($subscriptionId);
2143
+        $this->dispatcher->dispatchTyped(new SubscriptionCreatedEvent((int)$subscriptionId, $subscriptionRow));
2144
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createSubscription', new GenericEvent(
2145
+            '\OCA\DAV\CalDAV\CalDavBackend::createSubscription',
2146
+            [
2147
+                'subscriptionId' => $subscriptionId,
2148
+                'subscriptionData' => $subscriptionRow,
2149
+            ]));
2150
+
2151
+        return $subscriptionId;
2152
+    }
2153
+
2154
+    /**
2155
+     * Updates a subscription
2156
+     *
2157
+     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
2158
+     * To do the actual updates, you must tell this object which properties
2159
+     * you're going to process with the handle() method.
2160
+     *
2161
+     * Calling the handle method is like telling the PropPatch object "I
2162
+     * promise I can handle updating this property".
2163
+     *
2164
+     * Read the PropPatch documentation for more info and examples.
2165
+     *
2166
+     * @param mixed $subscriptionId
2167
+     * @param PropPatch $propPatch
2168
+     * @return void
2169
+     */
2170
+    public function updateSubscription($subscriptionId, PropPatch $propPatch) {
2171
+        $supportedProperties = array_keys($this->subscriptionPropertyMap);
2172
+        $supportedProperties[] = '{http://calendarserver.org/ns/}source';
2173
+
2174
+        $propPatch->handle($supportedProperties, function ($mutations) use ($subscriptionId) {
2175
+            $newValues = [];
2176
+
2177
+            foreach ($mutations as $propertyName => $propertyValue) {
2178
+                if ($propertyName === '{http://calendarserver.org/ns/}source') {
2179
+                    $newValues['source'] = $propertyValue->getHref();
2180
+                } else {
2181
+                    $fieldName = $this->subscriptionPropertyMap[$propertyName];
2182
+                    $newValues[$fieldName] = $propertyValue;
2183
+                }
2184
+            }
2185
+
2186
+            $query = $this->db->getQueryBuilder();
2187
+            $query->update('calendarsubscriptions')
2188
+                ->set('lastmodified', $query->createNamedParameter(time()));
2189
+            foreach ($newValues as $fieldName => $value) {
2190
+                $query->set($fieldName, $query->createNamedParameter($value));
2191
+            }
2192
+            $query->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
2193
+                ->execute();
2194
+
2195
+            $subscriptionRow = $this->getSubscriptionById($subscriptionId);
2196
+            $this->dispatcher->dispatchTyped(new SubscriptionUpdatedEvent((int)$subscriptionId, $subscriptionRow, [], $mutations));
2197
+            $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateSubscription', new GenericEvent(
2198
+                '\OCA\DAV\CalDAV\CalDavBackend::updateSubscription',
2199
+                [
2200
+                    'subscriptionId' => $subscriptionId,
2201
+                    'subscriptionData' => $subscriptionRow,
2202
+                    'propertyMutations' => $mutations,
2203
+                ]));
2204
+
2205
+            return true;
2206
+        });
2207
+    }
2208
+
2209
+    /**
2210
+     * Deletes a subscription.
2211
+     *
2212
+     * @param mixed $subscriptionId
2213
+     * @return void
2214
+     */
2215
+    public function deleteSubscription($subscriptionId) {
2216
+        $subscriptionRow = $this->getSubscriptionById($subscriptionId);
2217
+
2218
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription', new GenericEvent(
2219
+            '\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription',
2220
+            [
2221
+                'subscriptionId' => $subscriptionId,
2222
+                'subscriptionData' => $this->getSubscriptionById($subscriptionId),
2223
+            ]));
2224
+
2225
+        $query = $this->db->getQueryBuilder();
2226
+        $query->delete('calendarsubscriptions')
2227
+            ->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
2228
+            ->execute();
2229
+
2230
+        $query = $this->db->getQueryBuilder();
2231
+        $query->delete('calendarobjects')
2232
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2233
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2234
+            ->execute();
2235
+
2236
+        $query->delete('calendarchanges')
2237
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2238
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2239
+            ->execute();
2240
+
2241
+        $query->delete($this->dbObjectPropertiesTable)
2242
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2243
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2244
+            ->execute();
2245
+
2246
+        if ($subscriptionRow) {
2247
+            $this->dispatcher->dispatchTyped(new SubscriptionDeletedEvent((int)$subscriptionId, $subscriptionRow, []));
2248
+        }
2249
+    }
2250
+
2251
+    /**
2252
+     * Returns a single scheduling object for the inbox collection.
2253
+     *
2254
+     * The returned array should contain the following elements:
2255
+     *   * uri - A unique basename for the object. This will be used to
2256
+     *           construct a full uri.
2257
+     *   * calendardata - The iCalendar object
2258
+     *   * lastmodified - The last modification date. Can be an int for a unix
2259
+     *                    timestamp, or a PHP DateTime object.
2260
+     *   * etag - A unique token that must change if the object changed.
2261
+     *   * size - The size of the object, in bytes.
2262
+     *
2263
+     * @param string $principalUri
2264
+     * @param string $objectUri
2265
+     * @return array
2266
+     */
2267
+    public function getSchedulingObject($principalUri, $objectUri) {
2268
+        $query = $this->db->getQueryBuilder();
2269
+        $stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
2270
+            ->from('schedulingobjects')
2271
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2272
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
2273
+            ->execute();
2274
+
2275
+        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
2276
+
2277
+        if (!$row) {
2278
+            return null;
2279
+        }
2280
+
2281
+        return [
2282
+            'uri' => $row['uri'],
2283
+            'calendardata' => $row['calendardata'],
2284
+            'lastmodified' => $row['lastmodified'],
2285
+            'etag' => '"' . $row['etag'] . '"',
2286
+            'size' => (int)$row['size'],
2287
+        ];
2288
+    }
2289
+
2290
+    /**
2291
+     * Returns all scheduling objects for the inbox collection.
2292
+     *
2293
+     * These objects should be returned as an array. Every item in the array
2294
+     * should follow the same structure as returned from getSchedulingObject.
2295
+     *
2296
+     * The main difference is that 'calendardata' is optional.
2297
+     *
2298
+     * @param string $principalUri
2299
+     * @return array
2300
+     */
2301
+    public function getSchedulingObjects($principalUri) {
2302
+        $query = $this->db->getQueryBuilder();
2303
+        $stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
2304
+                ->from('schedulingobjects')
2305
+                ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2306
+                ->execute();
2307
+
2308
+        $result = [];
2309
+        foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
2310
+            $result[] = [
2311
+                'calendardata' => $row['calendardata'],
2312
+                'uri' => $row['uri'],
2313
+                'lastmodified' => $row['lastmodified'],
2314
+                'etag' => '"' . $row['etag'] . '"',
2315
+                'size' => (int)$row['size'],
2316
+            ];
2317
+        }
2318
+
2319
+        return $result;
2320
+    }
2321
+
2322
+    /**
2323
+     * Deletes a scheduling object from the inbox collection.
2324
+     *
2325
+     * @param string $principalUri
2326
+     * @param string $objectUri
2327
+     * @return void
2328
+     */
2329
+    public function deleteSchedulingObject($principalUri, $objectUri) {
2330
+        $query = $this->db->getQueryBuilder();
2331
+        $query->delete('schedulingobjects')
2332
+                ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2333
+                ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
2334
+                ->execute();
2335
+    }
2336
+
2337
+    /**
2338
+     * Creates a new scheduling object. This should land in a users' inbox.
2339
+     *
2340
+     * @param string $principalUri
2341
+     * @param string $objectUri
2342
+     * @param string $objectData
2343
+     * @return void
2344
+     */
2345
+    public function createSchedulingObject($principalUri, $objectUri, $objectData) {
2346
+        $query = $this->db->getQueryBuilder();
2347
+        $query->insert('schedulingobjects')
2348
+            ->values([
2349
+                'principaluri' => $query->createNamedParameter($principalUri),
2350
+                'calendardata' => $query->createNamedParameter($objectData, IQueryBuilder::PARAM_LOB),
2351
+                'uri' => $query->createNamedParameter($objectUri),
2352
+                'lastmodified' => $query->createNamedParameter(time()),
2353
+                'etag' => $query->createNamedParameter(md5($objectData)),
2354
+                'size' => $query->createNamedParameter(strlen($objectData))
2355
+            ])
2356
+            ->execute();
2357
+    }
2358
+
2359
+    /**
2360
+     * Adds a change record to the calendarchanges table.
2361
+     *
2362
+     * @param mixed $calendarId
2363
+     * @param string $objectUri
2364
+     * @param int $operation 1 = add, 2 = modify, 3 = delete.
2365
+     * @param int $calendarType
2366
+     * @return void
2367
+     */
2368
+    protected function addChange($calendarId, $objectUri, $operation, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2369
+        $table = $calendarType === self::CALENDAR_TYPE_CALENDAR ? 'calendars': 'calendarsubscriptions';
2370
+
2371
+        $query = $this->db->getQueryBuilder();
2372
+        $query->select('synctoken')
2373
+            ->from($table)
2374
+            ->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
2375
+        $result = $query->execute();
2376
+        $syncToken = (int)$result->fetchOne();
2377
+        $result->closeCursor();
2378
+
2379
+        $query = $this->db->getQueryBuilder();
2380
+        $query->insert('calendarchanges')
2381
+            ->values([
2382
+                'uri' => $query->createNamedParameter($objectUri),
2383
+                'synctoken' => $query->createNamedParameter($syncToken),
2384
+                'calendarid' => $query->createNamedParameter($calendarId),
2385
+                'operation' => $query->createNamedParameter($operation),
2386
+                'calendartype' => $query->createNamedParameter($calendarType),
2387
+            ])
2388
+            ->execute();
2389
+
2390
+        $stmt = $this->db->prepare("UPDATE `*PREFIX*$table` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?");
2391
+        $stmt->execute([
2392
+            $calendarId
2393
+        ]);
2394
+    }
2395
+
2396
+    /**
2397
+     * Parses some information from calendar objects, used for optimized
2398
+     * calendar-queries.
2399
+     *
2400
+     * Returns an array with the following keys:
2401
+     *   * etag - An md5 checksum of the object without the quotes.
2402
+     *   * size - Size of the object in bytes
2403
+     *   * componentType - VEVENT, VTODO or VJOURNAL
2404
+     *   * firstOccurence
2405
+     *   * lastOccurence
2406
+     *   * uid - value of the UID property
2407
+     *
2408
+     * @param string $calendarData
2409
+     * @return array
2410
+     */
2411
+    public function getDenormalizedData($calendarData) {
2412
+        $vObject = Reader::read($calendarData);
2413
+        $vEvents = [];
2414
+        $componentType = null;
2415
+        $component = null;
2416
+        $firstOccurrence = null;
2417
+        $lastOccurrence = null;
2418
+        $uid = null;
2419
+        $classification = self::CLASSIFICATION_PUBLIC;
2420
+        $hasDTSTART = false;
2421
+        foreach ($vObject->getComponents() as $component) {
2422
+            if ($component->name !== 'VTIMEZONE') {
2423
+                // Finding all VEVENTs, and track them
2424
+                if ($component->name === 'VEVENT') {
2425
+                    array_push($vEvents, $component);
2426
+                    if ($component->DTSTART) {
2427
+                        $hasDTSTART = true;
2428
+                    }
2429
+                }
2430
+                // Track first component type and uid
2431
+                if ($uid === null) {
2432
+                    $componentType = $component->name;
2433
+                    $uid = (string)$component->UID;
2434
+                }
2435
+            }
2436
+        }
2437
+        if (!$componentType) {
2438
+            throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
2439
+        }
2440
+
2441
+        if ($hasDTSTART) {
2442
+            $component = $vEvents[0];
2443
+
2444
+            // Finding the last occurrence is a bit harder
2445
+            if (!isset($component->RRULE) && count($vEvents) === 1) {
2446
+                $firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp();
2447
+                if (isset($component->DTEND)) {
2448
+                    $lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp();
2449
+                } elseif (isset($component->DURATION)) {
2450
+                    $endDate = clone $component->DTSTART->getDateTime();
2451
+                    $endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
2452
+                    $lastOccurrence = $endDate->getTimeStamp();
2453
+                } elseif (!$component->DTSTART->hasTime()) {
2454
+                    $endDate = clone $component->DTSTART->getDateTime();
2455
+                    $endDate->modify('+1 day');
2456
+                    $lastOccurrence = $endDate->getTimeStamp();
2457
+                } else {
2458
+                    $lastOccurrence = $firstOccurrence;
2459
+                }
2460
+            } else {
2461
+                $it = new EventIterator($vEvents);
2462
+                $maxDate = new DateTime(self::MAX_DATE);
2463
+                $firstOccurrence = $it->getDtStart()->getTimestamp();
2464
+                if ($it->isInfinite()) {
2465
+                    $lastOccurrence = $maxDate->getTimestamp();
2466
+                } else {
2467
+                    $end = $it->getDtEnd();
2468
+                    while ($it->valid() && $end < $maxDate) {
2469
+                        $end = $it->getDtEnd();
2470
+                        $it->next();
2471
+                    }
2472
+                    $lastOccurrence = $end->getTimestamp();
2473
+                }
2474
+            }
2475
+        }
2476
+
2477
+        if ($component->CLASS) {
2478
+            $classification = CalDavBackend::CLASSIFICATION_PRIVATE;
2479
+            switch ($component->CLASS->getValue()) {
2480
+                case 'PUBLIC':
2481
+                    $classification = CalDavBackend::CLASSIFICATION_PUBLIC;
2482
+                    break;
2483
+                case 'CONFIDENTIAL':
2484
+                    $classification = CalDavBackend::CLASSIFICATION_CONFIDENTIAL;
2485
+                    break;
2486
+            }
2487
+        }
2488
+        return [
2489
+            'etag' => md5($calendarData),
2490
+            'size' => strlen($calendarData),
2491
+            'componentType' => $componentType,
2492
+            'firstOccurence' => is_null($firstOccurrence) ? null : max(0, $firstOccurrence),
2493
+            'lastOccurence' => $lastOccurrence,
2494
+            'uid' => $uid,
2495
+            'classification' => $classification
2496
+        ];
2497
+    }
2498
+
2499
+    /**
2500
+     * @param $cardData
2501
+     * @return bool|string
2502
+     */
2503
+    private function readBlob($cardData) {
2504
+        if (is_resource($cardData)) {
2505
+            return stream_get_contents($cardData);
2506
+        }
2507
+
2508
+        return $cardData;
2509
+    }
2510
+
2511
+    /**
2512
+     * @param IShareable $shareable
2513
+     * @param array $add
2514
+     * @param array $remove
2515
+     */
2516
+    public function updateShares($shareable, $add, $remove) {
2517
+        $calendarId = $shareable->getResourceId();
2518
+        $calendarRow = $this->getCalendarById($calendarId);
2519
+        $oldShares = $this->getShares($calendarId);
2520
+
2521
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateShares', new GenericEvent(
2522
+            '\OCA\DAV\CalDAV\CalDavBackend::updateShares',
2523
+            [
2524
+                'calendarId' => $calendarId,
2525
+                'calendarData' => $calendarRow,
2526
+                'shares' => $oldShares,
2527
+                'add' => $add,
2528
+                'remove' => $remove,
2529
+            ]));
2530
+        $this->calendarSharingBackend->updateShares($shareable, $add, $remove);
2531
+
2532
+        $this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent((int)$calendarId, $calendarRow, $oldShares, $add, $remove));
2533
+    }
2534
+
2535
+    /**
2536
+     * @param int $resourceId
2537
+     * @param int $calendarType
2538
+     * @return array
2539
+     */
2540
+    public function getShares($resourceId, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2541
+        return $this->calendarSharingBackend->getShares($resourceId);
2542
+    }
2543
+
2544
+    /**
2545
+     * @param boolean $value
2546
+     * @param \OCA\DAV\CalDAV\Calendar $calendar
2547
+     * @return string|null
2548
+     */
2549
+    public function setPublishStatus($value, $calendar) {
2550
+        $calendarId = $calendar->getResourceId();
2551
+        $calendarData = $this->getCalendarById($calendarId);
2552
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::publishCalendar', new GenericEvent(
2553
+            '\OCA\DAV\CalDAV\CalDavBackend::updateShares',
2554
+            [
2555
+                'calendarId' => $calendarId,
2556
+                'calendarData' => $calendarData,
2557
+                'public' => $value,
2558
+            ]));
2559
+
2560
+        $query = $this->db->getQueryBuilder();
2561
+        if ($value) {
2562
+            $publicUri = $this->random->generate(16, ISecureRandom::CHAR_HUMAN_READABLE);
2563
+            $query->insert('dav_shares')
2564
+                ->values([
2565
+                    'principaluri' => $query->createNamedParameter($calendar->getPrincipalURI()),
2566
+                    'type' => $query->createNamedParameter('calendar'),
2567
+                    'access' => $query->createNamedParameter(self::ACCESS_PUBLIC),
2568
+                    'resourceid' => $query->createNamedParameter($calendar->getResourceId()),
2569
+                    'publicuri' => $query->createNamedParameter($publicUri)
2570
+                ]);
2571
+            $query->execute();
2572
+
2573
+            $this->dispatcher->dispatchTyped(new CalendarPublishedEvent((int)$calendarId, $calendarData, $publicUri));
2574
+            return $publicUri;
2575
+        }
2576
+        $query->delete('dav_shares')
2577
+            ->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
2578
+            ->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)));
2579
+        $query->execute();
2580
+
2581
+        $this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent((int)$calendarId, $calendarData));
2582
+        return null;
2583
+    }
2584
+
2585
+    /**
2586
+     * @param \OCA\DAV\CalDAV\Calendar $calendar
2587
+     * @return mixed
2588
+     */
2589
+    public function getPublishStatus($calendar) {
2590
+        $query = $this->db->getQueryBuilder();
2591
+        $result = $query->select('publicuri')
2592
+            ->from('dav_shares')
2593
+            ->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
2594
+            ->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
2595
+            ->execute();
2596
+
2597
+        $row = $result->fetch();
2598
+        $result->closeCursor();
2599
+        return $row ? reset($row) : false;
2600
+    }
2601
+
2602
+    /**
2603
+     * @param int $resourceId
2604
+     * @param array $acl
2605
+     * @return array
2606
+     */
2607
+    public function applyShareAcl($resourceId, $acl) {
2608
+        return $this->calendarSharingBackend->applyShareAcl($resourceId, $acl);
2609
+    }
2610
+
2611
+
2612
+
2613
+    /**
2614
+     * update properties table
2615
+     *
2616
+     * @param int $calendarId
2617
+     * @param string $objectUri
2618
+     * @param string $calendarData
2619
+     * @param int $calendarType
2620
+     */
2621
+    public function updateProperties($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2622
+        $objectId = $this->getCalendarObjectId($calendarId, $objectUri, $calendarType);
2623
+
2624
+        try {
2625
+            $vCalendar = $this->readCalendarData($calendarData);
2626
+        } catch (\Exception $ex) {
2627
+            return;
2628
+        }
2629
+
2630
+        $this->purgeProperties($calendarId, $objectId);
2631
+
2632
+        $query = $this->db->getQueryBuilder();
2633
+        $query->insert($this->dbObjectPropertiesTable)
2634
+            ->values(
2635
+                [
2636
+                    'calendarid' => $query->createNamedParameter($calendarId),
2637
+                    'calendartype' => $query->createNamedParameter($calendarType),
2638
+                    'objectid' => $query->createNamedParameter($objectId),
2639
+                    'name' => $query->createParameter('name'),
2640
+                    'parameter' => $query->createParameter('parameter'),
2641
+                    'value' => $query->createParameter('value'),
2642
+                ]
2643
+            );
2644
+
2645
+        $indexComponents = ['VEVENT', 'VJOURNAL', 'VTODO'];
2646
+        foreach ($vCalendar->getComponents() as $component) {
2647
+            if (!in_array($component->name, $indexComponents)) {
2648
+                continue;
2649
+            }
2650
+
2651
+            foreach ($component->children() as $property) {
2652
+                if (in_array($property->name, self::$indexProperties)) {
2653
+                    $value = $property->getValue();
2654
+                    // is this a shitty db?
2655
+                    if (!$this->db->supports4ByteText()) {
2656
+                        $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
2657
+                    }
2658
+                    $value = mb_substr($value, 0, 254);
2659
+
2660
+                    $query->setParameter('name', $property->name);
2661
+                    $query->setParameter('parameter', null);
2662
+                    $query->setParameter('value', $value);
2663
+                    $query->execute();
2664
+                }
2665
+
2666
+                if (array_key_exists($property->name, self::$indexParameters)) {
2667
+                    $parameters = $property->parameters();
2668
+                    $indexedParametersForProperty = self::$indexParameters[$property->name];
2669
+
2670
+                    foreach ($parameters as $key => $value) {
2671
+                        if (in_array($key, $indexedParametersForProperty)) {
2672
+                            // is this a shitty db?
2673
+                            if ($this->db->supports4ByteText()) {
2674
+                                $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
2675
+                            }
2676
+
2677
+                            $query->setParameter('name', $property->name);
2678
+                            $query->setParameter('parameter', mb_substr($key, 0, 254));
2679
+                            $query->setParameter('value', mb_substr($value, 0, 254));
2680
+                            $query->execute();
2681
+                        }
2682
+                    }
2683
+                }
2684
+            }
2685
+        }
2686
+    }
2687
+
2688
+    /**
2689
+     * deletes all birthday calendars
2690
+     */
2691
+    public function deleteAllBirthdayCalendars() {
2692
+        $query = $this->db->getQueryBuilder();
2693
+        $result = $query->select(['id'])->from('calendars')
2694
+            ->where($query->expr()->eq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)))
2695
+            ->execute();
2696
+
2697
+        $ids = $result->fetchAll();
2698
+        foreach ($ids as $id) {
2699
+            $this->deleteCalendar($id['id']);
2700
+        }
2701
+    }
2702
+
2703
+    /**
2704
+     * @param $subscriptionId
2705
+     */
2706
+    public function purgeAllCachedEventsForSubscription($subscriptionId) {
2707
+        $query = $this->db->getQueryBuilder();
2708
+        $query->select('uri')
2709
+            ->from('calendarobjects')
2710
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2711
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
2712
+        $stmt = $query->execute();
2713
+
2714
+        $uris = [];
2715
+        foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
2716
+            $uris[] = $row['uri'];
2717
+        }
2718
+        $stmt->closeCursor();
2719
+
2720
+        $query = $this->db->getQueryBuilder();
2721
+        $query->delete('calendarobjects')
2722
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2723
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2724
+            ->execute();
2725
+
2726
+        $query->delete('calendarchanges')
2727
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2728
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2729
+            ->execute();
2730
+
2731
+        $query->delete($this->dbObjectPropertiesTable)
2732
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2733
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2734
+            ->execute();
2735
+
2736
+        foreach ($uris as $uri) {
2737
+            $this->addChange($subscriptionId, $uri, 3, self::CALENDAR_TYPE_SUBSCRIPTION);
2738
+        }
2739
+    }
2740
+
2741
+    /**
2742
+     * Move a calendar from one user to another
2743
+     *
2744
+     * @param string $uriName
2745
+     * @param string $uriOrigin
2746
+     * @param string $uriDestination
2747
+     * @param string $newUriName (optional) the new uriName
2748
+     */
2749
+    public function moveCalendar($uriName, $uriOrigin, $uriDestination, $newUriName = null) {
2750
+        $query = $this->db->getQueryBuilder();
2751
+        $query->update('calendars')
2752
+            ->set('principaluri', $query->createNamedParameter($uriDestination))
2753
+            ->set('uri', $query->createNamedParameter($newUriName ?: $uriName))
2754
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($uriOrigin)))
2755
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($uriName)))
2756
+            ->execute();
2757
+    }
2758
+
2759
+    /**
2760
+     * read VCalendar data into a VCalendar object
2761
+     *
2762
+     * @param string $objectData
2763
+     * @return VCalendar
2764
+     */
2765
+    protected function readCalendarData($objectData) {
2766
+        return Reader::read($objectData);
2767
+    }
2768
+
2769
+    /**
2770
+     * delete all properties from a given calendar object
2771
+     *
2772
+     * @param int $calendarId
2773
+     * @param int $objectId
2774
+     */
2775
+    protected function purgeProperties($calendarId, $objectId) {
2776
+        $query = $this->db->getQueryBuilder();
2777
+        $query->delete($this->dbObjectPropertiesTable)
2778
+            ->where($query->expr()->eq('objectid', $query->createNamedParameter($objectId)))
2779
+            ->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
2780
+        $query->execute();
2781
+    }
2782
+
2783
+    /**
2784
+     * get ID from a given calendar object
2785
+     *
2786
+     * @param int $calendarId
2787
+     * @param string $uri
2788
+     * @param int $calendarType
2789
+     * @return int
2790
+     */
2791
+    protected function getCalendarObjectId($calendarId, $uri, $calendarType):int {
2792
+        $query = $this->db->getQueryBuilder();
2793
+        $query->select('id')
2794
+            ->from('calendarobjects')
2795
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
2796
+            ->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
2797
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
2798
+
2799
+        $result = $query->execute();
2800
+        $objectIds = $result->fetch();
2801
+        $result->closeCursor();
2802
+
2803
+        if (!isset($objectIds['id'])) {
2804
+            throw new \InvalidArgumentException('Calendarobject does not exists: ' . $uri);
2805
+        }
2806
+
2807
+        return (int)$objectIds['id'];
2808
+    }
2809
+
2810
+    /**
2811
+     * return legacy endpoint principal name to new principal name
2812
+     *
2813
+     * @param $principalUri
2814
+     * @param $toV2
2815
+     * @return string
2816
+     */
2817
+    private function convertPrincipal($principalUri, $toV2) {
2818
+        if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
2819
+            list(, $name) = Uri\split($principalUri);
2820
+            if ($toV2 === true) {
2821
+                return "principals/users/$name";
2822
+            }
2823
+            return "principals/$name";
2824
+        }
2825
+        return $principalUri;
2826
+    }
2827
+
2828
+    /**
2829
+     * adds information about an owner to the calendar data
2830
+     *
2831
+     * @param $calendarInfo
2832
+     */
2833
+    private function addOwnerPrincipal(&$calendarInfo) {
2834
+        $ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
2835
+        $displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
2836
+        if (isset($calendarInfo[$ownerPrincipalKey])) {
2837
+            $uri = $calendarInfo[$ownerPrincipalKey];
2838
+        } else {
2839
+            $uri = $calendarInfo['principaluri'];
2840
+        }
2841
+
2842
+        $principalInformation = $this->principalBackend->getPrincipalByPath($uri);
2843
+        if (isset($principalInformation['{DAV:}displayname'])) {
2844
+            $calendarInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
2845
+        }
2846
+    }
2847 2847
 }
Please login to merge, or discard this patch.
Spacing   +116 added lines, -116 removed lines patch added patch discarded remove patch
@@ -256,7 +256,7 @@  discard block
 block discarded – undo
256 256
 		}
257 257
 
258 258
 		$result = $query->execute();
259
-		$column = (int)$result->fetchOne();
259
+		$column = (int) $result->fetchOne();
260 260
 		$result->closeCursor();
261 261
 		return $column;
262 262
 	}
@@ -316,18 +316,18 @@  discard block
 block discarded – undo
316 316
 			$row['principaluri'] = (string) $row['principaluri'];
317 317
 			$components = [];
318 318
 			if ($row['components']) {
319
-				$components = explode(',',$row['components']);
319
+				$components = explode(',', $row['components']);
320 320
 			}
321 321
 
322 322
 			$calendar = [
323 323
 				'id' => $row['id'],
324 324
 				'uri' => $row['uri'],
325 325
 				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
326
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
327
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
328
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
329
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
330
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
326
+				'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
327
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
328
+				'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
329
+				'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
330
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
331 331
 			];
332 332
 
333 333
 			foreach ($this->propertyMap as $xmlName => $dbName) {
@@ -365,9 +365,9 @@  discard block
 block discarded – undo
365 365
 			->setParameter('type', 'calendar')
366 366
 			->setParameter('principaluri', $principals, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY);
367 367
 
368
-		$result	= $query->execute();
368
+		$result = $query->execute();
369 369
 
370
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
370
+		$readOnlyPropertyName = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}read-only';
371 371
 		while ($row = $result->fetch()) {
372 372
 			$row['principaluri'] = (string) $row['principaluri'];
373 373
 			if ($row['principaluri'] === $principalUri) {
@@ -388,21 +388,21 @@  discard block
 block discarded – undo
388 388
 			}
389 389
 
390 390
 			list(, $name) = Uri\split($row['principaluri']);
391
-			$uri = $row['uri'] . '_shared_by_' . $name;
392
-			$row['displayname'] = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
391
+			$uri = $row['uri'].'_shared_by_'.$name;
392
+			$row['displayname'] = $row['displayname'].' ('.$this->getUserDisplayName($name).')';
393 393
 			$components = [];
394 394
 			if ($row['components']) {
395
-				$components = explode(',',$row['components']);
395
+				$components = explode(',', $row['components']);
396 396
 			}
397 397
 			$calendar = [
398 398
 				'id' => $row['id'],
399 399
 				'uri' => $uri,
400 400
 				'principaluri' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
401
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
402
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
403
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
404
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp('transparent'),
405
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
401
+				'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
402
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
403
+				'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
404
+				'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp('transparent'),
405
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
406 406
 				$readOnlyPropertyName => $readOnly,
407 407
 			];
408 408
 
@@ -443,16 +443,16 @@  discard block
 block discarded – undo
443 443
 			$row['principaluri'] = (string) $row['principaluri'];
444 444
 			$components = [];
445 445
 			if ($row['components']) {
446
-				$components = explode(',',$row['components']);
446
+				$components = explode(',', $row['components']);
447 447
 			}
448 448
 			$calendar = [
449 449
 				'id' => $row['id'],
450 450
 				'uri' => $row['uri'],
451 451
 				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
452
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
453
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
454
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
455
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
452
+				'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
453
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
454
+				'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
455
+				'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
456 456
 			];
457 457
 			foreach ($this->propertyMap as $xmlName => $dbName) {
458 458
 				$calendar[$xmlName] = $row[$dbName];
@@ -512,22 +512,22 @@  discard block
 block discarded – undo
512 512
 		while ($row = $result->fetch()) {
513 513
 			$row['principaluri'] = (string) $row['principaluri'];
514 514
 			list(, $name) = Uri\split($row['principaluri']);
515
-			$row['displayname'] = $row['displayname'] . "($name)";
515
+			$row['displayname'] = $row['displayname']."($name)";
516 516
 			$components = [];
517 517
 			if ($row['components']) {
518
-				$components = explode(',',$row['components']);
518
+				$components = explode(',', $row['components']);
519 519
 			}
520 520
 			$calendar = [
521 521
 				'id' => $row['id'],
522 522
 				'uri' => $row['publicuri'],
523 523
 				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
524
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
525
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
526
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
527
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
528
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
529
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
530
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
524
+				'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
525
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
526
+				'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
527
+				'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
528
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
529
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}read-only' => (int) $row['access'] === Backend::ACCESS_READ,
530
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}public' => (int) $row['access'] === self::ACCESS_PUBLIC,
531 531
 			];
532 532
 
533 533
 			foreach ($this->propertyMap as $xmlName => $dbName) {
@@ -574,27 +574,27 @@  discard block
 block discarded – undo
574 574
 		$result->closeCursor();
575 575
 
576 576
 		if ($row === false) {
577
-			throw new NotFound('Node with name \'' . $uri . '\' could not be found');
577
+			throw new NotFound('Node with name \''.$uri.'\' could not be found');
578 578
 		}
579 579
 
580 580
 		$row['principaluri'] = (string) $row['principaluri'];
581 581
 		list(, $name) = Uri\split($row['principaluri']);
582
-		$row['displayname'] = $row['displayname'] . ' ' . "($name)";
582
+		$row['displayname'] = $row['displayname'].' '."($name)";
583 583
 		$components = [];
584 584
 		if ($row['components']) {
585
-			$components = explode(',',$row['components']);
585
+			$components = explode(',', $row['components']);
586 586
 		}
587 587
 		$calendar = [
588 588
 			'id' => $row['id'],
589 589
 			'uri' => $row['publicuri'],
590 590
 			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
591
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
592
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
593
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
594
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
595
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
596
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
597
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
591
+			'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
592
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
593
+			'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
594
+			'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
595
+			'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
596
+			'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}read-only' => (int) $row['access'] === Backend::ACCESS_READ,
597
+			'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}public' => (int) $row['access'] === self::ACCESS_PUBLIC,
598 598
 		];
599 599
 
600 600
 		foreach ($this->propertyMap as $xmlName => $dbName) {
@@ -637,17 +637,17 @@  discard block
 block discarded – undo
637 637
 		$row['principaluri'] = (string) $row['principaluri'];
638 638
 		$components = [];
639 639
 		if ($row['components']) {
640
-			$components = explode(',',$row['components']);
640
+			$components = explode(',', $row['components']);
641 641
 		}
642 642
 
643 643
 		$calendar = [
644 644
 			'id' => $row['id'],
645 645
 			'uri' => $row['uri'],
646 646
 			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
647
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
648
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
649
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
650
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
647
+			'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
648
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
649
+			'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
650
+			'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
651 651
 		];
652 652
 
653 653
 		foreach ($this->propertyMap as $xmlName => $dbName) {
@@ -688,17 +688,17 @@  discard block
 block discarded – undo
688 688
 		$row['principaluri'] = (string) $row['principaluri'];
689 689
 		$components = [];
690 690
 		if ($row['components']) {
691
-			$components = explode(',',$row['components']);
691
+			$components = explode(',', $row['components']);
692 692
 		}
693 693
 
694 694
 		$calendar = [
695 695
 			'id' => $row['id'],
696 696
 			'uri' => $row['uri'],
697 697
 			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
698
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
699
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
700
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
701
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
698
+			'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
699
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
700
+			'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
701
+			'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
702 702
 		];
703 703
 
704 704
 		foreach ($this->propertyMap as $xmlName => $dbName) {
@@ -742,8 +742,8 @@  discard block
 block discarded – undo
742 742
 			'principaluri' => $row['principaluri'],
743 743
 			'source' => $row['source'],
744 744
 			'lastmodified' => $row['lastmodified'],
745
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
746
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
745
+			'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
746
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
747 747
 		];
748 748
 
749 749
 		foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
@@ -780,16 +780,16 @@  discard block
 block discarded – undo
780 780
 		$sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
781 781
 		if (isset($properties[$sccs])) {
782 782
 			if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) {
783
-				throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
783
+				throw new DAV\Exception('The '.$sccs.' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
784 784
 			}
785
-			$values['components'] = implode(',',$properties[$sccs]->getValue());
785
+			$values['components'] = implode(',', $properties[$sccs]->getValue());
786 786
 		} elseif (isset($properties['components'])) {
787 787
 			// Allow to provide components internally without having
788 788
 			// to create a SupportedCalendarComponentSet object
789 789
 			$values['components'] = $properties['components'];
790 790
 		}
791 791
 
792
-		$transp = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
792
+		$transp = '{'.Plugin::NS_CALDAV.'}schedule-calendar-transp';
793 793
 		if (isset($properties[$transp])) {
794 794
 			$values['transparent'] = (int) ($properties[$transp]->getValue() === 'transparent');
795 795
 		}
@@ -809,7 +809,7 @@  discard block
 block discarded – undo
809 809
 		$calendarId = $query->getLastInsertId();
810 810
 
811 811
 		$calendarData = $this->getCalendarById($calendarId);
812
-		$this->dispatcher->dispatchTyped(new CalendarCreatedEvent((int)$calendarId, $calendarData));
812
+		$this->dispatcher->dispatchTyped(new CalendarCreatedEvent((int) $calendarId, $calendarData));
813 813
 		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendar', new GenericEvent(
814 814
 			'\OCA\DAV\CalDAV\CalDavBackend::createCalendar',
815 815
 			[
@@ -838,13 +838,13 @@  discard block
 block discarded – undo
838 838
 	 */
839 839
 	public function updateCalendar($calendarId, PropPatch $propPatch) {
840 840
 		$supportedProperties = array_keys($this->propertyMap);
841
-		$supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
841
+		$supportedProperties[] = '{'.Plugin::NS_CALDAV.'}schedule-calendar-transp';
842 842
 
843
-		$propPatch->handle($supportedProperties, function ($mutations) use ($calendarId) {
843
+		$propPatch->handle($supportedProperties, function($mutations) use ($calendarId) {
844 844
 			$newValues = [];
845 845
 			foreach ($mutations as $propertyName => $propertyValue) {
846 846
 				switch ($propertyName) {
847
-					case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp':
847
+					case '{'.Plugin::NS_CALDAV.'}schedule-calendar-transp':
848 848
 						$fieldName = 'transparent';
849 849
 						$newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent');
850 850
 						break;
@@ -866,7 +866,7 @@  discard block
 block discarded – undo
866 866
 
867 867
 			$calendarData = $this->getCalendarById($calendarId);
868 868
 			$shares = $this->getShares($calendarId);
869
-			$this->dispatcher->dispatchTyped(new CalendarUpdatedEvent((int)$calendarId, $calendarData, $shares, $mutations));
869
+			$this->dispatcher->dispatchTyped(new CalendarUpdatedEvent((int) $calendarId, $calendarData, $shares, $mutations));
870 870
 			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendar', new GenericEvent(
871 871
 				'\OCA\DAV\CalDAV\CalDavBackend::updateCalendar',
872 872
 				[
@@ -916,7 +916,7 @@  discard block
 block discarded – undo
916 916
 			->execute();
917 917
 
918 918
 		if ($calendarData) {
919
-			$this->dispatcher->dispatchTyped(new CalendarDeletedEvent((int)$calendarId, $calendarData, $shares));
919
+			$this->dispatcher->dispatchTyped(new CalendarDeletedEvent((int) $calendarId, $calendarData, $shares));
920 920
 		}
921 921
 	}
922 922
 
@@ -976,11 +976,11 @@  discard block
 block discarded – undo
976 976
 				'id' => $row['id'],
977 977
 				'uri' => $row['uri'],
978 978
 				'lastmodified' => $row['lastmodified'],
979
-				'etag' => '"' . $row['etag'] . '"',
979
+				'etag' => '"'.$row['etag'].'"',
980 980
 				'calendarid' => $row['calendarid'],
981
-				'size' => (int)$row['size'],
981
+				'size' => (int) $row['size'],
982 982
 				'component' => strtolower($row['componenttype']),
983
-				'classification' => (int)$row['classification']
983
+				'classification' => (int) $row['classification']
984 984
 			];
985 985
 		}
986 986
 		$stmt->closeCursor();
@@ -1024,12 +1024,12 @@  discard block
 block discarded – undo
1024 1024
 			'id' => $row['id'],
1025 1025
 			'uri' => $row['uri'],
1026 1026
 			'lastmodified' => $row['lastmodified'],
1027
-			'etag' => '"' . $row['etag'] . '"',
1027
+			'etag' => '"'.$row['etag'].'"',
1028 1028
 			'calendarid' => $row['calendarid'],
1029
-			'size' => (int)$row['size'],
1029
+			'size' => (int) $row['size'],
1030 1030
 			'calendardata' => $this->readBlob($row['calendardata']),
1031 1031
 			'component' => strtolower($row['componenttype']),
1032
-			'classification' => (int)$row['classification']
1032
+			'classification' => (int) $row['classification']
1033 1033
 		];
1034 1034
 	}
1035 1035
 
@@ -1070,12 +1070,12 @@  discard block
 block discarded – undo
1070 1070
 					'id' => $row['id'],
1071 1071
 					'uri' => $row['uri'],
1072 1072
 					'lastmodified' => $row['lastmodified'],
1073
-					'etag' => '"' . $row['etag'] . '"',
1073
+					'etag' => '"'.$row['etag'].'"',
1074 1074
 					'calendarid' => $row['calendarid'],
1075
-					'size' => (int)$row['size'],
1075
+					'size' => (int) $row['size'],
1076 1076
 					'calendardata' => $this->readBlob($row['calendardata']),
1077 1077
 					'component' => strtolower($row['componenttype']),
1078
-					'classification' => (int)$row['classification']
1078
+					'classification' => (int) $row['classification']
1079 1079
 				];
1080 1080
 			}
1081 1081
 			$result->closeCursor();
@@ -1147,7 +1147,7 @@  discard block
 block discarded – undo
1147 1147
 			$calendarRow = $this->getCalendarById($calendarId);
1148 1148
 			$shares = $this->getShares($calendarId);
1149 1149
 
1150
-			$this->dispatcher->dispatchTyped(new CalendarObjectCreatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
1150
+			$this->dispatcher->dispatchTyped(new CalendarObjectCreatedEvent((int) $calendarId, $calendarRow, $shares, $objectRow));
1151 1151
 			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', new GenericEvent(
1152 1152
 				'\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject',
1153 1153
 				[
@@ -1160,7 +1160,7 @@  discard block
 block discarded – undo
1160 1160
 		} else {
1161 1161
 			$subscriptionRow = $this->getSubscriptionById($calendarId);
1162 1162
 
1163
-			$this->dispatcher->dispatchTyped(new CachedCalendarObjectCreatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
1163
+			$this->dispatcher->dispatchTyped(new CachedCalendarObjectCreatedEvent((int) $calendarId, $subscriptionRow, [], $objectRow));
1164 1164
 			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject', new GenericEvent(
1165 1165
 				'\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject',
1166 1166
 				[
@@ -1172,7 +1172,7 @@  discard block
 block discarded – undo
1172 1172
 			));
1173 1173
 		}
1174 1174
 
1175
-		return '"' . $extraData['etag'] . '"';
1175
+		return '"'.$extraData['etag'].'"';
1176 1176
 	}
1177 1177
 
1178 1178
 	/**
@@ -1221,7 +1221,7 @@  discard block
 block discarded – undo
1221 1221
 				$calendarRow = $this->getCalendarById($calendarId);
1222 1222
 				$shares = $this->getShares($calendarId);
1223 1223
 
1224
-				$this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
1224
+				$this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent((int) $calendarId, $calendarRow, $shares, $objectRow));
1225 1225
 				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', new GenericEvent(
1226 1226
 					'\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject',
1227 1227
 					[
@@ -1234,7 +1234,7 @@  discard block
 block discarded – undo
1234 1234
 			} else {
1235 1235
 				$subscriptionRow = $this->getSubscriptionById($calendarId);
1236 1236
 
1237
-				$this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
1237
+				$this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent((int) $calendarId, $subscriptionRow, [], $objectRow));
1238 1238
 				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject', new GenericEvent(
1239 1239
 					'\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject',
1240 1240
 					[
@@ -1247,7 +1247,7 @@  discard block
 block discarded – undo
1247 1247
 			}
1248 1248
 		}
1249 1249
 
1250
-		return '"' . $extraData['etag'] . '"';
1250
+		return '"'.$extraData['etag'].'"';
1251 1251
 	}
1252 1252
 
1253 1253
 	/**
@@ -1284,7 +1284,7 @@  discard block
 block discarded – undo
1284 1284
 				$calendarRow = $this->getCalendarById($calendarId);
1285 1285
 				$shares = $this->getShares($calendarId);
1286 1286
 
1287
-				$this->dispatcher->dispatchTyped(new CalendarObjectDeletedEvent((int)$calendarId, $calendarRow, $shares, $data));
1287
+				$this->dispatcher->dispatchTyped(new CalendarObjectDeletedEvent((int) $calendarId, $calendarRow, $shares, $data));
1288 1288
 				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', new GenericEvent(
1289 1289
 					'\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject',
1290 1290
 					[
@@ -1297,7 +1297,7 @@  discard block
 block discarded – undo
1297 1297
 			} else {
1298 1298
 				$subscriptionRow = $this->getSubscriptionById($calendarId);
1299 1299
 
1300
-				$this->dispatcher->dispatchTyped(new CachedCalendarObjectDeletedEvent((int)$calendarId, $subscriptionRow, [], $data));
1300
+				$this->dispatcher->dispatchTyped(new CachedCalendarObjectDeletedEvent((int) $calendarId, $subscriptionRow, [], $data));
1301 1301
 				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject', new GenericEvent(
1302 1302
 					'\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject',
1303 1303
 					[
@@ -1569,7 +1569,7 @@  discard block
 block discarded – undo
1569 1569
 
1570 1570
 		$result = [];
1571 1571
 		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1572
-			$path = $uriMapper[$row['calendarid']] . '/' . $row['uri'];
1572
+			$path = $uriMapper[$row['calendarid']].'/'.$row['uri'];
1573 1573
 			if (!in_array($path, $result)) {
1574 1574
 				$result[] = $path;
1575 1575
 			}
@@ -1617,8 +1617,8 @@  discard block
 block discarded – undo
1617 1617
 
1618 1618
 		if ($pattern !== '') {
1619 1619
 			$innerQuery->andWhere($innerQuery->expr()->iLike('op.value',
1620
-				$outerQuery->createNamedParameter('%' .
1621
-					$this->db->escapeLikeParameter($pattern) . '%')));
1620
+				$outerQuery->createNamedParameter('%'.
1621
+					$this->db->escapeLikeParameter($pattern).'%')));
1622 1622
 		}
1623 1623
 
1624 1624
 		$outerQuery->select('c.id', 'c.calendardata', 'c.componenttype', 'c.uid', 'c.uri')
@@ -1657,7 +1657,7 @@  discard block
 block discarded – undo
1657 1657
 		$result = $outerQuery->execute();
1658 1658
 		$calendarObjects = $result->fetchAll();
1659 1659
 
1660
-		return array_map(function ($o) {
1660
+		return array_map(function($o) {
1661 1661
 			$calendarData = Reader::read($o['calendardata']);
1662 1662
 			$comps = $calendarData->getComponents();
1663 1663
 			$objects = [];
@@ -1675,10 +1675,10 @@  discard block
 block discarded – undo
1675 1675
 				'type' => $o['componenttype'],
1676 1676
 				'uid' => $o['uid'],
1677 1677
 				'uri' => $o['uri'],
1678
-				'objects' => array_map(function ($c) {
1678
+				'objects' => array_map(function($c) {
1679 1679
 					return $this->transformSearchData($c);
1680 1680
 				}, $objects),
1681
-				'timezones' => array_map(function ($c) {
1681
+				'timezones' => array_map(function($c) {
1682 1682
 					return $this->transformSearchData($c);
1683 1683
 				}, $timezones),
1684 1684
 			];
@@ -1694,7 +1694,7 @@  discard block
 block discarded – undo
1694 1694
 		/** @var Component[] $subComponents */
1695 1695
 		$subComponents = $comp->getComponents();
1696 1696
 		/** @var Property[] $properties */
1697
-		$properties = array_filter($comp->children(), function ($c) {
1697
+		$properties = array_filter($comp->children(), function($c) {
1698 1698
 			return $c instanceof Property;
1699 1699
 		});
1700 1700
 		$validationRules = $comp->getValidationRules();
@@ -1772,7 +1772,7 @@  discard block
 block discarded – undo
1772 1772
 		$subscriptions = $this->getSubscriptionsForUser($principalUri);
1773 1773
 		foreach ($calendars as $calendar) {
1774 1774
 			$calendarAnd = $calendarObjectIdQuery->expr()->andX();
1775
-			$calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$calendar['id'])));
1775
+			$calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int) $calendar['id'])));
1776 1776
 			$calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1777 1777
 
1778 1778
 			// If it's shared, limit search to public events
@@ -1785,7 +1785,7 @@  discard block
 block discarded – undo
1785 1785
 		}
1786 1786
 		foreach ($subscriptions as $subscription) {
1787 1787
 			$subscriptionAnd = $calendarObjectIdQuery->expr()->andX();
1788
-			$subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$subscription['id'])));
1788
+			$subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int) $subscription['id'])));
1789 1789
 			$subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
1790 1790
 
1791 1791
 			// If it's shared, limit search to public events
@@ -1830,7 +1830,7 @@  discard block
 block discarded – undo
1830 1830
 			if (!$escapePattern) {
1831 1831
 				$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter($pattern)));
1832 1832
 			} else {
1833
-				$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
1833
+				$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter('%'.$this->db->escapeLikeParameter($pattern).'%')));
1834 1834
 			}
1835 1835
 		}
1836 1836
 
@@ -1844,7 +1844,7 @@  discard block
 block discarded – undo
1844 1844
 		$result = $calendarObjectIdQuery->execute();
1845 1845
 		$matches = $result->fetchAll();
1846 1846
 		$result->closeCursor();
1847
-		$matches = array_map(static function (array $match):int {
1847
+		$matches = array_map(static function(array $match):int {
1848 1848
 			return (int) $match['objectid'];
1849 1849
 		}, $matches);
1850 1850
 
@@ -1857,9 +1857,9 @@  discard block
 block discarded – undo
1857 1857
 		$calendarObjects = $result->fetchAll();
1858 1858
 		$result->closeCursor();
1859 1859
 
1860
-		return array_map(function (array $array): array {
1861
-			$array['calendarid'] = (int)$array['calendarid'];
1862
-			$array['calendartype'] = (int)$array['calendartype'];
1860
+		return array_map(function(array $array): array {
1861
+			$array['calendarid'] = (int) $array['calendarid'];
1862
+			$array['calendartype'] = (int) $array['calendartype'];
1863 1863
 			$array['calendardata'] = $this->readBlob($array['calendardata']);
1864 1864
 
1865 1865
 			return $array;
@@ -1896,7 +1896,7 @@  discard block
 block discarded – undo
1896 1896
 		$stmt = $query->execute();
1897 1897
 
1898 1898
 		if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1899
-			return $row['calendaruri'] . '/' . $row['objecturi'];
1899
+			return $row['calendaruri'].'/'.$row['objecturi'];
1900 1900
 		}
1901 1901
 
1902 1902
 		return null;
@@ -1962,7 +1962,7 @@  discard block
 block discarded – undo
1962 1962
 	public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1963 1963
 		// Current synctoken
1964 1964
 		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*calendars` WHERE `id` = ?');
1965
-		$stmt->execute([ $calendarId ]);
1965
+		$stmt->execute([$calendarId]);
1966 1966
 		$currentToken = $stmt->fetchOne();
1967 1967
 
1968 1968
 		if (is_null($currentToken)) {
@@ -1979,7 +1979,7 @@  discard block
 block discarded – undo
1979 1979
 		if ($syncToken) {
1980 1980
 			$query = "SELECT `uri`, `operation` FROM `*PREFIX*calendarchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `calendarid` = ? AND `calendartype` = ? ORDER BY `synctoken`";
1981 1981
 			if ($limit > 0) {
1982
-				$query .= " LIMIT " . (int)$limit;
1982
+				$query .= " LIMIT ".(int) $limit;
1983 1983
 			}
1984 1984
 
1985 1985
 			// Fetching all changes
@@ -2075,8 +2075,8 @@  discard block
 block discarded – undo
2075 2075
 				'source' => $row['source'],
2076 2076
 				'lastmodified' => $row['lastmodified'],
2077 2077
 
2078
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
2079
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
2078
+				'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
2079
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
2080 2080
 			];
2081 2081
 
2082 2082
 			foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
@@ -2140,7 +2140,7 @@  discard block
 block discarded – undo
2140 2140
 		$subscriptionId = $this->db->lastInsertId('*PREFIX*calendarsubscriptions');
2141 2141
 
2142 2142
 		$subscriptionRow = $this->getSubscriptionById($subscriptionId);
2143
-		$this->dispatcher->dispatchTyped(new SubscriptionCreatedEvent((int)$subscriptionId, $subscriptionRow));
2143
+		$this->dispatcher->dispatchTyped(new SubscriptionCreatedEvent((int) $subscriptionId, $subscriptionRow));
2144 2144
 		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createSubscription', new GenericEvent(
2145 2145
 			'\OCA\DAV\CalDAV\CalDavBackend::createSubscription',
2146 2146
 			[
@@ -2171,7 +2171,7 @@  discard block
 block discarded – undo
2171 2171
 		$supportedProperties = array_keys($this->subscriptionPropertyMap);
2172 2172
 		$supportedProperties[] = '{http://calendarserver.org/ns/}source';
2173 2173
 
2174
-		$propPatch->handle($supportedProperties, function ($mutations) use ($subscriptionId) {
2174
+		$propPatch->handle($supportedProperties, function($mutations) use ($subscriptionId) {
2175 2175
 			$newValues = [];
2176 2176
 
2177 2177
 			foreach ($mutations as $propertyName => $propertyValue) {
@@ -2193,7 +2193,7 @@  discard block
 block discarded – undo
2193 2193
 				->execute();
2194 2194
 
2195 2195
 			$subscriptionRow = $this->getSubscriptionById($subscriptionId);
2196
-			$this->dispatcher->dispatchTyped(new SubscriptionUpdatedEvent((int)$subscriptionId, $subscriptionRow, [], $mutations));
2196
+			$this->dispatcher->dispatchTyped(new SubscriptionUpdatedEvent((int) $subscriptionId, $subscriptionRow, [], $mutations));
2197 2197
 			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateSubscription', new GenericEvent(
2198 2198
 				'\OCA\DAV\CalDAV\CalDavBackend::updateSubscription',
2199 2199
 				[
@@ -2244,7 +2244,7 @@  discard block
 block discarded – undo
2244 2244
 			->execute();
2245 2245
 
2246 2246
 		if ($subscriptionRow) {
2247
-			$this->dispatcher->dispatchTyped(new SubscriptionDeletedEvent((int)$subscriptionId, $subscriptionRow, []));
2247
+			$this->dispatcher->dispatchTyped(new SubscriptionDeletedEvent((int) $subscriptionId, $subscriptionRow, []));
2248 2248
 		}
2249 2249
 	}
2250 2250
 
@@ -2282,8 +2282,8 @@  discard block
 block discarded – undo
2282 2282
 			'uri' => $row['uri'],
2283 2283
 			'calendardata' => $row['calendardata'],
2284 2284
 			'lastmodified' => $row['lastmodified'],
2285
-			'etag' => '"' . $row['etag'] . '"',
2286
-			'size' => (int)$row['size'],
2285
+			'etag' => '"'.$row['etag'].'"',
2286
+			'size' => (int) $row['size'],
2287 2287
 		];
2288 2288
 	}
2289 2289
 
@@ -2311,8 +2311,8 @@  discard block
 block discarded – undo
2311 2311
 				'calendardata' => $row['calendardata'],
2312 2312
 				'uri' => $row['uri'],
2313 2313
 				'lastmodified' => $row['lastmodified'],
2314
-				'etag' => '"' . $row['etag'] . '"',
2315
-				'size' => (int)$row['size'],
2314
+				'etag' => '"'.$row['etag'].'"',
2315
+				'size' => (int) $row['size'],
2316 2316
 			];
2317 2317
 		}
2318 2318
 
@@ -2366,14 +2366,14 @@  discard block
 block discarded – undo
2366 2366
 	 * @return void
2367 2367
 	 */
2368 2368
 	protected function addChange($calendarId, $objectUri, $operation, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2369
-		$table = $calendarType === self::CALENDAR_TYPE_CALENDAR ? 'calendars': 'calendarsubscriptions';
2369
+		$table = $calendarType === self::CALENDAR_TYPE_CALENDAR ? 'calendars' : 'calendarsubscriptions';
2370 2370
 
2371 2371
 		$query = $this->db->getQueryBuilder();
2372 2372
 		$query->select('synctoken')
2373 2373
 			->from($table)
2374 2374
 			->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
2375 2375
 		$result = $query->execute();
2376
-		$syncToken = (int)$result->fetchOne();
2376
+		$syncToken = (int) $result->fetchOne();
2377 2377
 		$result->closeCursor();
2378 2378
 
2379 2379
 		$query = $this->db->getQueryBuilder();
@@ -2430,7 +2430,7 @@  discard block
 block discarded – undo
2430 2430
 				// Track first component type and uid
2431 2431
 				if ($uid === null) {
2432 2432
 					$componentType = $component->name;
2433
-					$uid = (string)$component->UID;
2433
+					$uid = (string) $component->UID;
2434 2434
 				}
2435 2435
 			}
2436 2436
 		}
@@ -2529,7 +2529,7 @@  discard block
 block discarded – undo
2529 2529
 			]));
2530 2530
 		$this->calendarSharingBackend->updateShares($shareable, $add, $remove);
2531 2531
 
2532
-		$this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent((int)$calendarId, $calendarRow, $oldShares, $add, $remove));
2532
+		$this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent((int) $calendarId, $calendarRow, $oldShares, $add, $remove));
2533 2533
 	}
2534 2534
 
2535 2535
 	/**
@@ -2570,7 +2570,7 @@  discard block
 block discarded – undo
2570 2570
 				]);
2571 2571
 			$query->execute();
2572 2572
 
2573
-			$this->dispatcher->dispatchTyped(new CalendarPublishedEvent((int)$calendarId, $calendarData, $publicUri));
2573
+			$this->dispatcher->dispatchTyped(new CalendarPublishedEvent((int) $calendarId, $calendarData, $publicUri));
2574 2574
 			return $publicUri;
2575 2575
 		}
2576 2576
 		$query->delete('dav_shares')
@@ -2578,7 +2578,7 @@  discard block
 block discarded – undo
2578 2578
 			->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)));
2579 2579
 		$query->execute();
2580 2580
 
2581
-		$this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent((int)$calendarId, $calendarData));
2581
+		$this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent((int) $calendarId, $calendarData));
2582 2582
 		return null;
2583 2583
 	}
2584 2584
 
@@ -2801,10 +2801,10 @@  discard block
 block discarded – undo
2801 2801
 		$result->closeCursor();
2802 2802
 
2803 2803
 		if (!isset($objectIds['id'])) {
2804
-			throw new \InvalidArgumentException('Calendarobject does not exists: ' . $uri);
2804
+			throw new \InvalidArgumentException('Calendarobject does not exists: '.$uri);
2805 2805
 		}
2806 2806
 
2807
-		return (int)$objectIds['id'];
2807
+		return (int) $objectIds['id'];
2808 2808
 	}
2809 2809
 
2810 2810
 	/**
@@ -2831,8 +2831,8 @@  discard block
 block discarded – undo
2831 2831
 	 * @param $calendarInfo
2832 2832
 	 */
2833 2833
 	private function addOwnerPrincipal(&$calendarInfo) {
2834
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
2835
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
2834
+		$ownerPrincipalKey = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal';
2835
+		$displaynameKey = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD.'}owner-displayname';
2836 2836
 		if (isset($calendarInfo[$ownerPrincipalKey])) {
2837 2837
 			$uri = $calendarInfo[$ownerPrincipalKey];
2838 2838
 		} else {
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
@@ -34,59 +34,59 @@
 block discarded – undo
34 34
 
35 35
 class BuildCalendarSearchIndex implements IRepairStep {
36 36
 
37
-	/** @var IDBConnection */
38
-	private $db;
37
+    /** @var IDBConnection */
38
+    private $db;
39 39
 
40
-	/** @var IJobList */
41
-	private $jobList;
40
+    /** @var IJobList */
41
+    private $jobList;
42 42
 
43
-	/** @var IConfig */
44
-	private $config;
43
+    /** @var IConfig */
44
+    private $config;
45 45
 
46
-	/**
47
-	 * @param IDBConnection $db
48
-	 * @param IJobList $jobList
49
-	 * @param IConfig $config
50
-	 */
51
-	public function __construct(IDBConnection $db,
52
-								IJobList $jobList,
53
-								IConfig $config) {
54
-		$this->db = $db;
55
-		$this->jobList = $jobList;
56
-		$this->config = $config;
57
-	}
46
+    /**
47
+     * @param IDBConnection $db
48
+     * @param IJobList $jobList
49
+     * @param IConfig $config
50
+     */
51
+    public function __construct(IDBConnection $db,
52
+                                IJobList $jobList,
53
+                                IConfig $config) {
54
+        $this->db = $db;
55
+        $this->jobList = $jobList;
56
+        $this->config = $config;
57
+    }
58 58
 
59
-	/**
60
-	 * @return string
61
-	 */
62
-	public function getName() {
63
-		return 'Registering building of calendar search index as background job';
64
-	}
59
+    /**
60
+     * @return string
61
+     */
62
+    public function getName() {
63
+        return 'Registering building of calendar search index as background job';
64
+    }
65 65
 
66
-	/**
67
-	 * @param IOutput $output
68
-	 */
69
-	public function run(IOutput $output) {
70
-		// only run once
71
-		if ($this->config->getAppValue('dav', 'buildCalendarSearchIndex') === 'yes') {
72
-			$output->info('Repair step already executed');
73
-			return;
74
-		}
66
+    /**
67
+     * @param IOutput $output
68
+     */
69
+    public function run(IOutput $output) {
70
+        // only run once
71
+        if ($this->config->getAppValue('dav', 'buildCalendarSearchIndex') === 'yes') {
72
+            $output->info('Repair step already executed');
73
+            return;
74
+        }
75 75
 
76
-		$query = $this->db->getQueryBuilder();
77
-		$query->select($query->createFunction('MAX(' . $query->getColumnName('id') . ')'))
78
-			->from('calendarobjects');
79
-		$result = $query->execute();
80
-		$maxId = (int) $result->fetchOne();
81
-		$result->closeCursor();
76
+        $query = $this->db->getQueryBuilder();
77
+        $query->select($query->createFunction('MAX(' . $query->getColumnName('id') . ')'))
78
+            ->from('calendarobjects');
79
+        $result = $query->execute();
80
+        $maxId = (int) $result->fetchOne();
81
+        $result->closeCursor();
82 82
 
83
-		$output->info('Add background job');
84
-		$this->jobList->add(BuildCalendarSearchIndexBackgroundJob::class, [
85
-			'offset' => 0,
86
-			'stopAt' => $maxId
87
-		]);
83
+        $output->info('Add background job');
84
+        $this->jobList->add(BuildCalendarSearchIndexBackgroundJob::class, [
85
+            'offset' => 0,
86
+            'stopAt' => $maxId
87
+        ]);
88 88
 
89
-		// if all were done, no need to redo the repair during next upgrade
90
-		$this->config->setAppValue('dav', 'buildCalendarSearchIndex', 'yes');
91
-	}
89
+        // if all were done, no need to redo the repair during next upgrade
90
+        $this->config->setAppValue('dav', 'buildCalendarSearchIndex', 'yes');
91
+    }
92 92
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -74,7 +74,7 @@
 block discarded – undo
74 74
 		}
75 75
 
76 76
 		$query = $this->db->getQueryBuilder();
77
-		$query->select($query->createFunction('MAX(' . $query->getColumnName('id') . ')'))
77
+		$query->select($query->createFunction('MAX('.$query->getColumnName('id').')'))
78 78
 			->from('calendarobjects');
79 79
 		$result = $query->execute();
80 80
 		$maxId = (int) $result->fetchOne();
Please login to merge, or discard this patch.
apps/dav/lib/Migration/CalDAVRemoveEmptyValue.php 1 patch
Indentation   +114 added lines, -114 removed lines patch added patch discarded remove patch
@@ -35,118 +35,118 @@
 block discarded – undo
35 35
 
36 36
 class CalDAVRemoveEmptyValue implements IRepairStep {
37 37
 
38
-	/** @var IDBConnection */
39
-	private $db;
40
-
41
-	/** @var CalDavBackend */
42
-	private $calDavBackend;
43
-
44
-	/** @var ILogger */
45
-	private $logger;
46
-
47
-	/**
48
-	 * @param IDBConnection $db
49
-	 * @param CalDavBackend $calDavBackend
50
-	 * @param ILogger $logger
51
-	 */
52
-	public function __construct(IDBConnection $db, CalDavBackend $calDavBackend, ILogger $logger) {
53
-		$this->db = $db;
54
-		$this->calDavBackend = $calDavBackend;
55
-		$this->logger = $logger;
56
-	}
57
-
58
-	public function getName() {
59
-		return 'Fix broken values of calendar objects';
60
-	}
61
-
62
-	public function run(IOutput $output) {
63
-		$pattern = ';VALUE=:';
64
-		$count = $warnings = 0;
65
-
66
-		$objects = $this->getInvalidObjects($pattern);
67
-
68
-		$output->startProgress(count($objects));
69
-		foreach ($objects as $row) {
70
-			$calObject = $this->calDavBackend->getCalendarObject((int)$row['calendarid'], $row['uri']);
71
-			$data = preg_replace('/' . $pattern . '/', ':', $calObject['calendardata']);
72
-
73
-			if ($data !== $calObject['calendardata']) {
74
-				$output->advance();
75
-
76
-				try {
77
-					$this->calDavBackend->getDenormalizedData($data);
78
-				} catch (InvalidDataException $e) {
79
-					$this->logger->info('Calendar object for calendar {cal} with uri {uri} still invalid', [
80
-						'app' => 'dav',
81
-						'cal' => (int)$row['calendarid'],
82
-						'uri' => $row['uri'],
83
-					]);
84
-					$warnings++;
85
-					continue;
86
-				}
87
-
88
-				$this->calDavBackend->updateCalendarObject((int)$row['calendarid'], $row['uri'], $data);
89
-				$count++;
90
-			}
91
-		}
92
-		$output->finishProgress();
93
-
94
-		if ($warnings > 0) {
95
-			$output->warning(sprintf('%d events could not be updated, see log file for more information', $warnings));
96
-		}
97
-		if ($count > 0) {
98
-			$output->info(sprintf('Updated %d events', $count));
99
-		}
100
-	}
101
-
102
-	protected function getInvalidObjects($pattern) {
103
-		if ($this->db->getDatabasePlatform() instanceof OraclePlatform) {
104
-			$rows = [];
105
-			$chunkSize = 500;
106
-			$query = $this->db->getQueryBuilder();
107
-			$query->select($query->func()->count('*', 'num_entries'))
108
-				->from('calendarobjects');
109
-			$result = $query->execute();
110
-			$count = $result->fetchOne();
111
-			$result->closeCursor();
112
-
113
-			$numChunks = ceil($count / $chunkSize);
114
-
115
-			$query = $this->db->getQueryBuilder();
116
-			$query->select(['calendarid', 'uri', 'calendardata'])
117
-				->from('calendarobjects')
118
-				->setMaxResults($chunkSize);
119
-			for ($chunk = 0; $chunk < $numChunks; $chunk++) {
120
-				$query->setFirstResult($chunk * $chunkSize);
121
-				$result = $query->execute();
122
-
123
-				while ($row = $result->fetch()) {
124
-					if (mb_strpos($row['calendardata'], $pattern) !== false) {
125
-						unset($row['calendardata']);
126
-						$rows[] = $row;
127
-					}
128
-				}
129
-				$result->closeCursor();
130
-			}
131
-			return $rows;
132
-		}
133
-
134
-		$query = $this->db->getQueryBuilder();
135
-		$query->select(['calendarid', 'uri'])
136
-			->from('calendarobjects')
137
-			->where($query->expr()->like(
138
-				'calendardata',
139
-				$query->createNamedParameter(
140
-					'%' . $this->db->escapeLikeParameter($pattern) . '%',
141
-					IQueryBuilder::PARAM_STR
142
-				),
143
-				IQueryBuilder::PARAM_STR
144
-			));
145
-
146
-		$result = $query->execute();
147
-		$rows = $result->fetchAll();
148
-		$result->closeCursor();
149
-
150
-		return $rows;
151
-	}
38
+    /** @var IDBConnection */
39
+    private $db;
40
+
41
+    /** @var CalDavBackend */
42
+    private $calDavBackend;
43
+
44
+    /** @var ILogger */
45
+    private $logger;
46
+
47
+    /**
48
+     * @param IDBConnection $db
49
+     * @param CalDavBackend $calDavBackend
50
+     * @param ILogger $logger
51
+     */
52
+    public function __construct(IDBConnection $db, CalDavBackend $calDavBackend, ILogger $logger) {
53
+        $this->db = $db;
54
+        $this->calDavBackend = $calDavBackend;
55
+        $this->logger = $logger;
56
+    }
57
+
58
+    public function getName() {
59
+        return 'Fix broken values of calendar objects';
60
+    }
61
+
62
+    public function run(IOutput $output) {
63
+        $pattern = ';VALUE=:';
64
+        $count = $warnings = 0;
65
+
66
+        $objects = $this->getInvalidObjects($pattern);
67
+
68
+        $output->startProgress(count($objects));
69
+        foreach ($objects as $row) {
70
+            $calObject = $this->calDavBackend->getCalendarObject((int)$row['calendarid'], $row['uri']);
71
+            $data = preg_replace('/' . $pattern . '/', ':', $calObject['calendardata']);
72
+
73
+            if ($data !== $calObject['calendardata']) {
74
+                $output->advance();
75
+
76
+                try {
77
+                    $this->calDavBackend->getDenormalizedData($data);
78
+                } catch (InvalidDataException $e) {
79
+                    $this->logger->info('Calendar object for calendar {cal} with uri {uri} still invalid', [
80
+                        'app' => 'dav',
81
+                        'cal' => (int)$row['calendarid'],
82
+                        'uri' => $row['uri'],
83
+                    ]);
84
+                    $warnings++;
85
+                    continue;
86
+                }
87
+
88
+                $this->calDavBackend->updateCalendarObject((int)$row['calendarid'], $row['uri'], $data);
89
+                $count++;
90
+            }
91
+        }
92
+        $output->finishProgress();
93
+
94
+        if ($warnings > 0) {
95
+            $output->warning(sprintf('%d events could not be updated, see log file for more information', $warnings));
96
+        }
97
+        if ($count > 0) {
98
+            $output->info(sprintf('Updated %d events', $count));
99
+        }
100
+    }
101
+
102
+    protected function getInvalidObjects($pattern) {
103
+        if ($this->db->getDatabasePlatform() instanceof OraclePlatform) {
104
+            $rows = [];
105
+            $chunkSize = 500;
106
+            $query = $this->db->getQueryBuilder();
107
+            $query->select($query->func()->count('*', 'num_entries'))
108
+                ->from('calendarobjects');
109
+            $result = $query->execute();
110
+            $count = $result->fetchOne();
111
+            $result->closeCursor();
112
+
113
+            $numChunks = ceil($count / $chunkSize);
114
+
115
+            $query = $this->db->getQueryBuilder();
116
+            $query->select(['calendarid', 'uri', 'calendardata'])
117
+                ->from('calendarobjects')
118
+                ->setMaxResults($chunkSize);
119
+            for ($chunk = 0; $chunk < $numChunks; $chunk++) {
120
+                $query->setFirstResult($chunk * $chunkSize);
121
+                $result = $query->execute();
122
+
123
+                while ($row = $result->fetch()) {
124
+                    if (mb_strpos($row['calendardata'], $pattern) !== false) {
125
+                        unset($row['calendardata']);
126
+                        $rows[] = $row;
127
+                    }
128
+                }
129
+                $result->closeCursor();
130
+            }
131
+            return $rows;
132
+        }
133
+
134
+        $query = $this->db->getQueryBuilder();
135
+        $query->select(['calendarid', 'uri'])
136
+            ->from('calendarobjects')
137
+            ->where($query->expr()->like(
138
+                'calendardata',
139
+                $query->createNamedParameter(
140
+                    '%' . $this->db->escapeLikeParameter($pattern) . '%',
141
+                    IQueryBuilder::PARAM_STR
142
+                ),
143
+                IQueryBuilder::PARAM_STR
144
+            ));
145
+
146
+        $result = $query->execute();
147
+        $rows = $result->fetchAll();
148
+        $result->closeCursor();
149
+
150
+        return $rows;
151
+    }
152 152
 }
Please login to merge, or discard this patch.
apps/dav/lib/Migration/BuildSocialSearchIndex.php 2 patches
Indentation   +49 added lines, -49 removed lines patch added patch discarded remove patch
@@ -31,62 +31,62 @@
 block discarded – undo
31 31
 
32 32
 class BuildSocialSearchIndex implements IRepairStep {
33 33
 
34
-	/** @var IDBConnection */
35
-	private $db;
34
+    /** @var IDBConnection */
35
+    private $db;
36 36
 
37
-	/** @var IJobList */
38
-	private $jobList;
37
+    /** @var IJobList */
38
+    private $jobList;
39 39
 
40
-	/** @var IConfig */
41
-	private $config;
40
+    /** @var IConfig */
41
+    private $config;
42 42
 
43
-	/**
44
-	 * @param IDBConnection $db
45
-	 * @param IJobList $jobList
46
-	 * @param IConfig $config
47
-	 */
48
-	public function __construct(IDBConnection $db,
49
-								IJobList $jobList,
50
-								IConfig $config) {
51
-		$this->db = $db;
52
-		$this->jobList = $jobList;
53
-		$this->config = $config;
54
-	}
43
+    /**
44
+     * @param IDBConnection $db
45
+     * @param IJobList $jobList
46
+     * @param IConfig $config
47
+     */
48
+    public function __construct(IDBConnection $db,
49
+                                IJobList $jobList,
50
+                                IConfig $config) {
51
+        $this->db = $db;
52
+        $this->jobList = $jobList;
53
+        $this->config = $config;
54
+    }
55 55
 
56
-	/**
57
-	 * @return string
58
-	 */
59
-	public function getName() {
60
-		return 'Register building of social profile search index as background job';
61
-	}
56
+    /**
57
+     * @return string
58
+     */
59
+    public function getName() {
60
+        return 'Register building of social profile search index as background job';
61
+    }
62 62
 
63
-	/**
64
-	 * @param IOutput $output
65
-	 */
66
-	public function run(IOutput $output) {
67
-		// only run once
68
-		if ($this->config->getAppValue('dav', 'builtSocialSearchIndex') === 'yes') {
69
-			$output->info('Repair step already executed');
70
-			return;
71
-		}
63
+    /**
64
+     * @param IOutput $output
65
+     */
66
+    public function run(IOutput $output) {
67
+        // only run once
68
+        if ($this->config->getAppValue('dav', 'builtSocialSearchIndex') === 'yes') {
69
+            $output->info('Repair step already executed');
70
+            return;
71
+        }
72 72
 
73
-		$query = $this->db->getQueryBuilder();
74
-		$query->select($query->func()->max('cardid'))
75
-			->from('cards_properties')
76
-			->where($query->expr()->eq('name', $query->createNamedParameter('X-SOCIALPROFILE')));
77
-		$maxId = (int)$query->execute()->fetchOne();
73
+        $query = $this->db->getQueryBuilder();
74
+        $query->select($query->func()->max('cardid'))
75
+            ->from('cards_properties')
76
+            ->where($query->expr()->eq('name', $query->createNamedParameter('X-SOCIALPROFILE')));
77
+        $maxId = (int)$query->execute()->fetchOne();
78 78
 
79
-		if ($maxId === 0) {
80
-			return;
81
-		}
79
+        if ($maxId === 0) {
80
+            return;
81
+        }
82 82
 
83
-		$output->info('Add background job');
84
-		$this->jobList->add(BuildSocialSearchIndexBackgroundJob::class, [
85
-			'offset' => 0,
86
-			'stopAt' => $maxId
87
-		]);
83
+        $output->info('Add background job');
84
+        $this->jobList->add(BuildSocialSearchIndexBackgroundJob::class, [
85
+            'offset' => 0,
86
+            'stopAt' => $maxId
87
+        ]);
88 88
 
89
-		// no need to redo the repair during next upgrade
90
-		$this->config->setAppValue('dav', 'builtSocialSearchIndex', 'yes');
91
-	}
89
+        // no need to redo the repair during next upgrade
90
+        $this->config->setAppValue('dav', 'builtSocialSearchIndex', 'yes');
91
+    }
92 92
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -74,7 +74,7 @@
 block discarded – undo
74 74
 		$query->select($query->func()->max('cardid'))
75 75
 			->from('cards_properties')
76 76
 			->where($query->expr()->eq('name', $query->createNamedParameter('X-SOCIALPROFILE')));
77
-		$maxId = (int)$query->execute()->fetchOne();
77
+		$maxId = (int) $query->execute()->fetchOne();
78 78
 
79 79
 		if ($maxId === 0) {
80 80
 			return;
Please login to merge, or discard this patch.