Completed
Pull Request — developer (#4001)
by Thom
542:26 queued 508:45
created
libraries/SabreDAV/CalDAV/Backend/PDO.php 5 patches
Unused Use Statements   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -2,10 +2,10 @@
 block discarded – undo
2 2
 
3 3
 namespace Sabre\CalDAV\Backend;
4 4
 
5
-use Sabre\VObject;
6 5
 use Sabre\CalDAV;
7 6
 use Sabre\DAV;
8 7
 use Sabre\DAV\Exception\Forbidden;
8
+use Sabre\VObject;
9 9
 
10 10
 /**
11 11
  * PDO CalDAV backend
Please login to merge, or discard this patch.
Braces   +12 added lines, -4 removed lines patch added patch discarded remove patch
@@ -398,7 +398,9 @@  discard block
 block discarded – undo
398 398
         $stmt->execute([$calendarId, $objectUri]);
399 399
         $row = $stmt->fetch(\PDO::FETCH_ASSOC);
400 400
 
401
-        if (!$row) return null;
401
+        if (!$row) {
402
+        	return null;
403
+        }
402 404
 
403 405
         return [
404 406
             'id'            => $row['id'],
@@ -862,7 +864,9 @@  discard block
 block discarded – undo
862 864
         $stmt->execute([ $calendarId ]);
863 865
         $currentToken = $stmt->fetchColumn(0);
864 866
 
865
-        if (is_null($currentToken)) return null;
867
+        if (is_null($currentToken)) {
868
+        	return null;
869
+        }
866 870
 
867 871
         $result = [
868 872
             'syncToken' => $currentToken,
@@ -874,7 +878,9 @@  discard block
 block discarded – undo
874 878
         if ($syncToken) {
875 879
 
876 880
             $query = sprintf('SELECT uri, operation FROM %s WHERE synctoken >= ? && synctoken < ? && calendarid = ? ORDER BY synctoken', $this->calendarChangesTableName);
877
-            if ($limit > 0) $query .= " LIMIT " . (int)$limit;
881
+            if ($limit > 0) {
882
+            	$query .= " LIMIT " . (int)$limit;
883
+            }
878 884
 
879 885
             // Fetching all changes
880 886
             $stmt = $this->pdo->prepare($query);
@@ -1150,7 +1156,9 @@  discard block
 block discarded – undo
1150 1156
         $stmt->execute([$principalUri, $objectUri]);
1151 1157
         $row = $stmt->fetch(\PDO::FETCH_ASSOC);
1152 1158
 
1153
-        if (!$row) return null;
1159
+        if (!$row) {
1160
+        	return null;
1161
+        }
1154 1162
 
1155 1163
         return [
1156 1164
             'uri'          => $row['uri'],
Please login to merge, or discard this patch.
Indentation   +1147 added lines, -1147 removed lines patch added patch discarded remove patch
@@ -19,763 +19,763 @@  discard block
 block discarded – undo
19 19
  */
20 20
 class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, SchedulingSupport {
21 21
 
22
-    /**
23
-     * We need to specify a max date, because we need to stop *somewhere*
24
-     *
25
-     * On 32 bit system the maximum for a signed integer is 2147483647, so
26
-     * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
27
-     * in 2038-01-19 to avoid problems when the date is converted
28
-     * to a unix timestamp.
29
-     */
30
-    const MAX_DATE = '2038-01-01';
31
-
32
-    /**
33
-     * pdo
34
-     *
35
-     * @var \PDO
36
-     */
37
-    protected $pdo;
38
-
39
-    /**
40
-     * The table name that will be used for calendars
41
-     *
42
-     * @var string
43
-     */
44
-    public $calendarTableName = 'calendars';
45
-
46
-    /**
47
-     * The table name that will be used for calendar objects
48
-     *
49
-     * @var string
50
-     */
51
-    public $calendarObjectTableName = 'calendarobjects';
52
-
53
-    /**
54
-     * The table name that will be used for tracking changes in calendars.
55
-     *
56
-     * @var string
57
-     */
58
-    public $calendarChangesTableName = 'calendarchanges';
59
-
60
-    /**
61
-     * The table name that will be used inbox items.
62
-     *
63
-     * @var string
64
-     */
65
-    public $schedulingObjectTableName = 'schedulingobjects';
66
-
67
-    /**
68
-     * The table name that will be used for calendar subscriptions.
69
-     *
70
-     * @var string
71
-     */
72
-    public $calendarSubscriptionsTableName = 'calendarsubscriptions';
73
-
74
-    /**
75
-     * List of CalDAV properties, and how they map to database fieldnames
76
-     * Add your own properties by simply adding on to this array.
77
-     *
78
-     * Note that only string-based properties are supported here.
79
-     *
80
-     * @var array
81
-     */
82
-    public $propertyMap = [
83
-        '{DAV:}displayname'                                   => 'displayname',
84
-        '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
85
-        '{urn:ietf:params:xml:ns:caldav}calendar-timezone'    => 'timezone',
86
-        '{http://apple.com/ns/ical/}calendar-order'           => 'calendarorder',
87
-        '{http://apple.com/ns/ical/}calendar-color'           => 'calendarcolor',
88
-    ];
89
-
90
-    /**
91
-     * List of subscription properties, and how they map to database fieldnames.
92
-     *
93
-     * @var array
94
-     */
95
-    public $subscriptionPropertyMap = [
96
-        '{DAV:}displayname'                                           => 'displayname',
97
-        '{http://apple.com/ns/ical/}refreshrate'                      => 'refreshrate',
98
-        '{http://apple.com/ns/ical/}calendar-order'                   => 'calendarorder',
99
-        '{http://apple.com/ns/ical/}calendar-color'                   => 'calendarcolor',
100
-        '{http://calendarserver.org/ns/}subscribed-strip-todos'       => 'striptodos',
101
-        '{http://calendarserver.org/ns/}subscribed-strip-alarms'      => 'stripalarms',
102
-        '{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
103
-    ];
104
-
105
-    /**
106
-     * Creates the backend
107
-     *
108
-     * @param \PDO $pdo
109
-     */
110
-    public function __construct(\PDO $pdo) {
111
-
112
-        $this->pdo = $pdo;
113
-
114
-    }
115
-
116
-    /**
117
-     * Returns a list of calendars for a principal.
118
-     *
119
-     * Every project is an array with the following keys:
120
-     *  * id, a unique id that will be used by other functions to modify the
121
-     *    calendar. This can be the same as the uri or a database key.
122
-     *  * uri. This is just the 'base uri' or 'filename' of the calendar.
123
-     *  * principaluri. The owner of the calendar. Almost always the same as
124
-     *    principalUri passed to this method.
125
-     *
126
-     * Furthermore it can contain webdav properties in clark notation. A very
127
-     * common one is '{DAV:}displayname'.
128
-     *
129
-     * Many clients also require:
130
-     * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
131
-     * For this property, you can just return an instance of
132
-     * Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet.
133
-     *
134
-     * If you return {http://sabredav.org/ns}read-only and set the value to 1,
135
-     * ACL will automatically be put in read-only mode.
136
-     *
137
-     * @param string $principalUri
138
-     * @return array
139
-     */
140
-    public function getCalendarsForUser($principalUri) {
141
-
142
-        $fields = array_values($this->propertyMap);
143
-        $fields[] = 'id';
144
-        $fields[] = 'uri';
145
-        $fields[] = 'synctoken';
146
-        $fields[] = 'components';
147
-        $fields[] = 'principaluri';
148
-        $fields[] = 'transparent';
149
-
150
-        // Making fields a comma-delimited list
151
-        $fields = implode(', ', $fields);
22
+	/**
23
+	 * We need to specify a max date, because we need to stop *somewhere*
24
+	 *
25
+	 * On 32 bit system the maximum for a signed integer is 2147483647, so
26
+	 * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
27
+	 * in 2038-01-19 to avoid problems when the date is converted
28
+	 * to a unix timestamp.
29
+	 */
30
+	const MAX_DATE = '2038-01-01';
31
+
32
+	/**
33
+	 * pdo
34
+	 *
35
+	 * @var \PDO
36
+	 */
37
+	protected $pdo;
38
+
39
+	/**
40
+	 * The table name that will be used for calendars
41
+	 *
42
+	 * @var string
43
+	 */
44
+	public $calendarTableName = 'calendars';
45
+
46
+	/**
47
+	 * The table name that will be used for calendar objects
48
+	 *
49
+	 * @var string
50
+	 */
51
+	public $calendarObjectTableName = 'calendarobjects';
52
+
53
+	/**
54
+	 * The table name that will be used for tracking changes in calendars.
55
+	 *
56
+	 * @var string
57
+	 */
58
+	public $calendarChangesTableName = 'calendarchanges';
59
+
60
+	/**
61
+	 * The table name that will be used inbox items.
62
+	 *
63
+	 * @var string
64
+	 */
65
+	public $schedulingObjectTableName = 'schedulingobjects';
66
+
67
+	/**
68
+	 * The table name that will be used for calendar subscriptions.
69
+	 *
70
+	 * @var string
71
+	 */
72
+	public $calendarSubscriptionsTableName = 'calendarsubscriptions';
73
+
74
+	/**
75
+	 * List of CalDAV properties, and how they map to database fieldnames
76
+	 * Add your own properties by simply adding on to this array.
77
+	 *
78
+	 * Note that only string-based properties are supported here.
79
+	 *
80
+	 * @var array
81
+	 */
82
+	public $propertyMap = [
83
+		'{DAV:}displayname'                                   => 'displayname',
84
+		'{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
85
+		'{urn:ietf:params:xml:ns:caldav}calendar-timezone'    => 'timezone',
86
+		'{http://apple.com/ns/ical/}calendar-order'           => 'calendarorder',
87
+		'{http://apple.com/ns/ical/}calendar-color'           => 'calendarcolor',
88
+	];
89
+
90
+	/**
91
+	 * List of subscription properties, and how they map to database fieldnames.
92
+	 *
93
+	 * @var array
94
+	 */
95
+	public $subscriptionPropertyMap = [
96
+		'{DAV:}displayname'                                           => 'displayname',
97
+		'{http://apple.com/ns/ical/}refreshrate'                      => 'refreshrate',
98
+		'{http://apple.com/ns/ical/}calendar-order'                   => 'calendarorder',
99
+		'{http://apple.com/ns/ical/}calendar-color'                   => 'calendarcolor',
100
+		'{http://calendarserver.org/ns/}subscribed-strip-todos'       => 'striptodos',
101
+		'{http://calendarserver.org/ns/}subscribed-strip-alarms'      => 'stripalarms',
102
+		'{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
103
+	];
104
+
105
+	/**
106
+	 * Creates the backend
107
+	 *
108
+	 * @param \PDO $pdo
109
+	 */
110
+	public function __construct(\PDO $pdo) {
111
+
112
+		$this->pdo = $pdo;
113
+
114
+	}
115
+
116
+	/**
117
+	 * Returns a list of calendars for a principal.
118
+	 *
119
+	 * Every project is an array with the following keys:
120
+	 *  * id, a unique id that will be used by other functions to modify the
121
+	 *    calendar. This can be the same as the uri or a database key.
122
+	 *  * uri. This is just the 'base uri' or 'filename' of the calendar.
123
+	 *  * principaluri. The owner of the calendar. Almost always the same as
124
+	 *    principalUri passed to this method.
125
+	 *
126
+	 * Furthermore it can contain webdav properties in clark notation. A very
127
+	 * common one is '{DAV:}displayname'.
128
+	 *
129
+	 * Many clients also require:
130
+	 * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
131
+	 * For this property, you can just return an instance of
132
+	 * Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet.
133
+	 *
134
+	 * If you return {http://sabredav.org/ns}read-only and set the value to 1,
135
+	 * ACL will automatically be put in read-only mode.
136
+	 *
137
+	 * @param string $principalUri
138
+	 * @return array
139
+	 */
140
+	public function getCalendarsForUser($principalUri) {
141
+
142
+		$fields = array_values($this->propertyMap);
143
+		$fields[] = 'id';
144
+		$fields[] = 'uri';
145
+		$fields[] = 'synctoken';
146
+		$fields[] = 'components';
147
+		$fields[] = 'principaluri';
148
+		$fields[] = 'transparent';
149
+
150
+		// Making fields a comma-delimited list
151
+		$fields = implode(', ', $fields);
152 152
 				$stmt = $this->pdo->prepare(
153 153
 <<<SQL
154 154
 		SELECT $fields FROM {$this->calendarTableName}
155 155
 		WHERE principaluri = ? ORDER BY calendarorder ASC
156 156
 SQL
157 157
 		);
158
-        $stmt->execute([$principalUri]);
159
-
160
-        $calendars = [];
161
-        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
162
-
163
-            $components = [];
164
-            if ($row['components']) {
165
-                $components = explode(',', $row['components']);
166
-            }
167
-
168
-            $calendar = [
169
-                'id'                                                                 => $row['id'],
170
-                'uri'                                                                => $row['uri'],
171
-                'principaluri'                                                       => $row['principaluri'],
172
-                '{' . CalDAV\Plugin::NS_CALENDARSERVER . '}getctag'                  => 'http://sabre.io/ns/sync/' . ($row['synctoken'] ? $row['synctoken'] : '0'),
173
-                '{http://sabredav.org/ns}sync-token'                                 => $row['synctoken'] ? $row['synctoken'] : '0',
174
-                '{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet($components),
175
-                '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp'         => new CalDAV\Xml\Property\ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
176
-            ];
177
-
178
-
179
-            foreach ($this->propertyMap as $xmlName => $dbName) {
180
-                $calendar[$xmlName] = $row[$dbName];
181
-            }
182
-
183
-            $calendars[] = $calendar;
184
-
185
-        }
186
-
187
-        return $calendars;
188
-
189
-    }
190
-
191
-    /**
192
-     * Creates a new calendar for a principal.
193
-     *
194
-     * If the creation was a success, an id must be returned that can be used
195
-     * to reference this calendar in other methods, such as updateCalendar.
196
-     *
197
-     * @param string $principalUri
198
-     * @param string $calendarUri
199
-     * @param array $properties
200
-     * @return string
201
-     */
202
-    public function createCalendar($principalUri, $calendarUri, array $properties) {
203
-
204
-        $fieldNames = [
205
-            'principaluri',
206
-            'uri',
207
-            'synctoken',
208
-            'transparent',
209
-        ];
210
-        $values = [
211
-            ':principaluri' => $principalUri,
212
-            ':uri'          => $calendarUri,
213
-            ':synctoken'    => 1,
214
-            ':transparent'  => 0,
215
-        ];
216
-
217
-        // Default value
218
-        $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
219
-        $fieldNames[] = 'components';
220
-        if (!isset($properties[$sccs])) {
221
-            $values[':components'] = 'VEVENT,VTODO';
222
-        } else {
223
-            if (!($properties[$sccs] instanceof CalDAV\Xml\Property\SupportedCalendarComponentSet)) {
224
-                throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet');
225
-            }
226
-            $values[':components'] = implode(',', $properties[$sccs]->getValue());
227
-        }
228
-        $transp = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp';
229
-        if (isset($properties[$transp])) {
230
-            $values[':transparent'] = $properties[$transp]->getValue() === 'transparent';
231
-        }
232
-
233
-        foreach ($this->propertyMap as $xmlName => $dbName) {
234
-            if (isset($properties[$xmlName])) {
235
-
236
-                $values[':' . $dbName] = $properties[$xmlName];
237
-                $fieldNames[] = $dbName;
238
-            }
239
-        }
240
-
241
-        $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")");
242
-        $stmt->execute($values);
243
-
244
-        return $this->pdo->lastInsertId();
245
-
246
-    }
247
-
248
-    /**
249
-     * Updates properties for a calendar.
250
-     *
251
-     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
252
-     * To do the actual updates, you must tell this object which properties
253
-     * you're going to process with the handle() method.
254
-     *
255
-     * Calling the handle method is like telling the PropPatch object "I
256
-     * promise I can handle updating this property".
257
-     *
258
-     * Read the PropPatch documenation for more info and examples.
259
-     *
260
-     * @param string $calendarId
261
-     * @param \Sabre\DAV\PropPatch $propPatch
262
-     * @return void
263
-     */
264
-    public function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch) {
265
-
266
-        $supportedProperties = array_keys($this->propertyMap);
267
-        $supportedProperties[] = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp';
268
-
269
-        $propPatch->handle($supportedProperties, function($mutations) use ($calendarId) {
270
-            $newValues = [];
271
-            foreach ($mutations as $propertyName => $propertyValue) {
272
-
273
-                switch ($propertyName) {
274
-                    case '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' :
275
-                        $fieldName = 'transparent';
276
-                        $newValues[$fieldName] = $propertyValue->getValue() === 'transparent';
277
-                        break;
278
-                    default :
279
-                        $fieldName = $this->propertyMap[$propertyName];
280
-                        $newValues[$fieldName] = $propertyValue;
281
-                        break;
282
-                }
283
-
284
-            }
285
-            $valuesSql = [];
286
-            foreach ($newValues as $fieldName => $value) {
287
-                $valuesSql[] = $fieldName . ' = ?';
288
-            }
158
+		$stmt->execute([$principalUri]);
159
+
160
+		$calendars = [];
161
+		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
162
+
163
+			$components = [];
164
+			if ($row['components']) {
165
+				$components = explode(',', $row['components']);
166
+			}
167
+
168
+			$calendar = [
169
+				'id'                                                                 => $row['id'],
170
+				'uri'                                                                => $row['uri'],
171
+				'principaluri'                                                       => $row['principaluri'],
172
+				'{' . CalDAV\Plugin::NS_CALENDARSERVER . '}getctag'                  => 'http://sabre.io/ns/sync/' . ($row['synctoken'] ? $row['synctoken'] : '0'),
173
+				'{http://sabredav.org/ns}sync-token'                                 => $row['synctoken'] ? $row['synctoken'] : '0',
174
+				'{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet($components),
175
+				'{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp'         => new CalDAV\Xml\Property\ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
176
+			];
177
+
178
+
179
+			foreach ($this->propertyMap as $xmlName => $dbName) {
180
+				$calendar[$xmlName] = $row[$dbName];
181
+			}
182
+
183
+			$calendars[] = $calendar;
184
+
185
+		}
186
+
187
+		return $calendars;
188
+
189
+	}
190
+
191
+	/**
192
+	 * Creates a new calendar for a principal.
193
+	 *
194
+	 * If the creation was a success, an id must be returned that can be used
195
+	 * to reference this calendar in other methods, such as updateCalendar.
196
+	 *
197
+	 * @param string $principalUri
198
+	 * @param string $calendarUri
199
+	 * @param array $properties
200
+	 * @return string
201
+	 */
202
+	public function createCalendar($principalUri, $calendarUri, array $properties) {
203
+
204
+		$fieldNames = [
205
+			'principaluri',
206
+			'uri',
207
+			'synctoken',
208
+			'transparent',
209
+		];
210
+		$values = [
211
+			':principaluri' => $principalUri,
212
+			':uri'          => $calendarUri,
213
+			':synctoken'    => 1,
214
+			':transparent'  => 0,
215
+		];
216
+
217
+		// Default value
218
+		$sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
219
+		$fieldNames[] = 'components';
220
+		if (!isset($properties[$sccs])) {
221
+			$values[':components'] = 'VEVENT,VTODO';
222
+		} else {
223
+			if (!($properties[$sccs] instanceof CalDAV\Xml\Property\SupportedCalendarComponentSet)) {
224
+				throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet');
225
+			}
226
+			$values[':components'] = implode(',', $properties[$sccs]->getValue());
227
+		}
228
+		$transp = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp';
229
+		if (isset($properties[$transp])) {
230
+			$values[':transparent'] = $properties[$transp]->getValue() === 'transparent';
231
+		}
232
+
233
+		foreach ($this->propertyMap as $xmlName => $dbName) {
234
+			if (isset($properties[$xmlName])) {
235
+
236
+				$values[':' . $dbName] = $properties[$xmlName];
237
+				$fieldNames[] = $dbName;
238
+			}
239
+		}
240
+
241
+		$stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")");
242
+		$stmt->execute($values);
243
+
244
+		return $this->pdo->lastInsertId();
245
+
246
+	}
247
+
248
+	/**
249
+	 * Updates properties for a calendar.
250
+	 *
251
+	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
252
+	 * To do the actual updates, you must tell this object which properties
253
+	 * you're going to process with the handle() method.
254
+	 *
255
+	 * Calling the handle method is like telling the PropPatch object "I
256
+	 * promise I can handle updating this property".
257
+	 *
258
+	 * Read the PropPatch documenation for more info and examples.
259
+	 *
260
+	 * @param string $calendarId
261
+	 * @param \Sabre\DAV\PropPatch $propPatch
262
+	 * @return void
263
+	 */
264
+	public function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch) {
265
+
266
+		$supportedProperties = array_keys($this->propertyMap);
267
+		$supportedProperties[] = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp';
268
+
269
+		$propPatch->handle($supportedProperties, function($mutations) use ($calendarId) {
270
+			$newValues = [];
271
+			foreach ($mutations as $propertyName => $propertyValue) {
272
+
273
+				switch ($propertyName) {
274
+					case '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' :
275
+						$fieldName = 'transparent';
276
+						$newValues[$fieldName] = $propertyValue->getValue() === 'transparent';
277
+						break;
278
+					default :
279
+						$fieldName = $this->propertyMap[$propertyName];
280
+						$newValues[$fieldName] = $propertyValue;
281
+						break;
282
+				}
283
+
284
+			}
285
+			$valuesSql = [];
286
+			foreach ($newValues as $fieldName => $value) {
287
+				$valuesSql[] = $fieldName . ' = ?';
288
+			}
289 289
 			$query = sprintf('UPDATE %s SET %s WHERE id = ?', $this->calendarTableName, implode(', ', $valuesSql));
290
-            $stmt = $this->pdo->prepare($query);
291
-            $newValues['id'] = $calendarId;
292
-            $stmt->execute(array_values($newValues));
290
+			$stmt = $this->pdo->prepare($query);
291
+			$newValues['id'] = $calendarId;
292
+			$stmt->execute(array_values($newValues));
293 293
 
294
-            $this->addChange($calendarId, "", 2);
294
+			$this->addChange($calendarId, "", 2);
295 295
 
296
-            return true;
296
+			return true;
297 297
 
298
-        });
298
+		});
299 299
 
300
-    }
300
+	}
301 301
 
