Completed
Pull Request — master (#4401)
by Georg
23:26
created
apps/dav/lib/DAV/Sharing/Plugin.php 1 patch
Indentation   +162 added lines, -162 removed lines patch added patch discarded remove patch
@@ -36,167 +36,167 @@
 block discarded – undo
36 36
 
37 37
 class Plugin extends ServerPlugin {
38 38
 
39
-	const NS_OWNCLOUD = 'http://owncloud.org/ns';
40
-	const NS_NEXTCLOUD = 'http://nextcloud.com/ns';
41
-
42
-	/** @var Auth */
43
-	private $auth;
44
-
45
-	/** @var IRequest */
46
-	private $request;
47
-
48
-	/**
49
-	 * Plugin constructor.
50
-	 *
51
-	 * @param Auth $authBackEnd
52
-	 * @param IRequest $request
53
-	 */
54
-	public function __construct(Auth $authBackEnd, IRequest $request) {
55
-		$this->auth = $authBackEnd;
56
-		$this->request = $request;
57
-	}
58
-
59
-	/**
60
-	 * Reference to SabreDAV server object.
61
-	 *
62
-	 * @var \Sabre\DAV\Server
63
-	 */
64
-	protected $server;
65
-
66
-	/**
67
-	 * This method should return a list of server-features.
68
-	 *
69
-	 * This is for example 'versioning' and is added to the DAV: header
70
-	 * in an OPTIONS response.
71
-	 *
72
-	 * @return string[]
73
-	 */
74
-	function getFeatures() {
75
-		return ['oc-resource-sharing'];
76
-	}
77
-
78
-	/**
79
-	 * Returns a plugin name.
80
-	 *
81
-	 * Using this name other plugins will be able to access other plugins
82
-	 * using Sabre\DAV\Server::getPlugin
83
-	 *
84
-	 * @return string
85
-	 */
86
-	function getPluginName() {
87
-		return 'oc-resource-sharing';
88
-	}
89
-
90
-	/**
91
-	 * This initializes the plugin.
92
-	 *
93
-	 * This function is called by Sabre\DAV\Server, after
94
-	 * addPlugin is called.
95
-	 *
96
-	 * This method should set up the required event subscriptions.
97
-	 *
98
-	 * @param Server $server
99
-	 * @return void
100
-	 */
101
-	function initialize(Server $server) {
102
-		$this->server = $server;
103
-		$this->server->xml->elementMap['{' . Plugin::NS_OWNCLOUD . '}share'] = 'OCA\\DAV\\DAV\\Sharing\\Xml\\ShareRequest';
104
-		$this->server->xml->elementMap['{' . Plugin::NS_OWNCLOUD . '}invite'] = 'OCA\\DAV\\DAV\\Sharing\\Xml\\Invite';
105
-
106
-		$this->server->on('method:POST', [$this, 'httpPost']);
107
-		$this->server->on('propFind',    [$this, 'propFind']);
108
-	}
109
-
110
-	/**
111
-	 * We intercept this to handle POST requests on a dav resource.
112
-	 *
113
-	 * @param RequestInterface $request
114
-	 * @param ResponseInterface $response
115
-	 * @return null|false
116
-	 */
117
-	function httpPost(RequestInterface $request, ResponseInterface $response) {
118
-
119
-		$path = $request->getPath();
120
-
121
-		// Only handling xml
122
-		$contentType = $request->getHeader('Content-Type');
123
-		if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false)
124
-			return;
125
-
126
-		// Making sure the node exists
127
-		try {
128
-			$node = $this->server->tree->getNodeForPath($path);
129
-		} catch (NotFound $e) {
130
-			return;
131
-		}
132
-
133
-		$requestBody = $request->getBodyAsString();
134
-
135
-		// If this request handler could not deal with this POST request, it
136
-		// will return 'null' and other plugins get a chance to handle the
137
-		// request.
138
-		//
139
-		// However, we already requested the full body. This is a problem,
140
-		// because a body can only be read once. This is why we preemptively
141
-		// re-populated the request body with the existing data.
142
-		$request->setBody($requestBody);
143
-
144
-		$message = $this->server->xml->parse($requestBody, $request->getUrl(), $documentType);
145
-
146
-		switch ($documentType) {
147
-
148
-			// Dealing with the 'share' document, which modified invitees on a
149
-			// calendar.
150
-			case '{' . self::NS_OWNCLOUD . '}share' :
151
-
152
-				// We can only deal with IShareableCalendar objects
153
-				if (!$node instanceof IShareable) {
154
-					return;
155
-				}
156
-
157
-				$this->server->transactionType = 'post-oc-resource-share';
158
-
159
-				// Getting ACL info
160
-				$acl = $this->server->getPlugin('acl');
161
-
162
-				// If there's no ACL support, we allow everything
163
-				if ($acl) {
164
-					/** @var \Sabre\DAVACL\Plugin $acl */
165
-					$acl->checkPrivileges($path, '{DAV:}write');
166
-				}
167
-
168
-				$node->updateShares($message->set, $message->remove);
169
-
170
-				$response->setStatus(200);
171
-				// Adding this because sending a response body may cause issues,
172
-				// and I wanted some type of indicator the response was handled.
173
-				$response->setHeader('X-Sabre-Status', 'everything-went-well');
174
-
175
-				// Breaking the event chain
176
-				return false;
177
-		}
178
-	}
179
-
180
-	/**
181
-	 * This event is triggered when properties are requested for a certain
182
-	 * node.
183
-	 *
184
-	 * This allows us to inject any properties early.
185
-	 *
186
-	 * @param PropFind $propFind
187
-	 * @param INode $node
188
-	 * @return void
189
-	 */
190
-	function propFind(PropFind $propFind, INode $node) {
191
-		if ($node instanceof IShareable) {
192
-
193
-			$propFind->handle('{' . Plugin::NS_OWNCLOUD . '}invite', function() use ($node) {
194
-				return new Invite(
195
-					$node->getShares()
196
-				);
197
-			});
198
-
199
-		}
200
-	}
39
+    const NS_OWNCLOUD = 'http://owncloud.org/ns';
40
+    const NS_NEXTCLOUD = 'http://nextcloud.com/ns';
41
+
42
+    /** @var Auth */
43
+    private $auth;
44
+
45
+    /** @var IRequest */
46
+    private $request;
47
+
48
+    /**
49
+     * Plugin constructor.
50
+     *
51
+     * @param Auth $authBackEnd
52
+     * @param IRequest $request
53
+     */
54
+    public function __construct(Auth $authBackEnd, IRequest $request) {
55
+        $this->auth = $authBackEnd;
56
+        $this->request = $request;
57
+    }
58
+
59
+    /**
60
+     * Reference to SabreDAV server object.
61
+     *
62
+     * @var \Sabre\DAV\Server
63
+     */
64
+    protected $server;
65
+
66
+    /**
67
+     * This method should return a list of server-features.
68
+     *
69
+     * This is for example 'versioning' and is added to the DAV: header
70
+     * in an OPTIONS response.
71
+     *
72
+     * @return string[]
73
+     */
74
+    function getFeatures() {
75
+        return ['oc-resource-sharing'];
76
+    }
77
+
78
+    /**
79
+     * Returns a plugin name.
80
+     *
81
+     * Using this name other plugins will be able to access other plugins
82
+     * using Sabre\DAV\Server::getPlugin
83
+     *
84
+     * @return string
85
+     */
86
+    function getPluginName() {
87
+        return 'oc-resource-sharing';
88
+    }
89
+
90
+    /**
91
+     * This initializes the plugin.
92
+     *
93
+     * This function is called by Sabre\DAV\Server, after
94
+     * addPlugin is called.
95
+     *
96
+     * This method should set up the required event subscriptions.
97
+     *
98
+     * @param Server $server
99
+     * @return void
100
+     */
101
+    function initialize(Server $server) {
102
+        $this->server = $server;
103
+        $this->server->xml->elementMap['{' . Plugin::NS_OWNCLOUD . '}share'] = 'OCA\\DAV\\DAV\\Sharing\\Xml\\ShareRequest';
104
+        $this->server->xml->elementMap['{' . Plugin::NS_OWNCLOUD . '}invite'] = 'OCA\\DAV\\DAV\\Sharing\\Xml\\Invite';
105
+
106
+        $this->server->on('method:POST', [$this, 'httpPost']);
107
+        $this->server->on('propFind',    [$this, 'propFind']);
108
+    }
109
+
110
+    /**
111
+     * We intercept this to handle POST requests on a dav resource.
112
+     *
113
+     * @param RequestInterface $request
114
+     * @param ResponseInterface $response
115
+     * @return null|false
116
+     */
117
+    function httpPost(RequestInterface $request, ResponseInterface $response) {
118
+
119
+        $path = $request->getPath();
120
+
121
+        // Only handling xml
122
+        $contentType = $request->getHeader('Content-Type');
123
+        if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false)
124
+            return;
125
+
126
+        // Making sure the node exists
127
+        try {
128
+            $node = $this->server->tree->getNodeForPath($path);
129
+        } catch (NotFound $e) {
130
+            return;
131
+        }
132
+
133
+        $requestBody = $request->getBodyAsString();
134
+
135
+        // If this request handler could not deal with this POST request, it
136
+        // will return 'null' and other plugins get a chance to handle the
137
+        // request.
138
+        //
139
+        // However, we already requested the full body. This is a problem,
140
+        // because a body can only be read once. This is why we preemptively
141
+        // re-populated the request body with the existing data.
142
+        $request->setBody($requestBody);
143
+
144
+        $message = $this->server->xml->parse($requestBody, $request->getUrl(), $documentType);
145
+
146
+        switch ($documentType) {
147
+
148
+            // Dealing with the 'share' document, which modified invitees on a
149
+            // calendar.
150
+            case '{' . self::NS_OWNCLOUD . '}share' :
151
+
152
+                // We can only deal with IShareableCalendar objects
153
+                if (!$node instanceof IShareable) {
154
+                    return;
155
+                }
156
+
157
+                $this->server->transactionType = 'post-oc-resource-share';
158
+
159
+                // Getting ACL info
160
+                $acl = $this->server->getPlugin('acl');
161
+
162
+                // If there's no ACL support, we allow everything
163
+                if ($acl) {
164
+                    /** @var \Sabre\DAVACL\Plugin $acl */
165
+                    $acl->checkPrivileges($path, '{DAV:}write');
166
+                }
167
+
168
+                $node->updateShares($message->set, $message->remove);
169
+
170
+                $response->setStatus(200);
171
+                // Adding this because sending a response body may cause issues,
172
+                // and I wanted some type of indicator the response was handled.
173
+                $response->setHeader('X-Sabre-Status', 'everything-went-well');
174
+
175
+                // Breaking the event chain
176
+                return false;
177
+        }
178
+    }
179
+
180
+    /**
181
+     * This event is triggered when properties are requested for a certain
182
+     * node.
183
+     *
184
+     * This allows us to inject any properties early.
185
+     *
186
+     * @param PropFind $propFind
187
+     * @param INode $node
188
+     * @return void
189
+     */
190
+    function propFind(PropFind $propFind, INode $node) {
191
+        if ($node instanceof IShareable) {
192
+
193
+            $propFind->handle('{' . Plugin::NS_OWNCLOUD . '}invite', function() use ($node) {
194
+                return new Invite(
195
+                    $node->getShares()
196
+                );
197
+            });
198
+
199
+        }
200
+    }
201 201
 
202 202
 }
Please login to merge, or discard this patch.
apps/dav/lib/CardDAV/CardDavBackend.php 2 patches
Indentation   +1068 added lines, -1068 removed lines patch added patch discarded remove patch
@@ -48,1072 +48,1072 @@
 block discarded – undo
48 48
 
