Passed
Push — master ( cdfad9...e39d65 )
by Joas
12:09 queued 10s
created
core/Migrations/Version17000Date20190514105811.php 1 patch
Indentation   +36 added lines, -36 removed lines patch added patch discarded remove patch
@@ -37,42 +37,42 @@
 block discarded – undo
37 37
 
38 38
 class Version17000Date20190514105811 extends SimpleMigrationStep {
39 39
 
40
-	/**
41
-	 * @param IOutput $output
42
-	 * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
43
-	 * @param array $options
44
-	 * @return ISchemaWrapper
45
-	 */
46
-	public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
47
-		/** @var ISchemaWrapper $schema */
48
-		$schema = $schemaClosure();
49
-		if (!$schema->hasTable('filecache_extended')) {
50
-			$table = $schema->createTable('filecache_extended');
51
-			$table->addColumn('fileid', Types::BIGINT, [
52
-				'notnull' => true,
53
-				'length' => 4,
54
-				'unsigned' => true,
55
-			]);
56
-			$table->addColumn('metadata_etag', Types::STRING, [
57
-				'notnull' => false,
58
-				'length' => 40,
59
-			]);
60
-			$table->addColumn('creation_time', Types::BIGINT, [
61
-				'notnull' => true,
62
-				'length' => 20,
63
-				'default' => 0,
64
-			]);
65
-			$table->addColumn('upload_time', Types::BIGINT, [
66
-				'notnull' => true,
67
-				'length' => 20,
68
-				'default' => 0,
69
-			]);
70
-			$table->setPrimaryKey(['fileid'], 'fce_pk');
40
+    /**
41
+     * @param IOutput $output
42
+     * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
43
+     * @param array $options
44
+     * @return ISchemaWrapper
45
+     */
46
+    public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
47
+        /** @var ISchemaWrapper $schema */
48
+        $schema = $schemaClosure();
49
+        if (!$schema->hasTable('filecache_extended')) {
50
+            $table = $schema->createTable('filecache_extended');
51
+            $table->addColumn('fileid', Types::BIGINT, [
52
+                'notnull' => true,
53
+                'length' => 4,
54
+                'unsigned' => true,
55
+            ]);
56
+            $table->addColumn('metadata_etag', Types::STRING, [
57
+                'notnull' => false,
58
+                'length' => 40,
59
+            ]);
60
+            $table->addColumn('creation_time', Types::BIGINT, [
61
+                'notnull' => true,
62
+                'length' => 20,
63
+                'default' => 0,
64
+            ]);
65
+            $table->addColumn('upload_time', Types::BIGINT, [
66
+                'notnull' => true,
67
+                'length' => 20,
68
+                'default' => 0,
69
+            ]);
70
+            $table->setPrimaryKey(['fileid'], 'fce_pk');
71 71
 //			$table->addUniqueIndex(['fileid'], 'fce_fileid_idx');
72
-			$table->addIndex(['creation_time'], 'fce_ctime_idx');
73
-			$table->addIndex(['upload_time'], 'fce_utime_idx');
74
-		}
72
+            $table->addIndex(['creation_time'], 'fce_ctime_idx');
73
+            $table->addIndex(['upload_time'], 'fce_utime_idx');
74
+        }
75 75
 
76
-		return $schema;
77
-	}
76
+        return $schema;
77
+    }
78 78
 }
Please login to merge, or discard this patch.
core/Application.php 1 patch
Indentation   +208 added lines, -208 removed lines patch added patch discarded remove patch
@@ -57,212 +57,212 @@
 block discarded – undo
57 57
  * @package OC\Core
58 58
  */
59 59
 class Application extends App {
60
-	public function __construct() {
61
-		parent::__construct('core');
62
-
63
-		$container = $this->getContainer();
64
-
65
-		$container->registerService('defaultMailAddress', function () {
66
-			return Util::getDefaultEmailAddress('lostpassword-noreply');
67
-		});
68
-
69
-		$server = $container->getServer();
70
-		/** @var IEventDispatcher $eventDispatcher */
71
-		$eventDispatcher = $server->query(IEventDispatcher::class);
72
-
73
-		$notificationManager = $server->getNotificationManager();
74
-		$notificationManager->registerNotifierService(RemoveLinkSharesNotifier::class);
75
-		$notificationManager->registerNotifierService(AuthenticationNotifier::class);
76
-
77
-		$oldEventDispatcher = $server->getEventDispatcher();
78
-
79
-		$oldEventDispatcher->addListener(IDBConnection::CHECK_MISSING_INDEXES_EVENT,
80
-			function (GenericEvent $event) use ($container) {
81
-				/** @var MissingIndexInformation $subject */
82
-				$subject = $event->getSubject();
83
-
84
-				$schema = new SchemaWrapper($container->query(IDBConnection::class));
85
-
86
-				if ($schema->hasTable('share')) {
87
-					$table = $schema->getTable('share');
88
-
89
-					if (!$table->hasIndex('share_with_index')) {
90
-						$subject->addHintForMissingSubject($table->getName(), 'share_with_index');
91
-					}
92
-					if (!$table->hasIndex('parent_index')) {
93
-						$subject->addHintForMissingSubject($table->getName(), 'parent_index');
94
-					}
95
-					if (!$table->hasIndex('owner_index')) {
96
-						$subject->addHintForMissingSubject($table->getName(), 'owner_index');
97
-					}
98
-					if (!$table->hasIndex('initiator_index')) {
99
-						$subject->addHintForMissingSubject($table->getName(), 'initiator_index');
100
-					}
101
-				}
102
-
103
-				if ($schema->hasTable('filecache')) {
104
-					$table = $schema->getTable('filecache');
105
-
106
-					if (!$table->hasIndex('fs_mtime')) {
107
-						$subject->addHintForMissingSubject($table->getName(), 'fs_mtime');
108
-					}
109
-
110
-					if (!$table->hasIndex('fs_size')) {
111
-						$subject->addHintForMissingSubject($table->getName(), 'fs_size');
112
-					}
113
-				}
114
-
115
-				if ($schema->hasTable('twofactor_providers')) {
116
-					$table = $schema->getTable('twofactor_providers');
117
-
118
-					if (!$table->hasIndex('twofactor_providers_uid')) {
119
-						$subject->addHintForMissingSubject($table->getName(), 'twofactor_providers_uid');
120
-					}
121
-				}
122
-
123
-				if ($schema->hasTable('login_flow_v2')) {
124
-					$table = $schema->getTable('login_flow_v2');
125
-
126
-					if (!$table->hasIndex('poll_token')) {
127
-						$subject->addHintForMissingSubject($table->getName(), 'poll_token');
128
-					}
129
-					if (!$table->hasIndex('login_token')) {
130
-						$subject->addHintForMissingSubject($table->getName(), 'login_token');
131
-					}
132
-					if (!$table->hasIndex('timestamp')) {
133
-						$subject->addHintForMissingSubject($table->getName(), 'timestamp');
134
-					}
135
-				}
136
-
137
-				if ($schema->hasTable('whats_new')) {
138
-					$table = $schema->getTable('whats_new');
139
-
140
-					if (!$table->hasIndex('version')) {
141
-						$subject->addHintForMissingSubject($table->getName(), 'version');
142
-					}
143
-				}
144
-
145
-				if ($schema->hasTable('cards')) {
146
-					$table = $schema->getTable('cards');
147
-
148
-					if (!$table->hasIndex('cards_abid')) {
149
-						$subject->addHintForMissingSubject($table->getName(), 'cards_abid');
150
-					}
151
-				}
152
-
153
-				if ($schema->hasTable('cards_properties')) {
154
-					$table = $schema->getTable('cards_properties');
155
-
156
-					if (!$table->hasIndex('cards_prop_abid')) {
157
-						$subject->addHintForMissingSubject($table->getName(), 'cards_prop_abid');
158
-					}
159
-				}
160
-
161
-				if ($schema->hasTable('calendarobjects_props')) {
162
-					$table = $schema->getTable('calendarobjects_props');
163
-
164
-					if (!$table->hasIndex('calendarobject_calid_index')) {
165
-						$subject->addHintForMissingSubject($table->getName(), 'calendarobject_calid_index');
166
-					}
167
-				}
168
-
169
-				if ($schema->hasTable('schedulingobjects')) {
170
-					$table = $schema->getTable('schedulingobjects');
171
-					if (!$table->hasIndex('schedulobj_principuri_index')) {
172
-						$subject->addHintForMissingSubject($table->getName(), 'schedulobj_principuri_index');
173
-					}
174
-				}
175
-
176
-				if ($schema->hasTable('properties')) {
177
-					$table = $schema->getTable('properties');
178
-					if (!$table->hasIndex('properties_path_index')) {
179
-						$subject->addHintForMissingSubject($table->getName(), 'properties_path_index');
180
-					}
181
-				}
182
-			}
183
-		);
184
-
185
-		$oldEventDispatcher->addListener(IDBConnection::CHECK_MISSING_PRIMARY_KEYS_EVENT,
186
-			function (GenericEvent $event) use ($container) {
187
-				/** @var MissingPrimaryKeyInformation $subject */
188
-				$subject = $event->getSubject();
189
-
190
-				$schema = new SchemaWrapper($container->query(IDBConnection::class));
191
-
192
-				if ($schema->hasTable('federated_reshares')) {
193
-					$table = $schema->getTable('federated_reshares');
194
-
195
-					if (!$table->hasPrimaryKey()) {
196
-						$subject->addHintForMissingSubject($table->getName());
197
-					}
198
-				}
199
-
200
-				if ($schema->hasTable('systemtag_object_mapping')) {
201
-					$table = $schema->getTable('systemtag_object_mapping');
202
-
203
-					if (!$table->hasPrimaryKey()) {
204
-						$subject->addHintForMissingSubject($table->getName());
205
-					}
206
-				}
207
-
208
-				if ($schema->hasTable('comments_read_markers')) {
209
-					$table = $schema->getTable('comments_read_markers');
210
-
211
-					if (!$table->hasPrimaryKey()) {
212
-						$subject->addHintForMissingSubject($table->getName());
213
-					}
214
-				}
215
-
216
-				if ($schema->hasTable('collres_resources')) {
217
-					$table = $schema->getTable('collres_resources');
218
-
219
-					if (!$table->hasPrimaryKey()) {
220
-						$subject->addHintForMissingSubject($table->getName());
221
-					}
222
-				}
223
-
224
-				if ($schema->hasTable('collres_accesscache')) {
225
-					$table = $schema->getTable('collres_accesscache');
226
-
227
-					if (!$table->hasPrimaryKey()) {
228
-						$subject->addHintForMissingSubject($table->getName());
229
-					}
230
-				}
231
-
232
-				if ($schema->hasTable('filecache_extended')) {
233
-					$table = $schema->getTable('filecache_extended');
234
-
235
-					if (!$table->hasPrimaryKey()) {
236
-						$subject->addHintForMissingSubject($table->getName());
237
-					}
238
-				}
239
-			}
240
-		);
241
-
242
-		$oldEventDispatcher->addListener(IDBConnection::CHECK_MISSING_COLUMNS_EVENT,
243
-			function (GenericEvent $event) use ($container) {
244
-				/** @var MissingColumnInformation $subject */
245
-				$subject = $event->getSubject();
246
-
247
-				$schema = new SchemaWrapper($container->query(IDBConnection::class));
248
-
249
-				if ($schema->hasTable('comments')) {
250
-					$table = $schema->getTable('comments');
251
-
252
-					if (!$table->hasColumn('reference_id')) {
253
-						$subject->addHintForMissingColumn($table->getName(), 'reference_id');
254
-					}
255
-				}
256
-			}
257
-		);
258
-
259
-		$eventDispatcher->addServiceListener(RemoteWipeStarted::class, RemoteWipeActivityListener::class);
260
-		$eventDispatcher->addServiceListener(RemoteWipeStarted::class, RemoteWipeNotificationsListener::class);
261
-		$eventDispatcher->addServiceListener(RemoteWipeStarted::class, RemoteWipeEmailListener::class);
262
-		$eventDispatcher->addServiceListener(RemoteWipeFinished::class, RemoteWipeActivityListener::class);
263
-		$eventDispatcher->addServiceListener(RemoteWipeFinished::class, RemoteWipeNotificationsListener::class);
264
-		$eventDispatcher->addServiceListener(RemoteWipeFinished::class, RemoteWipeEmailListener::class);
265
-		$eventDispatcher->addServiceListener(UserDeletedEvent::class, UserDeletedStoreCleanupListener::class);
266
-		$eventDispatcher->addServiceListener(UserDeletedEvent::class, UserDeletedTokenCleanupListener::class);
267
-	}
60
+    public function __construct() {
61
+        parent::__construct('core');
62
+
63
+        $container = $this->getContainer();
64
+
65
+        $container->registerService('defaultMailAddress', function () {
66
+            return Util::getDefaultEmailAddress('lostpassword-noreply');
67
+        });
68
+
69
+        $server = $container->getServer();
70
+        /** @var IEventDispatcher $eventDispatcher */
71
+        $eventDispatcher = $server->query(IEventDispatcher::class);
72
+
73
+        $notificationManager = $server->getNotificationManager();
74
+        $notificationManager->registerNotifierService(RemoveLinkSharesNotifier::class);
75
+        $notificationManager->registerNotifierService(AuthenticationNotifier::class);
76
+
77
+        $oldEventDispatcher = $server->getEventDispatcher();
78
+
79
+        $oldEventDispatcher->addListener(IDBConnection::CHECK_MISSING_INDEXES_EVENT,
80
+            function (GenericEvent $event) use ($container) {
81
+                /** @var MissingIndexInformation $subject */
82
+                $subject = $event->getSubject();
83
+
84
+                $schema = new SchemaWrapper($container->query(IDBConnection::class));
85
+
86
+                if ($schema->hasTable('share')) {
87
+                    $table = $schema->getTable('share');
88
+
89
+                    if (!$table->hasIndex('share_with_index')) {
90
+                        $subject->addHintForMissingSubject($table->getName(), 'share_with_index');
91
+                    }
92
+                    if (!$table->hasIndex('parent_index')) {
93
+                        $subject->addHintForMissingSubject($table->getName(), 'parent_index');
94
+                    }
95
+                    if (!$table->hasIndex('owner_index')) {
96
+                        $subject->addHintForMissingSubject($table->getName(), 'owner_index');
97
+                    }
98
+                    if (!$table->hasIndex('initiator_index')) {
99
+                        $subject->addHintForMissingSubject($table->getName(), 'initiator_index');
100
+                    }
101
+                }
102
+
103
+                if ($schema->hasTable('filecache')) {
104
+                    $table = $schema->getTable('filecache');
105
+
106
+                    if (!$table->hasIndex('fs_mtime')) {
107
+                        $subject->addHintForMissingSubject($table->getName(), 'fs_mtime');
108
+                    }
109
+
110
+                    if (!$table->hasIndex('fs_size')) {
111
+                        $subject->addHintForMissingSubject($table->getName(), 'fs_size');
112
+                    }
113
+                }
114
+
115
+                if ($schema->hasTable('twofactor_providers')) {
116
+                    $table = $schema->getTable('twofactor_providers');
117
+
118
+                    if (!$table->hasIndex('twofactor_providers_uid')) {
119
+                        $subject->addHintForMissingSubject($table->getName(), 'twofactor_providers_uid');
120
+                    }
121
+                }
122
+
123
+                if ($schema->hasTable('login_flow_v2')) {
124
+                    $table = $schema->getTable('login_flow_v2');
125
+
126
+                    if (!$table->hasIndex('poll_token')) {
127
+                        $subject->addHintForMissingSubject($table->getName(), 'poll_token');
128
+                    }
129
+                    if (!$table->hasIndex('login_token')) {
130
+                        $subject->addHintForMissingSubject($table->getName(), 'login_token');
131
+                    }
132
+                    if (!$table->hasIndex('timestamp')) {
133
+                        $subject->addHintForMissingSubject($table->getName(), 'timestamp');
134
+                    }
135
+                }
136
+
137
+                if ($schema->hasTable('whats_new')) {
138
+                    $table = $schema->getTable('whats_new');
139
+
140
+                    if (!$table->hasIndex('version')) {
141
+                        $subject->addHintForMissingSubject($table->getName(), 'version');
142
+                    }
143
+                }
144
+
145
+                if ($schema->hasTable('cards')) {
146
+                    $table = $schema->getTable('cards');
147
+
148
+                    if (!$table->hasIndex('cards_abid')) {
149
+                        $subject->addHintForMissingSubject($table->getName(), 'cards_abid');
150
+                    }
151
+                }
152
+
153
+                if ($schema->hasTable('cards_properties')) {
154
+                    $table = $schema->getTable('cards_properties');
155
+
156
+                    if (!$table->hasIndex('cards_prop_abid')) {
157
+                        $subject->addHintForMissingSubject($table->getName(), 'cards_prop_abid');
158
+                    }
159
+                }
160
+
161
+                if ($schema->hasTable('calendarobjects_props')) {
162
+                    $table = $schema->getTable('calendarobjects_props');
163
+
164
+                    if (!$table->hasIndex('calendarobject_calid_index')) {
165
+                        $subject->addHintForMissingSubject($table->getName(), 'calendarobject_calid_index');
166
+                    }
167
+                }
168
+
169
+                if ($schema->hasTable('schedulingobjects')) {
170
+                    $table = $schema->getTable('schedulingobjects');
171
+                    if (!$table->hasIndex('schedulobj_principuri_index')) {
172
+                        $subject->addHintForMissingSubject($table->getName(), 'schedulobj_principuri_index');
173
+                    }
174
+                }
175
+
176
+                if ($schema->hasTable('properties')) {
177
+                    $table = $schema->getTable('properties');
178
+                    if (!$table->hasIndex('properties_path_index')) {
179
+                        $subject->addHintForMissingSubject($table->getName(), 'properties_path_index');
180
+                    }
181
+                }
182
+            }
183
+        );
184
+
185
+        $oldEventDispatcher->addListener(IDBConnection::CHECK_MISSING_PRIMARY_KEYS_EVENT,
186
+            function (GenericEvent $event) use ($container) {
187
+                /** @var MissingPrimaryKeyInformation $subject */
188
+                $subject = $event->getSubject();
189
+
190
+                $schema = new SchemaWrapper($container->query(IDBConnection::class));
191
+
192
+                if ($schema->hasTable('federated_reshares')) {
193
+                    $table = $schema->getTable('federated_reshares');
194
+
195
+                    if (!$table->hasPrimaryKey()) {
196
+                        $subject->addHintForMissingSubject($table->getName());
197
+                    }
198
+                }
199
+
200
+                if ($schema->hasTable('systemtag_object_mapping')) {
201
+                    $table = $schema->getTable('systemtag_object_mapping');
202
+
203
+                    if (!$table->hasPrimaryKey()) {
204
+                        $subject->addHintForMissingSubject($table->getName());
205
+                    }
206
+                }
207
+
208
+                if ($schema->hasTable('comments_read_markers')) {
209
+                    $table = $schema->getTable('comments_read_markers');
210
+
211
+                    if (!$table->hasPrimaryKey()) {
212
+                        $subject->addHintForMissingSubject($table->getName());
213
+                    }
214
+                }
215
+
216
+                if ($schema->hasTable('collres_resources')) {
217
+                    $table = $schema->getTable('collres_resources');
218
+
219
+                    if (!$table->hasPrimaryKey()) {
220
+                        $subject->addHintForMissingSubject($table->getName());
221
+                    }
222
+                }
223
+
224
+                if ($schema->hasTable('collres_accesscache')) {
225
+                    $table = $schema->getTable('collres_accesscache');
226
+
227
+                    if (!$table->hasPrimaryKey()) {
228
+                        $subject->addHintForMissingSubject($table->getName());
229
+                    }
230
+                }
231
+
232
+                if ($schema->hasTable('filecache_extended')) {
233
+                    $table = $schema->getTable('filecache_extended');
234
+
235
+                    if (!$table->hasPrimaryKey()) {
236
+                        $subject->addHintForMissingSubject($table->getName());
237
+                    }
238
+                }
239
+            }
240
+        );
241
+
242
+        $oldEventDispatcher->addListener(IDBConnection::CHECK_MISSING_COLUMNS_EVENT,
243
+            function (GenericEvent $event) use ($container) {
244
+                /** @var MissingColumnInformation $subject */
245
+                $subject = $event->getSubject();
246
+
247
+                $schema = new SchemaWrapper($container->query(IDBConnection::class));
248
+
249
+                if ($schema->hasTable('comments')) {
250
+                    $table = $schema->getTable('comments');
251
+
252
+                    if (!$table->hasColumn('reference_id')) {
253
+                        $subject->addHintForMissingColumn($table->getName(), 'reference_id');
254
+                    }
255
+                }
256
+            }
257
+        );
258
+
259
+        $eventDispatcher->addServiceListener(RemoteWipeStarted::class, RemoteWipeActivityListener::class);
260
+        $eventDispatcher->addServiceListener(RemoteWipeStarted::class, RemoteWipeNotificationsListener::class);
261
+        $eventDispatcher->addServiceListener(RemoteWipeStarted::class, RemoteWipeEmailListener::class);
262
+        $eventDispatcher->addServiceListener(RemoteWipeFinished::class, RemoteWipeActivityListener::class);
263
+        $eventDispatcher->addServiceListener(RemoteWipeFinished::class, RemoteWipeNotificationsListener::class);
264
+        $eventDispatcher->addServiceListener(RemoteWipeFinished::class, RemoteWipeEmailListener::class);
265
+        $eventDispatcher->addServiceListener(UserDeletedEvent::class, UserDeletedStoreCleanupListener::class);
266
+        $eventDispatcher->addServiceListener(UserDeletedEvent::class, UserDeletedTokenCleanupListener::class);
267
+    }
268 268
 }
Please login to merge, or discard this patch.
core/Command/Db/AddMissingPrimaryKeys.php 1 patch
Indentation   +128 added lines, -128 removed lines patch added patch discarded remove patch
@@ -50,132 +50,132 @@
 block discarded – undo
50 50
  */
51 51
 class AddMissingPrimaryKeys extends Command {
52 52
 
53
-	/** @var IDBConnection */
54
-	private $connection;
55
-
56
-	/** @var EventDispatcherInterface */
57
-	private $dispatcher;
58
-
59
-	public function __construct(IDBConnection $connection, EventDispatcherInterface $dispatcher) {
60
-		parent::__construct();
61
-
62
-		$this->connection = $connection;
63
-		$this->dispatcher = $dispatcher;
64
-	}
65
-
66
-	protected function configure() {
67
-		$this
68
-			->setName('db:add-missing-primary-keys')
69
-			->setDescription('Add missing primary keys to the database tables');
70
-	}
71
-
72
-	protected function execute(InputInterface $input, OutputInterface $output): int {
73
-		$this->addCorePrimaryKeys($output);
74
-
75
-		// Dispatch event so apps can also update indexes if needed
76
-		$event = new GenericEvent($output);
77
-		$this->dispatcher->dispatch(IDBConnection::ADD_MISSING_PRIMARY_KEYS_EVENT, $event);
78
-		return 0;
79
-	}
80
-
81
-	/**
82
-	 * add missing indices to the share table
83
-	 *
84
-	 * @param OutputInterface $output
85
-	 * @throws \Doctrine\DBAL\Schema\SchemaException
86
-	 */
87
-	private function addCorePrimaryKeys(OutputInterface $output) {
88
-		$output->writeln('<info>Check primary keys.</info>');
89
-
90
-		$schema = new SchemaWrapper($this->connection);
91
-		$updated = false;
92
-
93
-		if ($schema->hasTable('federated_reshares')) {
94
-			$table = $schema->getTable('federated_reshares');
95
-			if (!$table->hasPrimaryKey()) {
96
-				$output->writeln('<info>Adding primary key to the federated_reshares table, this can take some time...</info>');
97
-				$table->setPrimaryKey(['share_id'], 'federated_res_pk');
98
-				if ($table->hasIndex('share_id_index')) {
99
-					$table->dropIndex('share_id_index');
100
-				}
101
-				$this->connection->migrateToSchema($schema->getWrappedSchema());
102
-				$updated = true;
103
-				$output->writeln('<info>federated_reshares table updated successfully.</info>');
104
-			}
105
-		}
106
-
107
-		if ($schema->hasTable('systemtag_object_mapping')) {
108
-			$table = $schema->getTable('systemtag_object_mapping');
109
-			if (!$table->hasPrimaryKey()) {
110
-				$output->writeln('<info>Adding primary key to the systemtag_object_mapping table, this can take some time...</info>');
111
-				$table->setPrimaryKey(['objecttype', 'objectid', 'systemtagid'], 'som_pk');
112
-				if ($table->hasIndex('mapping')) {
113
-					$table->dropIndex('mapping');
114
-				}
115
-				$this->connection->migrateToSchema($schema->getWrappedSchema());
116
-				$updated = true;
117
-				$output->writeln('<info>systemtag_object_mapping table updated successfully.</info>');
118
-			}
119
-		}
120
-
121
-		if ($schema->hasTable('comments_read_markers')) {
122
-			$table = $schema->getTable('comments_read_markers');
123
-			if (!$table->hasPrimaryKey()) {
124
-				$output->writeln('<info>Adding primary key to the comments_read_markers table, this can take some time...</info>');
125
-				$table->setPrimaryKey(['user_id', 'object_type', 'object_id'], 'crm_pk');
126
-				if ($table->hasIndex('comments_marker_index')) {
127
-					$table->dropIndex('comments_marker_index');
128
-				}
129
-				$this->connection->migrateToSchema($schema->getWrappedSchema());
130
-				$updated = true;
131
-				$output->writeln('<info>comments_read_markers table updated successfully.</info>');
132
-			}
133
-		}
134
-
135
-		if ($schema->hasTable('collres_resources')) {
136
-			$table = $schema->getTable('collres_resources');
137
-			if (!$table->hasPrimaryKey()) {
138
-				$output->writeln('<info>Adding primary key to the collres_resources table, this can take some time...</info>');
139
-				$table->setPrimaryKey(['collection_id', 'resource_type', 'resource_id'], 'crr_pk');
140
-				if ($table->hasIndex('collres_unique_res')) {
141
-					$table->dropIndex('collres_unique_res');
142
-				}
143
-				$this->connection->migrateToSchema($schema->getWrappedSchema());
144
-				$updated = true;
145
-				$output->writeln('<info>collres_resources table updated successfully.</info>');
146
-			}
147
-		}
148
-
149
-		if ($schema->hasTable('collres_accesscache')) {
150
-			$table = $schema->getTable('collres_accesscache');
151
-			if (!$table->hasPrimaryKey()) {
152
-				$output->writeln('<info>Adding primary key to the collres_accesscache table, this can take some time...</info>');
153
-				$table->setPrimaryKey(['user_id', 'collection_id', 'resource_type', 'resource_id'], 'cra_pk');
154
-				if ($table->hasIndex('collres_unique_user')) {
155
-					$table->dropIndex('collres_unique_user');
156
-				}
157
-				$this->connection->migrateToSchema($schema->getWrappedSchema());
158
-				$updated = true;
159
-				$output->writeln('<info>collres_accesscache table updated successfully.</info>');
160
-			}
161
-		}
162
-
163
-		if ($schema->hasTable('filecache_extended')) {
164
-			$table = $schema->getTable('filecache_extended');
165
-			if (!$table->hasPrimaryKey()) {
166
-				$output->writeln('<info>Adding primary key to the filecache_extended table, this can take some time...</info>');
167
-				$table->setPrimaryKey(['fileid'], 'fce_pk');
168
-				if ($table->hasIndex('fce_fileid_idx')) {
169
-					$table->dropIndex('fce_fileid_idx');
170
-				}
171
-				$this->connection->migrateToSchema($schema->getWrappedSchema());
172
-				$updated = true;
173
-				$output->writeln('<info>filecache_extended table updated successfully.</info>');
174
-			}
175
-		}
176
-
177
-		if (!$updated) {
178
-			$output->writeln('<info>Done.</info>');
179
-		}
180
-	}
53
+    /** @var IDBConnection */
54
+    private $connection;
55
+
56
+    /** @var EventDispatcherInterface */
57
+    private $dispatcher;
58
+
59
+    public function __construct(IDBConnection $connection, EventDispatcherInterface $dispatcher) {
60
+        parent::__construct();
61
+
62
+        $this->connection = $connection;
63
+        $this->dispatcher = $dispatcher;
64
+    }
65
+
66
+    protected function configure() {
67
+        $this
68
+            ->setName('db:add-missing-primary-keys')
69
+            ->setDescription('Add missing primary keys to the database tables');
70
+    }
71
+
72
+    protected function execute(InputInterface $input, OutputInterface $output): int {
73
+        $this->addCorePrimaryKeys($output);
74
+
75
+        // Dispatch event so apps can also update indexes if needed
76
+        $event = new GenericEvent($output);
77
+        $this->dispatcher->dispatch(IDBConnection::ADD_MISSING_PRIMARY_KEYS_EVENT, $event);
78
+        return 0;
79
+    }
80
+
81
+    /**
82
+     * add missing indices to the share table
83
+     *
84
+     * @param OutputInterface $output
85
+     * @throws \Doctrine\DBAL\Schema\SchemaException
86
+     */
87
+    private function addCorePrimaryKeys(OutputInterface $output) {
88
+        $output->writeln('<info>Check primary keys.</info>');
89
+
90
+        $schema = new SchemaWrapper($this->connection);
91
+        $updated = false;
92
+
93
+        if ($schema->hasTable('federated_reshares')) {
94
+            $table = $schema->getTable('federated_reshares');
95
+            if (!$table->hasPrimaryKey()) {
96
+                $output->writeln('<info>Adding primary key to the federated_reshares table, this can take some time...</info>');
97
+                $table->setPrimaryKey(['share_id'], 'federated_res_pk');
98
+                if ($table->hasIndex('share_id_index')) {
99
+                    $table->dropIndex('share_id_index');
100
+                }
101
+                $this->connection->migrateToSchema($schema->getWrappedSchema());
102
+                $updated = true;
103
+                $output->writeln('<info>federated_reshares table updated successfully.</info>');
104
+            }
105
+        }
106
+
107
+        if ($schema->hasTable('systemtag_object_mapping')) {
108
+            $table = $schema->getTable('systemtag_object_mapping');
109
+            if (!$table->hasPrimaryKey()) {
110
+                $output->writeln('<info>Adding primary key to the systemtag_object_mapping table, this can take some time...</info>');
111
+                $table->setPrimaryKey(['objecttype', 'objectid', 'systemtagid'], 'som_pk');
112
+                if ($table->hasIndex('mapping')) {
113
+                    $table->dropIndex('mapping');
114
+                }
115
+                $this->connection->migrateToSchema($schema->getWrappedSchema());
116
+                $updated = true;
117
+                $output->writeln('<info>systemtag_object_mapping table updated successfully.</info>');
118
+            }
119
+        }
120
+
121
+        if ($schema->hasTable('comments_read_markers')) {
122
+            $table = $schema->getTable('comments_read_markers');
123
+            if (!$table->hasPrimaryKey()) {
124
+                $output->writeln('<info>Adding primary key to the comments_read_markers table, this can take some time...</info>');
125
+                $table->setPrimaryKey(['user_id', 'object_type', 'object_id'], 'crm_pk');
126
+                if ($table->hasIndex('comments_marker_index')) {
127
+                    $table->dropIndex('comments_marker_index');
128
+                }
129
+                $this->connection->migrateToSchema($schema->getWrappedSchema());
130
+                $updated = true;
131
+                $output->writeln('<info>comments_read_markers table updated successfully.</info>');
132
+            }
133
+        }
134
+
135
+        if ($schema->hasTable('collres_resources')) {
136
+            $table = $schema->getTable('collres_resources');
137
+            if (!$table->hasPrimaryKey()) {
138
+                $output->writeln('<info>Adding primary key to the collres_resources table, this can take some time...</info>');
139
+                $table->setPrimaryKey(['collection_id', 'resource_type', 'resource_id'], 'crr_pk');
140
+                if ($table->hasIndex('collres_unique_res')) {
141
+                    $table->dropIndex('collres_unique_res');
142
+                }
143
+                $this->connection->migrateToSchema($schema->getWrappedSchema());
144
+                $updated = true;
145
+                $output->writeln('<info>collres_resources table updated successfully.</info>');
146
+            }
147
+        }
148
+
149
+        if ($schema->hasTable('collres_accesscache')) {
150
+            $table = $schema->getTable('collres_accesscache');
151
+            if (!$table->hasPrimaryKey()) {
152
+                $output->writeln('<info>Adding primary key to the collres_accesscache table, this can take some time...</info>');
153
+                $table->setPrimaryKey(['user_id', 'collection_id', 'resource_type', 'resource_id'], 'cra_pk');
154
+                if ($table->hasIndex('collres_unique_user')) {
155
+                    $table->dropIndex('collres_unique_user');
156
+                }
157
+                $this->connection->migrateToSchema($schema->getWrappedSchema());
158
+                $updated = true;
159
+                $output->writeln('<info>collres_accesscache table updated successfully.</info>');
160
+            }
161
+        }
162
+
163
+        if ($schema->hasTable('filecache_extended')) {
164
+            $table = $schema->getTable('filecache_extended');
165
+            if (!$table->hasPrimaryKey()) {
166
+                $output->writeln('<info>Adding primary key to the filecache_extended table, this can take some time...</info>');
167
+                $table->setPrimaryKey(['fileid'], 'fce_pk');
168
+                if ($table->hasIndex('fce_fileid_idx')) {
169
+                    $table->dropIndex('fce_fileid_idx');
170
+                }
171
+                $this->connection->migrateToSchema($schema->getWrappedSchema());
172
+                $updated = true;
173
+                $output->writeln('<info>filecache_extended table updated successfully.</info>');
174
+            }
175
+        }
176
+
177
+        if (!$updated) {
178
+            $output->writeln('<info>Done.</info>');
179
+        }
180
+    }
181 181
 }
Please login to merge, or discard this patch.
apps/dav/lib/CalDAV/CalDavBackend.php 1 patch
Indentation   +2747 added lines, -2747 removed lines patch added patch discarded remove patch
@@ -95,2751 +95,2751 @@
 block discarded – undo
95 95
  * @package OCA\DAV\CalDAV
96 96
  */