302
-    /**
303
-     * Delete a calendar and all it's objects
304
-     *
305
-     * @param string $calendarId
306
-     * @return void
307
-     */
308
-    public function deleteCalendar($calendarId) {
302
+	/**
303
+	 * Delete a calendar and all it's objects
304
+	 *
305
+	 * @param string $calendarId
306
+	 * @return void
307
+	 */
308
+	public function deleteCalendar($calendarId) {
309 309
 
310 310
 		$query = sprintf('DELETE FROM %s WHERE calendarid = ?', $this->calendarObjectTableName);
311
-        $stmt = $this->pdo->prepare($query);
312
-        $stmt->execute([$calendarId]);
311
+		$stmt = $this->pdo->prepare($query);
312
+		$stmt->execute([$calendarId]);
313 313
 
314 314
 		$query = sprintf('DELETE FROM %s WHERE id = ?', $this->calendarTableName);
315
-        $stmt = $this->pdo->prepare($query);
316
-        $stmt->execute([$calendarId]);
315
+		$stmt = $this->pdo->prepare($query);
316
+		$stmt->execute([$calendarId]);
317 317
 
318
-        $query = sprintf('DELETE FROM %s WHERE calendarid = ?', $this->calendarChangesTableName);
318
+		$query = sprintf('DELETE FROM %s WHERE calendarid = ?', $this->calendarChangesTableName);
319 319
 		$stmt = $this->pdo->prepare($query);
320
-        $stmt->execute([$calendarId]);
321
-
322
-    }
323
-
324
-    /**
325
-     * Returns all calendar objects within a calendar.
326
-     *
327
-     * Every item contains an array with the following keys:
328
-     *   * calendardata - The iCalendar-compatible calendar data
329
-     *   * uri - a unique key which will be used to construct the uri. This can
330
-     *     be any arbitrary string, but making sure it ends with '.ics' is a
331
-     *     good idea. This is only the basename, or filename, not the full
332
-     *     path.
333
-     *   * lastmodified - a timestamp of the last modification time
334
-     *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
335
-     *   '  "abcdef"')
336
-     *   * size - The size of the calendar objects, in bytes.
337
-     *   * component - optional, a string containing the type of object, such
338
-     *     as 'vevent' or 'vtodo'. If specified, this will be used to populate
339
-     *     the Content-Type header.
340
-     *
341
-     * Note that the etag is optional, but it's highly encouraged to return for
342
-     * speed reasons.
343
-     *
344
-     * The calendardata is also optional. If it's not returned
345
-     * 'getCalendarObject' will be called later, which *is* expected to return
346
-     * calendardata.
347
-     *
348
-     * If neither etag or size are specified, the calendardata will be
349
-     * used/fetched to determine these numbers. If both are specified the
350
-     * amount of times this is needed is reduced by a great degree.
351
-     *
352
-     * @param string $calendarId
353
-     * @return array
354
-     */
355
-    public function getCalendarObjects($calendarId) {
320
+		$stmt->execute([$calendarId]);
321
+
322
+	}
323
+
324
+	/**
325
+	 * Returns all calendar objects within a calendar.
326
+	 *
327
+	 * Every item contains an array with the following keys:
328
+	 *   * calendardata - The iCalendar-compatible calendar data
329
+	 *   * uri - a unique key which will be used to construct the uri. This can
330
+	 *     be any arbitrary string, but making sure it ends with '.ics' is a
331
+	 *     good idea. This is only the basename, or filename, not the full
332
+	 *     path.
333
+	 *   * lastmodified - a timestamp of the last modification time
334
+	 *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
335
+	 *   '  "abcdef"')
336
+	 *   * size - The size of the calendar objects, in bytes.
337
+	 *   * component - optional, a string containing the type of object, such
338
+	 *     as 'vevent' or 'vtodo'. If specified, this will be used to populate
339
+	 *     the Content-Type header.
340
+	 *
341
+	 * Note that the etag is optional, but it's highly encouraged to return for
342
+	 * speed reasons.
343
+	 *
344
+	 * The calendardata is also optional. If it's not returned
345
+	 * 'getCalendarObject' will be called later, which *is* expected to return
346
+	 * calendardata.
347
+	 *
348
+	 * If neither etag or size are specified, the calendardata will be
349
+	 * used/fetched to determine these numbers. If both are specified the
350
+	 * amount of times this is needed is reduced by a great degree.
351
+	 *
352
+	 * @param string $calendarId
353
+	 * @return array
354
+	 */
355
+	public function getCalendarObjects($calendarId) {
356 356
 		
357 357
 		$query = sprintf('SELECT id, uri, lastmodified, etag, calendarid, size, componenttype FROM %s WHERE calendarid = ?', $this->calendarObjectTableName);
358
-        $stmt = $this->pdo->prepare($query);
359
-        $stmt->execute([$calendarId]);
360
-
361
-        $result = [];
362
-        foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
363
-            $result[] = [
364
-                'id'           => $row['id'],
365
-                'uri'          => $row['uri'],
366
-                'lastmodified' => $row['lastmodified'],
367
-                'etag'         => '"' . $row['etag'] . '"',
368
-                'calendarid'   => $row['calendarid'],
369
-                'size'         => (int)$row['size'],
370
-                'component'    => strtolower($row['componenttype']),
371
-            ];
372
-        }
373
-
374
-        return $result;
375
-
376
-    }
377
-
378
-    /**
379
-     * Returns information from a single calendar object, based on it's object
380
-     * uri.
381
-     *
382
-     * The object uri is only the basename, or filename and not a full path.
383
-     *
384
-     * The returned array must have the same keys as getCalendarObjects. The
385
-     * 'calendardata' object is required here though, while it's not required
386
-     * for getCalendarObjects.
387
-     *
388
-     * This method must return null if the object did not exist.
389
-     *
390
-     * @param string $calendarId
391
-     * @param string $objectUri
392
-     * @return array|null
393
-     */
394
-    public function getCalendarObject($calendarId, $objectUri) {
358
+		$stmt = $this->pdo->prepare($query);
359
+		$stmt->execute([$calendarId]);
360
+
361
+		$result = [];
362
+		foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
363
+			$result[] = [
364
+				'id'           => $row['id'],
365
+				'uri'          => $row['uri'],
366
+				'lastmodified' => $row['lastmodified'],
367
+				'etag'         => '"' . $row['etag'] . '"',
368
+				'calendarid'   => $row['calendarid'],
369
+				'size'         => (int)$row['size'],
370
+				'component'    => strtolower($row['componenttype']),
371
+			];
372
+		}
373
+
374
+		return $result;
375
+
376
+	}
377
+
378
+	/**
379
+	 * Returns information from a single calendar object, based on it's object
380
+	 * uri.
381
+	 *
382
+	 * The object uri is only the basename, or filename and not a full path.
383
+	 *
384
+	 * The returned array must have the same keys as getCalendarObjects. The
385
+	 * 'calendardata' object is required here though, while it's not required
386
+	 * for getCalendarObjects.
387
+	 *
388
+	 * This method must return null if the object did not exist.
389
+	 *
390
+	 * @param string $calendarId
391
+	 * @param string $objectUri
392
+	 * @return array|null
393
+	 */
394
+	public function getCalendarObject($calendarId, $objectUri) {
395 395
 		
396 396
 		$query = sprintf('SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM %s WHERE calendarid = ? && uri = ?', $this->calendarObjectTableName);
397
-        $stmt = $this->pdo->prepare($query);
398
-        $stmt->execute([$calendarId, $objectUri]);
399
-        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
400
-
401
-        if (!$row) return null;
402
-
403
-        return [
404
-            'id'            => $row['id'],
405
-            'uri'           => $row['uri'],
406
-            'lastmodified'  => $row['lastmodified'],
407
-            'etag'          => '"' . $row['etag'] . '"',
408
-            'calendarid'    => $row['calendarid'],
409
-            'size'          => (int)$row['size'],
410
-            'calendardata'  => $row['calendardata'],
411
-            'component'     => strtolower($row['componenttype']),
412
-         ];
413
-
414
-    }
415
-
416
-    /**
417
-     * Returns a list of calendar objects.
418
-     *
419
-     * This method should work identical to getCalendarObject, but instead
420
-     * return all the calendar objects in the list as an array.
421
-     *
422
-     * If the backend supports this, it may allow for some speed-ups.
423
-     *
424
-     * @param mixed $calendarId
425
-     * @param array $uris
426
-     * @return array
427
-     */
428
-    public function getMultipleCalendarObjects($calendarId, array $uris) {
429
-
430
-        $query =sprintf('SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM %s WHERE calendarid = ? && uri IN (', $this->calendarObjectTableName);
431
-        // Inserting a whole bunch of question marks
432
-        $query .= implode(',', array_fill(0, count($uris), '?'));
433
-        $query .= ')';
434
-
435
-        $stmt = $this->pdo->prepare($query);
436
-        $stmt->execute(array_merge([$calendarId], $uris));
437
-
438
-        $result = [];
439
-        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
440
-
441
-            $result[] = [
442
-                'id'           => $row['id'],
443
-                'uri'          => $row['uri'],
444
-                'lastmodified' => $row['lastmodified'],
445
-                'etag'         => '"' . $row['etag'] . '"',
446
-                'calendarid'   => $row['calendarid'],
447
-                'size'         => (int)$row['size'],
448
-                'calendardata' => $row['calendardata'],
449
-                'component'    => strtolower($row['componenttype']),
450
-            ];
451
-
452
-        }
453
-        return $result;
454
-
455
-    }
456
-
457
-
458
-    /**
459
-     * Creates a new calendar object.
460
-     *
461
-     * The object uri is only the basename, or filename and not a full path.
462
-     *
463
-     * It is possible return an etag from this function, which will be used in
464
-     * the response to this PUT request. Note that the ETag must be surrounded
465
-     * by double-quotes.
466
-     *
467
-     * However, you should only really return this ETag if you don't mangle the
468
-     * calendar-data. If the result of a subsequent GET to this object is not
469
-     * the exact same as this request body, you should omit the ETag.
470
-     *
471
-     * @param mixed $calendarId
472
-     * @param string $objectUri
473
-     * @param string $calendarData
474
-     * @return string|null
475
-     */
476
-    public function createCalendarObject($calendarId, $objectUri, $calendarData) {
477
-
478
-        $extraData = $this->getDenormalizedData($calendarData);
479
-
480
-        $stmt = $this->pdo->prepare('INSERT INTO ' . $this->calendarObjectTableName . ' (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES (?,?,?,?,?,?,?,?,?,?)');
481
-        $stmt->execute([
482
-            $calendarId,
483
-            $objectUri,
484
-            $calendarData,
485
-            time(),
486
-            $extraData['etag'],
487
-            $extraData['size'],
488
-            $extraData['componentType'],
489
-            $extraData['firstOccurence'],
490
-            $extraData['lastOccurence'],
491
-            $extraData['uid'],
492
-        ]);
493
-        $this->addChange($calendarId, $objectUri, 1);
494
-
495
-        return '"' . $extraData['etag'] . '"';
496
-
497
-    }
498
-
499
-    /**
500
-     * Updates an existing calendarobject, based on it's uri.
501
-     *
502
-     * The object uri is only the basename, or filename and not a full path.
503
-     *
504
-     * It is possible return an etag from this function, which will be used in
505
-     * the response to this PUT request. Note that the ETag must be surrounded
506
-     * by double-quotes.
507
-     *
508
-     * However, you should only really return this ETag if you don't mangle the
509
-     * calendar-data. If the result of a subsequent GET to this object is not
510
-     * the exact same as this request body, you should omit the ETag.
511
-     *
512
-     * @param mixed $calendarId
513
-     * @param string $objectUri
514
-     * @param string $calendarData
515
-     * @return string|null
516
-     */
517
-    public function updateCalendarObject($calendarId, $objectUri, $calendarData) {
518
-
519
-        $extraData = $this->getDenormalizedData($calendarData);
397
+		$stmt = $this->pdo->prepare($query);
398
+		$stmt->execute([$calendarId, $objectUri]);
399
+		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
400
+
401
+		if (!$row) return null;
402
+
403
+		return [
404
+			'id'            => $row['id'],
405
+			'uri'           => $row['uri'],
406
+			'lastmodified'  => $row['lastmodified'],
407
+			'etag'          => '"' . $row['etag'] . '"',
408
+			'calendarid'    => $row['calendarid'],
409
+			'size'          => (int)$row['size'],
410
+			'calendardata'  => $row['calendardata'],
411
+			'component'     => strtolower($row['componenttype']),
412
+		 ];
413
+
414
+	}
415
+
416
+	/**
417
+	 * Returns a list of calendar objects.
418
+	 *
419
+	 * This method should work identical to getCalendarObject, but instead
420
+	 * return all the calendar objects in the list as an array.
421
+	 *
422
+	 * If the backend supports this, it may allow for some speed-ups.
423
+	 *
424
+	 * @param mixed $calendarId
425
+	 * @param array $uris
426
+	 * @return array
427
+	 */
428
+	public function getMultipleCalendarObjects($calendarId, array $uris) {
429
+
430
+		$query =sprintf('SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM %s WHERE calendarid = ? && uri IN (', $this->calendarObjectTableName);
431
+		// Inserting a whole bunch of question marks
432
+		$query .= implode(',', array_fill(0, count($uris), '?'));
433
+		$query .= ')';
434
+
435
+		$stmt = $this->pdo->prepare($query);
436
+		$stmt->execute(array_merge([$calendarId], $uris));
437
+
438
+		$result = [];
439
+		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
440
+
441
+			$result[] = [
442
+				'id'           => $row['id'],
443
+				'uri'          => $row['uri'],
444
+				'lastmodified' => $row['lastmodified'],
445
+				'etag'         => '"' . $row['etag'] . '"',
446
+				'calendarid'   => $row['calendarid'],
447
+				'size'         => (int)$row['size'],
448
+				'calendardata' => $row['calendardata'],
449
+				'component'    => strtolower($row['componenttype']),
450
+			];
451
+
452
+		}
453
+		return $result;
454
+
455
+	}
456
+
457
+
458
+	/**
459
+	 * Creates a new calendar object.
460
+	 *
461
+	 * The object uri is only the basename, or filename and not a full path.
462
+	 *
463
+	 * It is possible return an etag from this function, which will be used in
464
+	 * the response to this PUT request. Note that the ETag must be surrounded
465
+	 * by double-quotes.
466
+	 *
467
+	 * However, you should only really return this ETag if you don't mangle the
468
+	 * calendar-data. If the result of a subsequent GET to this object is not
469
+	 * the exact same as this request body, you should omit the ETag.
470
+	 *
471
+	 * @param mixed $calendarId
472
+	 * @param string $objectUri
473
+	 * @param string $calendarData
474
+	 * @return string|null
475
+	 */
476
+	public function createCalendarObject($calendarId, $objectUri, $calendarData) {
477
+
478
+		$extraData = $this->getDenormalizedData($calendarData);
479
+
480
+		$stmt = $this->pdo->prepare('INSERT INTO ' . $this->calendarObjectTableName . ' (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES (?,?,?,?,?,?,?,?,?,?)');
481
+		$stmt->execute([
482
+			$calendarId,
483
+			$objectUri,
484
+			$calendarData,
485
+			time(),
486
+			$extraData['etag'],
487
+			$extraData['size'],
488
+			$extraData['componentType'],
489
+			$extraData['firstOccurence'],
490
+			$extraData['lastOccurence'],
491
+			$extraData['uid'],
492
+		]);
493
+		$this->addChange($calendarId, $objectUri, 1);
494
+
495
+		return '"' . $extraData['etag'] . '"';
496
+
497
+	}
498
+
499
+	/**
500
+	 * Updates an existing calendarobject, based on it's uri.
501
+	 *
502
+	 * The object uri is only the basename, or filename and not a full path.
503
+	 *
504
+	 * It is possible return an etag from this function, which will be used in
505
+	 * the response to this PUT request. Note that the ETag must be surrounded
506
+	 * by double-quotes.
507
+	 *
508
+	 * However, you should only really return this ETag if you don't mangle the
509
+	 * calendar-data. If the result of a subsequent GET to this object is not
510
+	 * the exact same as this request body, you should omit the ETag.
511
+	 *
512
+	 * @param mixed $calendarId
513
+	 * @param string $objectUri
514
+	 * @param string $calendarData
515
+	 * @return string|null
516
+	 */
517
+	public function updateCalendarObject($calendarId, $objectUri, $calendarData) {
518
+
519
+		$extraData = $this->getDenormalizedData($calendarData);
520 520
 		$query = sprintf('UPDATE %s SET calendardata = ?, lastmodified = ?, etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ?, uid = ? WHERE calendarid = ? && uri = ?', $this->calendarObjectTableName);
521
-        $stmt = $this->pdo->prepare($query);
522
-        $stmt->execute([$calendarData, time(), $extraData['etag'], $extraData['size'], $extraData['componentType'], $extraData['firstOccurence'], $extraData['lastOccurence'], $extraData['uid'], $calendarId, $objectUri]);
523
-
524
-        $this->addChange($calendarId, $objectUri, 2);
525
-
526
-        return '"' . $extraData['etag'] . '"';
527
-
528
-    }
529
-
530
-    /**
531
-     * Parses some information from calendar objects, used for optimized
532
-     * calendar-queries.
533
-     *
534
-     * Returns an array with the following keys:
535
-     *   * etag - An md5 checksum of the object without the quotes.
536
-     *   * size - Size of the object in bytes
537
-     *   * componentType - VEVENT, VTODO or VJOURNAL
538
-     *   * firstOccurence
539
-     *   * lastOccurence
540
-     *   * uid - value of the UID property
541
-     *
542
-     * @param string $calendarData
543
-     * @return array
544
-     */
545
-    protected function getDenormalizedData($calendarData) {
546
-
547
-        $vObject = VObject\Reader::read($calendarData);
548
-        $componentType = null;
549
-        $component = null;
550
-        $firstOccurence = null;
551
-        $lastOccurence = null;
552
-        $uid = null;
553
-        foreach ($vObject->getComponents() as $component) {
554
-            if ($component->name !== 'VTIMEZONE') {
555
-                $componentType = $component->name;
556
-                $uid = (string)$component->UID;
557
-                break;
558
-            }
559
-        }
560
-        if (!$componentType) {
561
-            throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
562
-        }
563
-        if ($componentType === 'VEVENT') {
564
-            $firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp();
565
-            // Finding the last occurence is a bit harder
566
-            if (!isset($component->RRULE)) {
567
-                if (isset($component->DTEND)) {
568
-                    $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp();
569
-                } elseif (isset($component->DURATION)) {
570
-                    $endDate = clone $component->DTSTART->getDateTime();
571
-                    $endDate = $endDate->add(VObject\DateTimeParser::parse($component->DURATION->getValue()));
572
-                    $lastOccurence = $endDate->getTimeStamp();
573
-                } elseif (!$component->DTSTART->hasTime()) {
574
-                    $endDate = clone $component->DTSTART->getDateTime();
575
-                    $endDate = $endDate->modify('+1 day');
576
-                    $lastOccurence = $endDate->getTimeStamp();
577
-                } else {
578
-                    $lastOccurence = $firstOccurence;
579
-                }
580
-            } else {
581
-                $it = new VObject\Recur\EventIterator($vObject, (string)$component->UID);
582
-                $maxDate = new \DateTime(self::MAX_DATE);
583
-                if ($it->isInfinite()) {
584
-                    $lastOccurence = $maxDate->getTimeStamp();
585
-                } else {
586
-                    $end = $it->getDtEnd();
587
-                    while ($it->valid() && $end < $maxDate) {
588
-                        $end = $it->getDtEnd();
589
-                        $it->next();
590
-
591
-                    }
592
-                    $lastOccurence = $end->getTimeStamp();
593
-                }
594
-
595
-            }
596
-        }
597
-
598
-        // Destroy circular references to PHP will GC the object.
599
-        $vObject->destroy();
600
-
601
-        return [
602
-            'etag'           => md5($calendarData),
603
-            'size'           => strlen($calendarData),
604
-            'componentType'  => $componentType,
605
-            'firstOccurence' => $firstOccurence,
606
-            'lastOccurence'  => $lastOccurence,
607
-            'uid'            => $uid,
608
-        ];
609
-
610
-    }
611
-
612
-    /**
613
-     * Deletes an existing calendar object.
614
-     *
615
-     * The object uri is only the basename, or filename and not a full path.
616
-     *
617
-     * @param string $calendarId
618
-     * @param string $objectUri
619
-     * @return void
620
-     */
621
-    public function deleteCalendarObject($calendarId, $objectUri) {
521
+		$stmt = $this->pdo->prepare($query);
522
+		$stmt->execute([$calendarData, time(), $extraData['etag'], $extraData['size'], $extraData['componentType'], $extraData['firstOccurence'], $extraData['lastOccurence'], $extraData['uid'], $calendarId, $objectUri]);
523
+
524
+		$this->addChange($calendarId, $objectUri, 2);
525
+
526
+		return '"' . $extraData['etag'] . '"';
527
+
528
+	}
529
+
530
+	/**
531
+	 * Parses some information from calendar objects, used for optimized
532
+	 * calendar-queries.
533
+	 *
534
+	 * Returns an array with the following keys:
535
+	 *   * etag - An md5 checksum of the object without the quotes.
536
+	 *   * size - Size of the object in bytes
537
+	 *   * componentType - VEVENT, VTODO or VJOURNAL
538
+	 *   * firstOccurence
539
+	 *   * lastOccurence
540
+	 *   * uid - value of the UID property
541
+	 *
542
+	 * @param string $calendarData
543
+	 * @return array
544
+	 */
545
+	protected function getDenormalizedData($calendarData) {
546
+
547
+		$vObject = VObject\Reader::read($calendarData);
548
+		$componentType = null;
549
+		$component = null;
550
+		$firstOccurence = null;
551
+		$lastOccurence = null;
552
+		$uid = null;
553
+		foreach ($vObject->getComponents() as $component) {
554
+			if ($component->name !== 'VTIMEZONE') {
555
+				$componentType = $component->name;
556
+				$uid = (string)$component->UID;
557
+				break;
558
+			}
559
+		}
560
+		if (!$componentType) {
561
+			throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
562
+		}
563
+		if ($componentType === 'VEVENT') {
564
+			$firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp();
565
+			// Finding the last occurence is a bit harder
566
+			if (!isset($component->RRULE)) {
567
+				if (isset($component->DTEND)) {
568
+					$lastOccurence = $component->DTEND->getDateTime()->getTimeStamp();
569
+				} elseif (isset($component->DURATION)) {
570
+					$endDate = clone $component->DTSTART->getDateTime();
571
+					$endDate = $endDate->add(VObject\DateTimeParser::parse($component->DURATION->getValue()));
572
+					$lastOccurence = $endDate->getTimeStamp();
573
+				} elseif (!$component->DTSTART->hasTime()) {
574
+					$endDate = clone $component->DTSTART->getDateTime();
575
+					$endDate = $endDate->modify('+1 day');
576
+					$lastOccurence = $endDate->getTimeStamp();
577
+				} else {
578
+					$lastOccurence = $firstOccurence;
579
+				}
580
+			} else {
581
+				$it = new VObject\Recur\EventIterator($vObject, (string)$component->UID);
582
+				$maxDate = new \DateTime(self::MAX_DATE);
583
+				if ($it->isInfinite()) {
584
+					$lastOccurence = $maxDate->getTimeStamp();
585
+				} else {
586
+					$end = $it->getDtEnd();
587
+					while ($it->valid() && $end < $maxDate) {
588
+						$end = $it->getDtEnd();
589
+						$it->next();
590
+
591
+					}
592
+					$lastOccurence = $end->getTimeStamp();
593
+				}
594
+
595
+			}
596
+		}
597
+
598
+		// Destroy circular references to PHP will GC the object.
599
+		$vObject->destroy();
600
+
601
+		return [
602
+			'etag'           => md5($calendarData),
603
+			'size'           => strlen($calendarData),
604
+			'componentType'  => $componentType,
605
+			'firstOccurence' => $firstOccurence,
606
+			'lastOccurence'  => $lastOccurence,
607
+			'uid'            => $uid,
608
+		];
609
+
610
+	}
611
+
612
+	/**
613
+	 * Deletes an existing calendar object.
614
+	 *
615
+	 * The object uri is only the basename, or filename and not a full path.
616
+	 *
617
+	 * @param string $calendarId
618
+	 * @param string $objectUri
619
+	 * @return void
620
+	 */
621
+	public function deleteCalendarObject($calendarId, $objectUri) {
622 622
 
623 623
 		$query = sprintf('DELETE FROM %s WHERE calendarid = ? && uri = ?', $this->calendarObjectTableName);
624
-        $stmt = $this->pdo->prepare($query);
625
-        $stmt->execute([$calendarId, $objectUri]);
626
-
627
-        $this->addChange($calendarId, $objectUri, 3);
628
-
629
-    }
630
-
631
-    /**
632
-     * Performs a calendar-query on the contents of this calendar.
633
-     *
634
-     * The calendar-query is defined in RFC4791 : CalDAV. Using the
635
-     * calendar-query it is possible for a client to request a specific set of
636
-     * object, based on contents of iCalendar properties, date-ranges and
637
-     * iCalendar component types (VTODO, VEVENT).
638
-     *
639
-     * This method should just return a list of (relative) urls that match this
640
-     * query.
641
-     *
642
-     * The list of filters are specified as an array. The exact array is
643
-     * documented by \Sabre\CalDAV\CalendarQueryParser.
644
-     *
645
-     * Note that it is extremely likely that getCalendarObject for every path
646
-     * returned from this method will be called almost immediately after. You
647
-     * may want to anticipate this to speed up these requests.
648
-     *
649
-     * This method provides a default implementation, which parses *all* the
650
-     * iCalendar objects in the specified calendar.
651
-     *
652
-     * This default may well be good enough for personal use, and calendars
653
-     * that aren't very large. But if you anticipate high usage, big calendars
654
-     * or high loads, you are strongly adviced to optimize certain paths.
655
-     *
656
-     * The best way to do so is override this method and to optimize
657
-     * specifically for 'common filters'.
658
-     *
659
-     * Requests that are extremely common are:
660
-     *   * requests for just VEVENTS
661
-     *   * requests for just VTODO
662
-     *   * requests with a time-range-filter on a VEVENT.
663
-     *
664
-     * ..and combinations of these requests. It may not be worth it to try to
665
-     * handle every possible situation and just rely on the (relatively
666
-     * easy to use) CalendarQueryValidator to handle the rest.
667
-     *
668
-     * Note that especially time-range-filters may be difficult to parse. A
669
-     * time-range filter specified on a VEVENT must for instance also handle
670
-     * recurrence rules correctly.
671
-     * A good example of how to interprete all these filters can also simply
672
-     * be found in \Sabre\CalDAV\CalendarQueryFilter. This class is as correct
673
-     * as possible, so it gives you a good idea on what type of stuff you need
674
-     * to think of.
675
-     *
676
-     * This specific implementation (for the PDO) backend optimizes filters on
677
-     * specific components, and VEVENT time-ranges.
678
-     *
679
-     * @param string $calendarId
680
-     * @param array $filters
681
-     * @return array
682
-     */
683
-    public function calendarQuery($calendarId, array $filters) {
684
-
685
-        $componentType = null;
686
-        $requirePostFilter = true;
687
-        $timeRange = null;
688
-
689
-        // if no filters were specified, we don't need to filter after a query
690
-        if (!$filters['prop-filters'] && !$filters['comp-filters']) {
691
-            $requirePostFilter = false;
692
-        }
693
-
694
-        // Figuring out if there's a component filter
695
-        if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
696
-            $componentType = $filters['comp-filters'][0]['name'];
697
-
698
-            // Checking if we need post-filters
699
-            if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
700
-                $requirePostFilter = false;
701
-            }
702
-            // There was a time-range filter
703
-            if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range'])) {
704
-                $timeRange = $filters['comp-filters'][0]['time-range'];
705
-
706
-                // If start time OR the end time is not specified, we can do a
707
-                // 100% accurate mysql query.
708
-                if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
709
-                    $requirePostFilter = false;
710
-                }
711
-            }
712
-
713
-        }
714
-
715
-        if ($requirePostFilter) {
716
-            $query = sprintf("SELECT uri, calendardata FROM %s WHERE calendarid = :calendarid", $this->calendarObjectTableName);
717
-        } else {
718
-            $query = sprintf("SELECT uri FROM %s WHERE calendarid = :calendarid", $this->calendarObjectTableName);
719
-        }
720
-
721
-        $values = [
722
-            'calendarid' => $calendarId,
723
-        ];
724
-
725
-        if ($componentType) {
726
-            $query .= " && componenttype = :componenttype";
727
-            $values['componenttype'] = $componentType;
728
-        }
729
-
730
-        if ($timeRange && $timeRange['start']) {
731
-            $query .= " && lastoccurence > :startdate";
732
-            $values['startdate'] = $timeRange['start']->getTimeStamp();
733
-        }
734
-        if ($timeRange && $timeRange['end']) {
735
-            $query .= " && firstoccurence < :enddate";
736
-            $values['enddate'] = $timeRange['end']->getTimeStamp();
737
-        }
738
-
739
-        $stmt = $this->pdo->prepare($query);
740
-        $stmt->execute($values);
741
-
742
-        $result = [];
743
-        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
744
-            if ($requirePostFilter) {
745
-                if (!$this->validateFilterForObject($row, $filters)) {
746
-                    continue;
747
-                }
748
-            }
749
-            $result[] = $row['uri'];
750
-
751
-        }
752
-
753
-        return $result;
754
-
755
-    }
756
-
757
-    /**
758
-     * Searches through all of a users calendars and calendar objects to find
759
-     * an object with a specific UID.
760
-     *
761
-     * This method should return the path to this object, relative to the
762
-     * calendar home, so this path usually only contains two parts:
763
-     *
764
-     * calendarpath/objectpath.ics
765
-     *
766
-     * If the uid is not found, return null.
767
-     *
768
-     * This method should only consider * objects that the principal owns, so
769
-     * any calendars owned by other principals that also appear in this
770
-     * collection should be ignored.
771
-     *
772
-     * @param string $principalUri
773
-     * @param string $uid
774
-     * @return string|null
775
-     */
776
-    public function getCalendarObjectByUID($principalUri, $uid) {
777
-
778
-        $query = <<<SQL
624
+		$stmt = $this->pdo->prepare($query);
625
+		$stmt->execute([$calendarId, $objectUri]);
626
+
627
+		$this->addChange($calendarId, $objectUri, 3);
628
+
629
+	}
630
+
631
+	/**
632
+	 * Performs a calendar-query on the contents of this calendar.
633
+	 *
634
+	 * The calendar-query is defined in RFC4791 : CalDAV. Using the
635
+	 * calendar-query it is possible for a client to request a specific set of
636
+	 * object, based on contents of iCalendar properties, date-ranges and
637
+	 * iCalendar component types (VTODO, VEVENT).
638
+	 *
639
+	 * This method should just return a list of (relative) urls that match this
640
+	 * query.
641
+	 *
642
+	 * The list of filters are specified as an array. The exact array is
643
+	 * documented by \Sabre\CalDAV\CalendarQueryParser.
644
+	 *
645
+	 * Note that it is extremely likely that getCalendarObject for every path
646
+	 * returned from this method will be called almost immediately after. You
647
+	 * may want to anticipate this to speed up these requests.
648
+	 *
649
+	 * This method provides a default implementation, which parses *all* the
650
+	 * iCalendar objects in the specified calendar.
651
+	 *
652
+	 * This default may well be good enough for personal use, and calendars
653
+	 * that aren't very large. But if you anticipate high usage, big calendars
654
+	 * or high loads, you are strongly adviced to optimize certain paths.
655
+	 *
656
+	 * The best way to do so is override this method and to optimize
657
+	 * specifically for 'common filters'.
658
+	 *
659
+	 * Requests that are extremely common are:
660
+	 *   * requests for just VEVENTS
661
+	 *   * requests for just VTODO
662
+	 *   * requests with a time-range-filter on a VEVENT.
663
+	 *
664
+	 * ..and combinations of these requests. It may not be worth it to try to
665
+	 * handle every possible situation and just rely on the (relatively
666
+	 * easy to use) CalendarQueryValidator to handle the rest.
667
+	 *
668
+	 * Note that especially time-range-filters may be difficult to parse. A
669
+	 * time-range filter specified on a VEVENT must for instance also handle
670
+	 * recurrence rules correctly.
671
+	 * A good example of how to interprete all these filters can also simply
672
+	 * be found in \Sabre\CalDAV\CalendarQueryFilter. This class is as correct
673
+	 * as possible, so it gives you a good idea on what type of stuff you need
674
+	 * to think of.
675
+	 *
676
+	 * This specific implementation (for the PDO) backend optimizes filters on
677
+	 * specific components, and VEVENT time-ranges.
678
+	 *
679
+	 * @param string $calendarId
680
+	 * @param array $filters
681
+	 * @return array
682
+	 */
683
+	public function calendarQuery($calendarId, array $filters) {
684
+
685
+		$componentType = null;
686
+		$requirePostFilter = true;
687
+		$timeRange = null;
688
+
689
+		// if no filters were specified, we don't need to filter after a query
690
+		if (!$filters['prop-filters'] && !$filters['comp-filters']) {
691
+			$requirePostFilter = false;
692
+		}
693
+
694
+		// Figuring out if there's a component filter
695
+		if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
696
+			$componentType = $filters['comp-filters'][0]['name'];
697
+
698
+			// Checking if we need post-filters
699
+			if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
700
+				$requirePostFilter = false;
701
+			}
702
+			// There was a time-range filter
703
+			if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range'])) {
704
+				$timeRange = $filters['comp-filters'][0]['time-range'];
705
+
706
+				// If start time OR the end time is not specified, we can do a
707
+				// 100% accurate mysql query.
708
+				if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
709
+					$requirePostFilter = false;
710
+				}
711
+			}
712
+
713
+		}
714
+
715
+		if ($requirePostFilter) {
716
+			$query = sprintf("SELECT uri, calendardata FROM %s WHERE calendarid = :calendarid", $this->calendarObjectTableName);
717
+		} else {
718
+			$query = sprintf("SELECT uri FROM %s WHERE calendarid = :calendarid", $this->calendarObjectTableName);
719
+		}
720
+
721
+		$values = [
722
+			'calendarid' => $calendarId,
723
+		];
724
+
725
+		if ($componentType) {
726
+			$query .= " && componenttype = :componenttype";
727
+			$values['componenttype'] = $componentType;
728
+		}
729
+
730
+		if ($timeRange && $timeRange['start']) {
731
+			$query .= " && lastoccurence > :startdate";
732
+			$values['startdate'] = $timeRange['start']->getTimeStamp();
733
+		}
734
+		if ($timeRange && $timeRange['end']) {
735
+			$query .= " && firstoccurence < :enddate";
736
+			$values['enddate'] = $timeRange['end']->getTimeStamp();
737
+		}
738
+
739
+		$stmt = $this->pdo->prepare($query);
740
+		$stmt->execute($values);
741
+
742
+		$result = [];
743
+		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
744
+			if ($requirePostFilter) {
745
+				if (!$this->validateFilterForObject($row, $filters)) {
746
+					continue;
747
+				}
748
+			}
749
+			$result[] = $row['uri'];
750
+
751
+		}
752
+
753
+		return $result;
754
+
755
+	}
756
+
757
+	/**
758
+	 * Searches through all of a users calendars and calendar objects to find
759
+	 * an object with a specific UID.
760
+	 *
761
+	 * This method should return the path to this object, relative to the
762
+	 * calendar home, so this path usually only contains two parts:
763
+	 *
764
+	 * calendarpath/objectpath.ics
765
+	 *
766
+	 * If the uid is not found, return null.
767
+	 *
768
+	 * This method should only consider * objects that the principal owns, so
769
+	 * any calendars owned by other principals that also appear in this
770
+	 * collection should be ignored.
771
+	 *
772
+	 * @param string $principalUri
773
+	 * @param string $uid
774
+	 * @return string|null
775
+	 */
776
+	public function getCalendarObjectByUID($principalUri, $uid) {
777
+
778
+		$query = <<<SQL
779 779
 SELECT
780 780
     calendars.uri AS calendaruri, calendarobjects.uri as objecturi
781 781
 FROM
@@ -789,439 +789,439 @@  discard block
 block discarded – undo
789 789
     calendarobjects.uid = ?
790 790
 SQL;
791 791
 
792
-        $stmt = $this->pdo->prepare($query);
793
-        $stmt->execute([$principalUri, $uid]);
794
-
795
-        if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
796
-            return $row['calendaruri'] . '/' . $row['objecturi'];
797
-        }
798
-
799
-    }
800
-
801
-    /**
802
-     * The getChanges method returns all the changes that have happened, since
803
-     * the specified syncToken in the specified calendar.
804
-     *
805
-     * This function should return an array, such as the following:
806
-     *
807
-     * [
808
-     *   'syncToken' => 'The current synctoken',
809
-     *   'added'   => [
810
-     *      'new.txt',
811
-     *   ],
812
-     *   'modified'   => [
813
-     *      'modified.txt',
814
-     *   ],
815
-     *   'deleted' => [
816
-     *      'foo.php.bak',
817
-     *      'old.txt'
818
-     *   ]
819
-     * ];
820
-     *
821
-     * The returned syncToken property should reflect the *current* syncToken
822
-     * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
823
-     * property this is needed here too, to ensure the operation is atomic.
824
-     *
825
-     * If the $syncToken argument is specified as null, this is an initial
826
-     * sync, and all members should be reported.
827
-     *
828
-     * The modified property is an array of nodenames that have changed since
829
-     * the last token.
830
-     *
831
-     * The deleted property is an array with nodenames, that have been deleted
832
-     * from collection.
833
-     *
834
-     * The $syncLevel argument is basically the 'depth' of the report. If it's
835
-     * 1, you only have to report changes that happened only directly in
836
-     * immediate descendants. If it's 2, it should also include changes from
837
-     * the nodes below the child collections. (grandchildren)
838
-     *
839
-     * The $limit argument allows a client to specify how many results should
840
-     * be returned at most. If the limit is not specified, it should be treated
841
-     * as infinite.
842
-     *
843
-     * If the limit (infinite or not) is higher than you're willing to return,
844
-     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
845
-     *
846
-     * If the syncToken is expired (due to data cleanup) or unknown, you must
847
-     * return null.
848
-     *
849
-     * The limit is 'suggestive'. You are free to ignore it.
850
-     *
851
-     * @param string $calendarId
852
-     * @param string $syncToken
853
-     * @param int $syncLevel
854
-     * @param int $limit
855
-     * @return array
856
-     */
857
-    public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null) {
858
-
859
-        // Current synctoken
792
+		$stmt = $this->pdo->prepare($query);
793
+		$stmt->execute([$principalUri, $uid]);
794
+
795
+		if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
796
+			return $row['calendaruri'] . '/' . $row['objecturi'];
797
+		}
798
+
799
+	}
800
+
801
+	/**
802
+	 * The getChanges method returns all the changes that have happened, since
803
+	 * the specified syncToken in the specified calendar.
804
+	 *
805
+	 * This function should return an array, such as the following:
806
+	 *
807
+	 * [
808
+	 *   'syncToken' => 'The current synctoken',
809
+	 *   'added'   => [
810
+	 *      'new.txt',
811
+	 *   ],
812
+	 *   'modified'   => [
813
+	 *      'modified.txt',
814
+	 *   ],
815
+	 *   'deleted' => [
816
+	 *      'foo.php.bak',
817
+	 *      'old.txt'
818
+	 *   ]
819
+	 * ];
820
+	 *
821
+	 * The returned syncToken property should reflect the *current* syncToken
822
+	 * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
823
+	 * property this is needed here too, to ensure the operation is atomic.
824
+	 *
825
+	 * If the $syncToken argument is specified as null, this is an initial
826
+	 * sync, and all members should be reported.
827
+	 *
828
+	 * The modified property is an array of nodenames that have changed since
829
+	 * the last token.
830
+	 *
831
+	 * The deleted property is an array with nodenames, that have been deleted
832
+	 * from collection.
833
+	 *
834
+	 * The $syncLevel argument is basically the 'depth' of the report. If it's
835
+	 * 1, you only have to report changes that happened only directly in
836
+	 * immediate descendants. If it's 2, it should also include changes from
837
+	 * the nodes below the child collections. (grandchildren)
838
+	 *
839
+	 * The $limit argument allows a client to specify how many results should
840
+	 * be returned at most. If the limit is not specified, it should be treated
841
+	 * as infinite.
842
+	 *
843
+	 * If the limit (infinite or not) is higher than you're willing to return,
844
+	 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
845
+	 *
846
+	 * If the syncToken is expired (due to data cleanup) or unknown, you must
847
+	 * return null.
848
+	 *
849
+	 * The limit is 'suggestive'. You are free to ignore it.
850
+	 *
851
+	 * @param string $calendarId
852
+	 * @param string $syncToken
853
+	 * @param int $syncLevel
854
+	 * @param int $limit
855
+	 * @return array
856
+	 */
857
+	public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null) {
858
+
859
+		// Current synctoken
860 860
 		$query = sprintf('SELECT synctoken FROM %s WHERE id = ?', $this->calendarTableName);
861
-        $stmt = $this->pdo->prepare($query);
862
-        $stmt->execute([ $calendarId ]);
863
-        $currentToken = $stmt->fetchColumn(0);
864
-
865
-        if (is_null($currentToken)) return null;
866
-
867
-        $result = [
868
-            'syncToken' => $currentToken,
869
-            'added'     => [],
870
-            'modified'  => [],
871
-            'deleted'   => [],
872
-        ];
873
-
874
-        if ($syncToken) {
875
-
876
-            $query = sprintf('SELECT uri, operation FROM %s WHERE synctoken >= ? && synctoken < ? && calendarid = ? ORDER BY synctoken', $this->calendarChangesTableName);
877
-            if ($limit > 0) $query .= " LIMIT " . (int)$limit;
878
-
879
-            // Fetching all changes
880
-            $stmt = $this->pdo->prepare($query);
881
-            $stmt->execute([$syncToken, $currentToken, $calendarId]);
882
-
883
-            $changes = [];
884
-
885
-            // This loop ensures that any duplicates are overwritten, only the
886
-            // last change on a node is relevant.
887
-            while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
888
-
889
-                $changes[$row['uri']] = $row['operation'];
890
-
891
-            }
892
-
893
-            foreach ($changes as $uri => $operation) {
894
-
895
-                switch ($operation) {
896
-                    case 1 :
897
-                        $result['added'][] = $uri;
898
-                        break;
899
-                    case 2 :
900
-                        $result['modified'][] = $uri;
901
-                        break;
902
-                    case 3 :
903
-                        $result['deleted'][] = $uri;
904
-                        break;
905
-                }
906
-
907
-            }
908
-        } else {
909
-            // No synctoken supplied, this is the initial sync.
910
-            $query = sprintf('SELECT uri FROM %s WHERE calendarid = ?', $this->calendarObjectTableName);
911
-            $stmt = $this->pdo->prepare($query);
912
-            $stmt->execute([$calendarId]);
913
-
914
-            $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
915
-        }
916
-        return $result;
917
-
918
-    }
919
-
920
-    /**
921
-     * Adds a change record to the calendarchanges table.
922
-     *
923
-     * @param mixed $calendarId
924
-     * @param string $objectUri
925
-     * @param int $operation 1 = add, 2 = modify, 3 = delete.
926
-     * @return void
927
-     */
928
-    protected function addChange($calendarId, $objectUri, $operation) {
929
-
930
-        $stmt = $this->pdo->prepare('INSERT INTO ' . $this->calendarChangesTableName . ' (uri, synctoken, calendarid, operation) SELECT ?, synctoken, ?, ? FROM ' . $this->calendarTableName . ' WHERE id = ?');
931
-        $stmt->execute([
932
-            $objectUri,
933
-            $calendarId,
934
-            $operation,
935
-            $calendarId
936
-        ]);
861
+		$stmt = $this->pdo->prepare($query);
862
+		$stmt->execute([ $calendarId ]);
863
+		$currentToken = $stmt->fetchColumn(0);
864
+
865
+		if (is_null($currentToken)) return null;
866
+
867
+		$result = [
868
+			'syncToken' => $currentToken,
869
+			'added'     => [],
870
+			'modified'  => [],
871
+			'deleted'   => [],
872
+		];
873
+
874
+		if ($syncToken) {
875
+
876
+			$query = sprintf('SELECT uri, operation FROM %s WHERE synctoken >= ? && synctoken < ? && calendarid = ? ORDER BY synctoken', $this->calendarChangesTableName);
877
+			if ($limit > 0) $query .= " LIMIT " . (int)$limit;
878
+
879
+			// Fetching all changes
880
+			$stmt = $this->pdo->prepare($query);
881
+			$stmt->execute([$syncToken, $currentToken, $calendarId]);
882
+
883
+			$changes = [];
884
+
885
+			// This loop ensures that any duplicates are overwritten, only the
886
+			// last change on a node is relevant.
887
+			while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
888
+
889
+				$changes[$row['uri']] = $row['operation'];
890
+
891
+			}
892
+
893
+			foreach ($changes as $uri => $operation) {
894
+
895
+				switch ($operation) {
896
+					case 1 :
897
+						$result['added'][] = $uri;
898
+						break;
899
+					case 2 :
900
+						$result['modified'][] = $uri;
901
+						break;
902
+					case 3 :
903
+						$result['deleted'][] = $uri;
904
+						break;
905
+				}
906
+
907
+			}
908
+		} else {
909
+			// No synctoken supplied, this is the initial sync.
910
+			$query = sprintf('SELECT uri FROM %s WHERE calendarid = ?', $this->calendarObjectTableName);
911
+			$stmt = $this->pdo->prepare($query);
912
+			$stmt->execute([$calendarId]);
913
+
914
+			$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
915
+		}
916
+		return $result;
917
+
918
+	}
919
+
920
+	/**
921
+	 * Adds a change record to the calendarchanges table.
922
+	 *
923
+	 * @param mixed $calendarId
924
+	 * @param string $objectUri
925
+	 * @param int $operation 1 = add, 2 = modify, 3 = delete.
926
+	 * @return void
927
+	 */
928
+	protected function addChange($calendarId, $objectUri, $operation) {
929
+
930
+		$stmt = $this->pdo->prepare('INSERT INTO ' . $this->calendarChangesTableName . ' (uri, synctoken, calendarid, operation) SELECT ?, synctoken, ?, ? FROM ' . $this->calendarTableName . ' WHERE id = ?');
931
+		$stmt->execute([
932
+			$objectUri,
933
+			$calendarId,
934
+			$operation,
935
+			$calendarId
936
+		]);
937 937
 		$query = sprintf('UPDATE %s SET synctoken = synctoken + 1 WHERE id = ?', $this->calendarTableName);
938
-        $stmt = $this->pdo->prepare($query);
939
-        $stmt->execute([
940
-            $calendarId
941
-        ]);
942
-
943
-    }
944
-
945
-    /**
946
-     * Returns a list of subscriptions for a principal.
947
-     *
948
-     * Every subscription is an array with the following keys:
949
-     *  * id, a unique id that will be used by other functions to modify the
950
-     *    subscription. This can be the same as the uri or a database key.
951
-     *  * uri. This is just the 'base uri' or 'filename' of the subscription.
952
-     *  * principaluri. The owner of the subscription. Almost always the same as
953
-     *    principalUri passed to this method.
954
-     *  * source. Url to the actual feed
955
-     *
956
-     * Furthermore, all the subscription info must be returned too:
957
-     *
958
-     * 1. {DAV:}displayname
959
-     * 2. {http://apple.com/ns/ical/}refreshrate
960
-     * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
961
-     *    should not be stripped).
962
-     * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
963
-     *    should not be stripped).
964
-     * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
965
-     *    attachments should not be stripped).
966
-     * 7. {http://apple.com/ns/ical/}calendar-color
967
-     * 8. {http://apple.com/ns/ical/}calendar-order
968
-     * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
969
-     *    (should just be an instance of
970
-     *    Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
971
-     *    default components).
972
-     *
973
-     * @param string $principalUri
974
-     * @return array
975
-     */
976
-    public function getSubscriptionsForUser($principalUri) {
977
-
978
-        $fields = array_values($this->subscriptionPropertyMap);
979
-        $fields[] = 'id';
980
-        $fields[] = 'uri';
981
-        $fields[] = 'source';
982
-        $fields[] = 'principaluri';
983
-        $fields[] = 'lastmodified';
984
-
985
-        // Making fields a comma-delimited list
986
-        $fields = implode(', ', $fields);
938
+		$stmt = $this->pdo->prepare($query);
939
+		$stmt->execute([
940
+			$calendarId
941
+		]);
942
+
943
+	}
944
+
945
+	/**
946
+	 * Returns a list of subscriptions for a principal.
947
+	 *
948
+	 * Every subscription is an array with the following keys:
949
+	 *  * id, a unique id that will be used by other functions to modify the
950
+	 *    subscription. This can be the same as the uri or a database key.
951
+	 *  * uri. This is just the 'base uri' or 'filename' of the subscription.
952
+	 *  * principaluri. The owner of the subscription. Almost always the same as
953
+	 *    principalUri passed to this method.
954
+	 *  * source. Url to the actual feed
955
+	 *
956
+	 * Furthermore, all the subscription info must be returned too:
957
+	 *
958
+	 * 1. {DAV:}displayname
959
+	 * 2. {http://apple.com/ns/ical/}refreshrate
960
+	 * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
961
+	 *    should not be stripped).
962
+	 * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
963
+	 *    should not be stripped).
964
+	 * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
965
+	 *    attachments should not be stripped).
966
+	 * 7. {http://apple.com/ns/ical/}calendar-color
967
+	 * 8. {http://apple.com/ns/ical/}calendar-order
968
+	 * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
969
+	 *    (should just be an instance of
970
+	 *    Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
971
+	 *    default components).
972
+	 *
973
+	 * @param string $principalUri
974
+	 * @return array
975
+	 */
976
+	public function getSubscriptionsForUser($principalUri) {
977
+
978
+		$fields = array_values($this->subscriptionPropertyMap);
979
+		$fields[] = 'id';
980
+		$fields[] = 'uri';
981
+		$fields[] = 'source';
982
+		$fields[] = 'principaluri';
983
+		$fields[] = 'lastmodified';
984
+
985
+		// Making fields a comma-delimited list
986
+		$fields = implode(', ', $fields);
987 987
 		$query = sprintf('SELECT %s FROM %s WHERE principaluri = ? ORDER BY calendarorder ASC', $fields, $this->calendarSubscriptionsTableName);
988
-        $stmt = $this->pdo->prepare($query);
989
-        $stmt->execute([$principalUri]);
990
-
991
-        $subscriptions = [];
992
-        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
993
-
994
-            $subscription = [
995
-                'id'           => $row['id'],
996
-                'uri'          => $row['uri'],
997
-                'principaluri' => $row['principaluri'],
998
-                'source'       => $row['source'],
999
-                'lastmodified' => $row['lastmodified'],
1000
-
1001
-                '{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
1002
-            ];
1003
-
1004
-            foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
1005
-                if (!is_null($row[$dbName])) {
1006
-                    $subscription[$xmlName] = $row[$dbName];
1007
-                }
1008
-            }
1009
-
1010
-            $subscriptions[] = $subscription;
1011
-
1012
-        }
1013
-
1014
-        return $subscriptions;
1015
-
1016
-    }
1017
-
1018
-    /**
1019
-     * Creates a new subscription for a principal.
1020
-     *
1021
-     * If the creation was a success, an id must be returned that can be used to reference
1022
-     * this subscription in other methods, such as updateSubscription.
1023
-     *
1024
-     * @param string $principalUri
1025
-     * @param string $uri
1026
-     * @param array $properties
1027
-     * @return mixed
1028
-     */
1029
-    public function createSubscription($principalUri, $uri, array $properties) {
1030
-
1031
-        $fieldNames = [
1032
-            'principaluri',
1033
-            'uri',
1034
-            'source',
1035
-            'lastmodified',
1036
-        ];
1037
-
1038
-        if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
1039
-            throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
1040
-        }
1041
-
1042
-        $values = [
1043
-            ':principaluri' => $principalUri,
1044
-            ':uri'          => $uri,
1045
-            ':source'       => $properties['{http://calendarserver.org/ns/}source']->getHref(),
1046
-            ':lastmodified' => time(),
1047
-        ];
1048
-
1049
-        foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
1050
-            if (isset($properties[$xmlName])) {
1051
-
1052
-                $values[':' . $dbName] = $properties[$xmlName];
1053
-                $fieldNames[] = $dbName;
1054
-            }
1055
-        }
1056
-
1057
-        $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarSubscriptionsTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")");
1058
-        $stmt->execute($values);
1059
-
1060
-        return $this->pdo->lastInsertId();
1061
-
1062
-    }
1063
-
1064
-    /**
1065
-     * Updates a subscription
1066
-     *
1067
-     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
1068
-     * To do the actual updates, you must tell this object which properties
1069
-     * you're going to process with the handle() method.
1070
-     *
1071
-     * Calling the handle method is like telling the PropPatch object "I
1072
-     * promise I can handle updating this property".
1073
-     *
1074
-     * Read the PropPatch documenation for more info and examples.
1075
-     *
1076
-     * @param mixed $subscriptionId
1077
-     * @param \Sabre\DAV\PropPatch $propPatch
1078
-     * @return void
1079
-     */
1080
-    public function updateSubscription($subscriptionId, DAV\PropPatch $propPatch) {
1081
-
1082
-        $supportedProperties = array_keys($this->subscriptionPropertyMap);
1083
-        $supportedProperties[] = '{http://calendarserver.org/ns/}source';
1084
-
1085
-        $propPatch->handle($supportedProperties, function($mutations) use ($subscriptionId) {
1086
-
1087
-            $newValues = [];
1088
-
1089
-            foreach ($mutations as $propertyName => $propertyValue) {
1090
-
1091
-                if ($propertyName === '{http://calendarserver.org/ns/}source') {
1092
-                    $newValues['source'] = $propertyValue->getHref();
1093
-                } else {
1094
-                    $fieldName = $this->subscriptionPropertyMap[$propertyName];
1095
-                    $newValues[$fieldName] = $propertyValue;
1096
-                }
1097
-
1098
-            }
1099
-
1100
-            // Now we're generating the sql query.
1101
-            $valuesSql = [];
1102
-            foreach ($newValues as $fieldName => $value) {
1103
-                $valuesSql[] = $fieldName . ' = ?';
1104
-            }
988
+		$stmt = $this->pdo->prepare($query);
989
+		$stmt->execute([$principalUri]);
990
+
991
+		$subscriptions = [];
992
+		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
993
+
994
+			$subscription = [
995
+				'id'           => $row['id'],
996
+				'uri'          => $row['uri'],
997
+				'principaluri' => $row['principaluri'],
998
+				'source'       => $row['source'],
999
+				'lastmodified' => $row['lastmodified'],
1000
+
1001
+				'{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
1002
+			];
1003
+
1004
+			foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
1005
+				if (!is_null($row[$dbName])) {
1006
+					$subscription[$xmlName] = $row[$dbName];
1007
+				}
1008
+			}
1009
+
1010
+			$subscriptions[] = $subscription;
1011
+
1012
+		}
1013
+
1014
+		return $subscriptions;
1015
+
1016
+	}
1017
+
1018
+	/**
1019
+	 * Creates a new subscription for a principal.
1020
+	 *
1021
+	 * If the creation was a success, an id must be returned that can be used to reference
1022
+	 * this subscription in other methods, such as updateSubscription.
1023
+	 *
1024
+	 * @param string $principalUri
1025
+	 * @param string $uri
1026
+	 * @param array $properties
1027
+	 * @return mixed
1028
+	 */
1029
+	public function createSubscription($principalUri, $uri, array $properties) {
1030
+
1031
+		$fieldNames = [
1032
+			'principaluri',
1033
+			'uri',
1034
+			'source',
1035
+			'lastmodified',
1036
+		];
1037
+
1038
+		if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
1039
+			throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
1040
+		}
1041
+
1042
+		$values = [
1043
+			':principaluri' => $principalUri,
1044
+			':uri'          => $uri,
1045
+			':source'       => $properties['{http://calendarserver.org/ns/}source']->getHref(),
1046
+			':lastmodified' => time(),
1047
+		];
1048
+
1049
+		foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
1050
+			if (isset($properties[$xmlName])) {
1051
+
1052
+				$values[':' . $dbName] = $properties[$xmlName];
1053
+				$fieldNames[] = $dbName;
1054
+			}
1055
+		}
1056
+
1057
+		$stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarSubscriptionsTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")");
1058
+		$stmt->execute($values);
1059
+
1060
+		return $this->pdo->lastInsertId();
1061
+
1062
+	}
1063
+
1064
+	/**
1065
+	 * Updates a subscription
1066
+	 *
1067
+	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
1068
+	 * To do the actual updates, you must tell this object which properties
1069
+	 * you're going to process with the handle() method.
1070
+	 *
1071
+	 * Calling the handle method is like telling the PropPatch object "I
1072
+	 * promise I can handle updating this property".
1073
+	 *
1074
+	 * Read the PropPatch documenation for more info and examples.
1075
+	 *
1076
+	 * @param mixed $subscriptionId
1077
+	 * @param \Sabre\DAV\PropPatch $propPatch
1078
+	 * @return void
1079
+	 */
1080
+	public function updateSubscription($subscriptionId, DAV\PropPatch $propPatch) {
1081
+
1082
+		$supportedProperties = array_keys($this->subscriptionPropertyMap);
1083
+		$supportedProperties[] = '{http://calendarserver.org/ns/}source';
1084
+
1085
+		$propPatch->handle($supportedProperties, function($mutations) use ($subscriptionId) {
1086
+
1087
+			$newValues = [];
1088
+
1089
+			foreach ($mutations as $propertyName => $propertyValue) {
1090
+
1091
+				if ($propertyName === '{http://calendarserver.org/ns/}source') {
1092
+					$newValues['source'] = $propertyValue->getHref();
1093
+				} else {
1094
+					$fieldName = $this->subscriptionPropertyMap[$propertyName];
1095
+					$newValues[$fieldName] = $propertyValue;
1096
+				}
1097
+
1098
+			}
1099
+
1100
+			// Now we're generating the sql query.
1101
+			$valuesSql = [];
1102
+			foreach ($newValues as $fieldName => $value) {
1103
+				$valuesSql[] = $fieldName . ' = ?';
1104
+			}
1105 1105
 			$query = sprintf('UPDATE %s SET  %s , lastmodified = ? WHERE id = ?', $this->calendarSubscriptionsTableName, implode(', ', $valuesSql));
1106
-            $stmt = $this->pdo->prepare($query);
1107
-            $newValues['lastmodified'] = time();
1108
-            $newValues['id'] = $subscriptionId;
1109
-            $stmt->execute(array_values($newValues));
1106
+			$stmt = $this->pdo->prepare($query);
1107
+			$newValues['lastmodified'] = time();
1108
+			$newValues['id'] = $subscriptionId;
1109
+			$stmt->execute(array_values($newValues));
1110 1110
 
1111
-            return true;
1111
+			return true;
1112 1112
 
1113
-        });
1113
+		});
1114 1114
 