49 49
 class CardDavBackend implements BackendInterface, SyncSupport {
50 50
 
51
-	const PERSONAL_ADDRESSBOOK_URI = 'contacts';
52
-	const PERSONAL_ADDRESSBOOK_NAME = 'Contacts';
53
-
54
-	/** @var Principal */
55
-	private $principalBackend;
56
-
57
-	/** @var string */
58
-	private $dbCardsTable = 'cards';
59
-
60
-	/** @var string */
61
-	private $dbCardsPropertiesTable = 'cards_properties';
62
-
63
-	/** @var IDBConnection */
64
-	private $db;
65
-
66
-	/** @var Backend */
67
-	private $sharingBackend;
68
-
69
-	/** @var array properties to index */
70
-	public static $indexProperties = array(
71
-			'BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME',
72
-			'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO', 'CLOUD');
73
-
74
-	/**
75
-	 * @var string[] Map of uid => display name
76
-	 */
77
-	protected $userDisplayNames;
78
-
79
-	/** @var IUserManager */
80
-	private $userManager;
81
-
82
-	/** @var EventDispatcherInterface */
83
-	private $dispatcher;
84
-
85
-	/**
86
-	 * CardDavBackend constructor.
87
-	 *
88
-	 * @param IDBConnection $db
89
-	 * @param Principal $principalBackend
90
-	 * @param IUserManager $userManager
91
-	 * @param EventDispatcherInterface $dispatcher
92
-	 */
93
-	public function __construct(IDBConnection $db,
94
-								Principal $principalBackend,
95
-								IUserManager $userManager,
96
-								EventDispatcherInterface $dispatcher = null) {
97
-		$this->db = $db;
98
-		$this->principalBackend = $principalBackend;
99
-		$this->userManager = $userManager;
100
-		$this->dispatcher = $dispatcher;
101
-		$this->sharingBackend = new Backend($this->db, $principalBackend, 'addressbook');
102
-	}
103
-
104
-	/**
105
-	 * Return the number of address books for a principal
106
-	 *
107
-	 * @param $principalUri
108
-	 * @return int
109
-	 */
110
-	public function getAddressBooksForUserCount($principalUri) {
111
-		$principalUri = $this->convertPrincipal($principalUri, true);
112
-		$query = $this->db->getQueryBuilder();
113
-		$query->select($query->createFunction('COUNT(*)'))
114
-			->from('addressbooks')
115
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
116
-
117
-		return (int)$query->execute()->fetchColumn();
118
-	}
119
-
120
-	/**
121
-	 * Returns the list of address books for a specific user.
122
-	 *
123
-	 * Every addressbook should have the following properties:
124
-	 *   id - an arbitrary unique id
125
-	 *   uri - the 'basename' part of the url
126
-	 *   principaluri - Same as the passed parameter
127
-	 *
128
-	 * Any additional clark-notation property may be passed besides this. Some
129
-	 * common ones are :
130
-	 *   {DAV:}displayname
131
-	 *   {urn:ietf:params:xml:ns:carddav}addressbook-description
132
-	 *   {http://calendarserver.org/ns/}getctag
133
-	 *
134
-	 * @param string $principalUri
135
-	 * @return array
136
-	 */
137
-	function getAddressBooksForUser($principalUri) {
138
-		$principalUriOriginal = $principalUri;
139
-		$principalUri = $this->convertPrincipal($principalUri, true);
140
-		$query = $this->db->getQueryBuilder();
141
-		$query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
142
-			->from('addressbooks')
143
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
144
-
145
-		$addressBooks = [];
146
-
147
-		$result = $query->execute();
148
-		while($row = $result->fetch()) {
149
-			$addressBooks[$row['id']] = [
150
-				'id'  => $row['id'],
151
-				'uri' => $row['uri'],
152
-				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
153
-				'{DAV:}displayname' => $row['displayname'],
154
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
155
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
156
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
157
-			];
158
-
159
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
160
-		}
161
-		$result->closeCursor();
162
-
163
-		// query for shared calendars
164
-		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
165
-		$principals[]= $principalUri;
166
-
167
-		$query = $this->db->getQueryBuilder();
168
-		$result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
169
-			->from('dav_shares', 's')
170
-			->join('s', 'addressbooks', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
171
-			->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
172
-			->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
173
-			->setParameter('type', 'addressbook')
174
-			->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
175
-			->execute();
176
-
177
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
178
-		while($row = $result->fetch()) {
179
-			if ($row['principaluri'] === $principalUri) {
180
-				continue;
181
-			}
182
-
183
-			$readOnly = (int) $row['access'] === Backend::ACCESS_READ;
184
-			if (isset($addressBooks[$row['id']])) {
185
-				if ($readOnly) {
186
-					// New share can not have more permissions then the old one.
187
-					continue;
188
-				}
189
-				if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) &&
190
-					$addressBooks[$row['id']][$readOnlyPropertyName] === 0) {
191
-					// Old share is already read-write, no more permissions can be gained
192
-					continue;
193
-				}
194
-			}
195
-
196
-			list(, $name) = URLUtil::splitPath($row['principaluri']);
197
-			$uri = $row['uri'] . '_shared_by_' . $name;
198
-			$displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
199
-
200
-			$addressBooks[$row['id']] = [
201
-				'id'  => $row['id'],
202
-				'uri' => $uri,
203
-				'principaluri' => $principalUriOriginal,
204
-				'{DAV:}displayname' => $displayName,
205
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
206
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
207
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
208
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
209
-				$readOnlyPropertyName => $readOnly,
210
-			];
211
-
212
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
213
-		}
214
-		$result->closeCursor();
215
-
216
-		return array_values($addressBooks);
217
-	}
218
-
219
-	public function getUsersOwnAddressBooks($principalUri) {
220
-		$principalUriOriginal = $principalUri;
221
-		$principalUri = $this->convertPrincipal($principalUri, true);
222
-		$query = $this->db->getQueryBuilder();
223
-		$query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
224
-			  ->from('addressbooks')
225
-			  ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
226
-
227
-		$addressBooks = [];
228
-
229
-		$result = $query->execute();
230
-		while($row = $result->fetch()) {
231
-			$addressBooks[$row['id']] = [
232
-				'id'  => $row['id'],
233
-				'uri' => $row['uri'],
234
-				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
235
-				'{DAV:}displayname' => $row['displayname'],
236
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
237
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
238
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
239
-			];
240
-
241
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
242
-		}
243
-		$result->closeCursor();
244
-
245
-		return array_values($addressBooks);
246
-	}
247
-
248
-	private function getUserDisplayName($uid) {
249
-		if (!isset($this->userDisplayNames[$uid])) {
250
-			$user = $this->userManager->get($uid);
251
-
252
-			if ($user instanceof IUser) {
253
-				$this->userDisplayNames[$uid] = $user->getDisplayName();
254
-			} else {
255
-				$this->userDisplayNames[$uid] = $uid;
256
-			}
257
-		}
258
-
259
-		return $this->userDisplayNames[$uid];
260
-	}
261
-
262
-	/**
263
-	 * @param int $addressBookId
264
-	 */
265
-	public function getAddressBookById($addressBookId) {
266
-		$query = $this->db->getQueryBuilder();
267
-		$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
268
-			->from('addressbooks')
269
-			->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
270
-			->execute();
271
-
272
-		$row = $result->fetch();
273
-		$result->closeCursor();
274
-		if ($row === false) {
275
-			return null;
276
-		}
277
-
278
-		$addressBook = [
279
-			'id'  => $row['id'],
280
-			'uri' => $row['uri'],
281
-			'principaluri' => $row['principaluri'],
282
-			'{DAV:}displayname' => $row['displayname'],
283
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
284
-			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
285
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
286
-		];
287
-
288
-		$this->addOwnerPrincipal($addressBook);
289
-
290
-		return $addressBook;
291
-	}
292
-
293
-	/**
294
-	 * @param $addressBookUri
295
-	 * @return array|null
296
-	 */
297
-	public function getAddressBooksByUri($principal, $addressBookUri) {
298
-		$query = $this->db->getQueryBuilder();
299
-		$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
300
-			->from('addressbooks')
301
-			->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri)))
302
-			->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
303
-			->setMaxResults(1)
304
-			->execute();
305
-
306
-		$row = $result->fetch();
307
-		$result->closeCursor();
308
-		if ($row === false) {
309
-			return null;
310
-		}
311
-
312
-		$addressBook = [
313
-			'id'  => $row['id'],
314
-			'uri' => $row['uri'],
315
-			'principaluri' => $row['principaluri'],
316
-			'{DAV:}displayname' => $row['displayname'],
317
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
318
-			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
319
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
320
-		];
321
-
322
-		$this->addOwnerPrincipal($addressBook);
323
-
324
-		return $addressBook;
325
-	}
326
-
327
-	/**
328
-	 * Updates properties for an address book.
329
-	 *
330
-	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
331
-	 * To do the actual updates, you must tell this object which properties
332
-	 * you're going to process with the handle() method.
333
-	 *
334
-	 * Calling the handle method is like telling the PropPatch object "I
335
-	 * promise I can handle updating this property".
336
-	 *
337
-	 * Read the PropPatch documentation for more info and examples.
338
-	 *
339
-	 * @param string $addressBookId
340
-	 * @param \Sabre\DAV\PropPatch $propPatch
341
-	 * @return void
342
-	 */
343
-	function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
344
-		$supportedProperties = [
345
-			'{DAV:}displayname',
346
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description',
347
-		];
348
-
349
-		$propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) {
350
-
351
-			$updates = [];
352
-			foreach($mutations as $property=>$newValue) {
353
-
354
-				switch($property) {
355
-					case '{DAV:}displayname' :
356
-						$updates['displayname'] = $newValue;
357
-						break;
358
-					case '{' . Plugin::NS_CARDDAV . '}addressbook-description' :
359
-						$updates['description'] = $newValue;
360
-						break;
361
-				}
362
-			}
363
-			$query = $this->db->getQueryBuilder();
364
-			$query->update('addressbooks');
365
-
366
-			foreach($updates as $key=>$value) {
367
-				$query->set($key, $query->createNamedParameter($value));
368
-			}
369
-			$query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
370
-			->execute();
371
-
372
-			$this->addChange($addressBookId, "", 2);
373
-
374
-			return true;
375
-
376
-		});
377
-	}
378
-
379
-	/**
380
-	 * Creates a new address book
381
-	 *
382
-	 * @param string $principalUri
383
-	 * @param string $url Just the 'basename' of the url.
384
-	 * @param array $properties
385
-	 * @return int
386
-	 * @throws BadRequest
387
-	 */
388
-	function createAddressBook($principalUri, $url, array $properties) {
389
-		$values = [
390
-			'displayname' => null,
391
-			'description' => null,
392
-			'principaluri' => $principalUri,
393
-			'uri' => $url,
394
-			'synctoken' => 1
395
-		];
396
-
397
-		foreach($properties as $property=>$newValue) {
398
-
399
-			switch($property) {
400
-				case '{DAV:}displayname' :
401
-					$values['displayname'] = $newValue;
402
-					break;
403
-				case '{' . Plugin::NS_CARDDAV . '}addressbook-description' :
404
-					$values['description'] = $newValue;
405
-					break;
406
-				default :
407
-					throw new BadRequest('Unknown property: ' . $property);
408
-			}
409
-
410
-		}
411
-
412
-		// Fallback to make sure the displayname is set. Some clients may refuse
413
-		// to work with addressbooks not having a displayname.
414
-		if(is_null($values['displayname'])) {
415
-			$values['displayname'] = $url;
416
-		}
417
-
418
-		$query = $this->db->getQueryBuilder();
419
-		$query->insert('addressbooks')
420
-			->values([
421
-				'uri' => $query->createParameter('uri'),
422
-				'displayname' => $query->createParameter('displayname'),
423
-				'description' => $query->createParameter('description'),
424
-				'principaluri' => $query->createParameter('principaluri'),
425
-				'synctoken' => $query->createParameter('synctoken'),
426
-			])
427
-			->setParameters($values)
428
-			->execute();
429
-
430
-		return $query->getLastInsertId();
431
-	}
432
-
433
-	/**
434
-	 * Deletes an entire addressbook and all its contents
435
-	 *
436
-	 * @param mixed $addressBookId
437
-	 * @return void
438
-	 */
439
-	function deleteAddressBook($addressBookId) {
440
-		$query = $this->db->getQueryBuilder();
441
-		$query->delete('cards')
442
-			->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
443
-			->setParameter('addressbookid', $addressBookId)
444
-			->execute();
445
-
446
-		$query->delete('addressbookchanges')
447
-			->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
448
-			->setParameter('addressbookid', $addressBookId)
449
-			->execute();
450
-
451
-		$query->delete('addressbooks')
452
-			->where($query->expr()->eq('id', $query->createParameter('id')))
453
-			->setParameter('id', $addressBookId)
454
-			->execute();
455
-
456
-		$this->sharingBackend->deleteAllShares($addressBookId);
457
-
458
-		$query->delete($this->dbCardsPropertiesTable)
459
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
460
-			->execute();
461
-
462
-	}
463
-
464
-	/**
465
-	 * Returns all cards for a specific addressbook id.
466
-	 *
467
-	 * This method should return the following properties for each card:
468
-	 *   * carddata - raw vcard data
469
-	 *   * uri - Some unique url
470
-	 *   * lastmodified - A unix timestamp
471
-	 *
472
-	 * It's recommended to also return the following properties:
473
-	 *   * etag - A unique etag. This must change every time the card changes.
474
-	 *   * size - The size of the card in bytes.
475
-	 *
476
-	 * If these last two properties are provided, less time will be spent
477
-	 * calculating them. If they are specified, you can also ommit carddata.
478
-	 * This may speed up certain requests, especially with large cards.
479
-	 *
480
-	 * @param mixed $addressBookId
481
-	 * @return array
482
-	 */
483
-	function getCards($addressBookId) {
484
-		$query = $this->db->getQueryBuilder();
485
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata'])
486
-			->from('cards')
487
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
488
-
489
-		$cards = [];
490
-
491
-		$result = $query->execute();
492
-		while($row = $result->fetch()) {
493
-			$row['etag'] = '"' . $row['etag'] . '"';
494
-			$row['carddata'] = $this->readBlob($row['carddata']);
495
-			$cards[] = $row;
496
-		}
497
-		$result->closeCursor();
498
-
499
-		return $cards;
500
-	}
501
-
502
-	/**
503
-	 * Returns a specific card.
504
-	 *
505
-	 * The same set of properties must be returned as with getCards. The only
506
-	 * exception is that 'carddata' is absolutely required.
507
-	 *
508
-	 * If the card does not exist, you must return false.
509
-	 *
510
-	 * @param mixed $addressBookId
511
-	 * @param string $cardUri
512
-	 * @return array
513
-	 */
514
-	function getCard($addressBookId, $cardUri) {
515
-		$query = $this->db->getQueryBuilder();
516
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata'])
517
-			->from('cards')
518
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
519
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
520
-			->setMaxResults(1);
521
-
522
-		$result = $query->execute();
523
-		$row = $result->fetch();
524
-		if (!$row) {
525
-			return false;
526
-		}
527
-		$row['etag'] = '"' . $row['etag'] . '"';
528
-		$row['carddata'] = $this->readBlob($row['carddata']);
529
-
530
-		return $row;
531
-	}
532
-
533
-	/**
534
-	 * Returns a list of cards.
535
-	 *
536
-	 * This method should work identical to getCard, but instead return all the
537
-	 * cards in the list as an array.
538
-	 *
539
-	 * If the backend supports this, it may allow for some speed-ups.
540
-	 *
541
-	 * @param mixed $addressBookId
542
-	 * @param string[] $uris
543
-	 * @return array
544
-	 */
545
-	function getMultipleCards($addressBookId, array $uris) {
546
-		if (empty($uris)) {
547
-			return [];
548
-		}
549
-
550
-		$chunks = array_chunk($uris, 100);
551
-		$cards = [];
552
-
553
-		$query = $this->db->getQueryBuilder();
554
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata'])
555
-			->from('cards')
556
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
557
-			->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
558
-
559
-		foreach ($chunks as $uris) {
560
-			$query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
561
-			$result = $query->execute();
562
-
563
-			while ($row = $result->fetch()) {
564
-				$row['etag'] = '"' . $row['etag'] . '"';
565
-				$row['carddata'] = $this->readBlob($row['carddata']);
566
-				$cards[] = $row;
567
-			}
568
-			$result->closeCursor();
569
-		}
570
-		return $cards;
571
-	}
572
-
573
-	/**
574
-	 * Creates a new card.
575
-	 *
576
-	 * The addressbook id will be passed as the first argument. This is the
577
-	 * same id as it is returned from the getAddressBooksForUser method.
578
-	 *
579
-	 * The cardUri is a base uri, and doesn't include the full path. The
580
-	 * cardData argument is the vcard body, and is passed as a string.
581
-	 *
582
-	 * It is possible to return an ETag from this method. This ETag is for the
583
-	 * newly created resource, and must be enclosed with double quotes (that
584
-	 * is, the string itself must contain the double quotes).
585
-	 *
586
-	 * You should only return the ETag if you store the carddata as-is. If a
587
-	 * subsequent GET request on the same card does not have the same body,
588
-	 * byte-by-byte and you did return an ETag here, clients tend to get
589
-	 * confused.
590
-	 *
591
-	 * If you don't return an ETag, you can just return null.
592
-	 *
593
-	 * @param mixed $addressBookId
594
-	 * @param string $cardUri
595
-	 * @param string $cardData
596
-	 * @return string
597
-	 */
598
-	function createCard($addressBookId, $cardUri, $cardData) {
599
-		$etag = md5($cardData);
600
-
601
-		$query = $this->db->getQueryBuilder();
602
-		$query->insert('cards')
603
-			->values([
604
-				'carddata' => $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB),
605
-				'uri' => $query->createNamedParameter($cardUri),
606
-				'lastmodified' => $query->createNamedParameter(time()),
607
-				'addressbookid' => $query->createNamedParameter($addressBookId),
608
-				'size' => $query->createNamedParameter(strlen($cardData)),
609
-				'etag' => $query->createNamedParameter($etag),
610
-			])
611
-			->execute();
612
-
613
-		$this->addChange($addressBookId, $cardUri, 1);
614
-		$this->updateProperties($addressBookId, $cardUri, $cardData);
615
-
616
-		if (!is_null($this->dispatcher)) {
617
-			$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
618
-				new GenericEvent(null, [
619
-					'addressBookId' => $addressBookId,
620
-					'cardUri' => $cardUri,
621
-					'cardData' => $cardData]));
622
-		}
623
-
624
-		return '"' . $etag . '"';
625
-	}
626
-
627
-	/**
628
-	 * Updates a card.
629
-	 *
630
-	 * The addressbook id will be passed as the first argument. This is the
631
-	 * same id as it is returned from the getAddressBooksForUser method.
632
-	 *
633
-	 * The cardUri is a base uri, and doesn't include the full path. The
634
-	 * cardData argument is the vcard body, and is passed as a string.
635
-	 *
636
-	 * It is possible to return an ETag from this method. This ETag should
637
-	 * match that of the updated resource, and must be enclosed with double
638
-	 * quotes (that is: the string itself must contain the actual quotes).
639
-	 *
640
-	 * You should only return the ETag if you store the carddata as-is. If a
641
-	 * subsequent GET request on the same card does not have the same body,
642
-	 * byte-by-byte and you did return an ETag here, clients tend to get
643
-	 * confused.
644
-	 *
645
-	 * If you don't return an ETag, you can just return null.
646
-	 *
647
-	 * @param mixed $addressBookId
648
-	 * @param string $cardUri
649
-	 * @param string $cardData
650
-	 * @return string
651
-	 */
652
-	function updateCard($addressBookId, $cardUri, $cardData) {
653
-
654
-		$etag = md5($cardData);
655
-		$query = $this->db->getQueryBuilder();
656
-		$query->update('cards')
657
-			->set('carddata', $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB))
658
-			->set('lastmodified', $query->createNamedParameter(time()))
659
-			->set('size', $query->createNamedParameter(strlen($cardData)))
660
-			->set('etag', $query->createNamedParameter($etag))
661
-			->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
662
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
663
-			->execute();
664
-
665
-		$this->addChange($addressBookId, $cardUri, 2);
666
-		$this->updateProperties($addressBookId, $cardUri, $cardData);
667
-
668
-		if (!is_null($this->dispatcher)) {
669
-			$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
670
-				new GenericEvent(null, [
671
-					'addressBookId' => $addressBookId,
672
-					'cardUri' => $cardUri,
673
-					'cardData' => $cardData]));
674
-		}
675
-
676
-		return '"' . $etag . '"';
677
-	}
678
-
679
-	/**
680
-	 * Deletes a card
681
-	 *
682
-	 * @param mixed $addressBookId
683
-	 * @param string $cardUri
684
-	 * @return bool
685
-	 */
686
-	function deleteCard($addressBookId, $cardUri) {
687
-		try {
688
-			$cardId = $this->getCardId($addressBookId, $cardUri);
689
-		} catch (\InvalidArgumentException $e) {
690
-			$cardId = null;
691
-		}
692
-		$query = $this->db->getQueryBuilder();
693
-		$ret = $query->delete('cards')
694
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
695
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
696
-			->execute();
697
-
698
-		$this->addChange($addressBookId, $cardUri, 3);
699
-
700
-		if (!is_null($this->dispatcher)) {
701
-			$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
702
-				new GenericEvent(null, [
703
-					'addressBookId' => $addressBookId,
704
-					'cardUri' => $cardUri]));
705
-		}
706
-
707
-		if ($ret === 1) {
708
-			if ($cardId !== null) {
709
-				$this->purgeProperties($addressBookId, $cardId);
710
-			}
711
-			return true;
712
-		}
713
-
714
-		return false;
715
-	}
716
-
717
-	/**
718
-	 * The getChanges method returns all the changes that have happened, since
719
-	 * the specified syncToken in the specified address book.
720
-	 *
721
-	 * This function should return an array, such as the following:
722
-	 *
723
-	 * [
724
-	 *   'syncToken' => 'The current synctoken',
725
-	 *   'added'   => [
726
-	 *      'new.txt',
727
-	 *   ],
728
-	 *   'modified'   => [
729
-	 *      'modified.txt',
730
-	 *   ],
731
-	 *   'deleted' => [
732
-	 *      'foo.php.bak',
733
-	 *      'old.txt'
734
-	 *   ]
735
-	 * ];
736
-	 *
737
-	 * The returned syncToken property should reflect the *current* syncToken
738
-	 * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
739
-	 * property. This is needed here too, to ensure the operation is atomic.
740
-	 *
741
-	 * If the $syncToken argument is specified as null, this is an initial
742
-	 * sync, and all members should be reported.
743
-	 *
744
-	 * The modified property is an array of nodenames that have changed since
745
-	 * the last token.
746
-	 *
747
-	 * The deleted property is an array with nodenames, that have been deleted
748
-	 * from collection.
749
-	 *
750
-	 * The $syncLevel argument is basically the 'depth' of the report. If it's
751
-	 * 1, you only have to report changes that happened only directly in
752
-	 * immediate descendants. If it's 2, it should also include changes from
753
-	 * the nodes below the child collections. (grandchildren)
754
-	 *
755
-	 * The $limit argument allows a client to specify how many results should
756
-	 * be returned at most. If the limit is not specified, it should be treated
757
-	 * as infinite.
758
-	 *
759
-	 * If the limit (infinite or not) is higher than you're willing to return,
760
-	 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
761
-	 *
762
-	 * If the syncToken is expired (due to data cleanup) or unknown, you must
763
-	 * return null.
764
-	 *
765
-	 * The limit is 'suggestive'. You are free to ignore it.
766
-	 *
767
-	 * @param string $addressBookId
768
-	 * @param string $syncToken
769
-	 * @param int $syncLevel
770
-	 * @param int $limit
771
-	 * @return array
772
-	 */
773
-	function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
774
-		// Current synctoken
775
-		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
776
-		$stmt->execute([ $addressBookId ]);
777
-		$currentToken = $stmt->fetchColumn(0);
778
-
779
-		if (is_null($currentToken)) return null;
780
-
781
-		$result = [
782
-			'syncToken' => $currentToken,
783
-			'added'     => [],
784
-			'modified'  => [],
785
-			'deleted'   => [],
786
-		];
787
-
788
-		if ($syncToken) {
789
-
790
-			$query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
791
-			if ($limit>0) {
792
-				$query .= " `LIMIT` " . (int)$limit;
793
-			}
794
-
795
-			// Fetching all changes
796
-			$stmt = $this->db->prepare($query);
797
-			$stmt->execute([$syncToken, $currentToken, $addressBookId]);
798
-
799
-			$changes = [];
800
-
801
-			// This loop ensures that any duplicates are overwritten, only the
802
-			// last change on a node is relevant.
803
-			while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
804
-
805
-				$changes[$row['uri']] = $row['operation'];
806
-
807
-			}
808
-
809
-			foreach($changes as $uri => $operation) {
810
-
811
-				switch($operation) {
812
-					case 1:
813
-						$result['added'][] = $uri;
814
-						break;
815
-					case 2:
816
-						$result['modified'][] = $uri;
817
-						break;
818
-					case 3:
819
-						$result['deleted'][] = $uri;
820
-						break;
821
-				}
822
-
823
-			}
824
-		} else {
825
-			// No synctoken supplied, this is the initial sync.
826
-			$query = "SELECT `uri` FROM `*PREFIX*cards` WHERE `addressbookid` = ?";
827
-			$stmt = $this->db->prepare($query);
828
-			$stmt->execute([$addressBookId]);
829
-
830
-			$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
831
-		}
832
-		return $result;
833
-	}
834
-
835
-	/**
836
-	 * Adds a change record to the addressbookchanges table.
837
-	 *
838
-	 * @param mixed $addressBookId
839
-	 * @param string $objectUri
840
-	 * @param int $operation 1 = add, 2 = modify, 3 = delete
841
-	 * @return void
842
-	 */
843
-	protected function addChange($addressBookId, $objectUri, $operation) {
844
-		$sql = 'INSERT INTO `*PREFIX*addressbookchanges`(`uri`, `synctoken`, `addressbookid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*addressbooks` WHERE `id` = ?';
845
-		$stmt = $this->db->prepare($sql);
846
-		$stmt->execute([
847
-			$objectUri,
848
-			$addressBookId,
849
-			$operation,
850
-			$addressBookId
851
-		]);
852
-		$stmt = $this->db->prepare('UPDATE `*PREFIX*addressbooks` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
853
-		$stmt->execute([
854
-			$addressBookId
855
-		]);
856
-	}
857
-
858
-	private function readBlob($cardData) {
859
-		if (is_resource($cardData)) {
860
-			return stream_get_contents($cardData);
861
-		}
862
-
863
-		return $cardData;
864
-	}
865
-
866
-	/**
867
-	 * @param IShareable $shareable
868
-	 * @param string[] $add
869
-	 * @param string[] $remove
870
-	 */
871
-	public function updateShares(IShareable $shareable, $add, $remove) {
872
-		$this->sharingBackend->updateShares($shareable, $add, $remove);
873
-	}
874
-
875
-	/**
876
-	 * search contact
877
-	 *
878
-	 * @param int $addressBookId
879
-	 * @param string $pattern which should match within the $searchProperties
880
-	 * @param array $searchProperties defines the properties within the query pattern should match
881
-	 * @return array an array of contacts which are arrays of key-value-pairs
882
-	 */
883
-	public function search($addressBookId, $pattern, $searchProperties) {
884
-		$query = $this->db->getQueryBuilder();
885
-		$query2 = $this->db->getQueryBuilder();
886
-		$query2->selectDistinct('cp.cardid')->from($this->dbCardsPropertiesTable, 'cp');
887
-		foreach ($searchProperties as $property) {
888
-			$query2->orWhere(
889
-				$query2->expr()->andX(
890
-					$query2->expr()->eq('cp.name', $query->createNamedParameter($property)),
891
-					$query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%'))
892
-				)
893
-			);
894
-		}
895
-		$query2->andWhere($query2->expr()->eq('cp.addressbookid', $query->createNamedParameter($addressBookId)));
896
-
897
-		$query->select('c.carddata', 'c.uri')->from($this->dbCardsTable, 'c')
898
-			->where($query->expr()->in('c.id', $query->createFunction($query2->getSQL())));
899
-
900
-		$result = $query->execute();
901
-		$cards = $result->fetchAll();
902
-
903
-		$result->closeCursor();
904
-
905
-		return array_map(function($array) {
906
-			$array['carddata'] = $this->readBlob($array['carddata']);
907
-			return $array;
908
-		}, $cards);
909
-	}
910
-
911
-	/**
912
-	 * @param int $bookId
913
-	 * @param string $name
914
-	 * @return array
915
-	 */
916
-	public function collectCardProperties($bookId, $name) {
917
-		$query = $this->db->getQueryBuilder();
918
-		$result = $query->selectDistinct('value')
919
-			->from($this->dbCardsPropertiesTable)
920
-			->where($query->expr()->eq('name', $query->createNamedParameter($name)))
921
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($bookId)))
922
-			->execute();
923
-
924
-		$all = $result->fetchAll(PDO::FETCH_COLUMN);
925
-		$result->closeCursor();
926
-
927
-		return $all;
928
-	}
929
-
930
-	/**
931
-	 * get URI from a given contact
932
-	 *
933
-	 * @param int $id
934
-	 * @return string
935
-	 */
936
-	public function getCardUri($id) {
937
-		$query = $this->db->getQueryBuilder();
938
-		$query->select('uri')->from($this->dbCardsTable)
939
-				->where($query->expr()->eq('id', $query->createParameter('id')))
940
-				->setParameter('id', $id);
941
-
942
-		$result = $query->execute();
943
-		$uri = $result->fetch();
944
-		$result->closeCursor();
945
-
946
-		if (!isset($uri['uri'])) {
947
-			throw new \InvalidArgumentException('Card does not exists: ' . $id);
948
-		}
949
-
950
-		return $uri['uri'];
951
-	}
952
-
953
-	/**
954
-	 * return contact with the given URI
955
-	 *
956
-	 * @param int $addressBookId
957
-	 * @param string $uri
958
-	 * @returns array
959
-	 */
960
-	public function getContact($addressBookId, $uri) {
961
-		$result = [];
962
-		$query = $this->db->getQueryBuilder();
963
-		$query->select('*')->from($this->dbCardsTable)
964
-				->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
965
-				->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
966
-		$queryResult = $query->execute();
967
-		$contact = $queryResult->fetch();
968
-		$queryResult->closeCursor();
969
-
970
-		if (is_array($contact)) {
971
-			$result = $contact;
972
-		}
973
-
974
-		return $result;
975
-	}
976
-
977
-	/**
978
-	 * Returns the list of people whom this address book is shared with.
979
-	 *
980
-	 * Every element in this array should have the following properties:
981
-	 *   * href - Often a mailto: address
982
-	 *   * commonName - Optional, for example a first + last name
983
-	 *   * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
984
-	 *   * readOnly - boolean
985
-	 *   * summary - Optional, a description for the share
986
-	 *
987
-	 * @return array
988
-	 */
989
-	public function getShares($addressBookId) {
990
-		return $this->sharingBackend->getShares($addressBookId);
991
-	}
992
-
993
-	/**
994
-	 * update properties table
995
-	 *
996
-	 * @param int $addressBookId
997
-	 * @param string $cardUri
998
-	 * @param string $vCardSerialized
999
-	 */
1000
-	protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) {
1001
-		$cardId = $this->getCardId($addressBookId, $cardUri);
1002
-		$vCard = $this->readCard($vCardSerialized);
1003
-
1004
-		$this->purgeProperties($addressBookId, $cardId);
1005
-
1006
-		$query = $this->db->getQueryBuilder();
1007
-		$query->insert($this->dbCardsPropertiesTable)
1008
-			->values(
1009
-				[
1010
-					'addressbookid' => $query->createNamedParameter($addressBookId),
1011
-					'cardid' => $query->createNamedParameter($cardId),
1012
-					'name' => $query->createParameter('name'),
1013
-					'value' => $query->createParameter('value'),
1014
-					'preferred' => $query->createParameter('preferred')
1015
-				]
1016
-			);
1017
-
1018
-		foreach ($vCard->children() as $property) {
1019
-			if(!in_array($property->name, self::$indexProperties)) {
1020
-				continue;
1021
-			}
1022
-			$preferred = 0;
1023
-			foreach($property->parameters as $parameter) {
1024
-				if ($parameter->name == 'TYPE' && strtoupper($parameter->getValue()) == 'PREF') {
1025
-					$preferred = 1;
1026
-					break;
1027
-				}
1028
-			}
1029
-			$query->setParameter('name', $property->name);
1030
-			$query->setParameter('value', substr($property->getValue(), 0, 254));
1031
-			$query->setParameter('preferred', $preferred);
1032
-			$query->execute();
1033
-		}
1034
-	}
1035
-
1036
-	/**
1037
-	 * read vCard data into a vCard object
1038
-	 *
1039
-	 * @param string $cardData
1040
-	 * @return VCard
1041
-	 */
1042
-	protected function readCard($cardData) {
1043
-		return  Reader::read($cardData);
1044
-	}
1045
-
1046
-	/**
1047
-	 * delete all properties from a given card
1048
-	 *
1049
-	 * @param int $addressBookId
1050
-	 * @param int $cardId
1051
-	 */
1052
-	protected function purgeProperties($addressBookId, $cardId) {
1053
-		$query = $this->db->getQueryBuilder();
1054
-		$query->delete($this->dbCardsPropertiesTable)
1055
-			->where($query->expr()->eq('cardid', $query->createNamedParameter($cardId)))
1056
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1057
-		$query->execute();
1058
-	}
1059
-
1060
-	/**
1061
-	 * get ID from a given contact
1062
-	 *
1063
-	 * @param int $addressBookId
1064
-	 * @param string $uri
1065
-	 * @return int
1066
-	 */
1067
-	protected function getCardId($addressBookId, $uri) {
1068
-		$query = $this->db->getQueryBuilder();
1069
-		$query->select('id')->from($this->dbCardsTable)
1070
-			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1071
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1072
-
1073
-		$result = $query->execute();
1074
-		$cardIds = $result->fetch();
1075
-		$result->closeCursor();
1076
-
1077
-		if (!isset($cardIds['id'])) {
1078
-			throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1079
-		}
1080
-
1081
-		return (int)$cardIds['id'];
1082
-	}
1083
-
1084
-	/**
1085
-	 * For shared address books the sharee is set in the ACL of the address book
1086
-	 * @param $addressBookId
1087
-	 * @param $acl
1088
-	 * @return array
1089
-	 */
1090
-	public function applyShareAcl($addressBookId, $acl) {
1091
-		return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
1092
-	}
1093
-
1094
-	private function convertPrincipal($principalUri, $toV2) {
1095
-		if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
1096
-			list(, $name) = URLUtil::splitPath($principalUri);
1097
-			if ($toV2 === true) {
1098
-				return "principals/users/$name";
1099
-			}
1100
-			return "principals/$name";
1101
-		}
1102
-		return $principalUri;
1103
-	}
1104
-
1105
-	private function addOwnerPrincipal(&$addressbookInfo) {
1106
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1107
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1108
-		if (isset($addressbookInfo[$ownerPrincipalKey])) {
1109
-			$uri = $addressbookInfo[$ownerPrincipalKey];
1110
-		} else {
1111
-			$uri = $addressbookInfo['principaluri'];
1112
-		}
1113
-
1114
-		$principalInformation = $this->principalBackend->getPrincipalByPath($uri);
1115
-		if (isset($principalInformation['{DAV:}displayname'])) {
1116
-			$addressbookInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
1117
-		}
1118
-	}
51
+    const PERSONAL_ADDRESSBOOK_URI = 'contacts';
52
+    const PERSONAL_ADDRESSBOOK_NAME = 'Contacts';
53
+
54
+    /** @var Principal */
55
+    private $principalBackend;
56
+
57
+    /** @var string */
58
+    private $dbCardsTable = 'cards';
59
+
60
+    /** @var string */
61
+    private $dbCardsPropertiesTable = 'cards_properties';
62
+
63
+    /** @var IDBConnection */
64
+    private $db;
65
+
66
+    /** @var Backend */
67
+    private $sharingBackend;
68
+
69
+    /** @var array properties to index */
70
+    public static $indexProperties = array(
71
+            'BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME',
72
+            'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO', 'CLOUD');
73
+
74
+    /**
75
+     * @var string[] Map of uid => display name
76
+     */
77
+    protected $userDisplayNames;
78
+
79
+    /** @var IUserManager */
80
+    private $userManager;
81
+
82
+    /** @var EventDispatcherInterface */
83
+    private $dispatcher;
84
+
85
+    /**
86
+     * CardDavBackend constructor.
87
+     *
88
+     * @param IDBConnection $db
89
+     * @param Principal $principalBackend
90
+     * @param IUserManager $userManager
91
+     * @param EventDispatcherInterface $dispatcher
92
+     */
93
+    public function __construct(IDBConnection $db,
94
+                                Principal $principalBackend,
95
+                                IUserManager $userManager,
96
+                                EventDispatcherInterface $dispatcher = null) {
97
+        $this->db = $db;
98
+        $this->principalBackend = $principalBackend;
99
+        $this->userManager = $userManager;
100
+        $this->dispatcher = $dispatcher;
101
+        $this->sharingBackend = new Backend($this->db, $principalBackend, 'addressbook');
102
+    }
103
+
104
+    /**
105
+     * Return the number of address books for a principal
106
+     *
107
+     * @param $principalUri
108
+     * @return int
109
+     */
110
+    public function getAddressBooksForUserCount($principalUri) {
111
+        $principalUri = $this->convertPrincipal($principalUri, true);
112
+        $query = $this->db->getQueryBuilder();
113
+        $query->select($query->createFunction('COUNT(*)'))
114
+            ->from('addressbooks')
115
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
116
+
117
+        return (int)$query->execute()->fetchColumn();
118
+    }
119
+
120
+    /**
121
+     * Returns the list of address books for a specific user.
122
+     *
123
+     * Every addressbook should have the following properties:
124
+     *   id - an arbitrary unique id
125
+     *   uri - the 'basename' part of the url
126
+     *   principaluri - Same as the passed parameter
127
+     *
128
+     * Any additional clark-notation property may be passed besides this. Some
129
+     * common ones are :
130
+     *   {DAV:}displayname
131
+     *   {urn:ietf:params:xml:ns:carddav}addressbook-description
132
+     *   {http://calendarserver.org/ns/}getctag
133
+     *
134
+     * @param string $principalUri
135
+     * @return array
136
+     */
137
+    function getAddressBooksForUser($principalUri) {
138
+        $principalUriOriginal = $principalUri;
139
+        $principalUri = $this->convertPrincipal($principalUri, true);
140
+        $query = $this->db->getQueryBuilder();
141
+        $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
142
+            ->from('addressbooks')
143
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
144
+
145
+        $addressBooks = [];
146
+
147
+        $result = $query->execute();
148
+        while($row = $result->fetch()) {
149
+            $addressBooks[$row['id']] = [
150
+                'id'  => $row['id'],
151
+                'uri' => $row['uri'],
152
+                'principaluri' => $this->convertPrincipal($row['principaluri'], false),
153
+                '{DAV:}displayname' => $row['displayname'],
154
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
155
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
156
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
157
+            ];
158
+
159
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
160
+        }
161
+        $result->closeCursor();
162
+
163
+        // query for shared calendars
164
+        $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
165
+        $principals[]= $principalUri;
166
+
167
+        $query = $this->db->getQueryBuilder();
168
+        $result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
169
+            ->from('dav_shares', 's')
170
+            ->join('s', 'addressbooks', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
171
+            ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
172
+            ->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
173
+            ->setParameter('type', 'addressbook')
174
+            ->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
175
+            ->execute();
176
+
177
+        $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
178
+        while($row = $result->fetch()) {
179
+            if ($row['principaluri'] === $principalUri) {
180
+                continue;
181
+            }
182
+
183
+            $readOnly = (int) $row['access'] === Backend::ACCESS_READ;
184
+            if (isset($addressBooks[$row['id']])) {
185
+                if ($readOnly) {
186
+                    // New share can not have more permissions then the old one.
187
+                    continue;
188
+                }
189
+                if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) &&
190
+                    $addressBooks[$row['id']][$readOnlyPropertyName] === 0) {
191
+                    // Old share is already read-write, no more permissions can be gained
192
+                    continue;
193
+                }
194
+            }
195
+
196
+            list(, $name) = URLUtil::splitPath($row['principaluri']);
197
+            $uri = $row['uri'] . '_shared_by_' . $name;
198
+            $displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
199
+
200
+            $addressBooks[$row['id']] = [
201
+                'id'  => $row['id'],
202
+                'uri' => $uri,
203
+                'principaluri' => $principalUriOriginal,
204
+                '{DAV:}displayname' => $displayName,
205
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
206
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
207
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
208
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
209
+                $readOnlyPropertyName => $readOnly,
210
+            ];
211
+
212
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
213
+        }
214
+        $result->closeCursor();
215
+
216
+        return array_values($addressBooks);
217
+    }
218
+
219
+    public function getUsersOwnAddressBooks($principalUri) {
220
+        $principalUriOriginal = $principalUri;
221
+        $principalUri = $this->convertPrincipal($principalUri, true);
222
+        $query = $this->db->getQueryBuilder();
223
+        $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
224
+                ->from('addressbooks')
225
+                ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
226
+
227
+        $addressBooks = [];
228
+
229
+        $result = $query->execute();
230
+        while($row = $result->fetch()) {
231
+            $addressBooks[$row['id']] = [
232
+                'id'  => $row['id'],
233
+                'uri' => $row['uri'],
234
+                'principaluri' => $this->convertPrincipal($row['principaluri'], false),
235
+                '{DAV:}displayname' => $row['displayname'],
236
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
237
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
238
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
239
+            ];
240
+
241
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
242
+        }
243
+        $result->closeCursor();
244
+
245
+        return array_values($addressBooks);
246
+    }
247
+
248
+    private function getUserDisplayName($uid) {
249
+        if (!isset($this->userDisplayNames[$uid])) {
250
+            $user = $this->userManager->get($uid);
251
+
252
+            if ($user instanceof IUser) {
253
+                $this->userDisplayNames[$uid] = $user->getDisplayName();
254
+            } else {
255
+                $this->userDisplayNames[$uid] = $uid;
256
+            }
257
+        }
258
+
259
+        return $this->userDisplayNames[$uid];
260
+    }
261
+
262
+    /**
263
+     * @param int $addressBookId
264
+     */
265
+    public function getAddressBookById($addressBookId) {
266
+        $query = $this->db->getQueryBuilder();
267
+        $result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
268
+            ->from('addressbooks')
269
+            ->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
270
+            ->execute();
271
+
272
+        $row = $result->fetch();
273
+        $result->closeCursor();
274
+        if ($row === false) {
275
+            return null;
276
+        }
277
+
278
+        $addressBook = [
279
+            'id'  => $row['id'],
280
+            'uri' => $row['uri'],
281
+            'principaluri' => $row['principaluri'],
282
+            '{DAV:}displayname' => $row['displayname'],
283
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
284
+            '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
285
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
286
+        ];
287
+
288
+        $this->addOwnerPrincipal($addressBook);
289
+
290
+        return $addressBook;
291
+    }
292
+
293
+    /**
294
+     * @param $addressBookUri
295
+     * @return array|null
296
+     */
297
+    public function getAddressBooksByUri($principal, $addressBookUri) {
298
+        $query = $this->db->getQueryBuilder();
299
+        $result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
300
+            ->from('addressbooks')
301
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri)))
302
+            ->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
303
+            ->setMaxResults(1)
304
+            ->execute();
305
+
306
+        $row = $result->fetch();
307
+        $result->closeCursor();
308
+        if ($row === false) {
309
+            return null;
310
+        }
311
+
312
+        $addressBook = [
313
+            'id'  => $row['id'],
314
+            'uri' => $row['uri'],
315
+            'principaluri' => $row['principaluri'],
316
+            '{DAV:}displayname' => $row['displayname'],
317
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
318
+            '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
319
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
320
+        ];
321
+
322
+        $this->addOwnerPrincipal($addressBook);
323
+
324
+        return $addressBook;
325
+    }
326
+
327
+    /**
328
+     * Updates properties for an address book.
329
+     *
330
+     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
331
+     * To do the actual updates, you must tell this object which properties
332
+     * you're going to process with the handle() method.
333
+     *
334
+     * Calling the handle method is like telling the PropPatch object "I
335
+     * promise I can handle updating this property".
336
+     *
337
+     * Read the PropPatch documentation for more info and examples.
338
+     *
339
+     * @param string $addressBookId
340
+     * @param \Sabre\DAV\PropPatch $propPatch
341
+     * @return void
342
+     */
343
+    function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
344
+        $supportedProperties = [
345
+            '{DAV:}displayname',
346
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description',
347
+        ];
348
+
349
+        $propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) {
350
+
351
+            $updates = [];
352
+            foreach($mutations as $property=>$newValue) {
353
+
354
+                switch($property) {
355
+                    case '{DAV:}displayname' :
356
+                        $updates['displayname'] = $newValue;
357
+                        break;
358
+                    case '{' . Plugin::NS_CARDDAV . '}addressbook-description' :
359
+                        $updates['description'] = $newValue;
360
+                        break;
361
+                }
362
+            }
363
+            $query = $this->db->getQueryBuilder();
364
+            $query->update('addressbooks');
365
+
366
+            foreach($updates as $key=>$value) {
367
+                $query->set($key, $query->createNamedParameter($value));
368
+            }
369
+            $query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
370
+            ->execute();
371
+
372
+            $this->addChange($addressBookId, "", 2);
373
+
374
+            return true;
375
+
376
+        });
377
+    }
378
+
379
+    /**
380
+     * Creates a new address book
381
+     *
382
+     * @param string $principalUri
383
+     * @param string $url Just the 'basename' of the url.
384
+     * @param array $properties
385
+     * @return int
386
+     * @throws BadRequest
387
+     */
388
+    function createAddressBook($principalUri, $url, array $properties) {
389
+        $values = [
390
+            'displayname' => null,
391
+            'description' => null,
392
+            'principaluri' => $principalUri,
393
+            'uri' => $url,
394
+            'synctoken' => 1
395
+        ];
396
+
397
+        foreach($properties as $property=>$newValue) {
398
+
399
+            switch($property) {
400
+                case '{DAV:}displayname' :
401
+                    $values['displayname'] = $newValue;
402
+                    break;
403
+                case '{' . Plugin::NS_CARDDAV . '}addressbook-description' :
404
+                    $values['description'] = $newValue;
405
+                    break;
406
+                default :
407
+                    throw new BadRequest('Unknown property: ' . $property);
408
+            }
409
+
410
+        }
411
+
412
+        // Fallback to make sure the displayname is set. Some clients may refuse
413
+        // to work with addressbooks not having a displayname.
414
+        if(is_null($values['displayname'])) {
415
+            $values['displayname'] = $url;
416
+        }
417
+
418
+        $query = $this->db->getQueryBuilder();
419
+        $query->insert('addressbooks')
420
+            ->values([
421
+                'uri' => $query->createParameter('uri'),
422
+                'displayname' => $query->createParameter('displayname'),
423
+                'description' => $query->createParameter('description'),
424
+                'principaluri' => $query->createParameter('principaluri'),
425
+                'synctoken' => $query->createParameter('synctoken'),
426
+            ])
427
+            ->setParameters($values)
428
+            ->execute();
429
+
430
+        return $query->getLastInsertId();
431
+    }
432
+
433
+    /**
434
+     * Deletes an entire addressbook and all its contents
435
+     *
436
+     * @param mixed $addressBookId
437
+     * @return void
438
+     */
439
+    function deleteAddressBook($addressBookId) {
440
+        $query = $this->db->getQueryBuilder();
441
+        $query->delete('cards')
442
+            ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
443
+            ->setParameter('addressbookid', $addressBookId)
444
+            ->execute();
445
+
446
+        $query->delete('addressbookchanges')
447
+            ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
448
+            ->setParameter('addressbookid', $addressBookId)
449
+            ->execute();
450
+
451
+        $query->delete('addressbooks')
452
+            ->where($query->expr()->eq('id', $query->createParameter('id')))
453
+            ->setParameter('id', $addressBookId)
454
+            ->execute();
455
+
456
+        $this->sharingBackend->deleteAllShares($addressBookId);
457
+
458
+        $query->delete($this->dbCardsPropertiesTable)
459
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
460
+            ->execute();
461
+
462
+    }
463
+
464
+    /**
465
+     * Returns all cards for a specific addressbook id.
466
+     *
467
+     * This method should return the following properties for each card:
468
+     *   * carddata - raw vcard data
469
+     *   * uri - Some unique url
470
+     *   * lastmodified - A unix timestamp
471
+     *
472
+     * It's recommended to also return the following properties:
473
+     *   * etag - A unique etag. This must change every time the card changes.
474
+     *   * size - The size of the card in bytes.
475
+     *
476
+     * If these last two properties are provided, less time will be spent
477
+     * calculating them. If they are specified, you can also ommit carddata.
478
+     * This may speed up certain requests, especially with large cards.
479
+     *
480
+     * @param mixed $addressBookId
481
+     * @return array
482
+     */
483
+    function getCards($addressBookId) {
484
+        $query = $this->db->getQueryBuilder();
485
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata'])
486
+            ->from('cards')
487
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
488
+
489
+        $cards = [];
490
+
491
+        $result = $query->execute();
492
+        while($row = $result->fetch()) {
493
+            $row['etag'] = '"' . $row['etag'] . '"';
494
+            $row['carddata'] = $this->readBlob($row['carddata']);
495
+            $cards[] = $row;
496
+        }
497
+        $result->closeCursor();
498
+
499
+        return $cards;
500
+    }
501
+
502
+    /**
503
+     * Returns a specific card.
504
+     *
505
+     * The same set of properties must be returned as with getCards. The only
506
+     * exception is that 'carddata' is absolutely required.
507
+     *
508
+     * If the card does not exist, you must return false.
509
+     *
510
+     * @param mixed $addressBookId
511
+     * @param string $cardUri
512
+     * @return array
513
+     */
514
+    function getCard($addressBookId, $cardUri) {
515
+        $query = $this->db->getQueryBuilder();
516
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata'])
517
+            ->from('cards')
518
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
519
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
520
+            ->setMaxResults(1);
521
+
522
+        $result = $query->execute();
523
+        $row = $result->fetch();
524
+        if (!$row) {
525
+            return false;
526
+        }
527
+        $row['etag'] = '"' . $row['etag'] . '"';
528
+        $row['carddata'] = $this->readBlob($row['carddata']);
529
+
530
+        return $row;
531
+    }
532
+
533
+    /**
534
+     * Returns a list of cards.
535
+     *
536
+     * This method should work identical to getCard, but instead return all the
537
+     * cards in the list as an array.
538
+     *
539
+     * If the backend supports this, it may allow for some speed-ups.
540
+     *
541
+     * @param mixed $addressBookId
542
+     * @param string[] $uris
543
+     * @return array
544
+     */
545
+    function getMultipleCards($addressBookId, array $uris) {
546
+        if (empty($uris)) {
547
+            return [];
548
+        }
549
+
550
+        $chunks = array_chunk($uris, 100);
551
+        $cards = [];
552
+
553
+        $query = $this->db->getQueryBuilder();
554
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata'])
555
+            ->from('cards')
556
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
557
+            ->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
558
+
559
+        foreach ($chunks as $uris) {
560
+            $query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
561
+            $result = $query->execute();
562
+
563
+            while ($row = $result->fetch()) {
564
+                $row['etag'] = '"' . $row['etag'] . '"';
565
+                $row['carddata'] = $this->readBlob($row['carddata']);
566
+                $cards[] = $row;
567
+            }
568
+            $result->closeCursor();
569
+        }
570
+        return $cards;
571
+    }
572
+
573
+    /**
574
+     * Creates a new card.
575
+     *
576
+     * The addressbook id will be passed as the first argument. This is the
577
+     * same id as it is returned from the getAddressBooksForUser method.
578
+     *
579
+     * The cardUri is a base uri, and doesn't include the full path. The
580
+     * cardData argument is the vcard body, and is passed as a string.
581
+     *
582
+     * It is possible to return an ETag from this method. This ETag is for the
583
+     * newly created resource, and must be enclosed with double quotes (that
584
+     * is, the string itself must contain the double quotes).
585
+     *
586
+     * You should only return the ETag if you store the carddata as-is. If a
587
+     * subsequent GET request on the same card does not have the same body,
588
+     * byte-by-byte and you did return an ETag here, clients tend to get
589
+     * confused.
590
+     *
591
+     * If you don't return an ETag, you can just return null.
592
+     *
593
+     * @param mixed $addressBookId
594
+     * @param string $cardUri
595
+     * @param string $cardData
596
+     * @return string
597
+     */
598
+    function createCard($addressBookId, $cardUri, $cardData) {
599
+        $etag = md5($cardData);
600
+
601
+        $query = $this->db->getQueryBuilder();
602
+        $query->insert('cards')
603
+            ->values([
604
+                'carddata' => $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB),
605
+                'uri' => $query->createNamedParameter($cardUri),
606
+                'lastmodified' => $query->createNamedParameter(time()),
607
+                'addressbookid' => $query->createNamedParameter($addressBookId),
608
+                'size' => $query->createNamedParameter(strlen($cardData)),
609
+                'etag' => $query->createNamedParameter($etag),
610
+            ])
611
+            ->execute();
612
+
613
+        $this->addChange($addressBookId, $cardUri, 1);
614
+        $this->updateProperties($addressBookId, $cardUri, $cardData);
615
+
616
+        if (!is_null($this->dispatcher)) {
617
+            $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
618
+                new GenericEvent(null, [
619
+                    'addressBookId' => $addressBookId,
620
+                    'cardUri' => $cardUri,
621
+                    'cardData' => $cardData]));
622
+        }
623
+
624
+        return '"' . $etag . '"';
625
+    }
626
+
627
+    /**
628
+     * Updates a card.
629
+     *
630
+     * The addressbook id will be passed as the first argument. This is the
631
+     * same id as it is returned from the getAddressBooksForUser method.
632
+     *
633
+     * The cardUri is a base uri, and doesn't include the full path. The
634
+     * cardData argument is the vcard body, and is passed as a string.
635
+     *
636
+     * It is possible to return an ETag from this method. This ETag should
637
+     * match that of the updated resource, and must be enclosed with double
638
+     * quotes (that is: the string itself must contain the actual quotes).
639
+     *
640
+     * You should only return the ETag if you store the carddata as-is. If a
641
+     * subsequent GET request on the same card does not have the same body,
642
+     * byte-by-byte and you did return an ETag here, clients tend to get
643
+     * confused.
644
+     *
645
+     * If you don't return an ETag, you can just return null.
646
+     *
647
+     * @param mixed $addressBookId
648
+     * @param string $cardUri
649
+     * @param string $cardData
650
+     * @return string
651
+     */
652
+    function updateCard($addressBookId, $cardUri, $cardData) {
653
+
654
+        $etag = md5($cardData);
655
+        $query = $this->db->getQueryBuilder();
656
+        $query->update('cards')
657
+            ->set('carddata', $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB))
658
+            ->set('lastmodified', $query->createNamedParameter(time()))
659
+            ->set('size', $query->createNamedParameter(strlen($cardData)))
660
+            ->set('etag', $query->createNamedParameter($etag))
661
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
662
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
663
+            ->execute();
664
+
665
+        $this->addChange($addressBookId, $cardUri, 2);
666
+        $this->updateProperties($addressBookId, $cardUri, $cardData);
667
+
668
+        if (!is_null($this->dispatcher)) {
669
+            $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
670
+                new GenericEvent(null, [
671
+                    'addressBookId' => $addressBookId,
672
+                    'cardUri' => $cardUri,
673
+                    'cardData' => $cardData]));
674
+        }
675
+
676
+        return '"' . $etag . '"';
677
+    }
678
+
679
+    /**
680
+     * Deletes a card
681
+     *
682
+     * @param mixed $addressBookId
683
+     * @param string $cardUri
684
+     * @return bool
685
+     */
686
+    function deleteCard($addressBookId, $cardUri) {
687
+        try {
688
+            $cardId = $this->getCardId($addressBookId, $cardUri);
689
+        } catch (\InvalidArgumentException $e) {
690
+            $cardId = null;
691
+        }
692
+        $query = $this->db->getQueryBuilder();
693
+        $ret = $query->delete('cards')
694
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
695
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
696
+            ->execute();
697
+
698
+        $this->addChange($addressBookId, $cardUri, 3);
699
+
700
+        if (!is_null($this->dispatcher)) {
701
+            $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
702
+                new GenericEvent(null, [
703
+                    'addressBookId' => $addressBookId,
704
+                    'cardUri' => $cardUri]));
705
+        }
706
+
707
+        if ($ret === 1) {
708
+            if ($cardId !== null) {
709
+                $this->purgeProperties($addressBookId, $cardId);
710
+            }
711
+            return true;
712
+        }
713
+
714
+        return false;
715
+    }
716
+
717
+    /**
718
+     * The getChanges method returns all the changes that have happened, since
719
+     * the specified syncToken in the specified address book.
720
+     *
721
+     * This function should return an array, such as the following:
722
+     *
723
+     * [
724
+     *   'syncToken' => 'The current synctoken',
725
+     *   'added'   => [
726
+     *      'new.txt',
727
+     *   ],
728
+     *   'modified'   => [
729
+     *      'modified.txt',
730
+     *   ],
731
+     *   'deleted' => [
732
+     *      'foo.php.bak',
733
+     *      'old.txt'
734
+     *   ]
735
+     * ];
736
+     *
737
+     * The returned syncToken property should reflect the *current* syncToken
738
+     * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
739
+     * property. This is needed here too, to ensure the operation is atomic.
740
+     *
741
+     * If the $syncToken argument is specified as null, this is an initial
742
+     * sync, and all members should be reported.
743
+     *
744
+     * The modified property is an array of nodenames that have changed since
745
+     * the last token.
746
+     *
747
+     * The deleted property is an array with nodenames, that have been deleted
748
+     * from collection.
749
+     *
750
+     * The $syncLevel argument is basically the 'depth' of the report. If it's
751
+     * 1, you only have to report changes that happened only directly in
752
+     * immediate descendants. If it's 2, it should also include changes from
753
+     * the nodes below the child collections. (grandchildren)
754
+     *
755
+     * The $limit argument allows a client to specify how many results should
756
+     * be returned at most. If the limit is not specified, it should be treated
757
+     * as infinite.
758
+     *
759
+     * If the limit (infinite or not) is higher than you're willing to return,
760
+     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
761
+     *
762
+     * If the syncToken is expired (due to data cleanup) or unknown, you must
763
+     * return null.
764
+     *
765
+     * The limit is 'suggestive'. You are free to ignore it.
766
+     *
767
+     * @param string $addressBookId
768
+     * @param string $syncToken
769
+     * @param int $syncLevel
770
+     * @param int $limit
771
+     * @return array
772
+     */
773
+    function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
774
+        // Current synctoken
775
+        $stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
776
+        $stmt->execute([ $addressBookId ]);
777
+        $currentToken = $stmt->fetchColumn(0);
778
+
779
+        if (is_null($currentToken)) return null;
780
+
781
+        $result = [
782
+            'syncToken' => $currentToken,
783
+            'added'     => [],
784
+            'modified'  => [],
785
+            'deleted'   => [],
786
+        ];
787
+
788
+        if ($syncToken) {
789
+
790
+            $query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
791
+            if ($limit>0) {
792
+                $query .= " `LIMIT` " . (int)$limit;
793
+            }
794
+
795
+            // Fetching all changes
796
+            $stmt = $this->db->prepare($query);
797
+            $stmt->execute([$syncToken, $currentToken, $addressBookId]);
798
+
799
+            $changes = [];
800
+
801
+            // This loop ensures that any duplicates are overwritten, only the
802
+            // last change on a node is relevant.
803
+            while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
804
+
805
+                $changes[$row['uri']] = $row['operation'];
806
+
807
+            }
808
+
809
+            foreach($changes as $uri => $operation) {
810
+
811
+                switch($operation) {
812
+                    case 1:
813
+                        $result['added'][] = $uri;
814
+                        break;
815
+                    case 2:
816
+                        $result['modified'][] = $uri;
817
+                        break;
818
+                    case 3:
819
+                        $result['deleted'][] = $uri;
820
+                        break;
821
+                }
822
+
823
+            }
824
+        } else {
825
+            // No synctoken supplied, this is the initial sync.
826
+            $query = "SELECT `uri` FROM `*PREFIX*cards` WHERE `addressbookid` = ?";
827
+            $stmt = $this->db->prepare($query);
828
+            $stmt->execute([$addressBookId]);
829
+
830
+            $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
831
+        }
832
+        return $result;
833
+    }
834
+
835
+    /**
836
+     * Adds a change record to the addressbookchanges table.
837
+     *
838
+     * @param mixed $addressBookId
839
+     * @param string $objectUri
840
+     * @param int $operation 1 = add, 2 = modify, 3 = delete
841
+     * @return void
842
+     */
843
+    protected function addChange($addressBookId, $objectUri, $operation) {
844
+        $sql = 'INSERT INTO `*PREFIX*addressbookchanges`(`uri`, `synctoken`, `addressbookid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*addressbooks` WHERE `id` = ?';
845
+        $stmt = $this->db->prepare($sql);
846
+        $stmt->execute([
847
+            $objectUri,
848
+            $addressBookId,
849
+            $operation,
850
+            $addressBookId
851
+        ]);
852
+        $stmt = $this->db->prepare('UPDATE `*PREFIX*addressbooks` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
853
+        $stmt->execute([
854
+            $addressBookId
855
+        ]);
856
+    }
857
+
858
+    private function readBlob($cardData) {
859
+        if (is_resource($cardData)) {
860
+            return stream_get_contents($cardData);
861
+        }
862
+
863
+        return $cardData;
864
+    }
865
+
866
+    /**
867
+     * @param IShareable $shareable
868
+     * @param string[] $add
869
+     * @param string[] $remove
870
+     */
871
+    public function updateShares(IShareable $shareable, $add, $remove) {
872
+        $this->sharingBackend->updateShares($shareable, $add, $remove);
873
+    }
874
+
875
+    /**
876
+     * search contact
877
+     *
878
+     * @param int $addressBookId
879
+     * @param string $pattern which should match within the $searchProperties
880
+     * @param array $searchProperties defines the properties within the query pattern should match
881
+     * @return array an array of contacts which are arrays of key-value-pairs
882
+     */
883
+    public function search($addressBookId, $pattern, $searchProperties) {
884
+        $query = $this->db->getQueryBuilder();
885
+        $query2 = $this->db->getQueryBuilder();
886
+        $query2->selectDistinct('cp.cardid')->from($this->dbCardsPropertiesTable, 'cp');
887
+        foreach ($searchProperties as $property) {
888
+            $query2->orWhere(
889
+                $query2->expr()->andX(
890
+                    $query2->expr()->eq('cp.name', $query->createNamedParameter($property)),
891
+                    $query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%'))
892
+                )
893
+            );
894
+        }
895
+        $query2->andWhere($query2->expr()->eq('cp.addressbookid', $query->createNamedParameter($addressBookId)));
896
+
897
+        $query->select('c.carddata', 'c.uri')->from($this->dbCardsTable, 'c')
898
+            ->where($query->expr()->in('c.id', $query->createFunction($query2->getSQL())));
899
+
900
+        $result = $query->execute();
901
+        $cards = $result->fetchAll();
902
+
903
+        $result->closeCursor();
904
+
905
+        return array_map(function($array) {
906
+            $array['carddata'] = $this->readBlob($array['carddata']);
907
+            return $array;
908
+        }, $cards);
909
+    }
910
+
911
+    /**
912
+     * @param int $bookId
913
+     * @param string $name
914
+     * @return array
915
+     */
916
+    public function collectCardProperties($bookId, $name) {
917
+        $query = $this->db->getQueryBuilder();
918
+        $result = $query->selectDistinct('value')
919
+            ->from($this->dbCardsPropertiesTable)
920
+            ->where($query->expr()->eq('name', $query->createNamedParameter($name)))
921
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($bookId)))
922
+            ->execute();
923
+
924
+        $all = $result->fetchAll(PDO::FETCH_COLUMN);
925
+        $result->closeCursor();
926
+
927
+        return $all;
928
+    }
929
+
930
+    /**
931
+     * get URI from a given contact
932
+     *
933
+     * @param int $id
934
+     * @return string
935
+     */
936
+    public function getCardUri($id) {
937
+        $query = $this->db->getQueryBuilder();
938
+        $query->select('uri')->from($this->dbCardsTable)
939
+                ->where($query->expr()->eq('id', $query->createParameter('id')))
940
+                ->setParameter('id', $id);
941
+
942
+        $result = $query->execute();
943
+        $uri = $result->fetch();
944
+        $result->closeCursor();
945
+
946
+        if (!isset($uri['uri'])) {
947
+            throw new \InvalidArgumentException('Card does not exists: ' . $id);
948
+        }
949
+
950
+        return $uri['uri'];
951
+    }
952
+
953
+    /**
954
+     * return contact with the given URI
955
+     *
956
+     * @param int $addressBookId
957
+     * @param string $uri
958
+     * @returns array
959
+     */
960
+    public function getContact($addressBookId, $uri) {
961
+        $result = [];
962
+        $query = $this->db->getQueryBuilder();
963
+        $query->select('*')->from($this->dbCardsTable)
964
+                ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
965
+                ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
966
+        $queryResult = $query->execute();
967
+        $contact = $queryResult->fetch();
968
+        $queryResult->closeCursor();
969
+
970
+        if (is_array($contact)) {
971
+            $result = $contact;
972
+        }
973
+
974
+        return $result;
975
+    }
976
+
977
+    /**
978
+     * Returns the list of people whom this address book is shared with.
979
+     *
980
+     * Every element in this array should have the following properties:
981
+     *   * href - Often a mailto: address
982
+     *   * commonName - Optional, for example a first + last name
983
+     *   * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
984
+     *   * readOnly - boolean
985
+     *   * summary - Optional, a description for the share
986
+     *
987
+     * @return array
988
+     */
989
+    public function getShares($addressBookId) {
990
+        return $this->sharingBackend->getShares($addressBookId);
991
+    }
992
+
993
+    /**
994
+     * update properties table
995
+     *
996
+     * @param int $addressBookId
997
+     * @param string $cardUri
998
+     * @param string $vCardSerialized
999
+     */
1000
+    protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) {
1001
+        $cardId = $this->getCardId($addressBookId, $cardUri);
1002
+        $vCard = $this->readCard($vCardSerialized);
1003
+
1004
+        $this->purgeProperties($addressBookId, $cardId);
1005
+
1006
+        $query = $this->db->getQueryBuilder();
1007
+        $query->insert($this->dbCardsPropertiesTable)
1008
+            ->values(
1009
+                [
1010
+                    'addressbookid' => $query->createNamedParameter($addressBookId),
1011
+                    'cardid' => $query->createNamedParameter($cardId),
1012
+                    'name' => $query->createParameter('name'),
1013
+                    'value' => $query->createParameter('value'),
1014
+                    'preferred' => $query->createParameter('preferred')
1015
+                ]
1016
+            );
1017
+
1018
+        foreach ($vCard->children() as $property) {
1019
+            if(!in_array($property->name, self::$indexProperties)) {
1020
+                continue;
1021
+            }
1022
+            $preferred = 0;
1023
+            foreach($property->parameters as $parameter) {
1024
+                if ($parameter->name == 'TYPE' && strtoupper($parameter->getValue()) == 'PREF') {
1025
+                    $preferred = 1;
1026
+                    break;
1027
+                }
1028
+            }
1029
+            $query->setParameter('name', $property->name);
1030
+            $query->setParameter('value', substr($property->getValue(), 0, 254));
1031
+            $query->setParameter('preferred', $preferred);
1032
+            $query->execute();
1033
+        }
1034
+    }
1035
+
1036
+    /**
1037
+     * read vCard data into a vCard object
1038
+     *
1039
+     * @param string $cardData
1040
+     * @return VCard
1041
+     */
1042
+    protected function readCard($cardData) {
1043
+        return  Reader::read($cardData);
1044
+    }
1045
+
1046
+    /**
1047
+     * delete all properties from a given card
1048
+     *
1049
+     * @param int $addressBookId
1050
+     * @param int $cardId
1051
+     */
1052
+    protected function purgeProperties($addressBookId, $cardId) {
1053
+        $query = $this->db->getQueryBuilder();
1054
+        $query->delete($this->dbCardsPropertiesTable)
1055
+            ->where($query->expr()->eq('cardid', $query->createNamedParameter($cardId)))
1056
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1057
+        $query->execute();
1058
+    }
1059
+
1060
+    /**
1061
+     * get ID from a given contact
1062
+     *
1063
+     * @param int $addressBookId
1064
+     * @param string $uri
1065
+     * @return int
1066
+     */
1067
+    protected function getCardId($addressBookId, $uri) {
1068
+        $query = $this->db->getQueryBuilder();
1069
+        $query->select('id')->from($this->dbCardsTable)
1070
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1071
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1072
+
1073
+        $result = $query->execute();
1074
+        $cardIds = $result->fetch();
1075
+        $result->closeCursor();
1076
+
1077
+        if (!isset($cardIds['id'])) {
1078
+            throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1079
+        }
1080
+
1081
+        return (int)$cardIds['id'];
1082
+    }
1083
+
1084
+    /**
1085
+     * For shared address books the sharee is set in the ACL of the address book
1086
+     * @param $addressBookId
1087
+     * @param $acl
1088
+     * @return array
1089
+     */
1090
+    public function applyShareAcl($addressBookId, $acl) {
1091
+        return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
1092
+    }
1093
+
1094
+    private function convertPrincipal($principalUri, $toV2) {
1095
+        if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
1096
+            list(, $name) = URLUtil::splitPath($principalUri);
1097
+            if ($toV2 === true) {
1098
+                return "principals/users/$name";
1099
+            }
1100
+            return "principals/$name";
1101
+        }
1102
+        return $principalUri;
1103
+    }
1104
+
1105
+    private function addOwnerPrincipal(&$addressbookInfo) {
1106
+        $ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1107
+        $displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1108
+        if (isset($addressbookInfo[$ownerPrincipalKey])) {
1109
+            $uri = $addressbookInfo[$ownerPrincipalKey];
1110
+        } else {
1111
+            $uri = $addressbookInfo['principaluri'];
1112
+        }
1113
+
1114
+        $principalInformation = $this->principalBackend->getPrincipalByPath($uri);
1115
+        if (isset($principalInformation['{DAV:}displayname'])) {
1116
+            $addressbookInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
1117
+        }
1118
+    }
1119 1119
 }