97 97
 class CalDavBackend extends AbstractBackend implements SyncSupport, SubscriptionSupport, SchedulingSupport {
98
-	public const CALENDAR_TYPE_CALENDAR = 0;
99
-	public const CALENDAR_TYPE_SUBSCRIPTION = 1;
100
-
101
-	public const PERSONAL_CALENDAR_URI = 'personal';
102
-	public const PERSONAL_CALENDAR_NAME = 'Personal';
103
-
104
-	public const RESOURCE_BOOKING_CALENDAR_URI = 'calendar';
105
-	public const RESOURCE_BOOKING_CALENDAR_NAME = 'Calendar';
106
-
107
-	/**
108
-	 * We need to specify a max date, because we need to stop *somewhere*
109
-	 *
110
-	 * On 32 bit system the maximum for a signed integer is 2147483647, so
111
-	 * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
112
-	 * in 2038-01-19 to avoid problems when the date is converted
113
-	 * to a unix timestamp.
114
-	 */
115
-	public const MAX_DATE = '2038-01-01';
116
-
117
-	public const ACCESS_PUBLIC = 4;
118
-	public const CLASSIFICATION_PUBLIC = 0;
119
-	public const CLASSIFICATION_PRIVATE = 1;
120
-	public const CLASSIFICATION_CONFIDENTIAL = 2;
121
-
122
-	/**
123
-	 * List of CalDAV properties, and how they map to database field names
124
-	 * Add your own properties by simply adding on to this array.
125
-	 *
126
-	 * Note that only string-based properties are supported here.
127
-	 *
128
-	 * @var array
129
-	 */
130
-	public $propertyMap = [
131
-		'{DAV:}displayname' => 'displayname',
132
-		'{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
133
-		'{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone',
134
-		'{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
135
-		'{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
136
-	];
137
-
138
-	/**
139
-	 * List of subscription properties, and how they map to database field names.
140
-	 *
141
-	 * @var array
142
-	 */
143
-	public $subscriptionPropertyMap = [
144
-		'{DAV:}displayname' => 'displayname',
145
-		'{http://apple.com/ns/ical/}refreshrate' => 'refreshrate',
146
-		'{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
147
-		'{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
148
-		'{http://calendarserver.org/ns/}subscribed-strip-todos' => 'striptodos',
149
-		'{http://calendarserver.org/ns/}subscribed-strip-alarms' => 'stripalarms',
150
-		'{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
151
-	];
152
-
153
-	/** @var array properties to index */
154
-	public static $indexProperties = ['CATEGORIES', 'COMMENT', 'DESCRIPTION',
155
-		'LOCATION', 'RESOURCES', 'STATUS', 'SUMMARY', 'ATTENDEE', 'CONTACT',
156
-		'ORGANIZER'];
157
-
158
-	/** @var array parameters to index */
159
-	public static $indexParameters = [
160
-		'ATTENDEE' => ['CN'],
161
-		'ORGANIZER' => ['CN'],
162
-	];
163
-
164
-	/**
165
-	 * @var string[] Map of uid => display name
166
-	 */
167
-	protected $userDisplayNames;
168
-
169
-	/** @var IDBConnection */
170
-	private $db;
171
-
172
-	/** @var Backend */
173
-	private $calendarSharingBackend;
174
-
175
-	/** @var Principal */
176
-	private $principalBackend;
177
-
178
-	/** @var IUserManager */
179
-	private $userManager;
180
-
181
-	/** @var ISecureRandom */
182
-	private $random;
183
-
184
-	/** @var ILogger */
185
-	private $logger;
186
-
187
-	/** @var IEventDispatcher */
188
-	private $dispatcher;
189
-
190
-	/** @var EventDispatcherInterface */
191
-	private $legacyDispatcher;
192
-
193
-	/** @var bool */
194
-	private $legacyEndpoint;
195
-
196
-	/** @var string */
197
-	private $dbObjectPropertiesTable = 'calendarobjects_props';
198
-
199
-	/**
200
-	 * CalDavBackend constructor.
201
-	 *
202
-	 * @param IDBConnection $db
203
-	 * @param Principal $principalBackend
204
-	 * @param IUserManager $userManager
205
-	 * @param IGroupManager $groupManager
206
-	 * @param ISecureRandom $random
207
-	 * @param ILogger $logger
208
-	 * @param IEventDispatcher $dispatcher
209
-	 * @param EventDispatcherInterface $legacyDispatcher
210
-	 * @param bool $legacyEndpoint
211
-	 */
212
-	public function __construct(IDBConnection $db,
213
-								Principal $principalBackend,
214
-								IUserManager $userManager,
215
-								IGroupManager $groupManager,
216
-								ISecureRandom $random,
217
-								ILogger $logger,
218
-								IEventDispatcher $dispatcher,
219
-								EventDispatcherInterface $legacyDispatcher,
220
-								bool $legacyEndpoint = false) {
221
-		$this->db = $db;
222
-		$this->principalBackend = $principalBackend;
223
-		$this->userManager = $userManager;
224
-		$this->calendarSharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'calendar');
225
-		$this->random = $random;
226
-		$this->logger = $logger;
227
-		$this->dispatcher = $dispatcher;
228
-		$this->legacyDispatcher = $legacyDispatcher;
229
-		$this->legacyEndpoint = $legacyEndpoint;
230
-	}
231
-
232
-	/**
233
-	 * Return the number of calendars for a principal
234
-	 *
235
-	 * By default this excludes the automatically generated birthday calendar
236
-	 *
237
-	 * @param $principalUri
238
-	 * @param bool $excludeBirthday
239
-	 * @return int
240
-	 */
241
-	public function getCalendarsForUserCount($principalUri, $excludeBirthday = true) {
242
-		$principalUri = $this->convertPrincipal($principalUri, true);
243
-		$query = $this->db->getQueryBuilder();
244
-		$query->select($query->func()->count('*'))
245
-			->from('calendars');
246
-
247
-		if ($principalUri === '') {
248
-			$query->where($query->expr()->emptyString('principaluri'));
249
-		} else {
250
-			$query->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
251
-		}
252
-
253
-		if ($excludeBirthday) {
254
-			$query->andWhere($query->expr()->neq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)));
255
-		}
256
-
257
-		$result = $query->execute();
258
-		$column = (int)$result->fetchColumn();
259
-		$result->closeCursor();
260
-		return $column;
261
-	}
262
-
263
-	/**
264
-	 * Returns a list of calendars for a principal.
265
-	 *
266
-	 * Every project is an array with the following keys:
267
-	 *  * id, a unique id that will be used by other functions to modify the
268
-	 *    calendar. This can be the same as the uri or a database key.
269
-	 *  * uri, which the basename of the uri with which the calendar is
270
-	 *    accessed.
271
-	 *  * principaluri. The owner of the calendar. Almost always the same as
272
-	 *    principalUri passed to this method.
273
-	 *
274
-	 * Furthermore it can contain webdav properties in clark notation. A very
275
-	 * common one is '{DAV:}displayname'.
276
-	 *
277
-	 * Many clients also require:
278
-	 * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
279
-	 * For this property, you can just return an instance of
280
-	 * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
281
-	 *
282
-	 * If you return {http://sabredav.org/ns}read-only and set the value to 1,
283
-	 * ACL will automatically be put in read-only mode.
284
-	 *
285
-	 * @param string $principalUri
286
-	 * @return array
287
-	 */
288
-	public function getCalendarsForUser($principalUri) {
289
-		$principalUriOriginal = $principalUri;
290
-		$principalUri = $this->convertPrincipal($principalUri, true);
291
-		$fields = array_values($this->propertyMap);
292
-		$fields[] = 'id';
293
-		$fields[] = 'uri';
294
-		$fields[] = 'synctoken';
295
-		$fields[] = 'components';
296
-		$fields[] = 'principaluri';
297
-		$fields[] = 'transparent';
298
-
299
-		// Making fields a comma-delimited list
300
-		$query = $this->db->getQueryBuilder();
301
-		$query->select($fields)
302
-			->from('calendars')
303
-			->orderBy('calendarorder', 'ASC');
304
-
305
-		if ($principalUri === '') {
306
-			$query->where($query->expr()->emptyString('principaluri'));
307
-		} else {
308
-			$query->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
309
-		}
310
-
311
-		$result = $query->execute();
312
-
313
-		$calendars = [];
314
-		while ($row = $result->fetch()) {
315
-			$row['principaluri'] = (string) $row['principaluri'];
316
-			$components = [];
317
-			if ($row['components']) {
318
-				$components = explode(',',$row['components']);
319
-			}
320
-
321
-			$calendar = [
322
-				'id' => $row['id'],
323
-				'uri' => $row['uri'],
324
-				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
325
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
326
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
327
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
328
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
329
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
330
-			];
331
-
332
-			foreach ($this->propertyMap as $xmlName => $dbName) {
333
-				$calendar[$xmlName] = $row[$dbName];
334
-			}
335
-
336
-			$this->addOwnerPrincipal($calendar);
337
-
338
-			if (!isset($calendars[$calendar['id']])) {
339
-				$calendars[$calendar['id']] = $calendar;
340
-			}
341
-		}
342
-		$result->closeCursor();
343
-
344
-		// query for shared calendars
345
-		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
346
-		$principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
347
-
348
-		$principals = array_map(function ($principal) {
349
-			return urldecode($principal);
350
-		}, $principals);
351
-		$principals[] = $principalUri;
352
-
353
-		$fields = array_values($this->propertyMap);
354
-		$fields[] = 'a.id';
355
-		$fields[] = 'a.uri';
356
-		$fields[] = 'a.synctoken';
357
-		$fields[] = 'a.components';
358
-		$fields[] = 'a.principaluri';
359
-		$fields[] = 'a.transparent';
360
-		$fields[] = 's.access';
361
-		$query = $this->db->getQueryBuilder();
362
-		$query->select($fields)
363
-			->from('dav_shares', 's')
364
-			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
365
-			->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
366
-			->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
367
-			->setParameter('type', 'calendar')
368
-			->setParameter('principaluri', $principals, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY);
369
-
370
-		$result	= $query->execute();
371
-
372
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
373
-		while ($row = $result->fetch()) {
374
-			$row['principaluri'] = (string) $row['principaluri'];
375
-			if ($row['principaluri'] === $principalUri) {
376
-				continue;
377
-			}
378
-
379
-			$readOnly = (int) $row['access'] === Backend::ACCESS_READ;
380
-			if (isset($calendars[$row['id']])) {
381
-				if ($readOnly) {
382
-					// New share can not have more permissions then the old one.
383
-					continue;
384
-				}
385
-				if (isset($calendars[$row['id']][$readOnlyPropertyName]) &&
386
-					$calendars[$row['id']][$readOnlyPropertyName] === 0) {
387
-					// Old share is already read-write, no more permissions can be gained
388
-					continue;
389
-				}
390
-			}
391
-
392
-			list(, $name) = Uri\split($row['principaluri']);
393
-			$uri = $row['uri'] . '_shared_by_' . $name;
394
-			$row['displayname'] = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
395
-			$components = [];
396
-			if ($row['components']) {
397
-				$components = explode(',',$row['components']);
398
-			}
399
-			$calendar = [
400
-				'id' => $row['id'],
401
-				'uri' => $uri,
402
-				'principaluri' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
403
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
404
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
405
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
406
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp('transparent'),
407
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
408
-				$readOnlyPropertyName => $readOnly,
409
-			];
410
-
411
-			foreach ($this->propertyMap as $xmlName => $dbName) {
412
-				$calendar[$xmlName] = $row[$dbName];
413
-			}
414
-
415
-			$this->addOwnerPrincipal($calendar);
416
-
417
-			$calendars[$calendar['id']] = $calendar;
418
-		}
419
-		$result->closeCursor();
420
-
421
-		return array_values($calendars);
422
-	}
423
-
424
-	/**
425
-	 * @param $principalUri
426
-	 * @return array
427
-	 */
428
-	public function getUsersOwnCalendars($principalUri) {
429
-		$principalUri = $this->convertPrincipal($principalUri, true);
430
-		$fields = array_values($this->propertyMap);
431
-		$fields[] = 'id';
432
-		$fields[] = 'uri';
433
-		$fields[] = 'synctoken';
434
-		$fields[] = 'components';
435
-		$fields[] = 'principaluri';
436
-		$fields[] = 'transparent';
437
-		// Making fields a comma-delimited list
438
-		$query = $this->db->getQueryBuilder();
439
-		$query->select($fields)->from('calendars')
440
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
441
-			->orderBy('calendarorder', 'ASC');
442
-		$stmt = $query->execute();
443
-		$calendars = [];
444
-		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
445
-			$row['principaluri'] = (string) $row['principaluri'];
446
-			$components = [];
447
-			if ($row['components']) {
448
-				$components = explode(',',$row['components']);
449
-			}
450
-			$calendar = [
451
-				'id' => $row['id'],
452
-				'uri' => $row['uri'],
453
-				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
454
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
455
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
456
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
457
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
458
-			];
459
-			foreach ($this->propertyMap as $xmlName => $dbName) {
460
-				$calendar[$xmlName] = $row[$dbName];
461
-			}
462
-
463
-			$this->addOwnerPrincipal($calendar);
464
-
465
-			if (!isset($calendars[$calendar['id']])) {
466
-				$calendars[$calendar['id']] = $calendar;
467
-			}
468
-		}
469
-		$stmt->closeCursor();
470
-		return array_values($calendars);
471
-	}
472
-
473
-
474
-	/**
475
-	 * @param $uid
476
-	 * @return string
477
-	 */
478
-	private function getUserDisplayName($uid) {
479
-		if (!isset($this->userDisplayNames[$uid])) {
480
-			$user = $this->userManager->get($uid);
481
-
482
-			if ($user instanceof IUser) {
483
-				$this->userDisplayNames[$uid] = $user->getDisplayName();
484
-			} else {
485
-				$this->userDisplayNames[$uid] = $uid;
486
-			}
487
-		}
488
-
489
-		return $this->userDisplayNames[$uid];
490
-	}
491
-
492
-	/**
493
-	 * @return array
494
-	 */
495
-	public function getPublicCalendars() {
496
-		$fields = array_values($this->propertyMap);
497
-		$fields[] = 'a.id';
498
-		$fields[] = 'a.uri';
499
-		$fields[] = 'a.synctoken';
500
-		$fields[] = 'a.components';
501
-		$fields[] = 'a.principaluri';
502
-		$fields[] = 'a.transparent';
503
-		$fields[] = 's.access';
504
-		$fields[] = 's.publicuri';
505
-		$calendars = [];
506
-		$query = $this->db->getQueryBuilder();
507
-		$result = $query->select($fields)
508
-			->from('dav_shares', 's')
509
-			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
510
-			->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
511
-			->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
512
-			->execute();
513
-
514
-		while ($row = $result->fetch()) {
515
-			$row['principaluri'] = (string) $row['principaluri'];
516
-			list(, $name) = Uri\split($row['principaluri']);
517
-			$row['displayname'] = $row['displayname'] . "($name)";
518
-			$components = [];
519
-			if ($row['components']) {
520
-				$components = explode(',',$row['components']);
521
-			}
522
-			$calendar = [
523
-				'id' => $row['id'],
524
-				'uri' => $row['publicuri'],
525
-				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
526
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
527
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
528
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
529
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
530
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
531
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
532
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
533
-			];
534
-
535
-			foreach ($this->propertyMap as $xmlName => $dbName) {
536
-				$calendar[$xmlName] = $row[$dbName];
537
-			}
538
-
539
-			$this->addOwnerPrincipal($calendar);
540
-
541
-			if (!isset($calendars[$calendar['id']])) {
542
-				$calendars[$calendar['id']] = $calendar;
543
-			}
544
-		}
545
-		$result->closeCursor();
546
-
547
-		return array_values($calendars);
548
-	}
549
-
550
-	/**
551
-	 * @param string $uri
552
-	 * @return array
553
-	 * @throws NotFound
554
-	 */
555
-	public function getPublicCalendar($uri) {
556
-		$fields = array_values($this->propertyMap);
557
-		$fields[] = 'a.id';
558
-		$fields[] = 'a.uri';
559
-		$fields[] = 'a.synctoken';
560
-		$fields[] = 'a.components';
561
-		$fields[] = 'a.principaluri';
562
-		$fields[] = 'a.transparent';
563
-		$fields[] = 's.access';
564
-		$fields[] = 's.publicuri';
565
-		$query = $this->db->getQueryBuilder();
566
-		$result = $query->select($fields)
567
-			->from('dav_shares', 's')
568
-			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
569
-			->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
570
-			->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
571
-			->andWhere($query->expr()->eq('s.publicuri', $query->createNamedParameter($uri)))
572
-			->execute();
573
-
574
-		$row = $result->fetch(\PDO::FETCH_ASSOC);
575
-
576
-		$result->closeCursor();
577
-
578
-		if ($row === false) {
579
-			throw new NotFound('Node with name \'' . $uri . '\' could not be found');
580
-		}
581
-
582
-		$row['principaluri'] = (string) $row['principaluri'];
583
-		list(, $name) = Uri\split($row['principaluri']);
584
-		$row['displayname'] = $row['displayname'] . ' ' . "($name)";
585
-		$components = [];
586
-		if ($row['components']) {
587
-			$components = explode(',',$row['components']);
588
-		}
589
-		$calendar = [
590
-			'id' => $row['id'],
591
-			'uri' => $row['publicuri'],
592
-			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
593
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
594
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
595
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
596
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
597
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
598
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
599
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
600
-		];
601
-
602
-		foreach ($this->propertyMap as $xmlName => $dbName) {
603
-			$calendar[$xmlName] = $row[$dbName];
604
-		}
605
-
606
-		$this->addOwnerPrincipal($calendar);
607
-
608
-		return $calendar;
609
-	}
610
-
611
-	/**
612
-	 * @param string $principal
613
-	 * @param string $uri
614
-	 * @return array|null
615
-	 */
616
-	public function getCalendarByUri($principal, $uri) {
617
-		$fields = array_values($this->propertyMap);
618
-		$fields[] = 'id';
619
-		$fields[] = 'uri';
620
-		$fields[] = 'synctoken';
621
-		$fields[] = 'components';
622
-		$fields[] = 'principaluri';
623
-		$fields[] = 'transparent';
624
-
625
-		// Making fields a comma-delimited list
626
-		$query = $this->db->getQueryBuilder();
627
-		$query->select($fields)->from('calendars')
628
-			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
629
-			->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
630
-			->setMaxResults(1);
631
-		$stmt = $query->execute();
632
-
633
-		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
634
-		$stmt->closeCursor();
635
-		if ($row === false) {
636
-			return null;
637
-		}
638
-
639
-		$row['principaluri'] = (string) $row['principaluri'];
640
-		$components = [];
641
-		if ($row['components']) {
642
-			$components = explode(',',$row['components']);
643
-		}
644
-
645
-		$calendar = [
646
-			'id' => $row['id'],
647
-			'uri' => $row['uri'],
648
-			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
649
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
650
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
651
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
652
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
653
-		];
654
-
655
-		foreach ($this->propertyMap as $xmlName => $dbName) {
656
-			$calendar[$xmlName] = $row[$dbName];
657
-		}
658
-
659
-		$this->addOwnerPrincipal($calendar);
660
-
661
-		return $calendar;
662
-	}
663
-
664
-	/**
665
-	 * @param $calendarId
666
-	 * @return array|null
667
-	 */
668
-	public function getCalendarById($calendarId) {
669
-		$fields = array_values($this->propertyMap);
670
-		$fields[] = 'id';
671
-		$fields[] = 'uri';
672
-		$fields[] = 'synctoken';
673
-		$fields[] = 'components';
674
-		$fields[] = 'principaluri';
675
-		$fields[] = 'transparent';
676
-
677
-		// Making fields a comma-delimited list
678
-		$query = $this->db->getQueryBuilder();
679
-		$query->select($fields)->from('calendars')
680
-			->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)))
681
-			->setMaxResults(1);
682
-		$stmt = $query->execute();
683
-
684
-		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
685
-		$stmt->closeCursor();
686
-		if ($row === false) {
687
-			return null;
688
-		}
689
-
690
-		$row['principaluri'] = (string) $row['principaluri'];
691
-		$components = [];
692
-		if ($row['components']) {
693
-			$components = explode(',',$row['components']);
694
-		}
695
-
696
-		$calendar = [
697
-			'id' => $row['id'],
698
-			'uri' => $row['uri'],
699
-			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
700
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
701
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
702
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
703
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
704
-		];
705
-
706
-		foreach ($this->propertyMap as $xmlName => $dbName) {
707
-			$calendar[$xmlName] = $row[$dbName];
708
-		}
709
-
710
-		$this->addOwnerPrincipal($calendar);
711
-
712
-		return $calendar;
713
-	}
714
-
715
-	/**
716
-	 * @param $subscriptionId
717
-	 */
718
-	public function getSubscriptionById($subscriptionId) {
719
-		$fields = array_values($this->subscriptionPropertyMap);
720
-		$fields[] = 'id';
721
-		$fields[] = 'uri';
722
-		$fields[] = 'source';
723
-		$fields[] = 'synctoken';
724
-		$fields[] = 'principaluri';
725
-		$fields[] = 'lastmodified';
726
-
727
-		$query = $this->db->getQueryBuilder();
728
-		$query->select($fields)
729
-			->from('calendarsubscriptions')
730
-			->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
731
-			->orderBy('calendarorder', 'asc');
732
-		$stmt = $query->execute();
733
-
734
-		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
735
-		$stmt->closeCursor();
736
-		if ($row === false) {
737
-			return null;
738
-		}
739
-
740
-		$row['principaluri'] = (string) $row['principaluri'];
741
-		$subscription = [
742
-			'id' => $row['id'],
743
-			'uri' => $row['uri'],
744
-			'principaluri' => $row['principaluri'],
745
-			'source' => $row['source'],
746
-			'lastmodified' => $row['lastmodified'],
747
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
748
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
749
-		];
750
-
751
-		foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
752
-			if (!is_null($row[$dbName])) {
753
-				$subscription[$xmlName] = $row[$dbName];
754
-			}
755
-		}
756
-
757
-		return $subscription;
758
-	}
759
-
760
-	/**
761
-	 * Creates a new calendar for a principal.
762
-	 *
763
-	 * If the creation was a success, an id must be returned that can be used to reference
764
-	 * this calendar in other methods, such as updateCalendar.
765
-	 *
766
-	 * @param string $principalUri
767
-	 * @param string $calendarUri
768
-	 * @param array $properties
769
-	 * @return int
770
-	 */
771
-	public function createCalendar($principalUri, $calendarUri, array $properties) {
772
-		$values = [
773
-			'principaluri' => $this->convertPrincipal($principalUri, true),
774
-			'uri' => $calendarUri,
775
-			'synctoken' => 1,
776
-			'transparent' => 0,
777
-			'components' => 'VEVENT,VTODO',
778
-			'displayname' => $calendarUri
779
-		];
780
-
781
-		// Default value
782
-		$sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
783
-		if (isset($properties[$sccs])) {
784
-			if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) {
785
-				throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
786
-			}
787
-			$values['components'] = implode(',',$properties[$sccs]->getValue());
788
-		} elseif (isset($properties['components'])) {
789
-			// Allow to provide components internally without having
790
-			// to create a SupportedCalendarComponentSet object
791
-			$values['components'] = $properties['components'];
792
-		}
793
-
794
-		$transp = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
795
-		if (isset($properties[$transp])) {
796
-			$values['transparent'] = (int) ($properties[$transp]->getValue() === 'transparent');
797
-		}
798
-
799
-		foreach ($this->propertyMap as $xmlName => $dbName) {
800
-			if (isset($properties[$xmlName])) {
801
-				$values[$dbName] = $properties[$xmlName];
802
-			}
803
-		}
804
-
805
-		$query = $this->db->getQueryBuilder();
806
-		$query->insert('calendars');
807
-		foreach ($values as $column => $value) {
808
-			$query->setValue($column, $query->createNamedParameter($value));
809
-		}
810
-		$query->execute();
811
-		$calendarId = $query->getLastInsertId();
812
-
813
-		$calendarData = $this->getCalendarById($calendarId);
814
-		$this->dispatcher->dispatchTyped(new CalendarCreatedEvent((int)$calendarId, $calendarData));
815
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendar', new GenericEvent(
816
-			'\OCA\DAV\CalDAV\CalDavBackend::createCalendar',
817
-			[
818
-				'calendarId' => $calendarId,
819
-				'calendarData' => $calendarData,
820
-			]));
821
-
822
-		return $calendarId;
823
-	}
824
-
825
-	/**
826
-	 * Updates properties for a calendar.
827
-	 *
828
-	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
829
-	 * To do the actual updates, you must tell this object which properties
830
-	 * you're going to process with the handle() method.
831
-	 *
832
-	 * Calling the handle method is like telling the PropPatch object "I
833
-	 * promise I can handle updating this property".
834
-	 *
835
-	 * Read the PropPatch documentation for more info and examples.
836
-	 *
837
-	 * @param mixed $calendarId
838
-	 * @param PropPatch $propPatch
839
-	 * @return void
840
-	 */
841
-	public function updateCalendar($calendarId, PropPatch $propPatch) {
842
-		$supportedProperties = array_keys($this->propertyMap);
843
-		$supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
844
-
845
-		$propPatch->handle($supportedProperties, function ($mutations) use ($calendarId) {
846
-			$newValues = [];
847
-			foreach ($mutations as $propertyName => $propertyValue) {
848
-				switch ($propertyName) {
849
-					case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp':
850
-						$fieldName = 'transparent';
851
-						$newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent');
852
-						break;
853
-					default:
854
-						$fieldName = $this->propertyMap[$propertyName];
855
-						$newValues[$fieldName] = $propertyValue;
856
-						break;
857
-				}
858
-			}
859
-			$query = $this->db->getQueryBuilder();
860
-			$query->update('calendars');
861
-			foreach ($newValues as $fieldName => $value) {
862
-				$query->set($fieldName, $query->createNamedParameter($value));
863
-			}
864
-			$query->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
865
-			$query->execute();
866
-
867
-			$this->addChange($calendarId, "", 2);
868
-
869
-			$calendarData = $this->getCalendarById($calendarId);
870
-			$shares = $this->getShares($calendarId);
871
-			$this->dispatcher->dispatchTyped(new CalendarUpdatedEvent((int)$calendarId, $calendarData, $shares, $mutations));
872
-			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendar', new GenericEvent(
873
-				'\OCA\DAV\CalDAV\CalDavBackend::updateCalendar',
874
-				[
875
-					'calendarId' => $calendarId,
876
-					'calendarData' => $calendarData,
877
-					'shares' => $shares,
878
-					'propertyMutations' => $mutations,
879
-				]));
880
-
881
-			return true;
882
-		});
883
-	}
884
-
885
-	/**
886
-	 * Delete a calendar and all it's objects
887
-	 *
888
-	 * @param mixed $calendarId
889
-	 * @return void
890
-	 */
891
-	public function deleteCalendar($calendarId) {
892
-		$calendarData = $this->getCalendarById($calendarId);
893
-		$shares = $this->getShares($calendarId);
894
-
895
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar', new GenericEvent(
896
-			'\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar',
897
-			[
898
-				'calendarId' => $calendarId,
899
-				'calendarData' => $calendarData,
900
-				'shares' => $shares,
901
-			]));
902
-
903
-		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `calendartype` = ?');
904
-		$stmt->execute([$calendarId, self::CALENDAR_TYPE_CALENDAR]);
905
-
906
-		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendars` WHERE `id` = ?');
907
-		$stmt->execute([$calendarId]);
908
-
909
-		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarchanges` WHERE `calendarid` = ? AND `calendartype` = ?');
910
-		$stmt->execute([$calendarId, self::CALENDAR_TYPE_CALENDAR]);
911
-
912
-		$this->calendarSharingBackend->deleteAllShares($calendarId);
913
-
914
-		$query = $this->db->getQueryBuilder();
915
-		$query->delete($this->dbObjectPropertiesTable)
916
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
917
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)))
918
-			->execute();
919
-
920
-		if ($calendarData) {
921
-			$this->dispatcher->dispatchTyped(new CalendarDeletedEvent((int)$calendarId, $calendarData, $shares));
922
-		}
923
-	}
924
-
925
-	/**
926
-	 * Delete all of an user's shares
927
-	 *
928
-	 * @param string $principaluri
929
-	 * @return void
930
-	 */
931
-	public function deleteAllSharesByUser($principaluri) {
932
-		$this->calendarSharingBackend->deleteAllSharesByUser($principaluri);
933
-	}
934
-
935
-	/**
936
-	 * Returns all calendar objects within a calendar.
937
-	 *
938
-	 * Every item contains an array with the following keys:
939
-	 *   * calendardata - The iCalendar-compatible calendar data
940
-	 *   * uri - a unique key which will be used to construct the uri. This can
941
-	 *     be any arbitrary string, but making sure it ends with '.ics' is a
942
-	 *     good idea. This is only the basename, or filename, not the full
943
-	 *     path.
944
-	 *   * lastmodified - a timestamp of the last modification time
945
-	 *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
946
-	 *   '"abcdef"')
947
-	 *   * size - The size of the calendar objects, in bytes.
948
-	 *   * component - optional, a string containing the type of object, such
949
-	 *     as 'vevent' or 'vtodo'. If specified, this will be used to populate
950
-	 *     the Content-Type header.
951
-	 *
952
-	 * Note that the etag is optional, but it's highly encouraged to return for
953
-	 * speed reasons.
954
-	 *
955
-	 * The calendardata is also optional. If it's not returned
956
-	 * 'getCalendarObject' will be called later, which *is* expected to return
957
-	 * calendardata.
958
-	 *
959
-	 * If neither etag or size are specified, the calendardata will be
960
-	 * used/fetched to determine these numbers. If both are specified the
961
-	 * amount of times this is needed is reduced by a great degree.
962
-	 *
963
-	 * @param mixed $calendarId
964
-	 * @param int $calendarType
965
-	 * @return array
966
-	 */
967
-	public function getCalendarObjects($calendarId, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
968
-		$query = $this->db->getQueryBuilder();
969
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'componenttype', 'classification'])
970
-			->from('calendarobjects')
971
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
972
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
973
-		$stmt = $query->execute();
974
-
975
-		$result = [];
976
-		foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
977
-			$result[] = [
978
-				'id' => $row['id'],
979
-				'uri' => $row['uri'],
980
-				'lastmodified' => $row['lastmodified'],
981
-				'etag' => '"' . $row['etag'] . '"',
982
-				'calendarid' => $row['calendarid'],
983
-				'size' => (int)$row['size'],
984
-				'component' => strtolower($row['componenttype']),
985
-				'classification' => (int)$row['classification']
986
-			];
987
-		}
988
-		$stmt->closeCursor();
989
-
990
-		return $result;
991
-	}
992
-
993
-	/**
994
-	 * Returns information from a single calendar object, based on it's object
995
-	 * uri.
996
-	 *
997
-	 * The object uri is only the basename, or filename and not a full path.
998
-	 *
999
-	 * The returned array must have the same keys as getCalendarObjects. The
1000
-	 * 'calendardata' object is required here though, while it's not required
1001
-	 * for getCalendarObjects.
1002
-	 *
1003
-	 * This method must return null if the object did not exist.
1004
-	 *
1005
-	 * @param mixed $calendarId
1006
-	 * @param string $objectUri
1007
-	 * @param int $calendarType
1008
-	 * @return array|null
1009
-	 */
1010
-	public function getCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1011
-		$query = $this->db->getQueryBuilder();
1012
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
1013
-			->from('calendarobjects')
1014
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1015
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1016
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
1017
-		$stmt = $query->execute();
1018
-		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
1019
-		$stmt->closeCursor();
1020
-
1021
-		if (!$row) {
1022
-			return null;
1023
-		}
1024
-
1025
-		return [
1026
-			'id' => $row['id'],
1027
-			'uri' => $row['uri'],
1028
-			'lastmodified' => $row['lastmodified'],
1029
-			'etag' => '"' . $row['etag'] . '"',
1030
-			'calendarid' => $row['calendarid'],
1031
-			'size' => (int)$row['size'],
1032
-			'calendardata' => $this->readBlob($row['calendardata']),
1033
-			'component' => strtolower($row['componenttype']),
1034
-			'classification' => (int)$row['classification']
1035
-		];
1036
-	}
1037
-
1038
-	/**
1039
-	 * Returns a list of calendar objects.
1040
-	 *
1041
-	 * This method should work identical to getCalendarObject, but instead
1042
-	 * return all the calendar objects in the list as an array.
1043
-	 *
1044
-	 * If the backend supports this, it may allow for some speed-ups.
1045
-	 *
1046
-	 * @param mixed $calendarId
1047
-	 * @param string[] $uris
1048
-	 * @param int $calendarType
1049
-	 * @return array
1050
-	 */
1051
-	public function getMultipleCalendarObjects($calendarId, array $uris, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
1052
-		if (empty($uris)) {
1053
-			return [];
1054
-		}
1055
-
1056
-		$chunks = array_chunk($uris, 100);
1057
-		$objects = [];
1058
-
1059
-		$query = $this->db->getQueryBuilder();
1060
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
1061
-			->from('calendarobjects')
1062
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1063
-			->andWhere($query->expr()->in('uri', $query->createParameter('uri')))
1064
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
1065
-
1066
-		foreach ($chunks as $uris) {
1067
-			$query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
1068
-			$result = $query->execute();
1069
-
1070
-			while ($row = $result->fetch()) {
1071
-				$objects[] = [
1072
-					'id' => $row['id'],
1073
-					'uri' => $row['uri'],
1074
-					'lastmodified' => $row['lastmodified'],
1075
-					'etag' => '"' . $row['etag'] . '"',
1076
-					'calendarid' => $row['calendarid'],
1077
-					'size' => (int)$row['size'],
1078
-					'calendardata' => $this->readBlob($row['calendardata']),
1079
-					'component' => strtolower($row['componenttype']),
1080
-					'classification' => (int)$row['classification']
1081
-				];
1082
-			}
1083
-			$result->closeCursor();
1084
-		}
1085
-
1086
-		return $objects;
1087
-	}
1088
-
1089
-	/**
1090
-	 * Creates a new calendar object.
1091
-	 *
1092
-	 * The object uri is only the basename, or filename and not a full path.
1093
-	 *
1094
-	 * It is possible return an etag from this function, which will be used in
1095
-	 * the response to this PUT request. Note that the ETag must be surrounded
1096
-	 * by double-quotes.
1097
-	 *
1098
-	 * However, you should only really return this ETag if you don't mangle the
1099
-	 * calendar-data. If the result of a subsequent GET to this object is not
1100
-	 * the exact same as this request body, you should omit the ETag.
1101
-	 *
1102
-	 * @param mixed $calendarId
1103
-	 * @param string $objectUri
1104
-	 * @param string $calendarData
1105
-	 * @param int $calendarType
1106
-	 * @return string
1107
-	 */
1108
-	public function createCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1109
-		$extraData = $this->getDenormalizedData($calendarData);
1110
-
1111
-		$q = $this->db->getQueryBuilder();
1112
-		$q->select($q->func()->count('*'))
1113
-			->from('calendarobjects')
1114
-			->where($q->expr()->eq('calendarid', $q->createNamedParameter($calendarId)))
1115
-			->andWhere($q->expr()->eq('uid', $q->createNamedParameter($extraData['uid'])))
1116
-			->andWhere($q->expr()->eq('calendartype', $q->createNamedParameter($calendarType)));
1117
-
1118
-		$result = $q->execute();
1119
-		$count = (int) $result->fetchColumn();
1120
-		$result->closeCursor();
1121
-
1122
-		if ($count !== 0) {
1123
-			throw new \Sabre\DAV\Exception\BadRequest('Calendar object with uid already exists in this calendar collection.');
1124
-		}
1125
-
1126
-		$query = $this->db->getQueryBuilder();
1127
-		$query->insert('calendarobjects')
1128
-			->values([
1129
-				'calendarid' => $query->createNamedParameter($calendarId),
1130
-				'uri' => $query->createNamedParameter($objectUri),
1131
-				'calendardata' => $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB),
1132
-				'lastmodified' => $query->createNamedParameter(time()),
1133
-				'etag' => $query->createNamedParameter($extraData['etag']),
1134
-				'size' => $query->createNamedParameter($extraData['size']),
1135
-				'componenttype' => $query->createNamedParameter($extraData['componentType']),
1136
-				'firstoccurence' => $query->createNamedParameter($extraData['firstOccurence']),
1137
-				'lastoccurence' => $query->createNamedParameter($extraData['lastOccurence']),
1138
-				'classification' => $query->createNamedParameter($extraData['classification']),
1139
-				'uid' => $query->createNamedParameter($extraData['uid']),
1140
-				'calendartype' => $query->createNamedParameter($calendarType),
1141
-			])
1142
-			->execute();
1143
-
1144
-		$this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType);
1145
-		$this->addChange($calendarId, $objectUri, 1, $calendarType);
1146
-
1147
-		$objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1148
-		if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1149
-			$calendarRow = $this->getCalendarById($calendarId);
1150
-			$shares = $this->getShares($calendarId);
1151
-
1152
-			$this->dispatcher->dispatchTyped(new CalendarObjectCreatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
1153
-			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', new GenericEvent(
1154
-				'\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject',
1155
-				[
1156
-					'calendarId' => $calendarId,
1157
-					'calendarData' => $calendarRow,
1158
-					'shares' => $shares,
1159
-					'objectData' => $objectRow,
1160
-				]
1161
-			));
1162
-		} else {
1163
-			$subscriptionRow = $this->getSubscriptionById($calendarId);
1164
-
1165
-			$this->dispatcher->dispatchTyped(new CachedCalendarObjectCreatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
1166
-			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject', new GenericEvent(
1167
-				'\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject',
1168
-				[
1169
-					'subscriptionId' => $calendarId,
1170
-					'calendarData' => $subscriptionRow,
1171
-					'shares' => [],
1172
-					'objectData' => $objectRow,
1173
-				]
1174
-			));
1175
-		}
1176
-
1177
-		return '"' . $extraData['etag'] . '"';
1178
-	}
1179
-
1180
-	/**
1181
-	 * Updates an existing calendarobject, based on it's uri.
1182
-	 *
1183
-	 * The object uri is only the basename, or filename and not a full path.
1184
-	 *
1185
-	 * It is possible return an etag from this function, which will be used in
1186
-	 * the response to this PUT request. Note that the ETag must be surrounded
1187
-	 * by double-quotes.
1188
-	 *
1189
-	 * However, you should only really return this ETag if you don't mangle the
1190
-	 * calendar-data. If the result of a subsequent GET to this object is not
1191
-	 * the exact same as this request body, you should omit the ETag.
1192
-	 *
1193
-	 * @param mixed $calendarId
1194
-	 * @param string $objectUri
1195
-	 * @param string $calendarData
1196
-	 * @param int $calendarType
1197
-	 * @return string
1198
-	 */
1199
-	public function updateCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1200
-		$extraData = $this->getDenormalizedData($calendarData);
1201
-		$query = $this->db->getQueryBuilder();
1202
-		$query->update('calendarobjects')
1203
-				->set('calendardata', $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB))
1204
-				->set('lastmodified', $query->createNamedParameter(time()))
1205
-				->set('etag', $query->createNamedParameter($extraData['etag']))
1206
-				->set('size', $query->createNamedParameter($extraData['size']))
1207
-				->set('componenttype', $query->createNamedParameter($extraData['componentType']))
1208
-				->set('firstoccurence', $query->createNamedParameter($extraData['firstOccurence']))
1209
-				->set('lastoccurence', $query->createNamedParameter($extraData['lastOccurence']))
1210
-				->set('classification', $query->createNamedParameter($extraData['classification']))
1211
-				->set('uid', $query->createNamedParameter($extraData['uid']))
1212
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1213
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1214
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)))
1215
-			->execute();
1216
-
1217
-		$this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType);
1218
-		$this->addChange($calendarId, $objectUri, 2, $calendarType);
1219
-
1220
-		$objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1221
-		if (is_array($objectRow)) {
1222
-			if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1223
-				$calendarRow = $this->getCalendarById($calendarId);
1224
-				$shares = $this->getShares($calendarId);
1225
-
1226
-				$this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
1227
-				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', new GenericEvent(
1228
-					'\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject',
1229
-					[
1230
-						'calendarId' => $calendarId,
1231
-						'calendarData' => $calendarRow,
1232
-						'shares' => $shares,
1233
-						'objectData' => $objectRow,
1234
-					]
1235
-				));
1236
-			} else {
1237
-				$subscriptionRow = $this->getSubscriptionById($calendarId);
1238
-
1239
-				$this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
1240
-				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject', new GenericEvent(
1241
-					'\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject',
1242
-					[
1243
-						'subscriptionId' => $calendarId,
1244
-						'calendarData' => $subscriptionRow,
1245
-						'shares' => [],
1246
-						'objectData' => $objectRow,
1247
-					]
1248
-				));
1249
-			}
1250
-		}
1251
-
1252
-		return '"' . $extraData['etag'] . '"';
1253
-	}
1254
-
1255
-	/**
1256
-	 * @param int $calendarObjectId
1257
-	 * @param int $classification
1258
-	 */
1259
-	public function setClassification($calendarObjectId, $classification) {
1260
-		if (!in_array($classification, [
1261
-			self::CLASSIFICATION_PUBLIC, self::CLASSIFICATION_PRIVATE, self::CLASSIFICATION_CONFIDENTIAL
1262
-		])) {
1263
-			throw new \InvalidArgumentException();
1264
-		}
1265
-		$query = $this->db->getQueryBuilder();
1266
-		$query->update('calendarobjects')
1267
-			->set('classification', $query->createNamedParameter($classification))
1268
-			->where($query->expr()->eq('id', $query->createNamedParameter($calendarObjectId)))
1269
-			->execute();
1270
-	}
1271
-
1272
-	/**
1273
-	 * Deletes an existing calendar object.
1274
-	 *
1275
-	 * The object uri is only the basename, or filename and not a full path.
1276
-	 *
1277
-	 * @param mixed $calendarId
1278
-	 * @param string $objectUri
1279
-	 * @param int $calendarType
1280
-	 * @return void
1281
-	 */
1282
-	public function deleteCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1283
-		$data = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1284
-		if (is_array($data)) {
1285
-			if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1286
-				$calendarRow = $this->getCalendarById($calendarId);
1287
-				$shares = $this->getShares($calendarId);
1288
-
1289
-				$this->dispatcher->dispatchTyped(new CalendarObjectDeletedEvent((int)$calendarId, $calendarRow, $shares, $data));
1290
-				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', new GenericEvent(
1291
-					'\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject',
1292
-					[
1293
-						'calendarId' => $calendarId,
1294
-						'calendarData' => $calendarRow,
1295
-						'shares' => $shares,
1296
-						'objectData' => $data,
1297
-					]
1298
-				));
1299
-			} else {
1300
-				$subscriptionRow = $this->getSubscriptionById($calendarId);
1301
-
1302
-				$this->dispatcher->dispatchTyped(new CachedCalendarObjectDeletedEvent((int)$calendarId, $subscriptionRow, [], $data));
1303
-				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject', new GenericEvent(
1304
-					'\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject',
1305
-					[
1306
-						'subscriptionId' => $calendarId,
1307
-						'calendarData' => $subscriptionRow,
1308
-						'shares' => [],
1309
-						'objectData' => $data,
1310
-					]
1311
-				));
1312
-			}
1313
-		}
1314
-
1315
-		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ? AND `calendartype` = ?');
1316
-		$stmt->execute([$calendarId, $objectUri, $calendarType]);
1317
-
1318
-		if (is_array($data)) {
1319
-			$this->purgeProperties($calendarId, $data['id'], $calendarType);
1320
-		}
1321
-
1322
-		$this->addChange($calendarId, $objectUri, 3, $calendarType);
1323
-	}
1324
-
1325
-	/**
1326
-	 * Performs a calendar-query on the contents of this calendar.
1327
-	 *
1328
-	 * The calendar-query is defined in RFC4791 : CalDAV. Using the
1329
-	 * calendar-query it is possible for a client to request a specific set of
1330
-	 * object, based on contents of iCalendar properties, date-ranges and
1331
-	 * iCalendar component types (VTODO, VEVENT).
1332
-	 *
1333
-	 * This method should just return a list of (relative) urls that match this
1334
-	 * query.
1335
-	 *
1336
-	 * The list of filters are specified as an array. The exact array is
1337
-	 * documented by Sabre\CalDAV\CalendarQueryParser.
1338
-	 *
1339
-	 * Note that it is extremely likely that getCalendarObject for every path
1340
-	 * returned from this method will be called almost immediately after. You
1341
-	 * may want to anticipate this to speed up these requests.
1342
-	 *
1343
-	 * This method provides a default implementation, which parses *all* the
1344
-	 * iCalendar objects in the specified calendar.
1345
-	 *
1346
-	 * This default may well be good enough for personal use, and calendars
1347
-	 * that aren't very large. But if you anticipate high usage, big calendars
1348
-	 * or high loads, you are strongly advised to optimize certain paths.
1349
-	 *
1350
-	 * The best way to do so is override this method and to optimize
1351
-	 * specifically for 'common filters'.
1352
-	 *
1353
-	 * Requests that are extremely common are:
1354
-	 *   * requests for just VEVENTS
1355
-	 *   * requests for just VTODO
1356
-	 *   * requests with a time-range-filter on either VEVENT or VTODO.
1357
-	 *
1358
-	 * ..and combinations of these requests. It may not be worth it to try to
1359
-	 * handle every possible situation and just rely on the (relatively
1360
-	 * easy to use) CalendarQueryValidator to handle the rest.
1361
-	 *
1362
-	 * Note that especially time-range-filters may be difficult to parse. A
1363
-	 * time-range filter specified on a VEVENT must for instance also handle
1364
-	 * recurrence rules correctly.
1365
-	 * A good example of how to interprete all these filters can also simply
1366
-	 * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
1367
-	 * as possible, so it gives you a good idea on what type of stuff you need
1368
-	 * to think of.
1369
-	 *
1370
-	 * @param mixed $calendarId
1371
-	 * @param array $filters
1372
-	 * @param int $calendarType
1373
-	 * @return array
1374
-	 */
1375
-	public function calendarQuery($calendarId, array $filters, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
1376
-		$componentType = null;
1377
-		$requirePostFilter = true;
1378
-		$timeRange = null;
1379
-
1380
-		// if no filters were specified, we don't need to filter after a query
1381
-		if (!$filters['prop-filters'] && !$filters['comp-filters']) {
1382
-			$requirePostFilter = false;
1383
-		}
1384
-
1385
-		// Figuring out if there's a component filter
1386
-		if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
1387
-			$componentType = $filters['comp-filters'][0]['name'];
1388
-
1389
-			// Checking if we need post-filters
1390
-			if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
1391
-				$requirePostFilter = false;
1392
-			}
1393
-			// There was a time-range filter
1394
-			if ($componentType === 'VEVENT' && isset($filters['comp-filters'][0]['time-range']) && is_array($filters['comp-filters'][0]['time-range'])) {
1395
-				$timeRange = $filters['comp-filters'][0]['time-range'];
1396
-
1397
-				// If start time OR the end time is not specified, we can do a
1398
-				// 100% accurate mysql query.
1399
-				if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
1400
-					$requirePostFilter = false;
1401
-				}
1402
-			}
1403
-		}
1404
-		$columns = ['uri'];
1405
-		if ($requirePostFilter) {
1406
-			$columns = ['uri', 'calendardata'];
1407
-		}
1408
-		$query = $this->db->getQueryBuilder();
1409
-		$query->select($columns)
1410
-			->from('calendarobjects')
1411
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1412
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
1413
-
1414
-		if ($componentType) {
1415
-			$query->andWhere($query->expr()->eq('componenttype', $query->createNamedParameter($componentType)));
1416
-		}
1417
-
1418
-		if ($timeRange && $timeRange['start']) {
1419
-			$query->andWhere($query->expr()->gt('lastoccurence', $query->createNamedParameter($timeRange['start']->getTimeStamp())));
1420
-		}
1421
-		if ($timeRange && $timeRange['end']) {
1422
-			$query->andWhere($query->expr()->lt('firstoccurence', $query->createNamedParameter($timeRange['end']->getTimeStamp())));
1423
-		}
1424
-
1425
-		$stmt = $query->execute();
1426
-
1427
-		$result = [];
1428
-		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1429
-			if ($requirePostFilter) {
1430
-				// validateFilterForObject will parse the calendar data
1431
-				// catch parsing errors
1432
-				try {
1433
-					$matches = $this->validateFilterForObject($row, $filters);
1434
-				} catch (ParseException $ex) {
1435
-					$this->logger->logException($ex, [
1436
-						'app' => 'dav',
1437
-						'message' => 'Caught parsing exception for calendar data. This usually indicates invalid calendar data. calendar-id:'.$calendarId.' uri:'.$row['uri']
1438
-					]);
1439
-					continue;
1440
-				} catch (InvalidDataException $ex) {
1441
-					$this->logger->logException($ex, [
1442
-						'app' => 'dav',
1443
-						'message' => 'Caught invalid data exception for calendar data. This usually indicates invalid calendar data. calendar-id:'.$calendarId.' uri:'.$row['uri']
1444
-					]);
1445
-					continue;
1446
-				}
1447
-
1448
-				if (!$matches) {
1449
-					continue;
1450
-				}
1451
-			}
1452
-			$result[] = $row['uri'];
1453
-		}
1454
-
1455
-		return $result;
1456
-	}
1457
-
1458
-	/**
1459
-	 * custom Nextcloud search extension for CalDAV
1460
-	 *
1461
-	 * TODO - this should optionally cover cached calendar objects as well
1462
-	 *
1463
-	 * @param string $principalUri
1464
-	 * @param array $filters
1465
-	 * @param integer|null $limit
1466
-	 * @param integer|null $offset
1467
-	 * @return array
1468
-	 */
1469
-	public function calendarSearch($principalUri, array $filters, $limit = null, $offset = null) {
1470
-		$calendars = $this->getCalendarsForUser($principalUri);
1471
-		$ownCalendars = [];
1472
-		$sharedCalendars = [];
1473
-
1474
-		$uriMapper = [];
1475
-
1476
-		foreach ($calendars as $calendar) {
1477
-			if ($calendar['{http://owncloud.org/ns}owner-principal'] === $principalUri) {
1478
-				$ownCalendars[] = $calendar['id'];
1479
-			} else {
1480
-				$sharedCalendars[] = $calendar['id'];
1481
-			}
1482
-			$uriMapper[$calendar['id']] = $calendar['uri'];
1483
-		}
1484
-		if (count($ownCalendars) === 0 && count($sharedCalendars) === 0) {
1485
-			return [];
1486
-		}
1487
-
1488
-		$query = $this->db->getQueryBuilder();
1489
-		// Calendar id expressions
1490
-		$calendarExpressions = [];
1491
-		foreach ($ownCalendars as $id) {
1492
-			$calendarExpressions[] = $query->expr()->andX(
1493
-				$query->expr()->eq('c.calendarid',
1494
-					$query->createNamedParameter($id)),
1495
-				$query->expr()->eq('c.calendartype',
1496
-						$query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1497
-		}
1498
-		foreach ($sharedCalendars as $id) {
1499
-			$calendarExpressions[] = $query->expr()->andX(
1500
-				$query->expr()->eq('c.calendarid',
1501
-					$query->createNamedParameter($id)),
1502
-				$query->expr()->eq('c.classification',
1503
-					$query->createNamedParameter(self::CLASSIFICATION_PUBLIC)),
1504
-				$query->expr()->eq('c.calendartype',
1505
-					$query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1506
-		}
1507
-
1508
-		if (count($calendarExpressions) === 1) {
1509
-			$calExpr = $calendarExpressions[0];
1510
-		} else {
1511
-			$calExpr = call_user_func_array([$query->expr(), 'orX'], $calendarExpressions);
1512
-		}
1513
-
1514
-		// Component expressions
1515
-		$compExpressions = [];
1516
-		foreach ($filters['comps'] as $comp) {
1517
-			$compExpressions[] = $query->expr()
1518
-				->eq('c.componenttype', $query->createNamedParameter($comp));
1519
-		}
1520
-
1521
-		if (count($compExpressions) === 1) {
1522
-			$compExpr = $compExpressions[0];
1523
-		} else {
1524
-			$compExpr = call_user_func_array([$query->expr(), 'orX'], $compExpressions);
1525
-		}
1526
-
1527
-		if (!isset($filters['props'])) {
1528
-			$filters['props'] = [];
1529
-		}
1530
-		if (!isset($filters['params'])) {
1531
-			$filters['params'] = [];
1532
-		}
1533
-
1534
-		$propParamExpressions = [];
1535
-		foreach ($filters['props'] as $prop) {
1536
-			$propParamExpressions[] = $query->expr()->andX(
1537
-				$query->expr()->eq('i.name', $query->createNamedParameter($prop)),
1538
-				$query->expr()->isNull('i.parameter')
1539
-			);
1540
-		}
1541
-		foreach ($filters['params'] as $param) {
1542
-			$propParamExpressions[] = $query->expr()->andX(
1543
-				$query->expr()->eq('i.name', $query->createNamedParameter($param['property'])),
1544
-				$query->expr()->eq('i.parameter', $query->createNamedParameter($param['parameter']))
1545
-			);
1546
-		}
1547
-
1548
-		if (count($propParamExpressions) === 1) {
1549
-			$propParamExpr = $propParamExpressions[0];
1550
-		} else {
1551
-			$propParamExpr = call_user_func_array([$query->expr(), 'orX'], $propParamExpressions);
1552
-		}
1553
-
1554
-		$query->select(['c.calendarid', 'c.uri'])
1555
-			->from($this->dbObjectPropertiesTable, 'i')
1556
-			->join('i', 'calendarobjects', 'c', $query->expr()->eq('i.objectid', 'c.id'))
1557
-			->where($calExpr)
1558
-			->andWhere($compExpr)
1559
-			->andWhere($propParamExpr)
1560
-			->andWhere($query->expr()->iLike('i.value',
1561
-				$query->createNamedParameter('%'.$this->db->escapeLikeParameter($filters['search-term']).'%')));
1562
-
1563
-		if ($offset) {
1564
-			$query->setFirstResult($offset);
1565
-		}
1566
-		if ($limit) {
1567
-			$query->setMaxResults($limit);
1568
-		}
1569
-
1570
-		$stmt = $query->execute();
1571
-
1572
-		$result = [];
1573
-		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1574
-			$path = $uriMapper[$row['calendarid']] . '/' . $row['uri'];
1575
-			if (!in_array($path, $result)) {
1576
-				$result[] = $path;
1577
-			}
1578
-		}
1579
-
1580
-		return $result;
1581
-	}
1582
-
1583
-	/**
1584
-	 * used for Nextcloud's calendar API
1585
-	 *
1586
-	 * @param array $calendarInfo
1587
-	 * @param string $pattern
1588
-	 * @param array $searchProperties
1589
-	 * @param array $options
1590
-	 * @param integer|null $limit
1591
-	 * @param integer|null $offset
1592
-	 *
1593
-	 * @return array
1594
-	 */
1595
-	public function search(array $calendarInfo, $pattern, array $searchProperties,
1596
-						   array $options, $limit, $offset) {
1597
-		$outerQuery = $this->db->getQueryBuilder();
1598
-		$innerQuery = $this->db->getQueryBuilder();
1599
-
1600
-		$innerQuery->selectDistinct('op.objectid')
1601
-			->from($this->dbObjectPropertiesTable, 'op')
1602
-			->andWhere($innerQuery->expr()->eq('op.calendarid',
1603
-				$outerQuery->createNamedParameter($calendarInfo['id'])))
1604
-			->andWhere($innerQuery->expr()->eq('op.calendartype',
1605
-				$outerQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1606
-
1607
-		// only return public items for shared calendars for now
1608
-		if ($calendarInfo['principaluri'] !== $calendarInfo['{http://owncloud.org/ns}owner-principal']) {
1609
-			$innerQuery->andWhere($innerQuery->expr()->eq('c.classification',
1610
-				$outerQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1611
-		}
1612
-
1613
-		$or = $innerQuery->expr()->orX();
1614
-		foreach ($searchProperties as $searchProperty) {
1615
-			$or->add($innerQuery->expr()->eq('op.name',
1616
-				$outerQuery->createNamedParameter($searchProperty)));
1617
-		}
1618
-		$innerQuery->andWhere($or);
1619
-
1620
-		if ($pattern !== '') {
1621
-			$innerQuery->andWhere($innerQuery->expr()->iLike('op.value',
1622
-				$outerQuery->createNamedParameter('%' .
1623
-					$this->db->escapeLikeParameter($pattern) . '%')));
1624
-		}
1625
-
1626
-		$outerQuery->select('c.id', 'c.calendardata', 'c.componenttype', 'c.uid', 'c.uri')
1627
-			->from('calendarobjects', 'c');
1628
-
1629
-		if (isset($options['timerange'])) {
1630
-			if (isset($options['timerange']['start']) && $options['timerange']['start'] instanceof DateTime) {
1631
-				$outerQuery->andWhere($outerQuery->expr()->gt('lastoccurence',
1632
-					$outerQuery->createNamedParameter($options['timerange']['start']->getTimeStamp())));
1633
-			}
1634
-			if (isset($options['timerange']['end']) && $options['timerange']['end'] instanceof DateTime) {
1635
-				$outerQuery->andWhere($outerQuery->expr()->lt('firstoccurence',
1636
-					$outerQuery->createNamedParameter($options['timerange']['end']->getTimeStamp())));
1637
-			}
1638
-		}
1639
-
1640
-		if (isset($options['types'])) {
1641
-			$or = $outerQuery->expr()->orX();
1642
-			foreach ($options['types'] as $type) {
1643
-				$or->add($outerQuery->expr()->eq('componenttype',
1644
-					$outerQuery->createNamedParameter($type)));
1645
-			}
1646
-			$outerQuery->andWhere($or);
1647
-		}
1648
-
1649
-		$outerQuery->andWhere($outerQuery->expr()->in('c.id',
1650
-			$outerQuery->createFunction($innerQuery->getSQL())));
1651
-
1652
-		if ($offset) {
1653
-			$outerQuery->setFirstResult($offset);
1654
-		}
1655
-		if ($limit) {
1656
-			$outerQuery->setMaxResults($limit);
1657
-		}
1658
-
1659
-		$result = $outerQuery->execute();
1660
-		$calendarObjects = $result->fetchAll();
1661
-
1662
-		return array_map(function ($o) {
1663
-			$calendarData = Reader::read($o['calendardata']);
1664
-			$comps = $calendarData->getComponents();
1665
-			$objects = [];
1666
-			$timezones = [];
1667
-			foreach ($comps as $comp) {
1668
-				if ($comp instanceof VTimeZone) {
1669
-					$timezones[] = $comp;
1670
-				} else {
1671
-					$objects[] = $comp;
1672
-				}
1673
-			}
1674
-
1675
-			return [
1676
-				'id' => $o['id'],
1677
-				'type' => $o['componenttype'],
1678
-				'uid' => $o['uid'],
1679
-				'uri' => $o['uri'],
1680
-				'objects' => array_map(function ($c) {
1681
-					return $this->transformSearchData($c);
1682
-				}, $objects),
1683
-				'timezones' => array_map(function ($c) {
1684
-					return $this->transformSearchData($c);
1685
-				}, $timezones),
1686
-			];
1687
-		}, $calendarObjects);
1688
-	}
1689
-
1690
-	/**
1691
-	 * @param Component $comp
1692
-	 * @return array
1693
-	 */
1694
-	private function transformSearchData(Component $comp) {
1695
-		$data = [];
1696
-		/** @var Component[] $subComponents */
1697
-		$subComponents = $comp->getComponents();
1698
-		/** @var Property[] $properties */
1699
-		$properties = array_filter($comp->children(), function ($c) {
1700
-			return $c instanceof Property;
1701
-		});
1702
-		$validationRules = $comp->getValidationRules();
1703
-
1704
-		foreach ($subComponents as $subComponent) {
1705
-			$name = $subComponent->name;
1706
-			if (!isset($data[$name])) {
1707
-				$data[$name] = [];
1708
-			}
1709
-			$data[$name][] = $this->transformSearchData($subComponent);
1710
-		}
1711
-
1712
-		foreach ($properties as $property) {
1713
-			$name = $property->name;
1714
-			if (!isset($validationRules[$name])) {
1715
-				$validationRules[$name] = '*';
1716
-			}
1717
-
1718
-			$rule = $validationRules[$property->name];
1719
-			if ($rule === '+' || $rule === '*') { // multiple
1720
-				if (!isset($data[$name])) {
1721
-					$data[$name] = [];
1722
-				}
1723
-
1724
-				$data[$name][] = $this->transformSearchProperty($property);
1725
-			} else { // once
1726
-				$data[$name] = $this->transformSearchProperty($property);
1727
-			}
1728
-		}
1729
-
1730
-		return $data;
1731
-	}
1732
-
1733
-	/**
1734
-	 * @param Property $prop
1735
-	 * @return array
1736
-	 */
1737
-	private function transformSearchProperty(Property $prop) {
1738
-		// No need to check Date, as it extends DateTime
1739
-		if ($prop instanceof Property\ICalendar\DateTime) {
1740
-			$value = $prop->getDateTime();
1741
-		} else {
1742
-			$value = $prop->getValue();
1743
-		}
1744
-
1745
-		return [
1746
-			$value,
1747
-			$prop->parameters()
1748
-		];
1749
-	}
1750
-
1751
-	/**
1752
-	 * @param string $principalUri
1753
-	 * @param string $pattern
1754
-	 * @param array $componentTypes
1755
-	 * @param array $searchProperties
1756
-	 * @param array $searchParameters
1757
-	 * @param array $options
1758
-	 * @return array
1759
-	 */
1760
-	public function searchPrincipalUri(string $principalUri,
1761
-									   string $pattern,
1762
-									   array $componentTypes,
1763
-									   array $searchProperties,
1764
-									   array $searchParameters,
1765
-									   array $options = []): array {
1766
-		$escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false;
1767
-
1768
-		$calendarObjectIdQuery = $this->db->getQueryBuilder();
1769
-		$calendarOr = $calendarObjectIdQuery->expr()->orX();
1770
-		$searchOr = $calendarObjectIdQuery->expr()->orX();
1771
-
1772
-		// Fetch calendars and subscription
1773
-		$calendars = $this->getCalendarsForUser($principalUri);
1774
-		$subscriptions = $this->getSubscriptionsForUser($principalUri);
1775
-		foreach ($calendars as $calendar) {
1776
-			$calendarAnd = $calendarObjectIdQuery->expr()->andX();
1777
-			$calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$calendar['id'])));
1778
-			$calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1779
-
1780
-			// If it's shared, limit search to public events
1781
-			if ($calendar['principaluri'] !== $calendar['{http://owncloud.org/ns}owner-principal']) {
1782
-				$calendarAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1783
-			}
1784
-
1785
-			$calendarOr->add($calendarAnd);
1786
-		}
1787
-		foreach ($subscriptions as $subscription) {
1788
-			$subscriptionAnd = $calendarObjectIdQuery->expr()->andX();
1789
-			$subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$subscription['id'])));
1790
-			$subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
1791
-
1792
-			// If it's shared, limit search to public events
1793
-			if ($subscription['principaluri'] !== $subscription['{http://owncloud.org/ns}owner-principal']) {
1794
-				$subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1795
-			}
1796
-
1797
-			$calendarOr->add($subscriptionAnd);
1798
-		}
1799
-
1800
-		foreach ($searchProperties as $property) {
1801
-			$propertyAnd = $calendarObjectIdQuery->expr()->andX();
1802
-			$propertyAnd->add($calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)));
1803
-			$propertyAnd->add($calendarObjectIdQuery->expr()->isNull('cob.parameter'));
1804
-
1805
-			$searchOr->add($propertyAnd);
1806
-		}
1807
-		foreach ($searchParameters as $property => $parameter) {
1808
-			$parameterAnd = $calendarObjectIdQuery->expr()->andX();
1809
-			$parameterAnd->add($calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)));
1810
-			$parameterAnd->add($calendarObjectIdQuery->expr()->eq('cob.parameter', $calendarObjectIdQuery->createNamedParameter($parameter, IQueryBuilder::PARAM_STR_ARRAY)));
1811
-
1812
-			$searchOr->add($parameterAnd);
1813
-		}
1814
-
1815
-		if ($calendarOr->count() === 0) {
1816
-			return [];
1817
-		}
1818
-		if ($searchOr->count() === 0) {
1819
-			return [];
1820
-		}
1821
-
1822
-		$calendarObjectIdQuery->selectDistinct('cob.objectid')
1823
-			->from($this->dbObjectPropertiesTable, 'cob')
1824
-			->leftJoin('cob', 'calendarobjects', 'co', $calendarObjectIdQuery->expr()->eq('co.id', 'cob.objectid'))
1825
-			->andWhere($calendarObjectIdQuery->expr()->in('co.componenttype', $calendarObjectIdQuery->createNamedParameter($componentTypes, IQueryBuilder::PARAM_STR_ARRAY)))
1826
-			->andWhere($calendarOr)
1827
-			->andWhere($searchOr);
1828
-
1829
-		if ('' !== $pattern) {
1830
-			if (!$escapePattern) {
1831
-				$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter($pattern)));
1832
-			} else {
1833
-				$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
1834
-			}
1835
-		}
1836
-
1837
-		if (isset($options['limit'])) {
1838
-			$calendarObjectIdQuery->setMaxResults($options['limit']);
1839
-		}
1840
-		if (isset($options['offset'])) {
1841
-			$calendarObjectIdQuery->setFirstResult($options['offset']);
1842
-		}
1843
-
1844
-		$result = $calendarObjectIdQuery->execute();
1845
-		$matches = $result->fetchAll();
1846
-		$result->closeCursor();
1847
-		$matches = array_map(static function (array $match):int {
1848
-			return (int) $match['objectid'];
1849
-		}, $matches);
1850
-
1851
-		$query = $this->db->getQueryBuilder();
1852
-		$query->select('calendardata', 'uri', 'calendarid', 'calendartype')
1853
-			->from('calendarobjects')
1854
-			->where($query->expr()->in('id', $query->createNamedParameter($matches, IQueryBuilder::PARAM_INT_ARRAY)));
1855
-
1856
-		$result = $query->execute();
1857
-		$calendarObjects = $result->fetchAll();
1858
-		$result->closeCursor();
1859
-
1860
-		return array_map(function (array $array): array {
1861
-			$array['calendarid'] = (int)$array['calendarid'];
1862
-			$array['calendartype'] = (int)$array['calendartype'];
1863
-			$array['calendardata'] = $this->readBlob($array['calendardata']);
1864
-
1865
-			return $array;
1866
-		}, $calendarObjects);
1867
-	}
1868
-
1869
-	/**
1870
-	 * Searches through all of a users calendars and calendar objects to find
1871
-	 * an object with a specific UID.
1872
-	 *
1873
-	 * This method should return the path to this object, relative to the
1874
-	 * calendar home, so this path usually only contains two parts:
1875
-	 *
1876
-	 * calendarpath/objectpath.ics
1877
-	 *
1878
-	 * If the uid is not found, return null.
1879
-	 *
1880
-	 * This method should only consider * objects that the principal owns, so
1881
-	 * any calendars owned by other principals that also appear in this
1882
-	 * collection should be ignored.
1883
-	 *
1884
-	 * @param string $principalUri
1885
-	 * @param string $uid
1886
-	 * @return string|null
1887
-	 */
1888
-	public function getCalendarObjectByUID($principalUri, $uid) {
1889
-		$query = $this->db->getQueryBuilder();
1890
-		$query->selectAlias('c.uri', 'calendaruri')->selectAlias('co.uri', 'objecturi')
1891
-			->from('calendarobjects', 'co')
1892
-			->leftJoin('co', 'calendars', 'c', $query->expr()->eq('co.calendarid', 'c.id'))
1893
-			->where($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri)))
1894
-			->andWhere($query->expr()->eq('co.uid', $query->createNamedParameter($uid)));
1895
-
1896
-		$stmt = $query->execute();
1897
-
1898
-		if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1899
-			return $row['calendaruri'] . '/' . $row['objecturi'];
1900
-		}
1901
-
1902
-		return null;
1903
-	}
1904
-
1905
-	/**
1906
-	 * The getChanges method returns all the changes that have happened, since
1907
-	 * the specified syncToken in the specified calendar.
1908
-	 *
1909
-	 * This function should return an array, such as the following:
1910
-	 *
1911
-	 * [
1912
-	 *   'syncToken' => 'The current synctoken',
1913
-	 *   'added'   => [
1914
-	 *      'new.txt',
1915
-	 *   ],
1916
-	 *   'modified'   => [
1917
-	 *      'modified.txt',
1918
-	 *   ],
1919
-	 *   'deleted' => [
1920
-	 *      'foo.php.bak',
1921
-	 *      'old.txt'
1922
-	 *   ]
1923
-	 * );
1924
-	 *
1925
-	 * The returned syncToken property should reflect the *current* syncToken
1926
-	 * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
1927
-	 * property This is * needed here too, to ensure the operation is atomic.
1928
-	 *
1929
-	 * If the $syncToken argument is specified as null, this is an initial
1930
-	 * sync, and all members should be reported.
1931
-	 *
1932
-	 * The modified property is an array of nodenames that have changed since
1933
-	 * the last token.
1934
-	 *
1935
-	 * The deleted property is an array with nodenames, that have been deleted
1936
-	 * from collection.
1937
-	 *
1938
-	 * The $syncLevel argument is basically the 'depth' of the report. If it's
1939
-	 * 1, you only have to report changes that happened only directly in
1940
-	 * immediate descendants. If it's 2, it should also include changes from
1941
-	 * the nodes below the child collections. (grandchildren)
1942
-	 *
1943
-	 * The $limit argument allows a client to specify how many results should
1944
-	 * be returned at most. If the limit is not specified, it should be treated
1945
-	 * as infinite.
1946
-	 *
1947
-	 * If the limit (infinite or not) is higher than you're willing to return,
1948
-	 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
1949
-	 *
1950
-	 * If the syncToken is expired (due to data cleanup) or unknown, you must
1951
-	 * return null.
1952
-	 *
1953
-	 * The limit is 'suggestive'. You are free to ignore it.
1954
-	 *
1955
-	 * @param string $calendarId
1956
-	 * @param string $syncToken
1957
-	 * @param int $syncLevel
1958
-	 * @param int $limit
1959
-	 * @param int $calendarType
1960
-	 * @return array
1961
-	 */
1962
-	public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1963
-		// Current synctoken
1964
-		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*calendars` WHERE `id` = ?');
1965
-		$stmt->execute([ $calendarId ]);
1966
-		$currentToken = $stmt->fetchColumn(0);
1967
-
1968
-		if (is_null($currentToken)) {
1969
-			return null;
1970
-		}
1971
-
1972
-		$result = [
1973
-			'syncToken' => $currentToken,
1974
-			'added' => [],
1975
-			'modified' => [],
1976
-			'deleted' => [],
1977
-		];
1978
-
1979
-		if ($syncToken) {
1980
-			$query = "SELECT `uri`, `operation` FROM `*PREFIX*calendarchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `calendarid` = ? AND `calendartype` = ? ORDER BY `synctoken`";
1981
-			if ($limit > 0) {
1982
-				$query .= " LIMIT " . (int)$limit;
1983
-			}
1984
-
1985
-			// Fetching all changes
1986
-			$stmt = $this->db->prepare($query);
1987
-			$stmt->execute([$syncToken, $currentToken, $calendarId, $calendarType]);
1988
-
1989
-			$changes = [];
1990
-
1991
-			// This loop ensures that any duplicates are overwritten, only the
1992
-			// last change on a node is relevant.
1993
-			while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1994
-				$changes[$row['uri']] = $row['operation'];
1995
-			}
1996
-
1997
-			foreach ($changes as $uri => $operation) {
1998
-				switch ($operation) {
1999
-					case 1:
2000
-						$result['added'][] = $uri;
2001
-						break;
2002
-					case 2:
2003
-						$result['modified'][] = $uri;
2004
-						break;
2005
-					case 3:
2006
-						$result['deleted'][] = $uri;
2007
-						break;
2008
-				}
2009
-			}
2010
-		} else {
2011
-			// No synctoken supplied, this is the initial sync.
2012
-			$query = "SELECT `uri` FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `calendartype` = ?";
2013
-			$stmt = $this->db->prepare($query);
2014
-			$stmt->execute([$calendarId, $calendarType]);
2015
-
2016
-			$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
2017
-		}
2018
-		return $result;
2019
-	}
2020
-
2021
-	/**
2022
-	 * Returns a list of subscriptions for a principal.
2023
-	 *
2024
-	 * Every subscription is an array with the following keys:
2025
-	 *  * id, a unique id that will be used by other functions to modify the
2026
-	 *    subscription. This can be the same as the uri or a database key.
2027
-	 *  * uri. This is just the 'base uri' or 'filename' of the subscription.
2028
-	 *  * principaluri. The owner of the subscription. Almost always the same as
2029
-	 *    principalUri passed to this method.
2030
-	 *
2031
-	 * Furthermore, all the subscription info must be returned too:
2032
-	 *
2033
-	 * 1. {DAV:}displayname
2034
-	 * 2. {http://apple.com/ns/ical/}refreshrate
2035
-	 * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
2036
-	 *    should not be stripped).
2037
-	 * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
2038
-	 *    should not be stripped).
2039
-	 * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
2040
-	 *    attachments should not be stripped).
2041
-	 * 6. {http://calendarserver.org/ns/}source (Must be a
2042
-	 *     Sabre\DAV\Property\Href).
2043
-	 * 7. {http://apple.com/ns/ical/}calendar-color
2044
-	 * 8. {http://apple.com/ns/ical/}calendar-order
2045
-	 * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
2046
-	 *    (should just be an instance of
2047
-	 *    Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
2048
-	 *    default components).
2049
-	 *
2050
-	 * @param string $principalUri
2051
-	 * @return array
2052
-	 */
2053
-	public function getSubscriptionsForUser($principalUri) {
2054
-		$fields = array_values($this->subscriptionPropertyMap);
2055
-		$fields[] = 'id';
2056
-		$fields[] = 'uri';
2057
-		$fields[] = 'source';
2058
-		$fields[] = 'principaluri';
2059
-		$fields[] = 'lastmodified';
2060
-		$fields[] = 'synctoken';
2061
-
2062
-		$query = $this->db->getQueryBuilder();
2063
-		$query->select($fields)
2064
-			->from('calendarsubscriptions')
2065
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2066
-			->orderBy('calendarorder', 'asc');
2067
-		$stmt = $query->execute();
2068
-
2069
-		$subscriptions = [];
2070
-		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
2071
-			$subscription = [
2072
-				'id' => $row['id'],
2073
-				'uri' => $row['uri'],
2074
-				'principaluri' => $row['principaluri'],
2075
-				'source' => $row['source'],
2076
-				'lastmodified' => $row['lastmodified'],
2077
-
2078
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
2079
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
2080
-			];
2081
-
2082
-			foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
2083
-				if (!is_null($row[$dbName])) {
2084
-					$subscription[$xmlName] = $row[$dbName];
2085
-				}
2086
-			}
2087
-
2088
-			$subscriptions[] = $subscription;
2089
-		}
2090
-
2091
-		return $subscriptions;
2092
-	}
2093
-
2094
-	/**
2095
-	 * Creates a new subscription for a principal.
2096
-	 *
2097
-	 * If the creation was a success, an id must be returned that can be used to reference
2098
-	 * this subscription in other methods, such as updateSubscription.
2099
-	 *
2100
-	 * @param string $principalUri
2101
-	 * @param string $uri
2102
-	 * @param array $properties
2103
-	 * @return mixed
2104
-	 */
2105
-	public function createSubscription($principalUri, $uri, array $properties) {
2106
-		if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
2107
-			throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
2108
-		}
2109
-
2110
-		$values = [
2111
-			'principaluri' => $principalUri,
2112
-			'uri' => $uri,
2113
-			'source' => $properties['{http://calendarserver.org/ns/}source']->getHref(),
2114
-			'lastmodified' => time(),
2115
-		];
2116
-
2117
-		$propertiesBoolean = ['striptodos', 'stripalarms', 'stripattachments'];
2118
-
2119
-		foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
2120
-			if (array_key_exists($xmlName, $properties)) {
2121
-				$values[$dbName] = $properties[$xmlName];
2122
-				if (in_array($dbName, $propertiesBoolean)) {
2123
-					$values[$dbName] = true;
2124
-				}
2125
-			}
2126
-		}
2127
-
2128
-		$valuesToInsert = [];
2129
-
2130
-		$query = $this->db->getQueryBuilder();
2131
-
2132
-		foreach (array_keys($values) as $name) {
2133
-			$valuesToInsert[$name] = $query->createNamedParameter($values[$name]);
2134
-		}
2135
-
2136
-		$query->insert('calendarsubscriptions')
2137
-			->values($valuesToInsert)
2138
-			->execute();
2139
-
2140
-		$subscriptionId = $this->db->lastInsertId('*PREFIX*calendarsubscriptions');
2141
-
2142
-		$subscriptionRow = $this->getSubscriptionById($subscriptionId);
2143
-		$this->dispatcher->dispatchTyped(new SubscriptionCreatedEvent((int)$subscriptionId, $subscriptionRow));
2144
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createSubscription', new GenericEvent(
2145
-			'\OCA\DAV\CalDAV\CalDavBackend::createSubscription',
2146
-			[
2147
-				'subscriptionId' => $subscriptionId,
2148
-				'subscriptionData' => $subscriptionRow,
2149
-			]));
2150
-
2151
-		return $subscriptionId;
2152
-	}
2153
-
2154
-	/**
2155
-	 * Updates a subscription
2156
-	 *
2157
-	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
2158
-	 * To do the actual updates, you must tell this object which properties
2159
-	 * you're going to process with the handle() method.
2160
-	 *
2161
-	 * Calling the handle method is like telling the PropPatch object "I
2162
-	 * promise I can handle updating this property".
2163
-	 *
2164
-	 * Read the PropPatch documentation for more info and examples.
2165
-	 *
2166
-	 * @param mixed $subscriptionId
2167
-	 * @param PropPatch $propPatch
2168
-	 * @return void
2169
-	 */
2170
-	public function updateSubscription($subscriptionId, PropPatch $propPatch) {
2171
-		$supportedProperties = array_keys($this->subscriptionPropertyMap);
2172
-		$supportedProperties[] = '{http://calendarserver.org/ns/}source';
2173
-
2174
-		$propPatch->handle($supportedProperties, function ($mutations) use ($subscriptionId) {
2175
-			$newValues = [];
2176
-
2177
-			foreach ($mutations as $propertyName => $propertyValue) {
2178
-				if ($propertyName === '{http://calendarserver.org/ns/}source') {
2179
-					$newValues['source'] = $propertyValue->getHref();
2180
-				} else {
2181
-					$fieldName = $this->subscriptionPropertyMap[$propertyName];
2182
-					$newValues[$fieldName] = $propertyValue;
2183
-				}
2184
-			}
2185
-
2186
-			$query = $this->db->getQueryBuilder();
2187
-			$query->update('calendarsubscriptions')
2188
-				->set('lastmodified', $query->createNamedParameter(time()));
2189
-			foreach ($newValues as $fieldName => $value) {
2190
-				$query->set($fieldName, $query->createNamedParameter($value));
2191
-			}
2192
-			$query->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
2193
-				->execute();
2194
-
2195
-			$subscriptionRow = $this->getSubscriptionById($subscriptionId);
2196
-			$this->dispatcher->dispatchTyped(new SubscriptionUpdatedEvent((int)$subscriptionId, $subscriptionRow, [], $mutations));
2197
-			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateSubscription', new GenericEvent(
2198
-				'\OCA\DAV\CalDAV\CalDavBackend::updateSubscription',
2199
-				[
2200
-					'subscriptionId' => $subscriptionId,
2201
-					'subscriptionData' => $subscriptionRow,
2202
-					'propertyMutations' => $mutations,
2203
-				]));
2204
-
2205
-			return true;
2206
-		});
2207
-	}
2208
-
2209
-	/**
2210
-	 * Deletes a subscription.
2211
-	 *
2212
-	 * @param mixed $subscriptionId
2213
-	 * @return void
2214
-	 */
2215
-	public function deleteSubscription($subscriptionId) {
2216
-		$subscriptionRow = $this->getSubscriptionById($subscriptionId);
2217
-
2218
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription', new GenericEvent(
2219
-			'\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription',
2220
-			[
2221
-				'subscriptionId' => $subscriptionId,
2222
-				'subscriptionData' => $this->getSubscriptionById($subscriptionId),
2223
-			]));
2224
-
2225
-		$query = $this->db->getQueryBuilder();
2226
-		$query->delete('calendarsubscriptions')
2227
-			->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
2228
-			->execute();
2229
-
2230
-		$query = $this->db->getQueryBuilder();
2231
-		$query->delete('calendarobjects')
2232
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2233
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2234
-			->execute();
2235
-
2236
-		$query->delete('calendarchanges')
2237
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2238
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2239
-			->execute();
2240
-
2241
-		$query->delete($this->dbObjectPropertiesTable)
2242
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2243
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2244
-			->execute();
2245
-
2246
-		if ($subscriptionRow) {
2247
-			$this->dispatcher->dispatchTyped(new SubscriptionDeletedEvent((int)$subscriptionId, $subscriptionRow, []));
2248
-		}
2249
-	}
2250
-
2251
-	/**
2252
-	 * Returns a single scheduling object for the inbox collection.
2253
-	 *
2254
-	 * The returned array should contain the following elements:
2255
-	 *   * uri - A unique basename for the object. This will be used to
2256
-	 *           construct a full uri.
2257
-	 *   * calendardata - The iCalendar object
2258
-	 *   * lastmodified - The last modification date. Can be an int for a unix
2259
-	 *                    timestamp, or a PHP DateTime object.
2260
-	 *   * etag - A unique token that must change if the object changed.
2261
-	 *   * size - The size of the object, in bytes.
2262
-	 *
2263
-	 * @param string $principalUri
2264
-	 * @param string $objectUri
2265
-	 * @return array
2266
-	 */
2267
-	public function getSchedulingObject($principalUri, $objectUri) {
2268
-		$query = $this->db->getQueryBuilder();
2269
-		$stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
2270
-			->from('schedulingobjects')
2271
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2272
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
2273
-			->execute();
2274
-
2275
-		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
2276
-
2277
-		if (!$row) {
2278
-			return null;
2279
-		}
2280
-
2281
-		return [
2282
-			'uri' => $row['uri'],
2283
-			'calendardata' => $row['calendardata'],
2284
-			'lastmodified' => $row['lastmodified'],
2285
-			'etag' => '"' . $row['etag'] . '"',
2286
-			'size' => (int)$row['size'],
2287
-		];
2288
-	}
2289
-
2290
-	/**
2291
-	 * Returns all scheduling objects for the inbox collection.
2292
-	 *
2293
-	 * These objects should be returned as an array. Every item in the array
2294
-	 * should follow the same structure as returned from getSchedulingObject.
2295
-	 *
2296
-	 * The main difference is that 'calendardata' is optional.
2297
-	 *
2298
-	 * @param string $principalUri
2299
-	 * @return array
2300
-	 */
2301
-	public function getSchedulingObjects($principalUri) {
2302
-		$query = $this->db->getQueryBuilder();
2303
-		$stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
2304
-				->from('schedulingobjects')
2305
-				->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2306
-				->execute();
2307
-
2308
-		$result = [];
2309
-		foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
2310
-			$result[] = [
2311
-				'calendardata' => $row['calendardata'],
2312
-				'uri' => $row['uri'],
2313
-				'lastmodified' => $row['lastmodified'],
2314
-				'etag' => '"' . $row['etag'] . '"',
2315
-				'size' => (int)$row['size'],
2316
-			];
2317
-		}
2318
-
2319
-		return $result;
2320
-	}
2321
-
2322
-	/**
2323
-	 * Deletes a scheduling object from the inbox collection.
2324
-	 *
2325
-	 * @param string $principalUri
2326
-	 * @param string $objectUri
2327
-	 * @return void
2328
-	 */
2329
-	public function deleteSchedulingObject($principalUri, $objectUri) {
2330
-		$query = $this->db->getQueryBuilder();
2331
-		$query->delete('schedulingobjects')
2332
-				->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2333
-				->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
2334
-				->execute();
2335
-	}
2336
-
2337
-	/**
2338
-	 * Creates a new scheduling object. This should land in a users' inbox.
2339
-	 *
2340
-	 * @param string $principalUri
2341
-	 * @param string $objectUri
2342
-	 * @param string $objectData
2343
-	 * @return void
2344
-	 */
2345
-	public function createSchedulingObject($principalUri, $objectUri, $objectData) {
2346
-		$query = $this->db->getQueryBuilder();
2347
-		$query->insert('schedulingobjects')
2348
-			->values([
2349
-				'principaluri' => $query->createNamedParameter($principalUri),
2350
-				'calendardata' => $query->createNamedParameter($objectData, IQueryBuilder::PARAM_LOB),
2351
-				'uri' => $query->createNamedParameter($objectUri),
2352
-				'lastmodified' => $query->createNamedParameter(time()),
2353
-				'etag' => $query->createNamedParameter(md5($objectData)),
2354
-				'size' => $query->createNamedParameter(strlen($objectData))
2355
-			])
2356
-			->execute();
2357
-	}
2358
-
2359
-	/**
2360
-	 * Adds a change record to the calendarchanges table.
2361
-	 *
2362
-	 * @param mixed $calendarId
2363
-	 * @param string $objectUri
2364
-	 * @param int $operation 1 = add, 2 = modify, 3 = delete.
2365
-	 * @param int $calendarType
2366
-	 * @return void
2367
-	 */
2368
-	protected function addChange($calendarId, $objectUri, $operation, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2369
-		$table = $calendarType === self::CALENDAR_TYPE_CALENDAR ? 'calendars': 'calendarsubscriptions';
2370
-
2371
-		$query = $this->db->getQueryBuilder();
2372
-		$query->select('synctoken')
2373
-			->from($table)
2374
-			->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
2375
-		$result = $query->execute();
2376
-		$syncToken = (int)$result->fetchColumn();
2377
-		$result->closeCursor();
2378
-
2379
-		$query = $this->db->getQueryBuilder();
2380
-		$query->insert('calendarchanges')
2381
-			->values([
2382
-				'uri' => $query->createNamedParameter($objectUri),
2383
-				'synctoken' => $query->createNamedParameter($syncToken),
2384
-				'calendarid' => $query->createNamedParameter($calendarId),
2385
-				'operation' => $query->createNamedParameter($operation),
2386
-				'calendartype' => $query->createNamedParameter($calendarType),
2387
-			])
2388
-			->execute();
2389
-
2390
-		$stmt = $this->db->prepare("UPDATE `*PREFIX*$table` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?");
2391
-		$stmt->execute([
2392
-			$calendarId
2393
-		]);
2394
-	}
2395
-
2396
-	/**
2397
-	 * Parses some information from calendar objects, used for optimized
2398
-	 * calendar-queries.
2399
-	 *
2400
-	 * Returns an array with the following keys:
2401
-	 *   * etag - An md5 checksum of the object without the quotes.
2402
-	 *   * size - Size of the object in bytes
2403
-	 *   * componentType - VEVENT, VTODO or VJOURNAL
2404
-	 *   * firstOccurence
2405
-	 *   * lastOccurence
2406
-	 *   * uid - value of the UID property
2407
-	 *
2408
-	 * @param string $calendarData
2409
-	 * @return array
2410
-	 */
2411
-	public function getDenormalizedData($calendarData) {
2412
-		$vObject = Reader::read($calendarData);
2413
-		$vEvents = [];
2414
-		$componentType = null;
2415
-		$component = null;
2416
-		$firstOccurrence = null;
2417
-		$lastOccurrence = null;
2418
-		$uid = null;
2419
-		$classification = self::CLASSIFICATION_PUBLIC;
2420
-		$hasDTSTART = false;
2421
-		foreach ($vObject->getComponents() as $component) {
2422
-			if ($component->name !== 'VTIMEZONE') {
2423
-				// Finding all VEVENTs, and track them
2424
-				if ($component->name === 'VEVENT') {
2425
-					array_push($vEvents, $component);
2426
-					if ($component->DTSTART) {
2427
-						$hasDTSTART = true;
2428
-					}
2429
-				}
2430
-				// Track first component type and uid
2431
-				if ($uid === null) {
2432
-					$componentType = $component->name;
2433
-					$uid = (string)$component->UID;
2434
-				}
2435
-			}
2436
-		}
2437
-		if (!$componentType) {
2438
-			throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
2439
-		}
2440
-
2441
-		if ($hasDTSTART) {
2442
-			$component = $vEvents[0];
2443
-
2444
-			// Finding the last occurrence is a bit harder
2445
-			if (!isset($component->RRULE) && count($vEvents) === 1) {
2446
-				$firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp();
2447
-				if (isset($component->DTEND)) {
2448
-					$lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp();
2449
-				} elseif (isset($component->DURATION)) {
2450
-					$endDate = clone $component->DTSTART->getDateTime();
2451
-					$endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
2452
-					$lastOccurrence = $endDate->getTimeStamp();
2453
-				} elseif (!$component->DTSTART->hasTime()) {
2454
-					$endDate = clone $component->DTSTART->getDateTime();
2455
-					$endDate->modify('+1 day');
2456
-					$lastOccurrence = $endDate->getTimeStamp();
2457
-				} else {
2458
-					$lastOccurrence = $firstOccurrence;
2459
-				}
2460
-			} else {
2461
-				$it = new EventIterator($vEvents);
2462
-				$maxDate = new DateTime(self::MAX_DATE);
2463
-				$firstOccurrence = $it->getDtStart()->getTimestamp();
2464
-				if ($it->isInfinite()) {
2465
-					$lastOccurrence = $maxDate->getTimestamp();
2466
-				} else {
2467
-					$end = $it->getDtEnd();
2468
-					while ($it->valid() && $end < $maxDate) {
2469
-						$end = $it->getDtEnd();
2470
-						$it->next();
2471
-					}
2472
-					$lastOccurrence = $end->getTimestamp();
2473
-				}
2474
-			}
2475
-		}
2476
-
2477
-		if ($component->CLASS) {
2478
-			$classification = CalDavBackend::CLASSIFICATION_PRIVATE;
2479
-			switch ($component->CLASS->getValue()) {
2480
-				case 'PUBLIC':
2481
-					$classification = CalDavBackend::CLASSIFICATION_PUBLIC;
2482
-					break;
2483
-				case 'CONFIDENTIAL':
2484
-					$classification = CalDavBackend::CLASSIFICATION_CONFIDENTIAL;
2485
-					break;
2486
-			}
2487
-		}
2488
-		return [
2489
-			'etag' => md5($calendarData),
2490
-			'size' => strlen($calendarData),
2491
-			'componentType' => $componentType,
2492
-			'firstOccurence' => is_null($firstOccurrence) ? null : max(0, $firstOccurrence),
2493
-			'lastOccurence' => $lastOccurrence,
2494
-			'uid' => $uid,
2495
-			'classification' => $classification
2496
-		];
2497
-	}
2498
-
2499
-	/**
2500
-	 * @param $cardData
2501
-	 * @return bool|string
2502
-	 */
2503
-	private function readBlob($cardData) {
2504
-		if (is_resource($cardData)) {
2505
-			return stream_get_contents($cardData);
2506
-		}
2507
-
2508
-		return $cardData;
2509
-	}
2510
-
2511
-	/**
2512
-	 * @param IShareable $shareable
2513
-	 * @param array $add
2514
-	 * @param array $remove
2515
-	 */
2516
-	public function updateShares($shareable, $add, $remove) {
2517
-		$calendarId = $shareable->getResourceId();
2518
-		$calendarRow = $this->getCalendarById($calendarId);
2519
-		$oldShares = $this->getShares($calendarId);
2520
-
2521
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateShares', new GenericEvent(
2522
-			'\OCA\DAV\CalDAV\CalDavBackend::updateShares',
2523
-			[
2524
-				'calendarId' => $calendarId,
2525
-				'calendarData' => $calendarRow,
2526
-				'shares' => $oldShares,
2527
-				'add' => $add,
2528
-				'remove' => $remove,
2529
-			]));
2530
-		$this->calendarSharingBackend->updateShares($shareable, $add, $remove);
2531
-
2532
-		$this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent((int)$calendarId, $calendarRow, $oldShares, $add, $remove));
2533
-	}
2534
-
2535
-	/**
2536
-	 * @param int $resourceId
2537
-	 * @param int $calendarType
2538
-	 * @return array
2539
-	 */
2540
-	public function getShares($resourceId, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2541
-		return $this->calendarSharingBackend->getShares($resourceId);
2542
-	}
2543
-
2544
-	/**
2545
-	 * @param boolean $value
2546
-	 * @param \OCA\DAV\CalDAV\Calendar $calendar
2547
-	 * @return string|null
2548
-	 */
2549
-	public function setPublishStatus($value, $calendar) {
2550
-		$calendarId = $calendar->getResourceId();
2551
-		$calendarData = $this->getCalendarById($calendarId);
2552
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::publishCalendar', new GenericEvent(
2553
-			'\OCA\DAV\CalDAV\CalDavBackend::updateShares',
2554
-			[
2555
-				'calendarId' => $calendarId,
2556
-				'calendarData' => $calendarData,
2557
-				'public' => $value,
2558
-			]));
2559
-
2560
-		$query = $this->db->getQueryBuilder();
2561
-		if ($value) {
2562
-			$publicUri = $this->random->generate(16, ISecureRandom::CHAR_HUMAN_READABLE);
2563
-			$query->insert('dav_shares')
2564
-				->values([
2565
-					'principaluri' => $query->createNamedParameter($calendar->getPrincipalURI()),
2566
-					'type' => $query->createNamedParameter('calendar'),
2567
-					'access' => $query->createNamedParameter(self::ACCESS_PUBLIC),
2568
-					'resourceid' => $query->createNamedParameter($calendar->getResourceId()),
2569
-					'publicuri' => $query->createNamedParameter($publicUri)
2570
-				]);
2571
-			$query->execute();
2572
-
2573
-			$this->dispatcher->dispatchTyped(new CalendarPublishedEvent((int)$calendarId, $calendarData, $publicUri));
2574
-			return $publicUri;
2575
-		}
2576
-		$query->delete('dav_shares')
2577
-			->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
2578
-			->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)));
2579
-		$query->execute();
2580
-
2581
-		$this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent((int)$calendarId, $calendarData));
2582
-		return null;
2583
-	}
2584
-
2585
-	/**
2586
-	 * @param \OCA\DAV\CalDAV\Calendar $calendar
2587
-	 * @return mixed
2588
-	 */
2589
-	public function getPublishStatus($calendar) {
2590
-		$query = $this->db->getQueryBuilder();
2591
-		$result = $query->select('publicuri')
2592
-			->from('dav_shares')
2593
-			->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
2594
-			->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
2595
-			->execute();
2596
-
2597
-		$row = $result->fetch();
2598
-		$result->closeCursor();
2599
-		return $row ? reset($row) : false;
2600
-	}
2601
-
2602
-	/**
2603
-	 * @param int $resourceId
2604
-	 * @param array $acl
2605
-	 * @return array
2606
-	 */
2607
-	public function applyShareAcl($resourceId, $acl) {
2608
-		return $this->calendarSharingBackend->applyShareAcl($resourceId, $acl);
2609
-	}
2610
-
2611
-
2612
-
2613
-	/**
2614
-	 * update properties table
2615
-	 *
2616
-	 * @param int $calendarId
2617
-	 * @param string $objectUri
2618
-	 * @param string $calendarData
2619
-	 * @param int $calendarType
2620
-	 */
2621
-	public function updateProperties($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2622
-		$objectId = $this->getCalendarObjectId($calendarId, $objectUri, $calendarType);
2623
-
2624
-		try {
2625
-			$vCalendar = $this->readCalendarData($calendarData);
2626
-		} catch (\Exception $ex) {
2627
-			return;
2628
-		}
2629
-
2630
-		$this->purgeProperties($calendarId, $objectId);
2631
-
2632
-		$query = $this->db->getQueryBuilder();
2633
-		$query->insert($this->dbObjectPropertiesTable)
2634
-			->values(
2635
-				[
2636
-					'calendarid' => $query->createNamedParameter($calendarId),
2637
-					'calendartype' => $query->createNamedParameter($calendarType),
2638
-					'objectid' => $query->createNamedParameter($objectId),
2639
-					'name' => $query->createParameter('name'),
2640
-					'parameter' => $query->createParameter('parameter'),
2641
-					'value' => $query->createParameter('value'),
2642
-				]
2643
-			);
2644
-
2645
-		$indexComponents = ['VEVENT', 'VJOURNAL', 'VTODO'];
2646
-		foreach ($vCalendar->getComponents() as $component) {
2647
-			if (!in_array($component->name, $indexComponents)) {
2648
-				continue;
2649
-			}
2650
-
2651
-			foreach ($component->children() as $property) {
2652
-				if (in_array($property->name, self::$indexProperties)) {
2653
-					$value = $property->getValue();
2654
-					// is this a shitty db?
2655
-					if (!$this->db->supports4ByteText()) {
2656
-						$value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
2657
-					}
2658
-					$value = mb_substr($value, 0, 254);
2659
-
2660
-					$query->setParameter('name', $property->name);
2661
-					$query->setParameter('parameter', null);
2662
-					$query->setParameter('value', $value);
2663
-					$query->execute();
2664
-				}
2665
-
2666
-				if (array_key_exists($property->name, self::$indexParameters)) {
2667
-					$parameters = $property->parameters();
2668
-					$indexedParametersForProperty = self::$indexParameters[$property->name];
2669
-
2670
-					foreach ($parameters as $key => $value) {
2671
-						if (in_array($key, $indexedParametersForProperty)) {
2672
-							// is this a shitty db?
2673
-							if ($this->db->supports4ByteText()) {
2674
-								$value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
2675
-							}
2676
-
2677
-							$query->setParameter('name', $property->name);
2678
-							$query->setParameter('parameter', mb_substr($key, 0, 254));
2679
-							$query->setParameter('value', mb_substr($value, 0, 254));
2680
-							$query->execute();
2681
-						}
2682
-					}
2683
-				}
2684
-			}
2685
-		}
2686
-	}
2687
-
2688
-	/**
2689
-	 * deletes all birthday calendars
2690
-	 */
2691
-	public function deleteAllBirthdayCalendars() {
2692
-		$query = $this->db->getQueryBuilder();
2693
-		$result = $query->select(['id'])->from('calendars')
2694
-			->where($query->expr()->eq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)))
2695
-			->execute();
2696
-
2697
-		$ids = $result->fetchAll();
2698
-		foreach ($ids as $id) {
2699
-			$this->deleteCalendar($id['id']);
2700
-		}
2701
-	}
2702
-
2703
-	/**
2704
-	 * @param $subscriptionId
2705
-	 */
2706
-	public function purgeAllCachedEventsForSubscription($subscriptionId) {
2707
-		$query = $this->db->getQueryBuilder();
2708
-		$query->select('uri')
2709
-			->from('calendarobjects')
2710
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2711
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
2712
-		$stmt = $query->execute();
2713
-
2714
-		$uris = [];
2715
-		foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
2716
-			$uris[] = $row['uri'];
2717
-		}
2718
-		$stmt->closeCursor();
2719
-
2720
-		$query = $this->db->getQueryBuilder();
2721
-		$query->delete('calendarobjects')
2722
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2723
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2724
-			->execute();
2725
-
2726
-		$query->delete('calendarchanges')
2727
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2728
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2729
-			->execute();
2730
-
2731
-		$query->delete($this->dbObjectPropertiesTable)
2732
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2733
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2734
-			->execute();
2735
-
2736
-		foreach ($uris as $uri) {
2737
-			$this->addChange($subscriptionId, $uri, 3, self::CALENDAR_TYPE_SUBSCRIPTION);
2738
-		}
2739
-	}
2740
-
2741
-	/**
2742
-	 * Move a calendar from one user to another
2743
-	 *
2744
-	 * @param string $uriName
2745
-	 * @param string $uriOrigin
2746
-	 * @param string $uriDestination
2747
-	 */
2748
-	public function moveCalendar($uriName, $uriOrigin, $uriDestination) {
2749
-		$query = $this->db->getQueryBuilder();
2750
-		$query->update('calendars')
2751
-			->set('principaluri', $query->createNamedParameter($uriDestination))
2752
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($uriOrigin)))
2753
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($uriName)))
2754
-			->execute();
2755
-	}
2756
-
2757
-	/**
2758
-	 * read VCalendar data into a VCalendar object
2759
-	 *
2760
-	 * @param string $objectData
2761
-	 * @return VCalendar
2762
-	 */
2763
-	protected function readCalendarData($objectData) {
2764
-		return Reader::read($objectData);
2765
-	}
2766
-
2767
-	/**
2768
-	 * delete all properties from a given calendar object
2769
-	 *
2770
-	 * @param int $calendarId
2771
-	 * @param int $objectId
2772
-	 */
2773
-	protected function purgeProperties($calendarId, $objectId) {
2774
-		$query = $this->db->getQueryBuilder();
2775
-		$query->delete($this->dbObjectPropertiesTable)
2776
-			->where($query->expr()->eq('objectid', $query->createNamedParameter($objectId)))
2777
-			->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
2778
-		$query->execute();
2779
-	}
2780
-
2781
-	/**
2782
-	 * get ID from a given calendar object
2783
-	 *
2784
-	 * @param int $calendarId
2785
-	 * @param string $uri
2786
-	 * @param int $calendarType
2787
-	 * @return int
2788
-	 */
2789
-	protected function getCalendarObjectId($calendarId, $uri, $calendarType):int {
2790
-		$query = $this->db->getQueryBuilder();
2791
-		$query->select('id')
2792
-			->from('calendarobjects')
2793
-			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
2794
-			->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
2795
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
2796
-
2797
-		$result = $query->execute();
2798
-		$objectIds = $result->fetch();
2799
-		$result->closeCursor();
2800
-
2801
-		if (!isset($objectIds['id'])) {
2802
-			throw new \InvalidArgumentException('Calendarobject does not exists: ' . $uri);
2803
-		}
2804
-
2805
-		return (int)$objectIds['id'];
2806
-	}
2807
-
2808
-	/**
2809
-	 * return legacy endpoint principal name to new principal name
2810
-	 *
2811
-	 * @param $principalUri
2812
-	 * @param $toV2
2813
-	 * @return string
2814
-	 */
2815
-	private function convertPrincipal($principalUri, $toV2) {
2816
-		if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
2817
-			list(, $name) = Uri\split($principalUri);
2818
-			if ($toV2 === true) {
2819
-				return "principals/users/$name";
2820
-			}
2821
-			return "principals/$name";
2822
-		}
2823
-		return $principalUri;
2824
-	}
2825
-
2826
-	/**
2827
-	 * adds information about an owner to the calendar data
2828
-	 *
2829
-	 * @param $calendarInfo
2830
-	 */
2831
-	private function addOwnerPrincipal(&$calendarInfo) {
2832
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
2833
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
2834
-		if (isset($calendarInfo[$ownerPrincipalKey])) {
2835
-			$uri = $calendarInfo[$ownerPrincipalKey];
2836
-		} else {
2837
-			$uri = $calendarInfo['principaluri'];
2838
-		}
2839
-
2840
-		$principalInformation = $this->principalBackend->getPrincipalByPath($uri);
2841
-		if (isset($principalInformation['{DAV:}displayname'])) {
2842
-			$calendarInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
2843
-		}
2844
-	}
98
+    public const CALENDAR_TYPE_CALENDAR = 0;
99
+    public const CALENDAR_TYPE_SUBSCRIPTION = 1;
100
+
101
+    public const PERSONAL_CALENDAR_URI = 'personal';
102
+    public const PERSONAL_CALENDAR_NAME = 'Personal';
103
+
104
+    public const RESOURCE_BOOKING_CALENDAR_URI = 'calendar';
105
+    public const RESOURCE_BOOKING_CALENDAR_NAME = 'Calendar';
106
+
107
+    /**
108
+     * We need to specify a max date, because we need to stop *somewhere*
109
+     *
110
+     * On 32 bit system the maximum for a signed integer is 2147483647, so
111
+     * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
112
+     * in 2038-01-19 to avoid problems when the date is converted
113
+     * to a unix timestamp.
114
+     */
115
+    public const MAX_DATE = '2038-01-01';
116
+
117
+    public const ACCESS_PUBLIC = 4;
118
+    public const CLASSIFICATION_PUBLIC = 0;
119
+    public const CLASSIFICATION_PRIVATE = 1;
120
+    public const CLASSIFICATION_CONFIDENTIAL = 2;
121
+
122
+    /**
123
+     * List of CalDAV properties, and how they map to database field names
124
+     * Add your own properties by simply adding on to this array.
125
+     *
126
+     * Note that only string-based properties are supported here.
127
+     *
128
+     * @var array
129
+     */
130
+    public $propertyMap = [
131
+        '{DAV:}displayname' => 'displayname',
132
+        '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
133
+        '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone',
134
+        '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
135
+        '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
136
+    ];
137
+
138
+    /**
139
+     * List of subscription properties, and how they map to database field names.
140
+     *
141
+     * @var array
142
+     */
143
+    public $subscriptionPropertyMap = [
144
+        '{DAV:}displayname' => 'displayname',
145
+        '{http://apple.com/ns/ical/}refreshrate' => 'refreshrate',
146
+        '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
147
+        '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
148
+        '{http://calendarserver.org/ns/}subscribed-strip-todos' => 'striptodos',
149
+        '{http://calendarserver.org/ns/}subscribed-strip-alarms' => 'stripalarms',
150
+        '{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
151
+    ];
152
+
153
+    /** @var array properties to index */
154
+    public static $indexProperties = ['CATEGORIES', 'COMMENT', 'DESCRIPTION',
155
+        'LOCATION', 'RESOURCES', 'STATUS', 'SUMMARY', 'ATTENDEE', 'CONTACT',
156
+        'ORGANIZER'];
157
+
158
+    /** @var array parameters to index */
159
+    public static $indexParameters = [
160
+        'ATTENDEE' => ['CN'],
161
+        'ORGANIZER' => ['CN'],
162
+    ];
163
+
164
+    /**
165
+     * @var string[] Map of uid => display name
166
+     */
167
+    protected $userDisplayNames;
168
+
169
+    /** @var IDBConnection */
170
+    private $db;
171
+
172
+    /** @var Backend */
173
+    private $calendarSharingBackend;
174
+
175
+    /** @var Principal */
176
+    private $principalBackend;
177
+
178
+    /** @var IUserManager */
179
+    private $userManager;
180
+
181
+    /** @var ISecureRandom */
182
+    private $random;
183
+
184
+    /** @var ILogger */
185
+    private $logger;
186
+
187
+    /** @var IEventDispatcher */
188
+    private $dispatcher;
189
+
190
+    /** @var EventDispatcherInterface */
191
+    private $legacyDispatcher;
192
+
193
+    /** @var bool */
194
+    private $legacyEndpoint;
195
+
196
+    /** @var string */
197
+    private $dbObjectPropertiesTable = 'calendarobjects_props';
198
+
199
+    /**
200
+     * CalDavBackend constructor.
201
+     *
202
+     * @param IDBConnection $db
203
+     * @param Principal $principalBackend
204
+     * @param IUserManager $userManager
205
+     * @param IGroupManager $groupManager
206
+     * @param ISecureRandom $random
207
+     * @param ILogger $logger
208
+     * @param IEventDispatcher $dispatcher
209
+     * @param EventDispatcherInterface $legacyDispatcher
210
+     * @param bool $legacyEndpoint
211
+     */
212
+    public function __construct(IDBConnection $db,
213
+                                Principal $principalBackend,
214
+                                IUserManager $userManager,
215
+                                IGroupManager $groupManager,
216
+                                ISecureRandom $random,
217
+                                ILogger $logger,
218
+                                IEventDispatcher $dispatcher,
219
+                                EventDispatcherInterface $legacyDispatcher,
220
+                                bool $legacyEndpoint = false) {
221
+        $this->db = $db;
222
+        $this->principalBackend = $principalBackend;
223
+        $this->userManager = $userManager;
224
+        $this->calendarSharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'calendar');
225
+        $this->random = $random;
226
+        $this->logger = $logger;
227
+        $this->dispatcher = $dispatcher;
228
+        $this->legacyDispatcher = $legacyDispatcher;
229
+        $this->legacyEndpoint = $legacyEndpoint;
230
+    }
231
+
232
+    /**
233
+     * Return the number of calendars for a principal
234
+     *
235
+     * By default this excludes the automatically generated birthday calendar
236
+     *
237
+     * @param $principalUri
238
+     * @param bool $excludeBirthday
239
+     * @return int
240
+     */
241
+    public function getCalendarsForUserCount($principalUri, $excludeBirthday = true) {
242
+        $principalUri = $this->convertPrincipal($principalUri, true);
243
+        $query = $this->db->getQueryBuilder();
244
+        $query->select($query->func()->count('*'))
245
+            ->from('calendars');
246
+
247
+        if ($principalUri === '') {
248
+            $query->where($query->expr()->emptyString('principaluri'));
249
+        } else {
250
+            $query->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
251
+        }
252
+
253
+        if ($excludeBirthday) {
254
+            $query->andWhere($query->expr()->neq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)));
255
+        }
256
+
257
+        $result = $query->execute();
258
+        $column = (int)$result->fetchColumn();
259
+        $result->closeCursor();
260
+        return $column;
261
+    }
262
+
263
+    /**
264
+     * Returns a list of calendars for a principal.
265
+     *
266
+     * Every project is an array with the following keys:
267
+     *  * id, a unique id that will be used by other functions to modify the
268
+     *    calendar. This can be the same as the uri or a database key.
269
+     *  * uri, which the basename of the uri with which the calendar is
270
+     *    accessed.
271
+     *  * principaluri. The owner of the calendar. Almost always the same as
272
+     *    principalUri passed to this method.
273
+     *
274
+     * Furthermore it can contain webdav properties in clark notation. A very
275
+     * common one is '{DAV:}displayname'.
276
+     *
277
+     * Many clients also require:
278
+     * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
279
+     * For this property, you can just return an instance of
280
+     * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
281
+     *
282
+     * If you return {http://sabredav.org/ns}read-only and set the value to 1,
283
+     * ACL will automatically be put in read-only mode.
284
+     *
285
+     * @param string $principalUri
286
+     * @return array
287
+     */
288
+    public function getCalendarsForUser($principalUri) {
289
+        $principalUriOriginal = $principalUri;
290
+        $principalUri = $this->convertPrincipal($principalUri, true);
291
+        $fields = array_values($this->propertyMap);
292
+        $fields[] = 'id';
293
+        $fields[] = 'uri';
294
+        $fields[] = 'synctoken';
295
+        $fields[] = 'components';
296
+        $fields[] = 'principaluri';
297
+        $fields[] = 'transparent';
298
+
299
+        // Making fields a comma-delimited list
300
+        $query = $this->db->getQueryBuilder();
301
+        $query->select($fields)
302
+            ->from('calendars')
303
+            ->orderBy('calendarorder', 'ASC');
304
+
305
+        if ($principalUri === '') {
306
+            $query->where($query->expr()->emptyString('principaluri'));
307
+        } else {
308
+            $query->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
309
+        }
310
+
311
+        $result = $query->execute();
312
+
313
+        $calendars = [];
314
+        while ($row = $result->fetch()) {
315
+            $row['principaluri'] = (string) $row['principaluri'];
316
+            $components = [];
317
+            if ($row['components']) {
318
+                $components = explode(',',$row['components']);
319
+            }
320
+
321
+            $calendar = [
322
+                'id' => $row['id'],
323
+                'uri' => $row['uri'],
324
+                'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
325
+                '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
326
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
327
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
328
+                '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
329
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
330
+            ];
331
+
332
+            foreach ($this->propertyMap as $xmlName => $dbName) {
333
+                $calendar[$xmlName] = $row[$dbName];
334
+            }
335
+
336
+            $this->addOwnerPrincipal($calendar);
337
+
338
+            if (!isset($calendars[$calendar['id']])) {
339
+                $calendars[$calendar['id']] = $calendar;
340
+            }
341
+        }
342
+        $result->closeCursor();
343
+
344
+        // query for shared calendars
345
+        $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
346
+        $principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
347
+
348
+        $principals = array_map(function ($principal) {
349
+            return urldecode($principal);
350
+        }, $principals);
351
+        $principals[] = $principalUri;
352
+
353
+        $fields = array_values($this->propertyMap);
354
+        $fields[] = 'a.id';
355
+        $fields[] = 'a.uri';
356
+        $fields[] = 'a.synctoken';
357
+        $fields[] = 'a.components';
358
+        $fields[] = 'a.principaluri';
359
+        $fields[] = 'a.transparent';
360
+        $fields[] = 's.access';
361
+        $query = $this->db->getQueryBuilder();
362
+        $query->select($fields)
363
+            ->from('dav_shares', 's')
364
+            ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
365
+            ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
366
+            ->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
367
+            ->setParameter('type', 'calendar')
368
+            ->setParameter('principaluri', $principals, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY);
369
+
370
+        $result	= $query->execute();
371
+
372
+        $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
373
+        while ($row = $result->fetch()) {
374
+            $row['principaluri'] = (string) $row['principaluri'];
375
+            if ($row['principaluri'] === $principalUri) {
376
+                continue;
377
+            }
378
+
379
+            $readOnly = (int) $row['access'] === Backend::ACCESS_READ;
380
+            if (isset($calendars[$row['id']])) {
381
+                if ($readOnly) {
382
+                    // New share can not have more permissions then the old one.
383
+                    continue;
384
+                }
385
+                if (isset($calendars[$row['id']][$readOnlyPropertyName]) &&
386
+                    $calendars[$row['id']][$readOnlyPropertyName] === 0) {
387
+                    // Old share is already read-write, no more permissions can be gained
388
+                    continue;
389
+                }
390
+            }
391
+
392
+            list(, $name) = Uri\split($row['principaluri']);
393
+            $uri = $row['uri'] . '_shared_by_' . $name;
394
+            $row['displayname'] = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
395
+            $components = [];
396
+            if ($row['components']) {
397
+                $components = explode(',',$row['components']);
398
+            }
399
+            $calendar = [
400
+                'id' => $row['id'],
401
+                'uri' => $uri,
402
+                'principaluri' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
403
+                '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
404
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
405
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
406
+                '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp('transparent'),
407
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
408
+                $readOnlyPropertyName => $readOnly,
409
+            ];
410
+
411
+            foreach ($this->propertyMap as $xmlName => $dbName) {
412
+                $calendar[$xmlName] = $row[$dbName];
413
+            }
414
+
415
+            $this->addOwnerPrincipal($calendar);
416
+
417
+            $calendars[$calendar['id']] = $calendar;
418
+        }
419
+        $result->closeCursor();
420
+
421
+        return array_values($calendars);
422
+    }
423
+
424
+    /**
425
+     * @param $principalUri
426
+     * @return array
427
+     */
428
+    public function getUsersOwnCalendars($principalUri) {
429
+        $principalUri = $this->convertPrincipal($principalUri, true);
430
+        $fields = array_values($this->propertyMap);
431
+        $fields[] = 'id';
432
+        $fields[] = 'uri';
433
+        $fields[] = 'synctoken';
434
+        $fields[] = 'components';
435
+        $fields[] = 'principaluri';
436
+        $fields[] = 'transparent';
437
+        // Making fields a comma-delimited list
438
+        $query = $this->db->getQueryBuilder();
439
+        $query->select($fields)->from('calendars')
440
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
441
+            ->orderBy('calendarorder', 'ASC');
442
+        $stmt = $query->execute();
443
+        $calendars = [];
444
+        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
445
+            $row['principaluri'] = (string) $row['principaluri'];
446
+            $components = [];
447
+            if ($row['components']) {
448
+                $components = explode(',',$row['components']);
449
+            }
450
+            $calendar = [
451
+                'id' => $row['id'],
452
+                'uri' => $row['uri'],
453
+                'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
454
+                '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
455
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
456
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
457
+                '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
458
+            ];
459
+            foreach ($this->propertyMap as $xmlName => $dbName) {
460
+                $calendar[$xmlName] = $row[$dbName];
461
+            }
462
+
463
+            $this->addOwnerPrincipal($calendar);
464
+
465
+            if (!isset($calendars[$calendar['id']])) {
466
+                $calendars[$calendar['id']] = $calendar;
467
+            }
468
+        }
469
+        $stmt->closeCursor();
470
+        return array_values($calendars);
471
+    }
472
+
473
+
474
+    /**
475
+     * @param $uid
476
+     * @return string
477
+     */
478
+    private function getUserDisplayName($uid) {
479
+        if (!isset($this->userDisplayNames[$uid])) {
480
+            $user = $this->userManager->get($uid);
481
+
482
+            if ($user instanceof IUser) {
483
+                $this->userDisplayNames[$uid] = $user->getDisplayName();
484
+            } else {
485
+                $this->userDisplayNames[$uid] = $uid;
486
+            }
487
+        }
488
+
489
+        return $this->userDisplayNames[$uid];
490
+    }
491
+
492
+    /**
493
+     * @return array
494
+     */
495
+    public function getPublicCalendars() {
496
+        $fields = array_values($this->propertyMap);
497
+        $fields[] = 'a.id';
498
+        $fields[] = 'a.uri';
499
+        $fields[] = 'a.synctoken';
500
+        $fields[] = 'a.components';
501
+        $fields[] = 'a.principaluri';
502
+        $fields[] = 'a.transparent';
503
+        $fields[] = 's.access';
504
+        $fields[] = 's.publicuri';
505
+        $calendars = [];
506
+        $query = $this->db->getQueryBuilder();
507
+        $result = $query->select($fields)
508
+            ->from('dav_shares', 's')
509
+            ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
510
+            ->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
511
+            ->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
512
+            ->execute();
513
+
514
+        while ($row = $result->fetch()) {
515
+            $row['principaluri'] = (string) $row['principaluri'];
516
+            list(, $name) = Uri\split($row['principaluri']);
517
+            $row['displayname'] = $row['displayname'] . "($name)";
518
+            $components = [];
519
+            if ($row['components']) {
520
+                $components = explode(',',$row['components']);
521
+            }
522
+            $calendar = [
523
+                'id' => $row['id'],
524
+                'uri' => $row['publicuri'],
525
+                'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
526
+                '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
527
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
528
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
529
+                '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
530
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
531
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
532
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
533
+            ];
534
+
535
+            foreach ($this->propertyMap as $xmlName => $dbName) {
536
+                $calendar[$xmlName] = $row[$dbName];
537
+            }
538
+
539
+            $this->addOwnerPrincipal($calendar);
540
+
541
+            if (!isset($calendars[$calendar['id']])) {
542
+                $calendars[$calendar['id']] = $calendar;
543
+            }
544
+        }
545
+        $result->closeCursor();
546
+
547
+        return array_values($calendars);
548
+    }
549
+
550
+    /**
551
+     * @param string $uri
552
+     * @return array
553
+     * @throws NotFound
554
+     */
555
+    public function getPublicCalendar($uri) {
556
+        $fields = array_values($this->propertyMap);
557
+        $fields[] = 'a.id';
558
+        $fields[] = 'a.uri';
559
+        $fields[] = 'a.synctoken';
560
+        $fields[] = 'a.components';
561
+        $fields[] = 'a.principaluri';
562
+        $fields[] = 'a.transparent';
563
+        $fields[] = 's.access';
564
+        $fields[] = 's.publicuri';
565
+        $query = $this->db->getQueryBuilder();
566
+        $result = $query->select($fields)
567
+            ->from('dav_shares', 's')
568
+            ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
569
+            ->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
570
+            ->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
571
+            ->andWhere($query->expr()->eq('s.publicuri', $query->createNamedParameter($uri)))
572
+            ->execute();
573
+
574
+        $row = $result->fetch(\PDO::FETCH_ASSOC);
575
+
576
+        $result->closeCursor();
577
+
578
+        if ($row === false) {
579
+            throw new NotFound('Node with name \'' . $uri . '\' could not be found');
580
+        }
581
+
582
+        $row['principaluri'] = (string) $row['principaluri'];
583
+        list(, $name) = Uri\split($row['principaluri']);
584
+        $row['displayname'] = $row['displayname'] . ' ' . "($name)";
585
+        $components = [];
586
+        if ($row['components']) {
587
+            $components = explode(',',$row['components']);
588
+        }
589
+        $calendar = [
590
+            'id' => $row['id'],
591
+            'uri' => $row['publicuri'],
592
+            'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
593
+            '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
594
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
595
+            '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
596
+            '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
597
+            '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
598
+            '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
599
+            '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
600
+        ];
601
+
602
+        foreach ($this->propertyMap as $xmlName => $dbName) {
603
+            $calendar[$xmlName] = $row[$dbName];
604
+        }
605
+
606
+        $this->addOwnerPrincipal($calendar);
607
+
608
+        return $calendar;
609
+    }
610
+
611
+    /**
612
+     * @param string $principal
613
+     * @param string $uri
614
+     * @return array|null
615
+     */
616
+    public function getCalendarByUri($principal, $uri) {
617
+        $fields = array_values($this->propertyMap);
618
+        $fields[] = 'id';
619
+        $fields[] = 'uri';
620
+        $fields[] = 'synctoken';
621
+        $fields[] = 'components';
622
+        $fields[] = 'principaluri';
623
+        $fields[] = 'transparent';
624
+
625
+        // Making fields a comma-delimited list
626
+        $query = $this->db->getQueryBuilder();
627
+        $query->select($fields)->from('calendars')
628
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
629
+            ->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
630
+            ->setMaxResults(1);
631
+        $stmt = $query->execute();
632
+
633
+        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
634
+        $stmt->closeCursor();
635
+        if ($row === false) {
636
+            return null;
637
+        }
638
+
639
+        $row['principaluri'] = (string) $row['principaluri'];
640
+        $components = [];
641
+        if ($row['components']) {
642
+            $components = explode(',',$row['components']);
643
+        }
644
+
645
+        $calendar = [
646
+            'id' => $row['id'],
647
+            'uri' => $row['uri'],
648
+            'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
649
+            '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
650
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
651
+            '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
652
+            '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
653
+        ];
654
+
655
+        foreach ($this->propertyMap as $xmlName => $dbName) {
656
+            $calendar[$xmlName] = $row[$dbName];
657
+        }
658
+
659
+        $this->addOwnerPrincipal($calendar);
660
+
661
+        return $calendar;
662
+    }
663
+
664
+    /**
665
+     * @param $calendarId
666
+     * @return array|null
667
+     */
668
+    public function getCalendarById($calendarId) {
669
+        $fields = array_values($this->propertyMap);
670
+        $fields[] = 'id';
671
+        $fields[] = 'uri';
672
+        $fields[] = 'synctoken';
673
+        $fields[] = 'components';
674
+        $fields[] = 'principaluri';
675
+        $fields[] = 'transparent';
676
+
677
+        // Making fields a comma-delimited list
678
+        $query = $this->db->getQueryBuilder();
679
+        $query->select($fields)->from('calendars')
680
+            ->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)))
681
+            ->setMaxResults(1);
682
+        $stmt = $query->execute();
683
+
684
+        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
685
+        $stmt->closeCursor();
686
+        if ($row === false) {
687
+            return null;
688
+        }
689
+
690
+        $row['principaluri'] = (string) $row['principaluri'];
691
+        $components = [];
692
+        if ($row['components']) {
693
+            $components = explode(',',$row['components']);
694
+        }
695
+
696
+        $calendar = [
697
+            'id' => $row['id'],
698
+            'uri' => $row['uri'],
699
+            'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
700
+            '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
701
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
702
+            '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
703
+            '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
704
+        ];
705
+
706
+        foreach ($this->propertyMap as $xmlName => $dbName) {
707
+            $calendar[$xmlName] = $row[$dbName];
708
+        }
709
+
710
+        $this->addOwnerPrincipal($calendar);
711
+
712
+        return $calendar;
713
+    }
714
+
715
+    /**
716
+     * @param $subscriptionId
717
+     */
718
+    public function getSubscriptionById($subscriptionId) {
719
+        $fields = array_values($this->subscriptionPropertyMap);
720
+        $fields[] = 'id';
721
+        $fields[] = 'uri';
722
+        $fields[] = 'source';
723
+        $fields[] = 'synctoken';
724
+        $fields[] = 'principaluri';
725
+        $fields[] = 'lastmodified';
726
+
727
+        $query = $this->db->getQueryBuilder();
728
+        $query->select($fields)
729
+            ->from('calendarsubscriptions')
730
+            ->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
731
+            ->orderBy('calendarorder', 'asc');
732
+        $stmt = $query->execute();
733
+
734
+        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
735
+        $stmt->closeCursor();
736
+        if ($row === false) {
737
+            return null;
738
+        }
739
+
740
+        $row['principaluri'] = (string) $row['principaluri'];
741
+        $subscription = [
742
+            'id' => $row['id'],
743
+            'uri' => $row['uri'],
744
+            'principaluri' => $row['principaluri'],
745
+            'source' => $row['source'],
746
+            'lastmodified' => $row['lastmodified'],
747
+            '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
748
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
749
+        ];
750
+
751
+        foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
752
+            if (!is_null($row[$dbName])) {
753
+                $subscription[$xmlName] = $row[$dbName];
754
+            }
755
+        }
756
+
757
+        return $subscription;
758
+    }
759
+
760
+    /**
761
+     * Creates a new calendar for a principal.
762
+     *
763
+     * If the creation was a success, an id must be returned that can be used to reference
764
+     * this calendar in other methods, such as updateCalendar.
765
+     *
766
+     * @param string $principalUri
767
+     * @param string $calendarUri
768
+     * @param array $properties
769
+     * @return int
770
+     */
771
+    public function createCalendar($principalUri, $calendarUri, array $properties) {
772
+        $values = [
773
+            'principaluri' => $this->convertPrincipal($principalUri, true),
774
+            'uri' => $calendarUri,
775
+            'synctoken' => 1,
776
+            'transparent' => 0,
777
+            'components' => 'VEVENT,VTODO',
778
+            'displayname' => $calendarUri
779
+        ];
780
+
781
+        // Default value
782
+        $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
783
+        if (isset($properties[$sccs])) {
784
+            if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) {
785
+                throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
786
+            }
787
+            $values['components'] = implode(',',$properties[$sccs]->getValue());
788
+        } elseif (isset($properties['components'])) {
789
+            // Allow to provide components internally without having
790
+            // to create a SupportedCalendarComponentSet object
791
+            $values['components'] = $properties['components'];
792
+        }
793
+
794
+        $transp = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
795
+        if (isset($properties[$transp])) {
796
+            $values['transparent'] = (int) ($properties[$transp]->getValue() === 'transparent');
797
+        }
798
+
799
+        foreach ($this->propertyMap as $xmlName => $dbName) {
800
+            if (isset($properties[$xmlName])) {
801
+                $values[$dbName] = $properties[$xmlName];
802
+            }
803
+        }
804
+
805
+        $query = $this->db->getQueryBuilder();
806
+        $query->insert('calendars');
807
+        foreach ($values as $column => $value) {
808
+            $query->setValue($column, $query->createNamedParameter($value));
809
+        }
810
+        $query->execute();
811
+        $calendarId = $query->getLastInsertId();
812
+
813
+        $calendarData = $this->getCalendarById($calendarId);
814
+        $this->dispatcher->dispatchTyped(new CalendarCreatedEvent((int)$calendarId, $calendarData));
815
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendar', new GenericEvent(
816
+            '\OCA\DAV\CalDAV\CalDavBackend::createCalendar',
817
+            [
818
+                'calendarId' => $calendarId,
819
+                'calendarData' => $calendarData,
820
+            ]));
821
+
822
+        return $calendarId;
823
+    }
824
+
825
+    /**
826
+     * Updates properties for a calendar.
827
+     *
828
+     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
829
+     * To do the actual updates, you must tell this object which properties
830
+     * you're going to process with the handle() method.
831
+     *
832
+     * Calling the handle method is like telling the PropPatch object "I
833
+     * promise I can handle updating this property".
834
+     *
835
+     * Read the PropPatch documentation for more info and examples.
836
+     *
837
+     * @param mixed $calendarId
838
+     * @param PropPatch $propPatch
839
+     * @return void
840
+     */
841
+    public function updateCalendar($calendarId, PropPatch $propPatch) {
842
+        $supportedProperties = array_keys($this->propertyMap);
843
+        $supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
844
+
845
+        $propPatch->handle($supportedProperties, function ($mutations) use ($calendarId) {
846
+            $newValues = [];
847
+            foreach ($mutations as $propertyName => $propertyValue) {
848
+                switch ($propertyName) {
849
+                    case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp':
850
+                        $fieldName = 'transparent';
851
+                        $newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent');
852
+                        break;
853
+                    default:
854
+                        $fieldName = $this->propertyMap[$propertyName];
855
+                        $newValues[$fieldName] = $propertyValue;
856
+                        break;
857
+                }
858
+            }
859
+            $query = $this->db->getQueryBuilder();
860
+            $query->update('calendars');
861
+            foreach ($newValues as $fieldName => $value) {
862
+                $query->set($fieldName, $query->createNamedParameter($value));
863
+            }
864
+            $query->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
865
+            $query->execute();
866
+
867
+            $this->addChange($calendarId, "", 2);
868
+
869
+            $calendarData = $this->getCalendarById($calendarId);
870
+            $shares = $this->getShares($calendarId);
871
+            $this->dispatcher->dispatchTyped(new CalendarUpdatedEvent((int)$calendarId, $calendarData, $shares, $mutations));
872
+            $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendar', new GenericEvent(
873
+                '\OCA\DAV\CalDAV\CalDavBackend::updateCalendar',
874
+                [
875
+                    'calendarId' => $calendarId,
876
+                    'calendarData' => $calendarData,
877
+                    'shares' => $shares,
878
+                    'propertyMutations' => $mutations,
879
+                ]));
880
+
881
+            return true;
882
+        });
883
+    }
884
+
885
+    /**
886
+     * Delete a calendar and all it's objects
887
+     *
888
+     * @param mixed $calendarId
889
+     * @return void
890
+     */
891
+    public function deleteCalendar($calendarId) {
892
+        $calendarData = $this->getCalendarById($calendarId);
893
+        $shares = $this->getShares($calendarId);
894
+
895
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar', new GenericEvent(
896
+            '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar',
897
+            [
898
+                'calendarId' => $calendarId,
899
+                'calendarData' => $calendarData,
900
+                'shares' => $shares,
901
+            ]));
902
+
903
+        $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `calendartype` = ?');
904
+        $stmt->execute([$calendarId, self::CALENDAR_TYPE_CALENDAR]);
905
+
906
+        $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendars` WHERE `id` = ?');
907
+        $stmt->execute([$calendarId]);
908
+
909
+        $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarchanges` WHERE `calendarid` = ? AND `calendartype` = ?');
910
+        $stmt->execute([$calendarId, self::CALENDAR_TYPE_CALENDAR]);
911
+
912
+        $this->calendarSharingBackend->deleteAllShares($calendarId);
913
+
914
+        $query = $this->db->getQueryBuilder();
915
+        $query->delete($this->dbObjectPropertiesTable)
916
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
917
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)))
918
+            ->execute();
919
+
920
+        if ($calendarData) {
921
+            $this->dispatcher->dispatchTyped(new CalendarDeletedEvent((int)$calendarId, $calendarData, $shares));
922
+        }
923
+    }
924
+
925
+    /**
926
+     * Delete all of an user's shares
927
+     *
928
+     * @param string $principaluri
929
+     * @return void
930
+     */
931
+    public function deleteAllSharesByUser($principaluri) {
932
+        $this->calendarSharingBackend->deleteAllSharesByUser($principaluri);
933
+    }
934
+
935
+    /**
936
+     * Returns all calendar objects within a calendar.
937
+     *
938
+     * Every item contains an array with the following keys:
939
+     *   * calendardata - The iCalendar-compatible calendar data
940
+     *   * uri - a unique key which will be used to construct the uri. This can
941
+     *     be any arbitrary string, but making sure it ends with '.ics' is a
942
+     *     good idea. This is only the basename, or filename, not the full
943
+     *     path.
944
+     *   * lastmodified - a timestamp of the last modification time
945
+     *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
946
+     *   '"abcdef"')
947
+     *   * size - The size of the calendar objects, in bytes.
948
+     *   * component - optional, a string containing the type of object, such
949
+     *     as 'vevent' or 'vtodo'. If specified, this will be used to populate
950
+     *     the Content-Type header.
951
+     *
952
+     * Note that the etag is optional, but it's highly encouraged to return for
953
+     * speed reasons.
954
+     *
955
+     * The calendardata is also optional. If it's not returned
956
+     * 'getCalendarObject' will be called later, which *is* expected to return
957
+     * calendardata.
958
+     *
959
+     * If neither etag or size are specified, the calendardata will be
960
+     * used/fetched to determine these numbers. If both are specified the
961
+     * amount of times this is needed is reduced by a great degree.
962
+     *
963
+     * @param mixed $calendarId
964
+     * @param int $calendarType
965
+     * @return array
966
+     */
967
+    public function getCalendarObjects($calendarId, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
968
+        $query = $this->db->getQueryBuilder();
969
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'componenttype', 'classification'])
970
+            ->from('calendarobjects')
971
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
972
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
973
+        $stmt = $query->execute();
974
+
975
+        $result = [];
976
+        foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
977
+            $result[] = [
978
+                'id' => $row['id'],
979
+                'uri' => $row['uri'],
980
+                'lastmodified' => $row['lastmodified'],
981
+                'etag' => '"' . $row['etag'] . '"',
982
+                'calendarid' => $row['calendarid'],
983
+                'size' => (int)$row['size'],
984
+                'component' => strtolower($row['componenttype']),
985
+                'classification' => (int)$row['classification']
986
+            ];
987
+        }
988
+        $stmt->closeCursor();
989
+
990
+        return $result;
991
+    }
992
+
993
+    /**
994
+     * Returns information from a single calendar object, based on it's object
995
+     * uri.
996
+     *
997
+     * The object uri is only the basename, or filename and not a full path.
998
+     *
999
+     * The returned array must have the same keys as getCalendarObjects. The
1000
+     * 'calendardata' object is required here though, while it's not required
1001
+     * for getCalendarObjects.
1002
+     *
1003
+     * This method must return null if the object did not exist.
1004
+     *
1005
+     * @param mixed $calendarId
1006
+     * @param string $objectUri
1007
+     * @param int $calendarType
1008
+     * @return array|null
1009
+     */
1010
+    public function getCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1011
+        $query = $this->db->getQueryBuilder();
1012
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
1013
+            ->from('calendarobjects')
1014
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1015
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1016
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
1017
+        $stmt = $query->execute();
1018
+        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
1019
+        $stmt->closeCursor();
1020
+
1021
+        if (!$row) {
1022
+            return null;
1023
+        }
1024
+
1025
+        return [
1026
+            'id' => $row['id'],
1027
+            'uri' => $row['uri'],
1028
+            'lastmodified' => $row['lastmodified'],
1029
+            'etag' => '"' . $row['etag'] . '"',
1030
+            'calendarid' => $row['calendarid'],
1031
+            'size' => (int)$row['size'],
1032
+            'calendardata' => $this->readBlob($row['calendardata']),
1033
+            'component' => strtolower($row['componenttype']),
1034
+            'classification' => (int)$row['classification']
1035
+        ];
1036
+    }
1037
+
1038
+    /**
1039
+     * Returns a list of calendar objects.
1040
+     *
1041
+     * This method should work identical to getCalendarObject, but instead
1042
+     * return all the calendar objects in the list as an array.
1043
+     *
1044
+     * If the backend supports this, it may allow for some speed-ups.
1045
+     *
1046
+     * @param mixed $calendarId
1047
+     * @param string[] $uris
1048
+     * @param int $calendarType
1049
+     * @return array
1050
+     */
1051
+    public function getMultipleCalendarObjects($calendarId, array $uris, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
1052
+        if (empty($uris)) {
1053
+            return [];
1054
+        }
1055
+
1056
+        $chunks = array_chunk($uris, 100);
1057
+        $objects = [];
1058
+
1059
+        $query = $this->db->getQueryBuilder();
1060
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
1061
+            ->from('calendarobjects')
1062
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1063
+            ->andWhere($query->expr()->in('uri', $query->createParameter('uri')))
1064
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
1065
+
1066
+        foreach ($chunks as $uris) {
1067
+            $query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
1068
+            $result = $query->execute();
1069
+
1070
+            while ($row = $result->fetch()) {
1071
+                $objects[] = [
1072
+                    'id' => $row['id'],
1073
+                    'uri' => $row['uri'],
1074
+                    'lastmodified' => $row['lastmodified'],
1075
+                    'etag' => '"' . $row['etag'] . '"',
1076
+                    'calendarid' => $row['calendarid'],
1077
+                    'size' => (int)$row['size'],
1078
+                    'calendardata' => $this->readBlob($row['calendardata']),
1079
+                    'component' => strtolower($row['componenttype']),
1080
+                    'classification' => (int)$row['classification']
1081
+                ];
1082
+            }
1083
+            $result->closeCursor();
1084
+        }
1085
+
1086
+        return $objects;
1087
+    }
1088
+
1089
+    /**
1090
+     * Creates a new calendar object.
1091
+     *
1092
+     * The object uri is only the basename, or filename and not a full path.
1093
+     *
1094
+     * It is possible return an etag from this function, which will be used in
1095
+     * the response to this PUT request. Note that the ETag must be surrounded
1096
+     * by double-quotes.
1097
+     *
1098
+     * However, you should only really return this ETag if you don't mangle the
1099
+     * calendar-data. If the result of a subsequent GET to this object is not
1100
+     * the exact same as this request body, you should omit the ETag.
1101
+     *
1102
+     * @param mixed $calendarId
1103
+     * @param string $objectUri
1104
+     * @param string $calendarData
1105
+     * @param int $calendarType
1106
+     * @return string
1107
+     */
1108
+    public function createCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1109
+        $extraData = $this->getDenormalizedData($calendarData);
1110
+
1111
+        $q = $this->db->getQueryBuilder();
1112
+        $q->select($q->func()->count('*'))
1113
+            ->from('calendarobjects')
1114
+            ->where($q->expr()->eq('calendarid', $q->createNamedParameter($calendarId)))
1115
+            ->andWhere($q->expr()->eq('uid', $q->createNamedParameter($extraData['uid'])))
1116
+            ->andWhere($q->expr()->eq('calendartype', $q->createNamedParameter($calendarType)));
1117
+
1118
+        $result = $q->execute();
1119
+        $count = (int) $result->fetchColumn();
1120
+        $result->closeCursor();
1121
+
1122
+        if ($count !== 0) {
1123
+            throw new \Sabre\DAV\Exception\BadRequest('Calendar object with uid already exists in this calendar collection.');
1124
+        }
1125
+
1126
+        $query = $this->db->getQueryBuilder();
1127
+        $query->insert('calendarobjects')
1128
+            ->values([
1129
+                'calendarid' => $query->createNamedParameter($calendarId),
1130
+                'uri' => $query->createNamedParameter($objectUri),
1131
+                'calendardata' => $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB),
1132
+                'lastmodified' => $query->createNamedParameter(time()),
1133
+                'etag' => $query->createNamedParameter($extraData['etag']),
1134
+                'size' => $query->createNamedParameter($extraData['size']),
1135
+                'componenttype' => $query->createNamedParameter($extraData['componentType']),
1136
+                'firstoccurence' => $query->createNamedParameter($extraData['firstOccurence']),
1137
+                'lastoccurence' => $query->createNamedParameter($extraData['lastOccurence']),
1138
+                'classification' => $query->createNamedParameter($extraData['classification']),
1139
+                'uid' => $query->createNamedParameter($extraData['uid']),
1140
+                'calendartype' => $query->createNamedParameter($calendarType),
1141
+            ])
1142
+            ->execute();
1143
+
1144
+        $this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType);
1145
+        $this->addChange($calendarId, $objectUri, 1, $calendarType);
1146
+
1147
+        $objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1148
+        if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1149
+            $calendarRow = $this->getCalendarById($calendarId);
1150
+            $shares = $this->getShares($calendarId);
1151
+
1152
+            $this->dispatcher->dispatchTyped(new CalendarObjectCreatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
1153
+            $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', new GenericEvent(
1154
+                '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject',
1155
+                [
1156
+                    'calendarId' => $calendarId,
1157
+                    'calendarData' => $calendarRow,
1158
+                    'shares' => $shares,
1159
+                    'objectData' => $objectRow,
1160
+                ]
1161
+            ));
1162
+        } else {
1163
+            $subscriptionRow = $this->getSubscriptionById($calendarId);
1164
+
1165
+            $this->dispatcher->dispatchTyped(new CachedCalendarObjectCreatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
1166
+            $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject', new GenericEvent(
1167
+                '\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject',
1168
+                [
1169
+                    'subscriptionId' => $calendarId,
1170
+                    'calendarData' => $subscriptionRow,
1171
+                    'shares' => [],
1172
+                    'objectData' => $objectRow,
1173
+                ]
1174
+            ));
1175
+        }
1176
+
1177
+        return '"' . $extraData['etag'] . '"';
1178
+    }
1179
+
1180
+    /**
1181
+     * Updates an existing calendarobject, based on it's uri.
1182
+     *
1183
+     * The object uri is only the basename, or filename and not a full path.
1184
+     *
1185
+     * It is possible return an etag from this function, which will be used in
1186
+     * the response to this PUT request. Note that the ETag must be surrounded
1187
+     * by double-quotes.
1188
+     *
1189
+     * However, you should only really return this ETag if you don't mangle the
1190
+     * calendar-data. If the result of a subsequent GET to this object is not
1191
+     * the exact same as this request body, you should omit the ETag.
1192
+     *
1193
+     * @param mixed $calendarId
1194
+     * @param string $objectUri
1195
+     * @param string $calendarData
1196
+     * @param int $calendarType
1197
+     * @return string
1198
+     */
1199
+    public function updateCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1200
+        $extraData = $this->getDenormalizedData($calendarData);
1201
+        $query = $this->db->getQueryBuilder();
1202
+        $query->update('calendarobjects')
1203
+                ->set('calendardata', $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB))
1204
+                ->set('lastmodified', $query->createNamedParameter(time()))
1205
+                ->set('etag', $query->createNamedParameter($extraData['etag']))
1206
+                ->set('size', $query->createNamedParameter($extraData['size']))
1207
+                ->set('componenttype', $query->createNamedParameter($extraData['componentType']))
1208
+                ->set('firstoccurence', $query->createNamedParameter($extraData['firstOccurence']))
1209
+                ->set('lastoccurence', $query->createNamedParameter($extraData['lastOccurence']))
1210
+                ->set('classification', $query->createNamedParameter($extraData['classification']))
1211
+                ->set('uid', $query->createNamedParameter($extraData['uid']))
1212
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1213
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1214
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)))
1215
+            ->execute();
1216
+
1217
+        $this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType);
1218
+        $this->addChange($calendarId, $objectUri, 2, $calendarType);
1219
+
1220
+        $objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1221
+        if (is_array($objectRow)) {
1222
+            if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1223
+                $calendarRow = $this->getCalendarById($calendarId);
1224
+                $shares = $this->getShares($calendarId);
1225
+
1226
+                $this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
1227
+                $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', new GenericEvent(
1228
+                    '\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject',
1229
+                    [
1230
+                        'calendarId' => $calendarId,
1231
+                        'calendarData' => $calendarRow,
1232
+                        'shares' => $shares,
1233
+                        'objectData' => $objectRow,
1234
+                    ]
1235
+                ));
1236
+            } else {
1237
+                $subscriptionRow = $this->getSubscriptionById($calendarId);
1238
+
1239
+                $this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
1240
+                $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject', new GenericEvent(
1241
+                    '\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject',
1242
+                    [
1243
+                        'subscriptionId' => $calendarId,
1244
+                        'calendarData' => $subscriptionRow,
1245
+                        'shares' => [],
1246
+                        'objectData' => $objectRow,
1247
+                    ]
1248
+                ));
1249
+            }
1250
+        }
1251
+
1252
+        return '"' . $extraData['etag'] . '"';
1253
+    }
1254
+
1255
+    /**
1256
+     * @param int $calendarObjectId
1257
+     * @param int $classification
1258
+     */
1259
+    public function setClassification($calendarObjectId, $classification) {
1260
+        if (!in_array($classification, [
1261
+            self::CLASSIFICATION_PUBLIC, self::CLASSIFICATION_PRIVATE, self::CLASSIFICATION_CONFIDENTIAL
1262
+        ])) {
1263
+            throw new \InvalidArgumentException();
1264
+        }
1265
+        $query = $this->db->getQueryBuilder();
1266
+        $query->update('calendarobjects')
1267
+            ->set('classification', $query->createNamedParameter($classification))
1268
+            ->where($query->expr()->eq('id', $query->createNamedParameter($calendarObjectId)))
1269
+            ->execute();
1270
+    }
1271
+
1272
+    /**
1273
+     * Deletes an existing calendar object.
1274
+     *
1275
+     * The object uri is only the basename, or filename and not a full path.
1276
+     *
1277
+     * @param mixed $calendarId
1278
+     * @param string $objectUri
1279
+     * @param int $calendarType
1280
+     * @return void
1281
+     */
1282
+    public function deleteCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1283
+        $data = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1284
+        if (is_array($data)) {
1285
+            if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1286
+                $calendarRow = $this->getCalendarById($calendarId);
1287
+                $shares = $this->getShares($calendarId);
1288
+
1289
+                $this->dispatcher->dispatchTyped(new CalendarObjectDeletedEvent((int)$calendarId, $calendarRow, $shares, $data));
1290
+                $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', new GenericEvent(
1291
+                    '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject',
1292
+                    [
1293
+                        'calendarId' => $calendarId,
1294
+                        'calendarData' => $calendarRow,
1295
+                        'shares' => $shares,
1296
+                        'objectData' => $data,
1297
+                    ]
1298
+                ));
1299
+            } else {
1300
+                $subscriptionRow = $this->getSubscriptionById($calendarId);
1301
+
1302
+                $this->dispatcher->dispatchTyped(new CachedCalendarObjectDeletedEvent((int)$calendarId, $subscriptionRow, [], $data));
1303
+                $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject', new GenericEvent(
1304
+                    '\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject',
1305
+                    [
1306
+                        'subscriptionId' => $calendarId,
1307
+                        'calendarData' => $subscriptionRow,
1308
+                        'shares' => [],
1309
+                        'objectData' => $data,
1310
+                    ]
1311
+                ));
1312
+            }
1313
+        }
1314
+
1315
+        $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ? AND `calendartype` = ?');
1316
+        $stmt->execute([$calendarId, $objectUri, $calendarType]);
1317
+
1318
+        if (is_array($data)) {
1319
+            $this->purgeProperties($calendarId, $data['id'], $calendarType);
1320
+        }
1321
+
1322
+        $this->addChange($calendarId, $objectUri, 3, $calendarType);
1323
+    }
1324
+
1325
+    /**
1326
+     * Performs a calendar-query on the contents of this calendar.
1327
+     *
1328
+     * The calendar-query is defined in RFC4791 : CalDAV. Using the
1329
+     * calendar-query it is possible for a client to request a specific set of
1330
+     * object, based on contents of iCalendar properties, date-ranges and
1331
+     * iCalendar component types (VTODO, VEVENT).
1332
+     *
1333
+     * This method should just return a list of (relative) urls that match this
1334
+     * query.
1335
+     *
1336
+     * The list of filters are specified as an array. The exact array is
1337
+     * documented by Sabre\CalDAV\CalendarQueryParser.
1338
+     *
1339
+     * Note that it is extremely likely that getCalendarObject for every path
1340
+     * returned from this method will be called almost immediately after. You
1341
+     * may want to anticipate this to speed up these requests.
1342
+     *
1343
+     * This method provides a default implementation, which parses *all* the
1344
+     * iCalendar objects in the specified calendar.
1345
+     *
1346
+     * This default may well be good enough for personal use, and calendars
1347
+     * that aren't very large. But if you anticipate high usage, big calendars
1348
+     * or high loads, you are strongly advised to optimize certain paths.
1349
+     *
1350
+     * The best way to do so is override this method and to optimize
1351
+     * specifically for 'common filters'.
1352
+     *
1353
+     * Requests that are extremely common are:
1354
+     *   * requests for just VEVENTS
1355
+     *   * requests for just VTODO
1356
+     *   * requests with a time-range-filter on either VEVENT or VTODO.
1357
+     *
1358
+     * ..and combinations of these requests. It may not be worth it to try to
1359
+     * handle every possible situation and just rely on the (relatively
1360
+     * easy to use) CalendarQueryValidator to handle the rest.
1361
+     *
1362
+     * Note that especially time-range-filters may be difficult to parse. A
1363
+     * time-range filter specified on a VEVENT must for instance also handle
1364
+     * recurrence rules correctly.
1365
+     * A good example of how to interprete all these filters can also simply
1366
+     * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
1367
+     * as possible, so it gives you a good idea on what type of stuff you need
1368
+     * to think of.
1369
+     *
1370
+     * @param mixed $calendarId
1371
+     * @param array $filters
1372
+     * @param int $calendarType
1373
+     * @return array
1374
+     */
1375
+    public function calendarQuery($calendarId, array $filters, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
1376
+        $componentType = null;
1377
+        $requirePostFilter = true;
1378
+        $timeRange = null;
1379
+
1380
+        // if no filters were specified, we don't need to filter after a query
1381
+        if (!$filters['prop-filters'] && !$filters['comp-filters']) {
1382
+            $requirePostFilter = false;
1383
+        }
1384
+
1385
+        // Figuring out if there's a component filter
1386
+        if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
1387
+            $componentType = $filters['comp-filters'][0]['name'];
1388
+
1389
+            // Checking if we need post-filters
1390
+            if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
1391
+                $requirePostFilter = false;
1392
+            }
1393
+            // There was a time-range filter
1394
+            if ($componentType === 'VEVENT' && isset($filters['comp-filters'][0]['time-range']) && is_array($filters['comp-filters'][0]['time-range'])) {
1395
+                $timeRange = $filters['comp-filters'][0]['time-range'];
1396
+
1397
+                // If start time OR the end time is not specified, we can do a
1398
+                // 100% accurate mysql query.
1399
+                if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
1400
+                    $requirePostFilter = false;
1401
+                }
1402
+            }
1403
+        }
1404
+        $columns = ['uri'];
1405
+        if ($requirePostFilter) {
1406
+            $columns = ['uri', 'calendardata'];
1407
+        }
1408
+        $query = $this->db->getQueryBuilder();
1409
+        $query->select($columns)
1410
+            ->from('calendarobjects')
1411
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1412
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
1413
+
1414
+        if ($componentType) {
1415
+            $query->andWhere($query->expr()->eq('componenttype', $query->createNamedParameter($componentType)));
1416
+        }
1417
+
1418
+        if ($timeRange && $timeRange['start']) {
1419
+            $query->andWhere($query->expr()->gt('lastoccurence', $query->createNamedParameter($timeRange['start']->getTimeStamp())));
1420
+        }
1421
+        if ($timeRange && $timeRange['end']) {
1422
+            $query->andWhere($query->expr()->lt('firstoccurence', $query->createNamedParameter($timeRange['end']->getTimeStamp())));
1423
+        }
1424
+
1425
+        $stmt = $query->execute();
1426
+
1427
+        $result = [];
1428
+        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1429
+            if ($requirePostFilter) {
1430
+                // validateFilterForObject will parse the calendar data
1431
+                // catch parsing errors
1432
+                try {
1433
+                    $matches = $this->validateFilterForObject($row, $filters);
1434
+                } catch (ParseException $ex) {
1435
+                    $this->logger->logException($ex, [
1436
+                        'app' => 'dav',
1437
+                        'message' => 'Caught parsing exception for calendar data. This usually indicates invalid calendar data. calendar-id:'.$calendarId.' uri:'.$row['uri']
1438
+                    ]);
1439
+                    continue;
1440
+                } catch (InvalidDataException $ex) {
1441
+                    $this->logger->logException($ex, [
1442
+                        'app' => 'dav',
1443
+                        'message' => 'Caught invalid data exception for calendar data. This usually indicates invalid calendar data. calendar-id:'.$calendarId.' uri:'.$row['uri']
1444
+                    ]);
1445
+                    continue;
1446
+                }
1447
+
1448
+                if (!$matches) {
1449
+                    continue;
1450
+                }
1451
+            }
1452
+            $result[] = $row['uri'];
1453
+        }
1454
+
1455
+        return $result;
1456
+    }
1457
+
1458
+    /**
1459
+     * custom Nextcloud search extension for CalDAV
1460
+     *
1461
+     * TODO - this should optionally cover cached calendar objects as well
1462
+     *
1463
+     * @param string $principalUri
1464
+     * @param array $filters
1465
+     * @param integer|null $limit
1466
+     * @param integer|null $offset
1467
+     * @return array
1468
+     */
1469
+    public function calendarSearch($principalUri, array $filters, $limit = null, $offset = null) {
1470
+        $calendars = $this->getCalendarsForUser($principalUri);
1471
+        $ownCalendars = [];
1472
+        $sharedCalendars = [];
1473
+
1474
+        $uriMapper = [];
1475
+
1476
+        foreach ($calendars as $calendar) {
1477
+            if ($calendar['{http://owncloud.org/ns}owner-principal'] === $principalUri) {
1478
+                $ownCalendars[] = $calendar['id'];
1479
+            } else {
1480
+                $sharedCalendars[] = $calendar['id'];
1481
+            }
1482
+            $uriMapper[$calendar['id']] = $calendar['uri'];
1483
+        }
1484
+        if (count($ownCalendars) === 0 && count($sharedCalendars) === 0) {
1485
+            return [];
1486
+        }
1487
+
1488
+        $query = $this->db->getQueryBuilder();
1489
+        // Calendar id expressions
1490
+        $calendarExpressions = [];
1491
+        foreach ($ownCalendars as $id) {
1492
+            $calendarExpressions[] = $query->expr()->andX(
1493
+                $query->expr()->eq('c.calendarid',
1494
+                    $query->createNamedParameter($id)),
1495
+                $query->expr()->eq('c.calendartype',
1496
+                        $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1497
+        }
1498
+        foreach ($sharedCalendars as $id) {
1499
+            $calendarExpressions[] = $query->expr()->andX(
1500
+                $query->expr()->eq('c.calendarid',
1501
+                    $query->createNamedParameter($id)),
1502
+                $query->expr()->eq('c.classification',
1503
+                    $query->createNamedParameter(self::CLASSIFICATION_PUBLIC)),
1504
+                $query->expr()->eq('c.calendartype',
1505
+                    $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1506
+        }
1507
+
1508
+        if (count($calendarExpressions) === 1) {
1509
+            $calExpr = $calendarExpressions[0];
1510
+        } else {
1511
+            $calExpr = call_user_func_array([$query->expr(), 'orX'], $calendarExpressions);
1512
+        }
1513
+
1514
+        // Component expressions
1515
+        $compExpressions = [];
1516
+        foreach ($filters['comps'] as $comp) {
1517
+            $compExpressions[] = $query->expr()
1518
+                ->eq('c.componenttype', $query->createNamedParameter($comp));
1519
+        }
1520
+
1521
+        if (count($compExpressions) === 1) {
1522
+            $compExpr = $compExpressions[0];
1523
+        } else {
1524
+            $compExpr = call_user_func_array([$query->expr(), 'orX'], $compExpressions);
1525
+        }
1526
+
1527
+        if (!isset($filters['props'])) {
1528
+            $filters['props'] = [];
1529
+        }
1530
+        if (!isset($filters['params'])) {
1531
+            $filters['params'] = [];
1532
+        }
1533
+
1534
+        $propParamExpressions = [];
1535
+        foreach ($filters['props'] as $prop) {
1536
+            $propParamExpressions[] = $query->expr()->andX(
1537
+                $query->expr()->eq('i.name', $query->createNamedParameter($prop)),
1538
+                $query->expr()->isNull('i.parameter')
1539
+            );
1540
+        }
1541
+        foreach ($filters['params'] as $param) {
1542
+            $propParamExpressions[] = $query->expr()->andX(
1543
+                $query->expr()->eq('i.name', $query->createNamedParameter($param['property'])),
1544
+                $query->expr()->eq('i.parameter', $query->createNamedParameter($param['parameter']))
1545
+            );
1546
+        }
1547
+
1548
+        if (count($propParamExpressions) === 1) {
1549
+            $propParamExpr = $propParamExpressions[0];
1550
+        } else {
1551
+            $propParamExpr = call_user_func_array([$query->expr(), 'orX'], $propParamExpressions);
1552
+        }
1553
+
1554
+        $query->select(['c.calendarid', 'c.uri'])
1555
+            ->from($this->dbObjectPropertiesTable, 'i')
1556
+            ->join('i', 'calendarobjects', 'c', $query->expr()->eq('i.objectid', 'c.id'))
1557
+            ->where($calExpr)
1558
+            ->andWhere($compExpr)
1559
+            ->andWhere($propParamExpr)
1560
+            ->andWhere($query->expr()->iLike('i.value',
1561
+                $query->createNamedParameter('%'.$this->db->escapeLikeParameter($filters['search-term']).'%')));
1562
+
1563
+        if ($offset) {
1564
+            $query->setFirstResult($offset);
1565
+        }
1566
+        if ($limit) {
1567
+            $query->setMaxResults($limit);
1568
+        }
1569
+
1570
+        $stmt = $query->execute();
1571
+
1572
+        $result = [];
1573
+        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1574
+            $path = $uriMapper[$row['calendarid']] . '/' . $row['uri'];
1575
+            if (!in_array($path, $result)) {
1576
+                $result[] = $path;
1577
+            }
1578
+        }
1579
+
1580
+        return $result;
1581
+    }
1582
+
1583
+    /**
1584
+     * used for Nextcloud's calendar API
1585
+     *
1586
+     * @param array $calendarInfo
1587
+     * @param string $pattern
1588
+     * @param array $searchProperties
1589
+     * @param array $options
1590
+     * @param integer|null $limit
1591
+     * @param integer|null $offset
1592
+     *
1593
+     * @return array
1594
+     */
1595
+    public function search(array $calendarInfo, $pattern, array $searchProperties,
1596
+                            array $options, $limit, $offset) {
1597
+        $outerQuery = $this->db->getQueryBuilder();
1598
+        $innerQuery = $this->db->getQueryBuilder();
1599
+
1600
+        $innerQuery->selectDistinct('op.objectid')
1601
+            ->from($this->dbObjectPropertiesTable, 'op')
1602
+            ->andWhere($innerQuery->expr()->eq('op.calendarid',
1603
+                $outerQuery->createNamedParameter($calendarInfo['id'])))
1604
+            ->andWhere($innerQuery->expr()->eq('op.calendartype',
1605
+                $outerQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1606
+
1607
+        // only return public items for shared calendars for now
1608
+        if ($calendarInfo['principaluri'] !== $calendarInfo['{http://owncloud.org/ns}owner-principal']) {
1609
+            $innerQuery->andWhere($innerQuery->expr()->eq('c.classification',
1610
+                $outerQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1611
+        }
1612
+
1613
+        $or = $innerQuery->expr()->orX();
1614
+        foreach ($searchProperties as $searchProperty) {
1615
+            $or->add($innerQuery->expr()->eq('op.name',
1616
+                $outerQuery->createNamedParameter($searchProperty)));
1617
+        }
1618
+        $innerQuery->andWhere($or);
1619
+
1620
+        if ($pattern !== '') {
1621
+            $innerQuery->andWhere($innerQuery->expr()->iLike('op.value',
1622
+                $outerQuery->createNamedParameter('%' .
1623
+                    $this->db->escapeLikeParameter($pattern) . '%')));
1624
+        }
1625
+
1626
+        $outerQuery->select('c.id', 'c.calendardata', 'c.componenttype', 'c.uid', 'c.uri')
1627
+            ->from('calendarobjects', 'c');
1628
+
1629
+        if (isset($options['timerange'])) {
1630
+            if (isset($options['timerange']['start']) && $options['timerange']['start'] instanceof DateTime) {
1631
+                $outerQuery->andWhere($outerQuery->expr()->gt('lastoccurence',
1632
+                    $outerQuery->createNamedParameter($options['timerange']['start']->getTimeStamp())));
1633
+            }
1634
+            if (isset($options['timerange']['end']) && $options['timerange']['end'] instanceof DateTime) {
1635
+                $outerQuery->andWhere($outerQuery->expr()->lt('firstoccurence',
1636
+                    $outerQuery->createNamedParameter($options['timerange']['end']->getTimeStamp())));
1637
+            }
1638
+        }
1639
+
1640
+        if (isset($options['types'])) {
1641
+            $or = $outerQuery->expr()->orX();
1642
+            foreach ($options['types'] as $type) {
1643
+                $or->add($outerQuery->expr()->eq('componenttype',
1644
+                    $outerQuery->createNamedParameter($type)));
1645
+            }
1646
+            $outerQuery->andWhere($or);
1647
+        }
1648
+
1649
+        $outerQuery->andWhere($outerQuery->expr()->in('c.id',
1650
+            $outerQuery->createFunction($innerQuery->getSQL())));
1651
+
1652
+        if ($offset) {
1653
+            $outerQuery->setFirstResult($offset);
1654
+        }
1655
+        if ($limit) {
1656
+            $outerQuery->setMaxResults($limit);
1657
+        }
1658
+
1659
+        $result = $outerQuery->execute();
1660
+        $calendarObjects = $result->fetchAll();
1661
+
1662
+        return array_map(function ($o) {
1663
+            $calendarData = Reader::read($o['calendardata']);
1664
+            $comps = $calendarData->getComponents();
1665
+            $objects = [];
1666
+            $timezones = [];
1667
+            foreach ($comps as $comp) {
1668
+                if ($comp instanceof VTimeZone) {
1669
+                    $timezones[] = $comp;
1670
+                } else {
1671
+                    $objects[] = $comp;
1672
+                }
1673
+            }
1674
+
1675
+            return [
1676
+                'id' => $o['id'],
1677
+                'type' => $o['componenttype'],
1678
+                'uid' => $o['uid'],
1679
+                'uri' => $o['uri'],
1680
+                'objects' => array_map(function ($c) {
1681
+                    return $this->transformSearchData($c);
1682
+                }, $objects),
1683
+                'timezones' => array_map(function ($c) {
1684
+                    return $this->transformSearchData($c);
1685
+                }, $timezones),
1686
+            ];
1687
+        }, $calendarObjects);
1688
+    }
1689
+
1690
+    /**
1691
+     * @param Component $comp
1692
+     * @return array
1693
+     */
1694
+    private function transformSearchData(Component $comp) {
1695
+        $data = [];
1696
+        /** @var Component[] $subComponents */
1697
+        $subComponents = $comp->getComponents();
1698
+        /** @var Property[] $properties */
1699
+        $properties = array_filter($comp->children(), function ($c) {
1700
+            return $c instanceof Property;
1701
+        });
1702
+        $validationRules = $comp->getValidationRules();
1703
+
1704
+        foreach ($subComponents as $subComponent) {
1705
+            $name = $subComponent->name;
1706
+            if (!isset($data[$name])) {
1707
+                $data[$name] = [];
1708
+            }
1709
+            $data[$name][] = $this->transformSearchData($subComponent);
1710
+        }
1711
+
1712
+        foreach ($properties as $property) {
1713
+            $name = $property->name;
1714
+            if (!isset($validationRules[$name])) {
1715
+                $validationRules[$name] = '*';
1716
+            }
1717
+
1718
+            $rule = $validationRules[$property->name];
1719
+            if ($rule === '+' || $rule === '*') { // multiple
1720
+                if (!isset($data[$name])) {
1721
+                    $data[$name] = [];
1722
+                }
1723
+
1724
+                $data[$name][] = $this->transformSearchProperty($property);
1725
+            } else { // once
1726
+                $data[$name] = $this->transformSearchProperty($property);
1727
+            }
1728
+        }
1729
+
1730
+        return $data;
1731
+    }
1732
+
1733
+    /**
1734
+     * @param Property $prop
1735
+     * @return array
1736
+     */
1737
+    private function transformSearchProperty(Property $prop) {
1738
+        // No need to check Date, as it extends DateTime
1739
+        if ($prop instanceof Property\ICalendar\DateTime) {
1740
+            $value = $prop->getDateTime();
1741
+        } else {
1742
+            $value = $prop->getValue();
1743
+        }
1744
+
1745
+        return [
1746
+            $value,
1747
+            $prop->parameters()
1748
+        ];
1749
+    }
1750
+
1751
+    /**
1752
+     * @param string $principalUri
1753
+     * @param string $pattern
1754
+     * @param array $componentTypes
1755
+     * @param array $searchProperties
1756
+     * @param array $searchParameters
1757
+     * @param array $options
1758
+     * @return array
1759
+     */
1760
+    public function searchPrincipalUri(string $principalUri,
1761
+                                        string $pattern,
1762
+                                        array $componentTypes,
1763
+                                        array $searchProperties,
1764
+                                        array $searchParameters,
1765
+                                        array $options = []): array {
1766
+        $escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false;
1767
+
1768
+        $calendarObjectIdQuery = $this->db->getQueryBuilder();
1769
+        $calendarOr = $calendarObjectIdQuery->expr()->orX();
1770
+        $searchOr = $calendarObjectIdQuery->expr()->orX();
1771
+
1772
+        // Fetch calendars and subscription
1773
+        $calendars = $this->getCalendarsForUser($principalUri);
1774
+        $subscriptions = $this->getSubscriptionsForUser($principalUri);
1775
+        foreach ($calendars as $calendar) {
1776
+            $calendarAnd = $calendarObjectIdQuery->expr()->andX();
1777
+            $calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$calendar['id'])));
1778
+            $calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1779
+
1780
+            // If it's shared, limit search to public events
1781
+            if ($calendar['principaluri'] !== $calendar['{http://owncloud.org/ns}owner-principal']) {
1782
+                $calendarAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1783
+            }
1784
+
1785
+            $calendarOr->add($calendarAnd);
1786
+        }
1787
+        foreach ($subscriptions as $subscription) {
1788
+            $subscriptionAnd = $calendarObjectIdQuery->expr()->andX();
1789
+            $subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$subscription['id'])));
1790
+            $subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
1791
+
1792
+            // If it's shared, limit search to public events
1793
+            if ($subscription['principaluri'] !== $subscription['{http://owncloud.org/ns}owner-principal']) {
1794
+                $subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1795
+            }
1796
+
1797
+            $calendarOr->add($subscriptionAnd);
1798
+        }
1799
+
1800
+        foreach ($searchProperties as $property) {
1801
+            $propertyAnd = $calendarObjectIdQuery->expr()->andX();
1802
+            $propertyAnd->add($calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)));
1803
+            $propertyAnd->add($calendarObjectIdQuery->expr()->isNull('cob.parameter'));
1804
+
1805
+            $searchOr->add($propertyAnd);
1806
+        }
1807
+        foreach ($searchParameters as $property => $parameter) {
1808
+            $parameterAnd = $calendarObjectIdQuery->expr()->andX();
1809
+            $parameterAnd->add($calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)));
1810
+            $parameterAnd->add($calendarObjectIdQuery->expr()->eq('cob.parameter', $calendarObjectIdQuery->createNamedParameter($parameter, IQueryBuilder::PARAM_STR_ARRAY)));
1811
+
1812
+            $searchOr->add($parameterAnd);
1813
+        }
1814
+
1815
+        if ($calendarOr->count() === 0) {
1816
+            return [];
1817
+        }
1818
+        if ($searchOr->count() === 0) {
1819
+            return [];
1820
+        }
1821
+
1822
+        $calendarObjectIdQuery->selectDistinct('cob.objectid')
1823
+            ->from($this->dbObjectPropertiesTable, 'cob')
1824
+            ->leftJoin('cob', 'calendarobjects', 'co', $calendarObjectIdQuery->expr()->eq('co.id', 'cob.objectid'))
1825
+            ->andWhere($calendarObjectIdQuery->expr()->in('co.componenttype', $calendarObjectIdQuery->createNamedParameter($componentTypes, IQueryBuilder::PARAM_STR_ARRAY)))
1826
+            ->andWhere($calendarOr)
1827
+            ->andWhere($searchOr);
1828
+
1829
+        if ('' !== $pattern) {
1830
+            if (!$escapePattern) {
1831
+                $calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter($pattern)));
1832
+            } else {
1833
+                $calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
1834
+            }
1835
+        }
1836
+
1837
+        if (isset($options['limit'])) {
1838
+            $calendarObjectIdQuery->setMaxResults($options['limit']);
1839
+        }
1840
+        if (isset($options['offset'])) {
1841
+            $calendarObjectIdQuery->setFirstResult($options['offset']);
1842
+        }
1843
+
1844
+        $result = $calendarObjectIdQuery->execute();
1845
+        $matches = $result->fetchAll();
1846
+        $result->closeCursor();
1847
+        $matches = array_map(static function (array $match):int {
1848
+            return (int) $match['objectid'];
1849
+        }, $matches);
1850
+
1851
+        $query = $this->db->getQueryBuilder();
1852
+        $query->select('calendardata', 'uri', 'calendarid', 'calendartype')
1853
+            ->from('calendarobjects')
1854
+            ->where($query->expr()->in('id', $query->createNamedParameter($matches, IQueryBuilder::PARAM_INT_ARRAY)));
1855
+
1856
+        $result = $query->execute();
1857
+        $calendarObjects = $result->fetchAll();
1858
+        $result->closeCursor();
1859
+
1860
+        return array_map(function (array $array): array {
1861
+            $array['calendarid'] = (int)$array['calendarid'];
1862
+            $array['calendartype'] = (int)$array['calendartype'];
1863
+            $array['calendardata'] = $this->readBlob($array['calendardata']);
1864
+
1865
+            return $array;
1866
+        }, $calendarObjects);
1867
+    }
1868
+
1869
+    /**
1870
+     * Searches through all of a users calendars and calendar objects to find
1871
+     * an object with a specific UID.
1872
+     *
1873
+     * This method should return the path to this object, relative to the
1874
+     * calendar home, so this path usually only contains two parts:
1875
+     *
1876
+     * calendarpath/objectpath.ics
1877
+     *
1878
+     * If the uid is not found, return null.
1879
+     *
1880
+     * This method should only consider * objects that the principal owns, so
1881
+     * any calendars owned by other principals that also appear in this
1882
+     * collection should be ignored.
1883
+     *
1884
+     * @param string $principalUri
1885
+     * @param string $uid
1886
+     * @return string|null
1887
+     */
1888
+    public function getCalendarObjectByUID($principalUri, $uid) {
1889
+        $query = $this->db->getQueryBuilder();
1890
+        $query->selectAlias('c.uri', 'calendaruri')->selectAlias('co.uri', 'objecturi')
1891
+            ->from('calendarobjects', 'co')
1892
+            ->leftJoin('co', 'calendars', 'c', $query->expr()->eq('co.calendarid', 'c.id'))
1893
+            ->where($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri)))
1894
+            ->andWhere($query->expr()->eq('co.uid', $query->createNamedParameter($uid)));
1895
+
1896
+        $stmt = $query->execute();
1897
+
1898
+        if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1899
+            return $row['calendaruri'] . '/' . $row['objecturi'];
1900
+        }
1901
+
1902
+        return null;
1903
+    }
1904
+
1905
+    /**
1906
+     * The getChanges method returns all the changes that have happened, since
1907
+     * the specified syncToken in the specified calendar.
1908
+     *
1909
+     * This function should return an array, such as the following:
1910
+     *
1911
+     * [
1912
+     *   'syncToken' => 'The current synctoken',
1913
+     *   'added'   => [
1914
+     *      'new.txt',
1915
+     *   ],
1916
+     *   'modified'   => [
1917
+     *      'modified.txt',
1918
+     *   ],
1919
+     *   'deleted' => [
1920
+     *      'foo.php.bak',
1921
+     *      'old.txt'
1922
+     *   ]
1923
+     * );
1924
+     *
1925
+     * The returned syncToken property should reflect the *current* syncToken
1926
+     * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
1927
+     * property This is * needed here too, to ensure the operation is atomic.
1928
+     *
1929
+     * If the $syncToken argument is specified as null, this is an initial
1930
+     * sync, and all members should be reported.
1931
+     *
1932
+     * The modified property is an array of nodenames that have changed since
1933
+     * the last token.
1934
+     *
1935
+     * The deleted property is an array with nodenames, that have been deleted
1936
+     * from collection.
1937
+     *
1938
+     * The $syncLevel argument is basically the 'depth' of the report. If it's
1939
+     * 1, you only have to report changes that happened only directly in
1940
+     * immediate descendants. If it's 2, it should also include changes from
1941
+     * the nodes below the child collections. (grandchildren)
1942
+     *
1943
+     * The $limit argument allows a client to specify how many results should
1944
+     * be returned at most. If the limit is not specified, it should be treated
1945
+     * as infinite.
1946
+     *
1947
+     * If the limit (infinite or not) is higher than you're willing to return,
1948
+     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
1949
+     *
1950
+     * If the syncToken is expired (due to data cleanup) or unknown, you must
1951
+     * return null.
1952
+     *
1953
+     * The limit is 'suggestive'. You are free to ignore it.
1954
+     *
1955
+     * @param string $calendarId
1956
+     * @param string $syncToken
1957
+     * @param int $syncLevel
1958
+     * @param int $limit
1959
+     * @param int $calendarType
1960
+     * @return array
1961
+     */
1962
+    public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1963
+        // Current synctoken
1964
+        $stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*calendars` WHERE `id` = ?');
1965
+        $stmt->execute([ $calendarId ]);
1966
+        $currentToken = $stmt->fetchColumn(0);
1967
+
1968
+        if (is_null($currentToken)) {
1969
+            return null;
1970
+        }
1971
+
1972
+        $result = [
1973
+            'syncToken' => $currentToken,
1974
+            'added' => [],
1975
+            'modified' => [],
1976
+            'deleted' => [],
1977
+        ];
1978
+
1979
+        if ($syncToken) {
1980
+            $query = "SELECT `uri`, `operation` FROM `*PREFIX*calendarchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `calendarid` = ? AND `calendartype` = ? ORDER BY `synctoken`";
1981
+            if ($limit > 0) {
1982
+                $query .= " LIMIT " . (int)$limit;
1983
+            }
1984
+
1985
+            // Fetching all changes
1986
+            $stmt = $this->db->prepare($query);
1987
+            $stmt->execute([$syncToken, $currentToken, $calendarId, $calendarType]);
1988
+
1989
+            $changes = [];
1990
+
1991
+            // This loop ensures that any duplicates are overwritten, only the
1992
+            // last change on a node is relevant.
1993
+            while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1994
+                $changes[$row['uri']] = $row['operation'];
1995
+            }
1996
+
1997
+            foreach ($changes as $uri => $operation) {
1998
+                switch ($operation) {
1999
+                    case 1:
2000
+                        $result['added'][] = $uri;
2001
+                        break;
2002
+                    case 2:
2003
+                        $result['modified'][] = $uri;
2004
+                        break;
2005
+                    case 3:
2006
+                        $result['deleted'][] = $uri;
2007
+                        break;
2008
+                }
2009
+            }
2010
+        } else {
2011
+            // No synctoken supplied, this is the initial sync.
2012
+            $query = "SELECT `uri` FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `calendartype` = ?";
2013
+            $stmt = $this->db->prepare($query);
2014
+            $stmt->execute([$calendarId, $calendarType]);
2015
+
2016
+            $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
2017
+        }
2018
+        return $result;
2019
+    }
2020
+
2021
+    /**
2022
+     * Returns a list of subscriptions for a principal.
2023
+     *
2024
+     * Every subscription is an array with the following keys:
2025
+     *  * id, a unique id that will be used by other functions to modify the
2026
+     *    subscription. This can be the same as the uri or a database key.
2027
+     *  * uri. This is just the 'base uri' or 'filename' of the subscription.
2028
+     *  * principaluri. The owner of the subscription. Almost always the same as
2029
+     *    principalUri passed to this method.
2030
+     *
2031
+     * Furthermore, all the subscription info must be returned too:
2032
+     *
2033
+     * 1. {DAV:}displayname
2034
+     * 2. {http://apple.com/ns/ical/}refreshrate
2035
+     * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
2036
+     *    should not be stripped).
2037
+     * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
2038
+     *    should not be stripped).
2039
+     * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
2040
+     *    attachments should not be stripped).
2041
+     * 6. {http://calendarserver.org/ns/}source (Must be a
2042
+     *     Sabre\DAV\Property\Href).
2043
+     * 7. {http://apple.com/ns/ical/}calendar-color
2044
+     * 8. {http://apple.com/ns/ical/}calendar-order
2045
+     * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
2046
+     *    (should just be an instance of
2047
+     *    Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
2048
+     *    default components).
2049
+     *
2050
+     * @param string $principalUri
2051
+     * @return array
2052
+     */
2053
+    public function getSubscriptionsForUser($principalUri) {
2054
+        $fields = array_values($this->subscriptionPropertyMap);
2055
+        $fields[] = 'id';
2056
+        $fields[] = 'uri';
2057
+        $fields[] = 'source';
2058
+        $fields[] = 'principaluri';
2059
+        $fields[] = 'lastmodified';
2060
+        $fields[] = 'synctoken';
2061
+
2062
+        $query = $this->db->getQueryBuilder();
2063
+        $query->select($fields)
2064
+            ->from('calendarsubscriptions')
2065
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2066
+            ->orderBy('calendarorder', 'asc');
2067
+        $stmt = $query->execute();
2068
+
2069
+        $subscriptions = [];
2070
+        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
2071
+            $subscription = [
2072
+                'id' => $row['id'],
2073
+                'uri' => $row['uri'],
2074
+                'principaluri' => $row['principaluri'],
2075
+                'source' => $row['source'],
2076
+                'lastmodified' => $row['lastmodified'],
2077
+
2078
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
2079
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
2080
+            ];
2081
+
2082
+            foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
2083
+                if (!is_null($row[$dbName])) {
2084
+                    $subscription[$xmlName] = $row[$dbName];
2085
+                }
2086
+            }
2087
+
2088
+            $subscriptions[] = $subscription;
2089
+        }
2090
+
2091
+        return $subscriptions;
2092
+    }
2093
+
2094
+    /**
2095
+     * Creates a new subscription for a principal.
2096
+     *
2097
+     * If the creation was a success, an id must be returned that can be used to reference
2098
+     * this subscription in other methods, such as updateSubscription.
2099
+     *
2100
+     * @param string $principalUri
2101
+     * @param string $uri
2102
+     * @param array $properties
2103
+     * @return mixed
2104
+     */
2105
+    public function createSubscription($principalUri, $uri, array $properties) {
2106
+        if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
2107
+            throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
2108
+        }
2109
+
2110
+        $values = [
2111
+            'principaluri' => $principalUri,
2112
+            'uri' => $uri,
2113
+            'source' => $properties['{http://calendarserver.org/ns/}source']->getHref(),
2114
+            'lastmodified' => time(),
2115
+        ];
2116
+
2117
+        $propertiesBoolean = ['striptodos', 'stripalarms', 'stripattachments'];
2118
+
2119
+        foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
2120
+            if (array_key_exists($xmlName, $properties)) {
2121
+                $values[$dbName] = $properties[$xmlName];
2122
+                if (in_array($dbName, $propertiesBoolean)) {
2123
+                    $values[$dbName] = true;
2124
+                }
2125
+            }
2126
+        }
2127
+
2128
+        $valuesToInsert = [];
2129
+
2130
+        $query = $this->db->getQueryBuilder();
2131
+
2132
+        foreach (array_keys($values) as $name) {
2133
+            $valuesToInsert[$name] = $query->createNamedParameter($values[$name]);
2134
+        }
2135
+
2136
+        $query->insert('calendarsubscriptions')
2137
+            ->values($valuesToInsert)
2138
+            ->execute();
2139
+
2140
+        $subscriptionId = $this->db->lastInsertId('*PREFIX*calendarsubscriptions');
2141
+
2142
+        $subscriptionRow = $this->getSubscriptionById($subscriptionId);
2143
+        $this->dispatcher->dispatchTyped(new SubscriptionCreatedEvent((int)$subscriptionId, $subscriptionRow));
2144
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createSubscription', new GenericEvent(
2145
+            '\OCA\DAV\CalDAV\CalDavBackend::createSubscription',
2146
+            [
2147
+                'subscriptionId' => $subscriptionId,
2148
+                'subscriptionData' => $subscriptionRow,
2149
+            ]));
2150
+
2151
+        return $subscriptionId;
2152
+    }
2153
+
2154
+    /**
2155
+     * Updates a subscription
2156
+     *
2157
+     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
2158
+     * To do the actual updates, you must tell this object which properties
2159
+     * you're going to process with the handle() method.
2160
+     *
2161
+     * Calling the handle method is like telling the PropPatch object "I
2162
+     * promise I can handle updating this property".
2163
+     *
2164
+     * Read the PropPatch documentation for more info and examples.
2165
+     *
2166
+     * @param mixed $subscriptionId
2167
+     * @param PropPatch $propPatch
2168
+     * @return void
2169
+     */
2170
+    public function updateSubscription($subscriptionId, PropPatch $propPatch) {
2171
+        $supportedProperties = array_keys($this->subscriptionPropertyMap);
2172
+        $supportedProperties[] = '{http://calendarserver.org/ns/}source';
2173
+
2174
+        $propPatch->handle($supportedProperties, function ($mutations) use ($subscriptionId) {
2175
+            $newValues = [];
2176
+
2177
+            foreach ($mutations as $propertyName => $propertyValue) {
2178
+                if ($propertyName === '{http://calendarserver.org/ns/}source') {
2179
+                    $newValues['source'] = $propertyValue->getHref();
2180
+                } else {
2181
+                    $fieldName = $this->subscriptionPropertyMap[$propertyName];
2182
+                    $newValues[$fieldName] = $propertyValue;
2183
+                }
2184
+            }
2185
+
2186
+            $query = $this->db->getQueryBuilder();
2187
+            $query->update('calendarsubscriptions')
2188
+                ->set('lastmodified', $query->createNamedParameter(time()));
2189
+            foreach ($newValues as $fieldName => $value) {
2190
+                $query->set($fieldName, $query->createNamedParameter($value));
2191
+            }
2192
+            $query->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
2193
+                ->execute();
2194
+
2195
+            $subscriptionRow = $this->getSubscriptionById($subscriptionId);
2196
+            $this->dispatcher->dispatchTyped(new SubscriptionUpdatedEvent((int)$subscriptionId, $subscriptionRow, [], $mutations));
2197
+            $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateSubscription', new GenericEvent(
2198
+                '\OCA\DAV\CalDAV\CalDavBackend::updateSubscription',
2199
+                [
2200
+                    'subscriptionId' => $subscriptionId,
2201
+                    'subscriptionData' => $subscriptionRow,
2202
+                    'propertyMutations' => $mutations,
2203
+                ]));
2204
+
2205
+            return true;
2206
+        });
2207
+    }
2208
+
2209
+    /**
2210
+     * Deletes a subscription.
2211
+     *
2212
+     * @param mixed $subscriptionId
2213
+     * @return void
2214
+     */
2215
+    public function deleteSubscription($subscriptionId) {
2216
+        $subscriptionRow = $this->getSubscriptionById($subscriptionId);
2217
+
2218
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription', new GenericEvent(
2219
+            '\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription',
2220
+            [
2221
+                'subscriptionId' => $subscriptionId,
2222
+                'subscriptionData' => $this->getSubscriptionById($subscriptionId),
2223
+            ]));
2224
+
2225
+        $query = $this->db->getQueryBuilder();
2226
+        $query->delete('calendarsubscriptions')
2227
+            ->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
2228
+            ->execute();
2229
+
2230
+        $query = $this->db->getQueryBuilder();
2231
+        $query->delete('calendarobjects')
2232
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2233
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2234
+            ->execute();
2235
+
2236
+        $query->delete('calendarchanges')
2237
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2238
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2239
+            ->execute();
2240
+
2241
+        $query->delete($this->dbObjectPropertiesTable)
2242
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2243
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2244
+            ->execute();
2245
+
2246
+        if ($subscriptionRow) {
2247
+            $this->dispatcher->dispatchTyped(new SubscriptionDeletedEvent((int)$subscriptionId, $subscriptionRow, []));
2248
+        }
2249
+    }
2250
+
2251
+    /**
2252
+     * Returns a single scheduling object for the inbox collection.
2253
+     *
2254
+     * The returned array should contain the following elements:
2255
+     *   * uri - A unique basename for the object. This will be used to
2256
+     *           construct a full uri.
2257
+     *   * calendardata - The iCalendar object
2258
+     *   * lastmodified - The last modification date. Can be an int for a unix
2259
+     *                    timestamp, or a PHP DateTime object.
2260
+     *   * etag - A unique token that must change if the object changed.
2261
+     *   * size - The size of the object, in bytes.
2262
+     *
2263
+     * @param string $principalUri
2264
+     * @param string $objectUri
2265
+     * @return array
2266
+     */
2267
+    public function getSchedulingObject($principalUri, $objectUri) {
2268
+        $query = $this->db->getQueryBuilder();
2269
+        $stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
2270
+            ->from('schedulingobjects')
2271
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2272
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
2273
+            ->execute();
2274
+
2275
+        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
2276
+
2277
+        if (!$row) {
2278
+            return null;
2279
+        }
2280
+
2281
+        return [
2282
+            'uri' => $row['uri'],
2283
+            'calendardata' => $row['calendardata'],
2284
+            'lastmodified' => $row['lastmodified'],
2285
+            'etag' => '"' . $row['etag'] . '"',
2286
+            'size' => (int)$row['size'],
2287
+        ];
2288
+    }
2289
+
2290
+    /**
2291
+     * Returns all scheduling objects for the inbox collection.
2292
+     *
2293
+     * These objects should be returned as an array. Every item in the array
2294
+     * should follow the same structure as returned from getSchedulingObject.
2295
+     *
2296
+     * The main difference is that 'calendardata' is optional.
2297
+     *
2298
+     * @param string $principalUri
2299
+     * @return array
2300
+     */
2301
+    public function getSchedulingObjects($principalUri) {
2302
+        $query = $this->db->getQueryBuilder();
2303
+        $stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
2304
+                ->from('schedulingobjects')
2305
+                ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2306
+                ->execute();
2307
+
2308
+        $result = [];
2309
+        foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
2310
+            $result[] = [
2311
+                'calendardata' => $row['calendardata'],
2312
+                'uri' => $row['uri'],
2313
+                'lastmodified' => $row['lastmodified'],
2314
+                'etag' => '"' . $row['etag'] . '"',
2315
+                'size' => (int)$row['size'],
2316
+            ];
2317
+        }
2318
+
2319
+        return $result;
2320
+    }
2321
+
2322
+    /**
2323
+     * Deletes a scheduling object from the inbox collection.
2324
+     *
2325
+     * @param string $principalUri
2326
+     * @param string $objectUri
2327
+     * @return void
2328
+     */
2329
+    public function deleteSchedulingObject($principalUri, $objectUri) {
2330
+        $query = $this->db->getQueryBuilder();
2331
+        $query->delete('schedulingobjects')
2332
+                ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2333
+                ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
2334
+                ->execute();
2335
+    }
2336
+
2337
+    /**
2338
+     * Creates a new scheduling object. This should land in a users' inbox.
2339
+     *
2340
+     * @param string $principalUri
2341
+     * @param string $objectUri
2342
+     * @param string $objectData
2343
+     * @return void
2344
+     */
2345
+    public function createSchedulingObject($principalUri, $objectUri, $objectData) {
2346
+        $query = $this->db->getQueryBuilder();
2347
+        $query->insert('schedulingobjects')
2348
+            ->values([
2349
+                'principaluri' => $query->createNamedParameter($principalUri),
2350
+                'calendardata' => $query->createNamedParameter($objectData, IQueryBuilder::PARAM_LOB),
2351
+                'uri' => $query->createNamedParameter($objectUri),
2352
+                'lastmodified' => $query->createNamedParameter(time()),
2353
+                'etag' => $query->createNamedParameter(md5($objectData)),
2354
+                'size' => $query->createNamedParameter(strlen($objectData))
2355
+            ])
2356
+            ->execute();
2357
+    }
2358
+
2359
+    /**
2360
+     * Adds a change record to the calendarchanges table.
2361
+     *
2362
+     * @param mixed $calendarId
2363
+     * @param string $objectUri
2364
+     * @param int $operation 1 = add, 2 = modify, 3 = delete.
2365
+     * @param int $calendarType
2366
+     * @return void
2367
+     */
2368
+    protected function addChange($calendarId, $objectUri, $operation, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2369
+        $table = $calendarType === self::CALENDAR_TYPE_CALENDAR ? 'calendars': 'calendarsubscriptions';
2370
+
2371
+        $query = $this->db->getQueryBuilder();
2372
+        $query->select('synctoken')
2373
+            ->from($table)
2374
+            ->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
2375
+        $result = $query->execute();
2376
+        $syncToken = (int)$result->fetchColumn();
2377
+        $result->closeCursor();
2378
+
2379
+        $query = $this->db->getQueryBuilder();
2380
+        $query->insert('calendarchanges')
2381
+            ->values([
2382
+                'uri' => $query->createNamedParameter($objectUri),
2383
+                'synctoken' => $query->createNamedParameter($syncToken),
2384
+                'calendarid' => $query->createNamedParameter($calendarId),
2385
+                'operation' => $query->createNamedParameter($operation),
2386
+                'calendartype' => $query->createNamedParameter($calendarType),
2387
+            ])
2388
+            ->execute();
2389
+
2390
+        $stmt = $this->db->prepare("UPDATE `*PREFIX*$table` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?");
2391
+        $stmt->execute([
2392
+            $calendarId
2393
+        ]);
2394
+    }
2395
+
2396
+    /**
2397
+     * Parses some information from calendar objects, used for optimized
2398
+     * calendar-queries.
2399
+     *
2400
+     * Returns an array with the following keys:
2401
+     *   * etag - An md5 checksum of the object without the quotes.
2402
+     *   * size - Size of the object in bytes
2403
+     *   * componentType - VEVENT, VTODO or VJOURNAL
2404
+     *   * firstOccurence
2405
+     *   * lastOccurence
2406
+     *   * uid - value of the UID property
2407
+     *
2408
+     * @param string $calendarData
2409
+     * @return array
2410
+     */
2411
+    public function getDenormalizedData($calendarData) {
2412
+        $vObject = Reader::read($calendarData);
2413
+        $vEvents = [];
2414
+        $componentType = null;
2415
+        $component = null;
2416
+        $firstOccurrence = null;
2417
+        $lastOccurrence = null;
2418
+        $uid = null;
2419
+        $classification = self::CLASSIFICATION_PUBLIC;
2420
+        $hasDTSTART = false;
2421
+        foreach ($vObject->getComponents() as $component) {
2422
+            if ($component->name !== 'VTIMEZONE') {
2423
+                // Finding all VEVENTs, and track them
2424
+                if ($component->name === 'VEVENT') {
2425
+                    array_push($vEvents, $component);
2426
+                    if ($component->DTSTART) {
2427
+                        $hasDTSTART = true;
2428
+                    }
2429
+                }
2430
+                // Track first component type and uid
2431
+                if ($uid === null) {
2432
+                    $componentType = $component->name;
2433
+                    $uid = (string)$component->UID;
2434
+                }
2435
+            }
2436
+        }
2437
+        if (!$componentType) {
2438
+            throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
2439
+        }
2440
+
2441
+        if ($hasDTSTART) {
2442
+            $component = $vEvents[0];
2443
+
2444
+            // Finding the last occurrence is a bit harder
2445
+            if (!isset($component->RRULE) && count($vEvents) === 1) {
2446
+                $firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp();
2447
+                if (isset($component->DTEND)) {
2448
+                    $lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp();
2449
+                } elseif (isset($component->DURATION)) {
2450
+                    $endDate = clone $component->DTSTART->getDateTime();
2451
+                    $endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
2452
+                    $lastOccurrence = $endDate->getTimeStamp();
2453
+                } elseif (!$component->DTSTART->hasTime()) {
2454
+                    $endDate = clone $component->DTSTART->getDateTime();
2455
+                    $endDate->modify('+1 day');
2456
+                    $lastOccurrence = $endDate->getTimeStamp();
2457
+                } else {
2458
+                    $lastOccurrence = $firstOccurrence;
2459
+                }
2460
+            } else {
2461
+                $it = new EventIterator($vEvents);
2462
+                $maxDate = new DateTime(self::MAX_DATE);
2463
+                $firstOccurrence = $it->getDtStart()->getTimestamp();
2464
+                if ($it->isInfinite()) {
2465
+                    $lastOccurrence = $maxDate->getTimestamp();
2466
+                } else {
2467
+                    $end = $it->getDtEnd();
2468
+                    while ($it->valid() && $end < $maxDate) {
2469
+                        $end = $it->getDtEnd();
2470
+                        $it->next();
2471
+                    }
2472
+                    $lastOccurrence = $end->getTimestamp();
2473
+                }
2474
+            }
2475
+        }
2476
+
2477
+        if ($component->CLASS) {
2478
+            $classification = CalDavBackend::CLASSIFICATION_PRIVATE;
2479
+            switch ($component->CLASS->getValue()) {
2480
+                case 'PUBLIC':
2481
+                    $classification = CalDavBackend::CLASSIFICATION_PUBLIC;
2482
+                    break;
2483
+                case 'CONFIDENTIAL':
2484
+                    $classification = CalDavBackend::CLASSIFICATION_CONFIDENTIAL;
2485
+                    break;
2486
+            }
2487
+        }
2488
+        return [
2489
+            'etag' => md5($calendarData),
2490
+            'size' => strlen($calendarData),
2491
+            'componentType' => $componentType,
2492
+            'firstOccurence' => is_null($firstOccurrence) ? null : max(0, $firstOccurrence),
2493
+            'lastOccurence' => $lastOccurrence,
2494
+            'uid' => $uid,
2495
+            'classification' => $classification
2496
+        ];
2497
+    }
2498
+
2499
+    /**
2500
+     * @param $cardData
2501
+     * @return bool|string
2502
+     */
2503
+    private function readBlob($cardData) {
2504
+        if (is_resource($cardData)) {
2505
+            return stream_get_contents($cardData);
2506
+        }
2507
+
2508
+        return $cardData;
2509
+    }
2510
+
2511
+    /**
2512
+     * @param IShareable $shareable
2513
+     * @param array $add
2514
+     * @param array $remove
2515
+     */
2516
+    public function updateShares($shareable, $add, $remove) {
2517
+        $calendarId = $shareable->getResourceId();
2518
+        $calendarRow = $this->getCalendarById($calendarId);
2519
+        $oldShares = $this->getShares($calendarId);
2520
+
2521
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateShares', new GenericEvent(
2522
+            '\OCA\DAV\CalDAV\CalDavBackend::updateShares',
2523
+            [
2524
+                'calendarId' => $calendarId,
2525
+                'calendarData' => $calendarRow,
2526
+                'shares' => $oldShares,
2527
+                'add' => $add,
2528
+                'remove' => $remove,
2529
+            ]));
2530
+        $this->calendarSharingBackend->updateShares($shareable, $add, $remove);
2531
+
2532
+        $this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent((int)$calendarId, $calendarRow, $oldShares, $add, $remove));
2533
+    }
2534
+
2535
+    /**
2536
+     * @param int $resourceId
2537
+     * @param int $calendarType
2538
+     * @return array
2539
+     */
2540
+    public function getShares($resourceId, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2541
+        return $this->calendarSharingBackend->getShares($resourceId);
2542
+    }
2543
+
2544
+    /**
2545
+     * @param boolean $value
2546
+     * @param \OCA\DAV\CalDAV\Calendar $calendar
2547
+     * @return string|null
2548
+     */
2549
+    public function setPublishStatus($value, $calendar) {
2550
+        $calendarId = $calendar->getResourceId();
2551
+        $calendarData = $this->getCalendarById($calendarId);
2552
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::publishCalendar', new GenericEvent(
2553
+            '\OCA\DAV\CalDAV\CalDavBackend::updateShares',
2554
+            [
2555
+                'calendarId' => $calendarId,
2556
+                'calendarData' => $calendarData,
2557
+                'public' => $value,
2558
+            ]));
2559
+
2560
+        $query = $this->db->getQueryBuilder();
2561
+        if ($value) {
2562
+            $publicUri = $this->random->generate(16, ISecureRandom::CHAR_HUMAN_READABLE);
2563
+            $query->insert('dav_shares')
2564
+                ->values([
2565
+                    'principaluri' => $query->createNamedParameter($calendar->getPrincipalURI()),
2566
+                    'type' => $query->createNamedParameter('calendar'),
2567
+                    'access' => $query->createNamedParameter(self::ACCESS_PUBLIC),
2568
+                    'resourceid' => $query->createNamedParameter($calendar->getResourceId()),
2569
+                    'publicuri' => $query->createNamedParameter($publicUri)
2570
+                ]);
2571
+            $query->execute();
2572
+
2573
+            $this->dispatcher->dispatchTyped(new CalendarPublishedEvent((int)$calendarId, $calendarData, $publicUri));
2574
+            return $publicUri;
2575
+        }
2576
+        $query->delete('dav_shares')
2577
+            ->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
2578
+            ->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)));
2579
+        $query->execute();
2580
+
2581
+        $this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent((int)$calendarId, $calendarData));
2582
+        return null;
2583
+    }
2584
+
2585
+    /**
2586
+     * @param \OCA\DAV\CalDAV\Calendar $calendar
2587
+     * @return mixed
2588
+     */
2589
+    public function getPublishStatus($calendar) {
2590
+        $query = $this->db->getQueryBuilder();
2591
+        $result = $query->select('publicuri')
2592
+            ->from('dav_shares')
2593
+            ->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
2594
+            ->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
2595
+            ->execute();
2596
+
2597
+        $row = $result->fetch();
2598
+        $result->closeCursor();
2599
+        return $row ? reset($row) : false;
2600
+    }
2601
+
2602
+    /**
2603
+     * @param int $resourceId
2604
+     * @param array $acl
2605
+     * @return array
2606
+     */
2607
+    public function applyShareAcl($resourceId, $acl) {
2608
+        return $this->calendarSharingBackend->applyShareAcl($resourceId, $acl);
2609
+    }
2610
+
2611
+
2612
+
2613
+    /**
2614
+     * update properties table
2615
+     *
2616
+     * @param int $calendarId
2617
+     * @param string $objectUri
2618
+     * @param string $calendarData
2619
+     * @param int $calendarType
2620
+     */
2621
+    public function updateProperties($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2622
+        $objectId = $this->getCalendarObjectId($calendarId, $objectUri, $calendarType);
2623
+
2624
+        try {
2625
+            $vCalendar = $this->readCalendarData($calendarData);
2626
+        } catch (\Exception $ex) {
2627
+            return;
2628
+        }
2629
+
2630
+        $this->purgeProperties($calendarId, $objectId);
2631
+
2632
+        $query = $this->db->getQueryBuilder();
2633
+        $query->insert($this->dbObjectPropertiesTable)
2634
+            ->values(
2635
+                [
2636
+                    'calendarid' => $query->createNamedParameter($calendarId),
2637
+                    'calendartype' => $query->createNamedParameter($calendarType),
2638
+                    'objectid' => $query->createNamedParameter($objectId),
2639
+                    'name' => $query->createParameter('name'),
2640
+                    'parameter' => $query->createParameter('parameter'),
2641
+                    'value' => $query->createParameter('value'),
2642
+                ]
2643
+            );
2644
+
2645
+        $indexComponents = ['VEVENT', 'VJOURNAL', 'VTODO'];
2646
+        foreach ($vCalendar->getComponents() as $component) {
2647
+            if (!in_array($component->name, $indexComponents)) {
2648
+                continue;
2649
+            }
2650
+
2651
+            foreach ($component->children() as $property) {
2652
+                if (in_array($property->name, self::$indexProperties)) {
2653
+                    $value = $property->getValue();
2654
+                    // is this a shitty db?
2655
+                    if (!$this->db->supports4ByteText()) {
2656
+                        $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
2657
+                    }
2658
+                    $value = mb_substr($value, 0, 254);
2659
+
2660
+                    $query->setParameter('name', $property->name);
2661
+                    $query->setParameter('parameter', null);
2662
+                    $query->setParameter('value', $value);
2663
+                    $query->execute();
2664
+                }
2665
+
2666
+                if (array_key_exists($property->name, self::$indexParameters)) {
2667
+                    $parameters = $property->parameters();
2668
+                    $indexedParametersForProperty = self::$indexParameters[$property->name];
2669
+
2670
+                    foreach ($parameters as $key => $value) {
2671
+                        if (in_array($key, $indexedParametersForProperty)) {
2672
+                            // is this a shitty db?
2673
+                            if ($this->db->supports4ByteText()) {
2674
+                                $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
2675
+                            }
2676
+
2677
+                            $query->setParameter('name', $property->name);
2678
+                            $query->setParameter('parameter', mb_substr($key, 0, 254));
2679
+                            $query->setParameter('value', mb_substr($value, 0, 254));
2680
+                            $query->execute();
2681
+                        }
2682
+                    }
2683
+                }
2684
+            }
2685
+        }
2686
+    }
2687
+
2688
+    /**
2689
+     * deletes all birthday calendars
2690
+     */
2691
+    public function deleteAllBirthdayCalendars() {
2692
+        $query = $this->db->getQueryBuilder();
2693
+        $result = $query->select(['id'])->from('calendars')
2694
+            ->where($query->expr()->eq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)))
2695
+            ->execute();
2696
+
2697
+        $ids = $result->fetchAll();
2698
+        foreach ($ids as $id) {
2699
+            $this->deleteCalendar($id['id']);
2700
+        }
2701
+    }
2702
+
2703
+    /**
2704
+     * @param $subscriptionId
2705
+     */
2706
+    public function purgeAllCachedEventsForSubscription($subscriptionId) {
2707
+        $query = $this->db->getQueryBuilder();
2708
+        $query->select('uri')
2709
+            ->from('calendarobjects')
2710
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2711
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
2712
+        $stmt = $query->execute();
2713
+
2714
+        $uris = [];
2715
+        foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
2716
+            $uris[] = $row['uri'];
2717
+        }
2718
+        $stmt->closeCursor();
2719
+
2720
+        $query = $this->db->getQueryBuilder();
2721
+        $query->delete('calendarobjects')
2722
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2723
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2724
+            ->execute();
2725
+
2726
+        $query->delete('calendarchanges')
2727
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2728
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2729
+            ->execute();
2730
+
2731
+        $query->delete($this->dbObjectPropertiesTable)
2732
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2733
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2734
+            ->execute();
2735
+
2736
+        foreach ($uris as $uri) {
2737
+            $this->addChange($subscriptionId, $uri, 3, self::CALENDAR_TYPE_SUBSCRIPTION);
2738
+        }
2739
+    }
2740
+
2741
+    /**
2742
+     * Move a calendar from one user to another
2743
+     *
2744
+     * @param string $uriName
2745
+     * @param string $uriOrigin
2746
+     * @param string $uriDestination
2747
+     */
2748
+    public function moveCalendar($uriName, $uriOrigin, $uriDestination) {
2749
+        $query = $this->db->getQueryBuilder();
2750
+        $query->update('calendars')
2751
+            ->set('principaluri', $query->createNamedParameter($uriDestination))
2752
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($uriOrigin)))
2753
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($uriName)))
2754
+            ->execute();
2755
+    }
2756
+
2757
+    /**
2758
+     * read VCalendar data into a VCalendar object
2759
+     *
2760
+     * @param string $objectData
2761
+     * @return VCalendar
2762
+     */
2763
+    protected function readCalendarData($objectData) {
2764
+        return Reader::read($objectData);
2765
+    }
2766
+
2767
+    /**
2768
+     * delete all properties from a given calendar object
2769
+     *
2770
+     * @param int $calendarId
2771
+     * @param int $objectId
2772
+     */
2773
+    protected function purgeProperties($calendarId, $objectId) {
2774
+        $query = $this->db->getQueryBuilder();
2775
+        $query->delete($this->dbObjectPropertiesTable)
2776
+            ->where($query->expr()->eq('objectid', $query->createNamedParameter($objectId)))
2777
+            ->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
2778
+        $query->execute();
2779
+    }
2780
+
2781
+    /**
2782
+     * get ID from a given calendar object
2783
+     *
2784
+     * @param int $calendarId
2785
+     * @param string $uri
2786
+     * @param int $calendarType
2787
+     * @return int
2788
+     */
2789
+    protected function getCalendarObjectId($calendarId, $uri, $calendarType):int {
2790
+        $query = $this->db->getQueryBuilder();
2791
+        $query->select('id')
2792
+            ->from('calendarobjects')
2793
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
2794
+            ->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
2795
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
2796
+
2797
+        $result = $query->execute();
2798
+        $objectIds = $result->fetch();
2799
+        $result->closeCursor();
2800
+
2801
+        if (!isset($objectIds['id'])) {
2802
+            throw new \InvalidArgumentException('Calendarobject does not exists: ' . $uri);
2803
+        }
2804
+
2805
+        return (int)$objectIds['id'];
2806
+    }
2807
+
2808
+    /**
2809
+     * return legacy endpoint principal name to new principal name
2810
+     *
2811
+     * @param $principalUri
2812
+     * @param $toV2
2813
+     * @return string
2814
+     */
2815
+    private function convertPrincipal($principalUri, $toV2) {
2816
+        if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
2817
+            list(, $name) = Uri\split($principalUri);
2818
+            if ($toV2 === true) {
2819
+                return "principals/users/$name";
2820
+            }
2821
+            return "principals/$name";
2822
+        }
2823
+        return $principalUri;
2824
+    }
2825
+
2826
+    /**
2827
+     * adds information about an owner to the calendar data
2828
+     *
2829
+     * @param $calendarInfo
2830
+     */
2831
+    private function addOwnerPrincipal(&$calendarInfo) {
2832
+        $ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
2833
+        $displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
2834
+        if (isset($calendarInfo[$ownerPrincipalKey])) {
2835
+            $uri = $calendarInfo[$ownerPrincipalKey];
2836
+        } else {
2837
+            $uri = $calendarInfo['principaluri'];
2838
+        }
2839
+
2840
+        $principalInformation = $this->principalBackend->getPrincipalByPath($uri);
2841
+        if (isset($principalInformation['{DAV:}displayname'])) {
2842
+            $calendarInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
2843
+        }
2844
+    }
2845 2845
 }