1115
-    }
1115
+	}
1116 1116
 
1117
-    /**
1118
-     * Deletes a subscription
1119
-     *
1120
-     * @param mixed $subscriptionId
1121
-     * @return void
1122
-     */
1123
-    public function deleteSubscription($subscriptionId) {
1117
+	/**
1118
+	 * Deletes a subscription
1119
+	 *
1120
+	 * @param mixed $subscriptionId
1121
+	 * @return void
1122
+	 */
1123
+	public function deleteSubscription($subscriptionId) {
1124 1124
 		$query = sprintf('DELETE FROM %s WHERE id = ?', $this->calendarSubscriptionsTableName);
1125
-        $stmt = $this->pdo->prepare($query);
1126
-        $stmt->execute([$subscriptionId]);
1127
-
1128
-    }
1129
-
1130
-    /**
1131
-     * Returns a single scheduling object.
1132
-     *
1133
-     * The returned array should contain the following elements:
1134
-     *   * uri - A unique basename for the object. This will be used to
1135
-     *           construct a full uri.
1136
-     *   * calendardata - The iCalendar object
1137
-     *   * lastmodified - The last modification date. Can be an int for a unix
1138
-     *                    timestamp, or a PHP DateTime object.
1139
-     *   * etag - A unique token that must change if the object changed.
1140
-     *   * size - The size of the object, in bytes.
1141
-     *
1142
-     * @param string $principalUri
1143
-     * @param string $objectUri
1144
-     * @return array
1145
-     */
1146
-    public function getSchedulingObject($principalUri, $objectUri) {
1125
+		$stmt = $this->pdo->prepare($query);
1126
+		$stmt->execute([$subscriptionId]);
1127
+
1128
+	}
1129
+
1130
+	/**
1131
+	 * Returns a single scheduling object.
1132
+	 *
1133
+	 * The returned array should contain the following elements:
1134
+	 *   * uri - A unique basename for the object. This will be used to
1135
+	 *           construct a full uri.
1136
+	 *   * calendardata - The iCalendar object
1137
+	 *   * lastmodified - The last modification date. Can be an int for a unix
1138
+	 *                    timestamp, or a PHP DateTime object.
1139
+	 *   * etag - A unique token that must change if the object changed.
1140
+	 *   * size - The size of the object, in bytes.
1141
+	 *
1142
+	 * @param string $principalUri
1143
+	 * @param string $objectUri
1144
+	 * @return array
1145
+	 */
1146
+	public function getSchedulingObject($principalUri, $objectUri) {
1147 1147
 		
1148 1148
 		$query = sprintf('SELECT uri, calendardata, lastmodified, etag, size FROM %s WHERE principaluri = ? && uri = ?', $this->schedulingObjectTableName);
1149
-        $stmt = $this->pdo->prepare($query);
1150
-        $stmt->execute([$principalUri, $objectUri]);
1151
-        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
1152
-
1153
-        if (!$row) return null;
1154
-
1155
-        return [
1156
-            'uri'          => $row['uri'],
1157
-            'calendardata' => $row['calendardata'],
1158
-            'lastmodified' => $row['lastmodified'],
1159
-            'etag'         => '"' . $row['etag'] . '"',
1160
-            'size'         => (int)$row['size'],
1161
-         ];
1162
-
1163
-    }
1164
-
1165
-    /**
1166
-     * Returns all scheduling objects for the inbox collection.
1167
-     *
1168
-     * These objects should be returned as an array. Every item in the array
1169
-     * should follow the same structure as returned from getSchedulingObject.
1170
-     *
1171
-     * The main difference is that 'calendardata' is optional.
1172
-     *
1173
-     * @param string $principalUri
1174
-     * @return array
1175
-     */
1176
-    public function getSchedulingObjects($principalUri) {
1149
+		$stmt = $this->pdo->prepare($query);
1150
+		$stmt->execute([$principalUri, $objectUri]);
1151
+		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
1152
+
1153
+		if (!$row) return null;
1154
+
1155
+		return [
1156
+			'uri'          => $row['uri'],
1157
+			'calendardata' => $row['calendardata'],
1158
+			'lastmodified' => $row['lastmodified'],
1159
+			'etag'         => '"' . $row['etag'] . '"',
1160
+			'size'         => (int)$row['size'],
1161
+		 ];
1162
+
1163
+	}
1164
+
1165
+	/**
1166
+	 * Returns all scheduling objects for the inbox collection.
1167
+	 *
1168
+	 * These objects should be returned as an array. Every item in the array
1169
+	 * should follow the same structure as returned from getSchedulingObject.
1170
+	 *
1171
+	 * The main difference is that 'calendardata' is optional.
1172
+	 *
1173
+	 * @param string $principalUri
1174
+	 * @return array
1175
+	 */
1176
+	public function getSchedulingObjects($principalUri) {
1177 1177
 
1178 1178
 		$query = sprintf('SELECT id, calendardata, uri, lastmodified, etag, size FROM %s WHERE principaluri = ?', $this->schedulingObjectTableName);
1179
-        $stmt = $this->pdo->prepare($query);
1180
-        $stmt->execute([$principalUri]);
1181
-
1182
-        $result = [];
1183
-        foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
1184
-            $result[] = [
1185
-                'calendardata' => $row['calendardata'],
1186
-                'uri'          => $row['uri'],
1187
-                'lastmodified' => $row['lastmodified'],
1188
-                'etag'         => '"' . $row['etag'] . '"',
1189
-                'size'         => (int)$row['size'],
1190
-            ];
1191
-        }
1192
-
1193
-        return $result;
1194
-
1195
-    }
1196
-
1197
-    /**
1198
-     * Deletes a scheduling object
1199
-     *
1200
-     * @param string $principalUri
1201
-     * @param string $objectUri
1202
-     * @return void
1203
-     */
1204
-    public function deleteSchedulingObject($principalUri, $objectUri) {
1179
+		$stmt = $this->pdo->prepare($query);
1180
+		$stmt->execute([$principalUri]);
1181
+
1182
+		$result = [];
1183
+		foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
1184
+			$result[] = [
1185
+				'calendardata' => $row['calendardata'],
1186
+				'uri'          => $row['uri'],
1187
+				'lastmodified' => $row['lastmodified'],
1188
+				'etag'         => '"' . $row['etag'] . '"',
1189
+				'size'         => (int)$row['size'],
1190
+			];
1191
+		}
1192
+
1193
+		return $result;
1194
+
1195
+	}
1196
+
1197
+	/**
1198
+	 * Deletes a scheduling object
1199
+	 *
1200
+	 * @param string $principalUri
1201
+	 * @param string $objectUri
1202
+	 * @return void
1203
+	 */
1204
+	public function deleteSchedulingObject($principalUri, $objectUri) {
1205 1205
 
1206 1206
 		$query = sprintf('DELETE FROM %s WHERE principaluri = ? && uri = ?', $this->schedulingObjectTableName);	
1207
-        $stmt = $this->pdo->prepare($query);
1208
-        $stmt->execute([$principalUri, $objectUri]);
1207
+		$stmt = $this->pdo->prepare($query);
1208
+		$stmt->execute([$principalUri, $objectUri]);
1209 1209
 
1210
-    }
1210
+	}
1211 1211
 
1212
-    /**
1213
-     * Creates a new scheduling object. This should land in a users' inbox.
1214
-     *
1215
-     * @param string $principalUri
1216
-     * @param string $objectUri
1217
-     * @param string $objectData
1218
-     * @return void
1219
-     */
1220
-    public function createSchedulingObject($principalUri, $objectUri, $objectData) {
1212
+	/**
1213
+	 * Creates a new scheduling object. This should land in a users' inbox.
1214
+	 *
1215
+	 * @param string $principalUri
1216
+	 * @param string $objectUri
1217
+	 * @param string $objectData
1218
+	 * @return void
1219
+	 */
1220
+	public function createSchedulingObject($principalUri, $objectUri, $objectData) {
1221 1221
 
1222
-        $stmt = $this->pdo->prepare('INSERT INTO ' . $this->schedulingObjectTableName . ' (principaluri, calendardata, uri, lastmodified, etag, size) VALUES (?, ?, ?, ?, ?, ?)');
1223
-        $stmt->execute([$principalUri, $objectData, $objectUri, time(), md5($objectData), strlen($objectData) ]);
1222
+		$stmt = $this->pdo->prepare('INSERT INTO ' . $this->schedulingObjectTableName . ' (principaluri, calendardata, uri, lastmodified, etag, size) VALUES (?, ?, ?, ?, ?, ?)');
1223
+		$stmt->execute([$principalUri, $objectData, $objectUri, time(), md5($objectData), strlen($objectData) ]);
1224 1224
 
1225
-    }
1225
+	}
1226 1226
 
1227 1227
 }
Please login to merge, or discard this patch.
Doc Comments   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -1024,7 +1024,7 @@
 block discarded – undo
1024 1024
      * @param string $principalUri
1025 1025
      * @param string $uri
1026 1026
      * @param array $properties
1027
-     * @return mixed
1027
+     * @return string
1028 1028
      */
1029 1029
     public function createSubscription($principalUri, $uri, array $properties) {
1030 1030
 
Please login to merge, or discard this patch.
Spacing   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -366,7 +366,7 @@  discard block
 block discarded – undo
366 366
                 'lastmodified' => $row['lastmodified'],
367 367
                 'etag'         => '"' . $row['etag'] . '"',
368 368
                 'calendarid'   => $row['calendarid'],
369
-                'size'         => (int)$row['size'],
369
+                'size'         => (int) $row['size'],
370 370
                 'component'    => strtolower($row['componenttype']),
371 371
             ];
372 372
         }
@@ -406,7 +406,7 @@  discard block
 block discarded – undo
406 406
             'lastmodified'  => $row['lastmodified'],
407 407
             'etag'          => '"' . $row['etag'] . '"',
408 408
             'calendarid'    => $row['calendarid'],
409
-            'size'          => (int)$row['size'],
409
+            'size'          => (int) $row['size'],
410 410
             'calendardata'  => $row['calendardata'],
411 411
             'component'     => strtolower($row['componenttype']),
412 412
          ];
@@ -427,7 +427,7 @@  discard block
 block discarded – undo
427 427
      */
428 428
     public function getMultipleCalendarObjects($calendarId, array $uris) {
429 429
 
430
-        $query =sprintf('SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM %s WHERE calendarid = ? && uri IN (', $this->calendarObjectTableName);
430
+        $query = sprintf('SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM %s WHERE calendarid = ? && uri IN (', $this->calendarObjectTableName);
431 431
         // Inserting a whole bunch of question marks
432 432
         $query .= implode(',', array_fill(0, count($uris), '?'));
433 433
         $query .= ')';
@@ -444,7 +444,7 @@  discard block
 block discarded – undo
444 444
                 'lastmodified' => $row['lastmodified'],
445 445
                 'etag'         => '"' . $row['etag'] . '"',
446 446
                 'calendarid'   => $row['calendarid'],
447
-                'size'         => (int)$row['size'],
447
+                'size'         => (int) $row['size'],
448 448
                 'calendardata' => $row['calendardata'],
449 449
                 'component'    => strtolower($row['componenttype']),
450 450
             ];
@@ -553,7 +553,7 @@  discard block
 block discarded – undo
553 553
         foreach ($vObject->getComponents() as $component) {
554 554
             if ($component->name !== 'VTIMEZONE') {
555 555
                 $componentType = $component->name;
556
-                $uid = (string)$component->UID;
556
+                $uid = (string) $component->UID;
557 557
                 break;
558 558
             }
559 559
         }
@@ -578,7 +578,7 @@  discard block
 block discarded – undo
578 578
                     $lastOccurence = $firstOccurence;
579 579
                 }
580 580
             } else {
581
-                $it = new VObject\Recur\EventIterator($vObject, (string)$component->UID);
581
+                $it = new VObject\Recur\EventIterator($vObject, (string) $component->UID);
582 582
                 $maxDate = new \DateTime(self::MAX_DATE);
583 583
                 if ($it->isInfinite()) {
584 584
                     $lastOccurence = $maxDate->getTimeStamp();
@@ -859,7 +859,7 @@  discard block
 block discarded – undo
859 859
         // Current synctoken
860 860
 		$query = sprintf('SELECT synctoken FROM %s WHERE id = ?', $this->calendarTableName);
861 861
         $stmt = $this->pdo->prepare($query);
862
-        $stmt->execute([ $calendarId ]);
862
+        $stmt->execute([$calendarId]);
863 863
         $currentToken = $stmt->fetchColumn(0);
864 864
 
865 865
         if (is_null($currentToken)) return null;
@@ -874,7 +874,7 @@  discard block
 block discarded – undo
874 874
         if ($syncToken) {
875 875
 
876 876
             $query = sprintf('SELECT uri, operation FROM %s WHERE synctoken >= ? && synctoken < ? && calendarid = ? ORDER BY synctoken', $this->calendarChangesTableName);
877
-            if ($limit > 0) $query .= " LIMIT " . (int)$limit;
877
+            if ($limit > 0) $query .= " LIMIT " . (int) $limit;
878 878
 
879 879
             // Fetching all changes
880 880
             $stmt = $this->pdo->prepare($query);
@@ -1157,7 +1157,7 @@  discard block
 block discarded – undo
1157 1157
             'calendardata' => $row['calendardata'],
1158 1158
             'lastmodified' => $row['lastmodified'],
1159 1159
             'etag'         => '"' . $row['etag'] . '"',
1160
-            'size'         => (int)$row['size'],
1160
+            'size'         => (int) $row['size'],
1161 1161
          ];
1162 1162
 
1163 1163
     }
@@ -1186,7 +1186,7 @@  discard block
 block discarded – undo
1186 1186
                 'uri'          => $row['uri'],
1187 1187
                 'lastmodified' => $row['lastmodified'],
1188 1188
                 'etag'         => '"' . $row['etag'] . '"',
1189
-                'size'         => (int)$row['size'],
1189
+                'size'         => (int) $row['size'],
1190 1190
             ];
1191 1191
         }
1192 1192
 
@@ -1220,7 +1220,7 @@  discard block
 block discarded – undo
1220 1220
     public function createSchedulingObject($principalUri, $objectUri, $objectData) {
1221 1221
 
1222 1222
         $stmt = $this->pdo->prepare('INSERT INTO ' . $this->schedulingObjectTableName . ' (principaluri, calendardata, uri, lastmodified, etag, size) VALUES (?, ?, ?, ?, ?, ?)');
1223
-        $stmt->execute([$principalUri, $objectData, $objectUri, time(), md5($objectData), strlen($objectData) ]);
1223
+        $stmt->execute([$principalUri, $objectData, $objectUri, time(), md5($objectData), strlen($objectData)]);
1224 1224
 
1225 1225
     }
1226 1226
 
Please login to merge, or discard this patch.
libraries/SabreDAV/CalDAV/CalendarHome.php 3 patches
Unused Use Statements   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -3,9 +3,9 @@
 block discarded – undo
3 3
 namespace Sabre\CalDAV;
4 4
 
5 5
 use Sabre\DAV;
6
+use Sabre\DAVACL;
6 7
 use Sabre\DAV\Exception\NotFound;
7 8
 use Sabre\DAV\MkCol;
8
-use Sabre\DAVACL;
9 9
 use Sabre\HTTP\URLUtil;
10 10
 
11 11
 /**
Please login to merge, or discard this patch.
Indentation   +404 added lines, -404 removed lines patch added patch discarded remove patch
@@ -22,409 +22,409 @@
 block discarded – undo
22 22
  */
23 23
 class CalendarHome implements DAV\IExtendedCollection, DAVACL\IACL {
24 24
 
25
-    /**
26
-     * CalDAV backend
27
-     *
28
-     * @var Sabre\CalDAV\Backend\BackendInterface
29
-     */
30
-    protected $caldavBackend;
31
-
32
-    /**
33
-     * Principal information
34
-     *
35
-     * @var array
36
-     */
37
-    protected $principalInfo;
38
-
39
-    /**
40
-     * Constructor
41
-     *
42
-     * @param Backend\BackendInterface $caldavBackend
43
-     * @param mixed $userUri
44
-     */
45
-    public function __construct(Backend\BackendInterface $caldavBackend, $principalInfo) {
46
-
47
-        $this->caldavBackend = $caldavBackend;
48
-        $this->principalInfo = $principalInfo;
49
-
50
-    }
51
-
52
-    /**
53
-     * Returns the name of this object
54
-     *
55
-     * @return string
56
-     */
57
-    public function getName() {
58
-
59
-        list(, $name) = URLUtil::splitPath($this->principalInfo['uri']);
60
-        return $name;
61
-
62
-    }
63
-
64
-    /**
65
-     * Updates the name of this object
66
-     *
67
-     * @param string $name
68
-     * @return void
69
-     */
70
-    public function setName($name) {
71
-
72
-        throw new DAV\Exception\Forbidden();
73
-
74
-    }
75
-
76
-    /**
77
-     * Deletes this object
78
-     *
79
-     * @return void
80
-     */
81
-    public function delete() {
82
-
83
-        throw new DAV\Exception\Forbidden();
84
-
85
-    }
86
-
87
-    /**
88
-     * Returns the last modification date
89
-     *
90
-     * @return int
91
-     */
92
-    public function getLastModified() {
93
-
94
-        return null;
95
-
96
-    }
97
-
98
-    /**
99
-     * Creates a new file under this object.
100
-     *
101
-     * This is currently not allowed
102
-     *
103
-     * @param string $filename
104
-     * @param resource $data
105
-     * @return void
106
-     */
107
-    public function createFile($filename, $data = null) {
108
-
109
-        throw new DAV\Exception\MethodNotAllowed('Creating new files in this collection is not supported');
110
-
111
-    }
112
-
113
-    /**
114
-     * Creates a new directory under this object.
115
-     *
116
-     * This is currently not allowed.
117
-     *
118
-     * @param string $filename
119
-     * @return void
120
-     */
121
-    public function createDirectory($filename) {
122
-
123
-        throw new DAV\Exception\MethodNotAllowed('Creating new collections in this collection is not supported');
124
-
125
-    }
126
-
127
-    /**
128
-     * Returns a single calendar, by name
129
-     *
130
-     * @param string $name
131
-     * @return Calendar
132
-     */
133
-    public function getChild($name) {
134
-
135
-        // Special nodes
136
-        if ($name === 'inbox' && $this->caldavBackend instanceof Backend\SchedulingSupport) {
137
-            return new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']);
138
-        }
139
-        if ($name === 'outbox' && $this->caldavBackend instanceof Backend\SchedulingSupport) {
140
-            return new Schedule\Outbox($this->principalInfo['uri']);
141
-        }
142
-        if ($name === 'notifications' && $this->caldavBackend instanceof Backend\NotificationSupport) {
143
-            return new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
144
-        }
145
-
146
-        // Calendars
147
-        foreach ($this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']) as $calendar) {
148
-            if ($calendar['uri'] === $name) {
149
-                if ($this->caldavBackend instanceof Backend\SharingSupport) {
150
-                    if (isset($calendar['{http://calendarserver.org/ns/}shared-url'])) {
151
-                        return new SharedCalendar($this->caldavBackend, $calendar);
152
-                    } else {
153
-                        return new ShareableCalendar($this->caldavBackend, $calendar);
154
-                    }
155
-                } else {
156
-                    return new Calendar($this->caldavBackend, $calendar);
157
-                }
158
-            }
159
-        }
160
-
161
-        if ($this->caldavBackend instanceof Backend\SubscriptionSupport) {
162
-            foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) {
163
-                if ($subscription['uri'] === $name) {
164
-                    return new Subscriptions\Subscription($this->caldavBackend, $subscription);
165
-                }
166
-            }
167
-
168
-        }
169
-
170
-        throw new NotFound('Node with name \'' . $name . '\' could not be found');
171
-
172
-    }
173
-
174
-    /**
175
-     * Checks if a calendar exists.
176
-     *
177
-     * @param string $name
178
-     * @return bool
179
-     */
180
-    public function childExists($name) {
181
-
182
-        try {
183
-            return !!$this->getChild($name);
184
-        } catch (NotFound $e) {
185
-            return false;
186
-        }
187
-
188
-    }
189
-
190
-    /**
191
-     * Returns a list of calendars
192
-     *
193
-     * @return array
194
-     */
195
-    public function getChildren() {
196
-
197
-        $calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']);
198
-        $objs = [];
199
-        foreach ($calendars as $calendar) {
200
-            if ($this->caldavBackend instanceof Backend\SharingSupport) {
201
-                if (isset($calendar['{http://calendarserver.org/ns/}shared-url'])) {
202
-                    $objs[] = new SharedCalendar($this->caldavBackend, $calendar);
203
-                } else {
204
-                    $objs[] = new ShareableCalendar($this->caldavBackend, $calendar);
205
-                }
206
-            } else {
207
-                $objs[] = new Calendar($this->caldavBackend, $calendar);
208
-            }
209
-        }
210
-
211
-        if ($this->caldavBackend instanceof Backend\SchedulingSupport) {
212
-            $objs[] = new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']);
213
-            $objs[] = new Schedule\Outbox($this->principalInfo['uri']);
214
-        }
215
-
216
-        // We're adding a notifications node, if it's supported by the backend.
217
-        if ($this->caldavBackend instanceof Backend\NotificationSupport) {
218
-            $objs[] = new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
219
-        }
220
-
221
-        // If the backend supports subscriptions, we'll add those as well,
222
-        if ($this->caldavBackend instanceof Backend\SubscriptionSupport) {
223
-            foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) {
224
-                $objs[] = new Subscriptions\Subscription($this->caldavBackend, $subscription);
225
-            }
226
-        }
227
-
228
-        return $objs;
229
-
230
-    }
231
-
232
-    /**
233
-     * Creates a new calendar or subscription.
234
-     *
235
-     * @param string $name
236
-     * @param MkCol $mkCol
237
-     * @throws DAV\Exception\InvalidResourceType
238
-     * @return void
239
-     */
240
-    public function createExtendedCollection($name, MkCol $mkCol) {
241
-
242
-        $isCalendar = false;
243
-        $isSubscription = false;
244
-        foreach ($mkCol->getResourceType() as $rt) {
245
-            switch ($rt) {
246
-                case '{DAV:}collection' :
247
-                case '{http://calendarserver.org/ns/}shared-owner' :
248
-                    // ignore
249
-                    break;
250
-                case '{urn:ietf:params:xml:ns:caldav}calendar' :
251
-                    $isCalendar = true;
252
-                    break;
253
-                case '{http://calendarserver.org/ns/}subscribed' :
254
-                    $isSubscription = true;
255
-                    break;
256
-                default :
257
-                    throw new DAV\Exception\InvalidResourceType('Unknown resourceType: ' . $rt);
258
-            }
259
-        }
260
-
261
-        $properties = $mkCol->getRemainingValues();
262
-        $mkCol->setRemainingResultCode(201);
263
-
264
-        if ($isSubscription) {
265
-            if (!$this->caldavBackend instanceof Backend\SubscriptionSupport) {
266
-                throw new DAV\Exception\InvalidResourceType('This backend does not support subscriptions');
267
-            }
268
-            $this->caldavBackend->createSubscription($this->principalInfo['uri'], $name, $properties);
269
-
270
-        } elseif ($isCalendar) {
271
-            $this->caldavBackend->createCalendar($this->principalInfo['uri'], $name, $properties);
272
-
273
-        } else {
274
-            throw new DAV\Exception\InvalidResourceType('You can only create calendars and subscriptions in this collection');
275
-
276
-        }
277
-
278
-    }
279
-
280
-    /**
281
-     * Returns the owner principal
282
-     *
283
-     * This must be a url to a principal, or null if there's no owner
284
-     *
285
-     * @return string|null
286
-     */
287
-    public function getOwner() {
288
-
289
-        return $this->principalInfo['uri'];
290
-
291
-    }
292
-
293
-    /**
294
-     * Returns a group principal
295
-     *
296
-     * This must be a url to a principal, or null if there's no owner
297
-     *
298
-     * @return string|null
299
-     */
300
-    public function getGroup() {
301
-
302
-        return null;
303
-
304
-    }
305
-
306
-    /**
307
-     * Returns a list of ACE's for this node.
308
-     *
309
-     * Each ACE has the following properties:
310
-     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
311
-     *     currently the only supported privileges
312
-     *   * 'principal', a url to the principal who owns the node
313
-     *   * 'protected' (optional), indicating that this ACE is not allowed to
314
-     *      be updated.
315
-     *
316
-     * @return array
317
-     */
318
-    public function getACL() {
319
-
320
-        return [
321
-            [
322
-                'privilege' => '{DAV:}read',
323
-                'principal' => $this->principalInfo['uri'],
324
-                'protected' => true,
325
-            ],
326
-            [
327
-                'privilege' => '{DAV:}write',
328
-                'principal' => $this->principalInfo['uri'],
329
-                'protected' => true,
330
-            ],
331
-            [
332
-                'privilege' => '{DAV:}read',
333
-                'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write',
334
-                'protected' => true,
335
-            ],
336
-            [
337
-                'privilege' => '{DAV:}write',
338
-                'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write',
339
-                'protected' => true,
340
-            ],
341
-            [
342
-                'privilege' => '{DAV:}read',
343
-                'principal' => $this->principalInfo['uri'] . '/calendar-proxy-read',
344
-                'protected' => true,
345
-            ],
346
-
347
-        ];
348
-
349
-    }
350
-
351
-    /**
352
-     * Updates the ACL
353
-     *
354
-     * This method will receive a list of new ACE's.
355
-     *
356
-     * @param array $acl
357
-     * @return void
358
-     */
359
-    public function setACL(array $acl) {
360
-
361
-        throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
362
-
363
-    }
364
-
365
-    /**
366
-     * Returns the list of supported privileges for this node.
367
-     *
368
-     * The returned data structure is a list of nested privileges.
369
-     * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
370
-     * standard structure.
371
-     *
372
-     * If null is returned from this method, the default privilege set is used,
373
-     * which is fine for most common usecases.
374
-     *
375
-     * @return array|null
376
-     */
377
-    public function getSupportedPrivilegeSet() {
378
-
379
-        return null;
380
-
381
-    }
382
-
383
-    /**
384
-     * This method is called when a user replied to a request to share.
385
-     *
386
-     * This method should return the url of the newly created calendar if the
387
-     * share was accepted.
388
-     *
389
-     * @param string href The sharee who is replying (often a mailto: address)
390
-     * @param int status One of the SharingPlugin::STATUS_* constants
391
-     * @param string $calendarUri The url to the calendar thats being shared
392
-     * @param string $inReplyTo The unique id this message is a response to
393
-     * @param string $summary A description of the reply
394
-     * @return null|string
395
-     */
396
-    public function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null) {
397
-
398
-        if (!$this->caldavBackend instanceof Backend\SharingSupport) {
399
-            throw new DAV\Exception\NotImplemented('Sharing support is not implemented by this backend.');
400
-        }
401
-
402
-        return $this->caldavBackend->shareReply($href, $status, $calendarUri, $inReplyTo, $summary);
403
-
404
-    }
405
-
406
-    /**
407
-     * Searches through all of a users calendars and calendar objects to find
408
-     * an object with a specific UID.
409
-     *
410
-     * This method should return the path to this object, relative to the
411
-     * calendar home, so this path usually only contains two parts:
412
-     *
413
-     * calendarpath/objectpath.ics
414
-     *
415
-     * If the uid is not found, return null.
416
-     *
417
-     * This method should only consider * objects that the principal owns, so
418
-     * any calendars owned by other principals that also appear in this
419
-     * collection should be ignored.
420
-     *
421
-     * @param string $uid
422
-     * @return string|null
423
-     */
424
-    public function getCalendarObjectByUID($uid) {
425
-
426
-        return $this->caldavBackend->getCalendarObjectByUID($this->principalInfo['uri'], $uid);
427
-
428
-    }
25
+	/**
26
+	 * CalDAV backend
27
+	 *
28
+	 * @var Sabre\CalDAV\Backend\BackendInterface
29
+	 */
30
+	protected $caldavBackend;
31
+
32
+	/**
33
+	 * Principal information
34
+	 *
35
+	 * @var array
36
+	 */
37
+	protected $principalInfo;
38
+
39
+	/**
40
+	 * Constructor
41
+	 *
42
+	 * @param Backend\BackendInterface $caldavBackend
43
+	 * @param mixed $userUri
44
+	 */
45
+	public function __construct(Backend\BackendInterface $caldavBackend, $principalInfo) {
46
+
47
+		$this->caldavBackend = $caldavBackend;
48
+		$this->principalInfo = $principalInfo;
49
+
50
+	}
51
+
52
+	/**
53
+	 * Returns the name of this object
54
+	 *
55
+	 * @return string
56
+	 */
57
+	public function getName() {
58
+
59
+		list(, $name) = URLUtil::splitPath($this->principalInfo['uri']);
60
+		return $name;
61
+
62
+	}
63
+
64
+	/**
65
+	 * Updates the name of this object
66
+	 *
67
+	 * @param string $name
68
+	 * @return void
69
+	 */
70
+	public function setName($name) {
71
+
72
+		throw new DAV\Exception\Forbidden();
73
+
74
+	}
75
+
76
+	/**
77
+	 * Deletes this object
78
+	 *
79
+	 * @return void
80
+	 */
81
+	public function delete() {
82
+
83
+		throw new DAV\Exception\Forbidden();
84
+
85
+	}
86
+
87
+	/**
88
+	 * Returns the last modification date
89
+	 *
90
+	 * @return int
91
+	 */
92
+	public function getLastModified() {
93
+
94
+		return null;
95
+
96
+	}
97
+
98
+	/**
99
+	 * Creates a new file under this object.
100
+	 *
101
+	 * This is currently not allowed
102
+	 *
103
+	 * @param string $filename
104
+	 * @param resource $data
105
+	 * @return void
106
+	 */
107
+	public function createFile($filename, $data = null) {
108
+
109
+		throw new DAV\Exception\MethodNotAllowed('Creating new files in this collection is not supported');
110
+
111
+	}
112
+
113
+	/**
114
+	 * Creates a new directory under this object.
115
+	 *
116
+	 * This is currently not allowed.
117
+	 *
118
+	 * @param string $filename
119
+	 * @return void
120
+	 */
121
+	public function createDirectory($filename) {
122
+
123
+		throw new DAV\Exception\MethodNotAllowed('Creating new collections in this collection is not supported');
124
+
125
+	}
126
+
127
+	/**
128
+	 * Returns a single calendar, by name
129
+	 *
130
+	 * @param string $name
131
+	 * @return Calendar
132
+	 */
133
+	public function getChild($name) {
134
+
135
+		// Special nodes
136
+		if ($name === 'inbox' && $this->caldavBackend instanceof Backend\SchedulingSupport) {
137
+			return new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']);
138
+		}
139
+		if ($name === 'outbox' && $this->caldavBackend instanceof Backend\SchedulingSupport) {
140
+			return new Schedule\Outbox($this->principalInfo['uri']);
141
+		}
142
+		if ($name === 'notifications' && $this->caldavBackend instanceof Backend\NotificationSupport) {
143
+			return new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
144
+		}
145
+
146
+		// Calendars
147
+		foreach ($this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']) as $calendar) {
148
+			if ($calendar['uri'] === $name) {
149
+				if ($this->caldavBackend instanceof Backend\SharingSupport) {
150
+					if (isset($calendar['{http://calendarserver.org/ns/}shared-url'])) {
151
+						return new SharedCalendar($this->caldavBackend, $calendar);
152
+					} else {
153
+						return new ShareableCalendar($this->caldavBackend, $calendar);
154
+					}
155
+				} else {
156
+					return new Calendar($this->caldavBackend, $calendar);
157
+				}
158
+			}
159
+		}
160
+
161
+		if ($this->caldavBackend instanceof Backend\SubscriptionSupport) {
162
+			foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) {
163
+				if ($subscription['uri'] === $name) {
164
+					return new Subscriptions\Subscription($this->caldavBackend, $subscription);
165
+				}
166
+			}
167
+
168
+		}
169
+
170
+		throw new NotFound('Node with name \'' . $name . '\' could not be found');
171
+
172
+	}
173
+
174
+	/**
175
+	 * Checks if a calendar exists.
176
+	 *
177
+	 * @param string $name
178
+	 * @return bool
179
+	 */
180
+	public function childExists($name) {
181
+
182
+		try {
183
+			return !!$this->getChild($name);
184
+		} catch (NotFound $e) {
185
+			return false;
186
+		}
187
+
188
+	}
189
+
190
+	/**
191
+	 * Returns a list of calendars
192
+	 *
193
+	 * @return array
194
+	 */
195
+	public function getChildren() {
196
+
197
+		$calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']);
198
+		$objs = [];
199
+		foreach ($calendars as $calendar) {
200
+			if ($this->caldavBackend instanceof Backend\SharingSupport) {
201
+				if (isset($calendar['{http://calendarserver.org/ns/}shared-url'])) {
202
+					$objs[] = new SharedCalendar($this->caldavBackend, $calendar);
203
+				} else {
204
+					$objs[] = new ShareableCalendar($this->caldavBackend, $calendar);
205
+				}
206
+			} else {
207
+				$objs[] = new Calendar($this->caldavBackend, $calendar);
208
+			}
209
+		}
210
+
211
+		if ($this->caldavBackend instanceof Backend\SchedulingSupport) {
212
+			$objs[] = new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']);
213
+			$objs[] = new Schedule\Outbox($this->principalInfo['uri']);
214
+		}
215
+
216
+		// We're adding a notifications node, if it's supported by the backend.
217
+		if ($this->caldavBackend instanceof Backend\NotificationSupport) {
218
+			$objs[] = new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
219
+		}
220
+
221
+		// If the backend supports subscriptions, we'll add those as well,
222
+		if ($this->caldavBackend instanceof Backend\SubscriptionSupport) {
223
+			foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) {
224
+				$objs[] = new Subscriptions\Subscription($this->caldavBackend, $subscription);
225
+			}
226
+		}
227
+
228
+		return $objs;
229
+
230
+	}
231
+
232
+	/**
233
+	 * Creates a new calendar or subscription.
234
+	 *
235
+	 * @param string $name
236
+	 * @param MkCol $mkCol
237
+	 * @throws DAV\Exception\InvalidResourceType
238
+	 * @return void
239
+	 */
240
+	public function createExtendedCollection($name, MkCol $mkCol) {
241
+
242
+		$isCalendar = false;
243
+		$isSubscription = false;
244
+		foreach ($mkCol->getResourceType() as $rt) {
245
+			switch ($rt) {
246
+				case '{DAV:}collection' :
247
+				case '{http://calendarserver.org/ns/}shared-owner' :
248
+					// ignore
249
+					break;
250
+				case '{urn:ietf:params:xml:ns:caldav}calendar' :
251
+					$isCalendar = true;
252
+					break;
253
+				case '{http://calendarserver.org/ns/}subscribed' :
254
+					$isSubscription = true;
255
+					break;
256
+				default :
257
+					throw new DAV\Exception\InvalidResourceType('Unknown resourceType: ' . $rt);
258
+			}
259
+		}
260
+
261
+		$properties = $mkCol->getRemainingValues();
262
+		$mkCol->setRemainingResultCode(201);
263
+
264
+		if ($isSubscription) {
265
+			if (!$this->caldavBackend instanceof Backend\SubscriptionSupport) {
266
+				throw new DAV\Exception\InvalidResourceType('This backend does not support subscriptions');
267
+			}
268
+			$this->caldavBackend->createSubscription($this->principalInfo['uri'], $name, $properties);
269
+
270
+		} elseif ($isCalendar) {
271
+			$this->caldavBackend->createCalendar($this->principalInfo['uri'], $name, $properties);
272
+
273
+		} else {
274
+			throw new DAV\Exception\InvalidResourceType('You can only create calendars and subscriptions in this collection');
275
+
276
+		}
277
+
278
+	}
279
+
280
+	/**
281
+	 * Returns the owner principal
282
+	 *
283
+	 * This must be a url to a principal, or null if there's no owner
284
+	 *
285
+	 * @return string|null
286
+	 */
287
+	public function getOwner() {
288
+
289
+		return $this->principalInfo['uri'];
290
+
291
+	}
292
+
293
+	/**
294
+	 * Returns a group principal
295
+	 *
296
+	 * This must be a url to a principal, or null if there's no owner
297
+	 *
298
+	 * @return string|null
299
+	 */
300
+	public function getGroup() {
301
+
302
+		return null;
303
+
304
+	}
305
+
306
+	/**
307
+	 * Returns a list of ACE's for this node.
308
+	 *
309
+	 * Each ACE has the following properties:
310
+	 *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
311
+	 *     currently the only supported privileges
312
+	 *   * 'principal', a url to the principal who owns the node
313
+	 *   * 'protected' (optional), indicating that this ACE is not allowed to
314
+	 *      be updated.
315
+	 *
316
+	 * @return array
317
+	 */
318
+	public function getACL() {
319
+
320
+		return [
321
+			[
322
+				'privilege' => '{DAV:}read',
323
+				'principal' => $this->principalInfo['uri'],
324
+				'protected' => true,
325
+			],
326
+			[
327
+				'privilege' => '{DAV:}write',
328
+				'principal' => $this->principalInfo['uri'],
329
+				'protected' => true,
330
+			],
331
+			[
332
+				'privilege' => '{DAV:}read',
333
+				'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write',
334
+				'protected' => true,
335
+			],
336
+			[
337
+				'privilege' => '{DAV:}write',
338
+				'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write',
339
+				'protected' => true,
340
+			],
341
+			[
342
+				'privilege' => '{DAV:}read',
343
+				'principal' => $this->principalInfo['uri'] . '/calendar-proxy-read',
344
+				'protected' => true,
345
+			],
346
+
347
+		];
348
+
349
+	}
350
+
351
+	/**
352
+	 * Updates the ACL
353
+	 *
354
+	 * This method will receive a list of new ACE's.
355
+	 *
356
+	 * @param array $acl
357
+	 * @return void
358
+	 */
359
+	public function setACL(array $acl) {
360
+
361
+		throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
362
+
363
+	}
364
+
365
+	/**
366
+	 * Returns the list of supported privileges for this node.
367
+	 *
368
+	 * The returned data structure is a list of nested privileges.
369
+	 * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
370
+	 * standard structure.
371
+	 *
372
+	 * If null is returned from this method, the default privilege set is used,
373
+	 * which is fine for most common usecases.
374
+	 *
375
+	 * @return array|null
376
+	 */
377
+	public function getSupportedPrivilegeSet() {
378
+
379
+		return null;
380
+
381
+	}
382
+
383
+	/**
384
+	 * This method is called when a user replied to a request to share.
385
+	 *
386
+	 * This method should return the url of the newly created calendar if the
387
+	 * share was accepted.
388
+	 *
389
+	 * @param string href The sharee who is replying (often a mailto: address)
390
+	 * @param int status One of the SharingPlugin::STATUS_* constants
391
+	 * @param string $calendarUri The url to the calendar thats being shared
392
+	 * @param string $inReplyTo The unique id this message is a response to
393
+	 * @param string $summary A description of the reply
394
+	 * @return null|string
395
+	 */
396
+	public function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null) {
397
+
398
+		if (!$this->caldavBackend instanceof Backend\SharingSupport) {
399
+			throw new DAV\Exception\NotImplemented('Sharing support is not implemented by this backend.');
400
+		}
401
+
402
+		return $this->caldavBackend->shareReply($href, $status, $calendarUri, $inReplyTo, $summary);
403
+
404
+	}
405
+
406
+	/**
407
+	 * Searches through all of a users calendars and calendar objects to find
408
+	 * an object with a specific UID.
409
+	 *
410
+	 * This method should return the path to this object, relative to the
411
+	 * calendar home, so this path usually only contains two parts:
412
+	 *
413
+	 * calendarpath/objectpath.ics
414
+	 *
415
+	 * If the uid is not found, return null.
416
+	 *
417
+	 * This method should only consider * objects that the principal owns, so
418
+	 * any calendars owned by other principals that also appear in this
419
+	 * collection should be ignored.
420
+	 *
421
+	 * @param string $uid
422
+	 * @return string|null
423
+	 */
424
+	public function getCalendarObjectByUID($uid) {
425
+
426
+		return $this->caldavBackend->getCalendarObjectByUID($this->principalInfo['uri'], $uid);
427
+
428
+	}
429 429
 