Please login to merge, or discard this patch.
Spacing   +49 added lines, -49 removed lines patch added patch discarded remove patch
@@ -114,7 +114,7 @@  discard block
 block discarded – undo
114 114
 			->from('addressbooks')
115 115
 			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
116 116
 
117
-		return (int)$query->execute()->fetchColumn();
117
+		return (int) $query->execute()->fetchColumn();
118 118
 	}
119 119
 
120 120
 	/**
@@ -145,15 +145,15 @@  discard block
 block discarded – undo
145 145
 		$addressBooks = [];
146 146
 
147 147
 		$result = $query->execute();
148
-		while($row = $result->fetch()) {
148
+		while ($row = $result->fetch()) {
149 149
 			$addressBooks[$row['id']] = [
150 150
 				'id'  => $row['id'],
151 151
 				'uri' => $row['uri'],
152 152
 				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
153 153
 				'{DAV:}displayname' => $row['displayname'],
154
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
154
+				'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
155 155
 				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
156
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
156
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
157 157
 			];
158 158
 
159 159
 			$this->addOwnerPrincipal($addressBooks[$row['id']]);
@@ -162,7 +162,7 @@  discard block
 block discarded – undo
162 162
 
163 163
 		// query for shared calendars
164 164
 		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
165
-		$principals[]= $principalUri;
165
+		$principals[] = $principalUri;
166 166
 
167 167
 		$query = $this->db->getQueryBuilder();
168 168
 		$result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
@@ -174,8 +174,8 @@  discard block
 block discarded – undo
174 174
 			->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
175 175
 			->execute();
176 176
 
177
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
178
-		while($row = $result->fetch()) {
177
+		$readOnlyPropertyName = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}read-only';
178
+		while ($row = $result->fetch()) {
179 179
 			if ($row['principaluri'] === $principalUri) {
180 180
 				continue;
181 181
 			}
@@ -194,18 +194,18 @@  discard block
 block discarded – undo
194 194
 			}
195 195
 
196 196
 			list(, $name) = URLUtil::splitPath($row['principaluri']);
197
-			$uri = $row['uri'] . '_shared_by_' . $name;
198
-			$displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
197
+			$uri = $row['uri'].'_shared_by_'.$name;
198
+			$displayName = $row['displayname'].' ('.$this->getUserDisplayName($name).')';
199 199
 
200 200
 			$addressBooks[$row['id']] = [
201 201
 				'id'  => $row['id'],
202 202
 				'uri' => $uri,
203 203
 				'principaluri' => $principalUriOriginal,
204 204
 				'{DAV:}displayname' => $displayName,
205
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
205
+				'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
206 206
 				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
207
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
208
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
207
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
208
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $row['principaluri'],
209 209
 				$readOnlyPropertyName => $readOnly,
210 210
 			];
211 211
 
@@ -227,15 +227,15 @@  discard block
 block discarded – undo
227 227
 		$addressBooks = [];
228 228
 
229 229
 		$result = $query->execute();
230
-		while($row = $result->fetch()) {
230
+		while ($row = $result->fetch()) {
231 231
 			$addressBooks[$row['id']] = [
232 232
 				'id'  => $row['id'],
233 233
 				'uri' => $row['uri'],
234 234
 				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
235 235
 				'{DAV:}displayname' => $row['displayname'],
236
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
236
+				'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
237 237
 				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
238
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
238
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
239 239
 			];
240 240
 
241 241
 			$this->addOwnerPrincipal($addressBooks[$row['id']]);
@@ -280,9 +280,9 @@  discard block
 block discarded – undo
280 280
 			'uri' => $row['uri'],
281 281
 			'principaluri' => $row['principaluri'],
282 282
 			'{DAV:}displayname' => $row['displayname'],
283
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
283
+			'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
284 284
 			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
285
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
285
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
286 286
 		];
287 287
 
288 288
 		$this->addOwnerPrincipal($addressBook);
@@ -314,9 +314,9 @@  discard block
 block discarded – undo
314 314
 			'uri' => $row['uri'],
315 315
 			'principaluri' => $row['principaluri'],
316 316
 			'{DAV:}displayname' => $row['displayname'],
317
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
317
+			'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
318 318
 			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
319
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
319
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
320 320
 		];
321 321
 
322 322
 		$this->addOwnerPrincipal($addressBook);
@@ -343,19 +343,19 @@  discard block
 block discarded – undo
343 343
 	function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
344 344
 		$supportedProperties = [
345 345
 			'{DAV:}displayname',
346
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description',
346
+			'{'.Plugin::NS_CARDDAV.'}addressbook-description',
347 347
 		];
348 348
 
349 349
 		$propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) {
350 350
 
351 351
 			$updates = [];
352
-			foreach($mutations as $property=>$newValue) {
352
+			foreach ($mutations as $property=>$newValue) {
353 353
 
354
-				switch($property) {
354
+				switch ($property) {
355 355
 					case '{DAV:}displayname' :
356 356
 						$updates['displayname'] = $newValue;
357 357
 						break;
358
-					case '{' . Plugin::NS_CARDDAV . '}addressbook-description' :
358
+					case '{'.Plugin::NS_CARDDAV.'}addressbook-description' :
359 359
 						$updates['description'] = $newValue;
360 360
 						break;
361 361
 				}
@@ -363,7 +363,7 @@  discard block
 block discarded – undo
363 363
 			$query = $this->db->getQueryBuilder();
364 364
 			$query->update('addressbooks');
365 365
 
366
-			foreach($updates as $key=>$value) {
366
+			foreach ($updates as $key=>$value) {
367 367
 				$query->set($key, $query->createNamedParameter($value));
368 368
 			}
369 369
 			$query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
@@ -394,24 +394,24 @@  discard block
 block discarded – undo
394 394
 			'synctoken' => 1
395 395
 		];
396 396
 
397
-		foreach($properties as $property=>$newValue) {
397
+		foreach ($properties as $property=>$newValue) {
398 398
 
399
-			switch($property) {
399
+			switch ($property) {
400 400
 				case '{DAV:}displayname' :
401 401
 					$values['displayname'] = $newValue;
402 402
 					break;
403
-				case '{' . Plugin::NS_CARDDAV . '}addressbook-description' :
403
+				case '{'.Plugin::NS_CARDDAV.'}addressbook-description' :
404 404
 					$values['description'] = $newValue;
405 405
 					break;
406 406
 				default :
407
-					throw new BadRequest('Unknown property: ' . $property);
407
+					throw new BadRequest('Unknown property: '.$property);
408 408
 			}
409 409
 
410 410
 		}
411 411
 
412 412
 		// Fallback to make sure the displayname is set. Some clients may refuse
413 413
 		// to work with addressbooks not having a displayname.
414
-		if(is_null($values['displayname'])) {
414
+		if (is_null($values['displayname'])) {
415 415
 			$values['displayname'] = $url;
416 416
 		}
417 417
 
@@ -489,8 +489,8 @@  discard block
 block discarded – undo
489 489
 		$cards = [];
490 490
 
491 491
 		$result = $query->execute();
492
-		while($row = $result->fetch()) {
493
-			$row['etag'] = '"' . $row['etag'] . '"';
492
+		while ($row = $result->fetch()) {
493
+			$row['etag'] = '"'.$row['etag'].'"';
494 494
 			$row['carddata'] = $this->readBlob($row['carddata']);
495 495
 			$cards[] = $row;
496 496
 		}
@@ -524,7 +524,7 @@  discard block
 block discarded – undo
524 524
 		if (!$row) {
525 525
 			return false;
526 526
 		}
527
-		$row['etag'] = '"' . $row['etag'] . '"';
527
+		$row['etag'] = '"'.$row['etag'].'"';
528 528
 		$row['carddata'] = $this->readBlob($row['carddata']);
529 529
 
530 530
 		return $row;
@@ -561,7 +561,7 @@  discard block
 block discarded – undo
561 561
 			$result = $query->execute();
562 562
 
563 563
 			while ($row = $result->fetch()) {
564
-				$row['etag'] = '"' . $row['etag'] . '"';
564
+				$row['etag'] = '"'.$row['etag'].'"';
565 565
 				$row['carddata'] = $this->readBlob($row['carddata']);
566 566
 				$cards[] = $row;
567 567
 			}
@@ -621,7 +621,7 @@  discard block
 block discarded – undo
621 621
 					'cardData' => $cardData]));
622 622
 		}
623 623
 
624
-		return '"' . $etag . '"';
624
+		return '"'.$etag.'"';
625 625
 	}
626 626
 
627 627
 	/**
@@ -673,7 +673,7 @@  discard block
 block discarded – undo
673 673
 					'cardData' => $cardData]));
674 674
 		}
675 675
 
676
-		return '"' . $etag . '"';
676
+		return '"'.$etag.'"';
677 677
 	}
678 678
 
679 679
 	/**
@@ -773,7 +773,7 @@  discard block
 block discarded – undo
773 773
 	function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
774 774
 		// Current synctoken
775 775
 		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
776
-		$stmt->execute([ $addressBookId ]);
776
+		$stmt->execute([$addressBookId]);
777 777
 		$currentToken = $stmt->fetchColumn(0);
778 778
 
779 779
 		if (is_null($currentToken)) return null;
@@ -788,8 +788,8 @@  discard block
 block discarded – undo
788 788
 		if ($syncToken) {
789 789
 
790 790
 			$query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
791
-			if ($limit>0) {
792
-				$query .= " `LIMIT` " . (int)$limit;
791
+			if ($limit > 0) {
792
+				$query .= " `LIMIT` ".(int) $limit;
793 793
 			}
794 794
 
795 795
 			// Fetching all changes
@@ -800,15 +800,15 @@  discard block
 block discarded – undo
800 800
 
801 801
 			// This loop ensures that any duplicates are overwritten, only the
802 802
 			// last change on a node is relevant.
803
-			while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
803
+			while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
804 804
 
805 805
 				$changes[$row['uri']] = $row['operation'];
806 806
 
807 807
 			}
808 808
 
809
-			foreach($changes as $uri => $operation) {
809
+			foreach ($changes as $uri => $operation) {
810 810
 
811
-				switch($operation) {
811
+				switch ($operation) {
812 812
 					case 1:
813 813
 						$result['added'][] = $uri;
814 814
 						break;
@@ -888,7 +888,7 @@  discard block
 block discarded – undo
888 888
 			$query2->orWhere(
889 889
 				$query2->expr()->andX(
890 890
 					$query2->expr()->eq('cp.name', $query->createNamedParameter($property)),
891
-					$query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%'))
891
+					$query2->expr()->ilike('cp.value', $query->createNamedParameter('%'.$this->db->escapeLikeParameter($pattern).'%'))
892 892
 				)
893 893
 			);
894 894
 		}
@@ -944,7 +944,7 @@  discard block
 block discarded – undo
944 944
 		$result->closeCursor();
945 945
 
946 946
 		if (!isset($uri['uri'])) {
947
-			throw new \InvalidArgumentException('Card does not exists: ' . $id);
947
+			throw new \InvalidArgumentException('Card does not exists: '.$id);
948 948
 		}
949 949
 
950 950
 		return $uri['uri'];
@@ -1016,11 +1016,11 @@  discard block
 block discarded – undo
1016 1016
 			);
1017 1017
 
1018 1018
 		foreach ($vCard->children() as $property) {
1019
-			if(!in_array($property->name, self::$indexProperties)) {
1019
+			if (!in_array($property->name, self::$indexProperties)) {
1020 1020
 				continue;
1021 1021
 			}
1022 1022
 			$preferred = 0;
1023
-			foreach($property->parameters as $parameter) {
1023
+			foreach ($property->parameters as $parameter) {
1024 1024
 				if ($parameter->name == 'TYPE' && strtoupper($parameter->getValue()) == 'PREF') {
1025 1025
 					$preferred = 1;
1026 1026
 					break;
@@ -1075,10 +1075,10 @@  discard block
 block discarded – undo
1075 1075
 		$result->closeCursor();
1076 1076
 
1077 1077
 		if (!isset($cardIds['id'])) {
1078
-			throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1078
+			throw new \InvalidArgumentException('Card does not exists: '.$uri);
1079 1079
 		}
1080 1080
 
1081
-		return (int)$cardIds['id'];
1081
+		return (int) $cardIds['id'];
1082 1082
 	}
1083 1083
 
1084 1084
 	/**
@@ -1103,8 +1103,8 @@  discard block
 block discarded – undo
1103 1103
 	}
1104 1104
 
1105 1105
 	private function addOwnerPrincipal(&$addressbookInfo) {
1106
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1107
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1106
+		$ownerPrincipalKey = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal';
1107
+		$displaynameKey = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD.'}owner-displayname';
1108 1108
 		if (isset($addressbookInfo[$ownerPrincipalKey])) {
1109 1109
 			$uri = $addressbookInfo[$ownerPrincipalKey];
1110 1110
 		} else {
Please login to merge, or discard this patch.
apps/dav/lib/CalDAV/CalDavBackend.php 2 patches
Indentation   +1785 added lines, -1785 removed lines patch added patch discarded remove patch
@@ -59,1790 +59,1790 @@
 block discarded – undo
59 59
  */