Please login to merge, or discard this patch.
apps/dav/lib/Migration/Version1016Date20201109085907.php 1 patch
Indentation   +26 added lines, -26 removed lines patch added patch discarded remove patch
@@ -32,30 +32,30 @@
 block discarded – undo
32 32
 use OCP\Migration\SimpleMigrationStep;
33 33
 
34 34
 class Version1016Date20201109085907 extends SimpleMigrationStep {
35
-	/**
36
-	 * @param IOutput $output
37
-	 * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
38
-	 * @param array $options
39
-	 * @return null|ISchemaWrapper
40
-	 */
41
-	public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
42
-		/** @var ISchemaWrapper $schema */
43
-		$schema = $schemaClosure();
44
-
45
-		$result = $this->ensureColumnIsNullable($schema, 'calendar_reminders', 'is_recurring');
46
-
47
-		return $result ? $schema : null;
48
-	}
49
-
50
-	protected function ensureColumnIsNullable(ISchemaWrapper $schema, string $tableName, string $columnName): bool {
51
-		$table = $schema->getTable($tableName);
52
-		$column = $table->getColumn($columnName);
53
-
54
-		if ($column->getNotnull()) {
55
-			$column->setNotnull(false);
56
-			return true;
57
-		}
58
-
59
-		return false;
60
-	}
35
+    /**
36
+     * @param IOutput $output
37
+     * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
38
+     * @param array $options
39
+     * @return null|ISchemaWrapper
40
+     */
41
+    public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
42
+        /** @var ISchemaWrapper $schema */
43
+        $schema = $schemaClosure();
44
+
45
+        $result = $this->ensureColumnIsNullable($schema, 'calendar_reminders', 'is_recurring');
46
+
47
+        return $result ? $schema : null;
48
+    }
49
+
50
+    protected function ensureColumnIsNullable(ISchemaWrapper $schema, string $tableName, string $columnName): bool {
51
+        $table = $schema->getTable($tableName);
52
+        $column = $table->getColumn($columnName);
53
+
54
+        if ($column->getNotnull()) {
55
+            $column->setNotnull(false);
56
+            return true;
57
+        }
58
+
59
+        return false;
60
+    }
61 61
 }