430 430
 }
Please login to merge, or discard this patch.
Doc Comments   -1 removed lines patch added patch discarded remove patch
@@ -40,7 +40,6 @@
 block discarded – undo
40 40
      * Constructor
41 41
      *
42 42
      * @param Backend\BackendInterface $caldavBackend
43
-     * @param mixed $userUri
44 43
      */
45 44
     public function __construct(Backend\BackendInterface $caldavBackend, $principalInfo) {
46 45
 
Please login to merge, or discard this patch.
libraries/SabreDAV/CalDAV/CalendarQueryValidator.php 4 patches
Unused Use Statements   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -2,8 +2,8 @@
 block discarded – undo
2 2
 
3 3
 namespace Sabre\CalDAV;
4 4
 
5
-use Sabre\VObject;
6 5
 use DateTime;
6
+use Sabre\VObject;
7 7
 
8 8
 /**
9 9
  * CalendarQuery Validator
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -301,7 +301,7 @@  discard block
 block discarded – undo
301 301
                 if ($component->parent->name === 'VEVENT' && $component->parent->RRULE) {
302 302
 
303 303
                     // Fire up the iterator!
304
-                    $it = new VObject\Recur\EventIterator($component->parent->parent, (string)$component->parent->UID);
304
+                    $it = new VObject\Recur\EventIterator($component->parent->parent, (string) $component->parent->UID);
305 305
                     while ($it->valid()) {
306 306
                         $expandedEvent = $it->getEventObject();
307 307
 
@@ -317,7 +317,7 @@  discard block
 block discarded – undo
317 317
                                     return true;
318 318
                                 }
319 319
 
320
-                                if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') {
320
+                                if ((string) $expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') {
321 321
                                     // This is an alarm with a non-relative trigger
322 322
                                     // time, likely created by a buggy client. The
323 323
                                     // implication is that every alarm in this
Please login to merge, or discard this patch.
Indentation   +351 added lines, -351 removed lines patch added patch discarded remove patch
@@ -20,356 +20,356 @@
 block discarded – undo
20 20
  */
21 21
 class CalendarQueryValidator {
22 22
 
23
-    /**
24
-     * Verify if a list of filters applies to the calendar data object
25
-     *
26
-     * The list of filters must be formatted as parsed by \Sabre\CalDAV\CalendarQueryParser
27
-     *
28
-     * @param VObject\Component $vObject
29
-     * @param array $filters
30
-     * @return bool
31
-     */
32
-    public function validate(VObject\Component\VCalendar $vObject, array $filters) {
33
-
34
-        // The top level object is always a component filter.
35
-        // We'll parse it manually, as it's pretty simple.
36
-        if ($vObject->name !== $filters['name']) {
37
-            return false;
38
-        }
39
-
40
-        return
41
-            $this->validateCompFilters($vObject, $filters['comp-filters']) &&
42
-            $this->validatePropFilters($vObject, $filters['prop-filters']);
43
-
44
-
45
-    }
46
-
47
-    /**
48
-     * This method checks the validity of comp-filters.
49
-     *
50
-     * A list of comp-filters needs to be specified. Also the parent of the
51
-     * component we're checking should be specified, not the component to check
52
-     * itself.
53
-     *
54
-     * @param VObject\Component $parent
55
-     * @param array $filters
56
-     * @return bool
57
-     */
58
-    protected function validateCompFilters(VObject\Component $parent, array $filters) {
59
-
60
-        foreach ($filters as $filter) {
61
-
62
-            $isDefined = isset($parent->{$filter['name']});
63
-
64
-            if ($filter['is-not-defined']) {
65
-
66
-                if ($isDefined) {
67
-                    return false;
68
-                } else {
69
-                    continue;
70
-                }
71
-
72
-            }
73
-            if (!$isDefined) {
74
-                return false;
75
-            }
76
-
77
-            if ($filter['time-range']) {
78
-                foreach ($parent->{$filter['name']} as $subComponent) {
79
-                    if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
80
-                        continue 2;
81
-                    }
82
-                }
83
-                return false;
84
-            }
85
-
86
-            if (!$filter['comp-filters'] && !$filter['prop-filters']) {
87
-                continue;
88
-            }
89
-
90
-            // If there are sub-filters, we need to find at least one component
91
-            // for which the subfilters hold true.
92
-            foreach ($parent->{$filter['name']} as $subComponent) {
93
-
94
-                if (
95
-                    $this->validateCompFilters($subComponent, $filter['comp-filters']) &&
96
-                    $this->validatePropFilters($subComponent, $filter['prop-filters'])) {
97
-                        // We had a match, so this comp-filter succeeds
98
-                        continue 2;
99
-                }
100
-
101
-            }
102
-
103
-            // If we got here it means there were sub-comp-filters or
104
-            // sub-prop-filters and there was no match. This means this filter
105
-            // needs to return false.
106
-            return false;
107
-
108
-        }
109
-
110
-        // If we got here it means we got through all comp-filters alive so the
111
-        // filters were all true.
112
-        return true;
113
-
114
-    }
115
-
116
-    /**
117
-     * This method checks the validity of prop-filters.
118
-     *
119
-     * A list of prop-filters needs to be specified. Also the parent of the
120
-     * property we're checking should be specified, not the property to check
121
-     * itself.
122
-     *
123
-     * @param VObject\Component $parent
124
-     * @param array $filters
125
-     * @return bool
126
-     */
127
-    protected function validatePropFilters(VObject\Component $parent, array $filters) {
128
-
129
-        foreach ($filters as $filter) {
130
-
131
-            $isDefined = isset($parent->{$filter['name']});
132
-
133
-            if ($filter['is-not-defined']) {
134
-
135
-                if ($isDefined) {
136
-                    return false;
137
-                } else {
138
-                    continue;
139
-                }
140
-
141
-            }
142
-            if (!$isDefined) {
143
-                return false;
144
-            }
145
-
146
-            if ($filter['time-range']) {
147
-                foreach ($parent->{$filter['name']} as $subComponent) {
148
-                    if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
149
-                        continue 2;
150
-                    }
151
-                }
152
-                return false;
153
-            }
154
-
155
-            if (!$filter['param-filters'] && !$filter['text-match']) {
156
-                continue;
157
-            }
158
-
159
-            // If there are sub-filters, we need to find at least one property
160
-            // for which the subfilters hold true.
161
-            foreach ($parent->{$filter['name']} as $subComponent) {
162
-
163
-                if (
164
-                    $this->validateParamFilters($subComponent, $filter['param-filters']) &&
165
-                    (!$filter['text-match'] || $this->validateTextMatch($subComponent, $filter['text-match']))
166
-                ) {
167
-                    // We had a match, so this prop-filter succeeds
168
-                    continue 2;
169
-                }
170
-
171
-            }
172
-
173
-            // If we got here it means there were sub-param-filters or
174
-            // text-match filters and there was no match. This means the
175
-            // filter needs to return false.
176
-            return false;
177
-
178
-        }
179
-
180
-        // If we got here it means we got through all prop-filters alive so the
181
-        // filters were all true.
182
-        return true;
183
-
184
-    }
185
-
186
-    /**
187
-     * This method checks the validity of param-filters.
188
-     *
189
-     * A list of param-filters needs to be specified. Also the parent of the
190
-     * parameter we're checking should be specified, not the parameter to check
191
-     * itself.
192
-     *
193
-     * @param VObject\Property $parent
194
-     * @param array $filters
195
-     * @return bool
196
-     */
197
-    protected function validateParamFilters(VObject\Property $parent, array $filters) {
198
-
199
-        foreach ($filters as $filter) {
200
-
201
-            $isDefined = isset($parent[$filter['name']]);
202
-
203
-            if ($filter['is-not-defined']) {
204
-
205
-                if ($isDefined) {
206
-                    return false;
207
-                } else {
208
-                    continue;
209
-                }
210
-
211
-            }
212
-            if (!$isDefined) {
213
-                return false;
214
-            }
215
-
216
-            if (!$filter['text-match']) {
217
-                continue;
218
-            }
219
-
220
-            // If there are sub-filters, we need to find at least one parameter
221
-            // for which the subfilters hold true.
222
-            foreach ($parent[$filter['name']]->getParts() as $paramPart) {
223
-
224
-                if ($this->validateTextMatch($paramPart, $filter['text-match'])) {
225
-                    // We had a match, so this param-filter succeeds
226
-                    continue 2;
227
-                }
228
-
229
-            }
230
-
231
-            // If we got here it means there was a text-match filter and there
232
-            // were no matches. This means the filter needs to return false.
233
-            return false;
234
-
235
-        }
236
-
237
-        // If we got here it means we got through all param-filters alive so the
238
-        // filters were all true.
239
-        return true;
240
-
241
-    }
242
-
243
-    /**
244
-     * This method checks the validity of a text-match.
245
-     *
246
-     * A single text-match should be specified as well as the specific property
247
-     * or parameter we need to validate.
248
-     *
249
-     * @param VObject\Node|string $check Value to check against.
250
-     * @param array $textMatch
251
-     * @return bool
252
-     */
253
-    protected function validateTextMatch($check, array $textMatch) {
254
-
255
-        if ($check instanceof VObject\Node) {
256
-            $check = $check->getValue();
257
-        }
258
-
259
-        $isMatching = \Sabre\DAV\StringUtil::textMatch($check, $textMatch['value'], $textMatch['collation']);
260
-
261
-        return ($textMatch['negate-condition'] xor $isMatching);
262
-
263
-    }
264
-
265
-    /**
266
-     * Validates if a component matches the given time range.
267
-     *
268
-     * This is all based on the rules specified in rfc4791, which are quite
269
-     * complex.
270
-     *
271
-     * @param VObject\Node $component
272
-     * @param DateTime $start
273
-     * @param DateTime $end
274
-     * @return bool
275
-     */
276
-    protected function validateTimeRange(VObject\Node $component, $start, $end) {
277
-
278
-        if (is_null($start)) {
279
-            $start = new DateTime('1900-01-01');
280
-        }
281
-        if (is_null($end)) {
282
-            $end = new DateTime('3000-01-01');
283
-        }
284
-
285
-        switch ($component->name) {
286
-
287
-            case 'VEVENT' :
288
-            case 'VTODO' :
289
-            case 'VJOURNAL' :
290
-
291
-                return $component->isInTimeRange($start, $end);
292
-
293
-            case 'VALARM' :
294
-
295
-                // If the valarm is wrapped in a recurring event, we need to
296
-                // expand the recursions, and validate each.
297
-                //
298
-                // Our datamodel doesn't easily allow us to do this straight
299
-                // in the VALARM component code, so this is a hack, and an
300
-                // expensive one too.
301
-                if ($component->parent->name === 'VEVENT' && $component->parent->RRULE) {
302
-
303
-                    // Fire up the iterator!
304
-                    $it = new VObject\Recur\EventIterator($component->parent->parent, (string)$component->parent->UID);
305
-                    while ($it->valid()) {
306
-                        $expandedEvent = $it->getEventObject();
307
-
308
-                        // We need to check from these expanded alarms, which
309
-                        // one is the first to trigger. Based on this, we can
310
-                        // determine if we can 'give up' expanding events.
311
-                        $firstAlarm = null;
312
-                        if ($expandedEvent->VALARM !== null) {
313
-                            foreach ($expandedEvent->VALARM as $expandedAlarm) {
314
-
315
-                                $effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime();
316
-                                if ($expandedAlarm->isInTimeRange($start, $end)) {
317
-                                    return true;
318
-                                }
319
-
320
-                                if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') {
321
-                                    // This is an alarm with a non-relative trigger
322
-                                    // time, likely created by a buggy client. The
323
-                                    // implication is that every alarm in this
324
-                                    // recurring event trigger at the exact same
325
-                                    // time. It doesn't make sense to traverse
326
-                                    // further.
327
-                                } else {
328
-                                    // We store the first alarm as a means to
329
-                                    // figure out when we can stop traversing.
330
-                                    if (!$firstAlarm || $effectiveTrigger < $firstAlarm) {
331
-                                        $firstAlarm = $effectiveTrigger;
332
-                                    }
333
-                                }
334
-                            }
335
-                        }
336
-                        if (is_null($firstAlarm)) {
337
-                            // No alarm was found.
338
-                            //
339
-                            // Or technically: No alarm that will change for
340
-                            // every instance of the recurrence was found,
341
-                            // which means we can assume there was no match.
342
-                            return false;
343
-                        }
344
-                        if ($firstAlarm > $end) {
345
-                            return false;
346
-                        }
347
-                        $it->next();
348
-                    }
349
-                    return false;
350
-                } else {
351
-                    return $component->isInTimeRange($start, $end);
352
-                }
353
-
354
-            case 'VFREEBUSY' :
355
-                throw new \Sabre\DAV\Exception\NotImplemented('time-range filters are currently not supported on ' . $component->name . ' components');
356
-
357
-            case 'COMPLETED' :
358
-            case 'CREATED' :
359
-            case 'DTEND' :
360
-            case 'DTSTAMP' :
361
-            case 'DTSTART' :
362
-            case 'DUE' :
363
-            case 'LAST-MODIFIED' :
364
-                return ($start <= $component->getDateTime() && $end >= $component->getDateTime());
365
-
366
-
367
-
368
-            default :
369
-                throw new \Sabre\DAV\Exception\BadRequest('You cannot create a time-range filter on a ' . $component->name . ' component');
370
-
371
-        }
372
-
373
-    }
23
+	/**
24
+	 * Verify if a list of filters applies to the calendar data object
25
+	 *
26
+	 * The list of filters must be formatted as parsed by \Sabre\CalDAV\CalendarQueryParser
27
+	 *
28
+	 * @param VObject\Component $vObject
29
+	 * @param array $filters
30
+	 * @return bool
31
+	 */
32
+	public function validate(VObject\Component\VCalendar $vObject, array $filters) {
33
+
34
+		// The top level object is always a component filter.
35
+		// We'll parse it manually, as it's pretty simple.
36
+		if ($vObject->name !== $filters['name']) {
37
+			return false;
38
+		}
39
+
40
+		return
41
+			$this->validateCompFilters($vObject, $filters['comp-filters']) &&
42
+			$this->validatePropFilters($vObject, $filters['prop-filters']);
43
+
44
+
45
+	}
46
+
47
+	/**
48
+	 * This method checks the validity of comp-filters.
49
+	 *
50
+	 * A list of comp-filters needs to be specified. Also the parent of the
51
+	 * component we're checking should be specified, not the component to check
52
+	 * itself.
53
+	 *
54
+	 * @param VObject\Component $parent
55
+	 * @param array $filters
56
+	 * @return bool
57
+	 */
58
+	protected function validateCompFilters(VObject\Component $parent, array $filters) {
59
+
60
+		foreach ($filters as $filter) {
61
+
62
+			$isDefined = isset($parent->{$filter['name']});
63
+
64
+			if ($filter['is-not-defined']) {
65
+
66
+				if ($isDefined) {
67
+					return false;
68
+				} else {
69
+					continue;
70
+				}
71
+
72
+			}
73
+			if (!$isDefined) {
74
+				return false;
75
+			}
76
+
77
+			if ($filter['time-range']) {
78
+				foreach ($parent->{$filter['name']} as $subComponent) {
79
+					if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
80
+						continue 2;
81
+					}
82
+				}
83
+				return false;
84
+			}
85
+
86
+			if (!$filter['comp-filters'] && !$filter['prop-filters']) {
87
+				continue;
88
+			}
89
+
90
+			// If there are sub-filters, we need to find at least one component
91
+			// for which the subfilters hold true.
92
+			foreach ($parent->{$filter['name']} as $subComponent) {
93
+
94
+				if (
95
+					$this->validateCompFilters($subComponent, $filter['comp-filters']) &&
96
+					$this->validatePropFilters($subComponent, $filter['prop-filters'])) {
97
+						// We had a match, so this comp-filter succeeds
98
+						continue 2;
99
+				}
100
+
101
+			}
102
+
103
+			// If we got here it means there were sub-comp-filters or
104
+			// sub-prop-filters and there was no match. This means this filter
105
+			// needs to return false.
106
+			return false;
107
+
108
+		}
109
+
110
+		// If we got here it means we got through all comp-filters alive so the
111
+		// filters were all true.
112
+		return true;
113
+
114
+	}
115
+
116
+	/**
117
+	 * This method checks the validity of prop-filters.
118
+	 *
119
+	 * A list of prop-filters needs to be specified. Also the parent of the
120
+	 * property we're checking should be specified, not the property to check
121
+	 * itself.
122
+	 *
123
+	 * @param VObject\Component $parent
124
+	 * @param array $filters
125
+	 * @return bool
126
+	 */
127
+	protected function validatePropFilters(VObject\Component $parent, array $filters) {
128
+
129
+		foreach ($filters as $filter) {
130
+
131
+			$isDefined = isset($parent->{$filter['name']});
132
+
133
+			if ($filter['is-not-defined']) {
134
+
135
+				if ($isDefined) {
136
+					return false;
137
+				} else {
138
+					continue;
139
+				}
140
+
141
+			}
142
+			if (!$isDefined) {
143
+				return false;
144
+			}
145
+
146
+			if ($filter['time-range']) {
147
+				foreach ($parent->{$filter['name']} as $subComponent) {
148
+					if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
149
+						continue 2;
150
+					}
151
+				}
152
+				return false;
153
+			}
154
+
155
+			if (!$filter['param-filters'] && !$filter['text-match']) {
156
+				continue;
157
+			}
158
+
159
+			// If there are sub-filters, we need to find at least one property
160
+			// for which the subfilters hold true.
161
+			foreach ($parent->{$filter['name']} as $subComponent) {
162
+
163
+				if (
164
+					$this->validateParamFilters($subComponent, $filter['param-filters']) &&
165
+					(!$filter['text-match'] || $this->validateTextMatch($subComponent, $filter['text-match']))
166
+				) {
167
+					// We had a match, so this prop-filter succeeds
168
+					continue 2;
169
+				}
170
+
171
+			}
172
+
173
+			// If we got here it means there were sub-param-filters or
174
+			// text-match filters and there was no match. This means the
175
+			// filter needs to return false.
176
+			return false;
177
+
178
+		}
179
+
180
+		// If we got here it means we got through all prop-filters alive so the
181
+		// filters were all true.
182
+		return true;
183
+
184
+	}
185
+
186
+	/**
187
+	 * This method checks the validity of param-filters.
188
+	 *
189
+	 * A list of param-filters needs to be specified. Also the parent of the
190
+	 * parameter we're checking should be specified, not the parameter to check
191
+	 * itself.
192
+	 *
193
+	 * @param VObject\Property $parent
194
+	 * @param array $filters
195
+	 * @return bool
196
+	 */
197
+	protected function validateParamFilters(VObject\Property $parent, array $filters) {
198
+
199
+		foreach ($filters as $filter) {
200
+
201
+			$isDefined = isset($parent[$filter['name']]);
202
+
203
+			if ($filter['is-not-defined']) {
204
+
205
+				if ($isDefined) {
206
+					return false;
207
+				} else {
208
+					continue;
209
+				}
210
+
211
+			}
212
+			if (!$isDefined) {
213
+				return false;
214
+			}
215
+
216
+			if (!$filter['text-match']) {
217
+				continue;
218
+			}
219
+
220
+			// If there are sub-filters, we need to find at least one parameter
221
+			// for which the subfilters hold true.
222
+			foreach ($parent[$filter['name']]->getParts() as $paramPart) {
223
+
224
+				if ($this->validateTextMatch($paramPart, $filter['text-match'])) {
225
+					// We had a match, so this param-filter succeeds
226
+					continue 2;
227
+				}
228
+
229
+			}
230
+
231
+			// If we got here it means there was a text-match filter and there
232
+			// were no matches. This means the filter needs to return false.
233
+			return false;
234
+
235
+		}
236
+
237
+		// If we got here it means we got through all param-filters alive so the
238
+		// filters were all true.
239
+		return true;
240
+
241
+	}
242
+
243
+	/**
244
+	 * This method checks the validity of a text-match.
245
+	 *
246
+	 * A single text-match should be specified as well as the specific property
247
+	 * or parameter we need to validate.
248
+	 *
249
+	 * @param VObject\Node|string $check Value to check against.
250
+	 * @param array $textMatch
251
+	 * @return bool
252
+	 */
253
+	protected function validateTextMatch($check, array $textMatch) {
254
+
255
+		if ($check instanceof VObject\Node) {
256
+			$check = $check->getValue();
257
+		}
258
+
259
+		$isMatching = \Sabre\DAV\StringUtil::textMatch($check, $textMatch['value'], $textMatch['collation']);
260
+
261
+		return ($textMatch['negate-condition'] xor $isMatching);
262
+
263
+	}
264
+
265
+	/**
266
+	 * Validates if a component matches the given time range.
267
+	 *
268
+	 * This is all based on the rules specified in rfc4791, which are quite
269
+	 * complex.
270
+	 *
271
+	 * @param VObject\Node $component
272
+	 * @param DateTime $start
273
+	 * @param DateTime $end
274
+	 * @return bool
275
+	 */
276
+	protected function validateTimeRange(VObject\Node $component, $start, $end) {
277
+
278
+		if (is_null($start)) {
279
+			$start = new DateTime('1900-01-01');
280
+		}
281
+		if (is_null($end)) {
282
+			$end = new DateTime('3000-01-01');
283
+		}
284
+
285
+		switch ($component->name) {
286
+
287
+			case 'VEVENT' :
288
+			case 'VTODO' :
289
+			case 'VJOURNAL' :
290
+
291
+				return $component->isInTimeRange($start, $end);
292
+
293
+			case 'VALARM' :
294
+
295
+				// If the valarm is wrapped in a recurring event, we need to
296
+				// expand the recursions, and validate each.
297
+				//
298
+				// Our datamodel doesn't easily allow us to do this straight
299
+				// in the VALARM component code, so this is a hack, and an
300
+				// expensive one too.
301
+				if ($component->parent->name === 'VEVENT' && $component->parent->RRULE) {
302
+
303
+					// Fire up the iterator!
304
+					$it = new VObject\Recur\EventIterator($component->parent->parent, (string)$component->parent->UID);
305
+					while ($it->valid()) {
306
+						$expandedEvent = $it->getEventObject();
307
+
308
+						// We need to check from these expanded alarms, which
309
+						// one is the first to trigger. Based on this, we can
310
+						// determine if we can 'give up' expanding events.
311
+						$firstAlarm = null;
312
+						if ($expandedEvent->VALARM !== null) {
313
+							foreach ($expandedEvent->VALARM as $expandedAlarm) {
314
+
315
+								$effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime();
316
+								if ($expandedAlarm->isInTimeRange($start, $end)) {
317
+									return true;
318
+								}
319
+
320
+								if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') {
321
+									// This is an alarm with a non-relative trigger
322
+									// time, likely created by a buggy client. The
323
+									// implication is that every alarm in this
324
+									// recurring event trigger at the exact same
325
+									// time. It doesn't make sense to traverse
326
+									// further.
327
+								} else {
328
+									// We store the first alarm as a means to
329
+									// figure out when we can stop traversing.
330
+									if (!$firstAlarm || $effectiveTrigger < $firstAlarm) {
331
+										$firstAlarm = $effectiveTrigger;
332
+									}
333
+								}
334
+							}
335
+						}
336
+						if (is_null($firstAlarm)) {
337
+							// No alarm was found.
338
+							//
339
+							// Or technically: No alarm that will change for
340
+							// every instance of the recurrence was found,
341
+							// which means we can assume there was no match.
342
+							return false;
343
+						}
344
+						if ($firstAlarm > $end) {
345
+							return false;
346
+						}
347
+						$it->next();
348
+					}
349
+					return false;
350
+				} else {
351
+					return $component->isInTimeRange($start, $end);
352
+				}
353
+
354
+			case 'VFREEBUSY' :
355
+				throw new \Sabre\DAV\Exception\NotImplemented('time-range filters are currently not supported on ' . $component->name . ' components');
356
+
357
+			case 'COMPLETED' :
358
+			case 'CREATED' :
359
+			case 'DTEND' :
360
+			case 'DTSTAMP' :
361
+			case 'DTSTART' :
362
+			case 'DUE' :
363
+			case 'LAST-MODIFIED' :
364
+				return ($start <= $component->getDateTime() && $end >= $component->getDateTime());
365
+
366
+
367
+
368
+			default :
369
+				throw new \Sabre\DAV\Exception\BadRequest('You cannot create a time-range filter on a ' . $component->name . ' component');
370
+
371
+		}
372
+
373
+	}
374 374
 
375 375
 }
Please login to merge, or discard this patch.
Doc Comments   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -25,7 +25,7 @@
 block discarded – undo
25 25
      *
26 26
      * The list of filters must be formatted as parsed by \Sabre\CalDAV\CalendarQueryParser
27 27
      *
28
-     * @param VObject\Component $vObject
28
+     * @param VObject\Parser\Sabre\VObject\Document|null $vObject
29 29
      * @param array $filters
30 30
      * @return bool
31 31
      */
Please login to merge, or discard this patch.
libraries/SabreDAV/CalDAV/ICSExportPlugin.php 5 patches
Unused Use Statements   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -2,13 +2,13 @@
 block discarded – undo
2 2
 
3 3
 namespace Sabre\CalDAV;
4 4
 