60 60
 class CalDavBackend extends AbstractBackend implements SyncSupport, SubscriptionSupport, SchedulingSupport {
61 61
 
62
-	const PERSONAL_CALENDAR_URI = 'personal';
63
-	const PERSONAL_CALENDAR_NAME = 'Personal';
64
-
65
-	/**
66
-	 * We need to specify a max date, because we need to stop *somewhere*
67
-	 *
68
-	 * On 32 bit system the maximum for a signed integer is 2147483647, so
69
-	 * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
70
-	 * in 2038-01-19 to avoid problems when the date is converted
71
-	 * to a unix timestamp.
72
-	 */
73
-	const MAX_DATE = '2038-01-01';
74
-
75
-	const ACCESS_PUBLIC = 4;
76
-	const CLASSIFICATION_PUBLIC = 0;
77
-	const CLASSIFICATION_PRIVATE = 1;
78
-	const CLASSIFICATION_CONFIDENTIAL = 2;
79
-
80
-	/**
81
-	 * List of CalDAV properties, and how they map to database field names
82
-	 * Add your own properties by simply adding on to this array.
83
-	 *
84
-	 * Note that only string-based properties are supported here.
85
-	 *
86
-	 * @var array
87
-	 */
88
-	public $propertyMap = [
89
-		'{DAV:}displayname'                          => 'displayname',
90
-		'{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
91
-		'{urn:ietf:params:xml:ns:caldav}calendar-timezone'    => 'timezone',
92
-		'{http://apple.com/ns/ical/}calendar-order'  => 'calendarorder',
93
-		'{http://apple.com/ns/ical/}calendar-color'  => 'calendarcolor',
94
-	];
95
-
96
-	/**
97
-	 * List of subscription properties, and how they map to database field names.
98
-	 *
99
-	 * @var array
100
-	 */
101
-	public $subscriptionPropertyMap = [
102
-		'{DAV:}displayname'                                           => 'displayname',
103
-		'{http://apple.com/ns/ical/}refreshrate'                      => 'refreshrate',
104
-		'{http://apple.com/ns/ical/}calendar-order'                   => 'calendarorder',
105
-		'{http://apple.com/ns/ical/}calendar-color'                   => 'calendarcolor',
106
-		'{http://calendarserver.org/ns/}subscribed-strip-todos'       => 'striptodos',
107
-		'{http://calendarserver.org/ns/}subscribed-strip-alarms'      => 'stripalarms',
108
-		'{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
109
-	];
110
-
111
-	/**
112
-	 * @var string[] Map of uid => display name
113
-	 */
114
-	protected $userDisplayNames;
115
-
116
-	/** @var IDBConnection */
117
-	private $db;
118
-
119
-	/** @var Backend */
120
-	private $sharingBackend;
121
-
122
-	/** @var Principal */
123
-	private $principalBackend;
124
-
125
-	/** @var IUserManager */
126
-	private $userManager;
127
-
128
-	/** @var ISecureRandom */
129
-	private $random;
130
-
131
-	/** @var EventDispatcherInterface */
132
-	private $dispatcher;
133
-
134
-	/** @var bool */
135
-	private $legacyEndpoint;
136
-
137
-	/**
138
-	 * CalDavBackend constructor.
139
-	 *
140
-	 * @param IDBConnection $db
141
-	 * @param Principal $principalBackend
142
-	 * @param IUserManager $userManager
143
-	 * @param ISecureRandom $random
144
-	 * @param EventDispatcherInterface $dispatcher
145
-	 * @param bool $legacyEndpoint
146
-	 */
147
-	public function __construct(IDBConnection $db,
148
-								Principal $principalBackend,
149
-								IUserManager $userManager,
150
-								ISecureRandom $random,
151
-								EventDispatcherInterface $dispatcher,
152
-								$legacyEndpoint = false) {
153
-		$this->db = $db;
154
-		$this->principalBackend = $principalBackend;
155
-		$this->userManager = $userManager;
156
-		$this->sharingBackend = new Backend($this->db, $principalBackend, 'calendar');
157
-		$this->random = $random;
158
-		$this->dispatcher = $dispatcher;
159
-		$this->legacyEndpoint = $legacyEndpoint;
160
-	}
161
-
162
-	/**
163
-	 * Return the number of calendars for a principal
164
-	 *
165
-	 * By default this excludes the automatically generated birthday calendar
166
-	 *
167
-	 * @param $principalUri
168
-	 * @param bool $excludeBirthday
169
-	 * @return int
170
-	 */
171
-	public function getCalendarsForUserCount($principalUri, $excludeBirthday = true) {
172
-		$principalUri = $this->convertPrincipal($principalUri, true);
173
-		$query = $this->db->getQueryBuilder();
174
-		$query->select($query->createFunction('COUNT(*)'))
175
-			->from('calendars')
176
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
177
-
178
-		if ($excludeBirthday) {
179
-			$query->andWhere($query->expr()->neq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)));
180
-		}
181
-
182
-		return (int)$query->execute()->fetchColumn();
183
-	}
184
-
185
-	/**
186
-	 * Returns a list of calendars for a principal.
187
-	 *
188
-	 * Every project is an array with the following keys:
189
-	 *  * id, a unique id that will be used by other functions to modify the
190
-	 *    calendar. This can be the same as the uri or a database key.
191
-	 *  * uri, which the basename of the uri with which the calendar is
192
-	 *    accessed.
193
-	 *  * principaluri. The owner of the calendar. Almost always the same as
194
-	 *    principalUri passed to this method.
195
-	 *
196
-	 * Furthermore it can contain webdav properties in clark notation. A very
197
-	 * common one is '{DAV:}displayname'.
198
-	 *
199
-	 * Many clients also require:
200
-	 * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
201
-	 * For this property, you can just return an instance of
202
-	 * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
203
-	 *
204
-	 * If you return {http://sabredav.org/ns}read-only and set the value to 1,
205
-	 * ACL will automatically be put in read-only mode.
206
-	 *
207
-	 * @param string $principalUri
208
-	 * @return array
209
-	 */
210
-	function getCalendarsForUser($principalUri) {
211
-		$principalUriOriginal = $principalUri;
212
-		$principalUri = $this->convertPrincipal($principalUri, true);
213
-		$fields = array_values($this->propertyMap);
214
-		$fields[] = 'id';
215
-		$fields[] = 'uri';
216
-		$fields[] = 'synctoken';
217
-		$fields[] = 'components';
218
-		$fields[] = 'principaluri';
219
-		$fields[] = 'transparent';
220
-
221
-		// Making fields a comma-delimited list
222
-		$query = $this->db->getQueryBuilder();
223
-		$query->select($fields)->from('calendars')
224
-				->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
225
-				->orderBy('calendarorder', 'ASC');
226
-		$stmt = $query->execute();
227
-
228
-		$calendars = [];
229
-		while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
230
-
231
-			$components = [];
232
-			if ($row['components']) {
233
-				$components = explode(',',$row['components']);
234
-			}
235
-
236
-			$calendar = [
237
-				'id' => $row['id'],
238
-				'uri' => $row['uri'],
239
-				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
240
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
241
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
242
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
243
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
244
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
245
-			];
246
-
247
-			foreach($this->propertyMap as $xmlName=>$dbName) {
248
-				$calendar[$xmlName] = $row[$dbName];
249
-			}
250
-
251
-			$this->addOwnerPrincipal($calendar);
252
-
253
-			if (!isset($calendars[$calendar['id']])) {
254
-				$calendars[$calendar['id']] = $calendar;
255
-			}
256
-		}
257
-
258
-		$stmt->closeCursor();
259
-
260
-		// query for shared calendars
261
-		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
262
-		$principals[]= $principalUri;
263
-
264
-		$fields = array_values($this->propertyMap);
265
-		$fields[] = 'a.id';
266
-		$fields[] = 'a.uri';
267
-		$fields[] = 'a.synctoken';
268
-		$fields[] = 'a.components';
269
-		$fields[] = 'a.principaluri';
270
-		$fields[] = 'a.transparent';
271
-		$fields[] = 's.access';
272
-		$query = $this->db->getQueryBuilder();
273
-		$result = $query->select($fields)
274
-			->from('dav_shares', 's')
275
-			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
276
-			->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
277
-			->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
278
-			->setParameter('type', 'calendar')
279
-			->setParameter('principaluri', $principals, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
280
-			->execute();
281
-
282
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
283
-		while($row = $result->fetch()) {
284
-			if ($row['principaluri'] === $principalUri) {
285
-				continue;
286
-			}
287
-
288
-			$readOnly = (int) $row['access'] === Backend::ACCESS_READ;
289
-			if (isset($calendars[$row['id']])) {
290
-				if ($readOnly) {
291
-					// New share can not have more permissions then the old one.
292
-					continue;
293
-				}
294
-				if (isset($calendars[$row['id']][$readOnlyPropertyName]) &&
295
-					$calendars[$row['id']][$readOnlyPropertyName] === 0) {
296
-					// Old share is already read-write, no more permissions can be gained
297
-					continue;
298
-				}
299
-			}
300
-
301
-			list(, $name) = URLUtil::splitPath($row['principaluri']);
302
-			$uri = $row['uri'] . '_shared_by_' . $name;
303
-			$row['displayname'] = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
304
-			$components = [];
305
-			if ($row['components']) {
306
-				$components = explode(',',$row['components']);
307
-			}
308
-			$calendar = [
309
-				'id' => $row['id'],
310
-				'uri' => $uri,
311
-				'principaluri' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
312
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
313
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
314
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
315
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
316
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
317
-				$readOnlyPropertyName => $readOnly,
318
-			];
319
-
320
-			foreach($this->propertyMap as $xmlName=>$dbName) {
321
-				$calendar[$xmlName] = $row[$dbName];
322
-			}
323
-
324
-			$this->addOwnerPrincipal($calendar);
325
-
326
-			$calendars[$calendar['id']] = $calendar;
327
-		}
328
-		$result->closeCursor();
329
-
330
-		return array_values($calendars);
331
-	}
332
-
333
-	public function getUsersOwnCalendars($principalUri) {
334
-		$principalUri = $this->convertPrincipal($principalUri, true);
335
-		$fields = array_values($this->propertyMap);
336
-		$fields[] = 'id';
337
-		$fields[] = 'uri';
338
-		$fields[] = 'synctoken';
339
-		$fields[] = 'components';
340
-		$fields[] = 'principaluri';
341
-		$fields[] = 'transparent';
342
-		// Making fields a comma-delimited list
343
-		$query = $this->db->getQueryBuilder();
344
-		$query->select($fields)->from('calendars')
345
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
346
-			->orderBy('calendarorder', 'ASC');
347
-		$stmt = $query->execute();
348
-		$calendars = [];
349
-		while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
350
-			$components = [];
351
-			if ($row['components']) {
352
-				$components = explode(',',$row['components']);
353
-			}
354
-			$calendar = [
355
-				'id' => $row['id'],
356
-				'uri' => $row['uri'],
357
-				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
358
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
359
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
360
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
361
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
362
-			];
363
-			foreach($this->propertyMap as $xmlName=>$dbName) {
364
-				$calendar[$xmlName] = $row[$dbName];
365
-			}
366
-
367
-			$this->addOwnerPrincipal($calendar);
368
-
369
-			if (!isset($calendars[$calendar['id']])) {
370
-				$calendars[$calendar['id']] = $calendar;
371
-			}
372
-		}
373
-		$stmt->closeCursor();
374
-		return array_values($calendars);
375
-	}
376
-
377
-
378
-	private function getUserDisplayName($uid) {
379
-		if (!isset($this->userDisplayNames[$uid])) {
380
-			$user = $this->userManager->get($uid);
381
-
382
-			if ($user instanceof IUser) {
383
-				$this->userDisplayNames[$uid] = $user->getDisplayName();
384
-			} else {
385
-				$this->userDisplayNames[$uid] = $uid;
386
-			}
387
-		}
388
-
389
-		return $this->userDisplayNames[$uid];
390
-	}
62
+    const PERSONAL_CALENDAR_URI = 'personal';
63
+    const PERSONAL_CALENDAR_NAME = 'Personal';
64
+
65
+    /**
66
+     * We need to specify a max date, because we need to stop *somewhere*
67
+     *
68
+     * On 32 bit system the maximum for a signed integer is 2147483647, so
69
+     * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
70
+     * in 2038-01-19 to avoid problems when the date is converted
71
+     * to a unix timestamp.
72
+     */
73
+    const MAX_DATE = '2038-01-01';
74
+
75
+    const ACCESS_PUBLIC = 4;
76
+    const CLASSIFICATION_PUBLIC = 0;
77
+    const CLASSIFICATION_PRIVATE = 1;
78
+    const CLASSIFICATION_CONFIDENTIAL = 2;
79
+
80
+    /**
81
+     * List of CalDAV properties, and how they map to database field names
82
+     * Add your own properties by simply adding on to this array.
83
+     *
84
+     * Note that only string-based properties are supported here.
85
+     *
86
+     * @var array
87
+     */
88
+    public $propertyMap = [
89
+        '{DAV:}displayname'                          => 'displayname',
90
+        '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
91
+        '{urn:ietf:params:xml:ns:caldav}calendar-timezone'    => 'timezone',
92
+        '{http://apple.com/ns/ical/}calendar-order'  => 'calendarorder',
93
+        '{http://apple.com/ns/ical/}calendar-color'  => 'calendarcolor',
94
+    ];
95
+
96
+    /**
97
+     * List of subscription properties, and how they map to database field names.
98
+     *
99
+     * @var array
100
+     */
101
+    public $subscriptionPropertyMap = [
102
+        '{DAV:}displayname'                                           => 'displayname',
103
+        '{http://apple.com/ns/ical/}refreshrate'                      => 'refreshrate',
104
+        '{http://apple.com/ns/ical/}calendar-order'                   => 'calendarorder',
105
+        '{http://apple.com/ns/ical/}calendar-color'                   => 'calendarcolor',
106
+        '{http://calendarserver.org/ns/}subscribed-strip-todos'       => 'striptodos',
107
+        '{http://calendarserver.org/ns/}subscribed-strip-alarms'      => 'stripalarms',
108
+        '{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
109
+    ];
110
+
111
+    /**
112
+     * @var string[] Map of uid => display name
113
+     */
114
+    protected $userDisplayNames;
115
+
116
+    /** @var IDBConnection */
117
+    private $db;
118
+
119
+    /** @var Backend */
120
+    private $sharingBackend;
121
+
122
+    /** @var Principal */
123
+    private $principalBackend;
124
+
125
+    /** @var IUserManager */
126
+    private $userManager;
127
+
128
+    /** @var ISecureRandom */
129
+    private $random;
130
+
131
+    /** @var EventDispatcherInterface */
132
+    private $dispatcher;
133
+
134
+    /** @var bool */
135
+    private $legacyEndpoint;
136
+
137
+    /**
138
+     * CalDavBackend constructor.
139
+     *
140
+     * @param IDBConnection $db
141
+     * @param Principal $principalBackend
142
+     * @param IUserManager $userManager
143
+     * @param ISecureRandom $random
144
+     * @param EventDispatcherInterface $dispatcher
145
+     * @param bool $legacyEndpoint
146
+     */
147
+    public function __construct(IDBConnection $db,
148
+                                Principal $principalBackend,
149
+                                IUserManager $userManager,
150
+                                ISecureRandom $random,
151
+                                EventDispatcherInterface $dispatcher,
152
+                                $legacyEndpoint = false) {
153
+        $this->db = $db;
154
+        $this->principalBackend = $principalBackend;
155
+        $this->userManager = $userManager;
156
+        $this->sharingBackend = new Backend($this->db, $principalBackend, 'calendar');
157
+        $this->random = $random;
158
+        $this->dispatcher = $dispatcher;
159
+        $this->legacyEndpoint = $legacyEndpoint;
160
+    }
161
+
162
+    /**
163
+     * Return the number of calendars for a principal
164
+     *
165
+     * By default this excludes the automatically generated birthday calendar
166
+     *
167
+     * @param $principalUri
168
+     * @param bool $excludeBirthday
169
+     * @return int
170
+     */
171
+    public function getCalendarsForUserCount($principalUri, $excludeBirthday = true) {
172
+        $principalUri = $this->convertPrincipal($principalUri, true);
173
+        $query = $this->db->getQueryBuilder();
174
+        $query->select($query->createFunction('COUNT(*)'))
175
+            ->from('calendars')
176
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
177
+
178
+        if ($excludeBirthday) {
179
+            $query->andWhere($query->expr()->neq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)));
180
+        }
181
+
182
+        return (int)$query->execute()->fetchColumn();
183
+    }
184
+
185
+    /**
186
+     * Returns a list of calendars for a principal.
187
+     *
188
+     * Every project is an array with the following keys:
189
+     *  * id, a unique id that will be used by other functions to modify the
190
+     *    calendar. This can be the same as the uri or a database key.
191
+     *  * uri, which the basename of the uri with which the calendar is
192
+     *    accessed.
193
+     *  * principaluri. The owner of the calendar. Almost always the same as
194
+     *    principalUri passed to this method.
195
+     *
196
+     * Furthermore it can contain webdav properties in clark notation. A very
197
+     * common one is '{DAV:}displayname'.
198
+     *
199
+     * Many clients also require:
200
+     * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
201
+     * For this property, you can just return an instance of
202
+     * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
203
+     *
204
+     * If you return {http://sabredav.org/ns}read-only and set the value to 1,
205
+     * ACL will automatically be put in read-only mode.
206
+     *
207
+     * @param string $principalUri
208
+     * @return array
209
+     */
210
+    function getCalendarsForUser($principalUri) {
211
+        $principalUriOriginal = $principalUri;
212
+        $principalUri = $this->convertPrincipal($principalUri, true);
213
+        $fields = array_values($this->propertyMap);
214
+        $fields[] = 'id';
215
+        $fields[] = 'uri';
216
+        $fields[] = 'synctoken';
217
+        $fields[] = 'components';
218
+        $fields[] = 'principaluri';
219
+        $fields[] = 'transparent';
220
+
221
+        // Making fields a comma-delimited list
222
+        $query = $this->db->getQueryBuilder();
223
+        $query->select($fields)->from('calendars')
224
+                ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
225
+                ->orderBy('calendarorder', 'ASC');
226
+        $stmt = $query->execute();
227
+
228
+        $calendars = [];
229
+        while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
230
+
231
+            $components = [];
232
+            if ($row['components']) {
233
+                $components = explode(',',$row['components']);
234
+            }
235
+
236
+            $calendar = [
237
+                'id' => $row['id'],
238
+                'uri' => $row['uri'],
239
+                'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
240
+                '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
241
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
242
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
243
+                '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
244
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
245
+            ];
246
+
247
+            foreach($this->propertyMap as $xmlName=>$dbName) {
248
+                $calendar[$xmlName] = $row[$dbName];
249
+            }
250
+
251
+            $this->addOwnerPrincipal($calendar);
252
+
253
+            if (!isset($calendars[$calendar['id']])) {
254
+                $calendars[$calendar['id']] = $calendar;
255
+            }
256
+        }
257
+
258
+        $stmt->closeCursor();
259
+
260
+        // query for shared calendars
261
+        $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
262
+        $principals[]= $principalUri;
263
+
264
+        $fields = array_values($this->propertyMap);
265
+        $fields[] = 'a.id';
266
+        $fields[] = 'a.uri';
267
+        $fields[] = 'a.synctoken';
268
+        $fields[] = 'a.components';
269
+        $fields[] = 'a.principaluri';
270
+        $fields[] = 'a.transparent';
271
+        $fields[] = 's.access';
272
+        $query = $this->db->getQueryBuilder();
273
+        $result = $query->select($fields)
274
+            ->from('dav_shares', 's')
275
+            ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
276
+            ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
277
+            ->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
278
+            ->setParameter('type', 'calendar')
279
+            ->setParameter('principaluri', $principals, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
280
+            ->execute();
281
+
282
+        $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
283
+        while($row = $result->fetch()) {
284
+            if ($row['principaluri'] === $principalUri) {
285
+                continue;
286
+            }
287
+
288
+            $readOnly = (int) $row['access'] === Backend::ACCESS_READ;
289
+            if (isset($calendars[$row['id']])) {
290
+                if ($readOnly) {
291
+                    // New share can not have more permissions then the old one.
292
+                    continue;
293
+                }
294
+                if (isset($calendars[$row['id']][$readOnlyPropertyName]) &&
295
+                    $calendars[$row['id']][$readOnlyPropertyName] === 0) {
296
+                    // Old share is already read-write, no more permissions can be gained
297
+                    continue;
298
+                }
299
+            }
300
+
301
+            list(, $name) = URLUtil::splitPath($row['principaluri']);
302
+            $uri = $row['uri'] . '_shared_by_' . $name;
303
+            $row['displayname'] = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
304
+            $components = [];
305
+            if ($row['components']) {
306
+                $components = explode(',',$row['components']);
307
+            }
308
+            $calendar = [
309
+                'id' => $row['id'],
310
+                'uri' => $uri,
311
+                'principaluri' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
312
+                '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
313
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
314
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
315
+                '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
316
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
317
+                $readOnlyPropertyName => $readOnly,
318
+            ];
319
+
320
+            foreach($this->propertyMap as $xmlName=>$dbName) {
321
+                $calendar[$xmlName] = $row[$dbName];
322
+            }
323
+
324
+            $this->addOwnerPrincipal($calendar);
325
+
326
+            $calendars[$calendar['id']] = $calendar;
327
+        }
328
+        $result->closeCursor();
329
+
330
+        return array_values($calendars);
331
+    }
332
+
333
+    public function getUsersOwnCalendars($principalUri) {
334
+        $principalUri = $this->convertPrincipal($principalUri, true);
335
+        $fields = array_values($this->propertyMap);
336
+        $fields[] = 'id';
337
+        $fields[] = 'uri';
338
+        $fields[] = 'synctoken';
339
+        $fields[] = 'components';
340
+        $fields[] = 'principaluri';
341
+        $fields[] = 'transparent';
342
+        // Making fields a comma-delimited list
343
+        $query = $this->db->getQueryBuilder();
344
+        $query->select($fields)->from('calendars')
345
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
346
+            ->orderBy('calendarorder', 'ASC');
347
+        $stmt = $query->execute();
348
+        $calendars = [];
349
+        while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
350
+            $components = [];
351
+            if ($row['components']) {
352
+                $components = explode(',',$row['components']);
353
+            }
354
+            $calendar = [
355
+                'id' => $row['id'],
356
+                'uri' => $row['uri'],
357
+                'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
358
+                '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
359
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
360
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
361
+                '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
362
+            ];
363
+            foreach($this->propertyMap as $xmlName=>$dbName) {
364
+                $calendar[$xmlName] = $row[$dbName];
365
+            }
366
+
367
+            $this->addOwnerPrincipal($calendar);
368
+
369
+            if (!isset($calendars[$calendar['id']])) {
370
+                $calendars[$calendar['id']] = $calendar;
371
+            }
372
+        }
373
+        $stmt->closeCursor();
374
+        return array_values($calendars);
375
+    }
376
+
377
+
378
+    private function getUserDisplayName($uid) {
379
+        if (!isset($this->userDisplayNames[$uid])) {
380
+            $user = $this->userManager->get($uid);
381
+
382
+            if ($user instanceof IUser) {
383
+                $this->userDisplayNames[$uid] = $user->getDisplayName();
384
+            } else {
385
+                $this->userDisplayNames[$uid] = $uid;
386
+            }
387
+        }
388
+
389
+        return $this->userDisplayNames[$uid];
390
+    }
391 391
 	