Please login to merge, or discard this patch.
apps/dav/lib/Migration/Version1012Date20190808122342.php 1 patch
Indentation   +77 added lines, -77 removed lines patch added patch discarded remove patch
@@ -38,86 +38,86 @@
 block discarded – undo
38 38
  */
39 39
 class Version1012Date20190808122342 extends SimpleMigrationStep {
40 40
 
41
-	/**
42
-	 * @param IOutput $output
43
-	 * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
44
-	 * @param array $options
45
-	 * @return null|ISchemaWrapper
46
-	 * @since 17.0.0
47
-	 */
48
-	public function changeSchema(IOutput $output,
49
-								 \Closure $schemaClosure,
50
-								 array $options):?ISchemaWrapper {
51
-		/** @var ISchemaWrapper $schema */
52
-		$schema = $schemaClosure();
41
+    /**
42
+     * @param IOutput $output
43
+     * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
44
+     * @param array $options
45
+     * @return null|ISchemaWrapper
46
+     * @since 17.0.0
47
+     */
48
+    public function changeSchema(IOutput $output,
49
+                                    \Closure $schemaClosure,
50
+                                    array $options):?ISchemaWrapper {
51
+        /** @var ISchemaWrapper $schema */
52
+        $schema = $schemaClosure();
53 53
 
54
-		if (!$schema->hasTable('calendar_reminders')) {
55
-			$table = $schema->createTable('calendar_reminders');
54
+        if (!$schema->hasTable('calendar_reminders')) {
55
+            $table = $schema->createTable('calendar_reminders');
56 56
 
57
-			$table->addColumn('id', Types::BIGINT, [
58
-				'autoincrement' => true,
59
-				'notnull' => true,
60
-				'length' => 11,
61
-				'unsigned' => true,
62
-			]);
63
-			$table->addColumn('calendar_id', Types::BIGINT, [
64
-				'notnull' => true,
65
-				'length' => 11,
66
-			]);
67
-			$table->addColumn('object_id', Types::BIGINT, [
68
-				'notnull' => true,
69
-				'length' => 11,
70
-			]);
71
-			$table->addColumn('is_recurring', Types::SMALLINT, [
72
-				'notnull' => false,
73
-				'length' => 1,
74
-			]);
75
-			$table->addColumn('uid', Types::STRING, [
76
-				'notnull' => true,
77
-				'length' => 255,
78
-			]);
79
-			$table->addColumn('recurrence_id', Types::BIGINT, [
80
-				'notnull' => false,
81
-				'length' => 11,
82
-				'unsigned' => true,
83
-			]);
84
-			$table->addColumn('is_recurrence_exception', Types::SMALLINT, [
85
-				'notnull' => true,
86
-				'length' => 1,
87
-			]);
88
-			$table->addColumn('event_hash', Types::STRING, [
89
-				'notnull' => true,
90
-				'length' => 255,
91
-			]);
92
-			$table->addColumn('alarm_hash', Types::STRING, [
93
-				'notnull' => true,
94
-				'length' => 255,
95
-			]);
96
-			$table->addColumn('type', Types::STRING, [
97
-				'notnull' => true,
98
-				'length' => 255,
99
-			]);
100
-			$table->addColumn('is_relative', Types::SMALLINT, [
101
-				'notnull' => true,
102
-				'length' => 1,
103
-			]);
104
-			$table->addColumn('notification_date', Types::BIGINT, [
105
-				'notnull' => true,
106
-				'length' => 11,
107
-				'unsigned' => true,
108
-			]);
109
-			$table->addColumn('is_repeat_based', Types::SMALLINT, [
110
-				'notnull' => true,
111
-				'length' => 1,
112
-			]);
57
+            $table->addColumn('id', Types::BIGINT, [
58
+                'autoincrement' => true,
59
+                'notnull' => true,
60
+                'length' => 11,
61
+                'unsigned' => true,
62
+            ]);
63
+            $table->addColumn('calendar_id', Types::BIGINT, [
64
+                'notnull' => true,
65
+                'length' => 11,
66
+            ]);
67
+            $table->addColumn('object_id', Types::BIGINT, [
68
+                'notnull' => true,
69
+                'length' => 11,
70
+            ]);
71
+            $table->addColumn('is_recurring', Types::SMALLINT, [
72
+                'notnull' => false,
73
+                'length' => 1,
74
+            ]);
75
+            $table->addColumn('uid', Types::STRING, [
76
+                'notnull' => true,
77
+                'length' => 255,
78
+            ]);
79
+            $table->addColumn('recurrence_id', Types::BIGINT, [
80
+                'notnull' => false,
81
+                'length' => 11,
82
+                'unsigned' => true,
83
+            ]);
84
+            $table->addColumn('is_recurrence_exception', Types::SMALLINT, [
85
+                'notnull' => true,
86
+                'length' => 1,
87
+            ]);
88
+            $table->addColumn('event_hash', Types::STRING, [
89
+                'notnull' => true,
90
+                'length' => 255,
91
+            ]);
92
+            $table->addColumn('alarm_hash', Types::STRING, [
93
+                'notnull' => true,
94
+                'length' => 255,
95
+            ]);
96
+            $table->addColumn('type', Types::STRING, [
97
+                'notnull' => true,
98
+                'length' => 255,
99
+            ]);
100
+            $table->addColumn('is_relative', Types::SMALLINT, [
101
+                'notnull' => true,
102
+                'length' => 1,
103
+            ]);
104
+            $table->addColumn('notification_date', Types::BIGINT, [
105
+                'notnull' => true,
106
+                'length' => 11,
107
+                'unsigned' => true,
108
+            ]);
109
+            $table->addColumn('is_repeat_based', Types::SMALLINT, [
110
+                'notnull' => true,
111
+                'length' => 1,
112
+            ]);
113 113
 
114
-			$table->setPrimaryKey(['id']);
115
-			$table->addIndex(['object_id'], 'calendar_reminder_objid');
116
-			$table->addIndex(['uid', 'recurrence_id'], 'calendar_reminder_uidrec');
114
+            $table->setPrimaryKey(['id']);
115
+            $table->addIndex(['object_id'], 'calendar_reminder_objid');
116
+            $table->addIndex(['uid', 'recurrence_id'], 'calendar_reminder_uidrec');
117 117
 
118
-			return $schema;
119
-		}
118
+            return $schema;
119
+        }
120 120
 
121
-		return null;
122
-	}
121
+        return null;
122
+    }
123 123
 }