5
+use DateTime;
5 6
 use DateTimeZone;
6 7
 use Sabre\DAV;
7
-use Sabre\VObject;
8
+use Sabre\DAV\Exception\BadRequest;
8 9
 use Sabre\HTTP\RequestInterface;
9 10
 use Sabre\HTTP\ResponseInterface;
10
-use Sabre\DAV\Exception\BadRequest;
11
-use DateTime;
11
+use Sabre\VObject;
12 12
 
13 13
 /**
14 14
  * ICS Exporter
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -306,7 +306,7 @@
 block discarded – undo
306 306
                     // VTIMEZONE is special, because we need to filter out the duplicates
307 307
                     case 'VTIMEZONE' :
308 308
                         // Naively just checking tzid.
309
-                        if (in_array((string)$child->TZID, $collectedTimezones)) continue;
309
+                        if (in_array((string) $child->TZID, $collectedTimezones)) continue;
310 310
 
311 311
                         $timezones[] = clone $child;
312 312
                         $collectedTimezones[] = $child->TZID;
Please login to merge, or discard this patch.
Indentation   +316 added lines, -316 removed lines patch added patch discarded remove patch
@@ -46,321 +46,321 @@
 block discarded – undo
46 46
  */
47 47
 class ICSExportPlugin extends DAV\ServerPlugin {
48 48
 
49
-    /**
50
-     * Reference to Server class
51
-     *
52
-     * @var \Sabre\DAV\Server
53
-     */
54
-    protected $server;
55
-
56
-    /**
57
-     * Initializes the plugin and registers event handlers
58
-     *
59
-     * @param \Sabre\DAV\Server $server
60
-     * @return void
61
-     */
62
-    public function initialize(DAV\Server $server) {
63
-
64
-        $this->server = $server;
65
-        $server->on('method:GET', [$this, 'httpGet'], 90);
66
-        $server->on('browserButtonActions', function($path, $node, &$actions) {
67
-            if ($node instanceof ICalendar) {
68
-                $actions .= '<a href="' . htmlspecialchars($path, ENT_QUOTES, 'UTF-8') . '?export"><span class="oi" data-glyph="calendar"></span></a>';
69
-            }
70
-        });
71
-
72
-    }
73
-
74
-    /**
75
-     * Intercepts GET requests on calendar urls ending with ?export.
76
-     *
77
-     * @param RequestInterface $request
78
-     * @param ResponseInterface $response
79
-     * @return bool
80
-     */
81
-    public function httpGet(RequestInterface $request, ResponseInterface $response) {
82
-
83
-        $queryParams = $request->getQueryParameters();
84
-        if (!array_key_exists('export', $queryParams)) return;
85
-
86
-        $path = $request->getPath();
87
-
88
-        $node = $this->server->getProperties($path, [
89
-            '{DAV:}resourcetype',
90
-            '{DAV:}displayname',
91
-            '{http://sabredav.org/ns}sync-token',
92
-            '{DAV:}sync-token',
93
-            '{http://apple.com/ns/ical/}calendar-color',
94
-        ]);
95
-
96
-        if (!isset($node['{DAV:}resourcetype']) || !$node['{DAV:}resourcetype']->is('{' . Plugin::NS_CALDAV . '}calendar')) {
97
-            return;
98
-        }
99
-        // Marking the transactionType, for logging purposes.
100
-        $this->server->transactionType = 'get-calendar-export';
101
-
102
-        $properties = $node;
103
-
104
-        $start = null;
105
-        $end = null;
106
-        $expand = false;
107
-        $componentType = false;
108
-        if (isset($queryParams['start'])) {
109
-            if (!ctype_digit($queryParams['start'])) {
110
-                throw new BadRequest('The start= parameter must contain a unix timestamp');
111
-            }
112
-            $start = DateTime::createFromFormat('U', $queryParams['start']);
113
-        }
114
-        if (isset($queryParams['end'])) {
115
-            if (!ctype_digit($queryParams['end'])) {
116
-                throw new BadRequest('The end= parameter must contain a unix timestamp');
117
-            }
118
-            $end = DateTime::createFromFormat('U', $queryParams['end']);
119
-        }
120
-        if (isset($queryParams['expand']) && !!$queryParams['expand']) {
121
-            if (!$start || !$end) {
122
-                throw new BadRequest('If you\'d like to expand recurrences, you must specify both a start= and end= parameter.');
123
-            }
124
-            $expand = true;
125
-            $componentType = 'VEVENT';
126
-        }
127
-        if (isset($queryParams['componentType'])) {
128
-            if (!in_array($queryParams['componentType'], ['VEVENT', 'VTODO', 'VJOURNAL'])) {
129
-                throw new BadRequest('You are not allowed to search for components of type: ' . $queryParams['componentType'] . ' here');
130
-            }
131
-            $componentType = $queryParams['componentType'];
132
-        }
133
-
134
-        $format = \Sabre\HTTP\Util::Negotiate(
135
-            $request->getHeader('Accept'),
136
-            [
137
-                'text/calendar',
138
-                'application/calendar+json',
139
-            ]
140
-        );
141
-
142
-        if (isset($queryParams['accept'])) {
143
-            if ($queryParams['accept'] === 'application/calendar+json' || $queryParams['accept'] === 'jcal') {
144
-                $format = 'application/calendar+json';
145
-            }
146
-        }
147
-        if (!$format) {
148
-            $format = 'text/calendar';
149
-        }
150
-
151
-        $this->generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, $response);
152
-
153
-        // Returning false to break the event chain
154
-        return false;
155
-
156
-    }
157
-
158
-    /**
159
-     * This method is responsible for generating the actual, full response.
160
-     *
161
-     * @param string $path
162
-     * @param DateTime|null $start
163
-     * @param DateTime|null $end
164
-     * @param bool $expand
165
-     * @param string $componentType
166
-     * @param string $format
167
-     * @param array $properties
168
-     * @param ResponseInterface $response
169
-     */
170
-    protected function generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, ResponseInterface $response) {
171
-
172
-        $calDataProp = '{' . Plugin::NS_CALDAV . '}calendar-data';
173
-
174
-        $blobs = [];
175
-        if ($start || $end || $componentType) {
176
-
177
-            // If there was a start or end filter, we need to enlist
178
-            // calendarQuery for speed.
179
-            $calendarNode = $this->server->tree->getNodeForPath($path);
180
-            $queryResult = $calendarNode->calendarQuery([
181
-                'name'         => 'VCALENDAR',
182
-                'comp-filters' => [
183
-                    [
184
-                        'name'           => $componentType,
185
-                        'comp-filters'   => [],
186
-                        'prop-filters'   => [],
187
-                        'is-not-defined' => false,
188
-                        'time-range'     => [
189
-                            'start' => $start,
190
-                            'end'   => $end,
191
-                        ],
192
-                    ],
193
-                ],
194
-                'prop-filters'   => [],
195
-                'is-not-defined' => false,
196
-                'time-range'     => null,
197
-            ]);
198
-
199
-            // queryResult is just a list of base urls. We need to prefix the
200
-            // calendar path.
201
-            $queryResult = array_map(
202
-                function($item) use ($path) {
203
-                    return $path . '/' . $item;
204
-                },
205
-                $queryResult
206
-            );
207
-            $nodes = $this->server->getPropertiesForMultiplePaths($queryResult, [$calDataProp]);
208
-            unset($queryResult);
209
-
210
-        } else {
211
-            $nodes = $this->server->getPropertiesForPath($path, [$calDataProp], 1);
212
-        }
213
-
214
-        // Flattening the arrays
215
-        foreach ($nodes as $node) {
216
-            if (isset($node[200][$calDataProp])) {
217
-                $blobs[$node['href']] = $node[200][$calDataProp];
218
-            }
219
-        }
220
-        unset($nodes);
221
-
222
-        $mergedCalendar = $this->mergeObjects(
223
-            $properties,
224
-            $blobs
225
-        );
226
-
227
-        if ($expand) {
228
-            $calendarTimeZone = null;
229
-            // We're expanding, and for that we need to figure out the
230
-            // calendar's timezone.
231
-            $tzProp = '{' . Plugin::NS_CALDAV . '}calendar-timezone';
232
-            $tzResult = $this->server->getProperties($path, [$tzProp]);
233
-            if (isset($tzResult[$tzProp])) {
234
-                // This property contains a VCALENDAR with a single
235
-                // VTIMEZONE.
236
-                $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
237
-                $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
238
-                // Destroy circular references to PHP will GC the object.
239
-                $vtimezoneObj->destroy();
240
-                unset($vtimezoneObj);
241
-            } else {
242
-                // Defaulting to UTC.
243
-                $calendarTimeZone = new DateTimeZone('UTC');
244
-            }
245
-
246
-            $mergedCalendar = $mergedCalendar->expand($start, $end, $calendarTimeZone);
247
-        }
248
-
249
-        $response->setHeader('Content-Type', $format);
250
-
251
-        switch ($format) {
252
-            case 'text/calendar' :
253
-                $mergedCalendar = $mergedCalendar->serialize();
254
-                break;
255
-            case 'application/calendar+json' :
256
-                $mergedCalendar = json_encode($mergedCalendar->jsonSerialize());
257
-                break;
258
-        }
259
-
260
-        $response->setStatus(200);
261
-        $response->setBody($mergedCalendar);
262
-
263
-    }
264
-
265
-    /**
266
-     * Merges all calendar objects, and builds one big iCalendar blob.
267
-     *
268
-     * @param array $properties Some CalDAV properties
269
-     * @param array $inputObjects
270
-     * @return VObject\Component\VCalendar
271
-     */
272
-    public function mergeObjects(array $properties, array $inputObjects) {
273
-
274
-        $calendar = new VObject\Component\VCalendar();
275
-        $calendar->version = '2.0';
276
-        if (DAV\Server::$exposeVersion) {
277
-            $calendar->prodid = '-//SabreDAV//SabreDAV ' . DAV\Version::VERSION . '//EN';
278
-        } else {
279
-            $calendar->prodid = '-//SabreDAV//SabreDAV//EN';
280
-        }
281
-        if (isset($properties['{DAV:}displayname'])) {
282
-            $calendar->{'X-WR-CALNAME'} = $properties['{DAV:}displayname'];
283
-        }
284
-        if (isset($properties['{http://apple.com/ns/ical/}calendar-color'])) {
285
-            $calendar->{'X-APPLE-CALENDAR-COLOR'} = $properties['{http://apple.com/ns/ical/}calendar-color'];
286
-        }
287
-
288
-        $collectedTimezones = [];
289
-
290
-        $timezones = [];
291
-        $objects = [];
292
-
293
-        foreach ($inputObjects as $href => $inputObject) {
294
-
295
-            $nodeComp = VObject\Reader::read($inputObject);
296
-
297
-            foreach ($nodeComp->children() as $child) {
298
-
299
-                switch ($child->name) {
300
-                    case 'VEVENT' :
301
-                    case 'VTODO' :
302
-                    case 'VJOURNAL' :
303
-                        $objects[] = clone $child;
304
-                        break;
305
-
306
-                    // VTIMEZONE is special, because we need to filter out the duplicates
307
-                    case 'VTIMEZONE' :
308
-                        // Naively just checking tzid.
309
-                        if (in_array((string)$child->TZID, $collectedTimezones)) continue;
310
-
311
-                        $timezones[] = clone $child;
312
-                        $collectedTimezones[] = $child->TZID;
313
-                        break;
314
-
315
-                }
316
-
317
-            }
318
-            // Destroy circular references to PHP will GC the object.
319
-            $nodeComp->destroy();
320
-            unset($nodeComp);
321
-
322
-        }
323
-
324
-        foreach ($timezones as $tz) $calendar->add($tz);
325
-        foreach ($objects as $obj) $calendar->add($obj);
326
-
327
-        return $calendar;
328
-
329
-    }
330
-
331
-    /**
332
-     * Returns a plugin name.
333
-     *
334
-     * Using this name other plugins will be able to access other plugins
335
-     * using \Sabre\DAV\Server::getPlugin
336
-     *
337
-     * @return string
338
-     */
339
-    public function getPluginName() {
340
-
341
-        return 'ics-export';
342
-
343
-    }
344
-
345
-    /**
346
-     * Returns a bunch of meta-data about the plugin.
347
-     *
348
-     * Providing this information is optional, and is mainly displayed by the
349
-     * Browser plugin.
350
-     *
351
-     * The description key in the returned array may contain html and will not
352
-     * be sanitized.
353
-     *
354
-     * @return array
355
-     */
356
-    public function getPluginInfo() {
357
-
358
-        return [
359
-            'name'        => $this->getPluginName(),
360
-            'description' => 'Adds the ability to export CalDAV calendars as a single iCalendar file.',
361
-            'link'        => 'http://sabre.io/dav/ics-export-plugin/',
362
-        ];
363
-
364
-    }
49
+	/**
50
+	 * Reference to Server class
51
+	 *
52
+	 * @var \Sabre\DAV\Server
53
+	 */
54
+	protected $server;
55
+
56
+	/**
57
+	 * Initializes the plugin and registers event handlers
58
+	 *
59
+	 * @param \Sabre\DAV\Server $server
60
+	 * @return void
61
+	 */
62
+	public function initialize(DAV\Server $server) {
63
+
64
+		$this->server = $server;
65
+		$server->on('method:GET', [$this, 'httpGet'], 90);
66
+		$server->on('browserButtonActions', function($path, $node, &$actions) {
67
+			if ($node instanceof ICalendar) {
68
+				$actions .= '<a href="' . htmlspecialchars($path, ENT_QUOTES, 'UTF-8') . '?export"><span class="oi" data-glyph="calendar"></span></a>';
69
+			}
70
+		});
71
+
72
+	}
73
+
74
+	/**
75
+	 * Intercepts GET requests on calendar urls ending with ?export.
76
+	 *
77
+	 * @param RequestInterface $request
78
+	 * @param ResponseInterface $response
79
+	 * @return bool
80
+	 */
81
+	public function httpGet(RequestInterface $request, ResponseInterface $response) {
82
+
83
+		$queryParams = $request->getQueryParameters();
84
+		if (!array_key_exists('export', $queryParams)) return;
85
+
86
+		$path = $request->getPath();
87
+
88
+		$node = $this->server->getProperties($path, [
89
+			'{DAV:}resourcetype',
90
+			'{DAV:}displayname',
91
+			'{http://sabredav.org/ns}sync-token',
92
+			'{DAV:}sync-token',
93
+			'{http://apple.com/ns/ical/}calendar-color',
94
+		]);
95
+
96
+		if (!isset($node['{DAV:}resourcetype']) || !$node['{DAV:}resourcetype']->is('{' . Plugin::NS_CALDAV . '}calendar')) {
97
+			return;
98
+		}
99
+		// Marking the transactionType, for logging purposes.
100
+		$this->server->transactionType = 'get-calendar-export';
101
+
102
+		$properties = $node;
103
+
104
+		$start = null;
105
+		$end = null;
106
+		$expand = false;
107
+		$componentType = false;
108
+		if (isset($queryParams['start'])) {
109
+			if (!ctype_digit($queryParams['start'])) {
110
+				throw new BadRequest('The start= parameter must contain a unix timestamp');
111
+			}
112
+			$start = DateTime::createFromFormat('U', $queryParams['start']);
113
+		}
114
+		if (isset($queryParams['end'])) {
115
+			if (!ctype_digit($queryParams['end'])) {
116
+				throw new BadRequest('The end= parameter must contain a unix timestamp');
117
+			}
118
+			$end = DateTime::createFromFormat('U', $queryParams['end']);
119
+		}
120
+		if (isset($queryParams['expand']) && !!$queryParams['expand']) {
121
+			if (!$start || !$end) {
122
+				throw new BadRequest('If you\'d like to expand recurrences, you must specify both a start= and end= parameter.');
123
+			}
124
+			$expand = true;
125
+			$componentType = 'VEVENT';
126
+		}
127
+		if (isset($queryParams['componentType'])) {
128
+			if (!in_array($queryParams['componentType'], ['VEVENT', 'VTODO', 'VJOURNAL'])) {
129
+				throw new BadRequest('You are not allowed to search for components of type: ' . $queryParams['componentType'] . ' here');
130
+			}
131
+			$componentType = $queryParams['componentType'];
132
+		}
133
+
134
+		$format = \Sabre\HTTP\Util::Negotiate(
135
+			$request->getHeader('Accept'),
136
+			[
137
+				'text/calendar',
138
+				'application/calendar+json',
139
+			]
140
+		);
141
+
142
+		if (isset($queryParams['accept'])) {
143
+			if ($queryParams['accept'] === 'application/calendar+json' || $queryParams['accept'] === 'jcal') {
144
+				$format = 'application/calendar+json';
145
+			}
146
+		}
147
+		if (!$format) {
148
+			$format = 'text/calendar';
149
+		}
150
+
151
+		$this->generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, $response);
152
+
153
+		// Returning false to break the event chain
154
+		return false;
155
+
156
+	}
157
+
158
+	/**
159
+	 * This method is responsible for generating the actual, full response.
160
+	 *
161
+	 * @param string $path
162
+	 * @param DateTime|null $start
163
+	 * @param DateTime|null $end
164
+	 * @param bool $expand
165
+	 * @param string $componentType
166
+	 * @param string $format
167
+	 * @param array $properties
168
+	 * @param ResponseInterface $response
169
+	 */
170
+	protected function generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, ResponseInterface $response) {
171
+
172
+		$calDataProp = '{' . Plugin::NS_CALDAV . '}calendar-data';
173
+
174
+		$blobs = [];
175
+		if ($start || $end || $componentType) {
176
+
177
+			// If there was a start or end filter, we need to enlist
178
+			// calendarQuery for speed.
179
+			$calendarNode = $this->server->tree->getNodeForPath($path);
180
+			$queryResult = $calendarNode->calendarQuery([
181
+				'name'         => 'VCALENDAR',
182
+				'comp-filters' => [
183
+					[
184
+						'name'           => $componentType,
185
+						'comp-filters'   => [],
186
+						'prop-filters'   => [],
187
+						'is-not-defined' => false,
188
+						'time-range'     => [
189
+							'start' => $start,
190
+							'end'   => $end,
191
+						],
192
+					],
193
+				],
194
+				'prop-filters'   => [],
195
+				'is-not-defined' => false,
196
+				'time-range'     => null,
197
+			]);
198
+
199
+			// queryResult is just a list of base urls. We need to prefix the
200
+			// calendar path.
201
+			$queryResult = array_map(
202
+				function($item) use ($path) {
203
+					return $path . '/' . $item;
204
+				},
205
+				$queryResult
206
+			);
207
+			$nodes = $this->server->getPropertiesForMultiplePaths($queryResult, [$calDataProp]);
208
+			unset($queryResult);
209
+
210
+		} else {
211
+			$nodes = $this->server->getPropertiesForPath($path, [$calDataProp], 1);
212
+		}
213
+
214
+		// Flattening the arrays
215
+		foreach ($nodes as $node) {
216
+			if (isset($node[200][$calDataProp])) {
217
+				$blobs[$node['href']] = $node[200][$calDataProp];
218
+			}
219
+		}
220
+		unset($nodes);
221
+
222
+		$mergedCalendar = $this->mergeObjects(
223
+			$properties,
224
+			$blobs
225
+		);
226
+
227
+		if ($expand) {
228
+			$calendarTimeZone = null;
229
+			// We're expanding, and for that we need to figure out the
230
+			// calendar's timezone.
231
+			$tzProp = '{' . Plugin::NS_CALDAV . '}calendar-timezone';
232
+			$tzResult = $this->server->getProperties($path, [$tzProp]);
233
+			if (isset($tzResult[$tzProp])) {
234
+				// This property contains a VCALENDAR with a single
235
+				// VTIMEZONE.
236
+				$vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
237
+				$calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
238
+				// Destroy circular references to PHP will GC the object.
239
+				$vtimezoneObj->destroy();
240
+				unset($vtimezoneObj);
241
+			} else {
242
+				// Defaulting to UTC.
243
+				$calendarTimeZone = new DateTimeZone('UTC');
244
+			}
245
+
246
+			$mergedCalendar = $mergedCalendar->expand($start, $end, $calendarTimeZone);
247
+		}
248
+
249
+		$response->setHeader('Content-Type', $format);
250
+
251
+		switch ($format) {
252
+			case 'text/calendar' :
253
+				$mergedCalendar = $mergedCalendar->serialize();
254
+				break;
255
+			case 'application/calendar+json' :
256
+				$mergedCalendar = json_encode($mergedCalendar->jsonSerialize());
257
+				break;
258
+		}
259
+
260
+		$response->setStatus(200);
261
+		$response->setBody($mergedCalendar);
262
+
263
+	}
264
+
265
+	/**
266
+	 * Merges all calendar objects, and builds one big iCalendar blob.
267
+	 *
268
+	 * @param array $properties Some CalDAV properties
269
+	 * @param array $inputObjects
270
+	 * @return VObject\Component\VCalendar
271
+	 */
272
+	public function mergeObjects(array $properties, array $inputObjects) {
273
+
274
+		$calendar = new VObject\Component\VCalendar();
275
+		$calendar->version = '2.0';
276
+		if (DAV\Server::$exposeVersion) {
277
+			$calendar->prodid = '-//SabreDAV//SabreDAV ' . DAV\Version::VERSION . '//EN';
278
+		} else {
279
+			$calendar->prodid = '-//SabreDAV//SabreDAV//EN';
280
+		}
281
+		if (isset($properties['{DAV:}displayname'])) {
282
+			$calendar->{'X-WR-CALNAME'} = $properties['{DAV:}displayname'];
283
+		}
284
+		if (isset($properties['{http://apple.com/ns/ical/}calendar-color'])) {
285
+			$calendar->{'X-APPLE-CALENDAR-COLOR'} = $properties['{http://apple.com/ns/ical/}calendar-color'];
286
+		}
287
+
288
+		$collectedTimezones = [];
289
+
290
+		$timezones = [];
291
+		$objects = [];
292
+
293
+		foreach ($inputObjects as $href => $inputObject) {
294
+
295
+			$nodeComp = VObject\Reader::read($inputObject);
296
+
297
+			foreach ($nodeComp->children() as $child) {
298
+
299
+				switch ($child->name) {
300
+					case 'VEVENT' :
301
+					case 'VTODO' :
302
+					case 'VJOURNAL' :
303
+						$objects[] = clone $child;
304
+						break;
305
+
306
+					// VTIMEZONE is special, because we need to filter out the duplicates
307
+					case 'VTIMEZONE' :
308
+						// Naively just checking tzid.
309
+						if (in_array((string)$child->TZID, $collectedTimezones)) continue;
310
+
311
+						$timezones[] = clone $child;
312
+						$collectedTimezones[] = $child->TZID;
313
+						break;
314
+
315
+				}
316
+
317
+			}
318
+			// Destroy circular references to PHP will GC the object.
319
+			$nodeComp->destroy();
320
+			unset($nodeComp);
321
+
322
+		}
323
+
324
+		foreach ($timezones as $tz) $calendar->add($tz);
325
+		foreach ($objects as $obj) $calendar->add($obj);
326
+
327
+		return $calendar;
328
+
329
+	}
330
+
331
+	/**
332
+	 * Returns a plugin name.
333
+	 *
334
+	 * Using this name other plugins will be able to access other plugins
335
+	 * using \Sabre\DAV\Server::getPlugin
336
+	 *
337
+	 * @return string
338
+	 */
339
+	public function getPluginName() {
340
+
341
+		return 'ics-export';
342
+
343
+	}
344
+
345
+	/**
346
+	 * Returns a bunch of meta-data about the plugin.
347
+	 *
348
+	 * Providing this information is optional, and is mainly displayed by the
349
+	 * Browser plugin.
350
+	 *
351
+	 * The description key in the returned array may contain html and will not
352
+	 * be sanitized.
353
+	 *
354
+	 * @return array
355
+	 */
356
+	public function getPluginInfo() {
357
+
358
+		return [
359
+			'name'        => $this->getPluginName(),
360
+			'description' => 'Adds the ability to export CalDAV calendars as a single iCalendar file.',
361
+			'link'        => 'http://sabre.io/dav/ics-export-plugin/',
362
+		];
363
+
364
+	}
365 365
 
366 366
 }
Please login to merge, or discard this patch.
Doc Comments   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -76,7 +76,7 @@
 block discarded – undo
76 76
      *
77 77
      * @param RequestInterface $request
78 78
      * @param ResponseInterface $response
79
-     * @return bool
79
+     * @return null|false
80 80
      */