392
-	/**
393
-	 * @return array
394
-	 */
395
-	public function getPublicCalendars() {
396
-		$fields = array_values($this->propertyMap);
397
-		$fields[] = 'a.id';
398
-		$fields[] = 'a.uri';
399
-		$fields[] = 'a.synctoken';
400
-		$fields[] = 'a.components';
401
-		$fields[] = 'a.principaluri';
402
-		$fields[] = 'a.transparent';
403
-		$fields[] = 's.access';
404
-		$fields[] = 's.publicuri';
405
-		$calendars = [];
406
-		$query = $this->db->getQueryBuilder();
407
-		$result = $query->select($fields)
408
-			->from('dav_shares', 's')
409
-			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
410
-			->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
411
-			->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
412
-			->execute();
413
-
414
-		while($row = $result->fetch()) {
415
-			list(, $name) = URLUtil::splitPath($row['principaluri']);
416
-			$row['displayname'] = $row['displayname'] . "($name)";
417
-			$components = [];
418
-			if ($row['components']) {
419
-				$components = explode(',',$row['components']);
420
-			}
421
-			$calendar = [
422
-				'id' => $row['id'],
423
-				'uri' => $row['publicuri'],
424
-				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
425
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
426
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
427
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
428
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
429
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
430
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
431
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
432
-			];
433
-
434
-			foreach($this->propertyMap as $xmlName=>$dbName) {
435
-				$calendar[$xmlName] = $row[$dbName];
436
-			}
437
-
438
-			$this->addOwnerPrincipal($calendar);
439
-
440
-			if (!isset($calendars[$calendar['id']])) {
441
-				$calendars[$calendar['id']] = $calendar;
442
-			}
443
-		}
444
-		$result->closeCursor();
445
-
446
-		return array_values($calendars);
447
-	}
448
-
449
-	/**
450
-	 * @param string $uri
451
-	 * @return array
452
-	 * @throws NotFound
453
-	 */
454
-	public function getPublicCalendar($uri) {
455
-		$fields = array_values($this->propertyMap);
456
-		$fields[] = 'a.id';
457
-		$fields[] = 'a.uri';
458
-		$fields[] = 'a.synctoken';
459
-		$fields[] = 'a.components';
460
-		$fields[] = 'a.principaluri';
461
-		$fields[] = 'a.transparent';
462
-		$fields[] = 's.access';
463
-		$fields[] = 's.publicuri';
464
-		$query = $this->db->getQueryBuilder();
465
-		$result = $query->select($fields)
466
-			->from('dav_shares', 's')
467
-			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
468
-			->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
469
-			->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
470
-			->andWhere($query->expr()->eq('s.publicuri', $query->createNamedParameter($uri)))
471
-			->execute();
472
-
473
-		$row = $result->fetch(\PDO::FETCH_ASSOC);
474
-
475
-		$result->closeCursor();
476
-
477
-		if ($row === false) {
478
-			throw new NotFound('Node with name \'' . $uri . '\' could not be found');
479
-		}
480
-
481
-		list(, $name) = URLUtil::splitPath($row['principaluri']);
482
-		$row['displayname'] = $row['displayname'] . ' ' . "($name)";
483
-		$components = [];
484
-		if ($row['components']) {
485
-			$components = explode(',',$row['components']);
486
-		}
487
-		$calendar = [
488
-			'id' => $row['id'],
489
-			'uri' => $row['publicuri'],
490
-			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
491
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
492
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
493
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
494
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
495
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
496
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
497
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
498
-		];
499
-
500
-		foreach($this->propertyMap as $xmlName=>$dbName) {
501
-			$calendar[$xmlName] = $row[$dbName];
502
-		}
503
-
504
-		$this->addOwnerPrincipal($calendar);
505
-
506
-		return $calendar;
507
-
508
-	}
509
-
510
-	/**
511
-	 * @param string $principal
512
-	 * @param string $uri
513
-	 * @return array|null
514
-	 */
515
-	public function getCalendarByUri($principal, $uri) {
516
-		$fields = array_values($this->propertyMap);
517
-		$fields[] = 'id';
518
-		$fields[] = 'uri';
519
-		$fields[] = 'synctoken';
520
-		$fields[] = 'components';
521
-		$fields[] = 'principaluri';
522
-		$fields[] = 'transparent';
523
-
524
-		// Making fields a comma-delimited list
525
-		$query = $this->db->getQueryBuilder();
526
-		$query->select($fields)->from('calendars')
527
-			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
528
-			->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
529
-			->setMaxResults(1);
530
-		$stmt = $query->execute();
531
-
532
-		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
533
-		$stmt->closeCursor();
534
-		if ($row === false) {
535
-			return null;
536
-		}
537
-
538
-		$components = [];
539
-		if ($row['components']) {
540
-			$components = explode(',',$row['components']);
541
-		}
542
-
543
-		$calendar = [
544
-			'id' => $row['id'],
545
-			'uri' => $row['uri'],
546
-			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
547
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
548
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
549
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
550
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
551
-		];
552
-
553
-		foreach($this->propertyMap as $xmlName=>$dbName) {
554
-			$calendar[$xmlName] = $row[$dbName];
555
-		}
556
-
557
-		$this->addOwnerPrincipal($calendar);
558
-
559
-		return $calendar;
560
-	}
561
-
562
-	public function getCalendarById($calendarId) {
563
-		$fields = array_values($this->propertyMap);
564
-		$fields[] = 'id';
565
-		$fields[] = 'uri';
566
-		$fields[] = 'synctoken';
567
-		$fields[] = 'components';
568
-		$fields[] = 'principaluri';
569
-		$fields[] = 'transparent';
570
-
571
-		// Making fields a comma-delimited list
572
-		$query = $this->db->getQueryBuilder();
573
-		$query->select($fields)->from('calendars')
574
-			->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)))
575
-			->setMaxResults(1);
576
-		$stmt = $query->execute();
577
-
578
-		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
579
-		$stmt->closeCursor();
580
-		if ($row === false) {
581
-			return null;
582
-		}
583
-
584
-		$components = [];
585
-		if ($row['components']) {
586
-			$components = explode(',',$row['components']);
587
-		}
588
-
589
-		$calendar = [
590
-			'id' => $row['id'],
591
-			'uri' => $row['uri'],
592
-			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
593
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
594
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
595
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
596
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
597
-		];
598
-
599
-		foreach($this->propertyMap as $xmlName=>$dbName) {
600
-			$calendar[$xmlName] = $row[$dbName];
601
-		}
602
-
603
-		$this->addOwnerPrincipal($calendar);
604
-
605
-		return $calendar;
606
-	}
607
-
608
-	/**
609
-	 * Creates a new calendar for a principal.
610
-	 *
611
-	 * If the creation was a success, an id must be returned that can be used to reference
612
-	 * this calendar in other methods, such as updateCalendar.
613
-	 *
614
-	 * @param string $principalUri
615
-	 * @param string $calendarUri
616
-	 * @param array $properties
617
-	 * @return int
618
-	 */
619
-	function createCalendar($principalUri, $calendarUri, array $properties) {
620
-		$values = [
621
-			'principaluri' => $this->convertPrincipal($principalUri, true),
622
-			'uri'          => $calendarUri,
623
-			'synctoken'    => 1,
624
-			'transparent'  => 0,
625
-			'components'   => 'VEVENT,VTODO',
626
-			'displayname'  => $calendarUri
627
-		];
628
-
629
-		// Default value
630
-		$sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
631
-		if (isset($properties[$sccs])) {
632
-			if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) {
633
-				throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
634
-			}
635
-			$values['components'] = implode(',',$properties[$sccs]->getValue());
636
-		}
637
-		$transp = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
638
-		if (isset($properties[$transp])) {
639
-			$values['transparent'] = (int) ($properties[$transp]->getValue() === 'transparent');
640
-		}
641
-
642
-		foreach($this->propertyMap as $xmlName=>$dbName) {
643
-			if (isset($properties[$xmlName])) {
644
-				$values[$dbName] = $properties[$xmlName];
645
-			}
646
-		}
647
-
648
-		$query = $this->db->getQueryBuilder();
649
-		$query->insert('calendars');
650
-		foreach($values as $column => $value) {
651
-			$query->setValue($column, $query->createNamedParameter($value));
652
-		}
653
-		$query->execute();
654
-		$calendarId = $query->getLastInsertId();
655
-
656
-		$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendar', new GenericEvent(
657
-			'\OCA\DAV\CalDAV\CalDavBackend::createCalendar',
658
-			[
659
-				'calendarId' => $calendarId,
660
-				'calendarData' => $this->getCalendarById($calendarId),
661
-		]));
662
-
663
-		return $calendarId;
664
-	}
665
-
666
-	/**
667
-	 * Updates properties for a calendar.
668
-	 *
669
-	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
670
-	 * To do the actual updates, you must tell this object which properties
671
-	 * you're going to process with the handle() method.
672
-	 *
673
-	 * Calling the handle method is like telling the PropPatch object "I
674
-	 * promise I can handle updating this property".
675
-	 *
676
-	 * Read the PropPatch documentation for more info and examples.
677
-	 *
678
-	 * @param PropPatch $propPatch
679
-	 * @return void
680
-	 */
681
-	function updateCalendar($calendarId, PropPatch $propPatch) {
682
-		$supportedProperties = array_keys($this->propertyMap);
683
-		$supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
684
-
685
-		$propPatch->handle($supportedProperties, function($mutations) use ($calendarId) {
686
-			$newValues = [];
687
-			foreach ($mutations as $propertyName => $propertyValue) {
688
-
689
-				switch ($propertyName) {
690
-					case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' :
691
-						$fieldName = 'transparent';
692
-						$newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent');
693
-						break;
694
-					default :
695
-						$fieldName = $this->propertyMap[$propertyName];
696
-						$newValues[$fieldName] = $propertyValue;
697
-						break;
698
-				}
699
-
700
-			}
701
-			$query = $this->db->getQueryBuilder();
702
-			$query->update('calendars');
703
-			foreach ($newValues as $fieldName => $value) {
704
-				$query->set($fieldName, $query->createNamedParameter($value));
705
-			}
706
-			$query->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
707
-			$query->execute();
708
-
709
-			$this->addChange($calendarId, "", 2);
710
-
711
-			$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendar', new GenericEvent(
712
-				'\OCA\DAV\CalDAV\CalDavBackend::updateCalendar',
713
-				[
714
-					'calendarId' => $calendarId,
715
-					'calendarData' => $this->getCalendarById($calendarId),
716
-					'shares' => $this->getShares($calendarId),
717
-					'propertyMutations' => $mutations,
718
-			]));
719
-
720
-			return true;
721
-		});
722
-	}
723
-
724
-	/**
725
-	 * Delete a calendar and all it's objects
726
-	 *
727
-	 * @param mixed $calendarId
728
-	 * @return void
729
-	 */
730
-	function deleteCalendar($calendarId) {
731
-		$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar', new GenericEvent(
732
-			'\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar',
733
-			[
734
-				'calendarId' => $calendarId,
735
-				'calendarData' => $this->getCalendarById($calendarId),
736
-				'shares' => $this->getShares($calendarId),
737
-		]));
738
-
739
-		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ?');
740
-		$stmt->execute([$calendarId]);
741
-
742
-		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendars` WHERE `id` = ?');
743
-		$stmt->execute([$calendarId]);
744
-
745
-		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarchanges` WHERE `calendarid` = ?');
746
-		$stmt->execute([$calendarId]);
747
-
748
-		$this->sharingBackend->deleteAllShares($calendarId);
749
-	}
750
-
751
-	/**
752
-	 * Delete all of an user's shares
753
-	 *
754
-	 * @param string $principaluri
755
-	 * @return void
756
-	 */
757
-	function deleteAllSharesByUser($principaluri) {
758
-		$this->sharingBackend->deleteAllSharesByUser($principaluri);
759
-	}
760
-
761
-	/**
762
-	 * Returns all calendar objects within a calendar.
763
-	 *
764
-	 * Every item contains an array with the following keys:
765
-	 *   * calendardata - The iCalendar-compatible calendar data
766
-	 *   * uri - a unique key which will be used to construct the uri. This can
767
-	 *     be any arbitrary string, but making sure it ends with '.ics' is a
768
-	 *     good idea. This is only the basename, or filename, not the full
769
-	 *     path.
770
-	 *   * lastmodified - a timestamp of the last modification time
771
-	 *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
772
-	 *   '"abcdef"')
773
-	 *   * size - The size of the calendar objects, in bytes.
774
-	 *   * component - optional, a string containing the type of object, such
775
-	 *     as 'vevent' or 'vtodo'. If specified, this will be used to populate
776
-	 *     the Content-Type header.
777
-	 *
778
-	 * Note that the etag is optional, but it's highly encouraged to return for
779
-	 * speed reasons.
780
-	 *
781
-	 * The calendardata is also optional. If it's not returned
782
-	 * 'getCalendarObject' will be called later, which *is* expected to return
783
-	 * calendardata.
784
-	 *
785
-	 * If neither etag or size are specified, the calendardata will be
786
-	 * used/fetched to determine these numbers. If both are specified the
787
-	 * amount of times this is needed is reduced by a great degree.
788
-	 *
789
-	 * @param mixed $calendarId
790
-	 * @return array
791
-	 */
792
-	function getCalendarObjects($calendarId) {
793
-		$query = $this->db->getQueryBuilder();
794
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'componenttype', 'classification'])
795
-			->from('calendarobjects')
796
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
797
-		$stmt = $query->execute();
798
-
799
-		$result = [];
800
-		foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
801
-			$result[] = [
802
-					'id'           => $row['id'],
803
-					'uri'          => $row['uri'],
804
-					'lastmodified' => $row['lastmodified'],
805
-					'etag'         => '"' . $row['etag'] . '"',
806
-					'calendarid'   => $row['calendarid'],
807
-					'size'         => (int)$row['size'],
808
-					'component'    => strtolower($row['componenttype']),
809
-					'classification'=> (int)$row['classification']
810
-			];
811
-		}
812
-
813
-		return $result;
814
-	}
815
-
816
-	/**
817
-	 * Returns information from a single calendar object, based on it's object
818
-	 * uri.
819
-	 *
820
-	 * The object uri is only the basename, or filename and not a full path.
821
-	 *
822
-	 * The returned array must have the same keys as getCalendarObjects. The
823
-	 * 'calendardata' object is required here though, while it's not required
824
-	 * for getCalendarObjects.
825
-	 *
826
-	 * This method must return null if the object did not exist.
827
-	 *
828
-	 * @param mixed $calendarId
829
-	 * @param string $objectUri
830
-	 * @return array|null
831
-	 */
832
-	function getCalendarObject($calendarId, $objectUri) {
833
-
834
-		$query = $this->db->getQueryBuilder();
835
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
836
-				->from('calendarobjects')
837
-				->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
838
-				->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)));
839
-		$stmt = $query->execute();
840
-		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
841
-
842
-		if(!$row) return null;
843
-
844
-		return [
845
-				'id'            => $row['id'],
846
-				'uri'           => $row['uri'],
847
-				'lastmodified'  => $row['lastmodified'],
848
-				'etag'          => '"' . $row['etag'] . '"',
849
-				'calendarid'    => $row['calendarid'],
850
-				'size'          => (int)$row['size'],
851
-				'calendardata'  => $this->readBlob($row['calendardata']),
852
-				'component'     => strtolower($row['componenttype']),
853
-				'classification'=> (int)$row['classification']
854
-		];
855
-	}
856
-
857
-	/**
858
-	 * Returns a list of calendar objects.
859
-	 *
860
-	 * This method should work identical to getCalendarObject, but instead
861
-	 * return all the calendar objects in the list as an array.
862
-	 *
863
-	 * If the backend supports this, it may allow for some speed-ups.
864
-	 *
865
-	 * @param mixed $calendarId
866
-	 * @param string[] $uris
867
-	 * @return array
868
-	 */
869
-	function getMultipleCalendarObjects($calendarId, array $uris) {
870
-		if (empty($uris)) {
871
-			return [];
872
-		}
873
-
874
-		$chunks = array_chunk($uris, 100);
875
-		$objects = [];
876
-
877
-		$query = $this->db->getQueryBuilder();
878
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
879
-			->from('calendarobjects')
880
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
881
-			->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
882
-
883
-		foreach ($chunks as $uris) {
884
-			$query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
885
-			$result = $query->execute();
886
-
887
-			while ($row = $result->fetch()) {
888
-				$objects[] = [
889
-					'id'           => $row['id'],
890
-					'uri'          => $row['uri'],
891
-					'lastmodified' => $row['lastmodified'],
892
-					'etag'         => '"' . $row['etag'] . '"',
893
-					'calendarid'   => $row['calendarid'],
894
-					'size'         => (int)$row['size'],
895
-					'calendardata' => $this->readBlob($row['calendardata']),
896
-					'component'    => strtolower($row['componenttype']),
897
-					'classification' => (int)$row['classification']
898
-				];
899
-			}
900
-			$result->closeCursor();
901
-		}
902
-		return $objects;
903
-	}
904
-
905
-	/**
906
-	 * Creates a new calendar object.
907
-	 *
908
-	 * The object uri is only the basename, or filename and not a full path.
909
-	 *
910
-	 * It is possible return an etag from this function, which will be used in
911
-	 * the response to this PUT request. Note that the ETag must be surrounded
912
-	 * by double-quotes.
913
-	 *
914
-	 * However, you should only really return this ETag if you don't mangle the
915
-	 * calendar-data. If the result of a subsequent GET to this object is not
916
-	 * the exact same as this request body, you should omit the ETag.
917
-	 *
918
-	 * @param mixed $calendarId
919
-	 * @param string $objectUri
920
-	 * @param string $calendarData
921
-	 * @return string
922
-	 */
923
-	function createCalendarObject($calendarId, $objectUri, $calendarData) {
924
-		$extraData = $this->getDenormalizedData($calendarData);
925
-
926
-		$query = $this->db->getQueryBuilder();
927
-		$query->insert('calendarobjects')
928
-			->values([
929
-				'calendarid' => $query->createNamedParameter($calendarId),
930
-				'uri' => $query->createNamedParameter($objectUri),
931
-				'calendardata' => $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB),
932
-				'lastmodified' => $query->createNamedParameter(time()),
933
-				'etag' => $query->createNamedParameter($extraData['etag']),
934
-				'size' => $query->createNamedParameter($extraData['size']),
935
-				'componenttype' => $query->createNamedParameter($extraData['componentType']),
936
-				'firstoccurence' => $query->createNamedParameter($extraData['firstOccurence']),
937
-				'lastoccurence' => $query->createNamedParameter($extraData['lastOccurence']),
938
-				'classification' => $query->createNamedParameter($extraData['classification']),
939
-				'uid' => $query->createNamedParameter($extraData['uid']),
940
-			])
941
-			->execute();
942
-
943
-		$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', new GenericEvent(
944
-			'\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject',
945
-			[
946
-				'calendarId' => $calendarId,
947
-				'calendarData' => $this->getCalendarById($calendarId),
948
-				'shares' => $this->getShares($calendarId),
949
-				'objectData' => $this->getCalendarObject($calendarId, $objectUri),
950
-			]
951
-		));
952
-		$this->addChange($calendarId, $objectUri, 1);
953
-
954
-		return '"' . $extraData['etag'] . '"';
955
-	}
956
-
957
-	/**
958
-	 * Updates an existing calendarobject, based on it's uri.
959
-	 *
960
-	 * The object uri is only the basename, or filename and not a full path.
961
-	 *
962
-	 * It is possible return an etag from this function, which will be used in
963
-	 * the response to this PUT request. Note that the ETag must be surrounded
964
-	 * by double-quotes.
965
-	 *
966
-	 * However, you should only really return this ETag if you don't mangle the
967
-	 * calendar-data. If the result of a subsequent GET to this object is not
968
-	 * the exact same as this request body, you should omit the ETag.
969
-	 *
970
-	 * @param mixed $calendarId
971
-	 * @param string $objectUri
972
-	 * @param string $calendarData
973
-	 * @return string
974
-	 */
975
-	function updateCalendarObject($calendarId, $objectUri, $calendarData) {
976
-		$extraData = $this->getDenormalizedData($calendarData);
977
-
978
-		$query = $this->db->getQueryBuilder();
979
-		$query->update('calendarobjects')
980
-				->set('calendardata', $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB))
981
-				->set('lastmodified', $query->createNamedParameter(time()))
982
-				->set('etag', $query->createNamedParameter($extraData['etag']))
983
-				->set('size', $query->createNamedParameter($extraData['size']))
984
-				->set('componenttype', $query->createNamedParameter($extraData['componentType']))
985
-				->set('firstoccurence', $query->createNamedParameter($extraData['firstOccurence']))
986
-				->set('lastoccurence', $query->createNamedParameter($extraData['lastOccurence']))
987
-				->set('classification', $query->createNamedParameter($extraData['classification']))
988
-				->set('uid', $query->createNamedParameter($extraData['uid']))
989
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
990
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
991
-			->execute();
992
-
993
-		$data = $this->getCalendarObject($calendarId, $objectUri);
994
-		if (is_array($data)) {
995
-			$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', new GenericEvent(
996
-				'\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject',
997
-				[
998
-					'calendarId' => $calendarId,
999
-					'calendarData' => $this->getCalendarById($calendarId),
1000
-					'shares' => $this->getShares($calendarId),
1001
-					'objectData' => $data,
1002
-				]
1003
-			));
1004
-		}
1005
-		$this->addChange($calendarId, $objectUri, 2);
1006
-
1007
-		return '"' . $extraData['etag'] . '"';
1008
-	}
1009
-
1010
-	/**
1011
-	 * @param int $calendarObjectId
1012
-	 * @param int $classification
1013
-	 */
1014
-	public function setClassification($calendarObjectId, $classification) {
1015
-		if (!in_array($classification, [
1016
-			self::CLASSIFICATION_PUBLIC, self::CLASSIFICATION_PRIVATE, self::CLASSIFICATION_CONFIDENTIAL
1017
-		])) {
1018
-			throw new \InvalidArgumentException();
1019
-		}
1020
-		$query = $this->db->getQueryBuilder();
1021
-		$query->update('calendarobjects')
1022
-			->set('classification', $query->createNamedParameter($classification))
1023
-			->where($query->expr()->eq('id', $query->createNamedParameter($calendarObjectId)))
1024
-			->execute();
1025
-	}
1026
-
1027
-	/**
1028
-	 * Deletes an existing calendar object.
1029
-	 *
1030
-	 * The object uri is only the basename, or filename and not a full path.
1031
-	 *
1032
-	 * @param mixed $calendarId
1033
-	 * @param string $objectUri
1034
-	 * @return void
1035
-	 */
1036
-	function deleteCalendarObject($calendarId, $objectUri) {
1037
-		$data = $this->getCalendarObject($calendarId, $objectUri);
1038
-		if (is_array($data)) {
1039
-			$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', new GenericEvent(
1040
-				'\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject',
1041
-				[
1042
-					'calendarId' => $calendarId,
1043
-					'calendarData' => $this->getCalendarById($calendarId),
1044
-					'shares' => $this->getShares($calendarId),
1045
-					'objectData' => $data,
1046
-				]
1047
-			));
1048
-		}
1049
-
1050
-		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ?');
1051
-		$stmt->execute([$calendarId, $objectUri]);
1052
-
1053
-		$this->addChange($calendarId, $objectUri, 3);
1054
-	}
1055
-
1056
-	/**
1057
-	 * Performs a calendar-query on the contents of this calendar.
1058
-	 *
1059
-	 * The calendar-query is defined in RFC4791 : CalDAV. Using the
1060
-	 * calendar-query it is possible for a client to request a specific set of
1061
-	 * object, based on contents of iCalendar properties, date-ranges and
1062
-	 * iCalendar component types (VTODO, VEVENT).
1063
-	 *
1064
-	 * This method should just return a list of (relative) urls that match this
1065
-	 * query.
1066
-	 *
1067
-	 * The list of filters are specified as an array. The exact array is
1068
-	 * documented by Sabre\CalDAV\CalendarQueryParser.
1069
-	 *
1070
-	 * Note that it is extremely likely that getCalendarObject for every path
1071
-	 * returned from this method will be called almost immediately after. You
1072
-	 * may want to anticipate this to speed up these requests.
1073
-	 *
1074
-	 * This method provides a default implementation, which parses *all* the
1075
-	 * iCalendar objects in the specified calendar.
1076
-	 *
1077
-	 * This default may well be good enough for personal use, and calendars
1078
-	 * that aren't very large. But if you anticipate high usage, big calendars
1079
-	 * or high loads, you are strongly advised to optimize certain paths.
1080
-	 *
1081
-	 * The best way to do so is override this method and to optimize
1082
-	 * specifically for 'common filters'.
1083
-	 *
1084
-	 * Requests that are extremely common are:
1085
-	 *   * requests for just VEVENTS
1086
-	 *   * requests for just VTODO
1087
-	 *   * requests with a time-range-filter on either VEVENT or VTODO.
1088
-	 *
1089
-	 * ..and combinations of these requests. It may not be worth it to try to
1090
-	 * handle every possible situation and just rely on the (relatively
1091
-	 * easy to use) CalendarQueryValidator to handle the rest.
1092
-	 *
1093
-	 * Note that especially time-range-filters may be difficult to parse. A
1094
-	 * time-range filter specified on a VEVENT must for instance also handle
1095
-	 * recurrence rules correctly.
1096
-	 * A good example of how to interprete all these filters can also simply
1097
-	 * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
1098
-	 * as possible, so it gives you a good idea on what type of stuff you need
1099
-	 * to think of.
1100
-	 *
1101
-	 * @param mixed $calendarId
1102
-	 * @param array $filters
1103
-	 * @return array
1104
-	 */
1105
-	function calendarQuery($calendarId, array $filters) {
1106
-		$componentType = null;
1107
-		$requirePostFilter = true;
1108
-		$timeRange = null;
1109
-
1110
-		// if no filters were specified, we don't need to filter after a query
1111
-		if (!$filters['prop-filters'] && !$filters['comp-filters']) {
1112
-			$requirePostFilter = false;
1113
-		}
1114
-
1115
-		// Figuring out if there's a component filter
1116
-		if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
1117
-			$componentType = $filters['comp-filters'][0]['name'];
1118
-
1119
-			// Checking if we need post-filters
1120
-			if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
1121
-				$requirePostFilter = false;
1122
-			}
1123
-			// There was a time-range filter
1124
-			if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range'])) {
1125
-				$timeRange = $filters['comp-filters'][0]['time-range'];
1126
-
1127
-				// If start time OR the end time is not specified, we can do a
1128
-				// 100% accurate mysql query.
1129
-				if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
1130
-					$requirePostFilter = false;
1131
-				}
1132
-			}
1133
-
1134
-		}
1135
-		$columns = ['uri'];
1136
-		if ($requirePostFilter) {
1137
-			$columns = ['uri', 'calendardata'];
1138
-		}
1139
-		$query = $this->db->getQueryBuilder();
1140
-		$query->select($columns)
1141
-			->from('calendarobjects')
1142
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
1143
-
1144
-		if ($componentType) {
1145
-			$query->andWhere($query->expr()->eq('componenttype', $query->createNamedParameter($componentType)));
1146
-		}
1147
-
1148
-		if ($timeRange && $timeRange['start']) {
1149
-			$query->andWhere($query->expr()->gt('lastoccurence', $query->createNamedParameter($timeRange['start']->getTimeStamp())));
1150
-		}
1151
-		if ($timeRange && $timeRange['end']) {
1152
-			$query->andWhere($query->expr()->lt('firstoccurence', $query->createNamedParameter($timeRange['end']->getTimeStamp())));
1153
-		}
1154
-
1155
-		$stmt = $query->execute();
1156
-
1157
-		$result = [];
1158
-		while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1159
-			if ($requirePostFilter) {
1160
-				if (!$this->validateFilterForObject($row, $filters)) {
1161
-					continue;
1162
-				}
1163
-			}
1164
-			$result[] = $row['uri'];
1165
-		}
1166
-
1167
-		return $result;
1168
-	}
1169
-
1170
-	/**
1171
-	 * Searches through all of a users calendars and calendar objects to find
1172
-	 * an object with a specific UID.
1173
-	 *
1174
-	 * This method should return the path to this object, relative to the
1175
-	 * calendar home, so this path usually only contains two parts:
1176
-	 *
1177
-	 * calendarpath/objectpath.ics
1178
-	 *
1179
-	 * If the uid is not found, return null.
1180
-	 *
1181
-	 * This method should only consider * objects that the principal owns, so
1182
-	 * any calendars owned by other principals that also appear in this
1183
-	 * collection should be ignored.
1184
-	 *
1185
-	 * @param string $principalUri
1186
-	 * @param string $uid
1187
-	 * @return string|null
1188
-	 */
1189
-	function getCalendarObjectByUID($principalUri, $uid) {
1190
-
1191
-		$query = $this->db->getQueryBuilder();
1192
-		$query->selectAlias('c.uri', 'calendaruri')->selectAlias('co.uri', 'objecturi')
1193
-			->from('calendarobjects', 'co')
1194
-			->leftJoin('co', 'calendars', 'c', $query->expr()->eq('co.calendarid', 'c.id'))
1195
-			->where($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri)))
1196
-			->andWhere($query->expr()->eq('co.uid', $query->createNamedParameter($uid)));
1197
-
1198
-		$stmt = $query->execute();
1199
-
1200
-		if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1201
-			return $row['calendaruri'] . '/' . $row['objecturi'];
1202
-		}
1203
-
1204
-		return null;
1205
-	}
1206
-
1207
-	/**
1208
-	 * The getChanges method returns all the changes that have happened, since
1209
-	 * the specified syncToken in the specified calendar.
1210
-	 *
1211
-	 * This function should return an array, such as the following:
1212
-	 *
1213
-	 * [
1214
-	 *   'syncToken' => 'The current synctoken',
1215
-	 *   'added'   => [
1216
-	 *      'new.txt',
1217
-	 *   ],
1218
-	 *   'modified'   => [
1219
-	 *      'modified.txt',
1220
-	 *   ],
1221
-	 *   'deleted' => [
1222
-	 *      'foo.php.bak',
1223
-	 *      'old.txt'
1224
-	 *   ]
1225
-	 * );
1226
-	 *
1227
-	 * The returned syncToken property should reflect the *current* syncToken
1228
-	 * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
1229
-	 * property This is * needed here too, to ensure the operation is atomic.
1230
-	 *
1231
-	 * If the $syncToken argument is specified as null, this is an initial
1232
-	 * sync, and all members should be reported.
1233
-	 *
1234
-	 * The modified property is an array of nodenames that have changed since
1235
-	 * the last token.
1236
-	 *
1237
-	 * The deleted property is an array with nodenames, that have been deleted
1238
-	 * from collection.
1239
-	 *
1240
-	 * The $syncLevel argument is basically the 'depth' of the report. If it's
1241
-	 * 1, you only have to report changes that happened only directly in
1242
-	 * immediate descendants. If it's 2, it should also include changes from
1243
-	 * the nodes below the child collections. (grandchildren)
1244
-	 *
1245
-	 * The $limit argument allows a client to specify how many results should
1246
-	 * be returned at most. If the limit is not specified, it should be treated
1247
-	 * as infinite.
1248
-	 *
1249
-	 * If the limit (infinite or not) is higher than you're willing to return,
1250
-	 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
1251
-	 *
1252
-	 * If the syncToken is expired (due to data cleanup) or unknown, you must
1253
-	 * return null.
1254
-	 *
1255
-	 * The limit is 'suggestive'. You are free to ignore it.
1256
-	 *
1257
-	 * @param string $calendarId
1258
-	 * @param string $syncToken
1259
-	 * @param int $syncLevel
1260
-	 * @param int $limit
1261
-	 * @return array
1262
-	 */
1263
-	function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null) {
1264
-		// Current synctoken
1265
-		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*calendars` WHERE `id` = ?');
1266
-		$stmt->execute([ $calendarId ]);
1267
-		$currentToken = $stmt->fetchColumn(0);
1268
-
1269
-		if (is_null($currentToken)) {
1270
-			return null;
1271
-		}
1272
-
1273
-		$result = [
1274
-			'syncToken' => $currentToken,
1275
-			'added'     => [],
1276
-			'modified'  => [],
1277
-			'deleted'   => [],
1278
-		];
1279
-
1280
-		if ($syncToken) {
1281
-
1282
-			$query = "SELECT `uri`, `operation` FROM `*PREFIX*calendarchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `calendarid` = ? ORDER BY `synctoken`";
1283
-			if ($limit>0) {
1284
-				$query.= " `LIMIT` " . (int)$limit;
1285
-			}
1286
-
1287
-			// Fetching all changes
1288
-			$stmt = $this->db->prepare($query);
1289
-			$stmt->execute([$syncToken, $currentToken, $calendarId]);
1290
-
1291
-			$changes = [];
1292
-
1293
-			// This loop ensures that any duplicates are overwritten, only the
1294
-			// last change on a node is relevant.
1295
-			while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1296
-
1297
-				$changes[$row['uri']] = $row['operation'];
1298
-
1299
-			}
1300
-
1301
-			foreach($changes as $uri => $operation) {
1302
-
1303
-				switch($operation) {
1304
-					case 1 :
1305
-						$result['added'][] = $uri;
1306
-						break;
1307
-					case 2 :
1308
-						$result['modified'][] = $uri;
1309
-						break;
1310
-					case 3 :
1311
-						$result['deleted'][] = $uri;
1312
-						break;
1313
-				}
1314
-
1315
-			}
1316
-		} else {
1317
-			// No synctoken supplied, this is the initial sync.
1318
-			$query = "SELECT `uri` FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ?";
1319
-			$stmt = $this->db->prepare($query);
1320
-			$stmt->execute([$calendarId]);
1321
-
1322
-			$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
1323
-		}
1324
-		return $result;
1325
-
1326
-	}
1327
-
1328
-	/**
1329
-	 * Returns a list of subscriptions for a principal.
1330
-	 *
1331
-	 * Every subscription is an array with the following keys:
1332
-	 *  * id, a unique id that will be used by other functions to modify the
1333
-	 *    subscription. This can be the same as the uri or a database key.
1334
-	 *  * uri. This is just the 'base uri' or 'filename' of the subscription.
1335
-	 *  * principaluri. The owner of the subscription. Almost always the same as
1336
-	 *    principalUri passed to this method.
1337
-	 *
1338
-	 * Furthermore, all the subscription info must be returned too:
1339
-	 *
1340
-	 * 1. {DAV:}displayname
1341
-	 * 2. {http://apple.com/ns/ical/}refreshrate
1342
-	 * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
1343
-	 *    should not be stripped).
1344
-	 * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
1345
-	 *    should not be stripped).
1346
-	 * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
1347
-	 *    attachments should not be stripped).
1348
-	 * 6. {http://calendarserver.org/ns/}source (Must be a
1349
-	 *     Sabre\DAV\Property\Href).
1350
-	 * 7. {http://apple.com/ns/ical/}calendar-color
1351
-	 * 8. {http://apple.com/ns/ical/}calendar-order
1352
-	 * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
1353
-	 *    (should just be an instance of
1354
-	 *    Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
1355
-	 *    default components).
1356
-	 *
1357
-	 * @param string $principalUri
1358
-	 * @return array
1359
-	 */
1360
-	function getSubscriptionsForUser($principalUri) {
1361
-		$fields = array_values($this->subscriptionPropertyMap);
1362
-		$fields[] = 'id';
1363
-		$fields[] = 'uri';
1364
-		$fields[] = 'source';
1365
-		$fields[] = 'principaluri';
1366
-		$fields[] = 'lastmodified';
1367
-
1368
-		$query = $this->db->getQueryBuilder();
1369
-		$query->select($fields)
1370
-			->from('calendarsubscriptions')
1371
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
1372
-			->orderBy('calendarorder', 'asc');
1373
-		$stmt =$query->execute();
1374
-
1375
-		$subscriptions = [];
1376
-		while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1377
-
1378
-			$subscription = [
1379
-				'id'           => $row['id'],
1380
-				'uri'          => $row['uri'],
1381
-				'principaluri' => $row['principaluri'],
1382
-				'source'       => $row['source'],
1383
-				'lastmodified' => $row['lastmodified'],
1384
-
1385
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
1386
-			];
1387
-
1388
-			foreach($this->subscriptionPropertyMap as $xmlName=>$dbName) {
1389
-				if (!is_null($row[$dbName])) {
1390
-					$subscription[$xmlName] = $row[$dbName];
1391
-				}
1392
-			}
1393
-
1394
-			$subscriptions[] = $subscription;
1395
-
1396
-		}
1397
-
1398
-		return $subscriptions;
1399
-	}
1400
-
1401
-	/**
1402
-	 * Creates a new subscription for a principal.
1403
-	 *
1404
-	 * If the creation was a success, an id must be returned that can be used to reference
1405
-	 * this subscription in other methods, such as updateSubscription.
1406
-	 *
1407
-	 * @param string $principalUri
1408
-	 * @param string $uri
1409
-	 * @param array $properties
1410
-	 * @return mixed
1411
-	 */
1412
-	function createSubscription($principalUri, $uri, array $properties) {
1413
-
1414
-		if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
1415
-			throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
1416
-		}
1417
-
1418
-		$values = [
1419
-			'principaluri' => $principalUri,
1420
-			'uri'          => $uri,
1421
-			'source'       => $properties['{http://calendarserver.org/ns/}source']->getHref(),
1422
-			'lastmodified' => time(),
1423
-		];
1424
-
1425
-		$propertiesBoolean = ['striptodos', 'stripalarms', 'stripattachments'];
1426
-
1427
-		foreach($this->subscriptionPropertyMap as $xmlName=>$dbName) {
1428
-			if (array_key_exists($xmlName, $properties)) {
1429
-					$values[$dbName] = $properties[$xmlName];
1430
-					if (in_array($dbName, $propertiesBoolean)) {
1431
-						$values[$dbName] = true;
1432
-				}
1433
-			}
1434
-		}
1435
-
1436
-		$valuesToInsert = array();
1437
-
1438
-		$query = $this->db->getQueryBuilder();
1439
-
1440
-		foreach (array_keys($values) as $name) {
1441
-			$valuesToInsert[$name] = $query->createNamedParameter($values[$name]);
1442
-		}
1443
-
1444
-		$query->insert('calendarsubscriptions')
1445
-			->values($valuesToInsert)
1446
-			->execute();
1447
-
1448
-		return $this->db->lastInsertId('*PREFIX*calendarsubscriptions');
1449
-	}
1450
-
1451
-	/**
1452
-	 * Updates a subscription
1453
-	 *
1454
-	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
1455
-	 * To do the actual updates, you must tell this object which properties
1456
-	 * you're going to process with the handle() method.
1457
-	 *
1458
-	 * Calling the handle method is like telling the PropPatch object "I
1459
-	 * promise I can handle updating this property".
1460
-	 *
1461
-	 * Read the PropPatch documentation for more info and examples.
1462
-	 *
1463
-	 * @param mixed $subscriptionId
1464
-	 * @param PropPatch $propPatch
1465
-	 * @return void
1466
-	 */
1467
-	function updateSubscription($subscriptionId, PropPatch $propPatch) {
1468
-		$supportedProperties = array_keys($this->subscriptionPropertyMap);
1469
-		$supportedProperties[] = '{http://calendarserver.org/ns/}source';
1470
-
1471
-		$propPatch->handle($supportedProperties, function($mutations) use ($subscriptionId) {
1472
-
1473
-			$newValues = [];
1474
-
1475
-			foreach($mutations as $propertyName=>$propertyValue) {
1476
-				if ($propertyName === '{http://calendarserver.org/ns/}source') {
1477
-					$newValues['source'] = $propertyValue->getHref();
1478
-				} else {
1479
-					$fieldName = $this->subscriptionPropertyMap[$propertyName];
1480
-					$newValues[$fieldName] = $propertyValue;
1481
-				}
1482
-			}
1483
-
1484
-			$query = $this->db->getQueryBuilder();
1485
-			$query->update('calendarsubscriptions')
1486
-				->set('lastmodified', $query->createNamedParameter(time()));
1487
-			foreach($newValues as $fieldName=>$value) {
1488
-				$query->set($fieldName, $query->createNamedParameter($value));
1489
-			}
1490
-			$query->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
1491
-				->execute();
1492
-
1493
-			return true;
1494
-
1495
-		});
1496
-	}
1497
-
1498
-	/**
1499
-	 * Deletes a subscription.
1500
-	 *
1501
-	 * @param mixed $subscriptionId
1502
-	 * @return void
1503
-	 */
1504
-	function deleteSubscription($subscriptionId) {
1505
-		$query = $this->db->getQueryBuilder();
1506
-		$query->delete('calendarsubscriptions')
1507
-			->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
1508
-			->execute();
1509
-	}
1510
-
1511
-	/**
1512
-	 * Returns a single scheduling object for the inbox collection.
1513
-	 *
1514
-	 * The returned array should contain the following elements:
1515
-	 *   * uri - A unique basename for the object. This will be used to
1516
-	 *           construct a full uri.
1517
-	 *   * calendardata - The iCalendar object
1518
-	 *   * lastmodified - The last modification date. Can be an int for a unix
1519
-	 *                    timestamp, or a PHP DateTime object.
1520
-	 *   * etag - A unique token that must change if the object changed.
1521
-	 *   * size - The size of the object, in bytes.
1522
-	 *
1523
-	 * @param string $principalUri
1524
-	 * @param string $objectUri
1525
-	 * @return array
1526
-	 */
1527
-	function getSchedulingObject($principalUri, $objectUri) {
1528
-		$query = $this->db->getQueryBuilder();
1529
-		$stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
1530
-			->from('schedulingobjects')
1531
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
1532
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1533
-			->execute();
1534
-
1535
-		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
1536
-
1537
-		if(!$row) {
1538
-			return null;
1539
-		}
1540
-
1541
-		return [
1542
-				'uri'          => $row['uri'],
1543
-				'calendardata' => $row['calendardata'],
1544
-				'lastmodified' => $row['lastmodified'],
1545
-				'etag'         => '"' . $row['etag'] . '"',
1546
-				'size'         => (int)$row['size'],
1547
-		];
1548
-	}
1549
-
1550
-	/**
1551
-	 * Returns all scheduling objects for the inbox collection.
1552
-	 *
1553
-	 * These objects should be returned as an array. Every item in the array
1554
-	 * should follow the same structure as returned from getSchedulingObject.
1555
-	 *
1556
-	 * The main difference is that 'calendardata' is optional.
1557
-	 *
1558
-	 * @param string $principalUri
1559
-	 * @return array
1560
-	 */
1561
-	function getSchedulingObjects($principalUri) {
1562
-		$query = $this->db->getQueryBuilder();
1563
-		$stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
1564
-				->from('schedulingobjects')
1565
-				->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
1566
-				->execute();
1567
-
1568
-		$result = [];
1569
-		foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
1570
-			$result[] = [
1571
-					'calendardata' => $row['calendardata'],
1572
-					'uri'          => $row['uri'],
1573
-					'lastmodified' => $row['lastmodified'],
1574
-					'etag'         => '"' . $row['etag'] . '"',
1575
-					'size'         => (int)$row['size'],
1576
-			];
1577
-		}
1578
-
1579
-		return $result;
1580
-	}
1581
-
1582
-	/**
1583
-	 * Deletes a scheduling object from the inbox collection.
1584
-	 *
1585
-	 * @param string $principalUri
1586
-	 * @param string $objectUri
1587
-	 * @return void
1588
-	 */
1589
-	function deleteSchedulingObject($principalUri, $objectUri) {
1590
-		$query = $this->db->getQueryBuilder();
1591
-		$query->delete('schedulingobjects')
1592
-				->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
1593
-				->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1594
-				->execute();
1595
-	}
1596
-
1597
-	/**
1598
-	 * Creates a new scheduling object. This should land in a users' inbox.
1599
-	 *
1600
-	 * @param string $principalUri
1601
-	 * @param string $objectUri
1602
-	 * @param string $objectData
1603
-	 * @return void
1604
-	 */
1605
-	function createSchedulingObject($principalUri, $objectUri, $objectData) {
1606
-		$query = $this->db->getQueryBuilder();
1607
-		$query->insert('schedulingobjects')
1608
-			->values([
1609
-				'principaluri' => $query->createNamedParameter($principalUri),
1610
-				'calendardata' => $query->createNamedParameter($objectData),
1611
-				'uri' => $query->createNamedParameter($objectUri),
1612
-				'lastmodified' => $query->createNamedParameter(time()),
1613
-				'etag' => $query->createNamedParameter(md5($objectData)),
1614
-				'size' => $query->createNamedParameter(strlen($objectData))
1615
-			])
1616
-			->execute();
1617
-	}
1618
-
1619
-	/**
1620
-	 * Adds a change record to the calendarchanges table.
1621
-	 *
1622
-	 * @param mixed $calendarId
1623
-	 * @param string $objectUri
1624
-	 * @param int $operation 1 = add, 2 = modify, 3 = delete.
1625
-	 * @return void
1626
-	 */
1627
-	protected function addChange($calendarId, $objectUri, $operation) {
1628
-
1629
-		$stmt = $this->db->prepare('INSERT INTO `*PREFIX*calendarchanges` (`uri`, `synctoken`, `calendarid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*calendars` WHERE `id` = ?');
1630
-		$stmt->execute([
1631
-			$objectUri,
1632
-			$calendarId,
1633
-			$operation,
1634
-			$calendarId
1635
-		]);
1636
-		$stmt = $this->db->prepare('UPDATE `*PREFIX*calendars` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
1637
-		$stmt->execute([
1638
-			$calendarId
1639
-		]);
1640
-
1641
-	}
1642
-
1643
-	/**
1644
-	 * Parses some information from calendar objects, used for optimized
1645
-	 * calendar-queries.
1646
-	 *
1647
-	 * Returns an array with the following keys:
1648
-	 *   * etag - An md5 checksum of the object without the quotes.
1649
-	 *   * size - Size of the object in bytes
1650
-	 *   * componentType - VEVENT, VTODO or VJOURNAL
1651
-	 *   * firstOccurence
1652
-	 *   * lastOccurence
1653
-	 *   * uid - value of the UID property
1654
-	 *
1655
-	 * @param string $calendarData
1656
-	 * @return array
1657
-	 */
1658
-	public function getDenormalizedData($calendarData) {
1659
-
1660
-		$vObject = Reader::read($calendarData);
1661
-		$componentType = null;
1662
-		$component = null;
1663
-		$firstOccurrence = null;
1664
-		$lastOccurrence = null;
1665
-		$uid = null;
1666
-		$classification = self::CLASSIFICATION_PUBLIC;
1667
-		foreach($vObject->getComponents() as $component) {
1668
-			if ($component->name!=='VTIMEZONE') {
1669
-				$componentType = $component->name;
1670
-				$uid = (string)$component->UID;
1671
-				break;
1672
-			}
1673
-		}
1674
-		if (!$componentType) {
1675
-			throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
1676
-		}
1677
-		if ($componentType === 'VEVENT' && $component->DTSTART) {
1678
-			$firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp();
1679
-			// Finding the last occurrence is a bit harder
1680
-			if (!isset($component->RRULE)) {
1681
-				if (isset($component->DTEND)) {
1682
-					$lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp();
1683
-				} elseif (isset($component->DURATION)) {
1684
-					$endDate = clone $component->DTSTART->getDateTime();
1685
-					$endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
1686
-					$lastOccurrence = $endDate->getTimeStamp();
1687
-				} elseif (!$component->DTSTART->hasTime()) {
1688
-					$endDate = clone $component->DTSTART->getDateTime();
1689
-					$endDate->modify('+1 day');
1690
-					$lastOccurrence = $endDate->getTimeStamp();
1691
-				} else {
1692
-					$lastOccurrence = $firstOccurrence;
1693
-				}
1694
-			} else {
1695
-				$it = new EventIterator($vObject, (string)$component->UID);
1696
-				$maxDate = new \DateTime(self::MAX_DATE);
1697
-				if ($it->isInfinite()) {
1698
-					$lastOccurrence = $maxDate->getTimestamp();
1699
-				} else {
1700
-					$end = $it->getDtEnd();
1701
-					while($it->valid() && $end < $maxDate) {
1702
-						$end = $it->getDtEnd();
1703
-						$it->next();
1704
-
1705
-					}
1706
-					$lastOccurrence = $end->getTimestamp();
1707
-				}
1708
-
1709
-			}
1710
-		}
1711
-
1712
-		if ($component->CLASS) {
1713
-			$classification = CalDavBackend::CLASSIFICATION_PRIVATE;
1714
-			switch ($component->CLASS->getValue()) {
1715
-				case 'PUBLIC':
1716
-					$classification = CalDavBackend::CLASSIFICATION_PUBLIC;
1717
-					break;
1718
-				case 'CONFIDENTIAL':
1719
-					$classification = CalDavBackend::CLASSIFICATION_CONFIDENTIAL;
1720
-					break;
1721
-			}
1722
-		}
1723
-		return [
1724
-			'etag' => md5($calendarData),
1725
-			'size' => strlen($calendarData),
1726
-			'componentType' => $componentType,
1727
-			'firstOccurence' => is_null($firstOccurrence) ? null : max(0, $firstOccurrence),
1728
-			'lastOccurence'  => $lastOccurrence,
1729
-			'uid' => $uid,
1730
-			'classification' => $classification
1731
-		];
1732
-
1733
-	}
1734
-
1735
-	private function readBlob($cardData) {
1736
-		if (is_resource($cardData)) {
1737
-			return stream_get_contents($cardData);
1738
-		}
1739
-
1740
-		return $cardData;
1741
-	}
1742
-
1743
-	/**
1744
-	 * @param IShareable $shareable
1745
-	 * @param array $add
1746
-	 * @param array $remove
1747
-	 */
1748
-	public function updateShares($shareable, $add, $remove) {
1749
-		$calendarId = $shareable->getResourceId();
1750
-		$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateShares', new GenericEvent(
1751
-			'\OCA\DAV\CalDAV\CalDavBackend::updateShares',
1752
-			[
1753
-				'calendarId' => $calendarId,
1754
-				'calendarData' => $this->getCalendarById($calendarId),
1755
-				'shares' => $this->getShares($calendarId),
1756
-				'add' => $add,
1757
-				'remove' => $remove,
1758
-			]));
1759
-		$this->sharingBackend->updateShares($shareable, $add, $remove);
1760
-	}
1761
-
1762
-	/**
1763
-	 * @param int $resourceId
1764
-	 * @return array
1765
-	 */
1766
-	public function getShares($resourceId) {
1767
-		return $this->sharingBackend->getShares($resourceId);
1768
-	}
1769
-
1770
-	/**
1771
-	 * @param boolean $value
1772
-	 * @param \OCA\DAV\CalDAV\Calendar $calendar
1773
-	 * @return string|null
1774
-	 */
1775
-	public function setPublishStatus($value, $calendar) {
1776
-		$query = $this->db->getQueryBuilder();
1777
-		if ($value) {
1778
-			$publicUri = $this->random->generate(16, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS);
1779
-			$query->insert('dav_shares')
1780
-				->values([
1781
-					'principaluri' => $query->createNamedParameter($calendar->getPrincipalURI()),
1782
-					'type' => $query->createNamedParameter('calendar'),
1783
-					'access' => $query->createNamedParameter(self::ACCESS_PUBLIC),
1784
-					'resourceid' => $query->createNamedParameter($calendar->getResourceId()),
1785
-					'publicuri' => $query->createNamedParameter($publicUri)
1786
-				]);
1787
-			$query->execute();
1788
-			return $publicUri;
1789
-		}
1790
-		$query->delete('dav_shares')
1791
-			->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
1792
-			->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)));
1793
-		$query->execute();
1794
-		return null;
1795
-	}
1796
-
1797
-	/**
1798
-	 * @param \OCA\DAV\CalDAV\Calendar $calendar
1799
-	 * @return mixed
1800
-	 */
1801
-	public function getPublishStatus($calendar) {
1802
-		$query = $this->db->getQueryBuilder();
1803
-		$result = $query->select('publicuri')
1804
-			->from('dav_shares')
1805
-			->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
1806
-			->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
1807
-			->execute();
1808
-
1809
-		$row = $result->fetch();
1810
-		$result->closeCursor();
1811
-		return $row ? reset($row) : false;
1812
-	}
1813
-
1814
-	/**
1815
-	 * @param int $resourceId
1816
-	 * @param array $acl
1817
-	 * @return array
1818
-	 */
1819
-	public function applyShareAcl($resourceId, $acl) {
1820
-		return $this->sharingBackend->applyShareAcl($resourceId, $acl);
1821
-	}
1822
-
1823
-	private function convertPrincipal($principalUri, $toV2) {
1824
-		if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
1825
-			list(, $name) = URLUtil::splitPath($principalUri);
1826
-			if ($toV2 === true) {
1827
-				return "principals/users/$name";
1828
-			}
1829
-			return "principals/$name";
1830
-		}
1831
-		return $principalUri;
1832
-	}
1833
-
1834
-	private function addOwnerPrincipal(&$calendarInfo) {
1835
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1836
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1837
-		if (isset($calendarInfo[$ownerPrincipalKey])) {
1838
-			$uri = $calendarInfo[$ownerPrincipalKey];
1839
-		} else {
1840
-			$uri = $calendarInfo['principaluri'];
1841
-		}
1842
-
1843
-		$principalInformation = $this->principalBackend->getPrincipalByPath($uri);
1844
-		if (isset($principalInformation['{DAV:}displayname'])) {
1845
-			$calendarInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
1846
-		}
1847
-	}
392
+    /**
393
+     * @return array
394
+     */
395
+    public function getPublicCalendars() {
396
+        $fields = array_values($this->propertyMap);
397
+        $fields[] = 'a.id';
398
+        $fields[] = 'a.uri';
399
+        $fields[] = 'a.synctoken';
400
+        $fields[] = 'a.components';
401
+        $fields[] = 'a.principaluri';
402
+        $fields[] = 'a.transparent';
403
+        $fields[] = 's.access';
404
+        $fields[] = 's.publicuri';
405
+        $calendars = [];
406
+        $query = $this->db->getQueryBuilder();
407
+        $result = $query->select($fields)
408
+            ->from('dav_shares', 's')
409
+            ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
410
+            ->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
411
+            ->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
412
+            ->execute();
413
+
414
+        while($row = $result->fetch()) {
415
+            list(, $name) = URLUtil::splitPath($row['principaluri']);
416
+            $row['displayname'] = $row['displayname'] . "($name)";
417
+            $components = [];
418
+            if ($row['components']) {
419
+                $components = explode(',',$row['components']);
420
+            }
421
+            $calendar = [
422
+                'id' => $row['id'],
423
+                'uri' => $row['publicuri'],
424
+                'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
425
+                '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
426
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
427
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
428
+                '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
429
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
430
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
431
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
432
+            ];
433
+
434
+            foreach($this->propertyMap as $xmlName=>$dbName) {
435
+                $calendar[$xmlName] = $row[$dbName];
436
+            }
437
+
438
+            $this->addOwnerPrincipal($calendar);
439
+
440
+            if (!isset($calendars[$calendar['id']])) {
441
+                $calendars[$calendar['id']] = $calendar;
442
+            }
443
+        }
444
+        $result->closeCursor();
445
+
446
+        return array_values($calendars);
447
+    }
448
+
449
+    /**
450
+     * @param string $uri
451
+     * @return array
452
+     * @throws NotFound
453
+     */
454
+    public function getPublicCalendar($uri) {
455
+        $fields = array_values($this->propertyMap);
456
+        $fields[] = 'a.id';
457
+        $fields[] = 'a.uri';
458
+        $fields[] = 'a.synctoken';
459
+        $fields[] = 'a.components';
460
+        $fields[] = 'a.principaluri';
461
+        $fields[] = 'a.transparent';
462
+        $fields[] = 's.access';
463
+        $fields[] = 's.publicuri';
464
+        $query = $this->db->getQueryBuilder();
465
+        $result = $query->select($fields)
466
+            ->from('dav_shares', 's')
467
+            ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
468
+            ->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
469
+            ->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
470
+            ->andWhere($query->expr()->eq('s.publicuri', $query->createNamedParameter($uri)))
471
+            ->execute();
472
+
473
+        $row = $result->fetch(\PDO::FETCH_ASSOC);
474
+
475
+        $result->closeCursor();
476
+
477
+        if ($row === false) {
478
+            throw new NotFound('Node with name \'' . $uri . '\' could not be found');
479
+        }
480
+
481
+        list(, $name) = URLUtil::splitPath($row['principaluri']);
482
+        $row['displayname'] = $row['displayname'] . ' ' . "($name)";
483
+        $components = [];
484
+        if ($row['components']) {
485
+            $components = explode(',',$row['components']);
486
+        }
487
+        $calendar = [
488
+            'id' => $row['id'],
489
+            'uri' => $row['publicuri'],
490
+            'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
491
+            '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
492
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
493
+            '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
494
+            '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
495
+            '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
496
+            '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
497
+            '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
498
+        ];
499
+
500
+        foreach($this->propertyMap as $xmlName=>$dbName) {
501
+            $calendar[$xmlName] = $row[$dbName];
502
+        }
503
+
504
+        $this->addOwnerPrincipal($calendar);
505
+
506
+        return $calendar;
507
+
508
+    }
509
+
510
+    /**
511
+     * @param string $principal
512
+     * @param string $uri
513
+     * @return array|null
514
+     */
515
+    public function getCalendarByUri($principal, $uri) {
516
+        $fields = array_values($this->propertyMap);
517
+        $fields[] = 'id';
518
+        $fields[] = 'uri';
519
+        $fields[] = 'synctoken';
520
+        $fields[] = 'components';
521
+        $fields[] = 'principaluri';
522
+        $fields[] = 'transparent';
523
+
524
+        // Making fields a comma-delimited list
525
+        $query = $this->db->getQueryBuilder();
526
+        $query->select($fields)->from('calendars')
527
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
528
+            ->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
529
+            ->setMaxResults(1);
530
+        $stmt = $query->execute();
531
+
532
+        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
533
+        $stmt->closeCursor();
534
+        if ($row === false) {
535
+            return null;
536
+        }
537
+
538
+        $components = [];
539
+        if ($row['components']) {
540
+            $components = explode(',',$row['components']);
541
+        }
542
+
543
+        $calendar = [
544
+            'id' => $row['id'],
545
+            'uri' => $row['uri'],
546
+            'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
547
+            '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
548
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
549
+            '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
550
+            '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
551
+        ];
552
+
553
+        foreach($this->propertyMap as $xmlName=>$dbName) {
554
+            $calendar[$xmlName] = $row[$dbName];
555
+        }
556
+
557
+        $this->addOwnerPrincipal($calendar);
558
+
559
+        return $calendar;
560
+    }
561
+
562
+    public function getCalendarById($calendarId) {
563
+        $fields = array_values($this->propertyMap);
564
+        $fields[] = 'id';
565
+        $fields[] = 'uri';
566
+        $fields[] = 'synctoken';
567
+        $fields[] = 'components';
568
+        $fields[] = 'principaluri';
569
+        $fields[] = 'transparent';
570
+
571
+        // Making fields a comma-delimited list
572
+        $query = $this->db->getQueryBuilder();
573
+        $query->select($fields)->from('calendars')
574
+            ->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)))
575
+            ->setMaxResults(1);
576
+        $stmt = $query->execute();
577
+
578
+        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
579
+        $stmt->closeCursor();
580
+        if ($row === false) {
581
+            return null;
582
+        }
583
+
584
+        $components = [];
585
+        if ($row['components']) {
586
+            $components = explode(',',$row['components']);
587
+        }
588
+
589
+        $calendar = [
590
+            'id' => $row['id'],
591
+            'uri' => $row['uri'],
592
+            'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
593
+            '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
594
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
595
+            '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
596
+            '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
597
+        ];
598
+
599
+        foreach($this->propertyMap as $xmlName=>$dbName) {
600
+            $calendar[$xmlName] = $row[$dbName];
601
+        }
602
+
603
+        $this->addOwnerPrincipal($calendar);
604
+
605
+        return $calendar;
606
+    }
607
+
608
+    /**
609
+     * Creates a new calendar for a principal.
610
+     *
611
+     * If the creation was a success, an id must be returned that can be used to reference
612
+     * this calendar in other methods, such as updateCalendar.
613
+     *
614
+     * @param string $principalUri
615
+     * @param string $calendarUri
616
+     * @param array $properties
617
+     * @return int
618
+     */
619
+    function createCalendar($principalUri, $calendarUri, array $properties) {
620
+        $values = [
621
+            'principaluri' => $this->convertPrincipal($principalUri, true),
622
+            'uri'          => $calendarUri,
623
+            'synctoken'    => 1,
624
+            'transparent'  => 0,
625
+            'components'   => 'VEVENT,VTODO',
626
+            'displayname'  => $calendarUri
627
+        ];
628
+
629
+        // Default value
630
+        $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
631
+        if (isset($properties[$sccs])) {
632
+            if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) {
633
+                throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
634
+            }
635
+            $values['components'] = implode(',',$properties[$sccs]->getValue());
636
+        }
637
+        $transp = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
638
+        if (isset($properties[$transp])) {
639
+            $values['transparent'] = (int) ($properties[$transp]->getValue() === 'transparent');
640
+        }
641
+
642
+        foreach($this->propertyMap as $xmlName=>$dbName) {
643
+            if (isset($properties[$xmlName])) {
644
+                $values[$dbName] = $properties[$xmlName];
645
+            }
646
+        }
647
+
648
+        $query = $this->db->getQueryBuilder();
649
+        $query->insert('calendars');
650
+        foreach($values as $column => $value) {
651
+            $query->setValue($column, $query->createNamedParameter($value));
652
+        }
653
+        $query->execute();
654
+        $calendarId = $query->getLastInsertId();
655
+
656
+        $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendar', new GenericEvent(
657
+            '\OCA\DAV\CalDAV\CalDavBackend::createCalendar',
658
+            [
659
+                'calendarId' => $calendarId,
660
+                'calendarData' => $this->getCalendarById($calendarId),
661
+        ]));
662
+
663
+        return $calendarId;
664
+    }
665
+
666
+    /**
667
+     * Updates properties for a calendar.
668
+     *
669
+     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
670
+     * To do the actual updates, you must tell this object which properties
671
+     * you're going to process with the handle() method.
672
+     *
673
+     * Calling the handle method is like telling the PropPatch object "I
674
+     * promise I can handle updating this property".
675
+     *
676
+     * Read the PropPatch documentation for more info and examples.
677
+     *
678
+     * @param PropPatch $propPatch
679
+     * @return void
680
+     */
681
+    function updateCalendar($calendarId, PropPatch $propPatch) {
682
+        $supportedProperties = array_keys($this->propertyMap);
683
+        $supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
684
+
685
+        $propPatch->handle($supportedProperties, function($mutations) use ($calendarId) {
686
+            $newValues = [];
687
+            foreach ($mutations as $propertyName => $propertyValue) {
688
+
689
+                switch ($propertyName) {
690
+                    case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' :
691
+                        $fieldName = 'transparent';
692
+                        $newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent');
693
+                        break;
694
+                    default :
695
+                        $fieldName = $this->propertyMap[$propertyName];
696
+                        $newValues[$fieldName] = $propertyValue;
697
+                        break;
698
+                }
699
+
700
+            }
701
+            $query = $this->db->getQueryBuilder();
702
+            $query->update('calendars');
703
+            foreach ($newValues as $fieldName => $value) {
704
+                $query->set($fieldName, $query->createNamedParameter($value));
705
+            }
706
+            $query->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
707
+            $query->execute();
708
+
709
+            $this->addChange($calendarId, "", 2);
710
+
711
+            $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendar', new GenericEvent(
712
+                '\OCA\DAV\CalDAV\CalDavBackend::updateCalendar',
713
+                [
714
+                    'calendarId' => $calendarId,
715
+                    'calendarData' => $this->getCalendarById($calendarId),
716
+                    'shares' => $this->getShares($calendarId),
717
+                    'propertyMutations' => $mutations,
718
+            ]));
719
+
720
+            return true;
721
+        });
722
+    }
723
+
724
+    /**
725
+     * Delete a calendar and all it's objects
726
+     *
727
+     * @param mixed $calendarId
728
+     * @return void
729
+     */
730
+    function deleteCalendar($calendarId) {
731
+        $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar', new GenericEvent(
732
+            '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar',
733
+            [
734
+                'calendarId' => $calendarId,
735
+                'calendarData' => $this->getCalendarById($calendarId),
736
+                'shares' => $this->getShares($calendarId),
737
+        ]));
738
+
739
+        $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ?');
740
+        $stmt->execute([$calendarId]);
741
+
742
+        $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendars` WHERE `id` = ?');
743
+        $stmt->execute([$calendarId]);
744
+
745
+        $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarchanges` WHERE `calendarid` = ?');
746
+        $stmt->execute([$calendarId]);
747
+
748
+        $this->sharingBackend->deleteAllShares($calendarId);
749
+    }
750
+
751
+    /**
752
+     * Delete all of an user's shares
753
+     *
754
+     * @param string $principaluri
755
+     * @return void
756
+     */
757
+    function deleteAllSharesByUser($principaluri) {
758
+        $this->sharingBackend->deleteAllSharesByUser($principaluri);
759
+    }
760
+
761
+    /**
762
+     * Returns all calendar objects within a calendar.
763
+     *
764
+     * Every item contains an array with the following keys:
765
+     *   * calendardata - The iCalendar-compatible calendar data
766
+     *   * uri - a unique key which will be used to construct the uri. This can
767
+     *     be any arbitrary string, but making sure it ends with '.ics' is a
768
+     *     good idea. This is only the basename, or filename, not the full
769
+     *     path.
770
+     *   * lastmodified - a timestamp of the last modification time
771
+     *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
772
+     *   '"abcdef"')
773
+     *   * size - The size of the calendar objects, in bytes.
774
+     *   * component - optional, a string containing the type of object, such
775
+     *     as 'vevent' or 'vtodo'. If specified, this will be used to populate
776
+     *     the Content-Type header.
777
+     *
778
+     * Note that the etag is optional, but it's highly encouraged to return for
779
+     * speed reasons.
780
+     *
781
+     * The calendardata is also optional. If it's not returned
782
+     * 'getCalendarObject' will be called later, which *is* expected to return
783
+     * calendardata.
784
+     *
785
+     * If neither etag or size are specified, the calendardata will be
786
+     * used/fetched to determine these numbers. If both are specified the
787
+     * amount of times this is needed is reduced by a great degree.
788
+     *
789
+     * @param mixed $calendarId
790
+     * @return array
791
+     */
792
+    function getCalendarObjects($calendarId) {
793
+        $query = $this->db->getQueryBuilder();
794
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'componenttype', 'classification'])
795
+            ->from('calendarobjects')
796
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
797
+        $stmt = $query->execute();
798
+
799
+        $result = [];
800
+        foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
801
+            $result[] = [
802
+                    'id'           => $row['id'],
803
+                    'uri'          => $row['uri'],
804
+                    'lastmodified' => $row['lastmodified'],
805
+                    'etag'         => '"' . $row['etag'] . '"',
806
+                    'calendarid'   => $row['calendarid'],
807
+                    'size'         => (int)$row['size'],
808
+                    'component'    => strtolower($row['componenttype']),
809
+                    'classification'=> (int)$row['classification']
810
+            ];
811
+        }
812
+
813
+        return $result;
814
+    }
815
+
816
+    /**
817
+     * Returns information from a single calendar object, based on it's object
818
+     * uri.
819
+     *
820
+     * The object uri is only the basename, or filename and not a full path.
821
+     *
822
+     * The returned array must have the same keys as getCalendarObjects. The
823
+     * 'calendardata' object is required here though, while it's not required
824
+     * for getCalendarObjects.
825
+     *
826
+     * This method must return null if the object did not exist.
827
+     *
828
+     * @param mixed $calendarId
829
+     * @param string $objectUri
830
+     * @return array|null
831
+     */
832
+    function getCalendarObject($calendarId, $objectUri) {
833
+
834
+        $query = $this->db->getQueryBuilder();
835
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
836
+                ->from('calendarobjects')
837
+                ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
838
+                ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)));
839
+        $stmt = $query->execute();
840
+        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
841
+
842
+        if(!$row) return null;
843
+
844
+        return [
845
+                'id'            => $row['id'],
846
+                'uri'           => $row['uri'],
847
+                'lastmodified'  => $row['lastmodified'],
848
+                'etag'          => '"' . $row['etag'] . '"',
849
+                'calendarid'    => $row['calendarid'],
850
+                'size'          => (int)$row['size'],
851
+                'calendardata'  => $this->readBlob($row['calendardata']),
852
+                'component'     => strtolower($row['componenttype']),
853
+                'classification'=> (int)$row['classification']
854
+        ];
855
+    }
856
+
857
+    /**
858
+     * Returns a list of calendar objects.
859
+     *
860
+     * This method should work identical to getCalendarObject, but instead
861
+     * return all the calendar objects in the list as an array.
862
+     *
863
+     * If the backend supports this, it may allow for some speed-ups.
864
+     *
865
+     * @param mixed $calendarId
866
+     * @param string[] $uris
867
+     * @return array
868
+     */
869
+    function getMultipleCalendarObjects($calendarId, array $uris) {
870
+        if (empty($uris)) {
871
+            return [];
872
+        }
873
+
874
+        $chunks = array_chunk($uris, 100);
875
+        $objects = [];
876
+
877
+        $query = $this->db->getQueryBuilder();
878
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
879
+            ->from('calendarobjects')
880
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
881
+            ->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
882
+
883
+        foreach ($chunks as $uris) {
884
+            $query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
885
+            $result = $query->execute();
886
+
887
+            while ($row = $result->fetch()) {
888
+                $objects[] = [
889
+                    'id'           => $row['id'],
890
+                    'uri'          => $row['uri'],
891
+                    'lastmodified' => $row['lastmodified'],
892
+                    'etag'         => '"' . $row['etag'] . '"',
893
+                    'calendarid'   => $row['calendarid'],
894
+                    'size'         => (int)$row['size'],
895
+                    'calendardata' => $this->readBlob($row['calendardata']),
896
+                    'component'    => strtolower($row['componenttype']),
897
+                    'classification' => (int)$row['classification']
898
+                ];
899
+            }
900
+            $result->closeCursor();
901
+        }
902
+        return $objects;
903
+    }
904
+
905
+    /**
906
+     * Creates a new calendar object.
907
+     *
908
+     * The object uri is only the basename, or filename and not a full path.
909
+     *
910
+     * It is possible return an etag from this function, which will be used in
911
+     * the response to this PUT request. Note that the ETag must be surrounded
912
+     * by double-quotes.
913
+     *
914
+     * However, you should only really return this ETag if you don't mangle the
915
+     * calendar-data. If the result of a subsequent GET to this object is not
916
+     * the exact same as this request body, you should omit the ETag.
917
+     *
918
+     * @param mixed $calendarId
919
+     * @param string $objectUri
920
+     * @param string $calendarData
921
+     * @return string
922
+     */
923
+    function createCalendarObject($calendarId, $objectUri, $calendarData) {
924
+        $extraData = $this->getDenormalizedData($calendarData);
925
+
926
+        $query = $this->db->getQueryBuilder();
927
+        $query->insert('calendarobjects')
928
+            ->values([
929
+                'calendarid' => $query->createNamedParameter($calendarId),
930
+                'uri' => $query->createNamedParameter($objectUri),
931
+                'calendardata' => $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB),
932
+                'lastmodified' => $query->createNamedParameter(time()),
933
+                'etag' => $query->createNamedParameter($extraData['etag']),
934
+                'size' => $query->createNamedParameter($extraData['size']),
935
+                'componenttype' => $query->createNamedParameter($extraData['componentType']),
936
+                'firstoccurence' => $query->createNamedParameter($extraData['firstOccurence']),
937
+                'lastoccurence' => $query->createNamedParameter($extraData['lastOccurence']),
938
+                'classification' => $query->createNamedParameter($extraData['classification']),
939
+                'uid' => $query->createNamedParameter($extraData['uid']),
940
+            ])
941
+            ->execute();
942
+
943
+        $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', new GenericEvent(
944
+            '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject',
945
+            [
946
+                'calendarId' => $calendarId,
947
+                'calendarData' => $this->getCalendarById($calendarId),
948
+                'shares' => $this->getShares($calendarId),
949
+                'objectData' => $this->getCalendarObject($calendarId, $objectUri),
950
+            ]
951
+        ));
952
+        $this->addChange($calendarId, $objectUri, 1);
953
+
954
+        return '"' . $extraData['etag'] . '"';
955
+    }
956
+
957
+    /**
958
+     * Updates an existing calendarobject, based on it's uri.
959
+     *
960
+     * The object uri is only the basename, or filename and not a full path.
961
+     *
962
+     * It is possible return an etag from this function, which will be used in
963
+     * the response to this PUT request. Note that the ETag must be surrounded
964
+     * by double-quotes.
965
+     *
966
+     * However, you should only really return this ETag if you don't mangle the
967
+     * calendar-data. If the result of a subsequent GET to this object is not
968
+     * the exact same as this request body, you should omit the ETag.
969
+     *
970
+     * @param mixed $calendarId
971
+     * @param string $objectUri
972
+     * @param string $calendarData
973
+     * @return string
974
+     */
975
+    function updateCalendarObject($calendarId, $objectUri, $calendarData) {
976
+        $extraData = $this->getDenormalizedData($calendarData);
977
+
978
+        $query = $this->db->getQueryBuilder();
979
+        $query->update('calendarobjects')
980
+                ->set('calendardata', $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB))
981
+                ->set('lastmodified', $query->createNamedParameter(time()))
982
+                ->set('etag', $query->createNamedParameter($extraData['etag']))
983
+                ->set('size', $query->createNamedParameter($extraData['size']))
984
+                ->set('componenttype', $query->createNamedParameter($extraData['componentType']))
985
+                ->set('firstoccurence', $query->createNamedParameter($extraData['firstOccurence']))
986
+                ->set('lastoccurence', $query->createNamedParameter($extraData['lastOccurence']))
987
+                ->set('classification', $query->createNamedParameter($extraData['classification']))
988
+                ->set('uid', $query->createNamedParameter($extraData['uid']))
989
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
990
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
991
+            ->execute();
992
+
993
+        $data = $this->getCalendarObject($calendarId, $objectUri);
994
+        if (is_array($data)) {
995
+            $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', new GenericEvent(
996
+                '\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject',
997
+                [
998
+                    'calendarId' => $calendarId,
999
+                    'calendarData' => $this->getCalendarById($calendarId),
1000
+                    'shares' => $this->getShares($calendarId),
1001
+                    'objectData' => $data,
1002
+                ]
1003
+            ));
1004
+        }
1005
+        $this->addChange($calendarId, $objectUri, 2);
1006
+
1007
+        return '"' . $extraData['etag'] . '"';
1008
+    }
1009
+
1010
+    /**
1011
+     * @param int $calendarObjectId
1012
+     * @param int $classification
1013
+     */
1014
+    public function setClassification($calendarObjectId, $classification) {
1015
+        if (!in_array($classification, [
1016
+            self::CLASSIFICATION_PUBLIC, self::CLASSIFICATION_PRIVATE, self::CLASSIFICATION_CONFIDENTIAL
1017
+        ])) {
1018
+            throw new \InvalidArgumentException();
1019
+        }
1020
+        $query = $this->db->getQueryBuilder();
1021
+        $query->update('calendarobjects')
1022
+            ->set('classification', $query->createNamedParameter($classification))
1023
+            ->where($query->expr()->eq('id', $query->createNamedParameter($calendarObjectId)))
1024
+            ->execute();
1025
+    }
1026
+
1027
+    /**
1028
+     * Deletes an existing calendar object.
1029
+     *
1030
+     * The object uri is only the basename, or filename and not a full path.
1031
+     *
1032
+     * @param mixed $calendarId
1033
+     * @param string $objectUri
1034
+     * @return void
1035
+     */
1036
+    function deleteCalendarObject($calendarId, $objectUri) {
1037
+        $data = $this->getCalendarObject($calendarId, $objectUri);
1038
+        if (is_array($data)) {
1039
+            $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', new GenericEvent(
1040
+                '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject',
1041
+                [
1042
+                    'calendarId' => $calendarId,
1043
+                    'calendarData' => $this->getCalendarById($calendarId),
1044
+                    'shares' => $this->getShares($calendarId),
1045
+                    'objectData' => $data,
1046
+                ]
1047
+            ));
1048
+        }
1049
+
1050
+        $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ?');
1051
+        $stmt->execute([$calendarId, $objectUri]);
1052
+
1053
+        $this->addChange($calendarId, $objectUri, 3);
1054
+    }
1055
+
1056
+    /**
1057
+     * Performs a calendar-query on the contents of this calendar.
1058
+     *
1059
+     * The calendar-query is defined in RFC4791 : CalDAV. Using the
1060
+     * calendar-query it is possible for a client to request a specific set of
1061
+     * object, based on contents of iCalendar properties, date-ranges and
1062
+     * iCalendar component types (VTODO, VEVENT).
1063
+     *
1064
+     * This method should just return a list of (relative) urls that match this
1065
+     * query.
1066
+     *
1067
+     * The list of filters are specified as an array. The exact array is
1068
+     * documented by Sabre\CalDAV\CalendarQueryParser.
1069
+     *
1070
+     * Note that it is extremely likely that getCalendarObject for every path
1071
+     * returned from this method will be called almost immediately after. You
1072
+     * may want to anticipate this to speed up these requests.
1073
+     *
1074
+     * This method provides a default implementation, which parses *all* the
1075
+     * iCalendar objects in the specified calendar.
1076
+     *
1077
+     * This default may well be good enough for personal use, and calendars
1078
+     * that aren't very large. But if you anticipate high usage, big calendars
1079
+     * or high loads, you are strongly advised to optimize certain paths.
1080
+     *
1081
+     * The best way to do so is override this method and to optimize
1082
+     * specifically for 'common filters'.
1083
+     *
1084
+     * Requests that are extremely common are:
1085
+     *   * requests for just VEVENTS
1086
+     *   * requests for just VTODO
1087
+     *   * requests with a time-range-filter on either VEVENT or VTODO.
1088
+     *
1089
+     * ..and combinations of these requests. It may not be worth it to try to
1090
+     * handle every possible situation and just rely on the (relatively
1091
+     * easy to use) CalendarQueryValidator to handle the rest.
1092
+     *
1093
+     * Note that especially time-range-filters may be difficult to parse. A
1094
+     * time-range filter specified on a VEVENT must for instance also handle
1095
+     * recurrence rules correctly.
1096
+     * A good example of how to interprete all these filters can also simply
1097
+     * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
1098
+     * as possible, so it gives you a good idea on what type of stuff you need
1099
+     * to think of.
1100
+     *
1101
+     * @param mixed $calendarId
1102
+     * @param array $filters
1103
+     * @return array
1104
+     */
1105
+    function calendarQuery($calendarId, array $filters) {
1106
+        $componentType = null;
1107
+        $requirePostFilter = true;
1108
+        $timeRange = null;
1109
+
1110
+        // if no filters were specified, we don't need to filter after a query
1111
+        if (!$filters['prop-filters'] && !$filters['comp-filters']) {
1112
+            $requirePostFilter = false;
1113
+        }
1114
+
1115
+        // Figuring out if there's a component filter
1116
+        if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
1117
+            $componentType = $filters['comp-filters'][0]['name'];
1118
+
1119
+            // Checking if we need post-filters
1120
+            if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
1121
+                $requirePostFilter = false;
1122
+            }
1123
+            // There was a time-range filter
1124
+            if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range'])) {
1125
+                $timeRange = $filters['comp-filters'][0]['time-range'];
1126
+
1127
+                // If start time OR the end time is not specified, we can do a
1128
+                // 100% accurate mysql query.
1129
+                if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
1130
+                    $requirePostFilter = false;
1131
+                }
1132
+            }
1133
+
1134
+        }
1135
+        $columns = ['uri'];
1136
+        if ($requirePostFilter) {
1137
+            $columns = ['uri', 'calendardata'];
1138
+        }
1139
+        $query = $this->db->getQueryBuilder();
1140
+        $query->select($columns)
1141
+            ->from('calendarobjects')
1142
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
1143
+
1144
+        if ($componentType) {
1145
+            $query->andWhere($query->expr()->eq('componenttype', $query->createNamedParameter($componentType)));
1146
+        }
1147
+
1148
+        if ($timeRange && $timeRange['start']) {
1149
+            $query->andWhere($query->expr()->gt('lastoccurence', $query->createNamedParameter($timeRange['start']->getTimeStamp())));
1150
+        }
1151
+        if ($timeRange && $timeRange['end']) {
1152
+            $query->andWhere($query->expr()->lt('firstoccurence', $query->createNamedParameter($timeRange['end']->getTimeStamp())));
1153
+        }
1154
+
1155
+        $stmt = $query->execute();
1156
+
1157
+        $result = [];
1158
+        while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1159
+            if ($requirePostFilter) {
1160
+                if (!$this->validateFilterForObject($row, $filters)) {
1161
+                    continue;
1162
+                }
1163
+            }
1164
+            $result[] = $row['uri'];
1165
+        }
1166
+
1167
+        return $result;
1168
+    }
1169
+
1170
+    /**
1171
+     * Searches through all of a users calendars and calendar objects to find
1172
+     * an object with a specific UID.
1173
+     *
1174
+     * This method should return the path to this object, relative to the
1175
+     * calendar home, so this path usually only contains two parts:
1176
+     *
1177
+     * calendarpath/objectpath.ics
1178
+     *
1179
+     * If the uid is not found, return null.
1180
+     *
1181
+     * This method should only consider * objects that the principal owns, so
1182
+     * any calendars owned by other principals that also appear in this
1183
+     * collection should be ignored.
1184
+     *
1185
+     * @param string $principalUri
1186
+     * @param string $uid
1187
+     * @return string|null
1188
+     */
1189
+    function getCalendarObjectByUID($principalUri, $uid) {
1190
+
1191
+        $query = $this->db->getQueryBuilder();
1192
+        $query->selectAlias('c.uri', 'calendaruri')->selectAlias('co.uri', 'objecturi')
1193
+            ->from('calendarobjects', 'co')
1194
+            ->leftJoin('co', 'calendars', 'c', $query->expr()->eq('co.calendarid', 'c.id'))
1195
+            ->where($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri)))
1196
+            ->andWhere($query->expr()->eq('co.uid', $query->createNamedParameter($uid)));
1197
+
1198
+        $stmt = $query->execute();
1199
+
1200
+        if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1201
+            return $row['calendaruri'] . '/' . $row['objecturi'];
1202
+        }
1203
+
1204
+        return null;
1205
+    }
1206
+
1207
+    /**
1208
+     * The getChanges method returns all the changes that have happened, since
1209
+     * the specified syncToken in the specified calendar.
1210
+     *
1211
+     * This function should return an array, such as the following:
1212
+     *
1213
+     * [
1214
+     *   'syncToken' => 'The current synctoken',
1215
+     *   'added'   => [
1216
+     *      'new.txt',
1217
+     *   ],
1218
+     *   'modified'   => [
1219
+     *      'modified.txt',
1220
+     *   ],
1221
+     *   'deleted' => [
1222
+     *      'foo.php.bak',
1223
+     *      'old.txt'
1224
+     *   ]
1225
+     * );
1226
+     *
1227
+     * The returned syncToken property should reflect the *current* syncToken
1228
+     * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
1229
+     * property This is * needed here too, to ensure the operation is atomic.
1230
+     *
1231
+     * If the $syncToken argument is specified as null, this is an initial
1232
+     * sync, and all members should be reported.
1233
+     *
1234
+     * The modified property is an array of nodenames that have changed since
1235
+     * the last token.
1236
+     *
1237
+     * The deleted property is an array with nodenames, that have been deleted
1238
+     * from collection.
1239
+     *
1240
+     * The $syncLevel argument is basically the 'depth' of the report. If it's
1241
+     * 1, you only have to report changes that happened only directly in
1242
+     * immediate descendants. If it's 2, it should also include changes from
1243
+     * the nodes below the child collections. (grandchildren)
1244
+     *
1245
+     * The $limit argument allows a client to specify how many results should
1246
+     * be returned at most. If the limit is not specified, it should be treated
1247
+     * as infinite.
1248
+     *
1249
+     * If the limit (infinite or not) is higher than you're willing to return,
1250
+     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
1251
+     *
1252
+     * If the syncToken is expired (due to data cleanup) or unknown, you must
1253
+     * return null.
1254
+     *
1255
+     * The limit is 'suggestive'. You are free to ignore it.
1256
+     *
1257
+     * @param string $calendarId
1258
+     * @param string $syncToken
1259
+     * @param int $syncLevel
1260
+     * @param int $limit
1261
+     * @return array
1262
+     */
1263
+    function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null) {
1264
+        // Current synctoken
1265
+        $stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*calendars` WHERE `id` = ?');
1266
+        $stmt->execute([ $calendarId ]);
1267
+        $currentToken = $stmt->fetchColumn(0);
1268
+
1269
+        if (is_null($currentToken)) {
1270
+            return null;
1271
+        }
1272
+
1273
+        $result = [
1274
+            'syncToken' => $currentToken,
1275
+            'added'     => [],
1276
+            'modified'  => [],
1277
+            'deleted'   => [],
1278
+        ];
1279
+
1280
+        if ($syncToken) {
1281
+
1282
+            $query = "SELECT `uri`, `operation` FROM `*PREFIX*calendarchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `calendarid` = ? ORDER BY `synctoken`";
1283
+            if ($limit>0) {
1284
+                $query.= " `LIMIT` " . (int)$limit;
1285
+            }
1286
+
1287
+            // Fetching all changes
1288
+            $stmt = $this->db->prepare($query);
1289
+            $stmt->execute([$syncToken, $currentToken, $calendarId]);
1290
+
1291
+            $changes = [];
1292
+
1293
+            // This loop ensures that any duplicates are overwritten, only the
1294
+            // last change on a node is relevant.
1295
+            while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1296
+
1297
+                $changes[$row['uri']] = $row['operation'];
1298
+
1299
+            }
1300
+
1301
+            foreach($changes as $uri => $operation) {
1302
+
1303
+                switch($operation) {
1304
+                    case 1 :
1305
+                        $result['added'][] = $uri;
1306
+                        break;
1307
+                    case 2 :
1308
+                        $result['modified'][] = $uri;
1309
+                        break;
1310
+                    case 3 :
1311
+                        $result['deleted'][] = $uri;
1312
+                        break;
1313
+                }
1314
+
1315
+            }
1316
+        } else {
1317
+            // No synctoken supplied, this is the initial sync.
1318
+            $query = "SELECT `uri` FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ?";
1319
+            $stmt = $this->db->prepare($query);
1320
+            $stmt->execute([$calendarId]);
1321
+
1322
+            $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
1323
+        }
1324
+        return $result;
1325
+
1326
+    }
1327
+
1328
+    /**
1329
+     * Returns a list of subscriptions for a principal.
1330
+     *
1331
+     * Every subscription is an array with the following keys:
1332
+     *  * id, a unique id that will be used by other functions to modify the
1333
+     *    subscription. This can be the same as the uri or a database key.
1334
+     *  * uri. This is just the 'base uri' or 'filename' of the subscription.
1335
+     *  * principaluri. The owner of the subscription. Almost always the same as
1336
+     *    principalUri passed to this method.
1337
+     *
1338
+     * Furthermore, all the subscription info must be returned too:
1339
+     *
1340
+     * 1. {DAV:}displayname
1341
+     * 2. {http://apple.com/ns/ical/}refreshrate
1342
+     * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
1343
+     *    should not be stripped).
1344
+     * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
1345
+     *    should not be stripped).
1346
+     * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
1347
+     *    attachments should not be stripped).
1348
+     * 6. {http://calendarserver.org/ns/}source (Must be a
1349
+     *     Sabre\DAV\Property\Href).
1350
+     * 7. {http://apple.com/ns/ical/}calendar-color
1351
+     * 8. {http://apple.com/ns/ical/}calendar-order
1352
+     * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
1353
+     *    (should just be an instance of
1354
+     *    Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
1355
+     *    default components).
1356
+     *
1357
+     * @param string $principalUri
1358
+     * @return array
1359
+     */
1360
+    function getSubscriptionsForUser($principalUri) {
1361
+        $fields = array_values($this->subscriptionPropertyMap);
1362
+        $fields[] = 'id';
1363
+        $fields[] = 'uri';
1364
+        $fields[] = 'source';
1365
+        $fields[] = 'principaluri';
1366
+        $fields[] = 'lastmodified';
1367
+
1368
+        $query = $this->db->getQueryBuilder();
1369
+        $query->select($fields)
1370
+            ->from('calendarsubscriptions')
1371
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
1372
+            ->orderBy('calendarorder', 'asc');
1373
+        $stmt =$query->execute();
1374
+
1375
+        $subscriptions = [];
1376
+        while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1377
+
1378
+            $subscription = [
1379
+                'id'           => $row['id'],
1380
+                'uri'          => $row['uri'],
1381
+                'principaluri' => $row['principaluri'],
1382
+                'source'       => $row['source'],
1383
+                'lastmodified' => $row['lastmodified'],
1384
+
1385
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
1386
+            ];
1387
+
1388
+            foreach($this->subscriptionPropertyMap as $xmlName=>$dbName) {
1389
+                if (!is_null($row[$dbName])) {
1390
+                    $subscription[$xmlName] = $row[$dbName];
1391
+                }
1392
+            }
1393
+
1394
+            $subscriptions[] = $subscription;
1395
+
1396
+        }
1397
+
1398
+        return $subscriptions;
1399
+    }
1400
+
1401
+    /**
1402
+     * Creates a new subscription for a principal.
1403
+     *
1404
+     * If the creation was a success, an id must be returned that can be used to reference
1405
+     * this subscription in other methods, such as updateSubscription.
1406
+     *
1407
+     * @param string $principalUri
1408
+     * @param string $uri
1409
+     * @param array $properties
1410
+     * @return mixed
1411
+     */
1412
+    function createSubscription($principalUri, $uri, array $properties) {
1413
+
1414
+        if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
1415
+            throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
1416
+        }
1417
+
1418
+        $values = [
1419
+            'principaluri' => $principalUri,
1420
+            'uri'          => $uri,
1421
+            'source'       => $properties['{http://calendarserver.org/ns/}source']->getHref(),
1422
+            'lastmodified' => time(),
1423
+        ];
1424
+
1425
+        $propertiesBoolean = ['striptodos', 'stripalarms', 'stripattachments'];
1426
+
1427
+        foreach($this->subscriptionPropertyMap as $xmlName=>$dbName) {
1428
+            if (array_key_exists($xmlName, $properties)) {
1429
+                    $values[$dbName] = $properties[$xmlName];
1430
+                    if (in_array($dbName, $propertiesBoolean)) {
1431
+                        $values[$dbName] = true;
1432
+                }
1433
+            }
1434
+        }
1435
+
1436
+        $valuesToInsert = array();
1437
+
1438
+        $query = $this->db->getQueryBuilder();
1439
+
1440
+        foreach (array_keys($values) as $name) {
1441
+            $valuesToInsert[$name] = $query->createNamedParameter($values[$name]);
1442
+        }
1443
+
1444
+        $query->insert('calendarsubscriptions')
1445
+            ->values($valuesToInsert)
1446
+            ->execute();
1447
+
1448
+        return $this->db->lastInsertId('*PREFIX*calendarsubscriptions');
1449
+    }
1450
+
1451
+    /**
1452
+     * Updates a subscription
1453
+     *
1454
+     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
1455
+     * To do the actual updates, you must tell this object which properties
1456
+     * you're going to process with the handle() method.
1457
+     *
1458
+     * Calling the handle method is like telling the PropPatch object "I
1459
+     * promise I can handle updating this property".
1460
+     *
1461
+     * Read the PropPatch documentation for more info and examples.
1462
+     *
1463
+     * @param mixed $subscriptionId
1464
+     * @param PropPatch $propPatch
1465
+     * @return void
1466
+     */
1467
+    function updateSubscription($subscriptionId, PropPatch $propPatch) {
1468
+        $supportedProperties = array_keys($this->subscriptionPropertyMap);
1469
+        $supportedProperties[] = '{http://calendarserver.org/ns/}source';
1470
+
1471
+        $propPatch->handle($supportedProperties, function($mutations) use ($subscriptionId) {
1472
+
1473
+            $newValues = [];
1474
+
1475
+            foreach($mutations as $propertyName=>$propertyValue) {
1476
+                if ($propertyName === '{http://calendarserver.org/ns/}source') {
1477
+                    $newValues['source'] = $propertyValue->getHref();
1478
+                } else {
1479
+                    $fieldName = $this->subscriptionPropertyMap[$propertyName];
1480
+                    $newValues[$fieldName] = $propertyValue;
1481
+                }
1482
+            }
1483
+
1484
+            $query = $this->db->getQueryBuilder();
1485
+            $query->update('calendarsubscriptions')
1486
+                ->set('lastmodified', $query->createNamedParameter(time()));
1487
+            foreach($newValues as $fieldName=>$value) {
1488
+                $query->set($fieldName, $query->createNamedParameter($value));
1489
+            }
1490
+            $query->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
1491
+                ->execute();
1492
+
1493
+            return true;
1494
+
1495
+        });
1496
+    }
1497
+
1498
+    /**
1499
+     * Deletes a subscription.
1500
+     *
1501
+     * @param mixed $subscriptionId
1502
+     * @return void
1503
+     */
1504
+    function deleteSubscription($subscriptionId) {
1505
+        $query = $this->db->getQueryBuilder();
1506
+        $query->delete('calendarsubscriptions')
1507
+            ->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
1508
+            ->execute();
1509
+    }
1510
+
1511
+    /**
1512
+     * Returns a single scheduling object for the inbox collection.
1513
+     *
1514
+     * The returned array should contain the following elements:
1515
+     *   * uri - A unique basename for the object. This will be used to
1516
+     *           construct a full uri.
1517
+     *   * calendardata - The iCalendar object
1518
+     *   * lastmodified - The last modification date. Can be an int for a unix
1519
+     *                    timestamp, or a PHP DateTime object.
1520
+     *   * etag - A unique token that must change if the object changed.
1521
+     *   * size - The size of the object, in bytes.
1522
+     *
1523
+     * @param string $principalUri
1524
+     * @param string $objectUri
1525
+     * @return array
1526
+     */
1527
+    function getSchedulingObject($principalUri, $objectUri) {
1528
+        $query = $this->db->getQueryBuilder();
1529
+        $stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
1530
+            ->from('schedulingobjects')
1531
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
1532
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1533
+            ->execute();
1534
+
1535
+        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
1536
+
1537
+        if(!$row) {
1538
+            return null;
1539
+        }
1540
+
1541
+        return [
1542
+                'uri'          => $row['uri'],
1543
+                'calendardata' => $row['calendardata'],
1544
+                'lastmodified' => $row['lastmodified'],
1545
+                'etag'         => '"' . $row['etag'] . '"',
1546
+                'size'         => (int)$row['size'],
1547
+        ];
1548
+    }
1549
+
1550
+    /**
1551
+     * Returns all scheduling objects for the inbox collection.
1552
+     *
1553
+     * These objects should be returned as an array. Every item in the array
1554
+     * should follow the same structure as returned from getSchedulingObject.
1555
+     *
1556
+     * The main difference is that 'calendardata' is optional.
1557
+     *
1558
+     * @param string $principalUri
1559
+     * @return array
1560
+     */
1561
+    function getSchedulingObjects($principalUri) {
1562
+        $query = $this->db->getQueryBuilder();
1563
+        $stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
1564
+                ->from('schedulingobjects')
1565
+                ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
1566
+                ->execute();
1567
+
1568
+        $result = [];
1569
+        foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
1570
+            $result[] = [
1571
+                    'calendardata' => $row['calendardata'],
1572
+                    'uri'          => $row['uri'],
1573
+                    'lastmodified' => $row['lastmodified'],
1574
+                    'etag'         => '"' . $row['etag'] . '"',
1575
+                    'size'         => (int)$row['size'],
1576
+            ];
1577
+        }
1578
+
1579
+        return $result;
1580
+    }
1581
+
1582
+    /**
1583
+     * Deletes a scheduling object from the inbox collection.
1584
+     *
1585
+     * @param string $principalUri
1586
+     * @param string $objectUri
1587
+     * @return void
1588
+     */
1589
+    function deleteSchedulingObject($principalUri, $objectUri) {
1590
+        $query = $this->db->getQueryBuilder();
1591
+        $query->delete('schedulingobjects')
1592
+                ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
1593
+                ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1594
+                ->execute();
1595
+    }
1596
+
1597
+    /**
1598
+     * Creates a new scheduling object. This should land in a users' inbox.
1599
+     *
1600
+     * @param string $principalUri
1601
+     * @param string $objectUri
1602
+     * @param string $objectData
1603
+     * @return void
1604
+     */
1605
+    function createSchedulingObject($principalUri, $objectUri, $objectData) {
1606
+        $query = $this->db->getQueryBuilder();
1607
+        $query->insert('schedulingobjects')
1608
+            ->values([
1609
+                'principaluri' => $query->createNamedParameter($principalUri),
1610
+                'calendardata' => $query->createNamedParameter($objectData),
1611
+                'uri' => $query->createNamedParameter($objectUri),
1612
+                'lastmodified' => $query->createNamedParameter(time()),
1613
+                'etag' => $query->createNamedParameter(md5($objectData)),
1614
+                'size' => $query->createNamedParameter(strlen($objectData))
1615
+            ])
1616
+            ->execute();
1617
+    }
1618
+
1619
+    /**
1620
+     * Adds a change record to the calendarchanges table.
1621
+     *
1622
+     * @param mixed $calendarId
1623
+     * @param string $objectUri
1624
+     * @param int $operation 1 = add, 2 = modify, 3 = delete.
1625
+     * @return void
1626
+     */
1627
+    protected function addChange($calendarId, $objectUri, $operation) {
1628
+
1629
+        $stmt = $this->db->prepare('INSERT INTO `*PREFIX*calendarchanges` (`uri`, `synctoken`, `calendarid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*calendars` WHERE `id` = ?');
1630
+        $stmt->execute([
1631
+            $objectUri,
1632
+            $calendarId,
1633
+            $operation,
1634
+            $calendarId
1635
+        ]);
1636
+        $stmt = $this->db->prepare('UPDATE `*PREFIX*calendars` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
1637
+        $stmt->execute([
1638
+            $calendarId
1639
+        ]);
1640
+
1641
+    }
1642
+
1643
+    /**
1644
+     * Parses some information from calendar objects, used for optimized
1645
+     * calendar-queries.
1646
+     *
1647
+     * Returns an array with the following keys:
1648
+     *   * etag - An md5 checksum of the object without the quotes.
1649
+     *   * size - Size of the object in bytes
1650
+     *   * componentType - VEVENT, VTODO or VJOURNAL
1651
+     *   * firstOccurence
1652
+     *   * lastOccurence
1653
+     *   * uid - value of the UID property
1654
+     *
1655
+     * @param string $calendarData
1656
+     * @return array
1657
+     */
1658
+    public function getDenormalizedData($calendarData) {
1659
+
1660
+        $vObject = Reader::read($calendarData);
1661
+        $componentType = null;
1662
+        $component = null;
1663
+        $firstOccurrence = null;
1664
+        $lastOccurrence = null;
1665
+        $uid = null;
1666
+        $classification = self::CLASSIFICATION_PUBLIC;
1667
+        foreach($vObject->getComponents() as $component) {
1668
+            if ($component->name!=='VTIMEZONE') {
1669
+                $componentType = $component->name;
1670
+                $uid = (string)$component->UID;
1671
+                break;
1672
+            }
1673
+        }
1674
+        if (!$componentType) {
1675
+            throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
1676
+        }
1677
+        if ($componentType === 'VEVENT' && $component->DTSTART) {
1678
+            $firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp();
1679
+            // Finding the last occurrence is a bit harder
1680
+            if (!isset($component->RRULE)) {
1681
+                if (isset($component->DTEND)) {
1682
+                    $lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp();
1683
+                } elseif (isset($component->DURATION)) {
1684
+                    $endDate = clone $component->DTSTART->getDateTime();
1685
+                    $endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
1686
+                    $lastOccurrence = $endDate->getTimeStamp();
1687
+                } elseif (!$component->DTSTART->hasTime()) {
1688
+                    $endDate = clone $component->DTSTART->getDateTime();
1689
+                    $endDate->modify('+1 day');
1690
+                    $lastOccurrence = $endDate->getTimeStamp();
1691
+                } else {
1692
+                    $lastOccurrence = $firstOccurrence;
1693
+                }
1694
+            } else {
1695
+                $it = new EventIterator($vObject, (string)$component->UID);
1696
+                $maxDate = new \DateTime(self::MAX_DATE);
1697
+                if ($it->isInfinite()) {
1698
+                    $lastOccurrence = $maxDate->getTimestamp();
1699
+                } else {
1700
+                    $end = $it->getDtEnd();
1701
+                    while($it->valid() && $end < $maxDate) {
1702
+                        $end = $it->getDtEnd();
1703
+                        $it->next();
1704
+
1705
+                    }
1706
+                    $lastOccurrence = $end->getTimestamp();
1707
+                }
1708
+
1709
+            }
1710
+        }
1711
+
1712
+        if ($component->CLASS) {
1713
+            $classification = CalDavBackend::CLASSIFICATION_PRIVATE;
1714
+            switch ($component->CLASS->getValue()) {
1715
+                case 'PUBLIC':
1716
+                    $classification = CalDavBackend::CLASSIFICATION_PUBLIC;
1717
+                    break;
1718
+                case 'CONFIDENTIAL':
1719
+                    $classification = CalDavBackend::CLASSIFICATION_CONFIDENTIAL;
1720
+                    break;
1721
+            }
1722
+        }
1723
+        return [
1724
+            'etag' => md5($calendarData),
1725
+            'size' => strlen($calendarData),
1726
+            'componentType' => $componentType,
1727
+            'firstOccurence' => is_null($firstOccurrence) ? null : max(0, $firstOccurrence),
1728
+            'lastOccurence'  => $lastOccurrence,
1729
+            'uid' => $uid,
1730
+            'classification' => $classification
1731
+        ];
1732
+
1733
+    }
1734
+
1735
+    private function readBlob($cardData) {
1736
+        if (is_resource($cardData)) {
1737
+            return stream_get_contents($cardData);
1738
+        }
1739
+
1740
+        return $cardData;
1741
+    }
1742
+
1743
+    /**
1744
+     * @param IShareable $shareable
1745
+     * @param array $add
1746
+     * @param array $remove
1747
+     */
1748
+    public function updateShares($shareable, $add, $remove) {
1749
+        $calendarId = $shareable->getResourceId();
1750
+        $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateShares', new GenericEvent(
1751
+            '\OCA\DAV\CalDAV\CalDavBackend::updateShares',
1752
+            [
1753
+                'calendarId' => $calendarId,
1754
+                'calendarData' => $this->getCalendarById($calendarId),
1755
+                'shares' => $this->getShares($calendarId),
1756
+                'add' => $add,
1757
+                'remove' => $remove,
1758
+            ]));
1759
+        $this->sharingBackend->updateShares($shareable, $add, $remove);
1760
+    }
1761
+
1762
+    /**
1763
+     * @param int $resourceId
1764
+     * @return array
1765
+     */
1766
+    public function getShares($resourceId) {
1767
+        return $this->sharingBackend->getShares($resourceId);
1768
+    }
1769
+
1770
+    /**
1771
+     * @param boolean $value
1772
+     * @param \OCA\DAV\CalDAV\Calendar $calendar
1773
+     * @return string|null
1774
+     */
1775
+    public function setPublishStatus($value, $calendar) {
1776
+        $query = $this->db->getQueryBuilder();
1777
+        if ($value) {
1778
+            $publicUri = $this->random->generate(16, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS);
1779
+            $query->insert('dav_shares')
1780
+                ->values([
1781
+                    'principaluri' => $query->createNamedParameter($calendar->getPrincipalURI()),
1782
+                    'type' => $query->createNamedParameter('calendar'),
1783
+                    'access' => $query->createNamedParameter(self::ACCESS_PUBLIC),
1784
+                    'resourceid' => $query->createNamedParameter($calendar->getResourceId()),
1785
+                    'publicuri' => $query->createNamedParameter($publicUri)
1786
+                ]);
1787
+            $query->execute();
1788
+            return $publicUri;
1789
+        }
1790
+        $query->delete('dav_shares')
1791
+            ->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
1792
+            ->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)));
1793
+        $query->execute();
1794
+        return null;
1795
+    }
1796
+
1797
+    /**
1798
+     * @param \OCA\DAV\CalDAV\Calendar $calendar
1799
+     * @return mixed
1800
+     */
1801
+    public function getPublishStatus($calendar) {
1802
+        $query = $this->db->getQueryBuilder();
1803
+        $result = $query->select('publicuri')
1804
+            ->from('dav_shares')
1805
+            ->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
1806
+            ->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
1807
+            ->execute();
1808
+
1809
+        $row = $result->fetch();
1810
+        $result->closeCursor();
1811
+        return $row ? reset($row) : false;
1812
+    }
1813
+
1814
+    /**
1815
+     * @param int $resourceId
1816
+     * @param array $acl
1817
+     * @return array
1818
+     */
1819
+    public function applyShareAcl($resourceId, $acl) {
1820
+        return $this->sharingBackend->applyShareAcl($resourceId, $acl);
1821
+    }
1822
+
1823
+    private function convertPrincipal($principalUri, $toV2) {
1824
+        if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
1825
+            list(, $name) = URLUtil::splitPath($principalUri);
1826
+            if ($toV2 === true) {
1827
+                return "principals/users/$name";
1828
+            }
1829
+            return "principals/$name";
1830
+        }
1831
+        return $principalUri;
1832
+    }
1833
+
1834
+    private function addOwnerPrincipal(&$calendarInfo) {
1835
+        $ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1836
+        $displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1837
+        if (isset($calendarInfo[$ownerPrincipalKey])) {
1838
+            $uri = $calendarInfo[$ownerPrincipalKey];
1839
+        } else {
1840
+            $uri = $calendarInfo['principaluri'];
1841
+        }
1842
+
1843
+        $principalInformation = $this->principalBackend->getPrincipalByPath($uri);
1844
+        if (isset($principalInformation['{DAV:}displayname'])) {
1845
+            $calendarInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
1846
+        }
1847
+    }
1848 1848
 }