Please login to merge, or discard this patch.
apps/settings/lib/Controller/CheckSetupController.php 1 patch
Indentation   +665 added lines, -665 removed lines patch added patch discarded remove patch
@@ -76,292 +76,292 @@  discard block
 block discarded – undo
76 76
 use Symfony\Component\EventDispatcher\GenericEvent;
77 77
 
78 78
 class CheckSetupController extends Controller {
79
-	/** @var IConfig */
80
-	private $config;
81
-	/** @var IClientService */
82
-	private $clientService;
83
-	/** @var IURLGenerator */
84
-	private $urlGenerator;
85
-	/** @var IL10N */
86
-	private $l10n;
87
-	/** @var Checker */
88
-	private $checker;
89
-	/** @var ILogger */
90
-	private $logger;
91
-	/** @var EventDispatcherInterface */
92
-	private $dispatcher;
93
-	/** @var IDBConnection|Connection */
94
-	private $db;
95
-	/** @var ILockingProvider */
96
-	private $lockingProvider;
97
-	/** @var IDateTimeFormatter */
98
-	private $dateTimeFormatter;
99
-	/** @var MemoryInfo */
100
-	private $memoryInfo;
101
-	/** @var ISecureRandom */
102
-	private $secureRandom;
103
-	/** @var IniGetWrapper */
104
-	private $iniGetWrapper;
105
-
106
-	public function __construct($AppName,
107
-								IRequest $request,
108
-								IConfig $config,
109
-								IClientService $clientService,
110
-								IURLGenerator $urlGenerator,
111
-								IL10N $l10n,
112
-								Checker $checker,
113
-								ILogger $logger,
114
-								EventDispatcherInterface $dispatcher,
115
-								IDBConnection $db,
116
-								ILockingProvider $lockingProvider,
117
-								IDateTimeFormatter $dateTimeFormatter,
118
-								MemoryInfo $memoryInfo,
119
-								ISecureRandom $secureRandom,
120
-								IniGetWrapper $iniGetWrapper) {
121
-		parent::__construct($AppName, $request);
122
-		$this->config = $config;
123
-		$this->clientService = $clientService;
124
-		$this->urlGenerator = $urlGenerator;
125
-		$this->l10n = $l10n;
126
-		$this->checker = $checker;
127
-		$this->logger = $logger;
128
-		$this->dispatcher = $dispatcher;
129
-		$this->db = $db;
130
-		$this->lockingProvider = $lockingProvider;
131
-		$this->dateTimeFormatter = $dateTimeFormatter;
132
-		$this->memoryInfo = $memoryInfo;
133
-		$this->secureRandom = $secureRandom;
134
-		$this->iniGetWrapper = $iniGetWrapper;
135
-	}
136
-
137
-	/**
138
-	 * Checks if the server can connect to the internet using HTTPS and HTTP
139
-	 * @return bool
140
-	 */
141
-	private function hasInternetConnectivityProblems(): bool {
142
-		if ($this->config->getSystemValue('has_internet_connection', true) === false) {
143
-			return false;
144
-		}
145
-
146
-		$siteArray = $this->config->getSystemValue('connectivity_check_domains', [
147
-			'www.nextcloud.com', 'www.startpage.com', 'www.eff.org', 'www.edri.org'
148
-		]);
149
-
150
-		foreach ($siteArray as $site) {
151
-			if ($this->isSiteReachable($site)) {
152
-				return false;
153
-			}
154
-		}
155
-		return true;
156
-	}
157
-
158
-	/**
159
-	 * Checks if the Nextcloud server can connect to a specific URL using both HTTPS and HTTP
160
-	 * @return bool
161
-	 */
162
-	private function isSiteReachable($sitename) {
163
-		$httpSiteName = 'http://' . $sitename . '/';
164
-		$httpsSiteName = 'https://' . $sitename . '/';
165
-
166
-		try {
167
-			$client = $this->clientService->newClient();
168
-			$client->get($httpSiteName);
169
-			$client->get($httpsSiteName);
170
-		} catch (\Exception $e) {
171
-			$this->logger->logException($e, ['app' => 'internet_connection_check']);
172
-			return false;
173
-		}
174
-		return true;
175
-	}
176
-
177
-	/**
178
-	 * Checks whether a local memcache is installed or not
179
-	 * @return bool
180
-	 */
181
-	private function isMemcacheConfigured() {
182
-		return $this->config->getSystemValue('memcache.local', null) !== null;
183
-	}
184
-
185
-	/**
186
-	 * Whether PHP can generate "secure" pseudorandom integers
187
-	 *
188
-	 * @return bool
189
-	 */
190
-	private function isRandomnessSecure() {
191
-		try {
192
-			$this->secureRandom->generate(1);
193
-		} catch (\Exception $ex) {
194
-			return false;
195
-		}
196
-		return true;
197
-	}
198
-
199
-	/**
200
-	 * Public for the sake of unit-testing
201
-	 *
202
-	 * @return array
203
-	 */
204
-	protected function getCurlVersion() {
205
-		return curl_version();
206
-	}
207
-
208
-	/**
209
-	 * Check if the used  SSL lib is outdated. Older OpenSSL and NSS versions do
210
-	 * have multiple bugs which likely lead to problems in combination with
211
-	 * functionality required by ownCloud such as SNI.
212
-	 *
213
-	 * @link https://github.com/owncloud/core/issues/17446#issuecomment-122877546
214
-	 * @link https://bugzilla.redhat.com/show_bug.cgi?id=1241172
215
-	 * @return string
216
-	 */
217
-	private function isUsedTlsLibOutdated() {
218
-		// Don't run check when:
219
-		// 1. Server has `has_internet_connection` set to false
220
-		// 2. AppStore AND S2S is disabled
221
-		if (!$this->config->getSystemValue('has_internet_connection', true)) {
222
-			return '';
223
-		}
224
-		if (!$this->config->getSystemValue('appstoreenabled', true)
225
-			&& $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'no'
226
-			&& $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes') === 'no') {
227
-			return '';
228
-		}
229
-
230
-		$versionString = $this->getCurlVersion();
231
-		if (isset($versionString['ssl_version'])) {
232
-			$versionString = $versionString['ssl_version'];
233
-		} else {
234
-			return '';
235
-		}
236
-
237
-		$features = (string)$this->l10n->t('installing and updating apps via the app store or Federated Cloud Sharing');
238
-		if (!$this->config->getSystemValue('appstoreenabled', true)) {
239
-			$features = (string)$this->l10n->t('Federated Cloud Sharing');
240
-		}
241
-
242
-		// Check if at least OpenSSL after 1.01d or 1.0.2b
243
-		if (strpos($versionString, 'OpenSSL/') === 0) {
244
-			$majorVersion = substr($versionString, 8, 5);
245
-			$patchRelease = substr($versionString, 13, 6);
246
-
247
-			if (($majorVersion === '1.0.1' && ord($patchRelease) < ord('d')) ||
248
-				($majorVersion === '1.0.2' && ord($patchRelease) < ord('b'))) {
249
-				return $this->l10n->t('cURL is using an outdated %1$s version (%2$s). Please update your operating system or features such as %3$s will not work reliably.', ['OpenSSL', $versionString, $features]);
250
-			}
251
-		}
252
-
253
-		// Check if NSS and perform heuristic check
254
-		if (strpos($versionString, 'NSS/') === 0) {
255
-			try {
256
-				$firstClient = $this->clientService->newClient();
257
-				$firstClient->get('https://nextcloud.com/');
258
-
259
-				$secondClient = $this->clientService->newClient();
260
-				$secondClient->get('https://nextcloud.com/');
261
-			} catch (ClientException $e) {
262
-				if ($e->getResponse()->getStatusCode() === 400) {
263
-					return $this->l10n->t('cURL is using an outdated %1$s version (%2$s). Please update your operating system or features such as %3$s will not work reliably.', ['NSS', $versionString, $features]);
264
-				}
265
-			}
266
-		}
267
-
268
-		return '';
269
-	}
270
-
271
-	/**
272
-	 * Whether the version is outdated
273
-	 *
274
-	 * @return bool
275
-	 */
276
-	protected function isPhpOutdated(): bool {
277
-		return PHP_VERSION_ID < 70300;
278
-	}
279
-
280
-	/**
281
-	 * Whether the php version is still supported (at time of release)
282
-	 * according to: https://secure.php.net/supported-versions.php
283
-	 *
284
-	 * @return array
285
-	 */
286
-	private function isPhpSupported(): array {
287
-		return ['eol' => $this->isPhpOutdated(), 'version' => PHP_VERSION];
288
-	}
289
-
290
-	/**
291
-	 * Check if the reverse proxy configuration is working as expected
292
-	 *
293
-	 * @return bool
294
-	 */
295
-	private function forwardedForHeadersWorking() {
296
-		$trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
297
-		$remoteAddress = $this->request->getHeader('REMOTE_ADDR');
298
-
299
-		if (empty($trustedProxies) && $this->request->getHeader('X-Forwarded-Host') !== '') {
300
-			return false;
301
-		}
302
-
303
-		if (\is_array($trustedProxies) && \in_array($remoteAddress, $trustedProxies, true)) {
304
-			return $remoteAddress !== $this->request->getRemoteAddress();
305
-		}
306
-
307
-		// either not enabled or working correctly
308
-		return true;
309
-	}
310
-
311
-	/**
312
-	 * Checks if the correct memcache module for PHP is installed. Only
313
-	 * fails if memcached is configured and the working module is not installed.
314
-	 *
315
-	 * @return bool
316
-	 */
317
-	private function isCorrectMemcachedPHPModuleInstalled() {
318
-		if ($this->config->getSystemValue('memcache.distributed', null) !== '\OC\Memcache\Memcached') {
319
-			return true;
320
-		}
321
-
322
-		// there are two different memcached modules for PHP
323
-		// we only support memcached and not memcache
324
-		// https://code.google.com/p/memcached/wiki/PHPClientComparison
325
-		return !(!extension_loaded('memcached') && extension_loaded('memcache'));
326
-	}
327
-
328
-	/**
329
-	 * Checks if set_time_limit is not disabled.
330
-	 *
331
-	 * @return bool
332
-	 */
333
-	private function isSettimelimitAvailable() {
334
-		if (function_exists('set_time_limit')
335
-			&& strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
336
-			return true;
337
-		}
338
-
339
-		return false;
340
-	}
341
-
342
-	/**
343
-	 * @return RedirectResponse
344
-	 */
345
-	public function rescanFailedIntegrityCheck() {
346
-		$this->checker->runInstanceVerification();
347
-		return new RedirectResponse(
348
-			$this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'overview'])
349
-		);
350
-	}
351
-
352
-	/**
353
-	 * @NoCSRFRequired
354
-	 * @return DataResponse
355
-	 */
356
-	public function getFailedIntegrityCheckFiles() {
357
-		if (!$this->checker->isCodeCheckEnforced()) {
358
-			return new DataDisplayResponse('Integrity checker has been disabled. Integrity cannot be verified.');
359
-		}
360
-
361
-		$completeResults = $this->checker->getResults();
362
-
363
-		if (!empty($completeResults)) {
364
-			$formattedTextResponse = 'Technical information
79
+    /** @var IConfig */
80
+    private $config;
81
+    /** @var IClientService */
82
+    private $clientService;
83
+    /** @var IURLGenerator */
84
+    private $urlGenerator;
85
+    /** @var IL10N */
86
+    private $l10n;
87
+    /** @var Checker */
88
+    private $checker;
89
+    /** @var ILogger */
90
+    private $logger;
91
+    /** @var EventDispatcherInterface */
92
+    private $dispatcher;
93
+    /** @var IDBConnection|Connection */
94
+    private $db;
95
+    /** @var ILockingProvider */
96
+    private $lockingProvider;
97
+    /** @var IDateTimeFormatter */
98
+    private $dateTimeFormatter;
99
+    /** @var MemoryInfo */
100
+    private $memoryInfo;
101
+    /** @var ISecureRandom */
102
+    private $secureRandom;
103
+    /** @var IniGetWrapper */
104
+    private $iniGetWrapper;
105
+
106
+    public function __construct($AppName,
107
+                                IRequest $request,
108
+                                IConfig $config,
109
+                                IClientService $clientService,
110
+                                IURLGenerator $urlGenerator,
111
+                                IL10N $l10n,
112
+                                Checker $checker,
113
+                                ILogger $logger,
114
+                                EventDispatcherInterface $dispatcher,
115
+                                IDBConnection $db,
116
+                                ILockingProvider $lockingProvider,
117
+                                IDateTimeFormatter $dateTimeFormatter,
118
+                                MemoryInfo $memoryInfo,
119
+                                ISecureRandom $secureRandom,
120
+                                IniGetWrapper $iniGetWrapper) {
121
+        parent::__construct($AppName, $request);
122
+        $this->config = $config;
123
+        $this->clientService = $clientService;
124
+        $this->urlGenerator = $urlGenerator;
125
+        $this->l10n = $l10n;
126
+        $this->checker = $checker;
127
+        $this->logger = $logger;
128
+        $this->dispatcher = $dispatcher;
129
+        $this->db = $db;
130
+        $this->lockingProvider = $lockingProvider;
131
+        $this->dateTimeFormatter = $dateTimeFormatter;
132
+        $this->memoryInfo = $memoryInfo;
133
+        $this->secureRandom = $secureRandom;
134
+        $this->iniGetWrapper = $iniGetWrapper;
135
+    }
136
+
137
+    /**
138
+     * Checks if the server can connect to the internet using HTTPS and HTTP
139
+     * @return bool
140
+     */
141
+    private function hasInternetConnectivityProblems(): bool {
142
+        if ($this->config->getSystemValue('has_internet_connection', true) === false) {
143
+            return false;
144
+        }
145
+
146
+        $siteArray = $this->config->getSystemValue('connectivity_check_domains', [
147
+            'www.nextcloud.com', 'www.startpage.com', 'www.eff.org', 'www.edri.org'
148
+        ]);
149
+
150
+        foreach ($siteArray as $site) {
151
+            if ($this->isSiteReachable($site)) {
152
+                return false;
153
+            }
154
+        }
155
+        return true;
156
+    }
157
+
158
+    /**
159
+     * Checks if the Nextcloud server can connect to a specific URL using both HTTPS and HTTP
160
+     * @return bool
161
+     */
162
+    private function isSiteReachable($sitename) {
163
+        $httpSiteName = 'http://' . $sitename . '/';
164
+        $httpsSiteName = 'https://' . $sitename . '/';
165
+
166
+        try {
167
+            $client = $this->clientService->newClient();
168
+            $client->get($httpSiteName);
169
+            $client->get($httpsSiteName);
170
+        } catch (\Exception $e) {
171
+            $this->logger->logException($e, ['app' => 'internet_connection_check']);
172
+            return false;
173
+        }
174
+        return true;
175
+    }
176
+
177
+    /**
178
+     * Checks whether a local memcache is installed or not
179
+     * @return bool
180
+     */
181
+    private function isMemcacheConfigured() {
182
+        return $this->config->getSystemValue('memcache.local', null) !== null;
183
+    }
184
+
185
+    /**
186
+     * Whether PHP can generate "secure" pseudorandom integers
187
+     *
188
+     * @return bool
189
+     */
190
+    private function isRandomnessSecure() {
191
+        try {
192
+            $this->secureRandom->generate(1);
193
+        } catch (\Exception $ex) {
194
+            return false;
195
+        }
196
+        return true;
197
+    }
198
+
199
+    /**
200
+     * Public for the sake of unit-testing
201
+     *
202
+     * @return array
203
+     */
204
+    protected function getCurlVersion() {
205
+        return curl_version();
206
+    }
207
+
208
+    /**
209
+     * Check if the used  SSL lib is outdated. Older OpenSSL and NSS versions do
210
+     * have multiple bugs which likely lead to problems in combination with
211
+     * functionality required by ownCloud such as SNI.
212
+     *
213
+     * @link https://github.com/owncloud/core/issues/17446#issuecomment-122877546
214
+     * @link https://bugzilla.redhat.com/show_bug.cgi?id=1241172
215
+     * @return string
216
+     */
217
+    private function isUsedTlsLibOutdated() {
218
+        // Don't run check when:
219
+        // 1. Server has `has_internet_connection` set to false
220
+        // 2. AppStore AND S2S is disabled
221
+        if (!$this->config->getSystemValue('has_internet_connection', true)) {
222
+            return '';
223
+        }
224
+        if (!$this->config->getSystemValue('appstoreenabled', true)
225
+            && $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'no'
226
+            && $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes') === 'no') {
227
+            return '';
228
+        }
229
+
230
+        $versionString = $this->getCurlVersion();
231
+        if (isset($versionString['ssl_version'])) {
232
+            $versionString = $versionString['ssl_version'];
233
+        } else {
234
+            return '';
235
+        }
236
+
237
+        $features = (string)$this->l10n->t('installing and updating apps via the app store or Federated Cloud Sharing');
238
+        if (!$this->config->getSystemValue('appstoreenabled', true)) {
239
+            $features = (string)$this->l10n->t('Federated Cloud Sharing');
240
+        }
241
+
242
+        // Check if at least OpenSSL after 1.01d or 1.0.2b
243
+        if (strpos($versionString, 'OpenSSL/') === 0) {
244
+            $majorVersion = substr($versionString, 8, 5);
245
+            $patchRelease = substr($versionString, 13, 6);
246
+
247
+            if (($majorVersion === '1.0.1' && ord($patchRelease) < ord('d')) ||
248
+                ($majorVersion === '1.0.2' && ord($patchRelease) < ord('b'))) {
249
+                return $this->l10n->t('cURL is using an outdated %1$s version (%2$s). Please update your operating system or features such as %3$s will not work reliably.', ['OpenSSL', $versionString, $features]);
250
+            }
251
+        }
252
+
253
+        // Check if NSS and perform heuristic check
254
+        if (strpos($versionString, 'NSS/') === 0) {
255
+            try {
256
+                $firstClient = $this->clientService->newClient();
257
+                $firstClient->get('https://nextcloud.com/');
258
+
259
+                $secondClient = $this->clientService->newClient();
260
+                $secondClient->get('https://nextcloud.com/');
261
+            } catch (ClientException $e) {
262
+                if ($e->getResponse()->getStatusCode() === 400) {
263
+                    return $this->l10n->t('cURL is using an outdated %1$s version (%2$s). Please update your operating system or features such as %3$s will not work reliably.', ['NSS', $versionString, $features]);
264
+                }
265
+            }
266
+        }
267
+
268
+        return '';
269
+    }
270
+
271
+    /**
272
+     * Whether the version is outdated
273
+     *
274
+     * @return bool
275
+     */
276
+    protected function isPhpOutdated(): bool {
277
+        return PHP_VERSION_ID < 70300;
278
+    }
279
+
280
+    /**
281
+     * Whether the php version is still supported (at time of release)
282
+     * according to: https://secure.php.net/supported-versions.php
283
+     *
284
+     * @return array
285
+     */
286
+    private function isPhpSupported(): array {
287
+        return ['eol' => $this->isPhpOutdated(), 'version' => PHP_VERSION];
288
+    }
289
+
290
+    /**
291
+     * Check if the reverse proxy configuration is working as expected
292
+     *
293
+     * @return bool
294
+     */
295
+    private function forwardedForHeadersWorking() {
296
+        $trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
297
+        $remoteAddress = $this->request->getHeader('REMOTE_ADDR');
298
+
299
+        if (empty($trustedProxies) && $this->request->getHeader('X-Forwarded-Host') !== '') {
300
+            return false;
301
+        }
302
+
303
+        if (\is_array($trustedProxies) && \in_array($remoteAddress, $trustedProxies, true)) {
304
+            return $remoteAddress !== $this->request->getRemoteAddress();
305
+        }
306
+
307
+        // either not enabled or working correctly
308
+        return true;
309
+    }
310
+
311
+    /**
312
+     * Checks if the correct memcache module for PHP is installed. Only
313
+     * fails if memcached is configured and the working module is not installed.
314
+     *
315
+     * @return bool
316
+     */
317
+    private function isCorrectMemcachedPHPModuleInstalled() {
318
+        if ($this->config->getSystemValue('memcache.distributed', null) !== '\OC\Memcache\Memcached') {
319
+            return true;
320
+        }
321
+
322
+        // there are two different memcached modules for PHP
323
+        // we only support memcached and not memcache
324
+        // https://code.google.com/p/memcached/wiki/PHPClientComparison
325
+        return !(!extension_loaded('memcached') && extension_loaded('memcache'));
326
+    }
327
+
328
+    /**
329
+     * Checks if set_time_limit is not disabled.
330
+     *
331
+     * @return bool
332
+     */
333
+    private function isSettimelimitAvailable() {
334
+        if (function_exists('set_time_limit')
335
+            && strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
336
+            return true;
337
+        }
338
+
339
+        return false;
340
+    }
341
+
342
+    /**
343
+     * @return RedirectResponse
344
+     */
345
+    public function rescanFailedIntegrityCheck() {
346
+        $this->checker->runInstanceVerification();
347
+        return new RedirectResponse(
348
+            $this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'overview'])
349
+        );
350
+    }
351
+
352
+    /**
353
+     * @NoCSRFRequired
354
+     * @return DataResponse
355
+     */
356
+    public function getFailedIntegrityCheckFiles() {
357
+        if (!$this->checker->isCodeCheckEnforced()) {
358
+            return new DataDisplayResponse('Integrity checker has been disabled. Integrity cannot be verified.');
359
+        }
360
+
361
+        $completeResults = $this->checker->getResults();
362
+
363
+        if (!empty($completeResults)) {
364
+            $formattedTextResponse = 'Technical information
365 365
 =====================
366 366
 The following list covers which files have failed the integrity check. Please read
367 367
 the previous linked documentation to learn more about the errors and how to fix
@@ -370,386 +370,386 @@  discard block
 block discarded – undo
370 370
 Results
371 371
 =======
372 372
 ';
373
-			foreach ($completeResults as $context => $contextResult) {
374
-				$formattedTextResponse .= "- $context\n";
375
-
376
-				foreach ($contextResult as $category => $result) {
377
-					$formattedTextResponse .= "\t- $category\n";
378
-					if ($category !== 'EXCEPTION') {
379
-						foreach ($result as $key => $results) {
380
-							$formattedTextResponse .= "\t\t- $key\n";
381
-						}
382
-					} else {
383
-						foreach ($result as $key => $results) {
384
-							$formattedTextResponse .= "\t\t- $results\n";
385
-						}
386
-					}
387
-				}
388
-			}
389
-
390
-			$formattedTextResponse .= '
373
+            foreach ($completeResults as $context => $contextResult) {
374
+                $formattedTextResponse .= "- $context\n";
375
+
376
+                foreach ($contextResult as $category => $result) {
377
+                    $formattedTextResponse .= "\t- $category\n";
378
+                    if ($category !== 'EXCEPTION') {
379
+                        foreach ($result as $key => $results) {
380
+                            $formattedTextResponse .= "\t\t- $key\n";
381
+                        }
382
+                    } else {
383
+                        foreach ($result as $key => $results) {
384
+                            $formattedTextResponse .= "\t\t- $results\n";
385
+                        }
386
+                    }
387
+                }
388
+            }
389
+
390
+            $formattedTextResponse .= '
391 391
 Raw output
392 392
 ==========
393 393
 ';
394
-			$formattedTextResponse .= print_r($completeResults, true);
395
-		} else {
396
-			$formattedTextResponse = 'No errors have been found.';
397
-		}
398
-
399
-
400
-		$response = new DataDisplayResponse(
401
-			$formattedTextResponse,
402
-			Http::STATUS_OK,
403
-			[
404
-				'Content-Type' => 'text/plain',
405
-			]
406
-		);
407
-
408
-		return $response;
409
-	}
410
-
411
-	/**
412
-	 * Checks whether a PHP opcache is properly set up
413
-	 * @return bool
414
-	 */
415
-	protected function isOpcacheProperlySetup() {
416
-		if (!$this->iniGetWrapper->getBool('opcache.enable')) {
417
-			return false;
418
-		}
419
-
420
-		if (!$this->iniGetWrapper->getBool('opcache.save_comments')) {
421
-			return false;
422
-		}
423
-
424
-		if ($this->iniGetWrapper->getNumeric('opcache.max_accelerated_files') < 10000) {
425
-			return false;
426
-		}
427
-
428
-		if ($this->iniGetWrapper->getNumeric('opcache.memory_consumption') < 128) {
429
-			return false;
430
-		}
431
-
432
-		if ($this->iniGetWrapper->getNumeric('opcache.interned_strings_buffer') < 8) {
433
-			return false;
434
-		}
435
-
436
-		return true;
437
-	}
438
-
439
-	/**
440
-	 * Check if the required FreeType functions are present
441
-	 * @return bool
442
-	 */
443
-	protected function hasFreeTypeSupport() {
444
-		return function_exists('imagettfbbox') && function_exists('imagettftext');
445
-	}
446
-
447
-	protected function hasMissingIndexes(): array {
448
-		$indexInfo = new MissingIndexInformation();
449
-		// Dispatch event so apps can also hint for pending index updates if needed
450
-		$event = new GenericEvent($indexInfo);
451
-		$this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_INDEXES_EVENT, $event);
452
-
453
-		return $indexInfo->getListOfMissingIndexes();
454
-	}
455
-
456
-	protected function hasMissingPrimaryKeys(): array {
457
-		$info = new MissingPrimaryKeyInformation();
458
-		// Dispatch event so apps can also hint for pending index updates if needed
459
-		$event = new GenericEvent($info);
460
-		$this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_PRIMARY_KEYS_EVENT, $event);
461
-
462
-		return $info->getListOfMissingPrimaryKeys();
463
-	}
464
-
465
-	protected function hasMissingColumns(): array {
466
-		$indexInfo = new MissingColumnInformation();
467
-		// Dispatch event so apps can also hint for pending index updates if needed
468
-		$event = new GenericEvent($indexInfo);
469
-		$this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_COLUMNS_EVENT, $event);
470
-
471
-		return $indexInfo->getListOfMissingColumns();
472
-	}
473
-
474
-	protected function isSqliteUsed() {
475
-		return strpos($this->config->getSystemValue('dbtype'), 'sqlite') !== false;
476
-	}
477
-
478
-	protected function isReadOnlyConfig(): bool {
479
-		return \OC_Helper::isReadOnlyConfigEnabled();
480
-	}
481
-
482
-	protected function hasValidTransactionIsolationLevel(): bool {
483
-		try {
484
-			if ($this->db->getDatabasePlatform() instanceof SqlitePlatform) {
485
-				return true;
486
-			}
487
-
488
-			return $this->db->getTransactionIsolation() === Connection::TRANSACTION_READ_COMMITTED;
489
-		} catch (DBALException $e) {
490
-			// ignore
491
-		}
492
-
493
-		return true;
494
-	}
495
-
496
-	protected function hasFileinfoInstalled(): bool {
497
-		return \OC_Util::fileInfoLoaded();
498
-	}
499
-
500
-	protected function hasWorkingFileLocking(): bool {
501
-		return !($this->lockingProvider instanceof NoopLockingProvider);
502
-	}
503
-
504
-	protected function getSuggestedOverwriteCliURL(): string {
505
-		$suggestedOverwriteCliUrl = '';
506
-		if ($this->config->getSystemValue('overwrite.cli.url', '') === '') {
507
-			$suggestedOverwriteCliUrl = $this->request->getServerProtocol() . '://' . $this->request->getInsecureServerHost() . \OC::$WEBROOT;
508
-			if (!$this->config->getSystemValue('config_is_read_only', false)) {
509
-				// Set the overwrite URL when it was not set yet.
510
-				$this->config->setSystemValue('overwrite.cli.url', $suggestedOverwriteCliUrl);
511
-				$suggestedOverwriteCliUrl = '';
512
-			}
513
-		}
514
-		return $suggestedOverwriteCliUrl;
515
-	}
516
-
517
-	protected function getLastCronInfo(): array {
518
-		$lastCronRun = $this->config->getAppValue('core', 'lastcron', 0);
519
-		return [
520
-			'diffInSeconds' => time() - $lastCronRun,
521
-			'relativeTime' => $this->dateTimeFormatter->formatTimeSpan($lastCronRun),
522
-			'backgroundJobsUrl' => $this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'server']) . '#backgroundjobs',
523
-		];
524
-	}
525
-
526
-	protected function getCronErrors() {
527
-		$errors = json_decode($this->config->getAppValue('core', 'cronErrors', ''), true);
528
-
529
-		if (is_array($errors)) {
530
-			return $errors;
531
-		}
532
-
533
-		return [];
534
-	}
535
-
536
-	protected function hasOpcacheLoaded(): bool {
537
-		return extension_loaded('Zend OPcache');
538
-	}
539
-
540
-	/**
541
-	 * Iterates through the configured app roots and
542
-	 * tests if the subdirectories are owned by the same user than the current user.
543
-	 *
544
-	 * @return array
545
-	 */
546
-	protected function getAppDirsWithDifferentOwner(): array {
547
-		$currentUser = posix_getuid();
548
-		$appDirsWithDifferentOwner = [[]];
549
-
550
-		foreach (OC::$APPSROOTS as $appRoot) {
551
-			if ($appRoot['writable'] === true) {
552
-				$appDirsWithDifferentOwner[] = $this->getAppDirsWithDifferentOwnerForAppRoot($currentUser, $appRoot);
553
-			}
554
-		}
555
-
556
-		$appDirsWithDifferentOwner = array_merge(...$appDirsWithDifferentOwner);
557
-		sort($appDirsWithDifferentOwner);
558
-
559
-		return $appDirsWithDifferentOwner;
560
-	}
561
-
562
-	/**
563
-	 * Tests if the directories for one apps directory are writable by the current user.
564
-	 *
565
-	 * @param int $currentUser The current user
566
-	 * @param array $appRoot The app root config
567
-	 * @return string[] The none writable directory paths inside the app root
568
-	 */
569
-	private function getAppDirsWithDifferentOwnerForAppRoot(int $currentUser, array $appRoot): array {
570
-		$appDirsWithDifferentOwner = [];
571
-		$appsPath = $appRoot['path'];
572
-		$appsDir = new DirectoryIterator($appRoot['path']);
573
-
574
-		foreach ($appsDir as $fileInfo) {
575
-			if ($fileInfo->isDir() && !$fileInfo->isDot()) {
576
-				$absAppPath = $appsPath . DIRECTORY_SEPARATOR . $fileInfo->getFilename();
577
-				$appDirUser = fileowner($absAppPath);
578
-				if ($appDirUser !== $currentUser) {
579
-					$appDirsWithDifferentOwner[] = $absAppPath;
580
-				}
581
-			}
582
-		}
583
-
584
-		return $appDirsWithDifferentOwner;
585
-	}
586
-
587
-	/**
588
-	 * Checks for potential PHP modules that would improve the instance
589
-	 *
590
-	 * @return string[] A list of PHP modules that is recommended
591
-	 */
592
-	protected function hasRecommendedPHPModules(): array {
593
-		$recommendedPHPModules = [];
594
-
595
-		if (!extension_loaded('intl')) {
596
-			$recommendedPHPModules[] = 'intl';
597
-		}
598
-
599
-		if (!extension_loaded('bcmath')) {
600
-			$recommendedPHPModules[] = 'bcmath';
601
-		}
602
-
603
-		if (!extension_loaded('gmp')) {
604
-			$recommendedPHPModules[] = 'gmp';
605
-		}
606
-
607
-		if ($this->config->getAppValue('theming', 'enabled', 'no') === 'yes') {
608
-			if (!extension_loaded('imagick')) {
609
-				$recommendedPHPModules[] = 'imagick';
610
-			}
611
-		}
612
-
613
-		return $recommendedPHPModules;
614
-	}
615
-
616
-	protected function isMysqlUsedWithoutUTF8MB4(): bool {
617
-		return ($this->config->getSystemValue('dbtype', 'sqlite') === 'mysql') && ($this->config->getSystemValue('mysql.utf8mb4', false) === false);
618
-	}
619
-
620
-	protected function hasBigIntConversionPendingColumns(): array {
621
-		// copy of ConvertFilecacheBigInt::getColumnsByTable()
622
-		$tables = [
623
-			'activity' => ['activity_id', 'object_id'],
624
-			'activity_mq' => ['mail_id'],
625
-			'authtoken' => ['id'],
626
-			'bruteforce_attempts' => ['id'],
627
-			'filecache' => ['fileid', 'storage', 'parent', 'mimetype', 'mimepart', 'mtime', 'storage_mtime'],
628
-			'filecache_extended' => ['fileid'],
629
-			'file_locks' => ['id'],
630
-			'jobs' => ['id'],
631
-			'mimetypes' => ['id'],
632
-			'mounts' => ['id', 'storage_id', 'root_id', 'mount_id'],
633
-			'storages' => ['numeric_id'],
634
-		];
635
-
636
-		$schema = new SchemaWrapper($this->db);
637
-		$isSqlite = $this->db->getDatabasePlatform() instanceof SqlitePlatform;
638
-		$pendingColumns = [];
639
-
640
-		foreach ($tables as $tableName => $columns) {
641
-			if (!$schema->hasTable($tableName)) {
642
-				continue;
643
-			}
644
-
645
-			$table = $schema->getTable($tableName);
646
-			foreach ($columns as $columnName) {
647
-				$column = $table->getColumn($columnName);
648
-				$isAutoIncrement = $column->getAutoincrement();
649
-				$isAutoIncrementOnSqlite = $isSqlite && $isAutoIncrement;
650
-				if ($column->getType()->getName() !== Types::BIGINT && !$isAutoIncrementOnSqlite) {
651
-					$pendingColumns[] = $tableName . '.' . $columnName;
652
-				}
653
-			}
654
-		}
655
-
656
-		return $pendingColumns;
657
-	}
658
-
659
-	protected function isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(): bool {
660
-		$objectStore = $this->config->getSystemValue('objectstore', null);
661
-		$objectStoreMultibucket = $this->config->getSystemValue('objectstore_multibucket', null);
662
-
663
-		if (!isset($objectStoreMultibucket) && !isset($objectStore)) {
664
-			return true;
665
-		}
666
-
667
-		if (isset($objectStoreMultibucket['class']) && $objectStoreMultibucket['class'] !== 'OC\\Files\\ObjectStore\\S3') {
668
-			return true;
669
-		}
670
-
671
-		if (isset($objectStore['class']) && $objectStore['class'] !== 'OC\\Files\\ObjectStore\\S3') {
672
-			return true;
673
-		}
674
-
675
-		$tempPath = sys_get_temp_dir();
676
-		if (!is_dir($tempPath)) {
677
-			$this->logger->error('Error while checking the temporary PHP path - it was not properly set to a directory. value: ' . $tempPath);
678
-			return false;
679
-		}
680
-		$freeSpaceInTemp = disk_free_space($tempPath);
681
-		if ($freeSpaceInTemp === false) {
682
-			$this->logger->error('Error while checking the available disk space of temporary PHP path - no free disk space returned. temporary path: ' . $tempPath);
683
-			return false;
684
-		}
685
-
686
-		$freeSpaceInTempInGB = $freeSpaceInTemp / 1024 / 1024 / 1024;
687
-		if ($freeSpaceInTempInGB > 50) {
688
-			return true;
689
-		}
690
-
691
-		$this->logger->warning('Checking the available space in the temporary path resulted in ' . round($freeSpaceInTempInGB, 1) . ' GB instead of the recommended 50GB. Path: ' . $tempPath);
692
-		return false;
693
-	}
694
-
695
-	protected function imageMagickLacksSVGSupport(): bool {
696
-		return extension_loaded('imagick') && count(\Imagick::queryFormats('SVG')) === 0;
697
-	}
698
-
699
-	/**
700
-	 * @return DataResponse
701
-	 */
702
-	public function check() {
703
-		$phpDefaultCharset = new PhpDefaultCharset();
704
-		$phpOutputBuffering = new PhpOutputBuffering();
705
-		$legacySSEKeyFormat = new LegacySSEKeyFormat($this->l10n, $this->config, $this->urlGenerator);
706
-		$checkUserCertificates = new CheckUserCertificates($this->l10n, $this->config, $this->urlGenerator);
707
-
708
-		return new DataResponse(
709
-			[
710
-				'isGetenvServerWorking' => !empty(getenv('PATH')),
711
-				'isReadOnlyConfig' => $this->isReadOnlyConfig(),
712
-				'hasValidTransactionIsolationLevel' => $this->hasValidTransactionIsolationLevel(),
713
-				'hasFileinfoInstalled' => $this->hasFileinfoInstalled(),
714
-				'hasWorkingFileLocking' => $this->hasWorkingFileLocking(),
715
-				'suggestedOverwriteCliURL' => $this->getSuggestedOverwriteCliURL(),
716
-				'cronInfo' => $this->getLastCronInfo(),
717
-				'cronErrors' => $this->getCronErrors(),
718
-				'serverHasInternetConnectionProblems' => $this->hasInternetConnectivityProblems(),
719
-				'isMemcacheConfigured' => $this->isMemcacheConfigured(),
720
-				'memcacheDocs' => $this->urlGenerator->linkToDocs('admin-performance'),
721
-				'isRandomnessSecure' => $this->isRandomnessSecure(),
722
-				'securityDocs' => $this->urlGenerator->linkToDocs('admin-security'),
723
-				'isUsedTlsLibOutdated' => $this->isUsedTlsLibOutdated(),
724
-				'phpSupported' => $this->isPhpSupported(),
725
-				'forwardedForHeadersWorking' => $this->forwardedForHeadersWorking(),
726
-				'reverseProxyDocs' => $this->urlGenerator->linkToDocs('admin-reverse-proxy'),
727
-				'isCorrectMemcachedPHPModuleInstalled' => $this->isCorrectMemcachedPHPModuleInstalled(),
728
-				'hasPassedCodeIntegrityCheck' => $this->checker->hasPassedCheck(),
729
-				'codeIntegrityCheckerDocumentation' => $this->urlGenerator->linkToDocs('admin-code-integrity'),
730
-				'isOpcacheProperlySetup' => $this->isOpcacheProperlySetup(),
731
-				'hasOpcacheLoaded' => $this->hasOpcacheLoaded(),
732
-				'phpOpcacheDocumentation' => $this->urlGenerator->linkToDocs('admin-php-opcache'),
733
-				'isSettimelimitAvailable' => $this->isSettimelimitAvailable(),
734
-				'hasFreeTypeSupport' => $this->hasFreeTypeSupport(),
735
-				'missingPrimaryKeys' => $this->hasMissingPrimaryKeys(),
736
-				'missingIndexes' => $this->hasMissingIndexes(),
737
-				'missingColumns' => $this->hasMissingColumns(),
738
-				'isSqliteUsed' => $this->isSqliteUsed(),
739
-				'databaseConversionDocumentation' => $this->urlGenerator->linkToDocs('admin-db-conversion'),
740
-				'isMemoryLimitSufficient' => $this->memoryInfo->isMemoryLimitSufficient(),
741
-				'appDirsWithDifferentOwner' => $this->getAppDirsWithDifferentOwner(),
742
-				'recommendedPHPModules' => $this->hasRecommendedPHPModules(),
743
-				'pendingBigIntConversionColumns' => $this->hasBigIntConversionPendingColumns(),
744
-				'isMysqlUsedWithoutUTF8MB4' => $this->isMysqlUsedWithoutUTF8MB4(),
745
-				'isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed' => $this->isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(),
746
-				'reverseProxyGeneratedURL' => $this->urlGenerator->getAbsoluteURL('index.php'),
747
-				'imageMagickLacksSVGSupport' => $this->imageMagickLacksSVGSupport(),
748
-				PhpDefaultCharset::class => ['pass' => $phpDefaultCharset->run(), 'description' => $phpDefaultCharset->description(), 'severity' => $phpDefaultCharset->severity()],
749
-				PhpOutputBuffering::class => ['pass' => $phpOutputBuffering->run(), 'description' => $phpOutputBuffering->description(), 'severity' => $phpOutputBuffering->severity()],
750
-				LegacySSEKeyFormat::class => ['pass' => $legacySSEKeyFormat->run(), 'description' => $legacySSEKeyFormat->description(), 'severity' => $legacySSEKeyFormat->severity(), 'linkToDocumentation' => $legacySSEKeyFormat->linkToDocumentation()],
751
-				CheckUserCertificates::class => ['pass' => $checkUserCertificates->run(), 'description' => $checkUserCertificates->description(), 'severity' => $checkUserCertificates->severity(), 'elements' => $checkUserCertificates->elements()],
752
-			]
753
-		);
754
-	}
394
+            $formattedTextResponse .= print_r($completeResults, true);
395
+        } else {
396
+            $formattedTextResponse = 'No errors have been found.';
397
+        }
398
+
399
+
400
+        $response = new DataDisplayResponse(
401
+            $formattedTextResponse,
402
+            Http::STATUS_OK,
403
+            [
404
+                'Content-Type' => 'text/plain',
405
+            ]
406
+        );
407
+
408
+        return $response;
409
+    }
410
+
411
+    /**
412
+     * Checks whether a PHP opcache is properly set up
413
+     * @return bool
414
+     */
415
+    protected function isOpcacheProperlySetup() {
416
+        if (!$this->iniGetWrapper->getBool('opcache.enable')) {
417
+            return false;
418
+        }
419
+
420
+        if (!$this->iniGetWrapper->getBool('opcache.save_comments')) {
421
+            return false;
422
+        }
423
+
424
+        if ($this->iniGetWrapper->getNumeric('opcache.max_accelerated_files') < 10000) {
425
+            return false;
426
+        }
427
+
428
+        if ($this->iniGetWrapper->getNumeric('opcache.memory_consumption') < 128) {
429
+            return false;
430
+        }
431
+
432
+        if ($this->iniGetWrapper->getNumeric('opcache.interned_strings_buffer') < 8) {
433
+            return false;
434
+        }
435
+
436
+        return true;
437
+    }
438
+
439
+    /**
440
+     * Check if the required FreeType functions are present
441
+     * @return bool
442
+     */
443
+    protected function hasFreeTypeSupport() {
444
+        return function_exists('imagettfbbox') && function_exists('imagettftext');
445
+    }
446
+
447
+    protected function hasMissingIndexes(): array {
448
+        $indexInfo = new MissingIndexInformation();
449
+        // Dispatch event so apps can also hint for pending index updates if needed
450
+        $event = new GenericEvent($indexInfo);
451
+        $this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_INDEXES_EVENT, $event);
452
+
453
+        return $indexInfo->getListOfMissingIndexes();
454
+    }
455
+
456
+    protected function hasMissingPrimaryKeys(): array {
457
+        $info = new MissingPrimaryKeyInformation();
458
+        // Dispatch event so apps can also hint for pending index updates if needed
459
+        $event = new GenericEvent($info);
460
+        $this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_PRIMARY_KEYS_EVENT, $event);
461
+
462
+        return $info->getListOfMissingPrimaryKeys();
463
+    }
464
+
465
+    protected function hasMissingColumns(): array {
466
+        $indexInfo = new MissingColumnInformation();
467
+        // Dispatch event so apps can also hint for pending index updates if needed
468
+        $event = new GenericEvent($indexInfo);
469
+        $this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_COLUMNS_EVENT, $event);
470
+
471
+        return $indexInfo->getListOfMissingColumns();
472
+    }
473
+
474
+    protected function isSqliteUsed() {
475
+        return strpos($this->config->getSystemValue('dbtype'), 'sqlite') !== false;
476
+    }
477
+
478
+    protected function isReadOnlyConfig(): bool {
479
+        return \OC_Helper::isReadOnlyConfigEnabled();
480
+    }
481
+
482
+    protected function hasValidTransactionIsolationLevel(): bool {
483
+        try {
484
+            if ($this->db->getDatabasePlatform() instanceof SqlitePlatform) {
485
+                return true;
486
+            }
487
+
488
+            return $this->db->getTransactionIsolation() === Connection::TRANSACTION_READ_COMMITTED;
489
+        } catch (DBALException $e) {
490
+            // ignore
491
+        }
492
+
493
+        return true;
494
+    }
495
+
496
+    protected function hasFileinfoInstalled(): bool {
497
+        return \OC_Util::fileInfoLoaded();
498
+    }
499
+
500
+    protected function hasWorkingFileLocking(): bool {
501
+        return !($this->lockingProvider instanceof NoopLockingProvider);
502
+    }
503
+
504
+    protected function getSuggestedOverwriteCliURL(): string {
505
+        $suggestedOverwriteCliUrl = '';
506
+        if ($this->config->getSystemValue('overwrite.cli.url', '') === '') {
507
+            $suggestedOverwriteCliUrl = $this->request->getServerProtocol() . '://' . $this->request->getInsecureServerHost() . \OC::$WEBROOT;
508
+            if (!$this->config->getSystemValue('config_is_read_only', false)) {
509
+                // Set the overwrite URL when it was not set yet.
510
+                $this->config->setSystemValue('overwrite.cli.url', $suggestedOverwriteCliUrl);
511
+                $suggestedOverwriteCliUrl = '';
512
+            }
513
+        }
514
+        return $suggestedOverwriteCliUrl;
515
+    }
516
+
517
+    protected function getLastCronInfo(): array {
518
+        $lastCronRun = $this->config->getAppValue('core', 'lastcron', 0);
519
+        return [
520
+            'diffInSeconds' => time() - $lastCronRun,
521
+            'relativeTime' => $this->dateTimeFormatter->formatTimeSpan($lastCronRun),
522
+            'backgroundJobsUrl' => $this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'server']) . '#backgroundjobs',
523
+        ];
524
+    }
525
+
526
+    protected function getCronErrors() {
527
+        $errors = json_decode($this->config->getAppValue('core', 'cronErrors', ''), true);
528
+
529
+        if (is_array($errors)) {
530
+            return $errors;
531
+        }
532
+
533
+        return [];
534
+    }
535
+
536
+    protected function hasOpcacheLoaded(): bool {
537
+        return extension_loaded('Zend OPcache');
538
+    }
539
+
540
+    /**
541
+     * Iterates through the configured app roots and
542
+     * tests if the subdirectories are owned by the same user than the current user.
543
+     *
544
+     * @return array
545
+     */
546
+    protected function getAppDirsWithDifferentOwner(): array {
547
+        $currentUser = posix_getuid();
548
+        $appDirsWithDifferentOwner = [[]];
549
+
550
+        foreach (OC::$APPSROOTS as $appRoot) {
551
+            if ($appRoot['writable'] === true) {
552
+                $appDirsWithDifferentOwner[] = $this->getAppDirsWithDifferentOwnerForAppRoot($currentUser, $appRoot);
553
+            }
554
+        }
555
+
556
+        $appDirsWithDifferentOwner = array_merge(...$appDirsWithDifferentOwner);
557
+        sort($appDirsWithDifferentOwner);
558
+
559
+        return $appDirsWithDifferentOwner;
560
+    }
561
+
562
+    /**
563
+     * Tests if the directories for one apps directory are writable by the current user.
564
+     *
565
+     * @param int $currentUser The current user
566
+     * @param array $appRoot The app root config
567
+     * @return string[] The none writable directory paths inside the app root
568
+     */
569
+    private function getAppDirsWithDifferentOwnerForAppRoot(int $currentUser, array $appRoot): array {
570
+        $appDirsWithDifferentOwner = [];
571
+        $appsPath = $appRoot['path'];
572
+        $appsDir = new DirectoryIterator($appRoot['path']);
573
+
574
+        foreach ($appsDir as $fileInfo) {
575
+            if ($fileInfo->isDir() && !$fileInfo->isDot()) {
576
+                $absAppPath = $appsPath . DIRECTORY_SEPARATOR . $fileInfo->getFilename();
577
+                $appDirUser = fileowner($absAppPath);
578
+                if ($appDirUser !== $currentUser) {
579
+                    $appDirsWithDifferentOwner[] = $absAppPath;
580
+                }
581
+            }
582
+        }
583
+
584
+        return $appDirsWithDifferentOwner;
585
+    }
586
+
587
+    /**
588
+     * Checks for potential PHP modules that would improve the instance
589
+     *
590
+     * @return string[] A list of PHP modules that is recommended
591
+     */
592
+    protected function hasRecommendedPHPModules(): array {
593
+        $recommendedPHPModules = [];
594
+
595
+        if (!extension_loaded('intl')) {
596
+            $recommendedPHPModules[] = 'intl';
597
+        }
598
+
599
+        if (!extension_loaded('bcmath')) {
600
+            $recommendedPHPModules[] = 'bcmath';
601
+        }
602
+
603
+        if (!extension_loaded('gmp')) {
604
+            $recommendedPHPModules[] = 'gmp';
605
+        }
606
+
607
+        if ($this->config->getAppValue('theming', 'enabled', 'no') === 'yes') {
608
+            if (!extension_loaded('imagick')) {
609
+                $recommendedPHPModules[] = 'imagick';
610
+            }
611
+        }
612
+
613
+        return $recommendedPHPModules;
614
+    }
615
+
616
+    protected function isMysqlUsedWithoutUTF8MB4(): bool {
617
+        return ($this->config->getSystemValue('dbtype', 'sqlite') === 'mysql') && ($this->config->getSystemValue('mysql.utf8mb4', false) === false);
618
+    }
619
+
620
+    protected function hasBigIntConversionPendingColumns(): array {
621
+        // copy of ConvertFilecacheBigInt::getColumnsByTable()
622
+        $tables = [
623
+            'activity' => ['activity_id', 'object_id'],
624
+            'activity_mq' => ['mail_id'],
625
+            'authtoken' => ['id'],
626
+            'bruteforce_attempts' => ['id'],
627
+            'filecache' => ['fileid', 'storage', 'parent', 'mimetype', 'mimepart', 'mtime', 'storage_mtime'],
628
+            'filecache_extended' => ['fileid'],
629
+            'file_locks' => ['id'],
630
+            'jobs' => ['id'],
631
+            'mimetypes' => ['id'],
632
+            'mounts' => ['id', 'storage_id', 'root_id', 'mount_id'],
633
+            'storages' => ['numeric_id'],
634
+        ];
635
+
636
+        $schema = new SchemaWrapper($this->db);
637
+        $isSqlite = $this->db->getDatabasePlatform() instanceof SqlitePlatform;
638
+        $pendingColumns = [];
639
+
640
+        foreach ($tables as $tableName => $columns) {
641
+            if (!$schema->hasTable($tableName)) {
642
+                continue;
643
+            }
644
+
645
+            $table = $schema->getTable($tableName);
646
+            foreach ($columns as $columnName) {
647
+                $column = $table->getColumn($columnName);
648
+                $isAutoIncrement = $column->getAutoincrement();
649
+                $isAutoIncrementOnSqlite = $isSqlite && $isAutoIncrement;
650
+                if ($column->getType()->getName() !== Types::BIGINT && !$isAutoIncrementOnSqlite) {
651
+                    $pendingColumns[] = $tableName . '.' . $columnName;
652
+                }
653
+            }
654
+        }
655
+
656
+        return $pendingColumns;
657
+    }
658
+
659
+    protected function isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(): bool {
660
+        $objectStore = $this->config->getSystemValue('objectstore', null);
661
+        $objectStoreMultibucket = $this->config->getSystemValue('objectstore_multibucket', null);
662
+
663
+        if (!isset($objectStoreMultibucket) && !isset($objectStore)) {
664
+            return true;
665
+        }
666
+
667
+        if (isset($objectStoreMultibucket['class']) && $objectStoreMultibucket['class'] !== 'OC\\Files\\ObjectStore\\S3') {
668
+            return true;
669
+        }
670
+
671
+        if (isset($objectStore['class']) && $objectStore['class'] !== 'OC\\Files\\ObjectStore\\S3') {
672
+            return true;
673
+        }
674
+
675
+        $tempPath = sys_get_temp_dir();
676
+        if (!is_dir($tempPath)) {
677
+            $this->logger->error('Error while checking the temporary PHP path - it was not properly set to a directory. value: ' . $tempPath);
678
+            return false;
679
+        }
680
+        $freeSpaceInTemp = disk_free_space($tempPath);
681
+        if ($freeSpaceInTemp === false) {
682
+            $this->logger->error('Error while checking the available disk space of temporary PHP path - no free disk space returned. temporary path: ' . $tempPath);
683
+            return false;
684
+        }
685
+
686
+        $freeSpaceInTempInGB = $freeSpaceInTemp / 1024 / 1024 / 1024;
687
+        if ($freeSpaceInTempInGB > 50) {
688
+            return true;
689
+        }
690
+
691
+        $this->logger->warning('Checking the available space in the temporary path resulted in ' . round($freeSpaceInTempInGB, 1) . ' GB instead of the recommended 50GB. Path: ' . $tempPath);
692
+        return false;
693
+    }
694
+
695
+    protected function imageMagickLacksSVGSupport(): bool {
696
+        return extension_loaded('imagick') && count(\Imagick::queryFormats('SVG')) === 0;
697
+    }
698
+
699
+    /**
700
+     * @return DataResponse
701
+     */
702
+    public function check() {
703
+        $phpDefaultCharset = new PhpDefaultCharset();
704
+        $phpOutputBuffering = new PhpOutputBuffering();
705
+        $legacySSEKeyFormat = new LegacySSEKeyFormat($this->l10n, $this->config, $this->urlGenerator);
706
+        $checkUserCertificates = new CheckUserCertificates($this->l10n, $this->config, $this->urlGenerator);
707
+
708
+        return new DataResponse(
709
+            [
710
+                'isGetenvServerWorking' => !empty(getenv('PATH')),
711
+                'isReadOnlyConfig' => $this->isReadOnlyConfig(),
712
+                'hasValidTransactionIsolationLevel' => $this->hasValidTransactionIsolationLevel(),
713
+                'hasFileinfoInstalled' => $this->hasFileinfoInstalled(),
714
+                'hasWorkingFileLocking' => $this->hasWorkingFileLocking(),
715
+                'suggestedOverwriteCliURL' => $this->getSuggestedOverwriteCliURL(),
716
+                'cronInfo' => $this->getLastCronInfo(),
717
+                'cronErrors' => $this->getCronErrors(),
718
+                'serverHasInternetConnectionProblems' => $this->hasInternetConnectivityProblems(),
719
+                'isMemcacheConfigured' => $this->isMemcacheConfigured(),
720
+                'memcacheDocs' => $this->urlGenerator->linkToDocs('admin-performance'),
721
+                'isRandomnessSecure' => $this->isRandomnessSecure(),
722
+                'securityDocs' => $this->urlGenerator->linkToDocs('admin-security'),
723
+                'isUsedTlsLibOutdated' => $this->isUsedTlsLibOutdated(),
724
+                'phpSupported' => $this->isPhpSupported(),
725
+                'forwardedForHeadersWorking' => $this->forwardedForHeadersWorking(),
726
+                'reverseProxyDocs' => $this->urlGenerator->linkToDocs('admin-reverse-proxy'),
727
+                'isCorrectMemcachedPHPModuleInstalled' => $this->isCorrectMemcachedPHPModuleInstalled(),
728
+                'hasPassedCodeIntegrityCheck' => $this->checker->hasPassedCheck(),
729
+                'codeIntegrityCheckerDocumentation' => $this->urlGenerator->linkToDocs('admin-code-integrity'),
730
+                'isOpcacheProperlySetup' => $this->isOpcacheProperlySetup(),
731
+                'hasOpcacheLoaded' => $this->hasOpcacheLoaded(),
732
+                'phpOpcacheDocumentation' => $this->urlGenerator->linkToDocs('admin-php-opcache'),
733
+                'isSettimelimitAvailable' => $this->isSettimelimitAvailable(),
734
+                'hasFreeTypeSupport' => $this->hasFreeTypeSupport(),
735
+                'missingPrimaryKeys' => $this->hasMissingPrimaryKeys(),
736
+                'missingIndexes' => $this->hasMissingIndexes(),
737
+                'missingColumns' => $this->hasMissingColumns(),
738
+                'isSqliteUsed' => $this->isSqliteUsed(),
739
+                'databaseConversionDocumentation' => $this->urlGenerator->linkToDocs('admin-db-conversion'),
740
+                'isMemoryLimitSufficient' => $this->memoryInfo->isMemoryLimitSufficient(),
741
+                'appDirsWithDifferentOwner' => $this->getAppDirsWithDifferentOwner(),
742
+                'recommendedPHPModules' => $this->hasRecommendedPHPModules(),
743
+                'pendingBigIntConversionColumns' => $this->hasBigIntConversionPendingColumns(),
744
+                'isMysqlUsedWithoutUTF8MB4' => $this->isMysqlUsedWithoutUTF8MB4(),
745
+                'isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed' => $this->isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(),
746
+                'reverseProxyGeneratedURL' => $this->urlGenerator->getAbsoluteURL('index.php'),
747
+                'imageMagickLacksSVGSupport' => $this->imageMagickLacksSVGSupport(),
748
+                PhpDefaultCharset::class => ['pass' => $phpDefaultCharset->run(), 'description' => $phpDefaultCharset->description(), 'severity' => $phpDefaultCharset->severity()],
749
+                PhpOutputBuffering::class => ['pass' => $phpOutputBuffering->run(), 'description' => $phpOutputBuffering->description(), 'severity' => $phpOutputBuffering->severity()],
750
+                LegacySSEKeyFormat::class => ['pass' => $legacySSEKeyFormat->run(), 'description' => $legacySSEKeyFormat->description(), 'severity' => $legacySSEKeyFormat->severity(), 'linkToDocumentation' => $legacySSEKeyFormat->linkToDocumentation()],
751
+                CheckUserCertificates::class => ['pass' => $checkUserCertificates->run(), 'description' => $checkUserCertificates->description(), 'severity' => $checkUserCertificates->severity(), 'elements' => $checkUserCertificates->elements()],
752
+            ]
753
+        );
754
+    }
755 755
 }