81 81
     public function httpGet(RequestInterface $request, ResponseInterface $response) {
82 82
 
Please login to merge, or discard this patch.
Braces   +12 added lines, -4 removed lines patch added patch discarded remove patch
@@ -81,7 +81,9 @@  discard block
 block discarded – undo
81 81
     public function httpGet(RequestInterface $request, ResponseInterface $response) {
82 82
 
83 83
         $queryParams = $request->getQueryParameters();
84
-        if (!array_key_exists('export', $queryParams)) return;
84
+        if (!array_key_exists('export', $queryParams)) {
85
+        	return;
86
+        }
85 87
 
86 88
         $path = $request->getPath();
87 89
 
@@ -306,7 +308,9 @@  discard block
 block discarded – undo
306 308
                     // VTIMEZONE is special, because we need to filter out the duplicates
307 309
                     case 'VTIMEZONE' :
308 310
                         // Naively just checking tzid.
309
-                        if (in_array((string)$child->TZID, $collectedTimezones)) continue;
311
+                        if (in_array((string)$child->TZID, $collectedTimezones)) {
312
+                        	continue;
313
+                        }
310 314
 
311 315
                         $timezones[] = clone $child;
312 316
                         $collectedTimezones[] = $child->TZID;
@@ -321,8 +325,12 @@  discard block
 block discarded – undo
321 325
 
322 326
         }
323 327
 
324
-        foreach ($timezones as $tz) $calendar->add($tz);
325
-        foreach ($objects as $obj) $calendar->add($obj);
328
+        foreach ($timezones as $tz) {
329
+        	$calendar->add($tz);
330
+        }
331
+        foreach ($objects as $obj) {
332
+        	$calendar->add($obj);
333
+        }
326 334
 
327 335
         return $calendar;
328 336
 
Please login to merge, or discard this patch.
libraries/SabreDAV/CalDAV/Notifications/Collection.php 2 patches
Unused Use Statements   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -2,8 +2,8 @@
 block discarded – undo
2 2
 
3 3
 namespace Sabre\CalDAV\Notifications;
4 4
 
5
-use Sabre\DAV;
6 5
 use Sabre\CalDAV;
6
+use Sabre\DAV;
7 7
 use Sabre\DAVACL;
8 8
 
9 9
 /**
Please login to merge, or discard this patch.
Indentation   +147 added lines, -147 removed lines patch added patch discarded remove patch
@@ -22,152 +22,152 @@
 block discarded – undo
22 22
  */
23 23
 class Collection extends DAV\Collection implements ICollection, DAVACL\IACL {
24 24
 
25
-    /**
26
-     * The notification backend
27
-     *
28
-     * @var Sabre\CalDAV\Backend\NotificationSupport
29
-     */
30
-    protected $caldavBackend;
31
-
32
-    /**
33
-     * Principal uri
34
-     *
35
-     * @var string
36
-     */
37
-    protected $principalUri;
38
-
39
-    /**
40
-     * Constructor
41
-     *
42
-     * @param CalDAV\Backend\NotificationSupport $caldavBackend
43
-     * @param string $principalUri
44
-     */
45
-    public function __construct(CalDAV\Backend\NotificationSupport $caldavBackend, $principalUri) {
46
-
47
-        $this->caldavBackend = $caldavBackend;
48
-        $this->principalUri = $principalUri;
49
-
50
-    }
51
-
52
-    /**
53
-     * Returns all notifications for a principal
54
-     *
55
-     * @return array
56
-     */
57
-    public function getChildren() {
58
-
59
-        $children = [];
60
-        $notifications = $this->caldavBackend->getNotificationsForPrincipal($this->principalUri);
61
-
62
-        foreach ($notifications as $notification) {
63
-
64
-            $children[] = new Node(
65
-                $this->caldavBackend,
66
-                $this->principalUri,
67
-                $notification
68
-            );
69
-        }
70
-
71
-        return $children;
72
-
73
-    }
74
-
75
-    /**
76
-     * Returns the name of this object
77
-     *
78
-     * @return string
79
-     */
80
-    public function getName() {
81
-
82
-        return 'notifications';
83
-
84
-    }
85
-
86
-    /**
87
-     * Returns the owner principal
88
-     *
89
-     * This must be a url to a principal, or null if there's no owner
90
-     *
91
-     * @return string|null
92
-     */
93
-    public function getOwner() {
94
-
95
-        return $this->principalUri;
96
-
97
-    }
98
-
99
-    /**
100
-     * Returns a group principal
101
-     *
102
-     * This must be a url to a principal, or null if there's no owner
103
-     *
104
-     * @return string|null
105
-     */
106
-    public function getGroup() {
107
-
108
-        return null;
109
-
110
-    }
111
-
112
-    /**
113
-     * Returns a list of ACE's for this node.
114
-     *
115
-     * Each ACE has the following properties:
116
-     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
117
-     *     currently the only supported privileges
118
-     *   * 'principal', a url to the principal who owns the node
119
-     *   * 'protected' (optional), indicating that this ACE is not allowed to
120
-     *      be updated.
121
-     *
122
-     * @return array
123
-     */
124
-    public function getACL() {
125
-
126
-        return [
127
-            [
128
-                'principal' => $this->getOwner(),
129
-                'privilege' => '{DAV:}read',
130
-                'protected' => true,
131
-            ],
132
-            [
133
-                'principal' => $this->getOwner(),
134
-                'privilege' => '{DAV:}write',
135
-                'protected' => true,
136
-            ]
137
-        ];
138
-
139
-    }
140
-
141
-    /**
142
-     * Updates the ACL
143
-     *
144
-     * This method will receive a list of new ACE's as an array argument.
145
-     *
146
-     * @param array $acl
147
-     * @return void
148
-     */
149
-    public function setACL(array $acl) {
150
-
151
-        throw new DAV\Exception\NotImplemented('Updating ACLs is not implemented here');
152
-
153
-    }
154
-
155
-    /**
156
-     * Returns the list of supported privileges for this node.
157
-     *
158
-     * The returned data structure is a list of nested privileges.
159
-     * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
160
-     * standard structure.
161
-     *
162
-     * If null is returned from this method, the default privilege set is used,
163
-     * which is fine for most common usecases.
164
-     *
165
-     * @return array|null
166
-     */
167
-    public function getSupportedPrivilegeSet() {
168
-
169
-        return null;
170
-
171
-    }
25
+	/**
26
+	 * The notification backend
27
+	 *
28
+	 * @var Sabre\CalDAV\Backend\NotificationSupport
29
+	 */
30
+	protected $caldavBackend;
31
+
32
+	/**
33
+	 * Principal uri
34
+	 *
35
+	 * @var string
36
+	 */
37
+	protected $principalUri;
38
+
39
+	/**
40
+	 * Constructor
41
+	 *
42
+	 * @param CalDAV\Backend\NotificationSupport $caldavBackend
43
+	 * @param string $principalUri
44
+	 */
45
+	public function __construct(CalDAV\Backend\NotificationSupport $caldavBackend, $principalUri) {
46
+
47
+		$this->caldavBackend = $caldavBackend;
48
+		$this->principalUri = $principalUri;
49
+
50
+	}
51
+
52
+	/**
53
+	 * Returns all notifications for a principal
54
+	 *
55
+	 * @return array
56
+	 */
57
+	public function getChildren() {
58
+
59
+		$children = [];
60
+		$notifications = $this->caldavBackend->getNotificationsForPrincipal($this->principalUri);
61
+
62
+		foreach ($notifications as $notification) {
63
+
64
+			$children[] = new Node(
65
+				$this->caldavBackend,
66
+				$this->principalUri,
67
+				$notification
68
+			);
69
+		}
70
+
71
+		return $children;
72
+
73
+	}
74
+
75
+	/**
76
+	 * Returns the name of this object
77
+	 *
78
+	 * @return string
79
+	 */
80
+	public function getName() {
81
+
82
+		return 'notifications';
83
+
84
+	}
85
+
86
+	/**
87
+	 * Returns the owner principal
88
+	 *
89
+	 * This must be a url to a principal, or null if there's no owner
90
+	 *
91
+	 * @return string|null
92
+	 */
93
+	public function getOwner() {
94
+
95
+		return $this->principalUri;
96
+
97
+	}
98
+
99
+	/**
100
+	 * Returns a group principal
101
+	 *
102
+	 * This must be a url to a principal, or null if there's no owner
103
+	 *
104
+	 * @return string|null
105
+	 */
106
+	public function getGroup() {
107
+
108
+		return null;
109
+
110
+	}
111
+
112
+	/**
113
+	 * Returns a list of ACE's for this node.
114
+	 *
115
+	 * Each ACE has the following properties:
116
+	 *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
117
+	 *     currently the only supported privileges
118
+	 *   * 'principal', a url to the principal who owns the node
119
+	 *   * 'protected' (optional), indicating that this ACE is not allowed to
120
+	 *      be updated.
121
+	 *
122
+	 * @return array
123
+	 */
124
+	public function getACL() {
125
+
126
+		return [
127
+			[
128
+				'principal' => $this->getOwner(),
129
+				'privilege' => '{DAV:}read',
130
+				'protected' => true,
131
+			],
132
+			[
133
+				'principal' => $this->getOwner(),
134
+				'privilege' => '{DAV:}write',
135
+				'protected' => true,
136
+			]
137
+		];
138
+
139
+	}
140
+
141
+	/**
142
+	 * Updates the ACL
143
+	 *
144
+	 * This method will receive a list of new ACE's as an array argument.
145
+	 *
146
+	 * @param array $acl
147
+	 * @return void
148
+	 */
149
+	public function setACL(array $acl) {
150
+
151
+		throw new DAV\Exception\NotImplemented('Updating ACLs is not implemented here');
152
+
153
+	}
154
+
155
+	/**
156
+	 * Returns the list of supported privileges for this node.
157
+	 *
158
+	 * The returned data structure is a list of nested privileges.
159
+	 * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
160
+	 * standard structure.
161
+	 *
162
+	 * If null is returned from this method, the default privilege set is used,
163
+	 * which is fine for most common usecases.
164
+	 *
165
+	 * @return array|null
166
+	 */
167
+	public function getSupportedPrivilegeSet() {
168
+
169
+		return null;
170
+
171
+	}
172 172
 
173 173
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/CalDAV/Notifications/Node.php 3 patches
Unused Use Statements   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -2,9 +2,9 @@
 block discarded – undo
2 2
 
3 3
 namespace Sabre\CalDAV\Notifications;
4 4
 
5
-use Sabre\DAV;
6 5
 use Sabre\CalDAV;
7 6
 use Sabre\CalDAV\Xml\Notification\NotificationInterface;
7
+use Sabre\DAV;
8 8
 use Sabre\DAVACL;
9 9
 
10 10
 /**
Please login to merge, or discard this patch.
Indentation   +169 added lines, -169 removed lines patch added patch discarded remove patch
@@ -20,174 +20,174 @@
 block discarded – undo
20 20
  */
21 21
 class Node extends DAV\File implements INode, DAVACL\IACL {
22 22
 
23
-    /**
24
-     * The notification backend
25
-     *
26
-     * @var Sabre\CalDAV\Backend\NotificationSupport
27
-     */
28
-    protected $caldavBackend;
29
-
30
-    /**
31
-     * The actual notification
32
-     *
33
-     * @var Sabre\CalDAV\Notifications\INotificationType
34
-     */
35
-    protected $notification;
36
-
37
-    /**
38
-     * Owner principal of the notification
39
-     *
40
-     * @var string
41
-     */
42
-    protected $principalUri;
43
-
44
-    /**
45
-     * Constructor
46
-     *
47
-     * @param CalDAV\Backend\NotificationSupport $caldavBackend
48
-     * @param string $principalUri
49
-     * @param NotificationInterface $notification
50
-     */
51
-    public function __construct(CalDAV\Backend\NotificationSupport $caldavBackend, $principalUri, NotificationInterface $notification) {
52
-
53
-        $this->caldavBackend = $caldavBackend;
54
-        $this->principalUri = $principalUri;
55
-        $this->notification = $notification;
56
-
57
-    }
58
-
59
-    /**
60
-     * Returns the path name for this notification
61
-     *
62
-     * @return id
63
-     */
64
-    public function getName() {
65
-
66
-        return $this->notification->getId() . '.xml';
67
-
68
-    }
69
-
70
-    /**
71
-     * Returns the etag for the notification.
72
-     *
73
-     * The etag must be surrounded by litteral double-quotes.
74
-     *
75
-     * @return string
76
-     */
77
-    public function getETag() {
78
-
79
-        return $this->notification->getETag();
80
-
81
-    }
82
-
83
-    /**
84
-     * This method must return an xml element, using the
85
-     * Sabre\CalDAV\Notifications\INotificationType classes.
86
-     *
87
-     * @return INotificationType
88
-     */
89
-    public function getNotificationType() {
90
-
91
-        return $this->notification;
92
-
93
-    }
94
-
95
-    /**
96
-     * Deletes this notification
97
-     *
98
-     * @return void
99
-     */
100
-    public function delete() {
101
-
102
-        $this->caldavBackend->deleteNotification($this->getOwner(), $this->notification);
103
-
104
-    }
105
-
106
-    /**
107
-     * Returns the owner principal
108
-     *
109
-     * This must be a url to a principal, or null if there's no owner
110
-     *
111
-     * @return string|null
112
-     */
113
-    public function getOwner() {
114
-
115
-        return $this->principalUri;
116
-
117
-    }
118
-
119
-    /**
120
-     * Returns a group principal
121
-     *
122
-     * This must be a url to a principal, or null if there's no owner
123
-     *
124
-     * @return string|null
125
-     */
126
-    public function getGroup() {
127
-
128
-        return null;
129
-
130
-    }
131
-
132
-    /**
133
-     * Returns a list of ACE's for this node.
134
-     *
135
-     * Each ACE has the following properties:
136
-     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
137
-     *     currently the only supported privileges
138
-     *   * 'principal', a url to the principal who owns the node
139
-     *   * 'protected' (optional), indicating that this ACE is not allowed to
140
-     *      be updated.
141
-     *
142
-     * @return array
143
-     */
144
-    public function getACL() {
145
-
146
-        return [
147
-            [
148
-                'principal' => $this->getOwner(),
149
-                'privilege' => '{DAV:}read',
150
-                'protected' => true,
151
-            ],
152
-            [
153
-                'principal' => $this->getOwner(),
154
-                'privilege' => '{DAV:}write',
155
-                'protected' => true,
156
-            ]
157
-        ];
158
-
159
-    }
160
-
161
-    /**
162
-     * Updates the ACL
163
-     *
164
-     * This method will receive a list of new ACE's as an array argument.
165
-     *
166
-     * @param array $acl
167
-     * @return void
168
-     */
169
-    public function setACL(array $acl) {
170
-
171
-        throw new DAV\Exception\NotImplemented('Updating ACLs is not implemented here');
172
-
173
-    }
174
-
175
-    /**
176
-     * Returns the list of supported privileges for this node.
177
-     *
178
-     * The returned data structure is a list of nested privileges.
179
-     * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
180
-     * standard structure.
181
-     *
182
-     * If null is returned from this method, the default privilege set is used,
183
-     * which is fine for most common usecases.
184
-     *
185
-     * @return array|null
186
-     */
187
-    public function getSupportedPrivilegeSet() {
188
-
189
-        return null;
190
-
191
-    }
23
+	/**
24
+	 * The notification backend
25
+	 *
26
+	 * @var Sabre\CalDAV\Backend\NotificationSupport
27
+	 */
28
+	protected $caldavBackend;
29
+
30
+	/**
31
+	 * The actual notification
32
+	 *
33
+	 * @var Sabre\CalDAV\Notifications\INotificationType
34
+	 */
35
+	protected $notification;
36
+
37
+	/**
38
+	 * Owner principal of the notification
39
+	 *
40
+	 * @var string
41
+	 */
42
+	protected $principalUri;
43
+
44
+	/**
45
+	 * Constructor
46
+	 *
47
+	 * @param CalDAV\Backend\NotificationSupport $caldavBackend
48
+	 * @param string $principalUri
49
+	 * @param NotificationInterface $notification
50
+	 */
51
+	public function __construct(CalDAV\Backend\NotificationSupport $caldavBackend, $principalUri, NotificationInterface $notification) {
52
+
53
+		$this->caldavBackend = $caldavBackend;
54
+		$this->principalUri = $principalUri;
55
+		$this->notification = $notification;
56
+
57
+	}
58
+
59
+	/**
60
+	 * Returns the path name for this notification
61
+	 *
62
+	 * @return id
63
+	 */
64
+	public function getName() {
65
+
66
+		return $this->notification->getId() . '.xml';
67
+
68
+	}
69
+
70
+	/**
71
+	 * Returns the etag for the notification.
72
+	 *
73
+	 * The etag must be surrounded by litteral double-quotes.
74
+	 *
75
+	 * @return string
76
+	 */
77
+	public function getETag() {
78
+
79
+		return $this->notification->getETag();
80
+
81
+	}
82
+
83
+	/**
84
+	 * This method must return an xml element, using the
85
+	 * Sabre\CalDAV\Notifications\INotificationType classes.
86
+	 *
87
+	 * @return INotificationType
88
+	 */
89
+	public function getNotificationType() {
90
+
91
+		return $this->notification;
92
+
93
+	}
94
+
95
+	/**
96
+	 * Deletes this notification
97
+	 *
98
+	 * @return void
99
+	 */
100
+	public function delete() {
101
+
102
+		$this->caldavBackend->deleteNotification($this->getOwner(), $this->notification);
103
+
104
+	}
105
+
106
+	/**
107
+	 * Returns the owner principal
108
+	 *
109
+	 * This must be a url to a principal, or null if there's no owner
110
+	 *
111
+	 * @return string|null
112
+	 */
113
+	public function getOwner() {
114
+
115
+		return $this->principalUri;
116
+
117
+	}
118
+
119
+	/**
120
+	 * Returns a group principal
121
+	 *
122
+	 * This must be a url to a principal, or null if there's no owner
123
+	 *
124
+	 * @return string|null
125
+	 */
126
+	public function getGroup() {
127
+
128
+		return null;
129
+
130
+	}
131
+
132
+	/**
133
+	 * Returns a list of ACE's for this node.
134
+	 *
135
+	 * Each ACE has the following properties:
136
+	 *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
137
+	 *     currently the only supported privileges
138
+	 *   * 'principal', a url to the principal who owns the node
139
+	 *   * 'protected' (optional), indicating that this ACE is not allowed to
140
+	 *      be updated.
141
+	 *
142
+	 * @return array
143
+	 */
144
+	public function getACL() {
145
+
146
+		return [
147
+			[
148
+				'principal' => $this->getOwner(),
149
+				'privilege' => '{DAV:}read',
150
+				'protected' => true,
151
+			],
152
+			[
153
+				'principal' => $this->getOwner(),
154
+				'privilege' => '{DAV:}write',
155
+				'protected' => true,
156
+			]
157
+		];
158
+
159
+	}
160
+
161
+	/**
162
+	 * Updates the ACL
163
+	 *
164
+	 * This method will receive a list of new ACE's as an array argument.
165
+	 *
166
+	 * @param array $acl
167
+	 * @return void
168
+	 */
169
+	public function setACL(array $acl) {
170
+
171
+		throw new DAV\Exception\NotImplemented('Updating ACLs is not implemented here');
172
+
173
+	}
174
+
175
+	/**
176
+	 * Returns the list of supported privileges for this node.
177
+	 *
178
+	 * The returned data structure is a list of nested privileges.
179
+	 * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
180
+	 * standard structure.
181
+	 *
182
+	 * If null is returned from this method, the default privilege set is used,
183
+	 * which is fine for most common usecases.
184
+	 *
185
+	 * @return array|null
186
+	 */
187
+	public function getSupportedPrivilegeSet() {
188
+
189
+		return null;
190
+
191
+	}
192 192
 
193 193
 }
Please login to merge, or discard this patch.
Doc Comments   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -59,7 +59,7 @@
 block discarded – undo
59 59
     /**
60 60
      * Returns the path name for this notification
61 61
      *
62
-     * @return id
62
+     * @return string
63 63
      */
64 64
     public function getName() {
65 65
 
Please login to merge, or discard this patch.
libraries/SabreDAV/CalDAV/Notifications/Plugin.php 5 patches
Unused Use Statements   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -3,11 +3,11 @@
 block discarded – undo
3 3
 namespace Sabre\CalDAV\Notifications;
4 4
 
5 5
 use Sabre\DAV;
6
-use Sabre\DAV\PropFind;
6
+use Sabre\DAVACL;
7 7
 use Sabre\DAV\INode as BaseINode;
8
-use Sabre\DAV\ServerPlugin;
8
+use Sabre\DAV\PropFind;
9 9
 use Sabre\DAV\Server;
10
-use Sabre\DAVACL;
10
+use Sabre\DAV\ServerPlugin;
11 11
 use Sabre\HTTP\RequestInterface;
12 12
 use Sabre\HTTP\ResponseInterface;
13 13
 
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -67,7 +67,7 @@
 block discarded – undo
67 67
 
68 68
         $this->server = $server;
69 69
         $server->on('method:GET', [$this, 'httpGet'], 90);
70
-        $server->on('propFind',   [$this, 'propFind']);
70
+        $server->on('propFind', [$this, 'propFind']);
71 71
 
72 72
         $server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs';
73 73
         $server->resourceTypeMapping['\\Sabre\\CalDAV\\Notifications\\ICollection'] = '{' . self::NS_CALENDARSERVER . '}notification';
Please login to merge, or discard this patch.
Braces   +3 added lines, -2 removed lines patch added patch discarded remove patch
@@ -135,8 +135,9 @@
 block discarded – undo
135 135
             return;
136 136
         }
137 137
 
138
-        if (!$node instanceof INode)
139
-            return;
138
+        if (!$node instanceof INode) {
139
+                    return;
140
+        }
140 141
 
141 142
         $writer = $this->server->xml->getWriter();
142 143
         $writer->contextUri = $this->server->getBaseUri();
Please login to merge, or discard this patch.
Indentation   +150 added lines, -150 removed lines patch added patch discarded remove patch
@@ -26,155 +26,155 @@
 block discarded – undo
26 26
  */
27 27
 class Plugin extends ServerPlugin {
28 28
 
29
-    /**
30
-     * This is the namespace for the proprietary calendarserver extensions
31
-     */
32
-    const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
33
-
34
-    /**
35
-     * Reference to the main server object.
36
-     *
37
-     * @var Server
38
-     */
39
-    protected $server;
40
-
41
-    /**
42
-     * Returns a plugin name.
43
-     *
44
-     * Using this name other plugins will be able to access other plugins
45
-     * using \Sabre\DAV\Server::getPlugin
46
-     *
47
-     * @return string
48
-     */
49
-    public function getPluginName() {
50
-
51
-        return 'notifications';
52
-
53
-    }
54
-
55
-    /**
56
-     * This initializes the plugin.
57
-     *
58
-     * This function is called by Sabre\DAV\Server, after
59
-     * addPlugin is called.
60
-     *
61
-     * This method should set up the required event subscriptions.
62
-     *
63
-     * @param Server $server
64
-     * @return void
65
-     */
66
-    public function initialize(Server $server) {
67
-
68
-        $this->server = $server;
69
-        $server->on('method:GET', [$this, 'httpGet'], 90);
70
-        $server->on('propFind',   [$this, 'propFind']);
71
-
72
-        $server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs';
73
-        $server->resourceTypeMapping['\\Sabre\\CalDAV\\Notifications\\ICollection'] = '{' . self::NS_CALENDARSERVER . '}notification';
74
-
75
-        array_push($server->protectedProperties,
76
-            '{' . self::NS_CALENDARSERVER . '}notification-URL',
77
-            '{' . self::NS_CALENDARSERVER . '}notificationtype'
78
-        );
79
-
80
-    }
81
-
82
-    /**
83
-     * PropFind
84
-     *
85
-     * @param PropFind $propFind
86
-     * @param BaseINode $node
87
-     * @return void
88
-     */
89
-    public function propFind(PropFind $propFind, BaseINode $node) {
90
-
91
-        $caldavPlugin = $this->server->getPlugin('caldav');
92
-
93
-        if ($node instanceof DAVACL\IPrincipal) {
94
-
95
-            $principalUrl = $node->getPrincipalUrl();
96
-
97
-            // notification-URL property
98
-            $propFind->handle('{' . self::NS_CALENDARSERVER . '}notification-URL', function() use ($principalUrl, $caldavPlugin) {
99
-
100
-                $notificationPath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl) . '/notifications/';
101
-                return new DAV\Xml\Property\Href($notificationPath);
102
-
103
-            });
104
-
105
-        }
106
-
107
-        if ($node instanceof INode) {
108
-
109
-            $propFind->handle(
110
-                '{' . self::NS_CALENDARSERVER . '}notificationtype',
111
-                [$node, 'getNotificationType']
112
-            );
113
-
114
-        }
115
-
116
-    }
117
-
118
-    /**
119
-     * This event is triggered before the usual GET request handler.
120
-     *
121
-     * We use this to intercept GET calls to notification nodes, and return the
122
-     * proper response.
123
-     *
124
-     * @param RequestInterface $request
125
-     * @param ResponseInterface $response
126
-     * @return void
127
-     */
128
-    public function httpGet(RequestInterface $request, ResponseInterface $response) {
129
-
130
-        $path = $request->getPath();
131
-
132
-        try {
133
-            $node = $this->server->tree->getNodeForPath($path);
134
-        } catch (DAV\Exception\NotFound $e) {
135
-            return;
136
-        }
137
-
138
-        if (!$node instanceof INode)
139
-            return;
140
-
141
-        $writer = $this->server->xml->getWriter();
142
-        $writer->contextUri = $this->server->getBaseUri();
143
-        $writer->openMemory();
144
-        $writer->startDocument('1.0', 'UTF-8');
145
-        $writer->startElement('{http://calendarserver.org/ns/}notification');
146
-        $node->getNotificationType()->xmlSerializeFull($writer);
147
-        $writer->endElement();
148
-
149
-        $response->setHeader('Content-Type', 'application/xml');
150
-        $response->setHeader('ETag', $node->getETag());
151
-        $response->setStatus(200);
152
-        $response->setBody($writer->outputMemory());
153
-
154
-        // Return false to break the event chain.
155
-        return false;
156
-
157
-    }
158
-
159
-    /**
160
-     * Returns a bunch of meta-data about the plugin.
161
-     *
162
-     * Providing this information is optional, and is mainly displayed by the
163
-     * Browser plugin.
164
-     *
165
-     * The description key in the returned array may contain html and will not
166
-     * be sanitized.
167
-     *
168
-     * @return array
169
-     */
170
-    public function getPluginInfo() {
171
-
172
-        return [
173
-            'name'        => $this->getPluginName(),
174
-            'description' => 'Adds support for caldav-notifications, which is required to enable caldav-sharing.',
175
-            'link'        => 'http://sabre.io/dav/caldav-sharing/',
176
-        ];
177
-
178
-    }
29
+	/**
30
+	 * This is the namespace for the proprietary calendarserver extensions
31
+	 */
32
+	const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
33
+
34
+	/**
35
+	 * Reference to the main server object.
36
+	 *
37
+	 * @var Server
38
+	 */
39
+	protected $server;
40
+
41
+	/**
42
+	 * Returns a plugin name.
43
+	 *
44
+	 * Using this name other plugins will be able to access other plugins
45
+	 * using \Sabre\DAV\Server::getPlugin
46
+	 *
47
+	 * @return string
48
+	 */
49
+	public function getPluginName() {
50
+
51
+		return 'notifications';
52
+
53
+	}
54
+
55
+	/**
56
+	 * This initializes the plugin.
57
+	 *
58
+	 * This function is called by Sabre\DAV\Server, after
59
+	 * addPlugin is called.
60
+	 *
61
+	 * This method should set up the required event subscriptions.
62
+	 *
63
+	 * @param Server $server
64
+	 * @return void
65
+	 */
66
+	public function initialize(Server $server) {
67
+
68
+		$this->server = $server;
69
+		$server->on('method:GET', [$this, 'httpGet'], 90);
70
+		$server->on('propFind',   [$this, 'propFind']);
71
+
72
+		$server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs';
73
+		$server->resourceTypeMapping['\\Sabre\\CalDAV\\Notifications\\ICollection'] = '{' . self::NS_CALENDARSERVER . '}notification';
74
+
75
+		array_push($server->protectedProperties,
76
+			'{' . self::NS_CALENDARSERVER . '}notification-URL',
77
+			'{' . self::NS_CALENDARSERVER . '}notificationtype'
78
+		);
79
+
80
+	}
81
+
82
+	/**
83
+	 * PropFind
84
+	 *
85
+	 * @param PropFind $propFind
86
+	 * @param BaseINode $node
87
+	 * @return void
88
+	 */
89
+	public function propFind(PropFind $propFind, BaseINode $node) {
90
+
91
+		$caldavPlugin = $this->server->getPlugin('caldav');
92
+
93
+		if ($node instanceof DAVACL\IPrincipal) {
94
+
95
+			$principalUrl = $node->getPrincipalUrl();
96
+
97
+			// notification-URL property
98
+			$propFind->handle('{' . self::NS_CALENDARSERVER . '}notification-URL', function() use ($principalUrl, $caldavPlugin) {
99
+
100
+				$notificationPath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl) . '/notifications/';
101
+				return new DAV\Xml\Property\Href($notificationPath);
102
+
103
+			});
104
+
105
+		}
106
+
107
+		if ($node instanceof INode) {
108
+
109
+			$propFind->handle(
110
+				'{' . self::NS_CALENDARSERVER . '}notificationtype',
111
+				[$node, 'getNotificationType']
112
+			);
113
+
114
+		}
115
+
116
+	}
117
+
118
+	/**
119
+	 * This event is triggered before the usual GET request handler.
120
+	 *
121
+	 * We use this to intercept GET calls to notification nodes, and return the
122
+	 * proper response.
123
+	 *
124
+	 * @param RequestInterface $request
125
+	 * @param ResponseInterface $response
126
+	 * @return void
127
+	 */
128
+	public function httpGet(RequestInterface $request, ResponseInterface $response) {
129
+
130
+		$path = $request->getPath();
131
+
132
+		try {
133
+			$node = $this->server->tree->getNodeForPath($path);
134
+		} catch (DAV\Exception\NotFound $e) {
135
+			return;
136
+		}
137
+
138
+		if (!$node instanceof INode)
139
+			return;
140
+
141
+		$writer = $this->server->xml->getWriter();
142
+		$writer->contextUri = $this->server->getBaseUri();
143
+		$writer->openMemory();
144
+		$writer->startDocument('1.0', 'UTF-8');
145
+		$writer->startElement('{http://calendarserver.org/ns/}notification');
146
+		$node->getNotificationType()->xmlSerializeFull($writer);
147
+		$writer->endElement();
148
+
149
+		$response->setHeader('Content-Type', 'application/xml');
150
+		$response->setHeader('ETag', $node->getETag());
151
+		$response->setStatus(200);
152
+		$response->setBody($writer->outputMemory());
153
+
154
+		// Return false to break the event chain.
155
+		return false;
156
+
157
+	}
158
+
159
+	/**
160
+	 * Returns a bunch of meta-data about the plugin.
161
+	 *
162
+	 * Providing this information is optional, and is mainly displayed by the
163
+	 * Browser plugin.
164
+	 *
165
+	 * The description key in the returned array may contain html and will not
166
+	 * be sanitized.
167
+	 *
168
+	 * @return array
169
+	 */
170
+	public function getPluginInfo() {
171
+
172
+		return [
173
+			'name'        => $this->getPluginName(),
174
+			'description' => 'Adds support for caldav-notifications, which is required to enable caldav-sharing.',
175
+			'link'        => 'http://sabre.io/dav/caldav-sharing/',
176
+		];
177
+
178
+	}
179 179
 
180 180
 }
Please login to merge, or discard this patch.
Doc Comments   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -123,7 +123,7 @@
 block discarded – undo
123 123
      *
124 124
      * @param RequestInterface $request
125 125
      * @param ResponseInterface $response
126
-     * @return void
126
+     * @return null|false
127 127
      */