Please login to merge, or discard this patch.
Spacing   +110 added lines, -110 removed lines patch added patch discarded remove patch
@@ -179,7 +179,7 @@  discard block
 block discarded – undo
179 179
 			$query->andWhere($query->expr()->neq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)));
180 180
 		}
181 181
 
182
-		return (int)$query->execute()->fetchColumn();
182
+		return (int) $query->execute()->fetchColumn();
183 183
 	}
184 184
 
185 185
 	/**
@@ -226,25 +226,25 @@  discard block
 block discarded – undo
226 226
 		$stmt = $query->execute();
227 227
 
228 228
 		$calendars = [];
229
-		while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
229
+		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
230 230
 
231 231
 			$components = [];
232 232
 			if ($row['components']) {
233
-				$components = explode(',',$row['components']);
233
+				$components = explode(',', $row['components']);
234 234
 			}
235 235
 
236 236
 			$calendar = [
237 237
 				'id' => $row['id'],
238 238
 				'uri' => $row['uri'],
239 239
 				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
240
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
241
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
242
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
243
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
244
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
240
+				'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
241
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
242
+				'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
243
+				'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
244
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
245 245
 			];
246 246
 
247
-			foreach($this->propertyMap as $xmlName=>$dbName) {
247
+			foreach ($this->propertyMap as $xmlName=>$dbName) {
248 248
 				$calendar[$xmlName] = $row[$dbName];
249 249
 			}
250 250
 
@@ -259,7 +259,7 @@  discard block
 block discarded – undo
259 259
 
260 260
 		// query for shared calendars
261 261
 		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
262
-		$principals[]= $principalUri;
262
+		$principals[] = $principalUri;
263 263
 
264 264
 		$fields = array_values($this->propertyMap);
265 265
 		$fields[] = 'a.id';
@@ -279,8 +279,8 @@  discard block
 block discarded – undo
279 279
 			->setParameter('principaluri', $principals, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
280 280
 			->execute();
281 281
 
282
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
283
-		while($row = $result->fetch()) {
282
+		$readOnlyPropertyName = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}read-only';
283
+		while ($row = $result->fetch()) {
284 284
 			if ($row['principaluri'] === $principalUri) {
285 285
 				continue;
286 286
 			}
@@ -299,25 +299,25 @@  discard block
 block discarded – undo
299 299
 			}
300 300
 
301 301
 			list(, $name) = URLUtil::splitPath($row['principaluri']);
302
-			$uri = $row['uri'] . '_shared_by_' . $name;
303
-			$row['displayname'] = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
302
+			$uri = $row['uri'].'_shared_by_'.$name;
303
+			$row['displayname'] = $row['displayname'].' ('.$this->getUserDisplayName($name).')';
304 304
 			$components = [];
305 305
 			if ($row['components']) {
306
-				$components = explode(',',$row['components']);
306
+				$components = explode(',', $row['components']);
307 307
 			}
308 308
 			$calendar = [
309 309
 				'id' => $row['id'],
310 310
 				'uri' => $uri,
311 311
 				'principaluri' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
312
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
313
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
314
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
315
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
316
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
312
+				'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
313
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
314
+				'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
315
+				'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
316
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
317 317
 				$readOnlyPropertyName => $readOnly,
318 318
 			];
319 319
 
320
-			foreach($this->propertyMap as $xmlName=>$dbName) {
320
+			foreach ($this->propertyMap as $xmlName=>$dbName) {
321 321
 				$calendar[$xmlName] = $row[$dbName];
322 322
 			}
323 323
 
@@ -346,21 +346,21 @@  discard block
 block discarded – undo
346 346
 			->orderBy('calendarorder', 'ASC');
347 347
 		$stmt = $query->execute();
348 348
 		$calendars = [];
349
-		while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
349
+		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
350 350
 			$components = [];
351 351
 			if ($row['components']) {
352
-				$components = explode(',',$row['components']);
352
+				$components = explode(',', $row['components']);
353 353
 			}
354 354
 			$calendar = [
355 355
 				'id' => $row['id'],
356 356
 				'uri' => $row['uri'],
357 357
 				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
358
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
359
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
360
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
361
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
358
+				'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
359
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
360
+				'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
361
+				'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
362 362
 			];
363
-			foreach($this->propertyMap as $xmlName=>$dbName) {
363
+			foreach ($this->propertyMap as $xmlName=>$dbName) {
364 364
 				$calendar[$xmlName] = $row[$dbName];
365 365
 			}
366 366
 
@@ -411,27 +411,27 @@  discard block
 block discarded – undo
411 411
 			->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
412 412
 			->execute();
413 413
 
414
-		while($row = $result->fetch()) {
414
+		while ($row = $result->fetch()) {
415 415
 			list(, $name) = URLUtil::splitPath($row['principaluri']);
416
-			$row['displayname'] = $row['displayname'] . "($name)";
416
+			$row['displayname'] = $row['displayname']."($name)";
417 417
 			$components = [];
418 418
 			if ($row['components']) {
419
-				$components = explode(',',$row['components']);
419
+				$components = explode(',', $row['components']);
420 420
 			}
421 421
 			$calendar = [
422 422
 				'id' => $row['id'],
423 423
 				'uri' => $row['publicuri'],
424 424
 				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
425
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
426
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
427
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
428
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
429
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
430
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
431
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
425
+				'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
426
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
427
+				'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
428
+				'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
429
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
430
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}read-only' => (int) $row['access'] === Backend::ACCESS_READ,
431
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}public' => (int) $row['access'] === self::ACCESS_PUBLIC,
432 432
 			];
433 433
 
434
-			foreach($this->propertyMap as $xmlName=>$dbName) {
434
+			foreach ($this->propertyMap as $xmlName=>$dbName) {
435 435
 				$calendar[$xmlName] = $row[$dbName];
436 436
 			}
437 437
 
@@ -475,29 +475,29 @@  discard block
 block discarded – undo
475 475
 		$result->closeCursor();
476 476
 
477 477
 		if ($row === false) {
478
-			throw new NotFound('Node with name \'' . $uri . '\' could not be found');
478
+			throw new NotFound('Node with name \''.$uri.'\' could not be found');
479 479
 		}
480 480
 
481 481
 		list(, $name) = URLUtil::splitPath($row['principaluri']);
482
-		$row['displayname'] = $row['displayname'] . ' ' . "($name)";
482
+		$row['displayname'] = $row['displayname'].' '."($name)";
483 483
 		$components = [];
484 484
 		if ($row['components']) {
485
-			$components = explode(',',$row['components']);
485
+			$components = explode(',', $row['components']);
486 486
 		}
487 487
 		$calendar = [
488 488
 			'id' => $row['id'],
489 489
 			'uri' => $row['publicuri'],
490 490
 			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
491
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
492
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
493
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
494
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
495
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
496
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
497
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
491
+			'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
492
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
493
+			'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
494
+			'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
495
+			'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
496
+			'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}read-only' => (int) $row['access'] === Backend::ACCESS_READ,
497
+			'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}public' => (int) $row['access'] === self::ACCESS_PUBLIC,
498 498
 		];
499 499
 
500
-		foreach($this->propertyMap as $xmlName=>$dbName) {
500
+		foreach ($this->propertyMap as $xmlName=>$dbName) {
501 501
 			$calendar[$xmlName] = $row[$dbName];
502 502
 		}
503 503
 
@@ -537,20 +537,20 @@  discard block
 block discarded – undo
537 537
 
538 538
 		$components = [];
539 539
 		if ($row['components']) {
540
-			$components = explode(',',$row['components']);
540
+			$components = explode(',', $row['components']);
541 541
 		}
542 542
 
543 543
 		$calendar = [
544 544
 			'id' => $row['id'],
545 545
 			'uri' => $row['uri'],
546 546
 			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
547
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
548
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
549
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
550
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
547
+			'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
548
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
549
+			'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
550
+			'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
551 551
 		];
552 552
 
553
-		foreach($this->propertyMap as $xmlName=>$dbName) {
553
+		foreach ($this->propertyMap as $xmlName=>$dbName) {
554 554
 			$calendar[$xmlName] = $row[$dbName];
555 555
 		}
556 556
 
@@ -583,20 +583,20 @@  discard block
 block discarded – undo
583 583
 
584 584
 		$components = [];
585 585
 		if ($row['components']) {
586
-			$components = explode(',',$row['components']);
586
+			$components = explode(',', $row['components']);
587 587
 		}
588 588
 
589 589
 		$calendar = [
590 590
 			'id' => $row['id'],
591 591
 			'uri' => $row['uri'],
592 592
 			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
593
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
594
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
595
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
596
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
593
+			'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
594
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
595
+			'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
596
+			'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
597 597
 		];
598 598
 
599
-		foreach($this->propertyMap as $xmlName=>$dbName) {
599
+		foreach ($this->propertyMap as $xmlName=>$dbName) {
600 600
 			$calendar[$xmlName] = $row[$dbName];
601 601
 		}
602 602
 
@@ -630,16 +630,16 @@  discard block
 block discarded – undo
630 630
 		$sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
631 631
 		if (isset($properties[$sccs])) {
632 632
 			if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) {
633
-				throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
633
+				throw new DAV\Exception('The '.$sccs.' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
634 634
 			}
635
-			$values['components'] = implode(',',$properties[$sccs]->getValue());
635
+			$values['components'] = implode(',', $properties[$sccs]->getValue());
636 636
 		}
637
-		$transp = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
637
+		$transp = '{'.Plugin::NS_CALDAV.'}schedule-calendar-transp';
638 638
 		if (isset($properties[$transp])) {
639 639
 			$values['transparent'] = (int) ($properties[$transp]->getValue() === 'transparent');
640 640
 		}
641 641
 
642
-		foreach($this->propertyMap as $xmlName=>$dbName) {
642
+		foreach ($this->propertyMap as $xmlName=>$dbName) {
643 643
 			if (isset($properties[$xmlName])) {
644 644
 				$values[$dbName] = $properties[$xmlName];
645 645
 			}
@@ -647,7 +647,7 @@  discard block
 block discarded – undo
647 647
 
648 648
 		$query = $this->db->getQueryBuilder();
649 649
 		$query->insert('calendars');
650
-		foreach($values as $column => $value) {
650
+		foreach ($values as $column => $value) {
651 651
 			$query->setValue($column, $query->createNamedParameter($value));
652 652
 		}
653 653
 		$query->execute();
@@ -680,14 +680,14 @@  discard block
 block discarded – undo
680 680
 	 */