Please login to merge, or discard this patch.
apps/federatedfilesharing/lib/Migration/Version1010Date20200630191755.php 1 patch
Indentation   +23 added lines, -23 removed lines patch added patch discarded remove patch
@@ -32,29 +32,29 @@
 block discarded – undo
32 32
 use OCP\Migration\SimpleMigrationStep;
33 33
 
34 34
 class Version1010Date20200630191755 extends SimpleMigrationStep {
35
-	/**
36
-	 * @param IOutput $output
37
-	 * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
38
-	 * @param array $options
39
-	 * @return null|ISchemaWrapper
40
-	 */
41
-	public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
42
-		/** @var ISchemaWrapper $schema */
43
-		$schema = $schemaClosure();
35
+    /**
36
+     * @param IOutput $output
37
+     * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
38
+     * @param array $options
39
+     * @return null|ISchemaWrapper
40
+     */
41
+    public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
42
+        /** @var ISchemaWrapper $schema */
43
+        $schema = $schemaClosure();
44 44
 
45
-		if (!$schema->hasTable('federated_reshares')) {
46
-			$table = $schema->createTable('federated_reshares');
47
-			$table->addColumn('share_id', Types::INTEGER, [
48
-				'notnull' => true,
49
-				'length' => 4,
50
-			]);
51
-			$table->addColumn('remote_id', Types::INTEGER, [
52
-				'notnull' => true,
53
-				'length' => 4,
54
-			]);
55
-			$table->setPrimaryKey(['share_id'], 'federated_res_pk');
45
+        if (!$schema->hasTable('federated_reshares')) {
46
+            $table = $schema->createTable('federated_reshares');
47
+            $table->addColumn('share_id', Types::INTEGER, [
48
+                'notnull' => true,
49
+                'length' => 4,
50
+            ]);
51
+            $table->addColumn('remote_id', Types::INTEGER, [
52
+                'notnull' => true,
53
+                'length' => 4,
54
+            ]);
55
+            $table->setPrimaryKey(['share_id'], 'federated_res_pk');
56 56
 //			$table->addUniqueIndex(['share_id'], 'share_id_index');
57
-		}
58
-		return $schema;
59
-	}
57
+        }
58
+        return $schema;
59
+    }
60 60
 }