128 128
     public function httpGet(RequestInterface $request, ResponseInterface $response) {
129 129
 
Please login to merge, or discard this patch.
libraries/SabreDAV/CalDAV/Plugin.php 5 patches
Unused Use Statements   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -4,15 +4,15 @@
 block discarded – undo
4 4
 
5 5
 use DateTimeZone;
6 6
 use Sabre\DAV;
7
+use Sabre\DAVACL;
7 8
 use Sabre\DAV\Exception\BadRequest;
8 9
 use Sabre\DAV\MkCol;
9 10
 use Sabre\DAV\Xml\Property\Href;
10
-use Sabre\DAVACL;
11
-use Sabre\VObject;
12 11
 use Sabre\HTTP;
13
-use Sabre\Uri;
14 12
 use Sabre\HTTP\RequestInterface;
15 13
 use Sabre\HTTP\ResponseInterface;
14
+use Sabre\Uri;
15
+use Sabre\VObject;
16 16
 
17 17
 /**
18 18
  * CalDAV plugin
Please login to merge, or discard this patch.
Spacing   +10 added lines, -10 removed lines patch added patch discarded remove patch
@@ -179,13 +179,13 @@  discard block
 block discarded – undo
179 179
 
180 180
         $this->server = $server;
181 181
 
182
-        $server->on('method:MKCALENDAR',   [$this, 'httpMkCalendar']);
183
-        $server->on('report',              [$this, 'report']);
184
-        $server->on('propFind',            [$this, 'propFind']);
185
-        $server->on('onHTMLActionsPanel',  [$this, 'htmlActionsPanel']);
186
-        $server->on('beforeCreateFile',    [$this, 'beforeCreateFile']);
187
-        $server->on('beforeWriteContent',  [$this, 'beforeWriteContent']);
188
-        $server->on('afterMethod:GET',     [$this, 'httpAfterGET']);
182
+        $server->on('method:MKCALENDAR', [$this, 'httpMkCalendar']);
183
+        $server->on('report', [$this, 'report']);
184
+        $server->on('propFind', [$this, 'propFind']);
185
+        $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
186
+        $server->on('beforeCreateFile', [$this, 'beforeCreateFile']);
187
+        $server->on('beforeWriteContent', [$this, 'beforeWriteContent']);
188
+        $server->on('afterMethod:GET', [$this, 'httpAfterGET']);
189 189
 
190 190
         $server->xml->namespaceMap[self::NS_CALDAV] = 'cal';
191 191
         $server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs';
@@ -294,7 +294,7 @@  discard block
 block discarded – undo
294 294
         if (isset($properties['{DAV:}resourcetype'])) {
295 295
             $resourceType = $properties['{DAV:}resourcetype']->getValue();
296 296
         } else {
297
-            $resourceType = ['{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar'];
297
+            $resourceType = ['{DAV:}collection', '{urn:ietf:params:xml:ns:caldav}calendar'];
298 298
         }
299 299
 
300 300
         $this->server->createCollection($path, new MkCol($resourceType, $properties));
@@ -881,12 +881,12 @@  discard block
 block discarded – undo
881 881
                         if (!isset($component->UID)) {
882 882
                             throw new DAV\Exception\BadRequest('Every ' . $component->name . ' component must have an UID');
883 883
                         }
884
-                        $foundUID = (string)$component->UID;
884
+                        $foundUID = (string) $component->UID;
885 885
                     } else {
886 886
                         if ($foundType !== $component->name) {
887 887
                             throw new DAV\Exception\BadRequest('A calendar object must only contain 1 component. We found a ' . $component->name . ' as well as a ' . $foundType);
888 888
                         }
889
-                        if ($foundUID !== (string)$component->UID) {
889
+                        if ($foundUID !== (string) $component->UID) {
890 890
                             throw new DAV\Exception\BadRequest('Every ' . $component->name . ' in this object must have identical UIDs');
891 891
                         }
892 892
                     }
Please login to merge, or discard this patch.
Indentation   +986 added lines, -986 removed lines patch added patch discarded remove patch
@@ -26,929 +26,929 @@  discard block
 block discarded – undo
26 26
  */
27 27
 class Plugin extends DAV\ServerPlugin {
28 28
 
29
-    /**
30
-     * This is the official CalDAV namespace
31
-     */
32
-    const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav';
33
-
34
-    /**
35
-     * This is the namespace for the proprietary calendarserver extensions
36
-     */
37
-    const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
38
-
39
-    /**
40
-     * The hardcoded root for calendar objects. It is unfortunate
41
-     * that we're stuck with it, but it will have to do for now
42
-     */
43
-    const CALENDAR_ROOT = 'calendars';
44
-
45
-    /**
46
-     * Reference to server object
47
-     *
48
-     * @var DAV\Server
49
-     */
50
-    protected $server;
51
-
52
-    /**
53
-     * The default PDO storage uses a MySQL MEDIUMBLOB for iCalendar data,
54
-     * which can hold up to 2^24 = 16777216 bytes. This is plenty. We're
55
-     * capping it to 10M here.
56
-     */
57
-    protected $maxResourceSize = 10000000;
58
-
59
-    /**
60
-     * Use this method to tell the server this plugin defines additional
61
-     * HTTP methods.
62
-     *
63
-     * This method is passed a uri. It should only return HTTP methods that are
64
-     * available for the specified uri.
65
-     *
66
-     * @param string $uri
67
-     * @return array
68
-     */
69
-    public function getHTTPMethods($uri) {
70
-
71
-        // The MKCALENDAR is only available on unmapped uri's, whose
72
-        // parents extend IExtendedCollection
73
-        list($parent, $name) = Uri\split($uri);
74
-
75
-        $node = $this->server->tree->getNodeForPath($parent);
76
-
77
-        if ($node instanceof DAV\IExtendedCollection) {
78
-            try {
79
-                $node->getChild($name);
80
-            } catch (DAV\Exception\NotFound $e) {
81
-                return ['MKCALENDAR'];
82
-            }
83
-        }
84
-        return [];
85
-
86
-    }
87
-
88
-    /**
89
-     * Returns the path to a principal's calendar home.
90
-     *
91
-     * The return url must not end with a slash.
92
-     * This function should return null in case a principal did not have
93
-     * a calendar home.
94
-     *
95
-     * @param string $principalUrl
96
-     * @return string
97
-     */
98
-    public function getCalendarHomeForPrincipal($principalUrl) {
99
-
100
-        // The default behavior for most sabre/dav servers is that there is a
101
-        // principals root node, which contains users directly under it.
102
-        //
103
-        // This function assumes that there are two components in a principal
104
-        // path. If there's more, we don't return a calendar home. This
105
-        // excludes things like the calendar-proxy-read principal (which it
106
-        // should).
107
-        $parts = explode('/', trim($principalUrl, '/'));
108
-        if (count($parts) !== 2) return;
109
-        if ($parts[0] !== 'principals') return;
110
-
111
-        return self::CALENDAR_ROOT . '/' . $parts[1];
112
-
113
-    }
114
-
115
-    /**
116
-     * Returns a list of features for the DAV: HTTP header.
117
-     *
118
-     * @return array
119
-     */
120
-    public function getFeatures() {
121
-
122
-        return ['calendar-access', 'calendar-proxy'];
123
-
124
-    }
125
-
126
-    /**
127
-     * Returns a plugin name.
128
-     *
129
-     * Using this name other plugins will be able to access other plugins
130
-     * using DAV\Server::getPlugin
131
-     *
132
-     * @return string
133
-     */
134
-    public function getPluginName() {
135
-
136
-        return 'caldav';
137
-
138
-    }
139
-
140
-    /**
141
-     * Returns a list of reports this plugin supports.
142
-     *
143
-     * This will be used in the {DAV:}supported-report-set property.
144
-     * Note that you still need to subscribe to the 'report' event to actually
145
-     * implement them
146
-     *
147
-     * @param string $uri
148
-     * @return array
149
-     */
150
-    public function getSupportedReportSet($uri) {
151
-
152
-        $node = $this->server->tree->getNodeForPath($uri);
153
-
154
-        $reports = [];
155
-        if ($node instanceof ICalendarObjectContainer || $node instanceof ICalendarObject) {
156
-            $reports[] = '{' . self::NS_CALDAV . '}calendar-multiget';
157
-            $reports[] = '{' . self::NS_CALDAV . '}calendar-query';
158
-        }
159
-        if ($node instanceof ICalendar) {
160
-            $reports[] = '{' . self::NS_CALDAV . '}free-busy-query';
161
-        }
162
-        // iCal has a bug where it assumes that sync support is enabled, only
163
-        // if we say we support it on the calendar-home, even though this is
164
-        // not actually the case.
165
-        if ($node instanceof CalendarHome && $this->server->getPlugin('sync')) {
166
-            $reports[] = '{DAV:}sync-collection';
167
-        }
168
-        return $reports;
169
-
170
-    }
171
-
172
-    /**
173
-     * Initializes the plugin
174
-     *
175
-     * @param DAV\Server $server
176
-     * @return void
177
-     */
178
-    public function initialize(DAV\Server $server) {
179
-
180
-        $this->server = $server;
181
-
182
-        $server->on('method:MKCALENDAR',   [$this, 'httpMkCalendar']);
183
-        $server->on('report',              [$this, 'report']);
184
-        $server->on('propFind',            [$this, 'propFind']);
185
-        $server->on('onHTMLActionsPanel',  [$this, 'htmlActionsPanel']);
186
-        $server->on('beforeCreateFile',    [$this, 'beforeCreateFile']);
187
-        $server->on('beforeWriteContent',  [$this, 'beforeWriteContent']);
188
-        $server->on('afterMethod:GET',     [$this, 'httpAfterGET']);
189
-
190
-        $server->xml->namespaceMap[self::NS_CALDAV] = 'cal';
191
-        $server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs';
192
-
193
-        $server->xml->elementMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet';
194
-        $server->xml->elementMap['{' . self::NS_CALDAV . '}calendar-query'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarQueryReport';
195
-        $server->xml->elementMap['{' . self::NS_CALDAV . '}calendar-multiget'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarMultiGetReport';
196
-        $server->xml->elementMap['{' . self::NS_CALDAV . '}free-busy-query'] = 'Sabre\\CalDAV\\Xml\\Request\\FreeBusyQueryReport';
197
-        $server->xml->elementMap['{' . self::NS_CALDAV . '}mkcalendar'] = 'Sabre\\CalDAV\\Xml\\Request\\MkCalendar';
198
-        $server->xml->elementMap['{' . self::NS_CALDAV . '}schedule-calendar-transp'] = 'Sabre\\CalDAV\\Xml\\Property\\ScheduleCalendarTransp';
199
-        $server->xml->elementMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet';
200
-
201
-        $server->resourceTypeMapping['\\Sabre\\CalDAV\\ICalendar'] = '{urn:ietf:params:xml:ns:caldav}calendar';
202
-
203
-        $server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyRead'] = '{http://calendarserver.org/ns/}calendar-proxy-read';
204
-        $server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyWrite'] = '{http://calendarserver.org/ns/}calendar-proxy-write';
205
-
206
-        array_push($server->protectedProperties,
207
-
208
-            '{' . self::NS_CALDAV . '}supported-calendar-component-set',
209
-            '{' . self::NS_CALDAV . '}supported-calendar-data',
210
-            '{' . self::NS_CALDAV . '}max-resource-size',
211
-            '{' . self::NS_CALDAV . '}min-date-time',
212
-            '{' . self::NS_CALDAV . '}max-date-time',
213
-            '{' . self::NS_CALDAV . '}max-instances',
214
-            '{' . self::NS_CALDAV . '}max-attendees-per-instance',
215
-            '{' . self::NS_CALDAV . '}calendar-home-set',
216
-            '{' . self::NS_CALDAV . '}supported-collation-set',
217
-            '{' . self::NS_CALDAV . '}calendar-data',
218
-
219
-            // CalendarServer extensions
220
-            '{' . self::NS_CALENDARSERVER . '}getctag',
221
-            '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for',
222
-            '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for'
223
-
224
-        );
225
-
226
-        if ($aclPlugin = $server->getPlugin('acl')) {
227
-            $aclPlugin->principalSearchPropertySet['{' . self::NS_CALDAV . '}calendar-user-address-set'] = 'Calendar address';
228
-        }
229
-    }
230
-
231
-    /**
232
-     * This functions handles REPORT requests specific to CalDAV
233
-     *
234
-     * @param string $reportName
235
-     * @param mixed $report
236
-     * @return bool
237
-     */
238
-    public function report($reportName, $report) {
239
-
240
-        switch ($reportName) {
241
-            case '{' . self::NS_CALDAV . '}calendar-multiget' :
242
-                $this->server->transactionType = 'report-calendar-multiget';
243
-                $this->calendarMultiGetReport($report);
244
-                return false;
245
-            case '{' . self::NS_CALDAV . '}calendar-query' :
246
-                $this->server->transactionType = 'report-calendar-query';
247
-                $this->calendarQueryReport($report);
248
-                return false;
249
-            case '{' . self::NS_CALDAV . '}free-busy-query' :
250
-                $this->server->transactionType = 'report-free-busy-query';
251
-                $this->freeBusyQueryReport($report);
252
-                return false;
253
-
254
-        }
255
-
256
-
257
-    }
258
-
259
-    /**
260
-     * This function handles the MKCALENDAR HTTP method, which creates
261
-     * a new calendar.
262
-     *
263
-     * @param RequestInterface $request
264
-     * @param ResponseInterface $response
265
-     * @return bool
266
-     */
267
-    public function httpMkCalendar(RequestInterface $request, ResponseInterface $response) {
268
-
269
-        $body = $request->getBodyAsString();
270
-        $path = $request->getPath();
271
-
272
-        $properties = [];
273
-
274
-        if ($body) {
275
-
276
-            try {
277
-                $mkcalendar = $this->server->xml->expect(
278
-                    '{urn:ietf:params:xml:ns:caldav}mkcalendar',
279
-                    $body
280
-                );
281
-            } catch (\Sabre\Xml\ParseException $e) {
282
-                throw new BadRequest($e->getMessage(), null, $e);
283
-            }
284
-            $properties = $mkcalendar->getProperties();
285
-
286
-        }
287
-
288
-        // iCal abuses MKCALENDAR since iCal 10.9.2 to create server-stored
289
-        // subscriptions. Before that it used MKCOL which was the correct way
290
-        // to do this.
291
-        //
292
-        // If the body had a {DAV:}resourcetype, it means we stumbled upon this
293
-        // request, and we simply use it instead of the pre-defined list.
294
-        if (isset($properties['{DAV:}resourcetype'])) {
295
-            $resourceType = $properties['{DAV:}resourcetype']->getValue();
296
-        } else {
297
-            $resourceType = ['{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar'];
298
-        }
299
-
300
-        $this->server->createCollection($path, new MkCol($resourceType, $properties));
301
-
302
-        $this->server->httpResponse->setStatus(201);
303
-        $this->server->httpResponse->setHeader('Content-Length', 0);
304
-
305
-        // This breaks the method chain.
306
-        return false;
307
-    }
308
-
309
-    /**
310
-     * PropFind
311
-     *
312
-     * This method handler is invoked before any after properties for a
313
-     * resource are fetched. This allows us to add in any CalDAV specific
314
-     * properties.
315
-     *
316
-     * @param DAV\PropFind $propFind
317
-     * @param DAV\INode $node
318
-     * @return void
319
-     */
320
-    public function propFind(DAV\PropFind $propFind, DAV\INode $node) {
321
-
322
-        $ns = '{' . self::NS_CALDAV . '}';
323
-
324
-        if ($node instanceof ICalendarObjectContainer) {
325
-
326
-            $propFind->handle($ns . 'max-resource-size', $this->maxResourceSize);
327
-            $propFind->handle($ns . 'supported-calendar-data', function() {
328
-                return new Xml\Property\SupportedCalendarData();
329
-            });
330
-            $propFind->handle($ns . 'supported-collation-set', function() {
331
-                return new Xml\Property\SupportedCollationSet();
332
-            });
333
-
334
-        }
335
-
336
-        if ($node instanceof DAVACL\IPrincipal) {
337
-
338
-            $principalUrl = $node->getPrincipalUrl();
339
-
340
-            $propFind->handle('{' . self::NS_CALDAV . '}calendar-home-set', function() use ($principalUrl) {
341
-
342
-                $calendarHomePath = $this->getCalendarHomeForPrincipal($principalUrl);
343
-                if (is_null($calendarHomePath)) return null;
344
-                return new Href($calendarHomePath . '/');
345
-
346
-            });
347
-            // The calendar-user-address-set property is basically mapped to
348
-            // the {DAV:}alternate-URI-set property.
349
-            $propFind->handle('{' . self::NS_CALDAV . '}calendar-user-address-set', function() use ($node) {
350
-                $addresses = $node->getAlternateUriSet();
351
-                $addresses[] = $this->server->getBaseUri() . $node->getPrincipalUrl() . '/';
352
-                return new Href($addresses, false);
353
-            });
354
-            // For some reason somebody thought it was a good idea to add
355
-            // another one of these properties. We're supporting it too.
356
-            $propFind->handle('{' . self::NS_CALENDARSERVER . '}email-address-set', function() use ($node) {
357
-                $addresses = $node->getAlternateUriSet();
358
-                $emails = [];
359
-                foreach ($addresses as $address) {
360
-                    if (substr($address, 0, 7) === 'mailto:') {
361
-                        $emails[] = substr($address, 7);
362
-                    }
363
-                }
364
-                return new Xml\Property\EmailAddressSet($emails);
365
-            });
366
-
367
-            // These two properties are shortcuts for ical to easily find
368
-            // other principals this principal has access to.
369
-            $propRead = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for';
370
-            $propWrite = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for';
371
-
372
-            if ($propFind->getStatus($propRead) === 404 || $propFind->getStatus($propWrite) === 404) {
373
-
374
-                $aclPlugin = $this->server->getPlugin('acl');
375
-                $membership = $aclPlugin->getPrincipalMembership($propFind->getPath());
376
-                $readList = [];
377
-                $writeList = [];
378
-
379
-                foreach ($membership as $group) {
380
-
381
-                    $groupNode = $this->server->tree->getNodeForPath($group);
382
-
383
-                    $listItem = Uri\split($group)[0] . '/';
384
-
385
-                    // If the node is either ap proxy-read or proxy-write
386
-                    // group, we grab the parent principal and add it to the
387
-                    // list.
388
-                    if ($groupNode instanceof Principal\IProxyRead) {
389
-                        $readList[] = $listItem;
390
-                    }
391
-                    if ($groupNode instanceof Principal\IProxyWrite) {
392
-                        $writeList[] = $listItem;
393
-                    }
394
-
395
-                }
396
-
397
-                $propFind->set($propRead, new Href($readList));
398
-                $propFind->set($propWrite, new Href($writeList));
399
-
400
-            }
401
-
402
-        } // instanceof IPrincipal
403
-
404
-        if ($node instanceof ICalendarObject) {
405
-
406
-            // The calendar-data property is not supposed to be a 'real'
407
-            // property, but in large chunks of the spec it does act as such.
408
-            // Therefore we simply expose it as a property.
409
-            $propFind->handle('{' . self::NS_CALDAV . '}calendar-data', function() use ($node) {
410
-                $val = $node->get();
411
-                if (is_resource($val))
412
-                    $val = stream_get_contents($val);
413
-
414
-                // Taking out \r to not screw up the xml output
415
-                return str_replace("\r", "", $val);
416
-
417
-            });
418
-
419
-        }
420
-
421
-    }
422
-
423
-    /**
424
-     * This function handles the calendar-multiget REPORT.
425
-     *
426
-     * This report is used by the client to fetch the content of a series
427
-     * of urls. Effectively avoiding a lot of redundant requests.
428
-     *
429
-     * @param CalendarMultiGetReport $report
430
-     * @return void
431
-     */
432
-    public function calendarMultiGetReport($report) {
433
-
434
-        $needsJson = $report->contentType === 'application/calendar+json';
435
-
436
-        $timeZones = [];
437
-        $propertyList = [];
438
-
439
-        $paths = array_map(
440
-            [$this->server, 'calculateUri'],
441
-            $report->hrefs
442
-        );
443
-
444
-        foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $uri => $objProps) {
445
-
446
-            if (($needsJson || $report->expand) && isset($objProps[200]['{' . self::NS_CALDAV . '}calendar-data'])) {
447
-                $vObject = VObject\Reader::read($objProps[200]['{' . self::NS_CALDAV . '}calendar-data']);
448
-
449
-                if ($report->expand) {
450
-                    // We're expanding, and for that we need to figure out the
451
-                    // calendar's timezone.
452
-                    list($calendarPath) = Uri\split($uri);
453
-                    if (!isset($timeZones[$calendarPath])) {
454
-                        // Checking the calendar-timezone property.
455
-                        $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone';
456
-                        $tzResult = $this->server->getProperties($calendarPath, [$tzProp]);
457
-                        if (isset($tzResult[$tzProp])) {
458
-                            // This property contains a VCALENDAR with a single
459
-                            // VTIMEZONE.
460
-                            $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
461
-                            $timeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
462
-                        } else {
463
-                            // Defaulting to UTC.
464
-                            $timeZone = new DateTimeZone('UTC');
465
-                        }
466
-                        $timeZones[$calendarPath] = $timeZone;
467
-                    }
468
-
469
-                    $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $timeZones[$calendarPath]);
470
-                }
471
-                if ($needsJson) {
472
-                    $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize());
473
-                } else {
474
-                    $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
475
-                }
476
-                // Destroy circular references so PHP will garbage collect the
477
-                // object.
478
-                $vObject->destroy();
479
-            }
480
-
481
-            $propertyList[] = $objProps;
482
-
483
-        }
484
-
485
-        $prefer = $this->server->getHTTPPrefer();
486
-
487
-        $this->server->httpResponse->setStatus(207);
488
-        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
489
-        $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
490
-        $this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, $prefer['return'] === 'minimal'));
491
-
492
-    }
493
-
494
-    /**
495
-     * This function handles the calendar-query REPORT
496
-     *
497
-     * This report is used by clients to request calendar objects based on
498
-     * complex conditions.
499
-     *
500
-     * @param Xml\Request\CalendarQueryReport $report
501
-     * @return void
502
-     */
503
-    public function calendarQueryReport($report) {
504
-
505
-        $path = $this->server->getRequestUri();
506
-
507
-        $needsJson = $report->contentType === 'application/calendar+json';
508
-
509
-        $node = $this->server->tree->getNodeForPath($this->server->getRequestUri());
510
-        $depth = $this->server->getHTTPDepth(0);
511
-
512
-        // The default result is an empty array
513
-        $result = [];
514
-
515
-        $calendarTimeZone = null;
516
-        if ($report->expand) {
517
-            // We're expanding, and for that we need to figure out the
518
-            // calendar's timezone.
519
-            $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone';
520
-            $tzResult = $this->server->getProperties($path, [$tzProp]);
521
-            if (isset($tzResult[$tzProp])) {
522
-                // This property contains a VCALENDAR with a single
523
-                // VTIMEZONE.
524
-                $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
525
-                $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
526
-
527
-                // Destroy circular references so PHP will garbage collect the
528
-                // object.
529
-                $vtimezoneObj->destroy();
530
-            } else {
531
-                // Defaulting to UTC.
532
-                $calendarTimeZone = new DateTimeZone('UTC');
533
-            }
534
-        }
535
-
536
-        // The calendarobject was requested directly. In this case we handle
537
-        // this locally.
538
-        if ($depth == 0 && $node instanceof ICalendarObject) {
539
-
540
-            $requestedCalendarData = true;
541
-            $requestedProperties = $report->properties;
542
-
543
-            if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) {
544
-
545
-                // We always retrieve calendar-data, as we need it for filtering.
546
-                $requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data';
547
-
548
-                // If calendar-data wasn't explicitly requested, we need to remove
549
-                // it after processing.
550
-                $requestedCalendarData = false;
551
-            }
552
-
553
-            $properties = $this->server->getPropertiesForPath(
554
-                $path,
555
-                $requestedProperties,
556
-                0
557
-            );
558
-
559
-            // This array should have only 1 element, the first calendar
560
-            // object.
561
-            $properties = current($properties);
562
-
563
-            // If there wasn't any calendar-data returned somehow, we ignore
564
-            // this.
565
-            if (isset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) {
566
-
567
-                $validator = new CalendarQueryValidator();
568
-
569
-                $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
570
-                if ($validator->validate($vObject, $report->filters)) {
571
-
572
-                    // If the client didn't require the calendar-data property,
573
-                    // we won't give it back.
574
-                    if (!$requestedCalendarData) {
575
-                        unset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
576
-                    } else {
577
-
578
-
579
-                        if ($report->expand) {
580
-                            $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone);
581
-                        }
582
-                        if ($needsJson) {
583
-                            $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize());
584
-                        } elseif ($report->expand) {
585
-                            $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
586
-                        }
587
-                    }
588
-
589
-                    $result = [$properties];
590
-
591
-                }
592
-                // Destroy circular references so PHP will garbage collect the
593
-                // object.
594
-                $vObject->destroy();
595
-
596
-            }
597
-
598
-        }
599
-
600
-        if ($node instanceof ICalendarObjectContainer && $depth === 0) {
601
-
602
-            if (strpos($this->server->httpRequest->getHeader('User-Agent'), 'MSFT-') === 0) {
603
-                // Microsoft clients incorrectly supplied depth as 0, when it actually
604
-                // should have set depth to 1. We're implementing a workaround here
605
-                // to deal with this.
606
-                //
607
-                // This targets at least the following clients:
608
-                //   Windows 10
609
-                //   Windows Phone 8, 10
610
-                $depth = 1;
611
-            } else {
612
-                throw new BadRequest('A calendar-query REPORT on a calendar with a Depth: 0 is undefined. Set Depth to 1');
613
-            }
614
-
615
-        }
616
-
617
-        // If we're dealing with a calendar, the calendar itself is responsible
618
-        // for the calendar-query.
619
-        if ($node instanceof ICalendarObjectContainer && $depth == 1) {
620
-
621
-            $nodePaths = $node->calendarQuery($report->filters);
622
-
623
-            foreach ($nodePaths as $path) {
624
-
625
-                list($properties) =
626
-                    $this->server->getPropertiesForPath($this->server->getRequestUri() . '/' . $path, $report->properties);
627
-
628
-                if (($needsJson || $report->expand)) {
629
-                    $vObject = VObject\Reader::read($properties[200]['{' . self::NS_CALDAV . '}calendar-data']);
630
-
631
-                    if ($report->expand) {
632
-                        $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone);
633
-                    }
634
-
635
-                    if ($needsJson) {
636
-                        $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize());
637
-                    } else {
638
-                        $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
639
-                    }
640
-
641
-                    // Destroy circular references so PHP will garbage collect the
642
-                    // object.
643
-                    $vObject->destroy();
644
-                }
645
-                $result[] = $properties;
646
-
647
-            }
648
-
649
-        }
650
-
651
-        $prefer = $this->server->getHTTPPrefer();
652
-
653
-        $this->server->httpResponse->setStatus(207);
654
-        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
655
-        $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
656
-        $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return'] === 'minimal'));
657
-
658
-    }
659
-
660
-    /**
661
-     * This method is responsible for parsing the request and generating the
662
-     * response for the CALDAV:free-busy-query REPORT.
663
-     *
664
-     * @param Xml\Request\FreeBusyQueryReport $report
665
-     * @return void
666
-     */
667
-    protected function freeBusyQueryReport(Xml\Request\FreeBusyQueryReport $report) {
668
-
669
-        $uri = $this->server->getRequestUri();
670
-
671
-        $acl = $this->server->getPlugin('acl');
672
-        if ($acl) {
673
-            $acl->checkPrivileges($uri, '{' . self::NS_CALDAV . '}read-free-busy');
674
-        }
675
-
676
-        $calendar = $this->server->tree->getNodeForPath($uri);
677
-        if (!$calendar instanceof ICalendar) {
678
-            throw new DAV\Exception\NotImplemented('The free-busy-query REPORT is only implemented on calendars');
679
-        }
680
-
681
-        $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone';
682
-
683
-        // Figuring out the default timezone for the calendar, for floating
684
-        // times.
685
-        $calendarProps = $this->server->getProperties($uri, [$tzProp]);
686
-
687
-        if (isset($calendarProps[$tzProp])) {
688
-            $vtimezoneObj = VObject\Reader::read($calendarProps[$tzProp]);
689
-            $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
690
-            // Destroy circular references so PHP will garbage collect the object.
691
-            $vtimezoneObj->destroy();
692
-        } else {
693
-            $calendarTimeZone = new DateTimeZone('UTC');
694
-        }
695
-
696
-        // Doing a calendar-query first, to make sure we get the most
697
-        // performance.
698
-        $urls = $calendar->calendarQuery([
699
-            'name'         => 'VCALENDAR',
700
-            'comp-filters' => [
701
-                [
702
-                    'name'           => 'VEVENT',
703
-                    'comp-filters'   => [],
704
-                    'prop-filters'   => [],
705
-                    'is-not-defined' => false,
706
-                    'time-range'     => [
707
-                        'start' => $report->start,
708
-                        'end'   => $report->end,
709
-                    ],
710
-                ],
711
-            ],
712
-            'prop-filters'   => [],
713
-            'is-not-defined' => false,
714
-            'time-range'     => null,
715
-        ]);
716
-
717
-        $objects = array_map(function($url) use ($calendar) {
718
-            $obj = $calendar->getChild($url)->get();
719
-            return $obj;
720
-        }, $urls);
721
-
722
-        $generator = new VObject\FreeBusyGenerator();
723
-        $generator->setObjects($objects);
724
-        $generator->setTimeRange($report->start, $report->end);
725
-        $generator->setTimeZone($calendarTimeZone);
726
-        $result = $generator->getResult();
727
-        $result = $result->serialize();
728
-
729
-        $this->server->httpResponse->setStatus(200);
730
-        $this->server->httpResponse->setHeader('Content-Type', 'text/calendar');
731
-        $this->server->httpResponse->setHeader('Content-Length', strlen($result));
732
-        $this->server->httpResponse->setBody($result);
733
-
734
-    }
735
-
736
-    /**
737
-     * This method is triggered before a file gets updated with new content.
738
-     *
739
-     * This plugin uses this method to ensure that CalDAV objects receive
740
-     * valid calendar data.
741
-     *
742
-     * @param string $path
743
-     * @param DAV\IFile $node
744
-     * @param resource $data
745
-     * @param bool $modified Should be set to true, if this event handler
746
-     *                       changed &$data.
747
-     * @return void
748
-     */
749
-    public function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified) {
750
-
751
-        if (!$node instanceof ICalendarObject)
752
-            return;
753
-
754
-        // We're onyl interested in ICalendarObject nodes that are inside of a
755
-        // real calendar. This is to avoid triggering validation and scheduling
756
-        // for non-calendars (such as an inbox).
757
-        list($parent) = Uri\split($path);
758
-        $parentNode = $this->server->tree->getNodeForPath($parent);
759
-
760
-        if (!$parentNode instanceof ICalendar)
761
-            return;
762
-
763
-        $this->validateICalendar(
764
-            $data,
765
-            $path,
766
-            $modified,
767
-            $this->server->httpRequest,
768
-            $this->server->httpResponse,
769
-            false
770
-        );
771
-
772
-    }
773
-
774
-    /**
775
-     * This method is triggered before a new file is created.
776
-     *
777
-     * This plugin uses this method to ensure that newly created calendar
778
-     * objects contain valid calendar data.
779
-     *
780
-     * @param string $path
781
-     * @param resource $data
782
-     * @param DAV\ICollection $parentNode
783
-     * @param bool $modified Should be set to true, if this event handler
784
-     *                       changed &$data.
785
-     * @return void
786
-     */
787
-    public function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified) {
788
-
789
-        if (!$parentNode instanceof ICalendar)
790
-            return;
791
-
792
-        $this->validateICalendar(
793
-            $data,
794
-            $path,
795
-            $modified,
796
-            $this->server->httpRequest,
797
-            $this->server->httpResponse,
798
-            true
799
-        );
800
-
801
-    }
802
-
803
-    /**
804
-     * Checks if the submitted iCalendar data is in fact, valid.
805
-     *
806
-     * An exception is thrown if it's not.
807
-     *
808
-     * @param resource|string $data
809
-     * @param string $path
810
-     * @param bool $modified Should be set to true, if this event handler
811
-     *                       changed &$data.
812
-     * @param RequestInterface $request The http request.
813
-     * @param ResponseInterface $response The http response.
814
-     * @param bool $isNew Is the item a new one, or an update.
815
-     * @return void
816
-     */
817
-    protected function validateICalendar(&$data, $path, &$modified, RequestInterface $request, ResponseInterface $response, $isNew) {
818
-
819
-        // If it's a stream, we convert it to a string first.
820
-        if (is_resource($data)) {
821
-            $data = stream_get_contents($data);
822
-        }
823
-
824
-        $before = md5($data);
825
-        // Converting the data to unicode, if needed.
826
-        $data = DAV\StringUtil::ensureUTF8($data);
827
-
828
-        if ($before !== md5($data)) $modified = true;
829
-
830
-        try {
831
-
832
-            // If the data starts with a [, we can reasonably assume we're dealing
833
-            // with a jCal object.
834
-            if (substr($data, 0, 1) === '[') {
835
-                $vobj = VObject\Reader::readJson($data);
836
-
837
-                // Converting $data back to iCalendar, as that's what we
838
-                // technically support everywhere.
839
-                $data = $vobj->serialize();
840
-                $modified = true;
841
-            } else {
842
-                $vobj = VObject\Reader::read($data);
843
-            }
844
-
845
-        } catch (VObject\ParseException $e) {
846
-
847
-            throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid iCalendar 2.0 data. Parse error: ' . $e->getMessage());
848
-
849
-        }
850
-
851
-        if ($vobj->name !== 'VCALENDAR') {
852
-            throw new DAV\Exception\UnsupportedMediaType('This collection can only support iCalendar objects.');
853
-        }
854
-
855
-        $sCCS = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
856
-
857
-        // Get the Supported Components for the target calendar
858
-        list($parentPath) = Uri\split($path);
859
-        $calendarProperties = $this->server->getProperties($parentPath, [$sCCS]);
860
-
861
-        if (isset($calendarProperties[$sCCS])) {
862
-            $supportedComponents = $calendarProperties[$sCCS]->getValue();
863
-        } else {
864
-            $supportedComponents = ['VJOURNAL', 'VTODO', 'VEVENT'];
865
-        }
866
-
867
-        $foundType = null;
868
-        $foundUID = null;
869
-        foreach ($vobj->getComponents() as $component) {
870
-            switch ($component->name) {
871
-                case 'VTIMEZONE' :
872
-                    continue 2;
873
-                case 'VEVENT' :
874
-                case 'VTODO' :
875
-                case 'VJOURNAL' :
876
-                    if (is_null($foundType)) {
877
-                        $foundType = $component->name;
878
-                        if (!in_array($foundType, $supportedComponents)) {
879
-                            throw new Exception\InvalidComponentType('This calendar only supports ' . implode(', ', $supportedComponents) . '. We found a ' . $foundType);
880
-                        }
881
-                        if (!isset($component->UID)) {
882
-                            throw new DAV\Exception\BadRequest('Every ' . $component->name . ' component must have an UID');
883
-                        }
884
-                        $foundUID = (string)$component->UID;
885
-                    } else {
886
-                        if ($foundType !== $component->name) {
887
-                            throw new DAV\Exception\BadRequest('A calendar object must only contain 1 component. We found a ' . $component->name . ' as well as a ' . $foundType);
888
-                        }
889
-                        if ($foundUID !== (string)$component->UID) {
890
-                            throw new DAV\Exception\BadRequest('Every ' . $component->name . ' in this object must have identical UIDs');
891
-                        }
892
-                    }
893
-                    break;
894
-                default :
895
-                    throw new DAV\Exception\BadRequest('You are not allowed to create components of type: ' . $component->name . ' here');
896
-
897
-            }
898
-        }
899
-        if (!$foundType)
900
-            throw new DAV\Exception\BadRequest('iCalendar object must contain at least 1 of VEVENT, VTODO or VJOURNAL');
901
-
902
-        // We use an extra variable to allow event handles to tell us wether
903
-        // the object was modified or not.
904
-        //
905
-        // This helps us determine if we need to re-serialize the object.
906
-        $subModified = false;
907
-
908
-        $this->server->emit(
909
-            'calendarObjectChange',
910
-            [
911
-                $request,
912
-                $response,
913
-                $vobj,
914
-                $parentPath,
915
-                &$subModified,
916
-                $isNew
917
-            ]
918
-        );
919
-
920
-        if ($subModified) {
921
-            // An event handler told us that it modified the object.
922
-            $data = $vobj->serialize();
923
-
924
-            // Using md5 to figure out if there was an *actual* change.
925
-            if (!$modified && $before !== md5($data)) {
926
-                $modified = true;
927
-            }
928
-
929
-        }
930
-
931
-        // Destroy circular references so PHP will garbage collect the object.
932
-        $vobj->destroy();
933
-
934
-    }
935
-
936
-
937
-    /**
938
-     * This method is used to generate HTML output for the
939
-     * DAV\Browser\Plugin. This allows us to generate an interface users
940
-     * can use to create new calendars.
941
-     *
942
-     * @param DAV\INode $node
943
-     * @param string $output
944
-     * @return bool
945
-     */
946
-    public function htmlActionsPanel(DAV\INode $node, &$output) {
947
-
948
-        if (!$node instanceof CalendarHome)
949
-            return;
950
-
951
-        $output .= '<tr><td colspan="2"><form method="post" action="">
29
+	/**
30
+	 * This is the official CalDAV namespace
31
+	 */
32
+	const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav';
33
+
34
+	/**
35
+	 * This is the namespace for the proprietary calendarserver extensions
36
+	 */
37
+	const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
38
+
39
+	/**
40
+	 * The hardcoded root for calendar objects. It is unfortunate
41
+	 * that we're stuck with it, but it will have to do for now
42
+	 */
43
+	const CALENDAR_ROOT = 'calendars';
44
+
45
+	/**
46
+	 * Reference to server object
47
+	 *
48
+	 * @var DAV\Server
49
+	 */
50
+	protected $server;
51
+
52
+	/**
53
+	 * The default PDO storage uses a MySQL MEDIUMBLOB for iCalendar data,
54
+	 * which can hold up to 2^24 = 16777216 bytes. This is plenty. We're
55
+	 * capping it to 10M here.
56
+	 */
57
+	protected $maxResourceSize = 10000000;
58
+
59
+	/**
60
+	 * Use this method to tell the server this plugin defines additional
61
+	 * HTTP methods.
62
+	 *
63
+	 * This method is passed a uri. It should only return HTTP methods that are
64
+	 * available for the specified uri.
65
+	 *
66
+	 * @param string $uri
67
+	 * @return array
68
+	 */
69
+	public function getHTTPMethods($uri) {
70
+
71
+		// The MKCALENDAR is only available on unmapped uri's, whose
72
+		// parents extend IExtendedCollection
73
+		list($parent, $name) = Uri\split($uri);
74
+
75
+		$node = $this->server->tree->getNodeForPath($parent);
76
+
77
+		if ($node instanceof DAV\IExtendedCollection) {
78
+			try {
79
+				$node->getChild($name);
80
+			} catch (DAV\Exception\NotFound $e) {
81
+				return ['MKCALENDAR'];
82
+			}
83
+		}
84
+		return [];
85
+
86
+	}
87
+
88
+	/**
89
+	 * Returns the path to a principal's calendar home.
90
+	 *
91
+	 * The return url must not end with a slash.
92
+	 * This function should return null in case a principal did not have
93
+	 * a calendar home.
94
+	 *
95
+	 * @param string $principalUrl
96
+	 * @return string
97
+	 */
98
+	public function getCalendarHomeForPrincipal($principalUrl) {
99
+
100
+		// The default behavior for most sabre/dav servers is that there is a
101
+		// principals root node, which contains users directly under it.
102
+		//
103
+		// This function assumes that there are two components in a principal
104
+		// path. If there's more, we don't return a calendar home. This
105
+		// excludes things like the calendar-proxy-read principal (which it
106
+		// should).
107
+		$parts = explode('/', trim($principalUrl, '/'));
108
+		if (count($parts) !== 2) return;
109
+		if ($parts[0] !== 'principals') return;
110
+
111
+		return self::CALENDAR_ROOT . '/' . $parts[1];
112
+
113
+	}
114
+
115
+	/**
116
+	 * Returns a list of features for the DAV: HTTP header.
117
+	 *
118
+	 * @return array
119
+	 */
120
+	public function getFeatures() {
121
+
122
+		return ['calendar-access', 'calendar-proxy'];
123
+
124
+	}
125
+
126
+	/**
127
+	 * Returns a plugin name.
128
+	 *
129
+	 * Using this name other plugins will be able to access other plugins
130
+	 * using DAV\Server::getPlugin
131
+	 *
132
+	 * @return string
133
+	 */
134
+	public function getPluginName() {
135
+
136
+		return 'caldav';
137
+
138
+	}
139
+
140
+	/**
141
+	 * Returns a list of reports this plugin supports.
142
+	 *
143
+	 * This will be used in the {DAV:}supported-report-set property.
144
+	 * Note that you still need to subscribe to the 'report' event to actually
145
+	 * implement them
146
+	 *
147
+	 * @param string $uri
148
+	 * @return array
149
+	 */
150
+	public function getSupportedReportSet($uri) {
151
+
152
+		$node = $this->server->tree->getNodeForPath($uri);
153
+
154
+		$reports = [];
155
+		if ($node instanceof ICalendarObjectContainer || $node instanceof ICalendarObject) {
156
+			$reports[] = '{' . self::NS_CALDAV . '}calendar-multiget';
157
+			$reports[] = '{' . self::NS_CALDAV . '}calendar-query';
158
+		}
159
+		if ($node instanceof ICalendar) {
160
+			$reports[] = '{' . self::NS_CALDAV . '}free-busy-query';
161
+		}
162
+		// iCal has a bug where it assumes that sync support is enabled, only
163
+		// if we say we support it on the calendar-home, even though this is
164
+		// not actually the case.
165
+		if ($node instanceof CalendarHome && $this->server->getPlugin('sync')) {
166
+			$reports[] = '{DAV:}sync-collection';
167
+		}
168
+		return $reports;
169
+
170
+	}
171
+
172
+	/**
173
+	 * Initializes the plugin
174
+	 *
175
+	 * @param DAV\Server $server
176
+	 * @return void
177
+	 */
178
+	public function initialize(DAV\Server $server) {
179
+
180
+		$this->server = $server;
181
+
182
+		$server->on('method:MKCALENDAR',   [$this, 'httpMkCalendar']);
183
+		$server->on('report',              [$this, 'report']);
184
+		$server->on('propFind',            [$this, 'propFind']);
185
+		$server->on('onHTMLActionsPanel',  [$this, 'htmlActionsPanel']);
186
+		$server->on('beforeCreateFile',    [$this, 'beforeCreateFile']);
187
+		$server->on('beforeWriteContent',  [$this, 'beforeWriteContent']);
188
+		$server->on('afterMethod:GET',     [$this, 'httpAfterGET']);
189
+
190
+		$server->xml->namespaceMap[self::NS_CALDAV] = 'cal';
191
+		$server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs';
192
+
193
+		$server->xml->elementMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet';
194
+		$server->xml->elementMap['{' . self::NS_CALDAV . '}calendar-query'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarQueryReport';
195
+		$server->xml->elementMap['{' . self::NS_CALDAV . '}calendar-multiget'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarMultiGetReport';
196
+		$server->xml->elementMap['{' . self::NS_CALDAV . '}free-busy-query'] = 'Sabre\\CalDAV\\Xml\\Request\\FreeBusyQueryReport';
197
+		$server->xml->elementMap['{' . self::NS_CALDAV . '}mkcalendar'] = 'Sabre\\CalDAV\\Xml\\Request\\MkCalendar';
198
+		$server->xml->elementMap['{' . self::NS_CALDAV . '}schedule-calendar-transp'] = 'Sabre\\CalDAV\\Xml\\Property\\ScheduleCalendarTransp';
199
+		$server->xml->elementMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet';
200
+
201
+		$server->resourceTypeMapping['\\Sabre\\CalDAV\\ICalendar'] = '{urn:ietf:params:xml:ns:caldav}calendar';
202
+
203
+		$server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyRead'] = '{http://calendarserver.org/ns/}calendar-proxy-read';
204
+		$server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyWrite'] = '{http://calendarserver.org/ns/}calendar-proxy-write';
205
+
206
+		array_push($server->protectedProperties,
207
+
208
+			'{' . self::NS_CALDAV . '}supported-calendar-component-set',
209
+			'{' . self::NS_CALDAV . '}supported-calendar-data',
210
+			'{' . self::NS_CALDAV . '}max-resource-size',
211
+			'{' . self::NS_CALDAV . '}min-date-time',
212
+			'{' . self::NS_CALDAV . '}max-date-time',
213
+			'{' . self::NS_CALDAV . '}max-instances',
214
+			'{' . self::NS_CALDAV . '}max-attendees-per-instance',
215
+			'{' . self::NS_CALDAV . '}calendar-home-set',
216
+			'{' . self::NS_CALDAV . '}supported-collation-set',
217
+			'{' . self::NS_CALDAV . '}calendar-data',
218
+
219
+			// CalendarServer extensions
220
+			'{' . self::NS_CALENDARSERVER . '}getctag',
221
+			'{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for',
222
+			'{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for'
223
+
224
+		);
225
+
226
+		if ($aclPlugin = $server->getPlugin('acl')) {
227
+			$aclPlugin->principalSearchPropertySet['{' . self::NS_CALDAV . '}calendar-user-address-set'] = 'Calendar address';
228
+		}
229
+	}
230
+
231
+	/**
232
+	 * This functions handles REPORT requests specific to CalDAV
233
+	 *
234
+	 * @param string $reportName
235
+	 * @param mixed $report
236
+	 * @return bool
237
+	 */
238
+	public function report($reportName, $report) {
239
+
240
+		switch ($reportName) {
241
+			case '{' . self::NS_CALDAV . '}calendar-multiget' :
242
+				$this->server->transactionType = 'report-calendar-multiget';
243
+				$this->calendarMultiGetReport($report);
244
+				return false;
245
+			case '{' . self::NS_CALDAV . '}calendar-query' :
246
+				$this->server->transactionType = 'report-calendar-query';
247
+				$this->calendarQueryReport($report);
248
+				return false;
249
+			case '{' . self::NS_CALDAV . '}free-busy-query' :
250
+				$this->server->transactionType = 'report-free-busy-query';
251
+				$this->freeBusyQueryReport($report);
252
+				return false;
253
+
254
+		}
255
+
256
+
257
+	}
258
+
259
+	/**
260
+	 * This function handles the MKCALENDAR HTTP method, which creates
261
+	 * a new calendar.
262
+	 *
263
+	 * @param RequestInterface $request
264
+	 * @param ResponseInterface $response
265
+	 * @return bool
266
+	 */
267
+	public function httpMkCalendar(RequestInterface $request, ResponseInterface $response) {
268
+
269
+		$body = $request->getBodyAsString();
270
+		$path = $request->getPath();
271
+
272
+		$properties = [];
273
+
274
+		if ($body) {
275
+
276
+			try {
277
+				$mkcalendar = $this->server->xml->expect(
278
+					'{urn:ietf:params:xml:ns:caldav}mkcalendar',
279
+					$body
280
+				);
281
+			} catch (\Sabre\Xml\ParseException $e) {
282
+				throw new BadRequest($e->getMessage(), null, $e);
283
+			}
284
+			$properties = $mkcalendar->getProperties();
285
+
286
+		}
287
+
288
+		// iCal abuses MKCALENDAR since iCal 10.9.2 to create server-stored
289
+		// subscriptions. Before that it used MKCOL which was the correct way
290
+		// to do this.
291
+		//
292
+		// If the body had a {DAV:}resourcetype, it means we stumbled upon this
293
+		// request, and we simply use it instead of the pre-defined list.
294
+		if (isset($properties['{DAV:}resourcetype'])) {
295
+			$resourceType = $properties['{DAV:}resourcetype']->getValue();
296
+		} else {
297
+			$resourceType = ['{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar'];
298
+		}
299
+
300
+		$this->server->createCollection($path, new MkCol($resourceType, $properties));
301
+
302
+		$this->server->httpResponse->setStatus(201);
303
+		$this->server->httpResponse->setHeader('Content-Length', 0);
304
+
305
+		// This breaks the method chain.
306
+		return false;
307
+	}
308
+
309
+	/**
310
+	 * PropFind
311
+	 *
312
+	 * This method handler is invoked before any after properties for a
313
+	 * resource are fetched. This allows us to add in any CalDAV specific
314
+	 * properties.
315
+	 *
316
+	 * @param DAV\PropFind $propFind
317
+	 * @param DAV\INode $node
318
+	 * @return void
319
+	 */
320
+	public function propFind(DAV\PropFind $propFind, DAV\INode $node) {
321
+
322
+		$ns = '{' . self::NS_CALDAV . '}';
323
+
324
+		if ($node instanceof ICalendarObjectContainer) {
325
+
326
+			$propFind->handle($ns . 'max-resource-size', $this->maxResourceSize);
327
+			$propFind->handle($ns . 'supported-calendar-data', function() {
328
+				return new Xml\Property\SupportedCalendarData();
329
+			});
330
+			$propFind->handle($ns . 'supported-collation-set', function() {
331
+				return new Xml\Property\SupportedCollationSet();
332
+			});
333
+
334
+		}
335
+
336
+		if ($node instanceof DAVACL\IPrincipal) {
337
+
338
+			$principalUrl = $node->getPrincipalUrl();
339
+
340
+			$propFind->handle('{' . self::NS_CALDAV . '}calendar-home-set', function() use ($principalUrl) {
341
+
342
+				$calendarHomePath = $this->getCalendarHomeForPrincipal($principalUrl);
343
+				if (is_null($calendarHomePath)) return null;
344
+				return new Href($calendarHomePath . '/');
345
+
346
+			});
347
+			// The calendar-user-address-set property is basically mapped to
348
+			// the {DAV:}alternate-URI-set property.
349
+			$propFind->handle('{' . self::NS_CALDAV . '}calendar-user-address-set', function() use ($node) {
350
+				$addresses = $node->getAlternateUriSet();
351
+				$addresses[] = $this->server->getBaseUri() . $node->getPrincipalUrl() . '/';
352
+				return new Href($addresses, false);
353
+			});
354
+			// For some reason somebody thought it was a good idea to add
355
+			// another one of these properties. We're supporting it too.
356
+			$propFind->handle('{' . self::NS_CALENDARSERVER . '}email-address-set', function() use ($node) {
357
+				$addresses = $node->getAlternateUriSet();
358
+				$emails = [];
359
+				foreach ($addresses as $address) {
360
+					if (substr($address, 0, 7) === 'mailto:') {
361
+						$emails[] = substr($address, 7);
362
+					}
363
+				}
364
+				return new Xml\Property\EmailAddressSet($emails);
365
+			});
366
+
367
+			// These two properties are shortcuts for ical to easily find
368
+			// other principals this principal has access to.
369
+			$propRead = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for';
370
+			$propWrite = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for';
371
+
372
+			if ($propFind->getStatus($propRead) === 404 || $propFind->getStatus($propWrite) === 404) {
373
+
374
+				$aclPlugin = $this->server->getPlugin('acl');
375
+				$membership = $aclPlugin->getPrincipalMembership($propFind->getPath());
376
+				$readList = [];
377
+				$writeList = [];
378
+
379
+				foreach ($membership as $group) {
380
+
381
+					$groupNode = $this->server->tree->getNodeForPath($group);
382
+
383
+					$listItem = Uri\split($group)[0] . '/';
384
+
385
+					// If the node is either ap proxy-read or proxy-write
386
+					// group, we grab the parent principal and add it to the
387
+					// list.
388
+					if ($groupNode instanceof Principal\IProxyRead) {
389
+						$readList[] = $listItem;
390
+					}
391
+					if ($groupNode instanceof Principal\IProxyWrite) {
392
+						$writeList[] = $listItem;
393
+					}
394
+
395
+				}
396
+
397
+				$propFind->set($propRead, new Href($readList));
398
+				$propFind->set($propWrite, new Href($writeList));
399
+
400
+			}
401
+
402
+		} // instanceof IPrincipal
403
+
404
+		if ($node instanceof ICalendarObject) {
405
+
406
+			// The calendar-data property is not supposed to be a 'real'
407
+			// property, but in large chunks of the spec it does act as such.
408
+			// Therefore we simply expose it as a property.
409
+			$propFind->handle('{' . self::NS_CALDAV . '}calendar-data', function() use ($node) {
410
+				$val = $node->get();
411
+				if (is_resource($val))
412
+					$val = stream_get_contents($val);
413
+
414
+				// Taking out \r to not screw up the xml output
415
+				return str_replace("\r", "", $val);
416
+
417
+			});
418
+
419
+		}
420
+
421
+	}
422
+
423
+	/**
424
+	 * This function handles the calendar-multiget REPORT.
425
+	 *
426
+	 * This report is used by the client to fetch the content of a series
427
+	 * of urls. Effectively avoiding a lot of redundant requests.
428
+	 *
429
+	 * @param CalendarMultiGetReport $report
430
+	 * @return void
431
+	 */
432
+	public function calendarMultiGetReport($report) {
433
+
434
+		$needsJson = $report->contentType === 'application/calendar+json';
435
+
436
+		$timeZones = [];
437
+		$propertyList = [];
438
+
439
+		$paths = array_map(
440
+			[$this->server, 'calculateUri'],
441
+			$report->hrefs
442
+		);
443
+
444
+		foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $uri => $objProps) {
445
+
446
+			if (($needsJson || $report->expand) && isset($objProps[200]['{' . self::NS_CALDAV . '}calendar-data'])) {
447
+				$vObject = VObject\Reader::read($objProps[200]['{' . self::NS_CALDAV . '}calendar-data']);
448
+
449
+				if ($report->expand) {
450
+					// We're expanding, and for that we need to figure out the
451
+					// calendar's timezone.
452
+					list($calendarPath) = Uri\split($uri);
453
+					if (!isset($timeZones[$calendarPath])) {
454
+						// Checking the calendar-timezone property.
455
+						$tzProp = '{' . self::NS_CALDAV . '}calendar-timezone';
456
+						$tzResult = $this->server->getProperties($calendarPath, [$tzProp]);
457
+						if (isset($tzResult[$tzProp])) {
458
+							// This property contains a VCALENDAR with a single
459
+							// VTIMEZONE.
460
+							$vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
461
+							$timeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
462
+						} else {
463
+							// Defaulting to UTC.
464
+							$timeZone = new DateTimeZone('UTC');
465
+						}
466
+						$timeZones[$calendarPath] = $timeZone;
467
+					}
468
+
469
+					$vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $timeZones[$calendarPath]);
470
+				}
471
+				if ($needsJson) {
472
+					$objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize());
473
+				} else {
474
+					$objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
475
+				}
476
+				// Destroy circular references so PHP will garbage collect the
477
+				// object.
478
+				$vObject->destroy();
479
+			}
480
+
481
+			$propertyList[] = $objProps;
482
+
483
+		}
484
+
485
+		$prefer = $this->server->getHTTPPrefer();
486
+
487
+		$this->server->httpResponse->setStatus(207);
488
+		$this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
489
+		$this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
490
+		$this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, $prefer['return'] === 'minimal'));
491
+
492
+	}
493
+
494
+	/**
495
+	 * This function handles the calendar-query REPORT
496
+	 *
497
+	 * This report is used by clients to request calendar objects based on
498
+	 * complex conditions.
499
+	 *
500
+	 * @param Xml\Request\CalendarQueryReport $report
501
+	 * @return void
502
+	 */
503
+	public function calendarQueryReport($report) {
504
+
505
+		$path = $this->server->getRequestUri();
506
+
507
+		$needsJson = $report->contentType === 'application/calendar+json';
508
+
509
+		$node = $this->server->tree->getNodeForPath($this->server->getRequestUri());
510
+		$depth = $this->server->getHTTPDepth(0);
511
+
512
+		// The default result is an empty array
513
+		$result = [];
514
+
515
+		$calendarTimeZone = null;
516
+		if ($report->expand) {
517
+			// We're expanding, and for that we need to figure out the
518
+			// calendar's timezone.
519
+			$tzProp = '{' . self::NS_CALDAV . '}calendar-timezone';
520
+			$tzResult = $this->server->getProperties($path, [$tzProp]);
521
+			if (isset($tzResult[$tzProp])) {
522
+				// This property contains a VCALENDAR with a single
523
+				// VTIMEZONE.
524
+				$vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
525
+				$calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
526
+
527
+				// Destroy circular references so PHP will garbage collect the
528
+				// object.
529
+				$vtimezoneObj->destroy();
530
+			} else {
531
+				// Defaulting to UTC.
532
+				$calendarTimeZone = new DateTimeZone('UTC');
533
+			}
534
+		}
535
+
536
+		// The calendarobject was requested directly. In this case we handle
537
+		// this locally.
538
+		if ($depth == 0 && $node instanceof ICalendarObject) {
539
+
540
+			$requestedCalendarData = true;
541
+			$requestedProperties = $report->properties;
542
+
543
+			if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) {
544
+
545
+				// We always retrieve calendar-data, as we need it for filtering.
546
+				$requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data';
547
+
548
+				// If calendar-data wasn't explicitly requested, we need to remove
549
+				// it after processing.
550
+				$requestedCalendarData = false;
551
+			}
552
+
553
+			$properties = $this->server->getPropertiesForPath(
554
+				$path,
555
+				$requestedProperties,
556
+				0
557
+			);
558
+
559
+			// This array should have only 1 element, the first calendar
560
+			// object.
561
+			$properties = current($properties);
562
+
563
+			// If there wasn't any calendar-data returned somehow, we ignore
564
+			// this.
565
+			if (isset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) {
566
+
567
+				$validator = new CalendarQueryValidator();
568
+
569
+				$vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
570
+				if ($validator->validate($vObject, $report->filters)) {
571
+
572
+					// If the client didn't require the calendar-data property,
573
+					// we won't give it back.
574
+					if (!$requestedCalendarData) {
575
+						unset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
576
+					} else {
577
+
578
+
579
+						if ($report->expand) {
580
+							$vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone);
581
+						}
582
+						if ($needsJson) {
583
+							$properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize());
584
+						} elseif ($report->expand) {
585
+							$properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
586
+						}
587
+					}
588
+
589
+					$result = [$properties];
590
+
591
+				}
592
+				// Destroy circular references so PHP will garbage collect the
593
+				// object.
594
+				$vObject->destroy();
595
+
596
+			}
597
+
598
+		}
599
+
600
+		if ($node instanceof ICalendarObjectContainer && $depth === 0) {
601
+
602
+			if (strpos($this->server->httpRequest->getHeader('User-Agent'), 'MSFT-') === 0) {
603
+				// Microsoft clients incorrectly supplied depth as 0, when it actually
604
+				// should have set depth to 1. We're implementing a workaround here
605
+				// to deal with this.
606
+				//
607
+				// This targets at least the following clients:
608
+				//   Windows 10
609
+				//   Windows Phone 8, 10
610
+				$depth = 1;
611
+			} else {
612
+				throw new BadRequest('A calendar-query REPORT on a calendar with a Depth: 0 is undefined. Set Depth to 1');
613
+			}
614
+
615
+		}
616
+
617
+		// If we're dealing with a calendar, the calendar itself is responsible
618
+		// for the calendar-query.
619
+		if ($node instanceof ICalendarObjectContainer && $depth == 1) {
620
+
621
+			$nodePaths = $node->calendarQuery($report->filters);
622
+
623
+			foreach ($nodePaths as $path) {
624
+
625
+				list($properties) =
626
+					$this->server->getPropertiesForPath($this->server->getRequestUri() . '/' . $path, $report->properties);
627
+
628
+				if (($needsJson || $report->expand)) {
629
+					$vObject = VObject\Reader::read($properties[200]['{' . self::NS_CALDAV . '}calendar-data']);
630
+
631
+					if ($report->expand) {
632
+						$vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone);
633
+					}
634
+
635
+					if ($needsJson) {
636
+						$properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize());
637
+					} else {
638
+						$properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
639
+					}
640
+
641
+					// Destroy circular references so PHP will garbage collect the
642
+					// object.
643
+					$vObject->destroy();
644
+				}
645
+				$result[] = $properties;
646
+
647
+			}
648
+
649
+		}
650
+
651
+		$prefer = $this->server->getHTTPPrefer();
652
+
653
+		$this->server->httpResponse->setStatus(207);
654
+		$this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
655
+		$this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
656
+		$this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return'] === 'minimal'));
657
+
658
+	}
659
+
660
+	/**
661
+	 * This method is responsible for parsing the request and generating the
662
+	 * response for the CALDAV:free-busy-query REPORT.
663
+	 *
664
+	 * @param Xml\Request\FreeBusyQueryReport $report
665
+	 * @return void
666
+	 */
667
+	protected function freeBusyQueryReport(Xml\Request\FreeBusyQueryReport $report) {
668
+
669
+		$uri = $this->server->getRequestUri();
670
+
671
+		$acl = $this->server->getPlugin('acl');
672
+		if ($acl) {
673
+			$acl->checkPrivileges($uri, '{' . self::NS_CALDAV . '}read-free-busy');
674
+		}
675
+
676
+		$calendar = $this->server->tree->getNodeForPath($uri);
677
+		if (!$calendar instanceof ICalendar) {
678
+			throw new DAV\Exception\NotImplemented('The free-busy-query REPORT is only implemented on calendars');
679
+		}
680
+
681
+		$tzProp = '{' . self::NS_CALDAV . '}calendar-timezone';
682
+
683
+		// Figuring out the default timezone for the calendar, for floating
684
+		// times.
685
+		$calendarProps = $this->server->getProperties($uri, [$tzProp]);
686
+
687
+		if (isset($calendarProps[$tzProp])) {
688
+			$vtimezoneObj = VObject\Reader::read($calendarProps[$tzProp]);
689
+			$calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
690
+			// Destroy circular references so PHP will garbage collect the object.
691
+			$vtimezoneObj->destroy();
692
+		} else {
693
+			$calendarTimeZone = new DateTimeZone('UTC');
694
+		}
695
+
696
+		// Doing a calendar-query first, to make sure we get the most
697
+		// performance.
698
+		$urls = $calendar->calendarQuery([
699
+			'name'         => 'VCALENDAR',
700
+			'comp-filters' => [
701
+				[
702
+					'name'           => 'VEVENT',
703
+					'comp-filters'   => [],
704
+					'prop-filters'   => [],
705
+					'is-not-defined' => false,
706
+					'time-range'     => [
707
+						'start' => $report->start,
708
+						'end'   => $report->end,
709
+					],
710
+				],
711
+			],
712
+			'prop-filters'   => [],
713
+			'is-not-defined' => false,
714
+			'time-range'     => null,
715
+		]);
716
+
717
+		$objects = array_map(function($url) use ($calendar) {
718
+			$obj = $calendar->getChild($url)->get();
719
+			return $obj;
720
+		}, $urls);
721
+
722
+		$generator = new VObject\FreeBusyGenerator();
723
+		$generator->setObjects($objects);
724
+		$generator->setTimeRange($report->start, $report->end);
725
+		$generator->setTimeZone($calendarTimeZone);
726
+		$result = $generator->getResult();
727
+		$result = $result->serialize();
728
+
729
+		$this->server->httpResponse->setStatus(200);
730
+		$this->server->httpResponse->setHeader('Content-Type', 'text/calendar');
731
+		$this->server->httpResponse->setHeader('Content-Length', strlen($result));
732
+		$this->server->httpResponse->setBody($result);
733
+
734
+	}
735
+
736
+	/**
737
+	 * This method is triggered before a file gets updated with new content.
738
+	 *
739
+	 * This plugin uses this method to ensure that CalDAV objects receive
740
+	 * valid calendar data.
741
+	 *
742
+	 * @param string $path
743
+	 * @param DAV\IFile $node
744
+	 * @param resource $data
745
+	 * @param bool $modified Should be set to true, if this event handler
746
+	 *                       changed &$data.
747
+	 * @return void
748
+	 */
749
+	public function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified) {
750
+
751
+		if (!$node instanceof ICalendarObject)
752
+			return;
753
+
754
+		// We're onyl interested in ICalendarObject nodes that are inside of a
755
+		// real calendar. This is to avoid triggering validation and scheduling
756
+		// for non-calendars (such as an inbox).
757
+		list($parent) = Uri\split($path);
758
+		$parentNode = $this->server->tree->getNodeForPath($parent);
759
+
760
+		if (!$parentNode instanceof ICalendar)
761
+			return;
762
+
763
+		$this->validateICalendar(
764
+			$data,
765
+			$path,
766
+			$modified,
767
+			$this->server->httpRequest,
768
+			$this->server->httpResponse,
769
+			false
770
+		);
771
+
772
+	}
773
+
774
+	/**
775
+	 * This method is triggered before a new file is created.
776
+	 *
777
+	 * This plugin uses this method to ensure that newly created calendar
778
+	 * objects contain valid calendar data.
779
+	 *
780
+	 * @param string $path
781
+	 * @param resource $data
782
+	 * @param DAV\ICollection $parentNode
783
+	 * @param bool $modified Should be set to true, if this event handler
784
+	 *                       changed &$data.
785
+	 * @return void
786
+	 */
787
+	public function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified) {
788
+
789
+		if (!$parentNode instanceof ICalendar)
790
+			return;
791
+
792
+		$this->validateICalendar(
793
+			$data,
794
+			$path,
795
+			$modified,
796
+			$this->server->httpRequest,
797
+			$this->server->httpResponse,
798
+			true
799
+		);
800
+
801
+	}
802
+
803
+	/**
804
+	 * Checks if the submitted iCalendar data is in fact, valid.
805
+	 *
806
+	 * An exception is thrown if it's not.
807
+	 *
808
+	 * @param resource|string $data
809
+	 * @param string $path
810
+	 * @param bool $modified Should be set to true, if this event handler
811
+	 *                       changed &$data.
812
+	 * @param RequestInterface $request The http request.
813
+	 * @param ResponseInterface $response The http response.
814
+	 * @param bool $isNew Is the item a new one, or an update.
815
+	 * @return void
816
+	 */
817
+	protected function validateICalendar(&$data, $path, &$modified, RequestInterface $request, ResponseInterface $response, $isNew) {
818
+
819
+		// If it's a stream, we convert it to a string first.
820
+		if (is_resource($data)) {
821
+			$data = stream_get_contents($data);
822
+		}
823
+
824
+		$before = md5($data);
825
+		// Converting the data to unicode, if needed.
826
+		$data = DAV\StringUtil::ensureUTF8($data);
827
+
828
+		if ($before !== md5($data)) $modified = true;
829
+
830
+		try {
831
+
832
+			// If the data starts with a [, we can reasonably assume we're dealing
833
+			// with a jCal object.
834
+			if (substr($data, 0, 1) === '[') {
835
+				$vobj = VObject\Reader::readJson($data);
836
+
837
+				// Converting $data back to iCalendar, as that's what we
838
+				// technically support everywhere.
839
+				$data = $vobj->serialize();
840
+				$modified = true;
841
+			} else {
842
+				$vobj = VObject\Reader::read($data);
843
+			}
844
+
845
+		} catch (VObject\ParseException $e) {
846
+
847
+			throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid iCalendar 2.0 data. Parse error: ' . $e->getMessage());
848
+
849
+		}
850
+
851
+		if ($vobj->name !== 'VCALENDAR') {
852
+			throw new DAV\Exception\UnsupportedMediaType('This collection can only support iCalendar objects.');
853
+		}
854
+
855
+		$sCCS = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
856
+
857
+		// Get the Supported Components for the target calendar
858
+		list($parentPath) = Uri\split($path);
859
+		$calendarProperties = $this->server->getProperties($parentPath, [$sCCS]);
860
+
861
+		if (isset($calendarProperties[$sCCS])) {
862
+			$supportedComponents = $calendarProperties[$sCCS]->getValue();
863
+		} else {
864
+			$supportedComponents = ['VJOURNAL', 'VTODO', 'VEVENT'];
865
+		}
866
+
867
+		$foundType = null;
868
+		$foundUID = null;
869
+		foreach ($vobj->getComponents() as $component) {
870
+			switch ($component->name) {
871
+				case 'VTIMEZONE' :
872
+					continue 2;
873
+				case 'VEVENT' :
874
+				case 'VTODO' :
875
+				case 'VJOURNAL' :
876
+					if (is_null($foundType)) {
877
+						$foundType = $component->name;
878
+						if (!in_array($foundType, $supportedComponents)) {
879
+							throw new Exception\InvalidComponentType('This calendar only supports ' . implode(', ', $supportedComponents) . '. We found a ' . $foundType);
880
+						}
881
+						if (!isset($component->UID)) {
882
+							throw new DAV\Exception\BadRequest('Every ' . $component->name . ' component must have an UID');
883
+						}
884
+						$foundUID = (string)$component->UID;
885
+					} else {
886
+						if ($foundType !== $component->name) {
887
+							throw new DAV\Exception\BadRequest('A calendar object must only contain 1 component. We found a ' . $component->name . ' as well as a ' . $foundType);
888
+						}
889
+						if ($foundUID !== (string)$component->UID) {
890
+							throw new DAV\Exception\BadRequest('Every ' . $component->name . ' in this object must have identical UIDs');
891
+						}
892
+					}
893
+					break;
894
+				default :
895
+					throw new DAV\Exception\BadRequest('You are not allowed to create components of type: ' . $component->name . ' here');
896
+
897
+			}
898
+		}
899
+		if (!$foundType)
900
+			throw new DAV\Exception\BadRequest('iCalendar object must contain at least 1 of VEVENT, VTODO or VJOURNAL');
901
+
902
+		// We use an extra variable to allow event handles to tell us wether
903
+		// the object was modified or not.
904
+		//
905
+		// This helps us determine if we need to re-serialize the object.
906
+		$subModified = false;
907
+
908
+		$this->server->emit(
909
+			'calendarObjectChange',
910
+			[
911
+				$request,
912
+				$response,
913
+				$vobj,
914
+				$parentPath,
915
+				&$subModified,
916
+				$isNew
917
+			]
918
+		);
919
+
920
+		if ($subModified) {
921
+			// An event handler told us that it modified the object.
922
+			$data = $vobj->serialize();
923
+
924
+			// Using md5 to figure out if there was an *actual* change.
925
+			if (!$modified && $before !== md5($data)) {
926
+				$modified = true;
927
+			}
928
+
929
+		}
930
+
931
+		// Destroy circular references so PHP will garbage collect the object.
932
+		$vobj->destroy();
933
+
934
+	}
935
+
936
+
937
+	/**
938
+	 * This method is used to generate HTML output for the
939
+	 * DAV\Browser\Plugin. This allows us to generate an interface users
940
+	 * can use to create new calendars.
941
+	 *
942
+	 * @param DAV\INode $node
943
+	 * @param string $output
944
+	 * @return bool
945
+	 */
946
+	public function htmlActionsPanel(DAV\INode $node, &$output) {
947
+
948
+		if (!$node instanceof CalendarHome)
949
+			return;
950
+
951
+		$output .= '<tr><td colspan="2"><form method="post" action="">
952 952
             <h3>Create new calendar</h3>
953 953
             <input type="hidden" name="sabreAction" value="mkcol" />
954 954
             <input type="hidden" name="resourceType" value="{DAV:}collection,{' . self::NS_CALDAV . '}calendar" />
@@ -958,68 +958,68 @@  discard block
 block discarded – undo
958 958
             </form>
959 959
             </td></tr>';
960 960
 
961
-        return false;
962
-
963
-    }
964
-
965
-    /**
966
-     * This event is triggered after GET requests.
967
-     *
968
-     * This is used to transform data into jCal, if this was requested.
969
-     *
970
-     * @param RequestInterface $request
971
-     * @param ResponseInterface $response
972
-     * @return void
973
-     */
974
-    public function httpAfterGet(RequestInterface $request, ResponseInterface $response) {
975
-
976
-        if (strpos($response->getHeader('Content-Type'), 'text/calendar') === false) {
977
-            return;
978
-        }
979
-
980
-        $result = HTTP\Util::negotiate(
981
-            $request->getHeader('Accept'),
982
-            ['text/calendar', 'application/calendar+json']
983
-        );
984
-
985
-        if ($result !== 'application/calendar+json') {
986
-            // Do nothing
987
-            return;
988
-        }
989
-
990
-        // Transforming.
991
-        $vobj = VObject\Reader::read($response->getBody());
992
-
993
-        $jsonBody = json_encode($vobj->jsonSerialize());
994
-        $response->setBody($jsonBody);
995
-
996
-        // Destroy circular references so PHP will garbage collect the object.
997
-        $vobj->destroy();
998
-
999
-        $response->setHeader('Content-Type', 'application/calendar+json');
1000
-        $response->setHeader('Content-Length', strlen($jsonBody));
1001
-
1002
-    }
1003
-
1004
-    /**
1005
-     * Returns a bunch of meta-data about the plugin.
1006
-     *
1007
-     * Providing this information is optional, and is mainly displayed by the
1008
-     * Browser plugin.
1009
-     *
1010
-     * The description key in the returned array may contain html and will not
1011
-     * be sanitized.
1012
-     *
1013
-     * @return array
1014
-     */
1015
-    public function getPluginInfo() {
1016
-
1017
-        return [
1018
-            'name'        => $this->getPluginName(),
1019
-            'description' => 'Adds support for CalDAV (rfc4791)',
1020
-            'link'        => 'http://sabre.io/dav/caldav/',
1021
-        ];
1022
-
1023
-    }
961
+		return false;
962
+
963
+	}
964
+
965
+	/**
966
+	 * This event is triggered after GET requests.
967
+	 *
968
+	 * This is used to transform data into jCal, if this was requested.
969
+	 *
970
+	 * @param RequestInterface $request
971
+	 * @param ResponseInterface $response
972
+	 * @return void
973
+	 */
974
+	public function httpAfterGet(RequestInterface $request, ResponseInterface $response) {
975
+
976
+		if (strpos($response->getHeader('Content-Type'), 'text/calendar') === false) {
977
+			return;
978
+		}
979
+
980
+		$result = HTTP\Util::negotiate(
981
+			$request->getHeader('Accept'),
982
+			['text/calendar', 'application/calendar+json']
983
+		);
984
+
985
+		if ($result !== 'application/calendar+json') {
986
+			// Do nothing
987
+			return;
988
+		}
989
+
990
+		// Transforming.
991
+		$vobj = VObject\Reader::read($response->getBody());
992
+
993
+		$jsonBody = json_encode($vobj->jsonSerialize());
994
+		$response->setBody($jsonBody);
995
+
996
+		// Destroy circular references so PHP will garbage collect the object.
997
+		$vobj->destroy();
998
+
999
+		$response->setHeader('Content-Type', 'application/calendar+json');
1000
+		$response->setHeader('Content-Length', strlen($jsonBody));
1001
+
1002
+	}
1003
+
1004
+	/**
1005
+	 * Returns a bunch of meta-data about the plugin.
1006
+	 *
1007
+	 * Providing this information is optional, and is mainly displayed by the
1008
+	 * Browser plugin.
1009
+	 *
1010
+	 * The description key in the returned array may contain html and will not
1011
+	 * be sanitized.
1012
+	 *
1013
+	 * @return array
1014
+	 */
1015
+	public function getPluginInfo() {
1016
+
1017
+		return [
1018
+			'name'        => $this->getPluginName(),
1019
+			'description' => 'Adds support for CalDAV (rfc4791)',
1020
+			'link'        => 'http://sabre.io/dav/caldav/',
1021
+		];
1022
+
1023
+	}
1024 1024
 