681 681
 	function updateCalendar($calendarId, PropPatch $propPatch) {
682 682
 		$supportedProperties = array_keys($this->propertyMap);
683
-		$supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
683
+		$supportedProperties[] = '{'.Plugin::NS_CALDAV.'}schedule-calendar-transp';
684 684
 
685 685
 		$propPatch->handle($supportedProperties, function($mutations) use ($calendarId) {
686 686
 			$newValues = [];
687 687
 			foreach ($mutations as $propertyName => $propertyValue) {
688 688
 
689 689
 				switch ($propertyName) {
690
-					case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' :
690
+					case '{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' :
691 691
 						$fieldName = 'transparent';
692 692
 						$newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent');
693 693
 						break;
@@ -797,16 +797,16 @@  discard block
 block discarded – undo
797 797
 		$stmt = $query->execute();
798 798
 
799 799
 		$result = [];
800
-		foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
800
+		foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
801 801
 			$result[] = [
802 802
 					'id'           => $row['id'],
803 803
 					'uri'          => $row['uri'],
804 804
 					'lastmodified' => $row['lastmodified'],
805
-					'etag'         => '"' . $row['etag'] . '"',
805
+					'etag'         => '"'.$row['etag'].'"',
806 806
 					'calendarid'   => $row['calendarid'],
807
-					'size'         => (int)$row['size'],
807
+					'size'         => (int) $row['size'],
808 808
 					'component'    => strtolower($row['componenttype']),
809
-					'classification'=> (int)$row['classification']
809
+					'classification'=> (int) $row['classification']
810 810
 			];
811 811
 		}
812 812
 
@@ -839,18 +839,18 @@  discard block
 block discarded – undo
839 839
 		$stmt = $query->execute();
840 840
 		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
841 841
 
842
-		if(!$row) return null;
842
+		if (!$row) return null;
843 843
 
844 844
 		return [
845 845
 				'id'            => $row['id'],
846 846
 				'uri'           => $row['uri'],
847 847
 				'lastmodified'  => $row['lastmodified'],
848
-				'etag'          => '"' . $row['etag'] . '"',
848
+				'etag'          => '"'.$row['etag'].'"',
849 849
 				'calendarid'    => $row['calendarid'],
850
-				'size'          => (int)$row['size'],
850
+				'size'          => (int) $row['size'],
851 851
 				'calendardata'  => $this->readBlob($row['calendardata']),
852 852
 				'component'     => strtolower($row['componenttype']),
853
-				'classification'=> (int)$row['classification']
853
+				'classification'=> (int) $row['classification']
854 854
 		];
855 855
 	}
856 856
 
@@ -889,12 +889,12 @@  discard block
 block discarded – undo
889 889
 					'id'           => $row['id'],
890 890
 					'uri'          => $row['uri'],
891 891
 					'lastmodified' => $row['lastmodified'],
892
-					'etag'         => '"' . $row['etag'] . '"',
892
+					'etag'         => '"'.$row['etag'].'"',
893 893
 					'calendarid'   => $row['calendarid'],
894
-					'size'         => (int)$row['size'],
894
+					'size'         => (int) $row['size'],
895 895
 					'calendardata' => $this->readBlob($row['calendardata']),
896 896
 					'component'    => strtolower($row['componenttype']),
897
-					'classification' => (int)$row['classification']
897
+					'classification' => (int) $row['classification']
898 898
 				];
899 899
 			}
900 900
 			$result->closeCursor();
@@ -951,7 +951,7 @@  discard block
 block discarded – undo
951 951
 		));
