Completed
Branch develop (456163)
by
unknown
23:40
created
htdocs/includes/sabre/sabre/dav/lib/CalDAV/Notifications/INode.php 1 patch
Indentation   +15 added lines, -15 removed lines patch added patch discarded remove patch
@@ -22,20 +22,20 @@
 block discarded – undo
22 22
  */
23 23
 interface INode
24 24
 {
25
-    /**
26
-     * This method must return an xml element, using the
27
-     * Sabre\CalDAV\Xml\Notification\NotificationInterface classes.
28
-     *
29
-     * @return NotificationInterface
30
-     */
31
-    public function getNotificationType();
25
+	/**
26
+	 * This method must return an xml element, using the
27
+	 * Sabre\CalDAV\Xml\Notification\NotificationInterface classes.
28
+	 *
29
+	 * @return NotificationInterface
30
+	 */
31
+	public function getNotificationType();
32 32
 
33
-    /**
34
-     * Returns the etag for the notification.
35
-     *
36
-     * The etag must be surrounded by literal double-quotes.
37
-     *
38
-     * @return string
39
-     */
40
-    public function getETag();
33
+	/**
34
+	 * Returns the etag for the notification.
35
+	 *
36
+	 * The etag must be surrounded by literal double-quotes.
37
+	 *
38
+	 * @return string
39
+	 */
40
+	public function getETag();
41 41
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/CalDAV/SharingPlugin.php 1 patch
Indentation   +316 added lines, -316 removed lines patch added patch discarded remove patch
@@ -27,320 +27,320 @@
 block discarded – undo
27 27
  */
28 28
 class SharingPlugin extends DAV\ServerPlugin
29 29
 {
30
-    /**
31
-     * Reference to SabreDAV server object.
32
-     *
33
-     * @var DAV\Server
34
-     */
35
-    protected $server;
36
-
37
-    /**
38
-     * This method should return a list of server-features.
39
-     *
40
-     * This is for example 'versioning' and is added to the DAV: header
41
-     * in an OPTIONS response.
42
-     *
43
-     * @return array
44
-     */
45
-    public function getFeatures()
46
-    {
47
-        return ['calendarserver-sharing'];
48
-    }
49
-
50
-    /**
51
-     * Returns a plugin name.
52
-     *
53
-     * Using this name other plugins will be able to access other plugins
54
-     * using Sabre\DAV\Server::getPlugin
55
-     *
56
-     * @return string
57
-     */
58
-    public function getPluginName()
59
-    {
60
-        return 'caldav-sharing';
61
-    }
62
-
63
-    /**
64
-     * This initializes the plugin.
65
-     *
66
-     * This function is called by Sabre\DAV\Server, after
67
-     * addPlugin is called.
68
-     *
69
-     * This method should set up the required event subscriptions.
70
-     */
71
-    public function initialize(DAV\Server $server)
72
-    {
73
-        $this->server = $server;
74
-
75
-        if (is_null($this->server->getPlugin('sharing'))) {
76
-            throw new \LogicException('The generic "sharing" plugin must be loaded before the caldav sharing plugin. Call $server->addPlugin(new \Sabre\DAV\Sharing\Plugin()); before this one.');
77
-        }
78
-
79
-        array_push(
80
-            $this->server->protectedProperties,
81
-            '{'.Plugin::NS_CALENDARSERVER.'}invite',
82
-            '{'.Plugin::NS_CALENDARSERVER.'}allowed-sharing-modes',
83
-            '{'.Plugin::NS_CALENDARSERVER.'}shared-url'
84
-        );
85
-
86
-        $this->server->xml->elementMap['{'.Plugin::NS_CALENDARSERVER.'}share'] = 'Sabre\\CalDAV\\Xml\\Request\\Share';
87
-        $this->server->xml->elementMap['{'.Plugin::NS_CALENDARSERVER.'}invite-reply'] = 'Sabre\\CalDAV\\Xml\\Request\\InviteReply';
88
-
89
-        $this->server->on('propFind', [$this, 'propFindEarly']);
90
-        $this->server->on('propFind', [$this, 'propFindLate'], 150);
91
-        $this->server->on('propPatch', [$this, 'propPatch'], 40);
92
-        $this->server->on('method:POST', [$this, 'httpPost']);
93
-    }
94
-
95
-    /**
96
-     * This event is triggered when properties are requested for a certain
97
-     * node.
98
-     *
99
-     * This allows us to inject any properties early.
100
-     */
101
-    public function propFindEarly(DAV\PropFind $propFind, DAV\INode $node)
102
-    {
103
-        if ($node instanceof ISharedCalendar) {
104
-            $propFind->handle('{'.Plugin::NS_CALENDARSERVER.'}invite', function () use ($node) {
105
-                return new Xml\Property\Invite(
106
-                    $node->getInvites()
107
-                );
108
-            });
109
-        }
110
-    }
111
-
112
-    /**
113
-     * This method is triggered *after* all properties have been retrieved.
114
-     * This allows us to inject the correct resourcetype for calendars that
115
-     * have been shared.
116
-     */
117
-    public function propFindLate(DAV\PropFind $propFind, DAV\INode $node)
118
-    {
119
-        if ($node instanceof ISharedCalendar) {
120
-            $shareAccess = $node->getShareAccess();
121
-            if ($rt = $propFind->get('{DAV:}resourcetype')) {
122
-                switch ($shareAccess) {
123
-                    case \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER:
124
-                        $rt->add('{'.Plugin::NS_CALENDARSERVER.'}shared-owner');
125
-                        break;
126
-                    case \Sabre\DAV\Sharing\Plugin::ACCESS_READ:
127
-                    case \Sabre\DAV\Sharing\Plugin::ACCESS_READWRITE:
128
-                        $rt->add('{'.Plugin::NS_CALENDARSERVER.'}shared');
129
-                        break;
130
-                }
131
-            }
132
-            $propFind->handle('{'.Plugin::NS_CALENDARSERVER.'}allowed-sharing-modes', function () {
133
-                return new Xml\Property\AllowedSharingModes(true, false);
134
-            });
135
-        }
136
-    }
137
-
138
-    /**
139
-     * This method is trigged when a user attempts to update a node's
140
-     * properties.
141
-     *
142
-     * A previous draft of the sharing spec stated that it was possible to use
143
-     * PROPPATCH to remove 'shared-owner' from the resourcetype, thus unsharing
144
-     * the calendar.
145
-     *
146
-     * Even though this is no longer in the current spec, we keep this around
147
-     * because OS X 10.7 may still make use of this feature.
148
-     *
149
-     * @param string $path
150
-     */
151
-    public function propPatch($path, DAV\PropPatch $propPatch)
152
-    {
153
-        $node = $this->server->tree->getNodeForPath($path);
154
-        if (!$node instanceof ISharedCalendar) {
155
-            return;
156
-        }
157
-
158
-        if (\Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER === $node->getShareAccess() || \Sabre\DAV\Sharing\Plugin::ACCESS_NOTSHARED === $node->getShareAccess()) {
159
-            $propPatch->handle('{DAV:}resourcetype', function ($value) use ($node) {
160
-                if ($value->is('{'.Plugin::NS_CALENDARSERVER.'}shared-owner')) {
161
-                    return false;
162
-                }
163
-                $shares = $node->getInvites();
164
-                foreach ($shares as $share) {
165
-                    $share->access = DAV\Sharing\Plugin::ACCESS_NOACCESS;
166
-                }
167
-                $node->updateInvites($shares);
168
-
169
-                return true;
170
-            });
171
-        }
172
-    }
173
-
174
-    /**
175
-     * We intercept this to handle POST requests on calendars.
176
-     *
177
-     * @return bool|null
178
-     */
179
-    public function httpPost(RequestInterface $request, ResponseInterface $response)
180
-    {
181
-        $path = $request->getPath();
182
-
183
-        // Only handling xml
184
-        $contentType = $request->getHeader('Content-Type');
185
-        if (null === $contentType) {
186
-            return;
187
-        }
188
-        if (false === strpos($contentType, 'application/xml') && false === strpos($contentType, 'text/xml')) {
189
-            return;
190
-        }
191
-
192
-        // Making sure the node exists
193
-        try {
194
-            $node = $this->server->tree->getNodeForPath($path);
195
-        } catch (DAV\Exception\NotFound $e) {
196
-            return;
197
-        }
198
-
199
-        $requestBody = $request->getBodyAsString();
200
-
201
-        // If this request handler could not deal with this POST request, it
202
-        // will return 'null' and other plugins get a chance to handle the
203
-        // request.
204
-        //
205
-        // However, we already requested the full body. This is a problem,
206
-        // because a body can only be read once. This is why we preemptively
207
-        // re-populated the request body with the existing data.
208
-        $request->setBody($requestBody);
209
-
210
-        $message = $this->server->xml->parse($requestBody, $request->getUrl(), $documentType);
211
-
212
-        switch ($documentType) {
213
-            // Both the DAV:share-resource and CALENDARSERVER:share requests
214
-            // behave identically.
215
-            case '{'.Plugin::NS_CALENDARSERVER.'}share':
216
-                $sharingPlugin = $this->server->getPlugin('sharing');
217
-                $sharingPlugin->shareResource($path, $message->sharees);
218
-
219
-                $response->setStatus(200);
220
-                // Adding this because sending a response body may cause issues,
221
-                // and I wanted some type of indicator the response was handled.
222
-                $response->setHeader('X-Sabre-Status', 'everything-went-well');
223
-
224
-                // Breaking the event chain
225
-                return false;
226
-
227
-            // The invite-reply document is sent when the user replies to an
228
-            // invitation of a calendar share.
229
-            case '{'.Plugin::NS_CALENDARSERVER.'}invite-reply':
230
-                // This only works on the calendar-home-root node.
231
-                if (!$node instanceof CalendarHome) {
232
-                    return;
233
-                }
234
-                $this->server->transactionType = 'post-invite-reply';
235
-
236
-                // Getting ACL info
237
-                $acl = $this->server->getPlugin('acl');
238
-
239
-                // If there's no ACL support, we allow everything
240
-                if ($acl) {
241
-                    $acl->checkPrivileges($path, '{DAV:}write');
242
-                }
243
-
244
-                $url = $node->shareReply(
245
-                    $message->href,
246
-                    $message->status,
247
-                    $message->calendarUri,
248
-                    $message->inReplyTo,
249
-                    $message->summary
250
-                );
251
-
252
-                $response->setStatus(200);
253
-                // Adding this because sending a response body may cause issues,
254
-                // and I wanted some type of indicator the response was handled.
255
-                $response->setHeader('X-Sabre-Status', 'everything-went-well');
256
-
257
-                if ($url) {
258
-                    $writer = $this->server->xml->getWriter();
259
-                    $writer->contextUri = $request->getUrl();
260
-                    $writer->openMemory();
261
-                    $writer->startDocument();
262
-                    $writer->startElement('{'.Plugin::NS_CALENDARSERVER.'}shared-as');
263
-                    $writer->write(new LocalHref($url));
264
-                    $writer->endElement();
265
-                    $response->setHeader('Content-Type', 'application/xml');
266
-                    $response->setBody($writer->outputMemory());
267
-                }
268
-
269
-                // Breaking the event chain
270
-                return false;
271
-
272
-            case '{'.Plugin::NS_CALENDARSERVER.'}publish-calendar':
273
-                // We can only deal with IShareableCalendar objects
274
-                if (!$node instanceof ISharedCalendar) {
275
-                    return;
276
-                }
277
-                $this->server->transactionType = 'post-publish-calendar';
278
-
279
-                // Getting ACL info
280
-                $acl = $this->server->getPlugin('acl');
281
-
282
-                // If there's no ACL support, we allow everything
283
-                if ($acl) {
284
-                    $acl->checkPrivileges($path, '{DAV:}share');
285
-                }
286
-
287
-                $node->setPublishStatus(true);
288
-
289
-                // iCloud sends back the 202, so we will too.
290
-                $response->setStatus(202);
291
-
292
-                // Adding this because sending a response body may cause issues,
293
-                // and I wanted some type of indicator the response was handled.
294
-                $response->setHeader('X-Sabre-Status', 'everything-went-well');
295
-
296
-                // Breaking the event chain
297
-                return false;
298
-
299
-            case '{'.Plugin::NS_CALENDARSERVER.'}unpublish-calendar':
300
-                // We can only deal with IShareableCalendar objects
301
-                if (!$node instanceof ISharedCalendar) {
302
-                    return;
303
-                }
304
-                $this->server->transactionType = 'post-unpublish-calendar';
305
-
306
-                // Getting ACL info
307
-                $acl = $this->server->getPlugin('acl');
308
-
309
-                // If there's no ACL support, we allow everything
310
-                if ($acl) {
311
-                    $acl->checkPrivileges($path, '{DAV:}share');
312
-                }
313
-
314
-                $node->setPublishStatus(false);
315
-
316
-                $response->setStatus(200);
317
-
318
-                // Adding this because sending a response body may cause issues,
319
-                // and I wanted some type of indicator the response was handled.
320
-                $response->setHeader('X-Sabre-Status', 'everything-went-well');
321
-
322
-                // Breaking the event chain
323
-                return false;
324
-        }
325
-    }
326
-
327
-    /**
328
-     * Returns a bunch of meta-data about the plugin.
329
-     *
330
-     * Providing this information is optional, and is mainly displayed by the
331
-     * Browser plugin.
332
-     *
333
-     * The description key in the returned array may contain html and will not
334
-     * be sanitized.
335
-     *
336
-     * @return array
337
-     */
338
-    public function getPluginInfo()
339
-    {
340
-        return [
341
-            'name' => $this->getPluginName(),
342
-            'description' => 'Adds support for caldav-sharing.',
343
-            'link' => 'http://sabre.io/dav/caldav-sharing/',
344
-        ];
345
-    }
30
+	/**
31
+	 * Reference to SabreDAV server object.
32
+	 *
33
+	 * @var DAV\Server
34
+	 */
35
+	protected $server;
36
+
37
+	/**
38
+	 * This method should return a list of server-features.
39
+	 *
40
+	 * This is for example 'versioning' and is added to the DAV: header
41
+	 * in an OPTIONS response.
42
+	 *
43
+	 * @return array
44
+	 */
45
+	public function getFeatures()
46
+	{
47
+		return ['calendarserver-sharing'];
48
+	}
49
+
50
+	/**
51
+	 * Returns a plugin name.
52
+	 *
53
+	 * Using this name other plugins will be able to access other plugins
54
+	 * using Sabre\DAV\Server::getPlugin
55
+	 *
56
+	 * @return string
57
+	 */
58
+	public function getPluginName()
59
+	{
60
+		return 'caldav-sharing';
61
+	}
62
+
63
+	/**
64
+	 * This initializes the plugin.
65
+	 *
66
+	 * This function is called by Sabre\DAV\Server, after
67
+	 * addPlugin is called.
68
+	 *
69
+	 * This method should set up the required event subscriptions.
70
+	 */
71
+	public function initialize(DAV\Server $server)
72
+	{
73
+		$this->server = $server;
74
+
75
+		if (is_null($this->server->getPlugin('sharing'))) {
76
+			throw new \LogicException('The generic "sharing" plugin must be loaded before the caldav sharing plugin. Call $server->addPlugin(new \Sabre\DAV\Sharing\Plugin()); before this one.');
77
+		}
78
+
79
+		array_push(
80
+			$this->server->protectedProperties,
81
+			'{'.Plugin::NS_CALENDARSERVER.'}invite',
82
+			'{'.Plugin::NS_CALENDARSERVER.'}allowed-sharing-modes',
83
+			'{'.Plugin::NS_CALENDARSERVER.'}shared-url'
84
+		);
85
+
86
+		$this->server->xml->elementMap['{'.Plugin::NS_CALENDARSERVER.'}share'] = 'Sabre\\CalDAV\\Xml\\Request\\Share';
87
+		$this->server->xml->elementMap['{'.Plugin::NS_CALENDARSERVER.'}invite-reply'] = 'Sabre\\CalDAV\\Xml\\Request\\InviteReply';
88
+
89
+		$this->server->on('propFind', [$this, 'propFindEarly']);
90
+		$this->server->on('propFind', [$this, 'propFindLate'], 150);
91
+		$this->server->on('propPatch', [$this, 'propPatch'], 40);
92
+		$this->server->on('method:POST', [$this, 'httpPost']);
93
+	}
94
+
95
+	/**
96
+	 * This event is triggered when properties are requested for a certain
97
+	 * node.
98
+	 *
99
+	 * This allows us to inject any properties early.
100
+	 */
101
+	public function propFindEarly(DAV\PropFind $propFind, DAV\INode $node)
102
+	{
103
+		if ($node instanceof ISharedCalendar) {
104
+			$propFind->handle('{'.Plugin::NS_CALENDARSERVER.'}invite', function () use ($node) {
105
+				return new Xml\Property\Invite(
106
+					$node->getInvites()
107
+				);
108
+			});
109
+		}
110
+	}
111
+
112
+	/**
113
+	 * This method is triggered *after* all properties have been retrieved.
114
+	 * This allows us to inject the correct resourcetype for calendars that
115
+	 * have been shared.
116
+	 */
117
+	public function propFindLate(DAV\PropFind $propFind, DAV\INode $node)
118
+	{
119
+		if ($node instanceof ISharedCalendar) {
120
+			$shareAccess = $node->getShareAccess();
121
+			if ($rt = $propFind->get('{DAV:}resourcetype')) {
122
+				switch ($shareAccess) {
123
+					case \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER:
124
+						$rt->add('{'.Plugin::NS_CALENDARSERVER.'}shared-owner');
125
+						break;
126
+					case \Sabre\DAV\Sharing\Plugin::ACCESS_READ:
127
+					case \Sabre\DAV\Sharing\Plugin::ACCESS_READWRITE:
128
+						$rt->add('{'.Plugin::NS_CALENDARSERVER.'}shared');
129
+						break;
130
+				}
131
+			}
132
+			$propFind->handle('{'.Plugin::NS_CALENDARSERVER.'}allowed-sharing-modes', function () {
133
+				return new Xml\Property\AllowedSharingModes(true, false);
134
+			});
135
+		}
136
+	}
137
+
138
+	/**
139
+	 * This method is trigged when a user attempts to update a node's
140
+	 * properties.
141
+	 *
142
+	 * A previous draft of the sharing spec stated that it was possible to use
143
+	 * PROPPATCH to remove 'shared-owner' from the resourcetype, thus unsharing
144
+	 * the calendar.
145
+	 *
146
+	 * Even though this is no longer in the current spec, we keep this around
147
+	 * because OS X 10.7 may still make use of this feature.
148
+	 *
149
+	 * @param string $path
150
+	 */
151
+	public function propPatch($path, DAV\PropPatch $propPatch)
152
+	{
153
+		$node = $this->server->tree->getNodeForPath($path);
154
+		if (!$node instanceof ISharedCalendar) {
155
+			return;
156
+		}
157
+
158
+		if (\Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER === $node->getShareAccess() || \Sabre\DAV\Sharing\Plugin::ACCESS_NOTSHARED === $node->getShareAccess()) {
159
+			$propPatch->handle('{DAV:}resourcetype', function ($value) use ($node) {
160
+				if ($value->is('{'.Plugin::NS_CALENDARSERVER.'}shared-owner')) {
161
+					return false;
162
+				}
163
+				$shares = $node->getInvites();
164
+				foreach ($shares as $share) {
165
+					$share->access = DAV\Sharing\Plugin::ACCESS_NOACCESS;
166
+				}
167
+				$node->updateInvites($shares);
168
+
169
+				return true;
170
+			});
171
+		}
172
+	}
173
+
174
+	/**
175
+	 * We intercept this to handle POST requests on calendars.
176
+	 *
177
+	 * @return bool|null
178
+	 */
179
+	public function httpPost(RequestInterface $request, ResponseInterface $response)
180
+	{
181
+		$path = $request->getPath();
182
+
183
+		// Only handling xml
184
+		$contentType = $request->getHeader('Content-Type');
185
+		if (null === $contentType) {
186
+			return;
187
+		}
188
+		if (false === strpos($contentType, 'application/xml') && false === strpos($contentType, 'text/xml')) {
189
+			return;
190
+		}
191
+
192
+		// Making sure the node exists
193
+		try {
194
+			$node = $this->server->tree->getNodeForPath($path);
195
+		} catch (DAV\Exception\NotFound $e) {
196
+			return;
197
+		}
198
+
199
+		$requestBody = $request->getBodyAsString();
200
+
201
+		// If this request handler could not deal with this POST request, it
202
+		// will return 'null' and other plugins get a chance to handle the
203
+		// request.
204
+		//
205
+		// However, we already requested the full body. This is a problem,
206
+		// because a body can only be read once. This is why we preemptively
207
+		// re-populated the request body with the existing data.
208
+		$request->setBody($requestBody);
209
+
210
+		$message = $this->server->xml->parse($requestBody, $request->getUrl(), $documentType);
211
+
212
+		switch ($documentType) {
213
+			// Both the DAV:share-resource and CALENDARSERVER:share requests
214
+			// behave identically.
215
+			case '{'.Plugin::NS_CALENDARSERVER.'}share':
216
+				$sharingPlugin = $this->server->getPlugin('sharing');
217
+				$sharingPlugin->shareResource($path, $message->sharees);
218
+
219
+				$response->setStatus(200);
220
+				// Adding this because sending a response body may cause issues,
221
+				// and I wanted some type of indicator the response was handled.
222
+				$response->setHeader('X-Sabre-Status', 'everything-went-well');
223
+
224
+				// Breaking the event chain
225
+				return false;
226
+
227
+			// The invite-reply document is sent when the user replies to an
228
+			// invitation of a calendar share.
229
+			case '{'.Plugin::NS_CALENDARSERVER.'}invite-reply':
230
+				// This only works on the calendar-home-root node.
231
+				if (!$node instanceof CalendarHome) {
232
+					return;
233
+				}
234
+				$this->server->transactionType = 'post-invite-reply';
235
+
236
+				// Getting ACL info
237
+				$acl = $this->server->getPlugin('acl');
238
+
239
+				// If there's no ACL support, we allow everything
240
+				if ($acl) {
241
+					$acl->checkPrivileges($path, '{DAV:}write');
242
+				}
243
+
244
+				$url = $node->shareReply(
245
+					$message->href,
246
+					$message->status,
247
+					$message->calendarUri,
248
+					$message->inReplyTo,
249
+					$message->summary
250
+				);
251
+
252
+				$response->setStatus(200);
253
+				// Adding this because sending a response body may cause issues,
254
+				// and I wanted some type of indicator the response was handled.
255
+				$response->setHeader('X-Sabre-Status', 'everything-went-well');
256
+
257
+				if ($url) {
258
+					$writer = $this->server->xml->getWriter();
259
+					$writer->contextUri = $request->getUrl();
260
+					$writer->openMemory();
261
+					$writer->startDocument();
262
+					$writer->startElement('{'.Plugin::NS_CALENDARSERVER.'}shared-as');
263
+					$writer->write(new LocalHref($url));
264
+					$writer->endElement();
265
+					$response->setHeader('Content-Type', 'application/xml');
266
+					$response->setBody($writer->outputMemory());
267
+				}
268
+
269
+				// Breaking the event chain
270
+				return false;
271
+
272
+			case '{'.Plugin::NS_CALENDARSERVER.'}publish-calendar':
273
+				// We can only deal with IShareableCalendar objects
274
+				if (!$node instanceof ISharedCalendar) {
275
+					return;
276
+				}
277
+				$this->server->transactionType = 'post-publish-calendar';
278
+
279
+				// Getting ACL info
280
+				$acl = $this->server->getPlugin('acl');
281
+
282
+				// If there's no ACL support, we allow everything
283
+				if ($acl) {
284
+					$acl->checkPrivileges($path, '{DAV:}share');
285
+				}
286
+
287
+				$node->setPublishStatus(true);
288
+
289
+				// iCloud sends back the 202, so we will too.
290
+				$response->setStatus(202);
291
+
292
+				// Adding this because sending a response body may cause issues,
293
+				// and I wanted some type of indicator the response was handled.
294
+				$response->setHeader('X-Sabre-Status', 'everything-went-well');
295
+
296
+				// Breaking the event chain
297
+				return false;
298
+
299
+			case '{'.Plugin::NS_CALENDARSERVER.'}unpublish-calendar':
300
+				// We can only deal with IShareableCalendar objects
301
+				if (!$node instanceof ISharedCalendar) {
302
+					return;
303
+				}
304
+				$this->server->transactionType = 'post-unpublish-calendar';
305
+
306
+				// Getting ACL info
307
+				$acl = $this->server->getPlugin('acl');
308
+
309
+				// If there's no ACL support, we allow everything
310
+				if ($acl) {
311
+					$acl->checkPrivileges($path, '{DAV:}share');
312
+				}
313
+
314
+				$node->setPublishStatus(false);
315
+
316
+				$response->setStatus(200);
317
+
318
+				// Adding this because sending a response body may cause issues,
319
+				// and I wanted some type of indicator the response was handled.
320
+				$response->setHeader('X-Sabre-Status', 'everything-went-well');
321
+
322
+				// Breaking the event chain
323
+				return false;
324
+		}
325
+	}
326
+
327
+	/**
328
+	 * Returns a bunch of meta-data about the plugin.
329
+	 *
330
+	 * Providing this information is optional, and is mainly displayed by the
331
+	 * Browser plugin.
332
+	 *
333
+	 * The description key in the returned array may contain html and will not
334
+	 * be sanitized.
335
+	 *
336
+	 * @return array
337
+	 */
338
+	public function getPluginInfo()
339
+	{
340
+		return [
341
+			'name' => $this->getPluginName(),
342
+			'description' => 'Adds support for caldav-sharing.',
343
+			'link' => 'http://sabre.io/dav/caldav-sharing/',
344
+		];
345
+	}
346 346
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/CalDAV/ICalendarObjectContainer.php 1 patch
Indentation   +17 added lines, -17 removed lines patch added patch discarded remove patch
@@ -19,21 +19,21 @@
 block discarded – undo
19 19
  */
20 20
 interface ICalendarObjectContainer extends \Sabre\DAV\ICollection
21 21
 {
22
-    /**
23
-     * Performs a calendar-query on the contents of this calendar.
24
-     *
25
-     * The calendar-query is defined in RFC4791 : CalDAV. Using the
26
-     * calendar-query it is possible for a client to request a specific set of
27
-     * object, based on contents of iCalendar properties, date-ranges and
28
-     * iCalendar component types (VTODO, VEVENT).
29
-     *
30
-     * This method should just return a list of (relative) urls that match this
31
-     * query.
32
-     *
33
-     * The list of filters are specified as an array. The exact array is
34
-     * documented by \Sabre\CalDAV\CalendarQueryParser.
35
-     *
36
-     * @return array
37
-     */
38
-    public function calendarQuery(array $filters);
22
+	/**
23
+	 * Performs a calendar-query on the contents of this calendar.
24
+	 *
25
+	 * The calendar-query is defined in RFC4791 : CalDAV. Using the
26
+	 * calendar-query it is possible for a client to request a specific set of
27
+	 * object, based on contents of iCalendar properties, date-ranges and
28
+	 * iCalendar component types (VTODO, VEVENT).
29
+	 *
30
+	 * This method should just return a list of (relative) urls that match this
31
+	 * query.
32
+	 *
33
+	 * The list of filters are specified as an array. The exact array is
34
+	 * documented by \Sabre\CalDAV\CalendarQueryParser.
35
+	 *
36
+	 * @return array
37
+	 */
38
+	public function calendarQuery(array $filters);
39 39
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/CalDAV/CalendarObject.php 1 patch
Indentation   +207 added lines, -207 removed lines patch added patch discarded remove patch
@@ -13,211 +13,211 @@
 block discarded – undo
13 13
  */
14 14
 class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\DAVACL\IACL
15 15
 {
16
-    use \Sabre\DAVACL\ACLTrait;
17
-
18
-    /**
19
-     * Sabre\CalDAV\Backend\BackendInterface.
20
-     *
21
-     * @var Backend\AbstractBackend
22
-     */
23
-    protected $caldavBackend;
24
-
25
-    /**
26
-     * Array with information about this CalendarObject.
27
-     *
28
-     * @var array
29
-     */
30
-    protected $objectData;
31
-
32
-    /**
33
-     * Array with information about the containing calendar.
34
-     *
35
-     * @var array
36
-     */
37
-    protected $calendarInfo;
38
-
39
-    /**
40
-     * Constructor.
41
-     *
42
-     * The following properties may be passed within $objectData:
43
-     *
44
-     *   * calendarid - This must refer to a calendarid from a caldavBackend
45
-     *   * uri - A unique uri. Only the 'basename' must be passed.
46
-     *   * calendardata (optional) - The iCalendar data
47
-     *   * etag - (optional) The etag for this object, MUST be encloded with
48
-     *            double-quotes.
49
-     *   * size - (optional) The size of the data in bytes.
50
-     *   * lastmodified - (optional) format as a unix timestamp.
51
-     *   * acl - (optional) Use this to override the default ACL for the node.
52
-     */
53
-    public function __construct(Backend\BackendInterface $caldavBackend, array $calendarInfo, array $objectData)
54
-    {
55
-        $this->caldavBackend = $caldavBackend;
56
-
57
-        if (!isset($objectData['uri'])) {
58
-            throw new \InvalidArgumentException('The objectData argument must contain an \'uri\' property');
59
-        }
60
-
61
-        $this->calendarInfo = $calendarInfo;
62
-        $this->objectData = $objectData;
63
-    }
64
-
65
-    /**
66
-     * Returns the uri for this object.
67
-     *
68
-     * @return string
69
-     */
70
-    public function getName()
71
-    {
72
-        return $this->objectData['uri'];
73
-    }
74
-
75
-    /**
76
-     * Returns the ICalendar-formatted object.
77
-     *
78
-     * @return string
79
-     */
80
-    public function get()
81
-    {
82
-        // Pre-populating the 'calendardata' is optional, if we don't have it
83
-        // already we fetch it from the backend.
84
-        if (!isset($this->objectData['calendardata'])) {
85
-            $this->objectData = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $this->objectData['uri']);
86
-        }
87
-
88
-        return $this->objectData['calendardata'];
89
-    }
90
-
91
-    /**
92
-     * Updates the ICalendar-formatted object.
93
-     *
94
-     * @param string|resource $calendarData
95
-     *
96
-     * @return string
97
-     */
98
-    public function put($calendarData)
99
-    {
100
-        if (is_resource($calendarData)) {
101
-            $calendarData = stream_get_contents($calendarData);
102
-        }
103
-        $etag = $this->caldavBackend->updateCalendarObject($this->calendarInfo['id'], $this->objectData['uri'], $calendarData);
104
-        $this->objectData['calendardata'] = $calendarData;
105
-        $this->objectData['etag'] = $etag;
106
-
107
-        return $etag;
108
-    }
109
-
110
-    /**
111
-     * Deletes the calendar object.
112
-     */
113
-    public function delete()
114
-    {
115
-        $this->caldavBackend->deleteCalendarObject($this->calendarInfo['id'], $this->objectData['uri']);
116
-    }
117
-
118
-    /**
119
-     * Returns the mime content-type.
120
-     *
121
-     * @return string
122
-     */
123
-    public function getContentType()
124
-    {
125
-        $mime = 'text/calendar; charset=utf-8';
126
-        if (isset($this->objectData['component']) && $this->objectData['component']) {
127
-            $mime .= '; component='.$this->objectData['component'];
128
-        }
129
-
130
-        return $mime;
131
-    }
132
-
133
-    /**
134
-     * Returns an ETag for this object.
135
-     *
136
-     * The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
137
-     *
138
-     * @return string
139
-     */
140
-    public function getETag()
141
-    {
142
-        if (isset($this->objectData['etag'])) {
143
-            return $this->objectData['etag'];
144
-        } else {
145
-            return '"'.md5($this->get()).'"';
146
-        }
147
-    }
148
-
149
-    /**
150
-     * Returns the last modification date as a unix timestamp.
151
-     *
152
-     * @return int
153
-     */
154
-    public function getLastModified()
155
-    {
156
-        return $this->objectData['lastmodified'];
157
-    }
158
-
159
-    /**
160
-     * Returns the size of this object in bytes.
161
-     *
162
-     * @return int
163
-     */
164
-    public function getSize()
165
-    {
166
-        if (array_key_exists('size', $this->objectData)) {
167
-            return $this->objectData['size'];
168
-        } else {
169
-            return strlen($this->get());
170
-        }
171
-    }
172
-
173
-    /**
174
-     * Returns the owner principal.
175
-     *
176
-     * This must be a url to a principal, or null if there's no owner
177
-     *
178
-     * @return string|null
179
-     */
180
-    public function getOwner()
181
-    {
182
-        return $this->calendarInfo['principaluri'];
183
-    }
184
-
185
-    /**
186
-     * Returns a list of ACE's for this node.
187
-     *
188
-     * Each ACE has the following properties:
189
-     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
190
-     *     currently the only supported privileges
191
-     *   * 'principal', a url to the principal who owns the node
192
-     *   * 'protected' (optional), indicating that this ACE is not allowed to
193
-     *      be updated.
194
-     *
195
-     * @return array
196
-     */
197
-    public function getACL()
198
-    {
199
-        // An alternative acl may be specified in the object data.
200
-        if (isset($this->objectData['acl'])) {
201
-            return $this->objectData['acl'];
202
-        }
203
-
204
-        // The default ACL
205
-        return [
206
-            [
207
-                'privilege' => '{DAV:}all',
208
-                'principal' => $this->calendarInfo['principaluri'],
209
-                'protected' => true,
210
-            ],
211
-            [
212
-                'privilege' => '{DAV:}all',
213
-                'principal' => $this->calendarInfo['principaluri'].'/calendar-proxy-write',
214
-                'protected' => true,
215
-            ],
216
-            [
217
-                'privilege' => '{DAV:}read',
218
-                'principal' => $this->calendarInfo['principaluri'].'/calendar-proxy-read',
219
-                'protected' => true,
220
-            ],
221
-        ];
222
-    }
16
+	use \Sabre\DAVACL\ACLTrait;
17
+
18
+	/**
19
+	 * Sabre\CalDAV\Backend\BackendInterface.
20
+	 *
21
+	 * @var Backend\AbstractBackend
22
+	 */
23
+	protected $caldavBackend;
24
+
25
+	/**
26
+	 * Array with information about this CalendarObject.
27
+	 *
28
+	 * @var array
29
+	 */
30
+	protected $objectData;
31
+
32
+	/**
33
+	 * Array with information about the containing calendar.
34
+	 *
35
+	 * @var array
36
+	 */
37
+	protected $calendarInfo;
38
+
39
+	/**
40
+	 * Constructor.
41
+	 *
42
+	 * The following properties may be passed within $objectData:
43
+	 *
44
+	 *   * calendarid - This must refer to a calendarid from a caldavBackend
45
+	 *   * uri - A unique uri. Only the 'basename' must be passed.
46
+	 *   * calendardata (optional) - The iCalendar data
47
+	 *   * etag - (optional) The etag for this object, MUST be encloded with
48
+	 *            double-quotes.
49
+	 *   * size - (optional) The size of the data in bytes.
50
+	 *   * lastmodified - (optional) format as a unix timestamp.
51
+	 *   * acl - (optional) Use this to override the default ACL for the node.
52
+	 */
53
+	public function __construct(Backend\BackendInterface $caldavBackend, array $calendarInfo, array $objectData)
54
+	{
55
+		$this->caldavBackend = $caldavBackend;
56
+
57
+		if (!isset($objectData['uri'])) {
58
+			throw new \InvalidArgumentException('The objectData argument must contain an \'uri\' property');
59
+		}
60
+
61
+		$this->calendarInfo = $calendarInfo;
62
+		$this->objectData = $objectData;
63
+	}
64
+
65
+	/**
66
+	 * Returns the uri for this object.
67
+	 *
68
+	 * @return string
69
+	 */
70
+	public function getName()
71
+	{
72
+		return $this->objectData['uri'];
73
+	}
74
+
75
+	/**
76
+	 * Returns the ICalendar-formatted object.
77
+	 *
78
+	 * @return string
79
+	 */
80
+	public function get()
81
+	{
82
+		// Pre-populating the 'calendardata' is optional, if we don't have it
83
+		// already we fetch it from the backend.
84
+		if (!isset($this->objectData['calendardata'])) {
85
+			$this->objectData = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $this->objectData['uri']);
86
+		}
87
+
88
+		return $this->objectData['calendardata'];
89
+	}
90
+
91
+	/**
92
+	 * Updates the ICalendar-formatted object.
93
+	 *
94
+	 * @param string|resource $calendarData
95
+	 *
96
+	 * @return string
97
+	 */
98
+	public function put($calendarData)
99
+	{
100
+		if (is_resource($calendarData)) {
101
+			$calendarData = stream_get_contents($calendarData);
102
+		}
103
+		$etag = $this->caldavBackend->updateCalendarObject($this->calendarInfo['id'], $this->objectData['uri'], $calendarData);
104
+		$this->objectData['calendardata'] = $calendarData;
105
+		$this->objectData['etag'] = $etag;
106
+
107
+		return $etag;
108
+	}
109
+
110
+	/**
111
+	 * Deletes the calendar object.
112
+	 */
113
+	public function delete()
114
+	{
115
+		$this->caldavBackend->deleteCalendarObject($this->calendarInfo['id'], $this->objectData['uri']);
116
+	}
117
+
118
+	/**
119
+	 * Returns the mime content-type.
120
+	 *
121
+	 * @return string
122
+	 */
123
+	public function getContentType()
124
+	{
125
+		$mime = 'text/calendar; charset=utf-8';
126
+		if (isset($this->objectData['component']) && $this->objectData['component']) {
127
+			$mime .= '; component='.$this->objectData['component'];
128
+		}
129
+
130
+		return $mime;
131
+	}
132
+
133
+	/**
134
+	 * Returns an ETag for this object.
135
+	 *
136
+	 * The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
137
+	 *
138
+	 * @return string
139
+	 */
140
+	public function getETag()
141
+	{
142
+		if (isset($this->objectData['etag'])) {
143
+			return $this->objectData['etag'];
144
+		} else {
145
+			return '"'.md5($this->get()).'"';
146
+		}
147
+	}
148
+
149
+	/**
150
+	 * Returns the last modification date as a unix timestamp.
151
+	 *
152
+	 * @return int
153
+	 */
154
+	public function getLastModified()
155
+	{
156
+		return $this->objectData['lastmodified'];
157
+	}
158
+
159
+	/**
160
+	 * Returns the size of this object in bytes.
161
+	 *
162
+	 * @return int
163
+	 */
164
+	public function getSize()
165
+	{
166
+		if (array_key_exists('size', $this->objectData)) {
167
+			return $this->objectData['size'];
168
+		} else {
169
+			return strlen($this->get());
170
+		}
171
+	}
172
+
173
+	/**
174
+	 * Returns the owner principal.
175
+	 *
176
+	 * This must be a url to a principal, or null if there's no owner
177
+	 *
178
+	 * @return string|null
179
+	 */
180
+	public function getOwner()
181
+	{
182
+		return $this->calendarInfo['principaluri'];
183
+	}
184
+
185
+	/**
186
+	 * Returns a list of ACE's for this node.
187
+	 *
188
+	 * Each ACE has the following properties:
189
+	 *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
190
+	 *     currently the only supported privileges
191
+	 *   * 'principal', a url to the principal who owns the node
192
+	 *   * 'protected' (optional), indicating that this ACE is not allowed to
193
+	 *      be updated.
194
+	 *
195
+	 * @return array
196
+	 */
197
+	public function getACL()
198
+	{
199
+		// An alternative acl may be specified in the object data.
200
+		if (isset($this->objectData['acl'])) {
201
+			return $this->objectData['acl'];
202
+		}
203
+
204
+		// The default ACL
205
+		return [
206
+			[
207
+				'privilege' => '{DAV:}all',
208
+				'principal' => $this->calendarInfo['principaluri'],
209
+				'protected' => true,
210
+			],
211
+			[
212
+				'privilege' => '{DAV:}all',
213
+				'principal' => $this->calendarInfo['principaluri'].'/calendar-proxy-write',
214
+				'protected' => true,
215
+			],
216
+			[
217
+				'privilege' => '{DAV:}read',
218
+				'principal' => $this->calendarInfo['principaluri'].'/calendar-proxy-read',
219
+				'protected' => true,
220
+			],
221
+		];
222
+	}
223 223
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/CalDAV/ISharedCalendar.php 1 patch
Indentation   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -15,13 +15,13 @@
 block discarded – undo
15 15
  */
16 16
 interface ISharedCalendar extends ISharedNode
17 17
 {
18
-    /**
19
-     * Marks this calendar as published.
20
-     *
21
-     * Publishing a calendar should automatically create a read-only, public,
22
-     * subscribable calendar.
23
-     *
24
-     * @param bool $value
25
-     */
26
-    public function setPublishStatus($value);
18
+	/**
19
+	 * Marks this calendar as published.
20
+	 *
21
+	 * Publishing a calendar should automatically create a read-only, public,
22
+	 * subscribable calendar.
23
+	 *
24
+	 * @param bool $value
25
+	 */
26
+	public function setPublishStatus($value);
27 27
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/CalDAV/Schedule/Outbox.php 1 patch
Indentation   +89 added lines, -89 removed lines patch added patch discarded remove patch
@@ -21,99 +21,99 @@
 block discarded – undo
21 21
  */
22 22
 class Outbox extends DAV\Collection implements IOutbox
23 23
 {
24
-    use DAVACL\ACLTrait;
24
+	use DAVACL\ACLTrait;
25 25
 
26
-    /**
27
-     * The principal Uri.
28
-     *
29
-     * @var string
30
-     */
31
-    protected $principalUri;
26
+	/**
27
+	 * The principal Uri.
28
+	 *
29
+	 * @var string
30
+	 */
31
+	protected $principalUri;
32 32
 
33
-    /**
34
-     * Constructor.
35
-     *
36
-     * @param string $principalUri
37
-     */
38
-    public function __construct($principalUri)
39
-    {
40
-        $this->principalUri = $principalUri;
41
-    }
33
+	/**
34
+	 * Constructor.
35
+	 *
36
+	 * @param string $principalUri
37
+	 */
38
+	public function __construct($principalUri)
39
+	{
40
+		$this->principalUri = $principalUri;
41
+	}
42 42
 
43
-    /**
44
-     * Returns the name of the node.
45
-     *
46
-     * This is used to generate the url.
47
-     *
48
-     * @return string
49
-     */
50
-    public function getName()
51
-    {
52
-        return 'outbox';
53
-    }
43
+	/**
44
+	 * Returns the name of the node.
45
+	 *
46
+	 * This is used to generate the url.
47
+	 *
48
+	 * @return string
49
+	 */
50
+	public function getName()
51
+	{
52
+		return 'outbox';
53
+	}
54 54
 
55
-    /**
56
-     * Returns an array with all the child nodes.
57
-     *
58
-     * @return \Sabre\DAV\INode[]
59
-     */
60
-    public function getChildren()
61
-    {
62
-        return [];
63
-    }
55
+	/**
56
+	 * Returns an array with all the child nodes.
57
+	 *
58
+	 * @return \Sabre\DAV\INode[]
59
+	 */
60
+	public function getChildren()
61
+	{
62
+		return [];
63
+	}
64 64
 
65
-    /**
66
-     * Returns the owner principal.
67
-     *
68
-     * This must be a url to a principal, or null if there's no owner
69
-     *
70
-     * @return string|null
71
-     */
72
-    public function getOwner()
73
-    {
74
-        return $this->principalUri;
75
-    }
65
+	/**
66
+	 * Returns the owner principal.
67
+	 *
68
+	 * This must be a url to a principal, or null if there's no owner
69
+	 *
70
+	 * @return string|null
71
+	 */
72
+	public function getOwner()
73
+	{
74
+		return $this->principalUri;
75
+	}
76 76
 
77
-    /**
78
-     * Returns a list of ACE's for this node.
79
-     *
80
-     * Each ACE has the following properties:
81
-     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
82
-     *     currently the only supported privileges
83
-     *   * 'principal', a url to the principal who owns the node
84
-     *   * 'protected' (optional), indicating that this ACE is not allowed to
85
-     *      be updated.
86
-     *
87
-     * @return array
88
-     */
89
-    public function getACL()
90
-    {
91
-        return [
92
-            [
93
-                'privilege' => '{'.CalDAV\Plugin::NS_CALDAV.'}schedule-send',
94
-                'principal' => $this->getOwner(),
95
-                'protected' => true,
96
-            ],
97
-            [
98
-                'privilege' => '{DAV:}read',
99
-                'principal' => $this->getOwner(),
100
-                'protected' => true,
101
-            ],
102
-            [
103
-                'privilege' => '{'.CalDAV\Plugin::NS_CALDAV.'}schedule-send',
104
-                'principal' => $this->getOwner().'/calendar-proxy-write',
105
-                'protected' => true,
106
-            ],
107
-            [
108
-                'privilege' => '{DAV:}read',
109
-                'principal' => $this->getOwner().'/calendar-proxy-read',
110
-                'protected' => true,
111
-            ],
112
-            [
113
-                'privilege' => '{DAV:}read',
114
-                'principal' => $this->getOwner().'/calendar-proxy-write',
115
-                'protected' => true,
116
-            ],
117
-        ];
118
-    }
77
+	/**
78
+	 * Returns a list of ACE's for this node.
79
+	 *
80
+	 * Each ACE has the following properties:
81
+	 *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
82
+	 *     currently the only supported privileges
83
+	 *   * 'principal', a url to the principal who owns the node
84
+	 *   * 'protected' (optional), indicating that this ACE is not allowed to
85
+	 *      be updated.
86
+	 *
87
+	 * @return array
88
+	 */
89
+	public function getACL()
90
+	{
91
+		return [
92
+			[
93
+				'privilege' => '{'.CalDAV\Plugin::NS_CALDAV.'}schedule-send',
94
+				'principal' => $this->getOwner(),
95
+				'protected' => true,
96
+			],
97
+			[
98
+				'privilege' => '{DAV:}read',
99
+				'principal' => $this->getOwner(),
100
+				'protected' => true,
101
+			],
102
+			[
103
+				'privilege' => '{'.CalDAV\Plugin::NS_CALDAV.'}schedule-send',
104
+				'principal' => $this->getOwner().'/calendar-proxy-write',
105
+				'protected' => true,
106
+			],
107
+			[
108
+				'privilege' => '{DAV:}read',
109
+				'principal' => $this->getOwner().'/calendar-proxy-read',
110
+				'protected' => true,
111
+			],
112
+			[
113
+				'privilege' => '{DAV:}read',
114
+				'principal' => $this->getOwner().'/calendar-proxy-write',
115
+				'protected' => true,
116
+			],
117
+		];
118
+	}
119 119
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/CalDAV/Schedule/Inbox.php 1 patch
Indentation   +176 added lines, -176 removed lines patch added patch discarded remove patch
@@ -19,180 +19,180 @@
 block discarded – undo
19 19
  */
20 20
 class Inbox extends DAV\Collection implements IInbox
21 21
 {
22
-    use DAVACL\ACLTrait;
23
-
24
-    /**
25
-     * CalDAV backend.
26
-     *
27
-     * @var Backend\BackendInterface
28
-     */
29
-    protected $caldavBackend;
30
-
31
-    /**
32
-     * The principal Uri.
33
-     *
34
-     * @var string
35
-     */
36
-    protected $principalUri;
37
-
38
-    /**
39
-     * Constructor.
40
-     *
41
-     * @param string $principalUri
42
-     */
43
-    public function __construct(Backend\SchedulingSupport $caldavBackend, $principalUri)
44
-    {
45
-        $this->caldavBackend = $caldavBackend;
46
-        $this->principalUri = $principalUri;
47
-    }
48
-
49
-    /**
50
-     * Returns the name of the node.
51
-     *
52
-     * This is used to generate the url.
53
-     *
54
-     * @return string
55
-     */
56
-    public function getName()
57
-    {
58
-        return 'inbox';
59
-    }
60
-
61
-    /**
62
-     * Returns an array with all the child nodes.
63
-     *
64
-     * @return \Sabre\DAV\INode[]
65
-     */
66
-    public function getChildren()
67
-    {
68
-        $objs = $this->caldavBackend->getSchedulingObjects($this->principalUri);
69
-        $children = [];
70
-        foreach ($objs as $obj) {
71
-            //$obj['acl'] = $this->getACL();
72
-            $obj['principaluri'] = $this->principalUri;
73
-            $children[] = new SchedulingObject($this->caldavBackend, $obj);
74
-        }
75
-
76
-        return $children;
77
-    }
78
-
79
-    /**
80
-     * Creates a new file in the directory.
81
-     *
82
-     * Data will either be supplied as a stream resource, or in certain cases
83
-     * as a string. Keep in mind that you may have to support either.
84
-     *
85
-     * After successful creation of the file, you may choose to return the ETag
86
-     * of the new file here.
87
-     *
88
-     * The returned ETag must be surrounded by double-quotes (The quotes should
89
-     * be part of the actual string).
90
-     *
91
-     * If you cannot accurately determine the ETag, you should not return it.
92
-     * If you don't store the file exactly as-is (you're transforming it
93
-     * somehow) you should also not return an ETag.
94
-     *
95
-     * This means that if a subsequent GET to this new file does not exactly
96
-     * return the same contents of what was submitted here, you are strongly
97
-     * recommended to omit the ETag.
98
-     *
99
-     * @param string          $name Name of the file
100
-     * @param resource|string $data Initial payload
101
-     *
102
-     * @return string|null
103
-     */
104
-    public function createFile($name, $data = null)
105
-    {
106
-        $this->caldavBackend->createSchedulingObject($this->principalUri, $name, $data);
107
-    }
108
-
109
-    /**
110
-     * Returns the owner principal.
111
-     *
112
-     * This must be a url to a principal, or null if there's no owner
113
-     *
114
-     * @return string|null
115
-     */
116
-    public function getOwner()
117
-    {
118
-        return $this->principalUri;
119
-    }
120
-
121
-    /**
122
-     * Returns a list of ACE's for this node.
123
-     *
124
-     * Each ACE has the following properties:
125
-     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
126
-     *     currently the only supported privileges
127
-     *   * 'principal', a url to the principal who owns the node
128
-     *   * 'protected' (optional), indicating that this ACE is not allowed to
129
-     *      be updated.
130
-     *
131
-     * @return array
132
-     */
133
-    public function getACL()
134
-    {
135
-        return [
136
-            [
137
-                'privilege' => '{DAV:}read',
138
-                'principal' => '{DAV:}authenticated',
139
-                'protected' => true,
140
-            ],
141
-            [
142
-                'privilege' => '{DAV:}write-properties',
143
-                'principal' => $this->getOwner(),
144
-                'protected' => true,
145
-            ],
146
-            [
147
-                'privilege' => '{DAV:}unbind',
148
-                'principal' => $this->getOwner(),
149
-                'protected' => true,
150
-            ],
151
-            [
152
-                'privilege' => '{DAV:}unbind',
153
-                'principal' => $this->getOwner().'/calendar-proxy-write',
154
-                'protected' => true,
155
-            ],
156
-            [
157
-                'privilege' => '{'.CalDAV\Plugin::NS_CALDAV.'}schedule-deliver',
158
-                'principal' => '{DAV:}authenticated',
159
-                'protected' => true,
160
-            ],
161
-        ];
162
-    }
163
-
164
-    /**
165
-     * Performs a calendar-query on the contents of this calendar.
166
-     *
167
-     * The calendar-query is defined in RFC4791 : CalDAV. Using the
168
-     * calendar-query it is possible for a client to request a specific set of
169
-     * object, based on contents of iCalendar properties, date-ranges and
170
-     * iCalendar component types (VTODO, VEVENT).
171
-     *
172
-     * This method should just return a list of (relative) urls that match this
173
-     * query.
174
-     *
175
-     * The list of filters are specified as an array. The exact array is
176
-     * documented by \Sabre\CalDAV\CalendarQueryParser.
177
-     *
178
-     * @return array
179
-     */
180
-    public function calendarQuery(array $filters)
181
-    {
182
-        $result = [];
183
-        $validator = new CalDAV\CalendarQueryValidator();
184
-
185
-        $objects = $this->caldavBackend->getSchedulingObjects($this->principalUri);
186
-        foreach ($objects as $object) {
187
-            $vObject = VObject\Reader::read($object['calendardata']);
188
-            if ($validator->validate($vObject, $filters)) {
189
-                $result[] = $object['uri'];
190
-            }
191
-
192
-            // Destroy circular references to PHP will GC the object.
193
-            $vObject->destroy();
194
-        }
195
-
196
-        return $result;
197
-    }
22
+	use DAVACL\ACLTrait;
23
+
24
+	/**
25
+	 * CalDAV backend.
26
+	 *
27
+	 * @var Backend\BackendInterface
28
+	 */
29
+	protected $caldavBackend;
30
+
31
+	/**
32
+	 * The principal Uri.
33
+	 *
34
+	 * @var string
35
+	 */
36
+	protected $principalUri;
37
+
38
+	/**
39
+	 * Constructor.
40
+	 *
41
+	 * @param string $principalUri
42
+	 */
43
+	public function __construct(Backend\SchedulingSupport $caldavBackend, $principalUri)
44
+	{
45
+		$this->caldavBackend = $caldavBackend;
46
+		$this->principalUri = $principalUri;
47
+	}
48
+
49
+	/**
50
+	 * Returns the name of the node.
51
+	 *
52
+	 * This is used to generate the url.
53
+	 *
54
+	 * @return string
55
+	 */
56
+	public function getName()
57
+	{
58
+		return 'inbox';
59
+	}
60
+
61
+	/**
62
+	 * Returns an array with all the child nodes.
63
+	 *
64
+	 * @return \Sabre\DAV\INode[]
65
+	 */
66
+	public function getChildren()
67
+	{
68
+		$objs = $this->caldavBackend->getSchedulingObjects($this->principalUri);
69
+		$children = [];
70
+		foreach ($objs as $obj) {
71
+			//$obj['acl'] = $this->getACL();
72
+			$obj['principaluri'] = $this->principalUri;
73
+			$children[] = new SchedulingObject($this->caldavBackend, $obj);
74
+		}
75
+
76
+		return $children;
77
+	}
78
+
79
+	/**
80
+	 * Creates a new file in the directory.
81
+	 *
82
+	 * Data will either be supplied as a stream resource, or in certain cases
83
+	 * as a string. Keep in mind that you may have to support either.
84
+	 *
85
+	 * After successful creation of the file, you may choose to return the ETag
86
+	 * of the new file here.
87
+	 *
88
+	 * The returned ETag must be surrounded by double-quotes (The quotes should
89
+	 * be part of the actual string).
90
+	 *
91
+	 * If you cannot accurately determine the ETag, you should not return it.
92
+	 * If you don't store the file exactly as-is (you're transforming it
93
+	 * somehow) you should also not return an ETag.
94
+	 *
95
+	 * This means that if a subsequent GET to this new file does not exactly
96
+	 * return the same contents of what was submitted here, you are strongly
97
+	 * recommended to omit the ETag.
98
+	 *
99
+	 * @param string          $name Name of the file
100
+	 * @param resource|string $data Initial payload
101
+	 *
102
+	 * @return string|null
103
+	 */
104
+	public function createFile($name, $data = null)
105
+	{
106
+		$this->caldavBackend->createSchedulingObject($this->principalUri, $name, $data);
107
+	}
108
+
109
+	/**
110
+	 * Returns the owner principal.
111
+	 *
112
+	 * This must be a url to a principal, or null if there's no owner
113
+	 *
114
+	 * @return string|null
115
+	 */
116
+	public function getOwner()
117
+	{
118
+		return $this->principalUri;
119
+	}
120
+
121
+	/**
122
+	 * Returns a list of ACE's for this node.
123
+	 *
124
+	 * Each ACE has the following properties:
125
+	 *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
126
+	 *     currently the only supported privileges
127
+	 *   * 'principal', a url to the principal who owns the node
128
+	 *   * 'protected' (optional), indicating that this ACE is not allowed to
129
+	 *      be updated.
130
+	 *
131
+	 * @return array
132
+	 */
133
+	public function getACL()
134
+	{
135
+		return [
136
+			[
137
+				'privilege' => '{DAV:}read',
138
+				'principal' => '{DAV:}authenticated',
139
+				'protected' => true,
140
+			],
141
+			[
142
+				'privilege' => '{DAV:}write-properties',
143
+				'principal' => $this->getOwner(),
144
+				'protected' => true,
145
+			],
146
+			[
147
+				'privilege' => '{DAV:}unbind',
148
+				'principal' => $this->getOwner(),
149
+				'protected' => true,
150
+			],
151
+			[
152
+				'privilege' => '{DAV:}unbind',
153
+				'principal' => $this->getOwner().'/calendar-proxy-write',
154
+				'protected' => true,
155
+			],
156
+			[
157
+				'privilege' => '{'.CalDAV\Plugin::NS_CALDAV.'}schedule-deliver',
158
+				'principal' => '{DAV:}authenticated',
159
+				'protected' => true,
160
+			],
161
+		];
162
+	}
163
+
164
+	/**
165
+	 * Performs a calendar-query on the contents of this calendar.
166
+	 *
167
+	 * The calendar-query is defined in RFC4791 : CalDAV. Using the
168
+	 * calendar-query it is possible for a client to request a specific set of
169
+	 * object, based on contents of iCalendar properties, date-ranges and
170
+	 * iCalendar component types (VTODO, VEVENT).
171
+	 *
172
+	 * This method should just return a list of (relative) urls that match this
173
+	 * query.
174
+	 *
175
+	 * The list of filters are specified as an array. The exact array is
176
+	 * documented by \Sabre\CalDAV\CalendarQueryParser.
177
+	 *
178
+	 * @return array
179
+	 */
180
+	public function calendarQuery(array $filters)
181
+	{
182
+		$result = [];
183
+		$validator = new CalDAV\CalendarQueryValidator();
184
+
185
+		$objects = $this->caldavBackend->getSchedulingObjects($this->principalUri);
186
+		foreach ($objects as $object) {
187
+			$vObject = VObject\Reader::read($object['calendardata']);
188
+			if ($validator->validate($vObject, $filters)) {
189
+				$result[] = $object['uri'];
190
+			}
191
+
192
+			// Destroy circular references to PHP will GC the object.
193
+			$vObject->destroy();
194
+		}
195
+
196
+		return $result;
197
+	}
198 198
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php 1 patch
Indentation   +102 added lines, -102 removed lines patch added patch discarded remove patch
@@ -16,115 +16,115 @@
 block discarded – undo
16 16
  */
17 17
 class SchedulingObject extends \Sabre\CalDAV\CalendarObject implements ISchedulingObject
18 18
 {
19
-    /**
20
-     * Constructor.
21
-     *
22
-     * The following properties may be passed within $objectData:
23
-     *
24
-     *   * uri - A unique uri. Only the 'basename' must be passed.
25
-     *   * principaluri - the principal that owns the object.
26
-     *   * calendardata (optional) - The iCalendar data
27
-     *   * etag - (optional) The etag for this object, MUST be encloded with
28
-     *            double-quotes.
29
-     *   * size - (optional) The size of the data in bytes.
30
-     *   * lastmodified - (optional) format as a unix timestamp.
31
-     *   * acl - (optional) Use this to override the default ACL for the node.
32
-     */
33
-    public function __construct(Backend\SchedulingSupport $caldavBackend, array $objectData)
34
-    {
35
-        parent::__construct($caldavBackend, [], $objectData);
19
+	/**
20
+	 * Constructor.
21
+	 *
22
+	 * The following properties may be passed within $objectData:
23
+	 *
24
+	 *   * uri - A unique uri. Only the 'basename' must be passed.
25
+	 *   * principaluri - the principal that owns the object.
26
+	 *   * calendardata (optional) - The iCalendar data
27
+	 *   * etag - (optional) The etag for this object, MUST be encloded with
28
+	 *            double-quotes.
29
+	 *   * size - (optional) The size of the data in bytes.
30
+	 *   * lastmodified - (optional) format as a unix timestamp.
31
+	 *   * acl - (optional) Use this to override the default ACL for the node.
32
+	 */
33
+	public function __construct(Backend\SchedulingSupport $caldavBackend, array $objectData)
34
+	{
35
+		parent::__construct($caldavBackend, [], $objectData);
36 36
 
37
-        if (!isset($objectData['uri'])) {
38
-            throw new \InvalidArgumentException('The objectData argument must contain an \'uri\' property');
39
-        }
40
-    }
37
+		if (!isset($objectData['uri'])) {
38
+			throw new \InvalidArgumentException('The objectData argument must contain an \'uri\' property');
39
+		}
40
+	}
41 41
 
42
-    /**
43
-     * Returns the ICalendar-formatted object.
44
-     *
45
-     * @return string
46
-     */
47
-    public function get()
48
-    {
49
-        // Pre-populating the 'calendardata' is optional, if we don't have it
50
-        // already we fetch it from the backend.
51
-        if (!isset($this->objectData['calendardata'])) {
52
-            $this->objectData = $this->caldavBackend->getSchedulingObject($this->objectData['principaluri'], $this->objectData['uri']);
53
-        }
42
+	/**
43
+	 * Returns the ICalendar-formatted object.
44
+	 *
45
+	 * @return string
46
+	 */
47
+	public function get()
48
+	{
49
+		// Pre-populating the 'calendardata' is optional, if we don't have it
50
+		// already we fetch it from the backend.
51
+		if (!isset($this->objectData['calendardata'])) {
52
+			$this->objectData = $this->caldavBackend->getSchedulingObject($this->objectData['principaluri'], $this->objectData['uri']);
53
+		}
54 54
 
55
-        return $this->objectData['calendardata'];
56
-    }
55
+		return $this->objectData['calendardata'];
56
+	}
57 57
 
58
-    /**
59
-     * Updates the ICalendar-formatted object.
60
-     *
61
-     * @param string|resource $calendarData
62
-     *
63
-     * @return string
64
-     */
65
-    public function put($calendarData)
66
-    {
67
-        throw new MethodNotAllowed('Updating scheduling objects is not supported');
68
-    }
58
+	/**
59
+	 * Updates the ICalendar-formatted object.
60
+	 *
61
+	 * @param string|resource $calendarData
62
+	 *
63
+	 * @return string
64
+	 */
65
+	public function put($calendarData)
66
+	{
67
+		throw new MethodNotAllowed('Updating scheduling objects is not supported');
68
+	}
69 69
 
70
-    /**
71
-     * Deletes the scheduling message.
72
-     */
73
-    public function delete()
74
-    {
75
-        $this->caldavBackend->deleteSchedulingObject($this->objectData['principaluri'], $this->objectData['uri']);
76
-    }
70
+	/**
71
+	 * Deletes the scheduling message.
72
+	 */
73
+	public function delete()
74
+	{
75
+		$this->caldavBackend->deleteSchedulingObject($this->objectData['principaluri'], $this->objectData['uri']);
76
+	}
77 77
 
78
-    /**
79
-     * Returns the owner principal.
80
-     *
81
-     * This must be a url to a principal, or null if there's no owner
82
-     *
83
-     * @return string|null
84
-     */
85
-    public function getOwner()
86
-    {
87
-        return $this->objectData['principaluri'];
88
-    }
78
+	/**
79
+	 * Returns the owner principal.
80
+	 *
81
+	 * This must be a url to a principal, or null if there's no owner
82
+	 *
83
+	 * @return string|null
84
+	 */
85
+	public function getOwner()
86
+	{
87
+		return $this->objectData['principaluri'];
88
+	}
89 89
 
90
-    /**
91
-     * Returns a list of ACE's for this node.
92
-     *
93
-     * Each ACE has the following properties:
94
-     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
95
-     *     currently the only supported privileges
96
-     *   * 'principal', a url to the principal who owns the node
97
-     *   * 'protected' (optional), indicating that this ACE is not allowed to
98
-     *      be updated.
99
-     *
100
-     * @return array
101
-     */
102
-    public function getACL()
103
-    {
104
-        // An alternative acl may be specified in the object data.
105
-        //
90
+	/**
91
+	 * Returns a list of ACE's for this node.
92
+	 *
93
+	 * Each ACE has the following properties:
94
+	 *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
95
+	 *     currently the only supported privileges
96
+	 *   * 'principal', a url to the principal who owns the node
97
+	 *   * 'protected' (optional), indicating that this ACE is not allowed to
98
+	 *      be updated.
99
+	 *
100
+	 * @return array
101
+	 */
102
+	public function getACL()
103
+	{
104
+		// An alternative acl may be specified in the object data.
105
+		//
106 106
 
107
-        if (isset($this->objectData['acl'])) {
108
-            return $this->objectData['acl'];
109
-        }
107
+		if (isset($this->objectData['acl'])) {
108
+			return $this->objectData['acl'];
109
+		}
110 110
 
111
-        // The default ACL
112
-        return [
113
-            [
114
-                'privilege' => '{DAV:}all',
115
-                'principal' => '{DAV:}owner',
116
-                'protected' => true,
117
-            ],
118
-            [
119
-                'privilege' => '{DAV:}all',
120
-                'principal' => $this->objectData['principaluri'].'/calendar-proxy-write',
121
-                'protected' => true,
122
-            ],
123
-            [
124
-                'privilege' => '{DAV:}read',
125
-                'principal' => $this->objectData['principaluri'].'/calendar-proxy-read',
126
-                'protected' => true,
127
-            ],
128
-        ];
129
-    }
111
+		// The default ACL
112
+		return [
113
+			[
114
+				'privilege' => '{DAV:}all',
115
+				'principal' => '{DAV:}owner',
116
+				'protected' => true,
117
+			],
118
+			[
119
+				'privilege' => '{DAV:}all',
120
+				'principal' => $this->objectData['principaluri'].'/calendar-proxy-write',
121
+				'protected' => true,
122
+			],
123
+			[
124
+				'privilege' => '{DAV:}read',
125
+				'principal' => $this->objectData['principaluri'].'/calendar-proxy-read',
126
+				'protected' => true,
127
+			],
128
+		];
129
+	}
130 130
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/CardDAV/Plugin.php 1 patch
Indentation   +849 added lines, -849 removed lines patch added patch discarded remove patch
@@ -25,683 +25,683 @@  discard block
 block discarded – undo
25 25
  */
26 26
 class Plugin extends DAV\ServerPlugin
27 27
 {
28
-    /**
29
-     * Url to the addressbooks.
30
-     */
31
-    const ADDRESSBOOK_ROOT = 'addressbooks';
32
-
33
-    /**
34
-     * xml namespace for CardDAV elements.
35
-     */
36
-    const NS_CARDDAV = 'urn:ietf:params:xml:ns:carddav';
37
-
38
-    /**
39
-     * Add urls to this property to have them automatically exposed as
40
-     * 'directories' to the user.
41
-     *
42
-     * @var array
43
-     */
44
-    public $directories = [];
45
-
46
-    /**
47
-     * Server class.
48
-     *
49
-     * @var DAV\Server
50
-     */
51
-    protected $server;
52
-
53
-    /**
54
-     * The default PDO storage uses a MySQL MEDIUMBLOB for iCalendar data,
55
-     * which can hold up to 2^24 = 16777216 bytes. This is plenty. We're
56
-     * capping it to 10M here.
57
-     */
58
-    protected $maxResourceSize = 10000000;
59
-
60
-    /**
61
-     * Initializes the plugin.
62
-     */
63
-    public function initialize(DAV\Server $server)
64
-    {
65
-        /* Events */
66
-        $server->on('propFind', [$this, 'propFindEarly']);
67
-        $server->on('propFind', [$this, 'propFindLate'], 150);
68
-        $server->on('report', [$this, 'report']);
69
-        $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
70
-        $server->on('beforeWriteContent', [$this, 'beforeWriteContent']);
71
-        $server->on('beforeCreateFile', [$this, 'beforeCreateFile']);
72
-        $server->on('afterMethod:GET', [$this, 'httpAfterGet']);
73
-
74
-        $server->xml->namespaceMap[self::NS_CARDDAV] = 'card';
75
-
76
-        $server->xml->elementMap['{'.self::NS_CARDDAV.'}addressbook-query'] = 'Sabre\\CardDAV\\Xml\\Request\\AddressBookQueryReport';
77
-        $server->xml->elementMap['{'.self::NS_CARDDAV.'}addressbook-multiget'] = 'Sabre\\CardDAV\\Xml\\Request\\AddressBookMultiGetReport';
78
-
79
-        /* Mapping Interfaces to {DAV:}resourcetype values */
80
-        $server->resourceTypeMapping['Sabre\\CardDAV\\IAddressBook'] = '{'.self::NS_CARDDAV.'}addressbook';
81
-        $server->resourceTypeMapping['Sabre\\CardDAV\\IDirectory'] = '{'.self::NS_CARDDAV.'}directory';
82
-
83
-        /* Adding properties that may never be changed */
84
-        $server->protectedProperties[] = '{'.self::NS_CARDDAV.'}supported-address-data';
85
-        $server->protectedProperties[] = '{'.self::NS_CARDDAV.'}max-resource-size';
86
-        $server->protectedProperties[] = '{'.self::NS_CARDDAV.'}addressbook-home-set';
87
-        $server->protectedProperties[] = '{'.self::NS_CARDDAV.'}supported-collation-set';
88
-
89
-        $server->xml->elementMap['{http://calendarserver.org/ns/}me-card'] = 'Sabre\\DAV\\Xml\\Property\\Href';
90
-
91
-        $this->server = $server;
92
-    }
93
-
94
-    /**
95
-     * Returns a list of supported features.
96
-     *
97
-     * This is used in the DAV: header in the OPTIONS and PROPFIND requests.
98
-     *
99
-     * @return array
100
-     */
101
-    public function getFeatures()
102
-    {
103
-        return ['addressbook'];
104
-    }
105
-
106
-    /**
107
-     * Returns a list of reports this plugin supports.
108
-     *
109
-     * This will be used in the {DAV:}supported-report-set property.
110
-     * Note that you still need to subscribe to the 'report' event to actually
111
-     * implement them
112
-     *
113
-     * @param string $uri
114
-     *
115
-     * @return array
116
-     */
117
-    public function getSupportedReportSet($uri)
118
-    {
119
-        $node = $this->server->tree->getNodeForPath($uri);
120
-        if ($node instanceof IAddressBook || $node instanceof ICard) {
121
-            return [
122
-                 '{'.self::NS_CARDDAV.'}addressbook-multiget',
123
-                 '{'.self::NS_CARDDAV.'}addressbook-query',
124
-            ];
125
-        }
126
-
127
-        return [];
128
-    }
129
-
130
-    /**
131
-     * Adds all CardDAV-specific properties.
132
-     */
133
-    public function propFindEarly(DAV\PropFind $propFind, DAV\INode $node)
134
-    {
135
-        $ns = '{'.self::NS_CARDDAV.'}';
136
-
137
-        if ($node instanceof IAddressBook) {
138
-            $propFind->handle($ns.'max-resource-size', $this->maxResourceSize);
139
-            $propFind->handle($ns.'supported-address-data', function () {
140
-                return new Xml\Property\SupportedAddressData();
141
-            });
142
-            $propFind->handle($ns.'supported-collation-set', function () {
143
-                return new Xml\Property\SupportedCollationSet();
144
-            });
145
-        }
146
-        if ($node instanceof DAVACL\IPrincipal) {
147
-            $path = $propFind->getPath();
148
-
149
-            $propFind->handle('{'.self::NS_CARDDAV.'}addressbook-home-set', function () use ($path) {
150
-                return new LocalHref($this->getAddressBookHomeForPrincipal($path).'/');
151
-            });
152
-
153
-            if ($this->directories) {
154
-                $propFind->handle('{'.self::NS_CARDDAV.'}directory-gateway', function () {
155
-                    return new LocalHref($this->directories);
156
-                });
157
-            }
158
-        }
159
-
160
-        if ($node instanceof ICard) {
161
-            // The address-data property is not supposed to be a 'real'
162
-            // property, but in large chunks of the spec it does act as such.
163
-            // Therefore we simply expose it as a property.
164
-            $propFind->handle('{'.self::NS_CARDDAV.'}address-data', function () use ($node) {
165
-                $val = $node->get();
166
-                if (is_resource($val)) {
167
-                    $val = stream_get_contents($val);
168
-                }
169
-
170
-                return $val;
171
-            });
172
-        }
173
-    }
174
-
175
-    /**
176
-     * This functions handles REPORT requests specific to CardDAV.
177
-     *
178
-     * @param string   $reportName
179
-     * @param \DOMNode $dom
180
-     * @param mixed    $path
181
-     *
182
-     * @return bool
183
-     */
184
-    public function report($reportName, $dom, $path)
185
-    {
186
-        switch ($reportName) {
187
-            case '{'.self::NS_CARDDAV.'}addressbook-multiget':
188
-                $this->server->transactionType = 'report-addressbook-multiget';
189
-                $this->addressbookMultiGetReport($dom);
190
-
191
-                return false;
192
-            case '{'.self::NS_CARDDAV.'}addressbook-query':
193
-                $this->server->transactionType = 'report-addressbook-query';
194
-                $this->addressBookQueryReport($dom);
195
-
196
-                return false;
197
-            default:
198
-                return;
199
-        }
200
-    }
201
-
202
-    /**
203
-     * Returns the addressbook home for a given principal.
204
-     *
205
-     * @param string $principal
206
-     *
207
-     * @return string
208
-     */
209
-    protected function getAddressbookHomeForPrincipal($principal)
210
-    {
211
-        list(, $principalId) = Uri\split($principal);
212
-
213
-        return self::ADDRESSBOOK_ROOT.'/'.$principalId;
214
-    }
215
-
216
-    /**
217
-     * This function handles the addressbook-multiget REPORT.
218
-     *
219
-     * This report is used by the client to fetch the content of a series
220
-     * of urls. Effectively avoiding a lot of redundant requests.
221
-     *
222
-     * @param Xml\Request\AddressBookMultiGetReport $report
223
-     */
224
-    public function addressbookMultiGetReport($report)
225
-    {
226
-        $contentType = $report->contentType;
227
-        $version = $report->version;
228
-        if ($version) {
229
-            $contentType .= '; version='.$version;
230
-        }
231
-
232
-        $vcardType = $this->negotiateVCard(
233
-            $contentType
234
-        );
235
-
236
-        $propertyList = [];
237
-        $paths = array_map(
238
-            [$this->server, 'calculateUri'],
239
-            $report->hrefs
240
-        );
241
-        foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $props) {
242
-            if (isset($props['200']['{'.self::NS_CARDDAV.'}address-data'])) {
243
-                $props['200']['{'.self::NS_CARDDAV.'}address-data'] = $this->convertVCard(
244
-                    $props[200]['{'.self::NS_CARDDAV.'}address-data'],
245
-                    $vcardType
246
-                );
247
-            }
248
-            $propertyList[] = $props;
249
-        }
250
-
251
-        $prefer = $this->server->getHTTPPrefer();
252
-
253
-        $this->server->httpResponse->setStatus(207);
254
-        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
255
-        $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
256
-        $this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, 'minimal' === $prefer['return']));
257
-    }
258
-
259
-    /**
260
-     * This method is triggered before a file gets updated with new content.
261
-     *
262
-     * This plugin uses this method to ensure that Card nodes receive valid
263
-     * vcard data.
264
-     *
265
-     * @param string   $path
266
-     * @param resource $data
267
-     * @param bool     $modified should be set to true, if this event handler
268
-     *                           changed &$data
269
-     */
270
-    public function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified)
271
-    {
272
-        if (!$node instanceof ICard) {
273
-            return;
274
-        }
275
-
276
-        $this->validateVCard($data, $modified);
277
-    }
278
-
279
-    /**
280
-     * This method is triggered before a new file is created.
281
-     *
282
-     * This plugin uses this method to ensure that Card nodes receive valid
283
-     * vcard data.
284
-     *
285
-     * @param string   $path
286
-     * @param resource $data
287
-     * @param bool     $modified should be set to true, if this event handler
288
-     *                           changed &$data
289
-     */
290
-    public function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified)
291
-    {
292
-        if (!$parentNode instanceof IAddressBook) {
293
-            return;
294
-        }
295
-
296
-        $this->validateVCard($data, $modified);
297
-    }
298
-
299
-    /**
300
-     * Checks if the submitted iCalendar data is in fact, valid.
301
-     *
302
-     * An exception is thrown if it's not.
303
-     *
304
-     * @param resource|string $data
305
-     * @param bool            $modified should be set to true, if this event handler
306
-     *                                  changed &$data
307
-     */
308
-    protected function validateVCard(&$data, &$modified)
309
-    {
310
-        // If it's a stream, we convert it to a string first.
311
-        if (is_resource($data)) {
312
-            $data = stream_get_contents($data);
313
-        }
314
-
315
-        $before = $data;
316
-
317
-        try {
318
-            // If the data starts with a [, we can reasonably assume we're dealing
319
-            // with a jCal object.
320
-            if ('[' === substr($data, 0, 1)) {
321
-                $vobj = VObject\Reader::readJson($data);
322
-
323
-                // Converting $data back to iCalendar, as that's what we
324
-                // technically support everywhere.
325
-                $data = $vobj->serialize();
326
-                $modified = true;
327
-            } else {
328
-                $vobj = VObject\Reader::read($data);
329
-            }
330
-        } catch (VObject\ParseException $e) {
331
-            throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid vCard or jCard data. Parse error: '.$e->getMessage());
332
-        }
333
-
334
-        if ('VCARD' !== $vobj->name) {
335
-            throw new DAV\Exception\UnsupportedMediaType('This collection can only support vcard objects.');
336
-        }
337
-
338
-        $options = VObject\Node::PROFILE_CARDDAV;
339
-        $prefer = $this->server->getHTTPPrefer();
340
-
341
-        if ('strict' !== $prefer['handling']) {
342
-            $options |= VObject\Node::REPAIR;
343
-        }
344
-
345
-        $messages = $vobj->validate($options);
346
-
347
-        $highestLevel = 0;
348
-        $warningMessage = null;
349
-
350
-        // $messages contains a list of problems with the vcard, along with
351
-        // their severity.
352
-        foreach ($messages as $message) {
353
-            if ($message['level'] > $highestLevel) {
354
-                // Recording the highest reported error level.
355
-                $highestLevel = $message['level'];
356
-                $warningMessage = $message['message'];
357
-            }
358
-
359
-            switch ($message['level']) {
360
-                case 1:
361
-                    // Level 1 means that there was a problem, but it was repaired.
362
-                    $modified = true;
363
-                    break;
364
-                case 2:
365
-                    // Level 2 means a warning, but not critical
366
-                    break;
367
-                case 3:
368
-                    // Level 3 means a critical error
369
-                    throw new DAV\Exception\UnsupportedMediaType('Validation error in vCard: '.$message['message']);
370
-            }
371
-        }
372
-        if ($warningMessage) {
373
-            $this->server->httpResponse->setHeader(
374
-                'X-Sabre-Ew-Gross',
375
-                'vCard validation warning: '.$warningMessage
376
-            );
377
-
378
-            // Re-serializing object.
379
-            $data = $vobj->serialize();
380
-            if (!$modified && 0 !== strcmp($data, $before)) {
381
-                // This ensures that the system does not send an ETag back.
382
-                $modified = true;
383
-            }
384
-        }
385
-
386
-        // Destroy circular references to PHP will GC the object.
387
-        $vobj->destroy();
388
-    }
389
-
390
-    /**
391
-     * This function handles the addressbook-query REPORT.
392
-     *
393
-     * This report is used by the client to filter an addressbook based on a
394
-     * complex query.
395
-     *
396
-     * @param Xml\Request\AddressBookQueryReport $report
397
-     */
398
-    protected function addressbookQueryReport($report)
399
-    {
400
-        $depth = $this->server->getHTTPDepth(0);
401
-
402
-        if (0 == $depth) {
403
-            $candidateNodes = [
404
-                $this->server->tree->getNodeForPath($this->server->getRequestUri()),
405
-            ];
406
-            if (!$candidateNodes[0] instanceof ICard) {
407
-                throw new ReportNotSupported('The addressbook-query report is not supported on this url with Depth: 0');
408
-            }
409
-        } else {
410
-            $candidateNodes = $this->server->tree->getChildren($this->server->getRequestUri());
411
-        }
412
-
413
-        $contentType = $report->contentType;
414
-        if ($report->version) {
415
-            $contentType .= '; version='.$report->version;
416
-        }
417
-
418
-        $vcardType = $this->negotiateVCard(
419
-            $contentType
420
-        );
421
-
422
-        $validNodes = [];
423
-        foreach ($candidateNodes as $node) {
424
-            if (!$node instanceof ICard) {
425
-                continue;
426
-            }
427
-
428
-            $blob = $node->get();
429
-            if (is_resource($blob)) {
430
-                $blob = stream_get_contents($blob);
431
-            }
432
-
433
-            if (!$this->validateFilters($blob, $report->filters, $report->test)) {
434
-                continue;
435
-            }
436
-
437
-            $validNodes[] = $node;
438
-
439
-            if ($report->limit && $report->limit <= count($validNodes)) {
440
-                // We hit the maximum number of items, we can stop now.
441
-                break;
442
-            }
443
-        }
444
-
445
-        $result = [];
446
-        foreach ($validNodes as $validNode) {
447
-            if (0 == $depth) {
448
-                $href = $this->server->getRequestUri();
449
-            } else {
450
-                $href = $this->server->getRequestUri().'/'.$validNode->getName();
451
-            }
452
-
453
-            list($props) = $this->server->getPropertiesForPath($href, $report->properties, 0);
454
-
455
-            if (isset($props[200]['{'.self::NS_CARDDAV.'}address-data'])) {
456
-                $props[200]['{'.self::NS_CARDDAV.'}address-data'] = $this->convertVCard(
457
-                    $props[200]['{'.self::NS_CARDDAV.'}address-data'],
458
-                    $vcardType,
459
-                    $report->addressDataProperties
460
-                );
461
-            }
462
-            $result[] = $props;
463
-        }
464
-
465
-        $prefer = $this->server->getHTTPPrefer();
466
-
467
-        $this->server->httpResponse->setStatus(207);
468
-        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
469
-        $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
470
-        $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, 'minimal' === $prefer['return']));
471
-    }
472
-
473
-    /**
474
-     * Validates if a vcard makes it throught a list of filters.
475
-     *
476
-     * @param string $vcardData
477
-     * @param string $test      anyof or allof (which means OR or AND)
478
-     *
479
-     * @return bool
480
-     */
481
-    public function validateFilters($vcardData, array $filters, $test)
482
-    {
483
-        if (!$filters) {
484
-            return true;
485
-        }
486
-        $vcard = VObject\Reader::read($vcardData);
487
-
488
-        foreach ($filters as $filter) {
489
-            $isDefined = isset($vcard->{$filter['name']});
490
-            if ($filter['is-not-defined']) {
491
-                if ($isDefined) {
492
-                    $success = false;
493
-                } else {
494
-                    $success = true;
495
-                }
496
-            } elseif ((!$filter['param-filters'] && !$filter['text-matches']) || !$isDefined) {
497
-                // We only need to check for existence
498
-                $success = $isDefined;
499
-            } else {
500
-                $vProperties = $vcard->select($filter['name']);
501
-
502
-                $results = [];
503
-                if ($filter['param-filters']) {
504
-                    $results[] = $this->validateParamFilters($vProperties, $filter['param-filters'], $filter['test']);
505
-                }
506
-                if ($filter['text-matches']) {
507
-                    $texts = [];
508
-                    foreach ($vProperties as $vProperty) {
509
-                        $texts[] = $vProperty->getValue();
510
-                    }
511
-
512
-                    $results[] = $this->validateTextMatches($texts, $filter['text-matches'], $filter['test']);
513
-                }
514
-
515
-                if (1 === count($results)) {
516
-                    $success = $results[0];
517
-                } else {
518
-                    if ('anyof' === $filter['test']) {
519
-                        $success = $results[0] || $results[1];
520
-                    } else {
521
-                        $success = $results[0] && $results[1];
522
-                    }
523
-                }
524
-            } // else
525
-
526
-            // There are two conditions where we can already determine whether
527
-            // or not this filter succeeds.
528
-            if ('anyof' === $test && $success) {
529
-                // Destroy circular references to PHP will GC the object.
530
-                $vcard->destroy();
531
-
532
-                return true;
533
-            }
534
-            if ('allof' === $test && !$success) {
535
-                // Destroy circular references to PHP will GC the object.
536
-                $vcard->destroy();
537
-
538
-                return false;
539
-            }
540
-        } // foreach
541
-
542
-        // Destroy circular references to PHP will GC the object.
543
-        $vcard->destroy();
544
-
545
-        // If we got all the way here, it means we haven't been able to
546
-        // determine early if the test failed or not.
547
-        //
548
-        // This implies for 'anyof' that the test failed, and for 'allof' that
549
-        // we succeeded. Sounds weird, but makes sense.
550
-        return 'allof' === $test;
551
-    }
552
-
553
-    /**
554
-     * Validates if a param-filter can be applied to a specific property.
555
-     *
556
-     * @todo currently we're only validating the first parameter of the passed
557
-     *       property. Any subsequence parameters with the same name are
558
-     *       ignored.
559
-     *
560
-     * @param string $test
561
-     *
562
-     * @return bool
563
-     */
564
-    protected function validateParamFilters(array $vProperties, array $filters, $test)
565
-    {
566
-        foreach ($filters as $filter) {
567
-            $isDefined = false;
568
-            foreach ($vProperties as $vProperty) {
569
-                $isDefined = isset($vProperty[$filter['name']]);
570
-                if ($isDefined) {
571
-                    break;
572
-                }
573
-            }
574
-
575
-            if ($filter['is-not-defined']) {
576
-                if ($isDefined) {
577
-                    $success = false;
578
-                } else {
579
-                    $success = true;
580
-                }
581
-
582
-                // If there's no text-match, we can just check for existence
583
-            } elseif (!$filter['text-match'] || !$isDefined) {
584
-                $success = $isDefined;
585
-            } else {
586
-                $success = false;
587
-                foreach ($vProperties as $vProperty) {
588
-                    // If we got all the way here, we'll need to validate the
589
-                    // text-match filter.
590
-                    if (isset($vProperty[$filter['name']])) {
591
-                        $success = DAV\StringUtil::textMatch(
592
-                            $vProperty[$filter['name']]->getValue(),
593
-                            $filter['text-match']['value'],
594
-                            $filter['text-match']['collation'],
595
-                            $filter['text-match']['match-type']
596
-                        );
597
-                        if ($filter['text-match']['negate-condition']) {
598
-                            $success = !$success;
599
-                        }
600
-                    }
601
-                    if ($success) {
602
-                        break;
603
-                    }
604
-                }
605
-            } // else
606
-
607
-            // There are two conditions where we can already determine whether
608
-            // or not this filter succeeds.
609
-            if ('anyof' === $test && $success) {
610
-                return true;
611
-            }
612
-            if ('allof' === $test && !$success) {
613
-                return false;
614
-            }
615
-        }
616
-
617
-        // If we got all the way here, it means we haven't been able to
618
-        // determine early if the test failed or not.
619
-        //
620
-        // This implies for 'anyof' that the test failed, and for 'allof' that
621
-        // we succeeded. Sounds weird, but makes sense.
622
-        return 'allof' === $test;
623
-    }
624
-
625
-    /**
626
-     * Validates if a text-filter can be applied to a specific property.
627
-     *
628
-     * @param string $test
629
-     *
630
-     * @return bool
631
-     */
632
-    protected function validateTextMatches(array $texts, array $filters, $test)
633
-    {
634
-        foreach ($filters as $filter) {
635
-            $success = false;
636
-            foreach ($texts as $haystack) {
637
-                $success = DAV\StringUtil::textMatch($haystack, $filter['value'], $filter['collation'], $filter['match-type']);
638
-                if ($filter['negate-condition']) {
639
-                    $success = !$success;
640
-                }
641
-
642
-                // Breaking on the first match
643
-                if ($success) {
644
-                    break;
645
-                }
646
-            }
647
-
648
-            if ($success && 'anyof' === $test) {
649
-                return true;
650
-            }
651
-
652
-            if (!$success && 'allof' == $test) {
653
-                return false;
654
-            }
655
-        }
656
-
657
-        // If we got all the way here, it means we haven't been able to
658
-        // determine early if the test failed or not.
659
-        //
660
-        // This implies for 'anyof' that the test failed, and for 'allof' that
661
-        // we succeeded. Sounds weird, but makes sense.
662
-        return 'allof' === $test;
663
-    }
664
-
665
-    /**
666
-     * This event is triggered when fetching properties.
667
-     *
668
-     * This event is scheduled late in the process, after most work for
669
-     * propfind has been done.
670
-     */
671
-    public function propFindLate(DAV\PropFind $propFind, DAV\INode $node)
672
-    {
673
-        // If the request was made using the SOGO connector, we must rewrite
674
-        // the content-type property. By default SabreDAV will send back
675
-        // text/x-vcard; charset=utf-8, but for SOGO we must strip that last
676
-        // part.
677
-        if (false === strpos((string) $this->server->httpRequest->getHeader('User-Agent'), 'Thunderbird')) {
678
-            return;
679
-        }
680
-        $contentType = $propFind->get('{DAV:}getcontenttype');
681
-        if (null !== $contentType) {
682
-            list($part) = explode(';', $contentType);
683
-            if ('text/x-vcard' === $part || 'text/vcard' === $part) {
684
-                $propFind->set('{DAV:}getcontenttype', 'text/x-vcard');
685
-            }
686
-        }
687
-    }
688
-
689
-    /**
690
-     * This method is used to generate HTML output for the
691
-     * Sabre\DAV\Browser\Plugin. This allows us to generate an interface users
692
-     * can use to create new addressbooks.
693
-     *
694
-     * @param string $output
695
-     *
696
-     * @return bool
697
-     */
698
-    public function htmlActionsPanel(DAV\INode $node, &$output)
699
-    {
700
-        if (!$node instanceof AddressBookHome) {
701
-            return;
702
-        }
703
-
704
-        $output .= '<tr><td colspan="2"><form method="post" action="">
28
+	/**
29
+	 * Url to the addressbooks.
30
+	 */
31
+	const ADDRESSBOOK_ROOT = 'addressbooks';
32
+
33
+	/**
34
+	 * xml namespace for CardDAV elements.
35
+	 */
36
+	const NS_CARDDAV = 'urn:ietf:params:xml:ns:carddav';
37
+
38
+	/**
39
+	 * Add urls to this property to have them automatically exposed as
40
+	 * 'directories' to the user.
41
+	 *
42
+	 * @var array
43
+	 */
44
+	public $directories = [];
45
+
46
+	/**
47
+	 * Server class.
48
+	 *
49
+	 * @var DAV\Server
50
+	 */
51
+	protected $server;
52
+
53
+	/**
54
+	 * The default PDO storage uses a MySQL MEDIUMBLOB for iCalendar data,
55
+	 * which can hold up to 2^24 = 16777216 bytes. This is plenty. We're
56
+	 * capping it to 10M here.
57
+	 */
58
+	protected $maxResourceSize = 10000000;
59
+
60
+	/**
61
+	 * Initializes the plugin.
62
+	 */
63
+	public function initialize(DAV\Server $server)
64
+	{
65
+		/* Events */
66
+		$server->on('propFind', [$this, 'propFindEarly']);
67
+		$server->on('propFind', [$this, 'propFindLate'], 150);
68
+		$server->on('report', [$this, 'report']);
69
+		$server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
70
+		$server->on('beforeWriteContent', [$this, 'beforeWriteContent']);
71
+		$server->on('beforeCreateFile', [$this, 'beforeCreateFile']);
72
+		$server->on('afterMethod:GET', [$this, 'httpAfterGet']);
73
+
74
+		$server->xml->namespaceMap[self::NS_CARDDAV] = 'card';
75
+
76
+		$server->xml->elementMap['{'.self::NS_CARDDAV.'}addressbook-query'] = 'Sabre\\CardDAV\\Xml\\Request\\AddressBookQueryReport';
77
+		$server->xml->elementMap['{'.self::NS_CARDDAV.'}addressbook-multiget'] = 'Sabre\\CardDAV\\Xml\\Request\\AddressBookMultiGetReport';
78
+
79
+		/* Mapping Interfaces to {DAV:}resourcetype values */
80
+		$server->resourceTypeMapping['Sabre\\CardDAV\\IAddressBook'] = '{'.self::NS_CARDDAV.'}addressbook';
81
+		$server->resourceTypeMapping['Sabre\\CardDAV\\IDirectory'] = '{'.self::NS_CARDDAV.'}directory';
82
+
83
+		/* Adding properties that may never be changed */
84
+		$server->protectedProperties[] = '{'.self::NS_CARDDAV.'}supported-address-data';
85
+		$server->protectedProperties[] = '{'.self::NS_CARDDAV.'}max-resource-size';
86
+		$server->protectedProperties[] = '{'.self::NS_CARDDAV.'}addressbook-home-set';
87
+		$server->protectedProperties[] = '{'.self::NS_CARDDAV.'}supported-collation-set';
88
+
89
+		$server->xml->elementMap['{http://calendarserver.org/ns/}me-card'] = 'Sabre\\DAV\\Xml\\Property\\Href';
90
+
91
+		$this->server = $server;
92
+	}
93
+
94
+	/**
95
+	 * Returns a list of supported features.
96
+	 *
97
+	 * This is used in the DAV: header in the OPTIONS and PROPFIND requests.
98
+	 *
99
+	 * @return array
100
+	 */
101
+	public function getFeatures()
102
+	{
103
+		return ['addressbook'];
104
+	}
105
+
106
+	/**
107
+	 * Returns a list of reports this plugin supports.
108
+	 *
109
+	 * This will be used in the {DAV:}supported-report-set property.
110
+	 * Note that you still need to subscribe to the 'report' event to actually
111
+	 * implement them
112
+	 *
113
+	 * @param string $uri
114
+	 *
115
+	 * @return array
116
+	 */
117
+	public function getSupportedReportSet($uri)
118
+	{
119
+		$node = $this->server->tree->getNodeForPath($uri);
120
+		if ($node instanceof IAddressBook || $node instanceof ICard) {
121
+			return [
122
+				 '{'.self::NS_CARDDAV.'}addressbook-multiget',
123
+				 '{'.self::NS_CARDDAV.'}addressbook-query',
124
+			];
125
+		}
126
+
127
+		return [];
128
+	}
129
+
130
+	/**
131
+	 * Adds all CardDAV-specific properties.
132
+	 */
133
+	public function propFindEarly(DAV\PropFind $propFind, DAV\INode $node)
134
+	{
135
+		$ns = '{'.self::NS_CARDDAV.'}';
136
+
137
+		if ($node instanceof IAddressBook) {
138
+			$propFind->handle($ns.'max-resource-size', $this->maxResourceSize);
139
+			$propFind->handle($ns.'supported-address-data', function () {
140
+				return new Xml\Property\SupportedAddressData();
141
+			});
142
+			$propFind->handle($ns.'supported-collation-set', function () {
143
+				return new Xml\Property\SupportedCollationSet();
144
+			});
145
+		}
146
+		if ($node instanceof DAVACL\IPrincipal) {
147
+			$path = $propFind->getPath();
148
+
149
+			$propFind->handle('{'.self::NS_CARDDAV.'}addressbook-home-set', function () use ($path) {
150
+				return new LocalHref($this->getAddressBookHomeForPrincipal($path).'/');
151
+			});
152
+
153
+			if ($this->directories) {
154
+				$propFind->handle('{'.self::NS_CARDDAV.'}directory-gateway', function () {
155
+					return new LocalHref($this->directories);
156
+				});
157
+			}
158
+		}
159
+
160
+		if ($node instanceof ICard) {
161
+			// The address-data property is not supposed to be a 'real'
162
+			// property, but in large chunks of the spec it does act as such.
163
+			// Therefore we simply expose it as a property.
164
+			$propFind->handle('{'.self::NS_CARDDAV.'}address-data', function () use ($node) {
165
+				$val = $node->get();
166
+				if (is_resource($val)) {
167
+					$val = stream_get_contents($val);
168
+				}
169
+
170
+				return $val;
171
+			});
172
+		}
173
+	}
174
+
175
+	/**
176
+	 * This functions handles REPORT requests specific to CardDAV.
177
+	 *
178
+	 * @param string   $reportName
179
+	 * @param \DOMNode $dom
180
+	 * @param mixed    $path
181
+	 *
182
+	 * @return bool
183
+	 */
184
+	public function report($reportName, $dom, $path)
185
+	{
186
+		switch ($reportName) {
187
+			case '{'.self::NS_CARDDAV.'}addressbook-multiget':
188
+				$this->server->transactionType = 'report-addressbook-multiget';
189
+				$this->addressbookMultiGetReport($dom);
190
+
191
+				return false;
192
+			case '{'.self::NS_CARDDAV.'}addressbook-query':
193
+				$this->server->transactionType = 'report-addressbook-query';
194
+				$this->addressBookQueryReport($dom);
195
+
196
+				return false;
197
+			default:
198
+				return;
199
+		}
200
+	}
201
+
202
+	/**
203
+	 * Returns the addressbook home for a given principal.
204
+	 *
205
+	 * @param string $principal
206
+	 *
207
+	 * @return string
208
+	 */
209
+	protected function getAddressbookHomeForPrincipal($principal)
210
+	{
211
+		list(, $principalId) = Uri\split($principal);
212
+
213
+		return self::ADDRESSBOOK_ROOT.'/'.$principalId;
214
+	}
215
+
216
+	/**
217
+	 * This function handles the addressbook-multiget REPORT.
218
+	 *
219
+	 * This report is used by the client to fetch the content of a series
220
+	 * of urls. Effectively avoiding a lot of redundant requests.
221
+	 *
222
+	 * @param Xml\Request\AddressBookMultiGetReport $report
223
+	 */
224
+	public function addressbookMultiGetReport($report)
225
+	{
226
+		$contentType = $report->contentType;
227
+		$version = $report->version;
228
+		if ($version) {
229
+			$contentType .= '; version='.$version;
230
+		}
231
+
232
+		$vcardType = $this->negotiateVCard(
233
+			$contentType
234
+		);
235
+
236
+		$propertyList = [];
237
+		$paths = array_map(
238
+			[$this->server, 'calculateUri'],
239
+			$report->hrefs
240
+		);
241
+		foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $props) {
242
+			if (isset($props['200']['{'.self::NS_CARDDAV.'}address-data'])) {
243
+				$props['200']['{'.self::NS_CARDDAV.'}address-data'] = $this->convertVCard(
244
+					$props[200]['{'.self::NS_CARDDAV.'}address-data'],
245
+					$vcardType
246
+				);
247
+			}
248
+			$propertyList[] = $props;
249
+		}
250
+
251
+		$prefer = $this->server->getHTTPPrefer();
252
+
253
+		$this->server->httpResponse->setStatus(207);
254
+		$this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
255
+		$this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
256
+		$this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, 'minimal' === $prefer['return']));
257
+	}
258
+
259
+	/**
260
+	 * This method is triggered before a file gets updated with new content.
261
+	 *
262
+	 * This plugin uses this method to ensure that Card nodes receive valid
263
+	 * vcard data.
264
+	 *
265
+	 * @param string   $path
266
+	 * @param resource $data
267
+	 * @param bool     $modified should be set to true, if this event handler
268
+	 *                           changed &$data
269
+	 */
270
+	public function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified)
271
+	{
272
+		if (!$node instanceof ICard) {
273
+			return;
274
+		}
275
+
276
+		$this->validateVCard($data, $modified);
277
+	}
278
+
279
+	/**
280
+	 * This method is triggered before a new file is created.
281
+	 *
282
+	 * This plugin uses this method to ensure that Card nodes receive valid
283
+	 * vcard data.
284
+	 *
285
+	 * @param string   $path
286
+	 * @param resource $data
287
+	 * @param bool     $modified should be set to true, if this event handler
288
+	 *                           changed &$data
289
+	 */
290
+	public function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified)
291
+	{
292
+		if (!$parentNode instanceof IAddressBook) {
293
+			return;
294
+		}
295
+
296
+		$this->validateVCard($data, $modified);
297
+	}
298
+
299
+	/**
300
+	 * Checks if the submitted iCalendar data is in fact, valid.
301
+	 *
302
+	 * An exception is thrown if it's not.
303
+	 *
304
+	 * @param resource|string $data
305
+	 * @param bool            $modified should be set to true, if this event handler
306
+	 *                                  changed &$data
307
+	 */
308
+	protected function validateVCard(&$data, &$modified)
309
+	{
310
+		// If it's a stream, we convert it to a string first.
311
+		if (is_resource($data)) {
312
+			$data = stream_get_contents($data);
313
+		}
314
+
315
+		$before = $data;
316
+
317
+		try {
318
+			// If the data starts with a [, we can reasonably assume we're dealing
319
+			// with a jCal object.
320
+			if ('[' === substr($data, 0, 1)) {
321
+				$vobj = VObject\Reader::readJson($data);
322
+
323
+				// Converting $data back to iCalendar, as that's what we
324
+				// technically support everywhere.
325
+				$data = $vobj->serialize();
326
+				$modified = true;
327
+			} else {
328
+				$vobj = VObject\Reader::read($data);
329
+			}
330
+		} catch (VObject\ParseException $e) {
331
+			throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid vCard or jCard data. Parse error: '.$e->getMessage());
332
+		}
333
+
334
+		if ('VCARD' !== $vobj->name) {
335
+			throw new DAV\Exception\UnsupportedMediaType('This collection can only support vcard objects.');
336
+		}
337
+
338
+		$options = VObject\Node::PROFILE_CARDDAV;
339
+		$prefer = $this->server->getHTTPPrefer();
340
+
341
+		if ('strict' !== $prefer['handling']) {
342
+			$options |= VObject\Node::REPAIR;
343
+		}
344
+
345
+		$messages = $vobj->validate($options);
346
+
347
+		$highestLevel = 0;
348
+		$warningMessage = null;
349
+
350
+		// $messages contains a list of problems with the vcard, along with
351
+		// their severity.
352
+		foreach ($messages as $message) {
353
+			if ($message['level'] > $highestLevel) {
354
+				// Recording the highest reported error level.
355
+				$highestLevel = $message['level'];
356
+				$warningMessage = $message['message'];
357
+			}
358
+
359
+			switch ($message['level']) {
360
+				case 1:
361
+					// Level 1 means that there was a problem, but it was repaired.
362
+					$modified = true;
363
+					break;
364
+				case 2:
365
+					// Level 2 means a warning, but not critical
366
+					break;
367
+				case 3:
368
+					// Level 3 means a critical error
369
+					throw new DAV\Exception\UnsupportedMediaType('Validation error in vCard: '.$message['message']);
370
+			}
371
+		}
372
+		if ($warningMessage) {
373
+			$this->server->httpResponse->setHeader(
374
+				'X-Sabre-Ew-Gross',
375
+				'vCard validation warning: '.$warningMessage
376
+			);
377
+
378
+			// Re-serializing object.
379
+			$data = $vobj->serialize();
380
+			if (!$modified && 0 !== strcmp($data, $before)) {
381
+				// This ensures that the system does not send an ETag back.
382
+				$modified = true;
383
+			}
384
+		}
385
+
386
+		// Destroy circular references to PHP will GC the object.
387
+		$vobj->destroy();
388
+	}
389
+
390
+	/**
391
+	 * This function handles the addressbook-query REPORT.
392
+	 *
393
+	 * This report is used by the client to filter an addressbook based on a
394
+	 * complex query.
395
+	 *
396
+	 * @param Xml\Request\AddressBookQueryReport $report
397
+	 */
398
+	protected function addressbookQueryReport($report)
399
+	{
400
+		$depth = $this->server->getHTTPDepth(0);
401
+
402
+		if (0 == $depth) {
403
+			$candidateNodes = [
404
+				$this->server->tree->getNodeForPath($this->server->getRequestUri()),
405
+			];
406
+			if (!$candidateNodes[0] instanceof ICard) {
407
+				throw new ReportNotSupported('The addressbook-query report is not supported on this url with Depth: 0');
408
+			}
409
+		} else {
410
+			$candidateNodes = $this->server->tree->getChildren($this->server->getRequestUri());
411
+		}
412
+
413
+		$contentType = $report->contentType;
414
+		if ($report->version) {
415
+			$contentType .= '; version='.$report->version;
416
+		}
417
+
418
+		$vcardType = $this->negotiateVCard(
419
+			$contentType
420
+		);
421
+
422
+		$validNodes = [];
423
+		foreach ($candidateNodes as $node) {
424
+			if (!$node instanceof ICard) {
425
+				continue;
426
+			}
427
+
428
+			$blob = $node->get();
429
+			if (is_resource($blob)) {
430
+				$blob = stream_get_contents($blob);
431
+			}
432
+
433
+			if (!$this->validateFilters($blob, $report->filters, $report->test)) {
434
+				continue;
435
+			}
436
+
437
+			$validNodes[] = $node;
438
+
439
+			if ($report->limit && $report->limit <= count($validNodes)) {
440
+				// We hit the maximum number of items, we can stop now.
441
+				break;
442
+			}
443
+		}
444
+
445
+		$result = [];
446
+		foreach ($validNodes as $validNode) {
447
+			if (0 == $depth) {
448
+				$href = $this->server->getRequestUri();
449
+			} else {
450
+				$href = $this->server->getRequestUri().'/'.$validNode->getName();
451
+			}
452
+
453
+			list($props) = $this->server->getPropertiesForPath($href, $report->properties, 0);
454
+
455
+			if (isset($props[200]['{'.self::NS_CARDDAV.'}address-data'])) {
456
+				$props[200]['{'.self::NS_CARDDAV.'}address-data'] = $this->convertVCard(
457
+					$props[200]['{'.self::NS_CARDDAV.'}address-data'],
458
+					$vcardType,
459
+					$report->addressDataProperties
460
+				);
461
+			}
462
+			$result[] = $props;
463
+		}
464
+
465
+		$prefer = $this->server->getHTTPPrefer();
466
+
467
+		$this->server->httpResponse->setStatus(207);
468
+		$this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
469
+		$this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
470
+		$this->server->httpResponse->setBody($this->server->generateMultiStatus($result, 'minimal' === $prefer['return']));
471
+	}
472
+
473
+	/**
474
+	 * Validates if a vcard makes it throught a list of filters.
475
+	 *
476
+	 * @param string $vcardData
477
+	 * @param string $test      anyof or allof (which means OR or AND)
478
+	 *
479
+	 * @return bool
480
+	 */
481
+	public function validateFilters($vcardData, array $filters, $test)
482
+	{
483
+		if (!$filters) {
484
+			return true;
485
+		}
486
+		$vcard = VObject\Reader::read($vcardData);
487
+
488
+		foreach ($filters as $filter) {
489
+			$isDefined = isset($vcard->{$filter['name']});
490
+			if ($filter['is-not-defined']) {
491
+				if ($isDefined) {
492
+					$success = false;
493
+				} else {
494
+					$success = true;
495
+				}
496
+			} elseif ((!$filter['param-filters'] && !$filter['text-matches']) || !$isDefined) {
497
+				// We only need to check for existence
498
+				$success = $isDefined;
499
+			} else {
500
+				$vProperties = $vcard->select($filter['name']);
501
+
502
+				$results = [];
503
+				if ($filter['param-filters']) {
504
+					$results[] = $this->validateParamFilters($vProperties, $filter['param-filters'], $filter['test']);
505
+				}
506
+				if ($filter['text-matches']) {
507
+					$texts = [];
508
+					foreach ($vProperties as $vProperty) {
509
+						$texts[] = $vProperty->getValue();
510
+					}
511
+
512
+					$results[] = $this->validateTextMatches($texts, $filter['text-matches'], $filter['test']);
513
+				}
514
+
515
+				if (1 === count($results)) {
516
+					$success = $results[0];
517
+				} else {
518
+					if ('anyof' === $filter['test']) {
519
+						$success = $results[0] || $results[1];
520
+					} else {
521
+						$success = $results[0] && $results[1];
522
+					}
523
+				}
524
+			} // else
525
+
526
+			// There are two conditions where we can already determine whether
527
+			// or not this filter succeeds.
528
+			if ('anyof' === $test && $success) {
529
+				// Destroy circular references to PHP will GC the object.
530
+				$vcard->destroy();
531
+
532
+				return true;
533
+			}
534
+			if ('allof' === $test && !$success) {
535
+				// Destroy circular references to PHP will GC the object.
536
+				$vcard->destroy();
537
+
538
+				return false;
539
+			}
540
+		} // foreach
541
+
542
+		// Destroy circular references to PHP will GC the object.
543
+		$vcard->destroy();
544
+
545
+		// If we got all the way here, it means we haven't been able to
546
+		// determine early if the test failed or not.
547
+		//
548
+		// This implies for 'anyof' that the test failed, and for 'allof' that
549
+		// we succeeded. Sounds weird, but makes sense.
550
+		return 'allof' === $test;
551
+	}
552
+
553
+	/**
554
+	 * Validates if a param-filter can be applied to a specific property.
555
+	 *
556
+	 * @todo currently we're only validating the first parameter of the passed
557
+	 *       property. Any subsequence parameters with the same name are
558
+	 *       ignored.
559
+	 *
560
+	 * @param string $test
561
+	 *
562
+	 * @return bool
563
+	 */
564
+	protected function validateParamFilters(array $vProperties, array $filters, $test)
565
+	{
566
+		foreach ($filters as $filter) {
567
+			$isDefined = false;
568
+			foreach ($vProperties as $vProperty) {
569
+				$isDefined = isset($vProperty[$filter['name']]);
570
+				if ($isDefined) {
571
+					break;
572
+				}
573
+			}
574
+
575
+			if ($filter['is-not-defined']) {
576
+				if ($isDefined) {
577
+					$success = false;
578
+				} else {
579
+					$success = true;
580
+				}
581
+
582
+				// If there's no text-match, we can just check for existence
583
+			} elseif (!$filter['text-match'] || !$isDefined) {
584
+				$success = $isDefined;
585
+			} else {
586
+				$success = false;
587
+				foreach ($vProperties as $vProperty) {
588
+					// If we got all the way here, we'll need to validate the
589
+					// text-match filter.
590
+					if (isset($vProperty[$filter['name']])) {
591
+						$success = DAV\StringUtil::textMatch(
592
+							$vProperty[$filter['name']]->getValue(),
593
+							$filter['text-match']['value'],
594
+							$filter['text-match']['collation'],
595
+							$filter['text-match']['match-type']
596
+						);
597
+						if ($filter['text-match']['negate-condition']) {
598
+							$success = !$success;
599
+						}
600
+					}
601
+					if ($success) {
602
+						break;
603
+					}
604
+				}
605
+			} // else
606
+
607
+			// There are two conditions where we can already determine whether
608
+			// or not this filter succeeds.
609
+			if ('anyof' === $test && $success) {
610
+				return true;
611
+			}
612
+			if ('allof' === $test && !$success) {
613
+				return false;
614
+			}
615
+		}
616
+
617
+		// If we got all the way here, it means we haven't been able to
618
+		// determine early if the test failed or not.
619
+		//
620
+		// This implies for 'anyof' that the test failed, and for 'allof' that
621
+		// we succeeded. Sounds weird, but makes sense.
622
+		return 'allof' === $test;
623
+	}
624
+
625
+	/**
626
+	 * Validates if a text-filter can be applied to a specific property.
627
+	 *
628
+	 * @param string $test
629
+	 *
630
+	 * @return bool
631
+	 */
632
+	protected function validateTextMatches(array $texts, array $filters, $test)
633
+	{
634
+		foreach ($filters as $filter) {
635
+			$success = false;
636
+			foreach ($texts as $haystack) {
637
+				$success = DAV\StringUtil::textMatch($haystack, $filter['value'], $filter['collation'], $filter['match-type']);
638
+				if ($filter['negate-condition']) {
639
+					$success = !$success;
640
+				}
641
+
642
+				// Breaking on the first match
643
+				if ($success) {
644
+					break;
645
+				}
646
+			}
647
+
648
+			if ($success && 'anyof' === $test) {
649
+				return true;
650
+			}
651
+
652
+			if (!$success && 'allof' == $test) {
653
+				return false;
654
+			}
655
+		}
656
+
657
+		// If we got all the way here, it means we haven't been able to
658
+		// determine early if the test failed or not.
659
+		//
660
+		// This implies for 'anyof' that the test failed, and for 'allof' that
661
+		// we succeeded. Sounds weird, but makes sense.
662
+		return 'allof' === $test;
663
+	}
664
+
665
+	/**
666
+	 * This event is triggered when fetching properties.
667
+	 *
668
+	 * This event is scheduled late in the process, after most work for
669
+	 * propfind has been done.
670
+	 */
671
+	public function propFindLate(DAV\PropFind $propFind, DAV\INode $node)
672
+	{
673
+		// If the request was made using the SOGO connector, we must rewrite
674
+		// the content-type property. By default SabreDAV will send back
675
+		// text/x-vcard; charset=utf-8, but for SOGO we must strip that last
676
+		// part.
677
+		if (false === strpos((string) $this->server->httpRequest->getHeader('User-Agent'), 'Thunderbird')) {
678
+			return;
679
+		}
680
+		$contentType = $propFind->get('{DAV:}getcontenttype');
681
+		if (null !== $contentType) {
682
+			list($part) = explode(';', $contentType);
683
+			if ('text/x-vcard' === $part || 'text/vcard' === $part) {
684
+				$propFind->set('{DAV:}getcontenttype', 'text/x-vcard');
685
+			}
686
+		}
687
+	}
688
+
689
+	/**
690
+	 * This method is used to generate HTML output for the
691
+	 * Sabre\DAV\Browser\Plugin. This allows us to generate an interface users
692
+	 * can use to create new addressbooks.
693
+	 *
694
+	 * @param string $output
695
+	 *
696
+	 * @return bool
697
+	 */
698
+	public function htmlActionsPanel(DAV\INode $node, &$output)
699
+	{
700
+		if (!$node instanceof AddressBookHome) {
701
+			return;
702
+		}
703
+
704
+		$output .= '<tr><td colspan="2"><form method="post" action="">
705 705
             <h3>Create new address book</h3>
706 706
             <input type="hidden" name="sabreAction" value="mkcol" />
707 707
             <input type="hidden" name="resourceType" value="{DAV:}collection,{'.self::NS_CARDDAV.'}addressbook" />
@@ -711,176 +711,176 @@  discard block
 block discarded – undo
711 711
             </form>
712 712
             </td></tr>';
713 713
 
714
-        return false;
715
-    }
716
-
717
-    /**
718
-     * This event is triggered after GET requests.
719
-     *
720
-     * This is used to transform data into jCal, if this was requested.
721
-     */
722
-    public function httpAfterGet(RequestInterface $request, ResponseInterface $response)
723
-    {
724
-        $contentType = $response->getHeader('Content-Type');
725
-        if (null === $contentType || false === strpos($contentType, 'text/vcard')) {
726
-            return;
727
-        }
728
-
729
-        $target = $this->negotiateVCard($request->getHeader('Accept'), $mimeType);
730
-
731
-        $newBody = $this->convertVCard(
732
-            $response->getBody(),
733
-            $target
734
-        );
735
-
736
-        $response->setBody($newBody);
737
-        $response->setHeader('Content-Type', $mimeType.'; charset=utf-8');
738
-        $response->setHeader('Content-Length', strlen($newBody));
739
-    }
740
-
741
-    /**
742
-     * This helper function performs the content-type negotiation for vcards.
743
-     *
744
-     * It will return one of the following strings:
745
-     * 1. vcard3
746
-     * 2. vcard4
747
-     * 3. jcard
748
-     *
749
-     * It defaults to vcard3.
750
-     *
751
-     * @param string $input
752
-     * @param string $mimeType
753
-     *
754
-     * @return string
755
-     */
756
-    protected function negotiateVCard($input, &$mimeType = null)
757
-    {
758
-        $result = HTTP\negotiateContentType(
759
-            $input,
760
-            [
761
-                // Most often used mime-type. Version 3
762
-                'text/x-vcard',
763
-                // The correct standard mime-type. Defaults to version 3 as
764
-                // well.
765
-                'text/vcard',
766
-                // vCard 4
767
-                'text/vcard; version=4.0',
768
-                // vCard 3
769
-                'text/vcard; version=3.0',
770
-                // jCard
771
-                'application/vcard+json',
772
-            ]
773
-        );
774
-
775
-        $mimeType = $result;
776
-        switch ($result) {
777
-            default:
778
-            case 'text/x-vcard':
779
-            case 'text/vcard':
780
-            case 'text/vcard; version=3.0':
781
-                $mimeType = 'text/vcard';
782
-
783
-                return 'vcard3';
784
-            case 'text/vcard; version=4.0':
785
-                return 'vcard4';
786
-            case 'application/vcard+json':
787
-                return 'jcard';
788
-
789
-        // @codeCoverageIgnoreStart
790
-        }
791
-        // @codeCoverageIgnoreEnd
792
-    }
793
-
794
-    /**
795
-     * Converts a vcard blob to a different version, or jcard.
796
-     *
797
-     * @param string|resource $data
798
-     * @param string          $target
799
-     * @param array           $propertiesFilter
800
-     *
801
-     * @return string
802
-     */
803
-    protected function convertVCard($data, $target, array $propertiesFilter = null)
804
-    {
805
-        if (is_resource($data)) {
806
-            $data = stream_get_contents($data);
807
-        }
808
-        $input = VObject\Reader::read($data);
809
-        if (!empty($propertiesFilter)) {
810
-            $propertiesFilter = array_merge(['UID', 'VERSION', 'FN'], $propertiesFilter);
811
-            $keys = array_unique(array_map(function ($child) {
812
-                return $child->name;
813
-            }, $input->children()));
814
-            $keys = array_diff($keys, $propertiesFilter);
815
-            foreach ($keys as $key) {
816
-                unset($input->$key);
817
-            }
818
-            $data = $input->serialize();
819
-        }
820
-        $output = null;
821
-        try {
822
-            switch ($target) {
823
-                default:
824
-                case 'vcard3':
825
-                    if (VObject\Document::VCARD30 === $input->getDocumentType()) {
826
-                        // Do nothing
827
-                        return $data;
828
-                    }
829
-                    $output = $input->convert(VObject\Document::VCARD30);
830
-
831
-                    return $output->serialize();
832
-                case 'vcard4':
833
-                    if (VObject\Document::VCARD40 === $input->getDocumentType()) {
834
-                        // Do nothing
835
-                        return $data;
836
-                    }
837
-                    $output = $input->convert(VObject\Document::VCARD40);
838
-
839
-                    return $output->serialize();
840
-                case 'jcard':
841
-                    $output = $input->convert(VObject\Document::VCARD40);
842
-
843
-                    return json_encode($output);
844
-            }
845
-        } finally {
846
-            // Destroy circular references to PHP will GC the object.
847
-            $input->destroy();
848
-            if (!is_null($output)) {
849
-                $output->destroy();
850
-            }
851
-        }
852
-    }
853
-
854
-    /**
855
-     * Returns a plugin name.
856
-     *
857
-     * Using this name other plugins will be able to access other plugins
858
-     * using DAV\Server::getPlugin
859
-     *
860
-     * @return string
861
-     */
862
-    public function getPluginName()
863
-    {
864
-        return 'carddav';
865
-    }
866
-
867
-    /**
868
-     * Returns a bunch of meta-data about the plugin.
869
-     *
870
-     * Providing this information is optional, and is mainly displayed by the
871
-     * Browser plugin.
872
-     *
873
-     * The description key in the returned array may contain html and will not
874
-     * be sanitized.
875
-     *
876
-     * @return array
877
-     */
878
-    public function getPluginInfo()
879
-    {
880
-        return [
881
-            'name' => $this->getPluginName(),
882
-            'description' => 'Adds support for CardDAV (rfc6352)',
883
-            'link' => 'http://sabre.io/dav/carddav/',
884
-        ];
885
-    }
714
+		return false;
715
+	}
716
+
717
+	/**
718
+	 * This event is triggered after GET requests.
719
+	 *
720
+	 * This is used to transform data into jCal, if this was requested.
721
+	 */
722
+	public function httpAfterGet(RequestInterface $request, ResponseInterface $response)
723
+	{
724
+		$contentType = $response->getHeader('Content-Type');
725
+		if (null === $contentType || false === strpos($contentType, 'text/vcard')) {
726
+			return;
727
+		}
728
+
729
+		$target = $this->negotiateVCard($request->getHeader('Accept'), $mimeType);
730
+
731
+		$newBody = $this->convertVCard(
732
+			$response->getBody(),
733
+			$target
734
+		);
735
+
736
+		$response->setBody($newBody);
737
+		$response->setHeader('Content-Type', $mimeType.'; charset=utf-8');
738
+		$response->setHeader('Content-Length', strlen($newBody));
739
+	}
740
+
741
+	/**
742
+	 * This helper function performs the content-type negotiation for vcards.
743
+	 *
744
+	 * It will return one of the following strings:
745
+	 * 1. vcard3
746
+	 * 2. vcard4
747
+	 * 3. jcard
748
+	 *
749
+	 * It defaults to vcard3.
750
+	 *
751
+	 * @param string $input
752
+	 * @param string $mimeType
753
+	 *
754
+	 * @return string
755
+	 */
756
+	protected function negotiateVCard($input, &$mimeType = null)
757
+	{
758
+		$result = HTTP\negotiateContentType(
759
+			$input,
760
+			[
761
+				// Most often used mime-type. Version 3
762
+				'text/x-vcard',
763
+				// The correct standard mime-type. Defaults to version 3 as
764
+				// well.
765
+				'text/vcard',
766
+				// vCard 4
767
+				'text/vcard; version=4.0',
768
+				// vCard 3
769
+				'text/vcard; version=3.0',
770
+				// jCard
771
+				'application/vcard+json',
772
+			]
773
+		);
774
+
775
+		$mimeType = $result;
776
+		switch ($result) {
777
+			default:
778
+			case 'text/x-vcard':
779
+			case 'text/vcard':
780
+			case 'text/vcard; version=3.0':
781
+				$mimeType = 'text/vcard';
782
+
783
+				return 'vcard3';
784
+			case 'text/vcard; version=4.0':
785
+				return 'vcard4';
786
+			case 'application/vcard+json':
787
+				return 'jcard';
788
+
789
+		// @codeCoverageIgnoreStart
790
+		}
791
+		// @codeCoverageIgnoreEnd
792
+	}
793
+
794
+	/**
795
+	 * Converts a vcard blob to a different version, or jcard.
796
+	 *
797
+	 * @param string|resource $data
798
+	 * @param string          $target
799
+	 * @param array           $propertiesFilter
800
+	 *
801
+	 * @return string
802
+	 */
803
+	protected function convertVCard($data, $target, array $propertiesFilter = null)
804
+	{
805
+		if (is_resource($data)) {
806
+			$data = stream_get_contents($data);
807
+		}
808
+		$input = VObject\Reader::read($data);
809
+		if (!empty($propertiesFilter)) {
810
+			$propertiesFilter = array_merge(['UID', 'VERSION', 'FN'], $propertiesFilter);
811
+			$keys = array_unique(array_map(function ($child) {
812
+				return $child->name;
813
+			}, $input->children()));
814
+			$keys = array_diff($keys, $propertiesFilter);
815
+			foreach ($keys as $key) {
816
+				unset($input->$key);
817
+			}
818
+			$data = $input->serialize();
819
+		}
820
+		$output = null;
821
+		try {
822
+			switch ($target) {
823
+				default:
824
+				case 'vcard3':
825
+					if (VObject\Document::VCARD30 === $input->getDocumentType()) {
826
+						// Do nothing
827
+						return $data;
828
+					}
829
+					$output = $input->convert(VObject\Document::VCARD30);
830
+
831
+					return $output->serialize();
832
+				case 'vcard4':
833
+					if (VObject\Document::VCARD40 === $input->getDocumentType()) {
834
+						// Do nothing
835
+						return $data;
836
+					}
837
+					$output = $input->convert(VObject\Document::VCARD40);
838
+
839
+					return $output->serialize();
840
+				case 'jcard':
841
+					$output = $input->convert(VObject\Document::VCARD40);
842
+
843
+					return json_encode($output);
844
+			}
845
+		} finally {
846
+			// Destroy circular references to PHP will GC the object.
847
+			$input->destroy();
848
+			if (!is_null($output)) {
849
+				$output->destroy();
850
+			}
851
+		}
852
+	}
853
+
854
+	/**
855
+	 * Returns a plugin name.
856
+	 *
857
+	 * Using this name other plugins will be able to access other plugins
858
+	 * using DAV\Server::getPlugin
859
+	 *
860
+	 * @return string
861
+	 */
862
+	public function getPluginName()
863
+	{
864
+		return 'carddav';
865
+	}
866
+
867
+	/**
868
+	 * Returns a bunch of meta-data about the plugin.
869
+	 *
870
+	 * Providing this information is optional, and is mainly displayed by the
871
+	 * Browser plugin.
872
+	 *
873
+	 * The description key in the returned array may contain html and will not
874
+	 * be sanitized.
875
+	 *
876
+	 * @return array
877
+	 */
878
+	public function getPluginInfo()
879
+	{
880
+		return [
881
+			'name' => $this->getPluginName(),
882
+			'description' => 'Adds support for CardDAV (rfc6352)',
883
+			'link' => 'http://sabre.io/dav/carddav/',
884
+		];
885
+	}
886 886
 }
Please login to merge, or discard this patch.