1025 1025
 }
Please login to merge, or discard this patch.
Doc Comments   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -115,7 +115,7 @@  discard block
 block discarded – undo
115 115
     /**
116 116
      * Returns a list of features for the DAV: HTTP header.
117 117
      *
118
-     * @return array
118
+     * @return string[]
119 119
      */
120 120
     public function getFeatures() {
121 121
 
@@ -233,7 +233,7 @@  discard block
 block discarded – undo
233 233
      *
234 234
      * @param string $reportName
235 235
      * @param mixed $report
236
-     * @return bool
236
+     * @return false|null
237 237
      */
238 238
     public function report($reportName, $report) {
239 239
 
@@ -805,7 +805,7 @@  discard block
 block discarded – undo
805 805
      *
806 806
      * An exception is thrown if it's not.
807 807
      *
808
-     * @param resource|string $data
808
+     * @param resource $data
809 809
      * @param string $path
810 810
      * @param bool $modified Should be set to true, if this event handler
811 811
      *                       changed &$data.
@@ -941,7 +941,7 @@  discard block
 block discarded – undo
941 941
      *
942 942
      * @param DAV\INode $node
943 943
      * @param string $output
944
-     * @return bool
944
+     * @return null|false
945 945
      */
946 946
     public function htmlActionsPanel(DAV\INode $node, &$output) {
947 947
 
Please login to merge, or discard this patch.
Braces   +30 added lines, -16 removed lines patch added patch discarded remove patch
@@ -105,8 +105,12 @@  discard block
 block discarded – undo
105 105
         // excludes things like the calendar-proxy-read principal (which it
106 106
         // should).
107 107
         $parts = explode('/', trim($principalUrl, '/'));
108
-        if (count($parts) !== 2) return;
109
-        if ($parts[0] !== 'principals') return;
108
+        if (count($parts) !== 2) {
109
+        	return;
110
+        }
111
+        if ($parts[0] !== 'principals') {
112
+        	return;
113
+        }
110 114
 
111 115
         return self::CALENDAR_ROOT . '/' . $parts[1];
112 116
 
@@ -340,7 +344,9 @@  discard block
 block discarded – undo
340 344
             $propFind->handle('{' . self::NS_CALDAV . '}calendar-home-set', function() use ($principalUrl) {
341 345
 
342 346
                 $calendarHomePath = $this->getCalendarHomeForPrincipal($principalUrl);
343
-                if (is_null($calendarHomePath)) return null;
347
+                if (is_null($calendarHomePath)) {
348
+                	return null;
349
+                }
344 350
                 return new Href($calendarHomePath . '/');
345 351
 
346 352
             });
@@ -408,8 +414,9 @@  discard block
 block discarded – undo
408 414
             // Therefore we simply expose it as a property.
409 415
             $propFind->handle('{' . self::NS_CALDAV . '}calendar-data', function() use ($node) {
410 416
                 $val = $node->get();
411
-                if (is_resource($val))
412
-                    $val = stream_get_contents($val);
417
+                if (is_resource($val)) {
418
+                                    $val = stream_get_contents($val);
419
+                }
413 420
 
414 421
                 // Taking out \r to not screw up the xml output
415 422
                 return str_replace("\r", "", $val);
@@ -748,8 +755,9 @@  discard block
 block discarded – undo
748 755
      */
749 756
     public function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified) {
750 757
 
751
-        if (!$node instanceof ICalendarObject)
752
-            return;
758
+        if (!$node instanceof ICalendarObject) {
759
+                    return;
760
+        }
753 761
 
754 762
         // We're onyl interested in ICalendarObject nodes that are inside of a
755 763
         // real calendar. This is to avoid triggering validation and scheduling
@@ -757,8 +765,9 @@  discard block
 block discarded – undo
757 765
         list($parent) = Uri\split($path);
758 766
         $parentNode = $this->server->tree->getNodeForPath($parent);
759 767
 
760
-        if (!$parentNode instanceof ICalendar)
761
-            return;
768
+        if (!$parentNode instanceof ICalendar) {
769
+                    return;
770
+        }
762 771
 
763 772
         $this->validateICalendar(
764 773
             $data,
@@ -786,8 +795,9 @@  discard block
 block discarded – undo
786 795
      */
787 796
     public function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified) {
788 797
 
789
-        if (!$parentNode instanceof ICalendar)
790
-            return;
798
+        if (!$parentNode instanceof ICalendar) {
799
+                    return;
800
+        }
791 801
 
792 802
         $this->validateICalendar(
793 803
             $data,
@@ -825,7 +835,9 @@  discard block
 block discarded – undo
825 835
         // Converting the data to unicode, if needed.
826 836
         $data = DAV\StringUtil::ensureUTF8($data);
827 837
 
828
-        if ($before !== md5($data)) $modified = true;
838
+        if ($before !== md5($data)) {
839
+        	$modified = true;
840
+        }
829 841
 
830 842
         try {
831 843
 
@@ -896,8 +908,9 @@  discard block
 block discarded – undo
896 908
 
897 909
             }
898 910
         }
899
-        if (!$foundType)
900
-            throw new DAV\Exception\BadRequest('iCalendar object must contain at least 1 of VEVENT, VTODO or VJOURNAL');
911
+        if (!$foundType) {
912
+                    throw new DAV\Exception\BadRequest('iCalendar object must contain at least 1 of VEVENT, VTODO or VJOURNAL');
913
+        }
901 914
 
902 915
         // We use an extra variable to allow event handles to tell us wether
903 916
         // the object was modified or not.
@@ -945,8 +958,9 @@  discard block
 block discarded – undo
945 958
      */
946 959
     public function htmlActionsPanel(DAV\INode $node, &$output) {
947 960
 
948
-        if (!$node instanceof CalendarHome)
949
-            return;
961
+        if (!$node instanceof CalendarHome) {
962
+                    return;
963
+        }
950 964
 
951 965
         $output .= '<tr><td colspan="2"><form method="post" action="">
952 966
             <h3>Create new calendar</h3>
Please login to merge, or discard this patch.
libraries/SabreDAV/CalDAV/Principal/ProxyRead.php 2 patches
Unused Use Statements   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -2,8 +2,8 @@
 block discarded – undo
2 2
 
3 3
 namespace Sabre\CalDAV\Principal;
4 4
 
5
-use Sabre\DAVACL;
6 5
 use Sabre\DAV;
6
+use Sabre\DAVACL;
7 7
 
8 8
 /**
9 9
  * ProxyRead principal
Please login to merge, or discard this patch.
Indentation   +159 added lines, -159 removed lines patch added patch discarded remove patch
@@ -18,164 +18,164 @@
 block discarded – undo
18 18
  */
19 19
 class ProxyRead implements IProxyRead {
20 20
 
21
-    /**
22
-     * Principal information from the parent principal.
23
-     *
24
-     * @var array
25
-     */
26
-    protected $principalInfo;
27
-
28
-    /**
29
-     * Principal backend
30
-     *
31
-     * @var DAVACL\PrincipalBackend\BackendInterface
32
-     */
33
-    protected $principalBackend;
34
-
35
-    /**
36
-     * Creates the object.
37
-     *
38
-     * Note that you MUST supply the parent principal information.
39
-     *
40
-     * @param DAVACL\PrincipalBackend\BackendInterface $principalBackend
41
-     * @param array $principalInfo
42
-     */
43
-    public function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, array $principalInfo) {
44
-
45
-        $this->principalInfo = $principalInfo;
46
-        $this->principalBackend = $principalBackend;
47
-
48
-    }
49
-
50
-    /**
51
-     * Returns this principals name.
52
-     *
53
-     * @return string
54
-     */
55
-    public function getName() {
56
-
57
-        return 'calendar-proxy-read';
58
-
59
-    }
60
-
61
-    /**
62
-     * Returns the last modification time
63
-     *
64
-     * @return null
65
-     */
66
-    public function getLastModified() {
67
-
68
-        return null;
69
-
70
-    }
71
-
72
-    /**
73
-     * Deletes the current node
74
-     *
75
-     * @throws DAV\Exception\Forbidden
76
-     * @return void
77
-     */
78
-    public function delete() {
79
-
80
-        throw new DAV\Exception\Forbidden('Permission denied to delete node');
81
-
82
-    }
83
-
84
-    /**
85
-     * Renames the node
86
-     *
87
-     * @throws DAV\Exception\Forbidden
88
-     * @param string $name The new name
89
-     * @return void
90
-     */
91
-    public function setName($name) {
92
-
93
-        throw new DAV\Exception\Forbidden('Permission denied to rename file');
94
-
95
-    }
96
-
97
-
98
-    /**
99
-     * Returns a list of alternative urls for a principal
100
-     *
101
-     * This can for example be an email address, or ldap url.
102
-     *
103
-     * @return array
104
-     */
105
-    public function getAlternateUriSet() {
106
-
107
-        return [];
108
-
109
-    }
110
-
111
-    /**
112
-     * Returns the full principal url
113
-     *
114
-     * @return string
115
-     */
116
-    public function getPrincipalUrl() {
117
-
118
-        return $this->principalInfo['uri'] . '/' . $this->getName();
119
-
120
-    }
121
-
122
-    /**
123
-     * Returns the list of group members
124
-     *
125
-     * If this principal is a group, this function should return
126
-     * all member principal uri's for the group.
127
-     *
128
-     * @return array
129
-     */
130
-    public function getGroupMemberSet() {
131
-
132
-        return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl());
133
-
134
-    }
135
-
136
-    /**
137
-     * Returns the list of groups this principal is member of
138
-     *
139
-     * If this principal is a member of a (list of) groups, this function
140
-     * should return a list of principal uri's for it's members.
141
-     *
142
-     * @return array
143
-     */
144
-    public function getGroupMembership() {
145
-
146
-        return $this->principalBackend->getGroupMembership($this->getPrincipalUrl());
147
-
148
-    }
149
-
150
-    /**
151
-     * Sets a list of group members
152
-     *
153
-     * If this principal is a group, this method sets all the group members.
154
-     * The list of members is always overwritten, never appended to.
155
-     *
156
-     * This method should throw an exception if the members could not be set.
157
-     *
158
-     * @param array $principals
159
-     * @return void
160
-     */
161
-    public function setGroupMemberSet(array $principals) {
162
-
163
-        $this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals);
164
-
165
-    }
166
-
167
-    /**
168
-     * Returns the displayname
169
-     *
170
-     * This should be a human readable name for the principal.
171
-     * If none is available, return the nodename.
172
-     *
173
-     * @return string
174
-     */
175
-    public function getDisplayName() {
176
-
177
-        return $this->getName();
178
-
179
-    }
21
+	/**
22
+	 * Principal information from the parent principal.
23
+	 *
24
+	 * @var array
25
+	 */
26
+	protected $principalInfo;
27
+
28
+	/**
29
+	 * Principal backend
30
+	 *
31
+	 * @var DAVACL\PrincipalBackend\BackendInterface
32
+	 */
33
+	protected $principalBackend;
34
+
35
+	/**
36
+	 * Creates the object.
37
+	 *
38
+	 * Note that you MUST supply the parent principal information.
39
+	 *
40
+	 * @param DAVACL\PrincipalBackend\BackendInterface $principalBackend
41
+	 * @param array $principalInfo
42
+	 */
43
+	public function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, array $principalInfo) {
44
+
45
+		$this->principalInfo = $principalInfo;
46
+		$this->principalBackend = $principalBackend;
47
+
48
+	}
49
+
50
+	/**
51
+	 * Returns this principals name.
52
+	 *
53
+	 * @return string
54
+	 */
55
+	public function getName() {
56
+
57
+		return 'calendar-proxy-read';
58
+
59
+	}
60
+
61
+	/**
62
+	 * Returns the last modification time
63
+	 *
64
+	 * @return null
65
+	 */
66
+	public function getLastModified() {
67
+
68
+		return null;
69
+
70
+	}
71
+
72
+	/**
73
+	 * Deletes the current node
74
+	 *
75
+	 * @throws DAV\Exception\Forbidden
76
+	 * @return void
77
+	 */
78
+	public function delete() {
79
+
80
+		throw new DAV\Exception\Forbidden('Permission denied to delete node');
81
+
82
+	}
83
+
84
+	/**
85
+	 * Renames the node
86
+	 *
87
+	 * @throws DAV\Exception\Forbidden
88
+	 * @param string $name The new name
89
+	 * @return void
90
+	 */
91
+	public function setName($name) {
92
+
93
+		throw new DAV\Exception\Forbidden('Permission denied to rename file');
94
+
95
+	}
96
+
97
+
98
+	/**
99
+	 * Returns a list of alternative urls for a principal
100
+	 *
101
+	 * This can for example be an email address, or ldap url.
102
+	 *
103
+	 * @return array
104
+	 */
105
+	public function getAlternateUriSet() {
106
+
107
+		return [];
108
+
109
+	}
110
+
111
+	/**
112
+	 * Returns the full principal url
113
+	 *
114
+	 * @return string
115
+	 */
116
+	public function getPrincipalUrl() {
117
+
118
+		return $this->principalInfo['uri'] . '/' . $this->getName();
119
+
120
+	}
121
+
122
+	/**
123
+	 * Returns the list of group members
124
+	 *
125
+	 * If this principal is a group, this function should return
126
+	 * all member principal uri's for the group.
127
+	 *
128
+	 * @return array
129
+	 */
130
+	public function getGroupMemberSet() {
131
+
132
+		return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl());
133
+
134
+	}
135
+
136
+	/**
137
+	 * Returns the list of groups this principal is member of
138
+	 *
139
+	 * If this principal is a member of a (list of) groups, this function
140
+	 * should return a list of principal uri's for it's members.
141
+	 *
142
+	 * @return array
143
+	 */
144
+	public function getGroupMembership() {
145
+
146
+		return $this->principalBackend->getGroupMembership($this->getPrincipalUrl());
147
+
148
+	}
149
+
150
+	/**
151
+	 * Sets a list of group members
152
+	 *
153
+	 * If this principal is a group, this method sets all the group members.
154
+	 * The list of members is always overwritten, never appended to.
155
+	 *
156
+	 * This method should throw an exception if the members could not be set.
157
+	 *
158
+	 * @param array $principals
159
+	 * @return void
160
+	 */
161
+	public function setGroupMemberSet(array $principals) {
162
+
163
+		$this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals);
164
+
165
+	}
166
+
167
+	/**
168
+	 * Returns the displayname
169
+	 *
170
+	 * This should be a human readable name for the principal.
171
+	 * If none is available, return the nodename.
172
+	 *
173
+	 * @return string
174
+	 */
175
+	public function getDisplayName() {
176
+
177
+		return $this->getName();
178
+
179
+	}
180 180
 
181 181
 }
Please login to merge, or discard this patch.