952 952
 		$this->addChange($calendarId, $objectUri, 1);
953 953
 
954
-		return '"' . $extraData['etag'] . '"';
954
+		return '"'.$extraData['etag'].'"';
955 955
 	}
956 956
 
957 957
 	/**
@@ -1004,7 +1004,7 @@  discard block
 block discarded – undo
1004 1004
 		}
1005 1005
 		$this->addChange($calendarId, $objectUri, 2);
1006 1006
 
1007
-		return '"' . $extraData['etag'] . '"';
1007
+		return '"'.$extraData['etag'].'"';
1008 1008
 	}
1009 1009
 
1010 1010
 	/**
@@ -1155,7 +1155,7 @@  discard block
 block discarded – undo
1155 1155
 		$stmt = $query->execute();
1156 1156
 
1157 1157
 		$result = [];
1158
-		while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1158
+		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1159 1159
 			if ($requirePostFilter) {
1160 1160
 				if (!$this->validateFilterForObject($row, $filters)) {
1161 1161
 					continue;
@@ -1198,7 +1198,7 @@  discard block
 block discarded – undo
1198 1198
 		$stmt = $query->execute();
1199 1199
 
1200 1200
 		if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1201
-			return $row['calendaruri'] . '/' . $row['objecturi'];
1201
+			return $row['calendaruri'].'/'.$row['objecturi'];
1202 1202
 		}
1203 1203
 
1204 1204
 		return null;
@@ -1263,7 +1263,7 @@  discard block
 block discarded – undo
1263 1263
 	function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null) {
1264 1264
 		// Current synctoken
1265 1265
 		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*calendars` WHERE `id` = ?');
1266
-		$stmt->execute([ $calendarId ]);
1266
+		$stmt->execute([$calendarId]);
1267 1267
 		$currentToken = $stmt->fetchColumn(0);
1268 1268
 
1269 1269
 		if (is_null($currentToken)) {
@@ -1280,8 +1280,8 @@  discard block
 block discarded – undo
1280 1280
 		if ($syncToken) {
1281 1281
 
1282 1282
 			$query = "SELECT `uri`, `operation` FROM `*PREFIX*calendarchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `calendarid` = ? ORDER BY `synctoken`";
1283
-			if ($limit>0) {
1284
-				$query.= " `LIMIT` " . (int)$limit;
1283
+			if ($limit > 0) {
1284
+				$query .= " `LIMIT` ".(int) $limit;
1285 1285
 			}
1286 1286
 
1287 1287
 			// Fetching all changes
@@ -1292,15 +1292,15 @@  discard block
 block discarded – undo
1292 1292
 
1293 1293
 			// This loop ensures that any duplicates are overwritten, only the
1294 1294
 			// last change on a node is relevant.
1295
-			while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1295
+			while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1296 1296
 
1297 1297
 				$changes[$row['uri']] = $row['operation'];
1298 1298
 
1299 1299
 			}
1300 1300
 
1301
-			foreach($changes as $uri => $operation) {
1301
+			foreach ($changes as $uri => $operation) {
1302 1302
 
1303
-				switch($operation) {
1303
+				switch ($operation) {
1304 1304
 					case 1 :
1305 1305
 						$result['added'][] = $uri;
1306 1306
 						break;
@@ -1370,10 +1370,10 @@  discard block
 block discarded – undo
1370 1370
 			->from('calendarsubscriptions')
1371 1371
 			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
1372 1372
 			->orderBy('calendarorder', 'asc');
1373
-		$stmt =$query->execute();
1373
+		$stmt = $query->execute();
1374 1374
 
1375 1375
 		$subscriptions = [];
1376
-		while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1376
+		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1377 1377
 
1378 1378
 			$subscription = [
1379 1379
 				'id'           => $row['id'],
@@ -1382,10 +1382,10 @@  discard block
 block discarded – undo
1382 1382
 				'source'       => $row['source'],
1383 1383
 				'lastmodified' => $row['lastmodified'],
1384 1384
 
1385
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
1385
+				'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
1386 1386
 			];
1387 1387
 
1388
-			foreach($this->subscriptionPropertyMap as $xmlName=>$dbName) {
1388
+			foreach ($this->subscriptionPropertyMap as $xmlName=>$dbName) {
1389 1389
 				if (!is_null($row[$dbName])) {
1390 1390
 					$subscription[$xmlName] = $row[$dbName];
1391 1391
 				}
@@ -1424,7 +1424,7 @@  discard block
 block discarded – undo
1424 1424
 
1425 1425
 		$propertiesBoolean = ['striptodos', 'stripalarms', 'stripattachments'];
1426 1426
 
1427
-		foreach($this->subscriptionPropertyMap as $xmlName=>$dbName) {
1427
+		foreach ($this->subscriptionPropertyMap as $xmlName=>$dbName) {
1428 1428
 			if (array_key_exists($xmlName, $properties)) {
1429 1429
 					$values[$dbName] = $properties[$xmlName];
1430 1430
 					if (in_array($dbName, $propertiesBoolean)) {
@@ -1472,7 +1472,7 @@  discard block
 block discarded – undo
1472 1472
 
1473 1473
 			$newValues = [];
1474 1474
 
1475
-			foreach($mutations as $propertyName=>$propertyValue) {
1475
+			foreach ($mutations as $propertyName=>$propertyValue) {
1476 1476
 				if ($propertyName === '{http://calendarserver.org/ns/}source') {
1477 1477
 					$newValues['source'] = $propertyValue->getHref();
1478 1478
 				} else {
@@ -1484,7 +1484,7 @@  discard block
 block discarded – undo
1484 1484
 			$query = $this->db->getQueryBuilder();
1485 1485
 			$query->update('calendarsubscriptions')
1486 1486
 				->set('lastmodified', $query->createNamedParameter(time()));
1487
-			foreach($newValues as $fieldName=>$value) {
1487
+			foreach ($newValues as $fieldName=>$value) {
1488 1488
 				$query->set($fieldName, $query->createNamedParameter($value));
1489 1489
 			}
1490 1490
 			$query->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
@@ -1534,7 +1534,7 @@  discard block
 block discarded – undo
1534 1534
 
1535 1535
 		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
1536 1536
 
1537
-		if(!$row) {
1537
+		if (!$row) {
1538 1538
 			return null;
1539 1539
 		}
1540 1540
 
@@ -1542,8 +1542,8 @@  discard block
 block discarded – undo
1542 1542
 				'uri'          => $row['uri'],
1543 1543
 				'calendardata' => $row['calendardata'],
1544 1544
 				'lastmodified' => $row['lastmodified'],
1545
-				'etag'         => '"' . $row['etag'] . '"',
1546
-				'size'         => (int)$row['size'],
1545
+				'etag'         => '"'.$row['etag'].'"',
1546
+				'size'         => (int) $row['size'],
1547 1547
 		];
1548 1548
 	}
1549 1549
 
@@ -1566,13 +1566,13 @@  discard block
 block discarded – undo
1566 1566
 				->execute();
1567 1567
 
1568 1568
 		$result = [];
1569
-		foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
1569
+		foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
1570 1570
 			$result[] = [
1571 1571
 					'calendardata' => $row['calendardata'],
1572 1572
 					'uri'          => $row['uri'],
1573 1573
 					'lastmodified' => $row['lastmodified'],
1574
-					'etag'         => '"' . $row['etag'] . '"',
1575
-					'size'         => (int)$row['size'],
1574
+					'etag'         => '"'.$row['etag'].'"',
1575
+					'size'         => (int) $row['size'],
1576 1576
 			];
1577 1577
 		}
1578 1578
 
@@ -1664,10 +1664,10 @@  discard block
 block discarded – undo
1664 1664
 		$lastOccurrence = null;
1665 1665
 		$uid = null;
1666 1666
 		$classification = self::CLASSIFICATION_PUBLIC;
1667
-		foreach($vObject->getComponents() as $component) {
1668
-			if ($component->name!=='VTIMEZONE') {
1667
+		foreach ($vObject->getComponents() as $component) {
1668
+			if ($component->name !== 'VTIMEZONE') {
1669 1669
 				$componentType = $component->name;
1670
-				$uid = (string)$component->UID;
1670
+				$uid = (string) $component->UID;
1671 1671
 				break;
1672 1672
 			}
1673 1673
 		}
@@ -1692,13 +1692,13 @@  discard block
 block discarded – undo
1692 1692
 					$lastOccurrence = $firstOccurrence;
1693 1693
 				}
1694 1694
 			} else {
1695
-				$it = new EventIterator($vObject, (string)$component->UID);
1695
+				$it = new EventIterator($vObject, (string) $component->UID);
1696 1696
 				$maxDate = new \DateTime(self::MAX_DATE);
1697 1697
 				if ($it->isInfinite()) {
1698 1698
 					$lastOccurrence = $maxDate->getTimestamp();
1699 1699
 				} else {
1700 1700
 					$end = $it->getDtEnd();
1701
-					while($it->valid() && $end < $maxDate) {
1701
+					while ($it->valid() && $end < $maxDate) {
1702 1702
 						$end = $it->getDtEnd();
1703 1703
 						$it->next();
1704 1704
 
@@ -1832,8 +1832,8 @@  discard block
 block discarded – undo
1832 1832
 	}
1833 1833
 
1834 1834
 	private function addOwnerPrincipal(&$calendarInfo) {
1835
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1836
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1835
+		$ownerPrincipalKey = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal';
1836
+		$displaynameKey = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD.'}owner-displayname';
1837 1837
 		if (isset($calendarInfo[$ownerPrincipalKey])) {
1838 1838
 			$uri = $calendarInfo[$ownerPrincipalKey];
1839 1839
 		} else {
Please login to merge, or discard this patch.