Please login to merge, or discard this patch.
lib/private/Security/CredentialsManager.php 1 patch
Indentation   +100 added lines, -100 removed lines patch added patch discarded remove patch
@@ -35,104 +35,104 @@
 block discarded – undo
35 35
  * @package OC\Security
36 36
  */
37 37
 class CredentialsManager implements ICredentialsManager {
38
-	public const DB_TABLE = 'storages_credentials';
39
-
40
-	/** @var ICrypto */
41
-	protected $crypto;
42
-
43
-	/** @var IDBConnection */
44
-	protected $dbConnection;
45
-
46
-	/**
47
-	 * @param ICrypto $crypto
48
-	 * @param IDBConnection $dbConnection
49
-	 */
50
-	public function __construct(ICrypto $crypto, IDBConnection $dbConnection) {
51
-		$this->crypto = $crypto;
52
-		$this->dbConnection = $dbConnection;
53
-	}
54
-
55
-	/**
56
-	 * Store a set of credentials
57
-	 *
58
-	 * @param string $userId empty string for system-wide credentials
59
-	 * @param string $identifier
60
-	 * @param mixed $credentials
61
-	 */
62
-	public function store($userId, $identifier, $credentials) {
63
-		$value = $this->crypto->encrypt(json_encode($credentials));
64
-
65
-		$this->dbConnection->setValues(self::DB_TABLE, [
66
-			'user' => (string)$userId,
67
-			'identifier' => $identifier,
68
-		], [
69
-			'credentials' => $value,
70
-		]);
71
-	}
72
-
73
-	/**
74
-	 * Retrieve a set of credentials
75
-	 *
76
-	 * @param string $userId empty string for system-wide credentials
77
-	 * @param string $identifier
78
-	 * @return mixed
79
-	 */
80
-	public function retrieve($userId, $identifier) {
81
-		$qb = $this->dbConnection->getQueryBuilder();
82
-		$qb->select('credentials')
83
-			->from(self::DB_TABLE)
84
-			->where($qb->expr()->eq('identifier', $qb->createNamedParameter($identifier)));
85
-
86
-		if ($userId === '') {
87
-			$qb->andWhere($qb->expr()->emptyString('user'));
88
-		} else {
89
-			$qb->andWhere($qb->expr()->eq('user', $qb->createNamedParameter((string)$userId)));
90
-		}
91
-
92
-		$qResult = $qb->execute();
93
-		$result = $qResult->fetch();
94
-		$qResult->closeCursor();
95
-
96
-		if (!$result) {
97
-			return null;
98
-		}
99
-		$value = $result['credentials'];
100
-
101
-		return json_decode($this->crypto->decrypt($value), true);
102
-	}
103
-
104
-	/**
105
-	 * Delete a set of credentials
106
-	 *
107
-	 * @param string $userId empty string for system-wide credentials
108
-	 * @param string $identifier
109
-	 * @return int rows removed
110
-	 */
111
-	public function delete($userId, $identifier) {
112
-		$qb = $this->dbConnection->getQueryBuilder();
113
-		$qb->delete(self::DB_TABLE)
114
-			->where($qb->expr()->eq('identifier', $qb->createNamedParameter($identifier)));
115
-
116
-		if ($userId === '') {
117
-			$qb->andWhere($qb->expr()->emptyString('user'));
118
-		} else {
119
-			$qb->andWhere($qb->expr()->eq('user', $qb->createNamedParameter((string)$userId)));
120
-		}
121
-
122
-		return $qb->execute();
123
-	}
124
-
125
-	/**
126
-	 * Erase all credentials stored for a user
127
-	 *
128
-	 * @param string $userId
129
-	 * @return int rows removed
130
-	 */
131
-	public function erase($userId) {
132
-		$qb = $this->dbConnection->getQueryBuilder();
133
-		$qb->delete(self::DB_TABLE)
134
-			->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
135
-		;
136
-		return $qb->execute();
137
-	}
38
+    public const DB_TABLE = 'storages_credentials';
39
+
40
+    /** @var ICrypto */
41
+    protected $crypto;
42
+
43
+    /** @var IDBConnection */
44
+    protected $dbConnection;
45
+
46
+    /**
47
+     * @param ICrypto $crypto
48
+     * @param IDBConnection $dbConnection
49
+     */
50
+    public function __construct(ICrypto $crypto, IDBConnection $dbConnection) {
51
+        $this->crypto = $crypto;
52
+        $this->dbConnection = $dbConnection;
53
+    }
54
+
55
+    /**
56
+     * Store a set of credentials
57
+     *
58
+     * @param string $userId empty string for system-wide credentials
59
+     * @param string $identifier
60
+     * @param mixed $credentials
61
+     */
62
+    public function store($userId, $identifier, $credentials) {
63
+        $value = $this->crypto->encrypt(json_encode($credentials));
64
+
65
+        $this->dbConnection->setValues(self::DB_TABLE, [
66
+            'user' => (string)$userId,
67
+            'identifier' => $identifier,
68
+        ], [
69
+            'credentials' => $value,
70
+        ]);
71
+    }
72
+
73
+    /**
74
+     * Retrieve a set of credentials
75
+     *
76
+     * @param string $userId empty string for system-wide credentials
77
+     * @param string $identifier
78
+     * @return mixed
79
+     */
80
+    public function retrieve($userId, $identifier) {
81
+        $qb = $this->dbConnection->getQueryBuilder();
82
+        $qb->select('credentials')
83
+            ->from(self::DB_TABLE)
84
+            ->where($qb->expr()->eq('identifier', $qb->createNamedParameter($identifier)));
85
+
86
+        if ($userId === '') {
87
+            $qb->andWhere($qb->expr()->emptyString('user'));
88
+        } else {
89
+            $qb->andWhere($qb->expr()->eq('user', $qb->createNamedParameter((string)$userId)));
90
+        }
91
+
92
+        $qResult = $qb->execute();
93
+        $result = $qResult->fetch();
94
+        $qResult->closeCursor();
95
+
96
+        if (!$result) {
97
+            return null;
98
+        }
99
+        $value = $result['credentials'];
100
+
101
+        return json_decode($this->crypto->decrypt($value), true);
102
+    }
103
+
104
+    /**
105
+     * Delete a set of credentials
106
+     *
107
+     * @param string $userId empty string for system-wide credentials
108
+     * @param string $identifier
109
+     * @return int rows removed
110
+     */
111
+    public function delete($userId, $identifier) {
112
+        $qb = $this->dbConnection->getQueryBuilder();
113
+        $qb->delete(self::DB_TABLE)
114
+            ->where($qb->expr()->eq('identifier', $qb->createNamedParameter($identifier)));
115
+
116
+        if ($userId === '') {
117
+            $qb->andWhere($qb->expr()->emptyString('user'));
118
+        } else {
119
+            $qb->andWhere($qb->expr()->eq('user', $qb->createNamedParameter((string)$userId)));
120
+        }
121
+
122
+        return $qb->execute();
123
+    }
124
+
125
+    /**
126
+     * Erase all credentials stored for a user
127
+     *
128
+     * @param string $userId
129
+     * @return int rows removed
130
+     */
131
+    public function erase($userId) {
132
+        $qb = $this->dbConnection->getQueryBuilder();
133
+        $qb->delete(self::DB_TABLE)
134
+            ->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
135
+        ;
136
+        return $qb->execute();
137
+    }
138 138
 }
Please login to merge, or discard this patch.