Completed
Push — master ( b15e29...582bb1 )
by
unknown
26:14
created
lib/public/IConfig.php 1 patch
Indentation   +221 added lines, -221 removed lines patch added patch discarded remove patch
@@ -15,248 +15,248 @@
 block discarded – undo
15 15
  * @since 6.0.0
16 16
  */
17 17
 interface IConfig {
18
-	/**
19
-	 * @since 8.2.0
20
-	 */
21
-	public const SENSITIVE_VALUE = '***REMOVED SENSITIVE VALUE***';
18
+    /**
19
+     * @since 8.2.0
20
+     */
21
+    public const SENSITIVE_VALUE = '***REMOVED SENSITIVE VALUE***';
22 22
 
23
-	/**
24
-	 * Sets and deletes system wide values
25
-	 *
26
-	 * @param array $configs Associative array with `key => value` pairs
27
-	 *                       If value is null, the config key will be deleted
28
-	 * @throws HintException if config file is read-only
29
-	 * @since 8.0.0
30
-	 */
31
-	public function setSystemValues(array $configs);
23
+    /**
24
+     * Sets and deletes system wide values
25
+     *
26
+     * @param array $configs Associative array with `key => value` pairs
27
+     *                       If value is null, the config key will be deleted
28
+     * @throws HintException if config file is read-only
29
+     * @since 8.0.0
30
+     */
31
+    public function setSystemValues(array $configs);
32 32
 
33
-	/**
34
-	 * Sets a new system wide value
35
-	 *
36
-	 * @param string $key the key of the value, under which will be saved
37
-	 * @param mixed $value the value that should be stored
38
-	 * @throws HintException if config file is read-only
39
-	 * @since 8.0.0
40
-	 */
41
-	public function setSystemValue($key, $value);
33
+    /**
34
+     * Sets a new system wide value
35
+     *
36
+     * @param string $key the key of the value, under which will be saved
37
+     * @param mixed $value the value that should be stored
38
+     * @throws HintException if config file is read-only
39
+     * @since 8.0.0
40
+     */
41
+    public function setSystemValue($key, $value);
42 42
 
43
-	/**
44
-	 * Looks up a system wide defined value
45
-	 *
46
-	 * @param string $key the key of the value, under which it was saved
47
-	 * @param mixed $default the default value to be returned if the value isn't set
48
-	 * @return mixed the value or $default
49
-	 * @since 6.0.0 - parameter $default was added in 7.0.0
50
-	 */
51
-	public function getSystemValue($key, $default = '');
43
+    /**
44
+     * Looks up a system wide defined value
45
+     *
46
+     * @param string $key the key of the value, under which it was saved
47
+     * @param mixed $default the default value to be returned if the value isn't set
48
+     * @return mixed the value or $default
49
+     * @since 6.0.0 - parameter $default was added in 7.0.0
50
+     */
51
+    public function getSystemValue($key, $default = '');
52 52
 
53
-	/**
54
-	 * Looks up a boolean system wide defined value
55
-	 *
56
-	 * @param string $key the key of the value, under which it was saved
57
-	 * @param bool $default the default value to be returned if the value isn't set
58
-	 * @return bool the value or $default
59
-	 * @since 16.0.0
60
-	 */
61
-	public function getSystemValueBool(string $key, bool $default = false): bool;
53
+    /**
54
+     * Looks up a boolean system wide defined value
55
+     *
56
+     * @param string $key the key of the value, under which it was saved
57
+     * @param bool $default the default value to be returned if the value isn't set
58
+     * @return bool the value or $default
59
+     * @since 16.0.0
60
+     */
61
+    public function getSystemValueBool(string $key, bool $default = false): bool;
62 62
 
63
-	/**
64
-	 * Looks up an integer system wide defined value
65
-	 *
66
-	 * @param string $key the key of the value, under which it was saved
67
-	 * @param int $default the default value to be returned if the value isn't set
68
-	 * @return int the value or $default
69
-	 * @since 16.0.0
70
-	 */
71
-	public function getSystemValueInt(string $key, int $default = 0): int;
63
+    /**
64
+     * Looks up an integer system wide defined value
65
+     *
66
+     * @param string $key the key of the value, under which it was saved
67
+     * @param int $default the default value to be returned if the value isn't set
68
+     * @return int the value or $default
69
+     * @since 16.0.0
70
+     */
71
+    public function getSystemValueInt(string $key, int $default = 0): int;
72 72
 
73
-	/**
74
-	 * Looks up a string system wide defined value
75
-	 *
76
-	 * @param string $key the key of the value, under which it was saved
77
-	 * @param string $default the default value to be returned if the value isn't set
78
-	 * @return string the value or $default
79
-	 * @since 16.0.0
80
-	 */
81
-	public function getSystemValueString(string $key, string $default = ''): string;
73
+    /**
74
+     * Looks up a string system wide defined value
75
+     *
76
+     * @param string $key the key of the value, under which it was saved
77
+     * @param string $default the default value to be returned if the value isn't set
78
+     * @return string the value or $default
79
+     * @since 16.0.0
80
+     */
81
+    public function getSystemValueString(string $key, string $default = ''): string;
82 82
 
83
-	/**
84
-	 * Looks up a system wide defined value and filters out sensitive data
85
-	 *
86
-	 * @param string $key the key of the value, under which it was saved
87
-	 * @param mixed $default the default value to be returned if the value isn't set
88
-	 * @return mixed the value or $default
89
-	 * @since 8.2.0
90
-	 */
91
-	public function getFilteredSystemValue($key, $default = '');
83
+    /**
84
+     * Looks up a system wide defined value and filters out sensitive data
85
+     *
86
+     * @param string $key the key of the value, under which it was saved
87
+     * @param mixed $default the default value to be returned if the value isn't set
88
+     * @return mixed the value or $default
89
+     * @since 8.2.0
90
+     */
91
+    public function getFilteredSystemValue($key, $default = '');
92 92
 
93
-	/**
94
-	 * Delete a system wide defined value
95
-	 *
96
-	 * @param string $key the key of the value, under which it was saved
97
-	 * @since 8.0.0
98
-	 */
99
-	public function deleteSystemValue($key);
93
+    /**
94
+     * Delete a system wide defined value
95
+     *
96
+     * @param string $key the key of the value, under which it was saved
97
+     * @since 8.0.0
98
+     */
99
+    public function deleteSystemValue($key);
100 100
 
101
-	/**
102
-	 * Get all keys stored for an app
103
-	 *
104
-	 * @param string $appName the appName that we stored the value under
105
-	 * @return string[] the keys stored for the app
106
-	 * @since 8.0.0
107
-	 * @deprecated 29.0.0 Use {@see IAppConfig} directly
108
-	 */
109
-	public function getAppKeys($appName);
101
+    /**
102
+     * Get all keys stored for an app
103
+     *
104
+     * @param string $appName the appName that we stored the value under
105
+     * @return string[] the keys stored for the app
106
+     * @since 8.0.0
107
+     * @deprecated 29.0.0 Use {@see IAppConfig} directly
108
+     */
109
+    public function getAppKeys($appName);
110 110
 
111
-	/**
112
-	 * Writes a new app wide value
113
-	 *
114
-	 * @param string $appName the appName that we want to store the value under
115
-	 * @param string $key the key of the value, under which will be saved
116
-	 * @param string|float|int $value the value that should be stored
117
-	 * @return void
118
-	 * @since 6.0.0
119
-	 * @deprecated 29.0.0 Use {@see IAppConfig} directly
120
-	 */
121
-	public function setAppValue($appName, $key, $value);
111
+    /**
112
+     * Writes a new app wide value
113
+     *
114
+     * @param string $appName the appName that we want to store the value under
115
+     * @param string $key the key of the value, under which will be saved
116
+     * @param string|float|int $value the value that should be stored
117
+     * @return void
118
+     * @since 6.0.0
119
+     * @deprecated 29.0.0 Use {@see IAppConfig} directly
120
+     */
121
+    public function setAppValue($appName, $key, $value);
122 122
 
123
-	/**
124
-	 * Looks up an app wide defined value
125
-	 *
126
-	 * @param string $appName the appName that we stored the value under
127
-	 * @param string $key the key of the value, under which it was saved
128
-	 * @param string $default the default value to be returned if the value isn't set
129
-	 *
130
-	 * @return string the saved value
131
-	 * @since 6.0.0 - parameter $default was added in 7.0.0
132
-	 * @deprecated 29.0.0 Use {@see IAppConfig} directly
133
-	 */
134
-	public function getAppValue($appName, $key, $default = '');
123
+    /**
124
+     * Looks up an app wide defined value
125
+     *
126
+     * @param string $appName the appName that we stored the value under
127
+     * @param string $key the key of the value, under which it was saved
128
+     * @param string $default the default value to be returned if the value isn't set
129
+     *
130
+     * @return string the saved value
131
+     * @since 6.0.0 - parameter $default was added in 7.0.0
132
+     * @deprecated 29.0.0 Use {@see IAppConfig} directly
133
+     */
134
+    public function getAppValue($appName, $key, $default = '');
135 135
 
136
-	/**
137
-	 * Delete an app wide defined value
138
-	 *
139
-	 * @param string $appName the appName that we stored the value under
140
-	 * @param string $key the key of the value, under which it was saved
141
-	 * @since 8.0.0
142
-	 * @deprecated 29.0.0 Use {@see IAppConfig} directly
143
-	 */
144
-	public function deleteAppValue($appName, $key);
136
+    /**
137
+     * Delete an app wide defined value
138
+     *
139
+     * @param string $appName the appName that we stored the value under
140
+     * @param string $key the key of the value, under which it was saved
141
+     * @since 8.0.0
142
+     * @deprecated 29.0.0 Use {@see IAppConfig} directly
143
+     */
144
+    public function deleteAppValue($appName, $key);
145 145
 
146
-	/**
147
-	 * Removes all keys in appconfig belonging to the app
148
-	 *
149
-	 * @param string $appName the appName the configs are stored under
150
-	 * @since 8.0.0
151
-	 * @deprecated 29.0.0 Use {@see IAppConfig} directly
152
-	 */
153
-	public function deleteAppValues($appName);
146
+    /**
147
+     * Removes all keys in appconfig belonging to the app
148
+     *
149
+     * @param string $appName the appName the configs are stored under
150
+     * @since 8.0.0
151
+     * @deprecated 29.0.0 Use {@see IAppConfig} directly
152
+     */
153
+    public function deleteAppValues($appName);
154 154
 
155 155
 
156
-	/**
157
-	 * Set a user defined value
158
-	 *
159
-	 * @param string $userId the userId of the user that we want to store the value under
160
-	 * @param string $appName the appName that we want to store the value under
161
-	 * @param string $key the key under which the value is being stored
162
-	 * @param string $value the value that you want to store
163
-	 * @param string $preCondition only update if the config value was previously the value passed as $preCondition
164
-	 * @throws \OCP\PreConditionNotMetException if a precondition is specified and is not met
165
-	 * @throws \UnexpectedValueException when trying to store an unexpected value
166
-	 * @since 6.0.0 - parameter $precondition was added in 8.0.0
167
-	 * @deprecated 33.0.0 - use {@see IUserConfig} directly
168
-	 */
169
-	public function setUserValue($userId, $appName, $key, $value, $preCondition = null);
156
+    /**
157
+     * Set a user defined value
158
+     *
159
+     * @param string $userId the userId of the user that we want to store the value under
160
+     * @param string $appName the appName that we want to store the value under
161
+     * @param string $key the key under which the value is being stored
162
+     * @param string $value the value that you want to store
163
+     * @param string $preCondition only update if the config value was previously the value passed as $preCondition
164
+     * @throws \OCP\PreConditionNotMetException if a precondition is specified and is not met
165
+     * @throws \UnexpectedValueException when trying to store an unexpected value
166
+     * @since 6.0.0 - parameter $precondition was added in 8.0.0
167
+     * @deprecated 33.0.0 - use {@see IUserConfig} directly
168
+     */
169
+    public function setUserValue($userId, $appName, $key, $value, $preCondition = null);
170 170
 
171
-	/**
172
-	 * Shortcut for getting a user defined value
173
-	 *
174
-	 * @param ?string $userId the userId of the user that we want to store the value under
175
-	 * @param string $appName the appName that we stored the value under
176
-	 * @param string $key the key under which the value is being stored
177
-	 * @param mixed $default the default value to be returned if the value isn't set
178
-	 * @return string
179
-	 * @since 6.0.0 - parameter $default was added in 7.0.0
180
-	 * @deprecated 33.0.0 - use {@see IUserConfig} directly
181
-	 */
182
-	public function getUserValue($userId, $appName, $key, $default = '');
171
+    /**
172
+     * Shortcut for getting a user defined value
173
+     *
174
+     * @param ?string $userId the userId of the user that we want to store the value under
175
+     * @param string $appName the appName that we stored the value under
176
+     * @param string $key the key under which the value is being stored
177
+     * @param mixed $default the default value to be returned if the value isn't set
178
+     * @return string
179
+     * @since 6.0.0 - parameter $default was added in 7.0.0
180
+     * @deprecated 33.0.0 - use {@see IUserConfig} directly
181
+     */
182
+    public function getUserValue($userId, $appName, $key, $default = '');
183 183
 
184
-	/**
185
-	 * Fetches a mapped list of userId -> value, for a specified app and key and a list of user IDs.
186
-	 *
187
-	 * @param string $appName app to get the value for
188
-	 * @param string $key the key to get the value for
189
-	 * @param array $userIds the user IDs to fetch the values for
190
-	 * @return array Mapped values: userId => value
191
-	 * @deprecated 33.0.0 - use {@see IUserConfig::getValuesByUsers} directly
192
-	 * @since 8.0.0
193
-	 */
194
-	public function getUserValueForUsers($appName, $key, $userIds);
184
+    /**
185
+     * Fetches a mapped list of userId -> value, for a specified app and key and a list of user IDs.
186
+     *
187
+     * @param string $appName app to get the value for
188
+     * @param string $key the key to get the value for
189
+     * @param array $userIds the user IDs to fetch the values for
190
+     * @return array Mapped values: userId => value
191
+     * @deprecated 33.0.0 - use {@see IUserConfig::getValuesByUsers} directly
192
+     * @since 8.0.0
193
+     */
194
+    public function getUserValueForUsers($appName, $key, $userIds);
195 195
 
196
-	/**
197
-	 * Get the keys of all stored by an app for the user
198
-	 *
199
-	 * @param string $userId the userId of the user that we want to store the value under
200
-	 * @param string $appName the appName that we stored the value under
201
-	 * @return string[]
202
-	 * @since 8.0.0
203
-	 * @deprecated 33.0.0 - use {@see IUserConfig::getKeys} directly
204
-	 */
205
-	public function getUserKeys($userId, $appName);
196
+    /**
197
+     * Get the keys of all stored by an app for the user
198
+     *
199
+     * @param string $userId the userId of the user that we want to store the value under
200
+     * @param string $appName the appName that we stored the value under
201
+     * @return string[]
202
+     * @since 8.0.0
203
+     * @deprecated 33.0.0 - use {@see IUserConfig::getKeys} directly
204
+     */
205
+    public function getUserKeys($userId, $appName);
206 206
 
207
-	/**
208
-	 * Get all user configs sorted by app of one user
209
-	 *
210
-	 * @param string $userId the userId of the user that we want to get all values from
211
-	 * @psalm-return array<string, array<string, string>>
212
-	 * @return array[] - 2 dimensional array with the following structure:
213
-	 *                 [ $appId =>
214
-	 *                 [ $key => $value ]
215
-	 *                 ]
216
-	 * @since 24.0.0
217
-	 * @deprecated 33.0.0 - use {@see IUserConfig::getAllValues} directly
218
-	 */
219
-	public function getAllUserValues(string $userId): array;
207
+    /**
208
+     * Get all user configs sorted by app of one user
209
+     *
210
+     * @param string $userId the userId of the user that we want to get all values from
211
+     * @psalm-return array<string, array<string, string>>
212
+     * @return array[] - 2 dimensional array with the following structure:
213
+     *                 [ $appId =>
214
+     *                 [ $key => $value ]
215
+     *                 ]
216
+     * @since 24.0.0
217
+     * @deprecated 33.0.0 - use {@see IUserConfig::getAllValues} directly
218
+     */
219
+    public function getAllUserValues(string $userId): array;
220 220
 
221
-	/**
222
-	 * Delete a user value
223
-	 *
224
-	 * @param string $userId the userId of the user that we want to store the value under
225
-	 * @param string $appName the appName that we stored the value under
226
-	 * @param string $key the key under which the value is being stored
227
-	 * @since 8.0.0
228
-	 * @deprecated 33.0.0 - use {@see IUserConfig::deleteUserConfig} directly
229
-	 */
230
-	public function deleteUserValue($userId, $appName, $key);
221
+    /**
222
+     * Delete a user value
223
+     *
224
+     * @param string $userId the userId of the user that we want to store the value under
225
+     * @param string $appName the appName that we stored the value under
226
+     * @param string $key the key under which the value is being stored
227
+     * @since 8.0.0
228
+     * @deprecated 33.0.0 - use {@see IUserConfig::deleteUserConfig} directly
229
+     */
230
+    public function deleteUserValue($userId, $appName, $key);
231 231
 
232
-	/**
233
-	 * Delete all user values
234
-	 *
235
-	 * @param string $userId the userId of the user that we want to remove all values from
236
-	 * @since 8.0.0
237
-	 * @deprecated 33.0.0 - use {@see IUserConfig::deleteAllUserConfig} directly
238
-	 */
239
-	public function deleteAllUserValues($userId);
232
+    /**
233
+     * Delete all user values
234
+     *
235
+     * @param string $userId the userId of the user that we want to remove all values from
236
+     * @since 8.0.0
237
+     * @deprecated 33.0.0 - use {@see IUserConfig::deleteAllUserConfig} directly
238
+     */
239
+    public function deleteAllUserValues($userId);
240 240
 
241
-	/**
242
-	 * Delete all user related values of one app
243
-	 *
244
-	 * @param string $appName the appName of the app that we want to remove all values from
245
-	 * @since 8.0.0
246
-	 * @deprecated 33.0.0 - use {@see IUserConfig::deleteApp} directly
247
-	 */
248
-	public function deleteAppFromAllUsers($appName);
241
+    /**
242
+     * Delete all user related values of one app
243
+     *
244
+     * @param string $appName the appName of the app that we want to remove all values from
245
+     * @since 8.0.0
246
+     * @deprecated 33.0.0 - use {@see IUserConfig::deleteApp} directly
247
+     */
248
+    public function deleteAppFromAllUsers($appName);
249 249
 
250
-	/**
251
-	 * Determines the users that have the given value set for a specific app-key-pair
252
-	 *
253
-	 * @param string $appName the app to get the user for
254
-	 * @param string $key the key to get the user for
255
-	 * @param string $value the value to get the user for
256
-	 * @return list<string> of user IDs
257
-	 * @since 33.0.0 return type of `list<string>`
258
-	 * @since 8.0.0
259
-	 * @deprecated 33.0.0 - use {@see IUserConfig::searchUsersByValueString} directly
260
-	 */
261
-	public function getUsersForUserValue($appName, $key, $value);
250
+    /**
251
+     * Determines the users that have the given value set for a specific app-key-pair
252
+     *
253
+     * @param string $appName the app to get the user for
254
+     * @param string $key the key to get the user for
255
+     * @param string $value the value to get the user for
256
+     * @return list<string> of user IDs
257
+     * @since 33.0.0 return type of `list<string>`
258
+     * @since 8.0.0
259
+     * @deprecated 33.0.0 - use {@see IUserConfig::searchUsersByValueString} directly
260
+     */
261
+    public function getUsersForUserValue($appName, $key, $value);
262 262
 }
Please login to merge, or discard this patch.
lib/private/AllConfig.php 1 patch
Indentation   +306 added lines, -306 removed lines patch added patch discarded remove patch
@@ -18,337 +18,337 @@
 block discarded – undo
18 18
  * Class to combine all the configuration options Nextcloud offers
19 19
  */
20 20
 class AllConfig implements IConfig {
21
-	public function __construct(
22
-		private SystemConfig $systemConfig,
23
-	) {
24
-	}
21
+    public function __construct(
22
+        private SystemConfig $systemConfig,
23
+    ) {
24
+    }
25 25
 
26
-	/**
27
-	 * Sets and deletes system-wide values
28
-	 *
29
-	 * @param array $configs Associative array with `key => value` pairs
30
-	 *                       If value is null, the config key will be deleted
31
-	 */
32
-	public function setSystemValues(array $configs) {
33
-		$this->systemConfig->setValues($configs);
34
-	}
26
+    /**
27
+     * Sets and deletes system-wide values
28
+     *
29
+     * @param array $configs Associative array with `key => value` pairs
30
+     *                       If value is null, the config key will be deleted
31
+     */
32
+    public function setSystemValues(array $configs) {
33
+        $this->systemConfig->setValues($configs);
34
+    }
35 35
 
36
-	/**
37
-	 * Sets a new system-wide value
38
-	 *
39
-	 * @param string $key the key of the value, under which will be saved
40
-	 * @param mixed $value the value that should be stored
41
-	 */
42
-	public function setSystemValue($key, $value) {
43
-		$this->systemConfig->setValue($key, $value);
44
-	}
36
+    /**
37
+     * Sets a new system-wide value
38
+     *
39
+     * @param string $key the key of the value, under which will be saved
40
+     * @param mixed $value the value that should be stored
41
+     */
42
+    public function setSystemValue($key, $value) {
43
+        $this->systemConfig->setValue($key, $value);
44
+    }
45 45
 
46
-	/**
47
-	 * Looks up a system wide defined value
48
-	 *
49
-	 * @param string $key the key of the value, under which it was saved
50
-	 * @param mixed $default the default value to be returned if the value isn't set
51
-	 * @return mixed the value or $default
52
-	 */
53
-	public function getSystemValue($key, $default = '') {
54
-		return $this->systemConfig->getValue($key, $default);
55
-	}
46
+    /**
47
+     * Looks up a system wide defined value
48
+     *
49
+     * @param string $key the key of the value, under which it was saved
50
+     * @param mixed $default the default value to be returned if the value isn't set
51
+     * @return mixed the value or $default
52
+     */
53
+    public function getSystemValue($key, $default = '') {
54
+        return $this->systemConfig->getValue($key, $default);
55
+    }
56 56
 
57
-	/**
58
-	 * Looks up a boolean system wide defined value
59
-	 *
60
-	 * @param string $key the key of the value, under which it was saved
61
-	 * @param bool $default the default value to be returned if the value isn't set
62
-	 *
63
-	 * @return bool
64
-	 *
65
-	 * @since 16.0.0
66
-	 */
67
-	public function getSystemValueBool(string $key, bool $default = false): bool {
68
-		return (bool)$this->getSystemValue($key, $default);
69
-	}
57
+    /**
58
+     * Looks up a boolean system wide defined value
59
+     *
60
+     * @param string $key the key of the value, under which it was saved
61
+     * @param bool $default the default value to be returned if the value isn't set
62
+     *
63
+     * @return bool
64
+     *
65
+     * @since 16.0.0
66
+     */
67
+    public function getSystemValueBool(string $key, bool $default = false): bool {
68
+        return (bool)$this->getSystemValue($key, $default);
69
+    }
70 70
 
71
-	/**
72
-	 * Looks up an integer system wide defined value
73
-	 *
74
-	 * @param string $key the key of the value, under which it was saved
75
-	 * @param int $default the default value to be returned if the value isn't set
76
-	 *
77
-	 * @return int
78
-	 *
79
-	 * @since 16.0.0
80
-	 */
81
-	public function getSystemValueInt(string $key, int $default = 0): int {
82
-		return (int)$this->getSystemValue($key, $default);
83
-	}
71
+    /**
72
+     * Looks up an integer system wide defined value
73
+     *
74
+     * @param string $key the key of the value, under which it was saved
75
+     * @param int $default the default value to be returned if the value isn't set
76
+     *
77
+     * @return int
78
+     *
79
+     * @since 16.0.0
80
+     */
81
+    public function getSystemValueInt(string $key, int $default = 0): int {
82
+        return (int)$this->getSystemValue($key, $default);
83
+    }
84 84
 
85
-	/**
86
-	 * Looks up a string system wide defined value
87
-	 *
88
-	 * @param string $key the key of the value, under which it was saved
89
-	 * @param string $default the default value to be returned if the value isn't set
90
-	 *
91
-	 * @return string
92
-	 *
93
-	 * @since 16.0.0
94
-	 */
95
-	public function getSystemValueString(string $key, string $default = ''): string {
96
-		return (string)$this->getSystemValue($key, $default);
97
-	}
85
+    /**
86
+     * Looks up a string system wide defined value
87
+     *
88
+     * @param string $key the key of the value, under which it was saved
89
+     * @param string $default the default value to be returned if the value isn't set
90
+     *
91
+     * @return string
92
+     *
93
+     * @since 16.0.0
94
+     */
95
+    public function getSystemValueString(string $key, string $default = ''): string {
96
+        return (string)$this->getSystemValue($key, $default);
97
+    }
98 98
 
99
-	/**
100
-	 * Looks up a system wide defined value and filters out sensitive data
101
-	 *
102
-	 * @param string $key the key of the value, under which it was saved
103
-	 * @param mixed $default the default value to be returned if the value isn't set
104
-	 * @return mixed the value or $default
105
-	 */
106
-	public function getFilteredSystemValue($key, $default = '') {
107
-		return $this->systemConfig->getFilteredValue($key, $default);
108
-	}
99
+    /**
100
+     * Looks up a system wide defined value and filters out sensitive data
101
+     *
102
+     * @param string $key the key of the value, under which it was saved
103
+     * @param mixed $default the default value to be returned if the value isn't set
104
+     * @return mixed the value or $default
105
+     */
106
+    public function getFilteredSystemValue($key, $default = '') {
107
+        return $this->systemConfig->getFilteredValue($key, $default);
108
+    }
109 109
 
110
-	/**
111
-	 * Delete a system wide defined value
112
-	 *
113
-	 * @param string $key the key of the value, under which it was saved
114
-	 */
115
-	public function deleteSystemValue($key) {
116
-		$this->systemConfig->deleteValue($key);
117
-	}
110
+    /**
111
+     * Delete a system wide defined value
112
+     *
113
+     * @param string $key the key of the value, under which it was saved
114
+     */
115
+    public function deleteSystemValue($key) {
116
+        $this->systemConfig->deleteValue($key);
117
+    }
118 118
 
119
-	/**
120
-	 * Get all keys stored for an app
121
-	 *
122
-	 * @param string $appName the appName that we stored the value under
123
-	 * @return string[] the keys stored for the app
124
-	 * @deprecated 29.0.0 Use {@see IAppConfig} directly
125
-	 */
126
-	public function getAppKeys($appName) {
127
-		return \OC::$server->get(AppConfig::class)->getKeys($appName);
128
-	}
119
+    /**
120
+     * Get all keys stored for an app
121
+     *
122
+     * @param string $appName the appName that we stored the value under
123
+     * @return string[] the keys stored for the app
124
+     * @deprecated 29.0.0 Use {@see IAppConfig} directly
125
+     */
126
+    public function getAppKeys($appName) {
127
+        return \OC::$server->get(AppConfig::class)->getKeys($appName);
128
+    }
129 129
 
130
-	/**
131
-	 * Writes a new app wide value
132
-	 *
133
-	 * @param string $appName the appName that we want to store the value under
134
-	 * @param string $key the key of the value, under which will be saved
135
-	 * @param string|float|int $value the value that should be stored
136
-	 * @deprecated 29.0.0 Use {@see IAppConfig} directly
137
-	 */
138
-	public function setAppValue($appName, $key, $value) {
139
-		\OC::$server->get(AppConfig::class)->setValue($appName, $key, $value);
140
-	}
130
+    /**
131
+     * Writes a new app wide value
132
+     *
133
+     * @param string $appName the appName that we want to store the value under
134
+     * @param string $key the key of the value, under which will be saved
135
+     * @param string|float|int $value the value that should be stored
136
+     * @deprecated 29.0.0 Use {@see IAppConfig} directly
137
+     */
138
+    public function setAppValue($appName, $key, $value) {
139
+        \OC::$server->get(AppConfig::class)->setValue($appName, $key, $value);
140
+    }
141 141
 
142
-	/**
143
-	 * Looks up an app wide defined value
144
-	 *
145
-	 * @param string $appName the appName that we stored the value under
146
-	 * @param string $key the key of the value, under which it was saved
147
-	 * @param string $default the default value to be returned if the value isn't set
148
-	 * @return string the saved value
149
-	 * @deprecated 29.0.0 Use {@see IAppConfig} directly
150
-	 */
151
-	public function getAppValue($appName, $key, $default = '') {
152
-		return \OC::$server->get(AppConfig::class)->getValue($appName, $key, $default) ?? $default;
153
-	}
142
+    /**
143
+     * Looks up an app wide defined value
144
+     *
145
+     * @param string $appName the appName that we stored the value under
146
+     * @param string $key the key of the value, under which it was saved
147
+     * @param string $default the default value to be returned if the value isn't set
148
+     * @return string the saved value
149
+     * @deprecated 29.0.0 Use {@see IAppConfig} directly
150
+     */
151
+    public function getAppValue($appName, $key, $default = '') {
152
+        return \OC::$server->get(AppConfig::class)->getValue($appName, $key, $default) ?? $default;
153
+    }
154 154
 
155
-	/**
156
-	 * Delete an app wide defined value
157
-	 *
158
-	 * @param string $appName the appName that we stored the value under
159
-	 * @param string $key the key of the value, under which it was saved
160
-	 * @deprecated 29.0.0 Use {@see IAppConfig} directly
161
-	 */
162
-	public function deleteAppValue($appName, $key) {
163
-		\OC::$server->get(AppConfig::class)->deleteKey($appName, $key);
164
-	}
155
+    /**
156
+     * Delete an app wide defined value
157
+     *
158
+     * @param string $appName the appName that we stored the value under
159
+     * @param string $key the key of the value, under which it was saved
160
+     * @deprecated 29.0.0 Use {@see IAppConfig} directly
161
+     */
162
+    public function deleteAppValue($appName, $key) {
163
+        \OC::$server->get(AppConfig::class)->deleteKey($appName, $key);
164
+    }
165 165
 
166
-	/**
167
-	 * Removes all keys in appconfig belonging to the app
168
-	 *
169
-	 * @param string $appName the appName the configs are stored under
170
-	 * @deprecated 29.0.0 Use {@see IAppConfig} directly
171
-	 */
172
-	public function deleteAppValues($appName) {
173
-		\OC::$server->get(AppConfig::class)->deleteApp($appName);
174
-	}
166
+    /**
167
+     * Removes all keys in appconfig belonging to the app
168
+     *
169
+     * @param string $appName the appName the configs are stored under
170
+     * @deprecated 29.0.0 Use {@see IAppConfig} directly
171
+     */
172
+    public function deleteAppValues($appName) {
173
+        \OC::$server->get(AppConfig::class)->deleteApp($appName);
174
+    }
175 175
 
176 176
 
177
-	/**
178
-	 * Set a user defined value
179
-	 *
180
-	 * @param string $userId the userId of the user that we want to store the value under
181
-	 * @param string $appName the appName that we want to store the value under
182
-	 * @param string $key the key under which the value is being stored
183
-	 * @param string|float|int $value the value that you want to store
184
-	 * @param string $preCondition only update if the config value was previously the value passed as $preCondition
185
-	 *
186
-	 * @throws \OCP\PreConditionNotMetException if a precondition is specified and is not met
187
-	 * @throws \UnexpectedValueException when trying to store an unexpected value
188
-	 * @deprecated 31.0.0 - use {@see IUserConfig} directly
189
-	 * @see IUserConfig::getValueString
190
-	 * @see IUserConfig::getValueInt
191
-	 * @see IUserConfig::getValueFloat
192
-	 * @see IUserConfig::getValueArray
193
-	 * @see IUserConfig::getValueBool
194
-	 */
195
-	public function setUserValue($userId, $appName, $key, $value, $preCondition = null) {
196
-		if (!is_int($value) && !is_float($value) && !is_string($value)) {
197
-			throw new \UnexpectedValueException('Only integers, floats and strings are allowed as value');
198
-		}
177
+    /**
178
+     * Set a user defined value
179
+     *
180
+     * @param string $userId the userId of the user that we want to store the value under
181
+     * @param string $appName the appName that we want to store the value under
182
+     * @param string $key the key under which the value is being stored
183
+     * @param string|float|int $value the value that you want to store
184
+     * @param string $preCondition only update if the config value was previously the value passed as $preCondition
185
+     *
186
+     * @throws \OCP\PreConditionNotMetException if a precondition is specified and is not met
187
+     * @throws \UnexpectedValueException when trying to store an unexpected value
188
+     * @deprecated 31.0.0 - use {@see IUserConfig} directly
189
+     * @see IUserConfig::getValueString
190
+     * @see IUserConfig::getValueInt
191
+     * @see IUserConfig::getValueFloat
192
+     * @see IUserConfig::getValueArray
193
+     * @see IUserConfig::getValueBool
194
+     */
195
+    public function setUserValue($userId, $appName, $key, $value, $preCondition = null) {
196
+        if (!is_int($value) && !is_float($value) && !is_string($value)) {
197
+            throw new \UnexpectedValueException('Only integers, floats and strings are allowed as value');
198
+        }
199 199
 
200
-		/** @var UserConfig $userPreferences */
201
-		$userPreferences = \OCP\Server::get(IUserConfig::class);
202
-		if ($preCondition !== null) {
203
-			try {
204
-				if ($userPreferences->hasKey($userId, $appName, $key) && $userPreferences->getValueMixed($userId, $appName, $key) !== (string)$preCondition) {
205
-					throw new PreConditionNotMetException();
206
-				}
207
-			} catch (TypeConflictException) {
208
-			}
209
-		}
200
+        /** @var UserConfig $userPreferences */
201
+        $userPreferences = \OCP\Server::get(IUserConfig::class);
202
+        if ($preCondition !== null) {
203
+            try {
204
+                if ($userPreferences->hasKey($userId, $appName, $key) && $userPreferences->getValueMixed($userId, $appName, $key) !== (string)$preCondition) {
205
+                    throw new PreConditionNotMetException();
206
+                }
207
+            } catch (TypeConflictException) {
208
+            }
209
+        }
210 210
 
211
-		$userPreferences->setValueMixed($userId, $appName, $key, (string)$value);
212
-	}
211
+        $userPreferences->setValueMixed($userId, $appName, $key, (string)$value);
212
+    }
213 213
 
214
-	/**
215
-	 * Getting a user defined value
216
-	 *
217
-	 * @param ?string $userId the userId of the user that we want to store the value under
218
-	 * @param string $appName the appName that we stored the value under
219
-	 * @param string $key the key under which the value is being stored
220
-	 * @param mixed $default the default value to be returned if the value isn't set
221
-	 *
222
-	 * @return string
223
-	 * @deprecated 31.0.0 - use {@see IUserConfig} directly
224
-	 * @see IUserConfig::getValueString
225
-	 * @see IUserConfig::getValueInt
226
-	 * @see IUserConfig::getValueFloat
227
-	 * @see IUserConfig::getValueArray
228
-	 * @see IUserConfig::getValueBool
229
-	 */
230
-	public function getUserValue($userId, $appName, $key, $default = '') {
231
-		if ($userId === null || $userId === '') {
232
-			return $default;
233
-		}
234
-		/** @var UserConfig $userPreferences */
235
-		$userPreferences = \OCP\Server::get(IUserConfig::class);
236
-		// because $default can be null ...
237
-		if (!$userPreferences->hasKey($userId, $appName, $key)) {
238
-			return $default;
239
-		}
240
-		return $userPreferences->getValueMixed($userId, $appName, $key, $default ?? '');
241
-	}
214
+    /**
215
+     * Getting a user defined value
216
+     *
217
+     * @param ?string $userId the userId of the user that we want to store the value under
218
+     * @param string $appName the appName that we stored the value under
219
+     * @param string $key the key under which the value is being stored
220
+     * @param mixed $default the default value to be returned if the value isn't set
221
+     *
222
+     * @return string
223
+     * @deprecated 31.0.0 - use {@see IUserConfig} directly
224
+     * @see IUserConfig::getValueString
225
+     * @see IUserConfig::getValueInt
226
+     * @see IUserConfig::getValueFloat
227
+     * @see IUserConfig::getValueArray
228
+     * @see IUserConfig::getValueBool
229
+     */
230
+    public function getUserValue($userId, $appName, $key, $default = '') {
231
+        if ($userId === null || $userId === '') {
232
+            return $default;
233
+        }
234
+        /** @var UserConfig $userPreferences */
235
+        $userPreferences = \OCP\Server::get(IUserConfig::class);
236
+        // because $default can be null ...
237
+        if (!$userPreferences->hasKey($userId, $appName, $key)) {
238
+            return $default;
239
+        }
240
+        return $userPreferences->getValueMixed($userId, $appName, $key, $default ?? '');
241
+    }
242 242
 
243
-	/**
244
-	 * Get the keys of all stored by an app for the user
245
-	 *
246
-	 * @param string $userId the userId of the user that we want to store the value under
247
-	 * @param string $appName the appName that we stored the value under
248
-	 *
249
-	 * @return string[]
250
-	 * @deprecated 31.0.0 - use {@see IUserConfig::getKeys} directly
251
-	 */
252
-	public function getUserKeys($userId, $appName) {
253
-		return \OCP\Server::get(IUserConfig::class)->getKeys($userId, $appName);
254
-	}
243
+    /**
244
+     * Get the keys of all stored by an app for the user
245
+     *
246
+     * @param string $userId the userId of the user that we want to store the value under
247
+     * @param string $appName the appName that we stored the value under
248
+     *
249
+     * @return string[]
250
+     * @deprecated 31.0.0 - use {@see IUserConfig::getKeys} directly
251
+     */
252
+    public function getUserKeys($userId, $appName) {
253
+        return \OCP\Server::get(IUserConfig::class)->getKeys($userId, $appName);
254
+    }
255 255
 
256
-	/**
257
-	 * Delete a user value
258
-	 *
259
-	 * @param string $userId the userId of the user that we want to store the value under
260
-	 * @param string $appName the appName that we stored the value under
261
-	 * @param string $key the key under which the value is being stored
262
-	 *
263
-	 * @deprecated 31.0.0 - use {@see IUserConfig::deleteUserConfig} directly
264
-	 */
265
-	public function deleteUserValue($userId, $appName, $key) {
266
-		\OCP\Server::get(IUserConfig::class)->deleteUserConfig($userId, $appName, $key);
267
-	}
256
+    /**
257
+     * Delete a user value
258
+     *
259
+     * @param string $userId the userId of the user that we want to store the value under
260
+     * @param string $appName the appName that we stored the value under
261
+     * @param string $key the key under which the value is being stored
262
+     *
263
+     * @deprecated 31.0.0 - use {@see IUserConfig::deleteUserConfig} directly
264
+     */
265
+    public function deleteUserValue($userId, $appName, $key) {
266
+        \OCP\Server::get(IUserConfig::class)->deleteUserConfig($userId, $appName, $key);
267
+    }
268 268
 
269
-	/**
270
-	 * Delete all user values
271
-	 *
272
-	 * @param string $userId the userId of the user that we want to remove all values from
273
-	 *
274
-	 * @deprecated 31.0.0 - use {@see IUserConfig::deleteAllUserConfig} directly
275
-	 */
276
-	public function deleteAllUserValues($userId) {
277
-		if ($userId === null) {
278
-			return;
279
-		}
280
-		\OCP\Server::get(IUserConfig::class)->deleteAllUserConfig($userId);
281
-	}
269
+    /**
270
+     * Delete all user values
271
+     *
272
+     * @param string $userId the userId of the user that we want to remove all values from
273
+     *
274
+     * @deprecated 31.0.0 - use {@see IUserConfig::deleteAllUserConfig} directly
275
+     */
276
+    public function deleteAllUserValues($userId) {
277
+        if ($userId === null) {
278
+            return;
279
+        }
280
+        \OCP\Server::get(IUserConfig::class)->deleteAllUserConfig($userId);
281
+    }
282 282
 
283
-	/**
284
-	 * Delete all user related values of one app
285
-	 *
286
-	 * @param string $appName the appName of the app that we want to remove all values from
287
-	 *
288
-	 * @deprecated 31.0.0 - use {@see IUserConfig::deleteApp} directly
289
-	 */
290
-	public function deleteAppFromAllUsers($appName) {
291
-		\OCP\Server::get(IUserConfig::class)->deleteApp($appName);
292
-	}
283
+    /**
284
+     * Delete all user related values of one app
285
+     *
286
+     * @param string $appName the appName of the app that we want to remove all values from
287
+     *
288
+     * @deprecated 31.0.0 - use {@see IUserConfig::deleteApp} directly
289
+     */
290
+    public function deleteAppFromAllUsers($appName) {
291
+        \OCP\Server::get(IUserConfig::class)->deleteApp($appName);
292
+    }
293 293
 
294
-	/**
295
-	 * Returns all user configs sorted by app of one user
296
-	 *
297
-	 * @param ?string $userId the user ID to get the app configs from
298
-	 *
299
-	 * @psalm-return array<string, array<string, string>>
300
-	 * @return array[] - 2 dimensional array with the following structure:
301
-	 *                 [ $appId =>
302
-	 *                 [ $key => $value ]
303
-	 *                 ]
304
-	 * @deprecated 31.0.0 - use {@see IUserConfig::getAllValues} directly
305
-	 */
306
-	public function getAllUserValues(?string $userId): array {
307
-		if ($userId === null || $userId === '') {
308
-			return [];
309
-		}
294
+    /**
295
+     * Returns all user configs sorted by app of one user
296
+     *
297
+     * @param ?string $userId the user ID to get the app configs from
298
+     *
299
+     * @psalm-return array<string, array<string, string>>
300
+     * @return array[] - 2 dimensional array with the following structure:
301
+     *                 [ $appId =>
302
+     *                 [ $key => $value ]
303
+     *                 ]
304
+     * @deprecated 31.0.0 - use {@see IUserConfig::getAllValues} directly
305
+     */
306
+    public function getAllUserValues(?string $userId): array {
307
+        if ($userId === null || $userId === '') {
308
+            return [];
309
+        }
310 310
 
311
-		$values = \OCP\Server::get(IUserConfig::class)->getAllValues($userId);
312
-		$result = [];
313
-		foreach ($values as $app => $list) {
314
-			foreach ($list as $key => $value) {
315
-				$result[$app][$key] = (string)$value;
316
-			}
317
-		}
318
-		return $result;
319
-	}
311
+        $values = \OCP\Server::get(IUserConfig::class)->getAllValues($userId);
312
+        $result = [];
313
+        foreach ($values as $app => $list) {
314
+            foreach ($list as $key => $value) {
315
+                $result[$app][$key] = (string)$value;
316
+            }
317
+        }
318
+        return $result;
319
+    }
320 320
 
321
-	/**
322
-	 * Fetches a mapped list of userId -> value, for a specified app and key and a list of user IDs.
323
-	 *
324
-	 * @param string $appName app to get the value for
325
-	 * @param string $key the key to get the value for
326
-	 * @param array $userIds the user IDs to fetch the values for
327
-	 *
328
-	 * @return array Mapped values: userId => value
329
-	 * @deprecated 31.0.0 - use {@see IUserConfig::getValuesByUsers} directly
330
-	 */
331
-	public function getUserValueForUsers($appName, $key, $userIds) {
332
-		return \OCP\Server::get(IUserConfig::class)->getValuesByUsers($appName, $key, ValueType::MIXED, $userIds);
333
-	}
321
+    /**
322
+     * Fetches a mapped list of userId -> value, for a specified app and key and a list of user IDs.
323
+     *
324
+     * @param string $appName app to get the value for
325
+     * @param string $key the key to get the value for
326
+     * @param array $userIds the user IDs to fetch the values for
327
+     *
328
+     * @return array Mapped values: userId => value
329
+     * @deprecated 31.0.0 - use {@see IUserConfig::getValuesByUsers} directly
330
+     */
331
+    public function getUserValueForUsers($appName, $key, $userIds) {
332
+        return \OCP\Server::get(IUserConfig::class)->getValuesByUsers($appName, $key, ValueType::MIXED, $userIds);
333
+    }
334 334
 
335
-	/**
336
-	 * Determines the users that have the given value set for a specific app-key-pair
337
-	 *
338
-	 * @param string $appName the app to get the user for
339
-	 * @param string $key the key to get the user for
340
-	 * @param string $value the value to get the user for
341
-	 *
342
-	 * @return list<string> of user IDs
343
-	 * @deprecated 31.0.0 - use {@see IUserConfig::searchUsersByValueString} directly
344
-	 */
345
-	public function getUsersForUserValue($appName, $key, $value) {
346
-		/** @var list<string> $result */
347
-		$result = iterator_to_array(\OCP\Server::get(IUserConfig::class)->searchUsersByValueString($appName, $key, $value));
348
-		return $result;
349
-	}
335
+    /**
336
+     * Determines the users that have the given value set for a specific app-key-pair
337
+     *
338
+     * @param string $appName the app to get the user for
339
+     * @param string $key the key to get the user for
340
+     * @param string $value the value to get the user for
341
+     *
342
+     * @return list<string> of user IDs
343
+     * @deprecated 31.0.0 - use {@see IUserConfig::searchUsersByValueString} directly
344
+     */
345
+    public function getUsersForUserValue($appName, $key, $value) {
346
+        /** @var list<string> $result */
347
+        $result = iterator_to_array(\OCP\Server::get(IUserConfig::class)->searchUsersByValueString($appName, $key, $value));
348
+        return $result;
349
+    }
350 350
 
351
-	public function getSystemConfig() {
352
-		return $this->systemConfig;
353
-	}
351
+    public function getSystemConfig() {
352
+        return $this->systemConfig;
353
+    }
354 354
 }
Please login to merge, or discard this patch.
lib/private/Server.php 1 patch
Indentation   +1392 added lines, -1392 removed lines patch added patch discarded remove patch
@@ -259,1436 +259,1436 @@
 block discarded – undo
259 259
  * TODO: hookup all manager classes
260 260
  */
261 261
 class Server extends ServerContainer implements IServerContainer {
262
-	/** @var string */
263
-	private $webRoot;
264
-
265
-	/**
266
-	 * @param string $webRoot
267
-	 * @param \OC\Config $config
268
-	 */
269
-	public function __construct($webRoot, \OC\Config $config) {
270
-		parent::__construct();
271
-		$this->webRoot = $webRoot;
272
-
273
-		// To find out if we are running from CLI or not
274
-		$this->registerParameter('isCLI', \OC::$CLI);
275
-		$this->registerParameter('serverRoot', \OC::$SERVERROOT);
276
-
277
-		$this->registerService(ContainerInterface::class, function (ContainerInterface $c) {
278
-			return $c;
279
-		});
280
-		$this->registerDeprecatedAlias(\OCP\IServerContainer::class, ContainerInterface::class);
281
-
282
-		$this->registerAlias(\OCP\Calendar\IManager::class, \OC\Calendar\Manager::class);
283
-
284
-		$this->registerAlias(\OCP\Calendar\Resource\IManager::class, \OC\Calendar\Resource\Manager::class);
285
-
286
-		$this->registerAlias(\OCP\Calendar\Room\IManager::class, \OC\Calendar\Room\Manager::class);
287
-
288
-		$this->registerAlias(\OCP\Contacts\IManager::class, \OC\ContactsManager::class);
289
-
290
-		$this->registerAlias(\OCP\ContextChat\IContentManager::class, \OC\ContextChat\ContentManager::class);
291
-
292
-		$this->registerAlias(\OCP\DirectEditing\IManager::class, \OC\DirectEditing\Manager::class);
293
-		$this->registerAlias(ITemplateManager::class, TemplateManager::class);
294
-		$this->registerAlias(\OCP\Template\ITemplateManager::class, \OC\Template\TemplateManager::class);
295
-
296
-		$this->registerAlias(IActionFactory::class, ActionFactory::class);
297
-
298
-		$this->registerService(View::class, function (Server $c) {
299
-			return new View();
300
-		}, false);
301
-
302
-		$this->registerService(IPreview::class, function (ContainerInterface $c) {
303
-			return new PreviewManager(
304
-				$c->get(\OCP\IConfig::class),
305
-				$c->get(IRootFolder::class),
306
-				$c->get(IEventDispatcher::class),
307
-				$c->get(GeneratorHelper::class),
308
-				$c->get(ISession::class)->get('user_id'),
309
-				$c->get(Coordinator::class),
310
-				$c->get(IServerContainer::class),
311
-				$c->get(IBinaryFinder::class),
312
-				$c->get(IMagickSupport::class)
313
-			);
314
-		});
315
-		$this->registerAlias(IMimeIconProvider::class, MimeIconProvider::class);
316
-
317
-		$this->registerService(Watcher::class, function (ContainerInterface $c): Watcher {
318
-			return new Watcher(
319
-				$c->get(\OC\Preview\Storage\StorageFactory::class),
320
-				$c->get(PreviewMapper::class),
321
-				$c->get(IDBConnection::class),
322
-			);
323
-		});
324
-
325
-		$this->registerService(IProfiler::class, function (Server $c) {
326
-			return new Profiler($c->get(SystemConfig::class));
327
-		});
328
-
329
-		$this->registerService(Encryption\Manager::class, function (Server $c): Encryption\Manager {
330
-			$view = new View();
331
-			$util = new Encryption\Util(
332
-				$view,
333
-				$c->get(IUserManager::class),
334
-				$c->get(IGroupManager::class),
335
-				$c->get(\OCP\IConfig::class)
336
-			);
337
-			return new Encryption\Manager(
338
-				$c->get(\OCP\IConfig::class),
339
-				$c->get(LoggerInterface::class),
340
-				$c->getL10N('core'),
341
-				new View(),
342
-				$util,
343
-				new ArrayCache()
344
-			);
345
-		});
346
-		$this->registerAlias(\OCP\Encryption\IManager::class, Encryption\Manager::class);
347
-
348
-		$this->registerService(IFile::class, function (ContainerInterface $c) {
349
-			$util = new Encryption\Util(
350
-				new View(),
351
-				$c->get(IUserManager::class),
352
-				$c->get(IGroupManager::class),
353
-				$c->get(\OCP\IConfig::class)
354
-			);
355
-			return new Encryption\File(
356
-				$util,
357
-				$c->get(IRootFolder::class),
358
-				$c->get(\OCP\Share\IManager::class)
359
-			);
360
-		});
361
-
362
-		$this->registerService(IStorage::class, function (ContainerInterface $c) {
363
-			$view = new View();
364
-			$util = new Encryption\Util(
365
-				$view,
366
-				$c->get(IUserManager::class),
367
-				$c->get(IGroupManager::class),
368
-				$c->get(\OCP\IConfig::class)
369
-			);
370
-
371
-			return new Encryption\Keys\Storage(
372
-				$view,
373
-				$util,
374
-				$c->get(ICrypto::class),
375
-				$c->get(\OCP\IConfig::class)
376
-			);
377
-		});
378
-
379
-		$this->registerAlias(\OCP\ITagManager::class, TagManager::class);
380
-
381
-		$this->registerService('SystemTagManagerFactory', function (ContainerInterface $c) {
382
-			/** @var \OCP\IConfig $config */
383
-			$config = $c->get(\OCP\IConfig::class);
384
-			$factoryClass = $config->getSystemValue('systemtags.managerFactory', SystemTagManagerFactory::class);
385
-			return new $factoryClass($this);
386
-		});
387
-		$this->registerService(ISystemTagManager::class, function (ContainerInterface $c) {
388
-			return $c->get('SystemTagManagerFactory')->getManager();
389
-		});
390
-		/** @deprecated 19.0.0 */
391
-		$this->registerDeprecatedAlias('SystemTagManager', ISystemTagManager::class);
392
-
393
-		$this->registerService(ISystemTagObjectMapper::class, function (ContainerInterface $c) {
394
-			return $c->get('SystemTagManagerFactory')->getObjectMapper();
395
-		});
396
-		$this->registerAlias(IFileAccess::class, FileAccess::class);
397
-		$this->registerService('RootFolder', function (ContainerInterface $c) {
398
-			$manager = \OC\Files\Filesystem::getMountManager();
399
-			$view = new View();
400
-			/** @var IUserSession $userSession */
401
-			$userSession = $c->get(IUserSession::class);
402
-			$root = new Root(
403
-				$manager,
404
-				$view,
405
-				$userSession->getUser(),
406
-				$c->get(IUserMountCache::class),
407
-				$this->get(LoggerInterface::class),
408
-				$this->get(IUserManager::class),
409
-				$this->get(IEventDispatcher::class),
410
-				$this->get(ICacheFactory::class),
411
-				$this->get(IAppConfig::class),
412
-			);
413
-
414
-			$previewConnector = new \OC\Preview\WatcherConnector(
415
-				$root,
416
-				$c->get(SystemConfig::class),
417
-				$this->get(IEventDispatcher::class)
418
-			);
419
-			$previewConnector->connectWatcher();
420
-
421
-			return $root;
422
-		});
423
-		$this->registerService(HookConnector::class, function (ContainerInterface $c) {
424
-			return new HookConnector(
425
-				$c->get(IRootFolder::class),
426
-				new View(),
427
-				$c->get(IEventDispatcher::class),
428
-				$c->get(LoggerInterface::class)
429
-			);
430
-		});
431
-
432
-		$this->registerService(IRootFolder::class, function (ContainerInterface $c) {
433
-			return new LazyRoot(function () use ($c) {
434
-				return $c->get('RootFolder');
435
-			});
436
-		});
437
-
438
-		$this->registerAlias(\OCP\IUserManager::class, \OC\User\Manager::class);
439
-
440
-		$this->registerService(DisplayNameCache::class, function (ContainerInterface $c) {
441
-			return $c->get(\OC\User\Manager::class)->getDisplayNameCache();
442
-		});
443
-
444
-		$this->registerService(\OCP\IGroupManager::class, function (ContainerInterface $c) {
445
-			$groupManager = new \OC\Group\Manager(
446
-				$this->get(IUserManager::class),
447
-				$this->get(IEventDispatcher::class),
448
-				$this->get(LoggerInterface::class),
449
-				$this->get(ICacheFactory::class),
450
-				$this->get(IRemoteAddress::class),
451
-			);
452
-			return $groupManager;
453
-		});
454
-
455
-		$this->registerService(Store::class, function (ContainerInterface $c) {
456
-			$session = $c->get(ISession::class);
457
-			if (\OC::$server->get(SystemConfig::class)->getValue('installed', false)) {
458
-				$tokenProvider = $c->get(IProvider::class);
459
-			} else {
460
-				$tokenProvider = null;
461
-			}
462
-			$logger = $c->get(LoggerInterface::class);
463
-			$crypto = $c->get(ICrypto::class);
464
-			return new Store($session, $logger, $crypto, $tokenProvider);
465
-		});
466
-		$this->registerAlias(IStore::class, Store::class);
467
-		$this->registerAlias(IProvider::class, Authentication\Token\Manager::class);
468
-		$this->registerAlias(OCPIProvider::class, Authentication\Token\Manager::class);
469
-
470
-		$this->registerService(\OC\User\Session::class, function (Server $c) {
471
-			$manager = $c->get(IUserManager::class);
472
-			$session = new \OC\Session\Memory();
473
-			$timeFactory = new TimeFactory();
474
-			// Token providers might require a working database. This code
475
-			// might however be called when Nextcloud is not yet setup.
476
-			if (\OC::$server->get(SystemConfig::class)->getValue('installed', false)) {
477
-				$provider = $c->get(IProvider::class);
478
-			} else {
479
-				$provider = null;
480
-			}
481
-
482
-			$userSession = new \OC\User\Session(
483
-				$manager,
484
-				$session,
485
-				$timeFactory,
486
-				$provider,
487
-				$c->get(\OCP\IConfig::class),
488
-				$c->get(ISecureRandom::class),
489
-				$c->get('LockdownManager'),
490
-				$c->get(LoggerInterface::class),
491
-				$c->get(IEventDispatcher::class),
492
-			);
493
-			/** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */
494
-			$userSession->listen('\OC\User', 'preCreateUser', function ($uid, $password) {
495
-				\OC_Hook::emit('OC_User', 'pre_createUser', ['run' => true, 'uid' => $uid, 'password' => $password]);
496
-			});
497
-			/** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */
498
-			$userSession->listen('\OC\User', 'postCreateUser', function ($user, $password) {
499
-				/** @var \OC\User\User $user */
500
-				\OC_Hook::emit('OC_User', 'post_createUser', ['uid' => $user->getUID(), 'password' => $password]);
501
-			});
502
-			/** @deprecated 21.0.0 use BeforeUserDeletedEvent event with the IEventDispatcher instead */
503
-			$userSession->listen('\OC\User', 'preDelete', function ($user) {
504
-				/** @var \OC\User\User $user */
505
-				\OC_Hook::emit('OC_User', 'pre_deleteUser', ['run' => true, 'uid' => $user->getUID()]);
506
-			});
507
-			/** @deprecated 21.0.0 use UserDeletedEvent event with the IEventDispatcher instead */
508
-			$userSession->listen('\OC\User', 'postDelete', function ($user) {
509
-				/** @var \OC\User\User $user */
510
-				\OC_Hook::emit('OC_User', 'post_deleteUser', ['uid' => $user->getUID()]);
511
-			});
512
-			$userSession->listen('\OC\User', 'preSetPassword', function ($user, $password, $recoveryPassword) {
513
-				/** @var \OC\User\User $user */
514
-				\OC_Hook::emit('OC_User', 'pre_setPassword', ['run' => true, 'uid' => $user->getUID(), 'password' => $password, 'recoveryPassword' => $recoveryPassword]);
515
-			});
516
-			$userSession->listen('\OC\User', 'postSetPassword', function ($user, $password, $recoveryPassword) {
517
-				/** @var \OC\User\User $user */
518
-				\OC_Hook::emit('OC_User', 'post_setPassword', ['run' => true, 'uid' => $user->getUID(), 'password' => $password, 'recoveryPassword' => $recoveryPassword]);
519
-			});
520
-			$userSession->listen('\OC\User', 'preLogin', function ($uid, $password) {
521
-				\OC_Hook::emit('OC_User', 'pre_login', ['run' => true, 'uid' => $uid, 'password' => $password]);
522
-
523
-				/** @var IEventDispatcher $dispatcher */
524
-				$dispatcher = $this->get(IEventDispatcher::class);
525
-				$dispatcher->dispatchTyped(new BeforeUserLoggedInEvent($uid, $password));
526
-			});
527
-			$userSession->listen('\OC\User', 'postLogin', function ($user, $loginName, $password, $isTokenLogin) {
528
-				/** @var \OC\User\User $user */
529
-				\OC_Hook::emit('OC_User', 'post_login', ['run' => true, 'uid' => $user->getUID(), 'loginName' => $loginName, 'password' => $password, 'isTokenLogin' => $isTokenLogin]);
530
-
531
-				/** @var IEventDispatcher $dispatcher */
532
-				$dispatcher = $this->get(IEventDispatcher::class);
533
-				$dispatcher->dispatchTyped(new UserLoggedInEvent($user, $loginName, $password, $isTokenLogin));
534
-			});
535
-			$userSession->listen('\OC\User', 'preRememberedLogin', function ($uid) {
536
-				/** @var IEventDispatcher $dispatcher */
537
-				$dispatcher = $this->get(IEventDispatcher::class);
538
-				$dispatcher->dispatchTyped(new BeforeUserLoggedInWithCookieEvent($uid));
539
-			});
540
-			$userSession->listen('\OC\User', 'postRememberedLogin', function ($user, $password) {
541
-				/** @var \OC\User\User $user */
542
-				\OC_Hook::emit('OC_User', 'post_login', ['run' => true, 'uid' => $user->getUID(), 'password' => $password]);
543
-
544
-				/** @var IEventDispatcher $dispatcher */
545
-				$dispatcher = $this->get(IEventDispatcher::class);
546
-				$dispatcher->dispatchTyped(new UserLoggedInWithCookieEvent($user, $password));
547
-			});
548
-			$userSession->listen('\OC\User', 'logout', function ($user) {
549
-				\OC_Hook::emit('OC_User', 'logout', []);
550
-
551
-				/** @var IEventDispatcher $dispatcher */
552
-				$dispatcher = $this->get(IEventDispatcher::class);
553
-				$dispatcher->dispatchTyped(new BeforeUserLoggedOutEvent($user));
554
-			});
555
-			$userSession->listen('\OC\User', 'postLogout', function ($user) {
556
-				/** @var IEventDispatcher $dispatcher */
557
-				$dispatcher = $this->get(IEventDispatcher::class);
558
-				$dispatcher->dispatchTyped(new UserLoggedOutEvent($user));
559
-			});
560
-			$userSession->listen('\OC\User', 'changeUser', function ($user, $feature, $value, $oldValue) {
561
-				/** @var \OC\User\User $user */
562
-				\OC_Hook::emit('OC_User', 'changeUser', ['run' => true, 'user' => $user, 'feature' => $feature, 'value' => $value, 'old_value' => $oldValue]);
563
-			});
564
-			return $userSession;
565
-		});
566
-		$this->registerAlias(\OCP\IUserSession::class, \OC\User\Session::class);
567
-
568
-		$this->registerAlias(\OCP\Authentication\TwoFactorAuth\IRegistry::class, \OC\Authentication\TwoFactorAuth\Registry::class);
569
-
570
-		$this->registerAlias(INavigationManager::class, \OC\NavigationManager::class);
571
-
572
-		$this->registerAlias(\OCP\IConfig::class, \OC\AllConfig::class);
573
-
574
-		$this->registerService(\OC\SystemConfig::class, function ($c) use ($config) {
575
-			return new \OC\SystemConfig($config);
576
-		});
577
-
578
-		$this->registerAlias(IAppConfig::class, \OC\AppConfig::class);
579
-		$this->registerAlias(IUserConfig::class, \OC\Config\UserConfig::class);
580
-		$this->registerAlias(IAppManager::class, AppManager::class);
581
-
582
-		$this->registerService(IFactory::class, function (Server $c) {
583
-			return new \OC\L10N\Factory(
584
-				$c->get(\OCP\IConfig::class),
585
-				$c->getRequest(),
586
-				$c->get(IUserSession::class),
587
-				$c->get(ICacheFactory::class),
588
-				\OC::$SERVERROOT,
589
-				$c->get(IAppManager::class),
590
-			);
591
-		});
592
-
593
-		$this->registerAlias(IURLGenerator::class, URLGenerator::class);
594
-
595
-		$this->registerAlias(ICache::class, Cache\File::class);
596
-		$this->registerService(Factory::class, function (Server $c) {
597
-			$profiler = $c->get(IProfiler::class);
598
-			$logger = $c->get(LoggerInterface::class);
599
-			$serverVersion = $c->get(ServerVersion::class);
600
-			/** @var SystemConfig $config */
601
-			$config = $c->get(SystemConfig::class);
602
-			if (!$config->getValue('installed', false) || (defined('PHPUNIT_RUN') && PHPUNIT_RUN)) {
603
-				return new \OC\Memcache\Factory(
604
-					$logger,
605
-					$profiler,
606
-					$serverVersion,
607
-					ArrayCache::class,
608
-					ArrayCache::class,
609
-					ArrayCache::class
610
-				);
611
-			}
612
-
613
-			return new \OC\Memcache\Factory(
614
-				$logger,
615
-				$profiler,
616
-				$serverVersion,
617
-				/** @psalm-taint-escape callable */
618
-				$config->getValue('memcache.local', null),
619
-				/** @psalm-taint-escape callable */
620
-				$config->getValue('memcache.distributed', null),
621
-				/** @psalm-taint-escape callable */
622
-				$config->getValue('memcache.locking', null),
623
-				/** @psalm-taint-escape callable */
624
-				$config->getValue('redis_log_file')
625
-			);
626
-		});
627
-		$this->registerAlias(ICacheFactory::class, Factory::class);
628
-
629
-		$this->registerService('RedisFactory', function (Server $c) {
630
-			$systemConfig = $c->get(SystemConfig::class);
631
-			return new RedisFactory($systemConfig, $c->get(IEventLogger::class));
632
-		});
633
-
634
-		$this->registerService(\OCP\Activity\IManager::class, function (Server $c) {
635
-			$l10n = $this->get(IFactory::class)->get('lib');
636
-			return new \OC\Activity\Manager(
637
-				$c->getRequest(),
638
-				$c->get(IUserSession::class),
639
-				$c->get(\OCP\IConfig::class),
640
-				$c->get(IValidator::class),
641
-				$c->get(IRichTextFormatter::class),
642
-				$l10n,
643
-				$c->get(ITimeFactory::class),
644
-			);
645
-		});
646
-
647
-		$this->registerService(\OCP\Activity\IEventMerger::class, function (Server $c) {
648
-			return new \OC\Activity\EventMerger(
649
-				$c->getL10N('lib')
650
-			);
651
-		});
652
-		$this->registerAlias(IValidator::class, Validator::class);
653
-
654
-		$this->registerService(AvatarManager::class, function (Server $c) {
655
-			return new AvatarManager(
656
-				$c->get(IUserSession::class),
657
-				$c->get(\OC\User\Manager::class),
658
-				$c->getAppDataDir('avatar'),
659
-				$c->getL10N('lib'),
660
-				$c->get(LoggerInterface::class),
661
-				$c->get(\OCP\IConfig::class),
662
-				$c->get(IAccountManager::class),
663
-				$c->get(KnownUserService::class)
664
-			);
665
-		});
666
-
667
-		$this->registerAlias(IAvatarManager::class, AvatarManager::class);
668
-
669
-		$this->registerAlias(\OCP\Support\CrashReport\IRegistry::class, \OC\Support\CrashReport\Registry::class);
670
-		$this->registerAlias(\OCP\Support\Subscription\IRegistry::class, \OC\Support\Subscription\Registry::class);
671
-		$this->registerAlias(\OCP\Support\Subscription\IAssertion::class, \OC\Support\Subscription\Assertion::class);
672
-
673
-		/** Only used by the PsrLoggerAdapter should not be used by apps */
674
-		$this->registerService(\OC\Log::class, function (Server $c) {
675
-			$logType = $c->get(AllConfig::class)->getSystemValue('log_type', 'file');
676
-			$factory = new LogFactory($c, $this->get(SystemConfig::class));
677
-			$logger = $factory->get($logType);
678
-			$registry = $c->get(\OCP\Support\CrashReport\IRegistry::class);
679
-
680
-			return new Log($logger, $this->get(SystemConfig::class), crashReporters: $registry);
681
-		});
682
-		// PSR-3 logger
683
-		$this->registerAlias(LoggerInterface::class, PsrLoggerAdapter::class);
684
-
685
-		$this->registerService(ILogFactory::class, function (Server $c) {
686
-			return new LogFactory($c, $this->get(SystemConfig::class));
687
-		});
688
-
689
-		$this->registerAlias(IJobList::class, \OC\BackgroundJob\JobList::class);
690
-
691
-		$this->registerService(Router::class, function (Server $c) {
692
-			$cacheFactory = $c->get(ICacheFactory::class);
693
-			if ($cacheFactory->isLocalCacheAvailable()) {
694
-				$router = $c->resolve(CachingRouter::class);
695
-			} else {
696
-				$router = $c->resolve(Router::class);
697
-			}
698
-			return $router;
699
-		});
700
-		$this->registerAlias(IRouter::class, Router::class);
701
-
702
-		$this->registerService(\OC\Security\RateLimiting\Backend\IBackend::class, function ($c) {
703
-			$config = $c->get(\OCP\IConfig::class);
704
-			if (ltrim($config->getSystemValueString('memcache.distributed', ''), '\\') === \OC\Memcache\Redis::class) {
705
-				$backend = new \OC\Security\RateLimiting\Backend\MemoryCacheBackend(
706
-					$c->get(AllConfig::class),
707
-					$this->get(ICacheFactory::class),
708
-					new \OC\AppFramework\Utility\TimeFactory()
709
-				);
710
-			} else {
711
-				$backend = new \OC\Security\RateLimiting\Backend\DatabaseBackend(
712
-					$c->get(AllConfig::class),
713
-					$c->get(IDBConnection::class),
714
-					new \OC\AppFramework\Utility\TimeFactory()
715
-				);
716
-			}
717
-
718
-			return $backend;
719
-		});
720
-
721
-		$this->registerAlias(\OCP\Security\ISecureRandom::class, SecureRandom::class);
722
-		$this->registerAlias(\OCP\Security\IRemoteHostValidator::class, \OC\Security\RemoteHostValidator::class);
723
-		$this->registerAlias(IVerificationToken::class, VerificationToken::class);
724
-
725
-		$this->registerAlias(ICrypto::class, Crypto::class);
726
-
727
-		$this->registerAlias(IHasher::class, Hasher::class);
728
-
729
-		$this->registerAlias(ICredentialsManager::class, CredentialsManager::class);
730
-
731
-		$this->registerAlias(IDBConnection::class, ConnectionAdapter::class);
732
-		$this->registerService(Connection::class, function (Server $c) {
733
-			$systemConfig = $c->get(SystemConfig::class);
734
-			$factory = new \OC\DB\ConnectionFactory($systemConfig, $c->get(ICacheFactory::class));
735
-			$type = $systemConfig->getValue('dbtype', 'sqlite');
736
-			if (!$factory->isValidType($type)) {
737
-				throw new \OC\DatabaseException('Invalid database type');
738
-			}
739
-			$connection = $factory->getConnection($type, []);
740
-			return $connection;
741
-		});
742
-
743
-		$this->registerAlias(ICertificateManager::class, CertificateManager::class);
744
-		$this->registerAlias(IClientService::class, ClientService::class);
745
-		$this->registerService(NegativeDnsCache::class, function (ContainerInterface $c) {
746
-			return new NegativeDnsCache(
747
-				$c->get(ICacheFactory::class),
748
-			);
749
-		});
750
-		$this->registerDeprecatedAlias('HttpClientService', IClientService::class);
751
-		$this->registerService(IEventLogger::class, function (ContainerInterface $c) {
752
-			return new EventLogger($c->get(SystemConfig::class), $c->get(LoggerInterface::class), $c->get(Log::class));
753
-		});
754
-
755
-		$this->registerService(IQueryLogger::class, function (ContainerInterface $c) {
756
-			$queryLogger = new QueryLogger();
757
-			if ($c->get(SystemConfig::class)->getValue('debug', false)) {
758
-				// In debug mode, module is being activated by default
759
-				$queryLogger->activate();
760
-			}
761
-			return $queryLogger;
762
-		});
763
-
764
-		$this->registerAlias(ITempManager::class, TempManager::class);
765
-		$this->registerAlias(IDateTimeZone::class, DateTimeZone::class);
766
-
767
-		$this->registerService(IDateTimeFormatter::class, function (Server $c) {
768
-			$language = $c->get(\OCP\IConfig::class)->getUserValue($c->get(ISession::class)->get('user_id'), 'core', 'lang', null);
769
-
770
-			return new DateTimeFormatter(
771
-				$c->get(IDateTimeZone::class)->getTimeZone(),
772
-				$c->getL10N('lib', $language)
773
-			);
774
-		});
775
-
776
-		$this->registerService(IUserMountCache::class, function (ContainerInterface $c) {
777
-			$mountCache = $c->get(UserMountCache::class);
778
-			$listener = new UserMountCacheListener($mountCache);
779
-			$listener->listen($c->get(IUserManager::class));
780
-			return $mountCache;
781
-		});
782
-
783
-		$this->registerService(IMountProviderCollection::class, function (ContainerInterface $c) {
784
-			$loader = $c->get(IStorageFactory::class);
785
-			$mountCache = $c->get(IUserMountCache::class);
786
-			$eventLogger = $c->get(IEventLogger::class);
787
-			$manager = new MountProviderCollection($loader, $mountCache, $eventLogger);
788
-
789
-			// builtin providers
790
-
791
-			$config = $c->get(\OCP\IConfig::class);
792
-			$logger = $c->get(LoggerInterface::class);
793
-			$objectStoreConfig = $c->get(PrimaryObjectStoreConfig::class);
794
-			$manager->registerProvider(new CacheMountProvider($config));
795
-			$manager->registerHomeProvider(new LocalHomeMountProvider());
796
-			$manager->registerHomeProvider(new ObjectHomeMountProvider($objectStoreConfig));
797
-			$manager->registerRootProvider(new RootMountProvider($objectStoreConfig, $config));
798
-
799
-			return $manager;
800
-		});
801
-
802
-		$this->registerService(IBus::class, function (ContainerInterface $c) {
803
-			$busClass = $c->get(\OCP\IConfig::class)->getSystemValueString('commandbus');
804
-			if ($busClass) {
805
-				[$app, $class] = explode('::', $busClass, 2);
806
-				if ($c->get(IAppManager::class)->isEnabledForUser($app)) {
807
-					$c->get(IAppManager::class)->loadApp($app);
808
-					return $c->get($class);
809
-				} else {
810
-					throw new ServiceUnavailableException("The app providing the command bus ($app) is not enabled");
811
-				}
812
-			} else {
813
-				$jobList = $c->get(IJobList::class);
814
-				return new CronBus($jobList);
815
-			}
816
-		});
817
-		$this->registerDeprecatedAlias('AsyncCommandBus', IBus::class);
818
-		$this->registerAlias(ITrustedDomainHelper::class, TrustedDomainHelper::class);
819
-		$this->registerAlias(IThrottler::class, Throttler::class);
820
-
821
-		$this->registerService(\OC\Security\Bruteforce\Backend\IBackend::class, function ($c) {
822
-			$config = $c->get(\OCP\IConfig::class);
823
-			if (!$config->getSystemValueBool('auth.bruteforce.protection.force.database', false)
824
-				&& ltrim($config->getSystemValueString('memcache.distributed', ''), '\\') === \OC\Memcache\Redis::class) {
825
-				$backend = $c->get(\OC\Security\Bruteforce\Backend\MemoryCacheBackend::class);
826
-			} else {
827
-				$backend = $c->get(\OC\Security\Bruteforce\Backend\DatabaseBackend::class);
828
-			}
829
-
830
-			return $backend;
831
-		});
832
-
833
-		$this->registerDeprecatedAlias('IntegrityCodeChecker', Checker::class);
834
-		$this->registerService(Checker::class, function (ContainerInterface $c) {
835
-			// IConfig requires a working database. This code
836
-			// might however be called when Nextcloud is not yet setup.
837
-			if (\OC::$server->get(SystemConfig::class)->getValue('installed', false)) {
838
-				$config = $c->get(\OCP\IConfig::class);
839
-				$appConfig = $c->get(\OCP\IAppConfig::class);
840
-			} else {
841
-				$config = null;
842
-				$appConfig = null;
843
-			}
844
-
845
-			return new Checker(
846
-				$c->get(ServerVersion::class),
847
-				$c->get(EnvironmentHelper::class),
848
-				new FileAccessHelper(),
849
-				$config,
850
-				$appConfig,
851
-				$c->get(ICacheFactory::class),
852
-				$c->get(IAppManager::class),
853
-				$c->get(IMimeTypeDetector::class)
854
-			);
855
-		});
856
-		$this->registerService(Request::class, function (ContainerInterface $c) {
857
-			if (isset($this['urlParams'])) {
858
-				$urlParams = $this['urlParams'];
859
-			} else {
860
-				$urlParams = [];
861
-			}
862
-
863
-			if (defined('PHPUNIT_RUN') && PHPUNIT_RUN
864
-				&& in_array('fakeinput', stream_get_wrappers())
865
-			) {
866
-				$stream = 'fakeinput://data';
867
-			} else {
868
-				$stream = 'php://input';
869
-			}
870
-
871
-			return new Request(
872
-				[
873
-					'get' => $_GET,
874
-					'post' => $_POST,
875
-					'files' => $_FILES,
876
-					'server' => $_SERVER,
877
-					'env' => $_ENV,
878
-					'cookies' => $_COOKIE,
879
-					'method' => (isset($_SERVER) && isset($_SERVER['REQUEST_METHOD']))
880
-						? $_SERVER['REQUEST_METHOD']
881
-						: '',
882
-					'urlParams' => $urlParams,
883
-				],
884
-				$this->get(IRequestId::class),
885
-				$this->get(\OCP\IConfig::class),
886
-				$this->get(CsrfTokenManager::class),
887
-				$stream
888
-			);
889
-		});
890
-		$this->registerAlias(\OCP\IRequest::class, Request::class);
891
-
892
-		$this->registerService(IRequestId::class, function (ContainerInterface $c): IRequestId {
893
-			return new RequestId(
894
-				$_SERVER['UNIQUE_ID'] ?? '',
895
-				$this->get(ISecureRandom::class)
896
-			);
897
-		});
898
-
899
-		/** @since 32.0.0 */
900
-		$this->registerAlias(IEmailValidator::class, EmailValidator::class);
901
-
902
-		$this->registerService(IMailer::class, function (Server $c) {
903
-			return new Mailer(
904
-				$c->get(\OCP\IConfig::class),
905
-				$c->get(LoggerInterface::class),
906
-				$c->get(Defaults::class),
907
-				$c->get(IURLGenerator::class),
908
-				$c->getL10N('lib'),
909
-				$c->get(IEventDispatcher::class),
910
-				$c->get(IFactory::class),
911
-				$c->get(IEmailValidator::class),
912
-			);
913
-		});
914
-
915
-		/** @since 30.0.0 */
916
-		$this->registerAlias(\OCP\Mail\Provider\IManager::class, \OC\Mail\Provider\Manager::class);
917
-
918
-		$this->registerService(ILDAPProviderFactory::class, function (ContainerInterface $c) {
919
-			$config = $c->get(\OCP\IConfig::class);
920
-			$factoryClass = $config->getSystemValue('ldapProviderFactory', null);
921
-			if (is_null($factoryClass) || !class_exists($factoryClass)) {
922
-				return new NullLDAPProviderFactory($this);
923
-			}
924
-			/** @var \OCP\LDAP\ILDAPProviderFactory $factory */
925
-			return new $factoryClass($this);
926
-		});
927
-		$this->registerService(ILDAPProvider::class, function (ContainerInterface $c) {
928
-			$factory = $c->get(ILDAPProviderFactory::class);
929
-			return $factory->getLDAPProvider();
930
-		});
931
-		$this->registerService(ILockingProvider::class, function (ContainerInterface $c) {
932
-			$ini = $c->get(IniGetWrapper::class);
933
-			$config = $c->get(\OCP\IConfig::class);
934
-			$ttl = $config->getSystemValueInt('filelocking.ttl', max(3600, $ini->getNumeric('max_execution_time')));
935
-			if ($config->getSystemValueBool('filelocking.enabled', true) || (defined('PHPUNIT_RUN') && PHPUNIT_RUN)) {
936
-				/** @var \OC\Memcache\Factory $memcacheFactory */
937
-				$memcacheFactory = $c->get(ICacheFactory::class);
938
-				$memcache = $memcacheFactory->createLocking('lock');
939
-				if (!($memcache instanceof \OC\Memcache\NullCache)) {
940
-					$timeFactory = $c->get(ITimeFactory::class);
941
-					return new MemcacheLockingProvider($memcache, $timeFactory, $ttl);
942
-				}
943
-				return new DBLockingProvider(
944
-					$c->get(IDBConnection::class),
945
-					new TimeFactory(),
946
-					$ttl,
947
-					!\OC::$CLI
948
-				);
949
-			}
950
-			return new NoopLockingProvider();
951
-		});
952
-
953
-		$this->registerService(ILockManager::class, function (Server $c): LockManager {
954
-			return new LockManager();
955
-		});
956
-
957
-		$this->registerAlias(ILockdownManager::class, 'LockdownManager');
958
-		$this->registerService(SetupManager::class, function ($c) {
959
-			// create the setupmanager through the mount manager to resolve the cyclic dependency
960
-			return $c->get(\OC\Files\Mount\Manager::class)->getSetupManager();
961
-		});
962
-		$this->registerAlias(IMountManager::class, \OC\Files\Mount\Manager::class);
963
-
964
-		$this->registerService(IMimeTypeDetector::class, function (ContainerInterface $c) {
965
-			return new \OC\Files\Type\Detection(
966
-				$c->get(IURLGenerator::class),
967
-				$c->get(LoggerInterface::class),
968
-				\OC::$configDir,
969
-				\OC::$SERVERROOT . '/resources/config/'
970
-			);
971
-		});
972
-
973
-		$this->registerAlias(IMimeTypeLoader::class, Loader::class);
974
-		$this->registerService(BundleFetcher::class, function () {
975
-			return new BundleFetcher($this->getL10N('lib'));
976
-		});
977
-		$this->registerAlias(\OCP\Notification\IManager::class, Manager::class);
978
-
979
-		$this->registerService(CapabilitiesManager::class, function (ContainerInterface $c) {
980
-			$manager = new CapabilitiesManager($c->get(LoggerInterface::class));
981
-			$manager->registerCapability(function () use ($c) {
982
-				return new \OC\OCS\CoreCapabilities($c->get(\OCP\IConfig::class));
983
-			});
984
-			$manager->registerCapability(function () use ($c) {
985
-				return $c->get(\OC\Security\Bruteforce\Capabilities::class);
986
-			});
987
-			return $manager;
988
-		});
989
-
990
-		$this->registerService(ICommentsManager::class, function (Server $c) {
991
-			$config = $c->get(\OCP\IConfig::class);
992
-			$factoryClass = $config->getSystemValue('comments.managerFactory', CommentsManagerFactory::class);
993
-			/** @var \OCP\Comments\ICommentsManagerFactory $factory */
994
-			$factory = new $factoryClass($this);
995
-			$manager = $factory->getManager();
996
-
997
-			$manager->registerDisplayNameResolver('user', function ($id) use ($c) {
998
-				$manager = $c->get(IUserManager::class);
999
-				$userDisplayName = $manager->getDisplayName($id);
1000
-				if ($userDisplayName === null) {
1001
-					$l = $c->get(IFactory::class)->get('core');
1002
-					return $l->t('Unknown account');
1003
-				}
1004
-				return $userDisplayName;
1005
-			});
1006
-
1007
-			return $manager;
1008
-		});
1009
-
1010
-		$this->registerAlias(\OC_Defaults::class, 'ThemingDefaults');
1011
-		$this->registerService('ThemingDefaults', function (Server $c) {
1012
-			try {
1013
-				$classExists = class_exists('OCA\Theming\ThemingDefaults');
1014
-			} catch (\OCP\AutoloadNotAllowedException $e) {
1015
-				// App disabled or in maintenance mode
1016
-				$classExists = false;
1017
-			}
1018
-
1019
-			if ($classExists && $c->get(\OCP\IConfig::class)->getSystemValueBool('installed', false) && $c->get(IAppManager::class)->isEnabledForAnyone('theming') && $c->get(TrustedDomainHelper::class)->isTrustedDomain($c->getRequest()->getInsecureServerHost())) {
1020
-				$backgroundService = new BackgroundService(
1021
-					$c->get(IRootFolder::class),
1022
-					$c->get(IAppDataFactory::class)->get('theming'),
1023
-					$c->get(IAppConfig::class),
1024
-					$c->get(\OCP\IConfig::class),
1025
-					$c->get(ISession::class)->get('user_id'),
1026
-				);
1027
-				$imageManager = new ImageManager(
1028
-					$c->get(\OCP\IConfig::class),
1029
-					$c->get(IAppDataFactory::class)->get('theming'),
1030
-					$c->get(IURLGenerator::class),
1031
-					$c->get(ICacheFactory::class),
1032
-					$c->get(LoggerInterface::class),
1033
-					$c->get(ITempManager::class),
1034
-					$backgroundService,
1035
-				);
1036
-				return new ThemingDefaults(
1037
-					new AppConfig(
1038
-						$c->get(\OCP\IConfig::class),
1039
-						$c->get(\OCP\IAppConfig::class),
1040
-						'theming',
1041
-					),
1042
-					$c->get(IUserConfig::class),
1043
-					$c->get(IFactory::class)->get('theming'),
1044
-					$c->get(IUserSession::class),
1045
-					$c->get(IURLGenerator::class),
1046
-					$c->get(ICacheFactory::class),
1047
-					new Util(
1048
-						$c->get(ServerVersion::class),
1049
-						$c->get(\OCP\IConfig::class),
1050
-						$this->get(IAppManager::class),
1051
-						$c->get(IAppDataFactory::class)->get('theming'),
1052
-						$imageManager,
1053
-					),
1054
-					$imageManager,
1055
-					$c->get(IAppManager::class),
1056
-					$c->get(INavigationManager::class),
1057
-					$backgroundService,
1058
-				);
1059
-			}
1060
-			return new \OC_Defaults();
1061
-		});
1062
-		$this->registerService(JSCombiner::class, function (Server $c) {
1063
-			return new JSCombiner(
1064
-				$c->getAppDataDir('js'),
1065
-				$c->get(IURLGenerator::class),
1066
-				$this->get(ICacheFactory::class),
1067
-				$c->get(\OCP\IConfig::class),
1068
-				$c->get(LoggerInterface::class)
1069
-			);
1070
-		});
1071
-		$this->registerAlias(\OCP\EventDispatcher\IEventDispatcher::class, \OC\EventDispatcher\EventDispatcher::class);
1072
-
1073
-		$this->registerService('CryptoWrapper', function (ContainerInterface $c) {
1074
-			// FIXME: Instantiated here due to cyclic dependency
1075
-			$request = new Request(
1076
-				[
1077
-					'get' => $_GET,
1078
-					'post' => $_POST,
1079
-					'files' => $_FILES,
1080
-					'server' => $_SERVER,
1081
-					'env' => $_ENV,
1082
-					'cookies' => $_COOKIE,
1083
-					'method' => (isset($_SERVER) && isset($_SERVER['REQUEST_METHOD']))
1084
-						? $_SERVER['REQUEST_METHOD']
1085
-						: null,
1086
-				],
1087
-				$c->get(IRequestId::class),
1088
-				$c->get(\OCP\IConfig::class)
1089
-			);
1090
-
1091
-			return new CryptoWrapper(
1092
-				$c->get(ICrypto::class),
1093
-				$c->get(ISecureRandom::class),
1094
-				$request
1095
-			);
1096
-		});
1097
-		$this->registerService(SessionStorage::class, function (ContainerInterface $c) {
1098
-			return new SessionStorage($c->get(ISession::class));
1099
-		});
1100
-		$this->registerAlias(\OCP\Security\IContentSecurityPolicyManager::class, ContentSecurityPolicyManager::class);
1101
-
1102
-		$this->registerService(IProviderFactory::class, function (ContainerInterface $c) {
1103
-			$config = $c->get(\OCP\IConfig::class);
1104
-			$factoryClass = $config->getSystemValue('sharing.managerFactory', ProviderFactory::class);
1105
-			/** @var \OCP\Share\IProviderFactory $factory */
1106
-			return $c->get($factoryClass);
1107
-		});
1108
-
1109
-		$this->registerAlias(\OCP\Share\IManager::class, \OC\Share20\Manager::class);
1110
-
1111
-		$this->registerService(\OCP\Collaboration\Collaborators\ISearch::class, function (Server $c) {
1112
-			$instance = new Collaboration\Collaborators\Search($c);
1113
-
1114
-			// register default plugins
1115
-			$instance->registerPlugin(['shareType' => 'SHARE_TYPE_USER', 'class' => UserPlugin::class]);
1116
-			$instance->registerPlugin(['shareType' => 'SHARE_TYPE_USER', 'class' => UserByMailPlugin::class]);
1117
-			$instance->registerPlugin(['shareType' => 'SHARE_TYPE_GROUP', 'class' => GroupPlugin::class]);
1118
-			$instance->registerPlugin(['shareType' => 'SHARE_TYPE_EMAIL', 'class' => MailByMailPlugin::class]);
1119
-			$instance->registerPlugin(['shareType' => 'SHARE_TYPE_REMOTE', 'class' => RemotePlugin::class]);
1120
-			$instance->registerPlugin(['shareType' => 'SHARE_TYPE_REMOTE_GROUP', 'class' => RemoteGroupPlugin::class]);
1121
-
1122
-			return $instance;
1123
-		});
1124
-		$this->registerAlias(\OCP\Collaboration\Collaborators\ISearchResult::class, \OC\Collaboration\Collaborators\SearchResult::class);
1125
-
1126
-		$this->registerAlias(\OCP\Collaboration\AutoComplete\IManager::class, \OC\Collaboration\AutoComplete\Manager::class);
1127
-
1128
-		$this->registerAlias(\OCP\Collaboration\Resources\IProviderManager::class, \OC\Collaboration\Resources\ProviderManager::class);
1129
-		$this->registerAlias(\OCP\Collaboration\Resources\IManager::class, \OC\Collaboration\Resources\Manager::class);
1130
-
1131
-		$this->registerAlias(IReferenceManager::class, ReferenceManager::class);
1132
-		$this->registerAlias(ITeamManager::class, TeamManager::class);
1133
-
1134
-		$this->registerDeprecatedAlias('SettingsManager', \OC\Settings\Manager::class);
1135
-		$this->registerAlias(\OCP\Settings\IManager::class, \OC\Settings\Manager::class);
1136
-		$this->registerService(\OC\Files\AppData\Factory::class, function (ContainerInterface $c) {
1137
-			return new \OC\Files\AppData\Factory(
1138
-				$c->get(IRootFolder::class),
1139
-				$c->get(SystemConfig::class)
1140
-			);
1141
-		});
1142
-
1143
-		$this->registerService('LockdownManager', function (ContainerInterface $c) {
1144
-			return new LockdownManager(function () use ($c) {
1145
-				return $c->get(ISession::class);
1146
-			});
1147
-		});
1148
-
1149
-		$this->registerService(\OCP\OCS\IDiscoveryService::class, function (ContainerInterface $c) {
1150
-			return new DiscoveryService(
1151
-				$c->get(ICacheFactory::class),
1152
-				$c->get(IClientService::class)
1153
-			);
1154
-		});
1155
-		$this->registerAlias(IOCMDiscoveryService::class, OCMDiscoveryService::class);
1156
-
1157
-		$this->registerService(ICloudIdManager::class, function (ContainerInterface $c) {
1158
-			return new CloudIdManager(
1159
-				$c->get(ICacheFactory::class),
1160
-				$c->get(IEventDispatcher::class),
1161
-				$c->get(\OCP\Contacts\IManager::class),
1162
-				$c->get(IURLGenerator::class),
1163
-				$c->get(IUserManager::class),
1164
-			);
1165
-		});
262
+    /** @var string */
263
+    private $webRoot;
264
+
265
+    /**
266
+     * @param string $webRoot
267
+     * @param \OC\Config $config
268
+     */
269
+    public function __construct($webRoot, \OC\Config $config) {
270
+        parent::__construct();
271
+        $this->webRoot = $webRoot;
272
+
273
+        // To find out if we are running from CLI or not
274
+        $this->registerParameter('isCLI', \OC::$CLI);
275
+        $this->registerParameter('serverRoot', \OC::$SERVERROOT);
276
+
277
+        $this->registerService(ContainerInterface::class, function (ContainerInterface $c) {
278
+            return $c;
279
+        });
280
+        $this->registerDeprecatedAlias(\OCP\IServerContainer::class, ContainerInterface::class);
281
+
282
+        $this->registerAlias(\OCP\Calendar\IManager::class, \OC\Calendar\Manager::class);
283
+
284
+        $this->registerAlias(\OCP\Calendar\Resource\IManager::class, \OC\Calendar\Resource\Manager::class);
285
+
286
+        $this->registerAlias(\OCP\Calendar\Room\IManager::class, \OC\Calendar\Room\Manager::class);
287
+
288
+        $this->registerAlias(\OCP\Contacts\IManager::class, \OC\ContactsManager::class);
289
+
290
+        $this->registerAlias(\OCP\ContextChat\IContentManager::class, \OC\ContextChat\ContentManager::class);
291
+
292
+        $this->registerAlias(\OCP\DirectEditing\IManager::class, \OC\DirectEditing\Manager::class);
293
+        $this->registerAlias(ITemplateManager::class, TemplateManager::class);
294
+        $this->registerAlias(\OCP\Template\ITemplateManager::class, \OC\Template\TemplateManager::class);
295
+
296
+        $this->registerAlias(IActionFactory::class, ActionFactory::class);
297
+
298
+        $this->registerService(View::class, function (Server $c) {
299
+            return new View();
300
+        }, false);
301
+
302
+        $this->registerService(IPreview::class, function (ContainerInterface $c) {
303
+            return new PreviewManager(
304
+                $c->get(\OCP\IConfig::class),
305
+                $c->get(IRootFolder::class),
306
+                $c->get(IEventDispatcher::class),
307
+                $c->get(GeneratorHelper::class),
308
+                $c->get(ISession::class)->get('user_id'),
309
+                $c->get(Coordinator::class),
310
+                $c->get(IServerContainer::class),
311
+                $c->get(IBinaryFinder::class),
312
+                $c->get(IMagickSupport::class)
313
+            );
314
+        });
315
+        $this->registerAlias(IMimeIconProvider::class, MimeIconProvider::class);
316
+
317
+        $this->registerService(Watcher::class, function (ContainerInterface $c): Watcher {
318
+            return new Watcher(
319
+                $c->get(\OC\Preview\Storage\StorageFactory::class),
320
+                $c->get(PreviewMapper::class),
321
+                $c->get(IDBConnection::class),
322
+            );
323
+        });
324
+
325
+        $this->registerService(IProfiler::class, function (Server $c) {
326
+            return new Profiler($c->get(SystemConfig::class));
327
+        });
328
+
329
+        $this->registerService(Encryption\Manager::class, function (Server $c): Encryption\Manager {
330
+            $view = new View();
331
+            $util = new Encryption\Util(
332
+                $view,
333
+                $c->get(IUserManager::class),
334
+                $c->get(IGroupManager::class),
335
+                $c->get(\OCP\IConfig::class)
336
+            );
337
+            return new Encryption\Manager(
338
+                $c->get(\OCP\IConfig::class),
339
+                $c->get(LoggerInterface::class),
340
+                $c->getL10N('core'),
341
+                new View(),
342
+                $util,
343
+                new ArrayCache()
344
+            );
345
+        });
346
+        $this->registerAlias(\OCP\Encryption\IManager::class, Encryption\Manager::class);
347
+
348
+        $this->registerService(IFile::class, function (ContainerInterface $c) {
349
+            $util = new Encryption\Util(
350
+                new View(),
351
+                $c->get(IUserManager::class),
352
+                $c->get(IGroupManager::class),
353
+                $c->get(\OCP\IConfig::class)
354
+            );
355
+            return new Encryption\File(
356
+                $util,
357
+                $c->get(IRootFolder::class),
358
+                $c->get(\OCP\Share\IManager::class)
359
+            );
360
+        });
361
+
362
+        $this->registerService(IStorage::class, function (ContainerInterface $c) {
363
+            $view = new View();
364
+            $util = new Encryption\Util(
365
+                $view,
366
+                $c->get(IUserManager::class),
367
+                $c->get(IGroupManager::class),
368
+                $c->get(\OCP\IConfig::class)
369
+            );
370
+
371
+            return new Encryption\Keys\Storage(
372
+                $view,
373
+                $util,
374
+                $c->get(ICrypto::class),
375
+                $c->get(\OCP\IConfig::class)
376
+            );
377
+        });
378
+
379
+        $this->registerAlias(\OCP\ITagManager::class, TagManager::class);
380
+
381
+        $this->registerService('SystemTagManagerFactory', function (ContainerInterface $c) {
382
+            /** @var \OCP\IConfig $config */
383
+            $config = $c->get(\OCP\IConfig::class);
384
+            $factoryClass = $config->getSystemValue('systemtags.managerFactory', SystemTagManagerFactory::class);
385
+            return new $factoryClass($this);
386
+        });
387
+        $this->registerService(ISystemTagManager::class, function (ContainerInterface $c) {
388
+            return $c->get('SystemTagManagerFactory')->getManager();
389
+        });
390
+        /** @deprecated 19.0.0 */
391
+        $this->registerDeprecatedAlias('SystemTagManager', ISystemTagManager::class);
392
+
393
+        $this->registerService(ISystemTagObjectMapper::class, function (ContainerInterface $c) {
394
+            return $c->get('SystemTagManagerFactory')->getObjectMapper();
395
+        });
396
+        $this->registerAlias(IFileAccess::class, FileAccess::class);
397
+        $this->registerService('RootFolder', function (ContainerInterface $c) {
398
+            $manager = \OC\Files\Filesystem::getMountManager();
399
+            $view = new View();
400
+            /** @var IUserSession $userSession */
401
+            $userSession = $c->get(IUserSession::class);
402
+            $root = new Root(
403
+                $manager,
404
+                $view,
405
+                $userSession->getUser(),
406
+                $c->get(IUserMountCache::class),
407
+                $this->get(LoggerInterface::class),
408
+                $this->get(IUserManager::class),
409
+                $this->get(IEventDispatcher::class),
410
+                $this->get(ICacheFactory::class),
411
+                $this->get(IAppConfig::class),
412
+            );
413
+
414
+            $previewConnector = new \OC\Preview\WatcherConnector(
415
+                $root,
416
+                $c->get(SystemConfig::class),
417
+                $this->get(IEventDispatcher::class)
418
+            );
419
+            $previewConnector->connectWatcher();
420
+
421
+            return $root;
422
+        });
423
+        $this->registerService(HookConnector::class, function (ContainerInterface $c) {
424
+            return new HookConnector(
425
+                $c->get(IRootFolder::class),
426
+                new View(),
427
+                $c->get(IEventDispatcher::class),
428
+                $c->get(LoggerInterface::class)
429
+            );
430
+        });
431
+
432
+        $this->registerService(IRootFolder::class, function (ContainerInterface $c) {
433
+            return new LazyRoot(function () use ($c) {
434
+                return $c->get('RootFolder');
435
+            });
436
+        });
437
+
438
+        $this->registerAlias(\OCP\IUserManager::class, \OC\User\Manager::class);
439
+
440
+        $this->registerService(DisplayNameCache::class, function (ContainerInterface $c) {
441
+            return $c->get(\OC\User\Manager::class)->getDisplayNameCache();
442
+        });
443
+
444
+        $this->registerService(\OCP\IGroupManager::class, function (ContainerInterface $c) {
445
+            $groupManager = new \OC\Group\Manager(
446
+                $this->get(IUserManager::class),
447
+                $this->get(IEventDispatcher::class),
448
+                $this->get(LoggerInterface::class),
449
+                $this->get(ICacheFactory::class),
450
+                $this->get(IRemoteAddress::class),
451
+            );
452
+            return $groupManager;
453
+        });
454
+
455
+        $this->registerService(Store::class, function (ContainerInterface $c) {
456
+            $session = $c->get(ISession::class);
457
+            if (\OC::$server->get(SystemConfig::class)->getValue('installed', false)) {
458
+                $tokenProvider = $c->get(IProvider::class);
459
+            } else {
460
+                $tokenProvider = null;
461
+            }
462
+            $logger = $c->get(LoggerInterface::class);
463
+            $crypto = $c->get(ICrypto::class);
464
+            return new Store($session, $logger, $crypto, $tokenProvider);
465
+        });
466
+        $this->registerAlias(IStore::class, Store::class);
467
+        $this->registerAlias(IProvider::class, Authentication\Token\Manager::class);
468
+        $this->registerAlias(OCPIProvider::class, Authentication\Token\Manager::class);
469
+
470
+        $this->registerService(\OC\User\Session::class, function (Server $c) {
471
+            $manager = $c->get(IUserManager::class);
472
+            $session = new \OC\Session\Memory();
473
+            $timeFactory = new TimeFactory();
474
+            // Token providers might require a working database. This code
475
+            // might however be called when Nextcloud is not yet setup.
476
+            if (\OC::$server->get(SystemConfig::class)->getValue('installed', false)) {
477
+                $provider = $c->get(IProvider::class);
478
+            } else {
479
+                $provider = null;
480
+            }
481
+
482
+            $userSession = new \OC\User\Session(
483
+                $manager,
484
+                $session,
485
+                $timeFactory,
486
+                $provider,
487
+                $c->get(\OCP\IConfig::class),
488
+                $c->get(ISecureRandom::class),
489
+                $c->get('LockdownManager'),
490
+                $c->get(LoggerInterface::class),
491
+                $c->get(IEventDispatcher::class),
492
+            );
493
+            /** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */
494
+            $userSession->listen('\OC\User', 'preCreateUser', function ($uid, $password) {
495
+                \OC_Hook::emit('OC_User', 'pre_createUser', ['run' => true, 'uid' => $uid, 'password' => $password]);
496
+            });
497
+            /** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */
498
+            $userSession->listen('\OC\User', 'postCreateUser', function ($user, $password) {
499
+                /** @var \OC\User\User $user */
500
+                \OC_Hook::emit('OC_User', 'post_createUser', ['uid' => $user->getUID(), 'password' => $password]);
501
+            });
502
+            /** @deprecated 21.0.0 use BeforeUserDeletedEvent event with the IEventDispatcher instead */
503
+            $userSession->listen('\OC\User', 'preDelete', function ($user) {
504
+                /** @var \OC\User\User $user */
505
+                \OC_Hook::emit('OC_User', 'pre_deleteUser', ['run' => true, 'uid' => $user->getUID()]);
506
+            });
507
+            /** @deprecated 21.0.0 use UserDeletedEvent event with the IEventDispatcher instead */
508
+            $userSession->listen('\OC\User', 'postDelete', function ($user) {
509
+                /** @var \OC\User\User $user */
510
+                \OC_Hook::emit('OC_User', 'post_deleteUser', ['uid' => $user->getUID()]);
511
+            });
512
+            $userSession->listen('\OC\User', 'preSetPassword', function ($user, $password, $recoveryPassword) {
513
+                /** @var \OC\User\User $user */
514
+                \OC_Hook::emit('OC_User', 'pre_setPassword', ['run' => true, 'uid' => $user->getUID(), 'password' => $password, 'recoveryPassword' => $recoveryPassword]);
515
+            });
516
+            $userSession->listen('\OC\User', 'postSetPassword', function ($user, $password, $recoveryPassword) {
517
+                /** @var \OC\User\User $user */
518
+                \OC_Hook::emit('OC_User', 'post_setPassword', ['run' => true, 'uid' => $user->getUID(), 'password' => $password, 'recoveryPassword' => $recoveryPassword]);
519
+            });
520
+            $userSession->listen('\OC\User', 'preLogin', function ($uid, $password) {
521
+                \OC_Hook::emit('OC_User', 'pre_login', ['run' => true, 'uid' => $uid, 'password' => $password]);
522
+
523
+                /** @var IEventDispatcher $dispatcher */
524
+                $dispatcher = $this->get(IEventDispatcher::class);
525
+                $dispatcher->dispatchTyped(new BeforeUserLoggedInEvent($uid, $password));
526
+            });
527
+            $userSession->listen('\OC\User', 'postLogin', function ($user, $loginName, $password, $isTokenLogin) {
528
+                /** @var \OC\User\User $user */
529
+                \OC_Hook::emit('OC_User', 'post_login', ['run' => true, 'uid' => $user->getUID(), 'loginName' => $loginName, 'password' => $password, 'isTokenLogin' => $isTokenLogin]);
530
+
531
+                /** @var IEventDispatcher $dispatcher */
532
+                $dispatcher = $this->get(IEventDispatcher::class);
533
+                $dispatcher->dispatchTyped(new UserLoggedInEvent($user, $loginName, $password, $isTokenLogin));
534
+            });
535
+            $userSession->listen('\OC\User', 'preRememberedLogin', function ($uid) {
536
+                /** @var IEventDispatcher $dispatcher */
537
+                $dispatcher = $this->get(IEventDispatcher::class);
538
+                $dispatcher->dispatchTyped(new BeforeUserLoggedInWithCookieEvent($uid));
539
+            });
540
+            $userSession->listen('\OC\User', 'postRememberedLogin', function ($user, $password) {
541
+                /** @var \OC\User\User $user */
542
+                \OC_Hook::emit('OC_User', 'post_login', ['run' => true, 'uid' => $user->getUID(), 'password' => $password]);
543
+
544
+                /** @var IEventDispatcher $dispatcher */
545
+                $dispatcher = $this->get(IEventDispatcher::class);
546
+                $dispatcher->dispatchTyped(new UserLoggedInWithCookieEvent($user, $password));
547
+            });
548
+            $userSession->listen('\OC\User', 'logout', function ($user) {
549
+                \OC_Hook::emit('OC_User', 'logout', []);
550
+
551
+                /** @var IEventDispatcher $dispatcher */
552
+                $dispatcher = $this->get(IEventDispatcher::class);
553
+                $dispatcher->dispatchTyped(new BeforeUserLoggedOutEvent($user));
554
+            });
555
+            $userSession->listen('\OC\User', 'postLogout', function ($user) {
556
+                /** @var IEventDispatcher $dispatcher */
557
+                $dispatcher = $this->get(IEventDispatcher::class);
558
+                $dispatcher->dispatchTyped(new UserLoggedOutEvent($user));
559
+            });
560
+            $userSession->listen('\OC\User', 'changeUser', function ($user, $feature, $value, $oldValue) {
561
+                /** @var \OC\User\User $user */
562
+                \OC_Hook::emit('OC_User', 'changeUser', ['run' => true, 'user' => $user, 'feature' => $feature, 'value' => $value, 'old_value' => $oldValue]);
563
+            });
564
+            return $userSession;
565
+        });
566
+        $this->registerAlias(\OCP\IUserSession::class, \OC\User\Session::class);
567
+
568
+        $this->registerAlias(\OCP\Authentication\TwoFactorAuth\IRegistry::class, \OC\Authentication\TwoFactorAuth\Registry::class);
569
+
570
+        $this->registerAlias(INavigationManager::class, \OC\NavigationManager::class);
571
+
572
+        $this->registerAlias(\OCP\IConfig::class, \OC\AllConfig::class);
573
+
574
+        $this->registerService(\OC\SystemConfig::class, function ($c) use ($config) {
575
+            return new \OC\SystemConfig($config);
576
+        });
577
+
578
+        $this->registerAlias(IAppConfig::class, \OC\AppConfig::class);
579
+        $this->registerAlias(IUserConfig::class, \OC\Config\UserConfig::class);
580
+        $this->registerAlias(IAppManager::class, AppManager::class);
581
+
582
+        $this->registerService(IFactory::class, function (Server $c) {
583
+            return new \OC\L10N\Factory(
584
+                $c->get(\OCP\IConfig::class),
585
+                $c->getRequest(),
586
+                $c->get(IUserSession::class),
587
+                $c->get(ICacheFactory::class),
588
+                \OC::$SERVERROOT,
589
+                $c->get(IAppManager::class),
590
+            );
591
+        });
592
+
593
+        $this->registerAlias(IURLGenerator::class, URLGenerator::class);
594
+
595
+        $this->registerAlias(ICache::class, Cache\File::class);
596
+        $this->registerService(Factory::class, function (Server $c) {
597
+            $profiler = $c->get(IProfiler::class);
598
+            $logger = $c->get(LoggerInterface::class);
599
+            $serverVersion = $c->get(ServerVersion::class);
600
+            /** @var SystemConfig $config */
601
+            $config = $c->get(SystemConfig::class);
602
+            if (!$config->getValue('installed', false) || (defined('PHPUNIT_RUN') && PHPUNIT_RUN)) {
603
+                return new \OC\Memcache\Factory(
604
+                    $logger,
605
+                    $profiler,
606
+                    $serverVersion,
607
+                    ArrayCache::class,
608
+                    ArrayCache::class,
609
+                    ArrayCache::class
610
+                );
611
+            }
612
+
613
+            return new \OC\Memcache\Factory(
614
+                $logger,
615
+                $profiler,
616
+                $serverVersion,
617
+                /** @psalm-taint-escape callable */
618
+                $config->getValue('memcache.local', null),
619
+                /** @psalm-taint-escape callable */
620
+                $config->getValue('memcache.distributed', null),
621
+                /** @psalm-taint-escape callable */
622
+                $config->getValue('memcache.locking', null),
623
+                /** @psalm-taint-escape callable */
624
+                $config->getValue('redis_log_file')
625
+            );
626
+        });
627
+        $this->registerAlias(ICacheFactory::class, Factory::class);
628
+
629
+        $this->registerService('RedisFactory', function (Server $c) {
630
+            $systemConfig = $c->get(SystemConfig::class);
631
+            return new RedisFactory($systemConfig, $c->get(IEventLogger::class));
632
+        });
633
+
634
+        $this->registerService(\OCP\Activity\IManager::class, function (Server $c) {
635
+            $l10n = $this->get(IFactory::class)->get('lib');
636
+            return new \OC\Activity\Manager(
637
+                $c->getRequest(),
638
+                $c->get(IUserSession::class),
639
+                $c->get(\OCP\IConfig::class),
640
+                $c->get(IValidator::class),
641
+                $c->get(IRichTextFormatter::class),
642
+                $l10n,
643
+                $c->get(ITimeFactory::class),
644
+            );
645
+        });
646
+
647
+        $this->registerService(\OCP\Activity\IEventMerger::class, function (Server $c) {
648
+            return new \OC\Activity\EventMerger(
649
+                $c->getL10N('lib')
650
+            );
651
+        });
652
+        $this->registerAlias(IValidator::class, Validator::class);
653
+
654
+        $this->registerService(AvatarManager::class, function (Server $c) {
655
+            return new AvatarManager(
656
+                $c->get(IUserSession::class),
657
+                $c->get(\OC\User\Manager::class),
658
+                $c->getAppDataDir('avatar'),
659
+                $c->getL10N('lib'),
660
+                $c->get(LoggerInterface::class),
661
+                $c->get(\OCP\IConfig::class),
662
+                $c->get(IAccountManager::class),
663
+                $c->get(KnownUserService::class)
664
+            );
665
+        });
666
+
667
+        $this->registerAlias(IAvatarManager::class, AvatarManager::class);
668
+
669
+        $this->registerAlias(\OCP\Support\CrashReport\IRegistry::class, \OC\Support\CrashReport\Registry::class);
670
+        $this->registerAlias(\OCP\Support\Subscription\IRegistry::class, \OC\Support\Subscription\Registry::class);
671
+        $this->registerAlias(\OCP\Support\Subscription\IAssertion::class, \OC\Support\Subscription\Assertion::class);
672
+
673
+        /** Only used by the PsrLoggerAdapter should not be used by apps */
674
+        $this->registerService(\OC\Log::class, function (Server $c) {
675
+            $logType = $c->get(AllConfig::class)->getSystemValue('log_type', 'file');
676
+            $factory = new LogFactory($c, $this->get(SystemConfig::class));
677
+            $logger = $factory->get($logType);
678
+            $registry = $c->get(\OCP\Support\CrashReport\IRegistry::class);
679
+
680
+            return new Log($logger, $this->get(SystemConfig::class), crashReporters: $registry);
681
+        });
682
+        // PSR-3 logger
683
+        $this->registerAlias(LoggerInterface::class, PsrLoggerAdapter::class);
684
+
685
+        $this->registerService(ILogFactory::class, function (Server $c) {
686
+            return new LogFactory($c, $this->get(SystemConfig::class));
687
+        });
688
+
689
+        $this->registerAlias(IJobList::class, \OC\BackgroundJob\JobList::class);
690
+
691
+        $this->registerService(Router::class, function (Server $c) {
692
+            $cacheFactory = $c->get(ICacheFactory::class);
693
+            if ($cacheFactory->isLocalCacheAvailable()) {
694
+                $router = $c->resolve(CachingRouter::class);
695
+            } else {
696
+                $router = $c->resolve(Router::class);
697
+            }
698
+            return $router;
699
+        });
700
+        $this->registerAlias(IRouter::class, Router::class);
701
+
702
+        $this->registerService(\OC\Security\RateLimiting\Backend\IBackend::class, function ($c) {
703
+            $config = $c->get(\OCP\IConfig::class);
704
+            if (ltrim($config->getSystemValueString('memcache.distributed', ''), '\\') === \OC\Memcache\Redis::class) {
705
+                $backend = new \OC\Security\RateLimiting\Backend\MemoryCacheBackend(
706
+                    $c->get(AllConfig::class),
707
+                    $this->get(ICacheFactory::class),
708
+                    new \OC\AppFramework\Utility\TimeFactory()
709
+                );
710
+            } else {
711
+                $backend = new \OC\Security\RateLimiting\Backend\DatabaseBackend(
712
+                    $c->get(AllConfig::class),
713
+                    $c->get(IDBConnection::class),
714
+                    new \OC\AppFramework\Utility\TimeFactory()
715
+                );
716
+            }
717
+
718
+            return $backend;
719
+        });
720
+
721
+        $this->registerAlias(\OCP\Security\ISecureRandom::class, SecureRandom::class);
722
+        $this->registerAlias(\OCP\Security\IRemoteHostValidator::class, \OC\Security\RemoteHostValidator::class);
723
+        $this->registerAlias(IVerificationToken::class, VerificationToken::class);
724
+
725
+        $this->registerAlias(ICrypto::class, Crypto::class);
726
+
727
+        $this->registerAlias(IHasher::class, Hasher::class);
728
+
729
+        $this->registerAlias(ICredentialsManager::class, CredentialsManager::class);
730
+
731
+        $this->registerAlias(IDBConnection::class, ConnectionAdapter::class);
732
+        $this->registerService(Connection::class, function (Server $c) {
733
+            $systemConfig = $c->get(SystemConfig::class);
734
+            $factory = new \OC\DB\ConnectionFactory($systemConfig, $c->get(ICacheFactory::class));
735
+            $type = $systemConfig->getValue('dbtype', 'sqlite');
736
+            if (!$factory->isValidType($type)) {
737
+                throw new \OC\DatabaseException('Invalid database type');
738
+            }
739
+            $connection = $factory->getConnection($type, []);
740
+            return $connection;
741
+        });
742
+
743
+        $this->registerAlias(ICertificateManager::class, CertificateManager::class);
744
+        $this->registerAlias(IClientService::class, ClientService::class);
745
+        $this->registerService(NegativeDnsCache::class, function (ContainerInterface $c) {
746
+            return new NegativeDnsCache(
747
+                $c->get(ICacheFactory::class),
748
+            );
749
+        });
750
+        $this->registerDeprecatedAlias('HttpClientService', IClientService::class);
751
+        $this->registerService(IEventLogger::class, function (ContainerInterface $c) {
752
+            return new EventLogger($c->get(SystemConfig::class), $c->get(LoggerInterface::class), $c->get(Log::class));
753
+        });
754
+
755
+        $this->registerService(IQueryLogger::class, function (ContainerInterface $c) {
756
+            $queryLogger = new QueryLogger();
757
+            if ($c->get(SystemConfig::class)->getValue('debug', false)) {
758
+                // In debug mode, module is being activated by default
759
+                $queryLogger->activate();
760
+            }
761
+            return $queryLogger;
762
+        });
763
+
764
+        $this->registerAlias(ITempManager::class, TempManager::class);
765
+        $this->registerAlias(IDateTimeZone::class, DateTimeZone::class);
766
+
767
+        $this->registerService(IDateTimeFormatter::class, function (Server $c) {
768
+            $language = $c->get(\OCP\IConfig::class)->getUserValue($c->get(ISession::class)->get('user_id'), 'core', 'lang', null);
769
+
770
+            return new DateTimeFormatter(
771
+                $c->get(IDateTimeZone::class)->getTimeZone(),
772
+                $c->getL10N('lib', $language)
773
+            );
774
+        });
775
+
776
+        $this->registerService(IUserMountCache::class, function (ContainerInterface $c) {
777
+            $mountCache = $c->get(UserMountCache::class);
778
+            $listener = new UserMountCacheListener($mountCache);
779
+            $listener->listen($c->get(IUserManager::class));
780
+            return $mountCache;
781
+        });
782
+
783
+        $this->registerService(IMountProviderCollection::class, function (ContainerInterface $c) {
784
+            $loader = $c->get(IStorageFactory::class);
785
+            $mountCache = $c->get(IUserMountCache::class);
786
+            $eventLogger = $c->get(IEventLogger::class);
787
+            $manager = new MountProviderCollection($loader, $mountCache, $eventLogger);
788
+
789
+            // builtin providers
790
+
791
+            $config = $c->get(\OCP\IConfig::class);
792
+            $logger = $c->get(LoggerInterface::class);
793
+            $objectStoreConfig = $c->get(PrimaryObjectStoreConfig::class);
794
+            $manager->registerProvider(new CacheMountProvider($config));
795
+            $manager->registerHomeProvider(new LocalHomeMountProvider());
796
+            $manager->registerHomeProvider(new ObjectHomeMountProvider($objectStoreConfig));
797
+            $manager->registerRootProvider(new RootMountProvider($objectStoreConfig, $config));
798
+
799
+            return $manager;
800
+        });
801
+
802
+        $this->registerService(IBus::class, function (ContainerInterface $c) {
803
+            $busClass = $c->get(\OCP\IConfig::class)->getSystemValueString('commandbus');
804
+            if ($busClass) {
805
+                [$app, $class] = explode('::', $busClass, 2);
806
+                if ($c->get(IAppManager::class)->isEnabledForUser($app)) {
807
+                    $c->get(IAppManager::class)->loadApp($app);
808
+                    return $c->get($class);
809
+                } else {
810
+                    throw new ServiceUnavailableException("The app providing the command bus ($app) is not enabled");
811
+                }
812
+            } else {
813
+                $jobList = $c->get(IJobList::class);
814
+                return new CronBus($jobList);
815
+            }
816
+        });
817
+        $this->registerDeprecatedAlias('AsyncCommandBus', IBus::class);
818
+        $this->registerAlias(ITrustedDomainHelper::class, TrustedDomainHelper::class);
819
+        $this->registerAlias(IThrottler::class, Throttler::class);
820
+
821
+        $this->registerService(\OC\Security\Bruteforce\Backend\IBackend::class, function ($c) {
822
+            $config = $c->get(\OCP\IConfig::class);
823
+            if (!$config->getSystemValueBool('auth.bruteforce.protection.force.database', false)
824
+                && ltrim($config->getSystemValueString('memcache.distributed', ''), '\\') === \OC\Memcache\Redis::class) {
825
+                $backend = $c->get(\OC\Security\Bruteforce\Backend\MemoryCacheBackend::class);
826
+            } else {
827
+                $backend = $c->get(\OC\Security\Bruteforce\Backend\DatabaseBackend::class);
828
+            }
829
+
830
+            return $backend;
831
+        });
832
+
833
+        $this->registerDeprecatedAlias('IntegrityCodeChecker', Checker::class);
834
+        $this->registerService(Checker::class, function (ContainerInterface $c) {
835
+            // IConfig requires a working database. This code
836
+            // might however be called when Nextcloud is not yet setup.
837
+            if (\OC::$server->get(SystemConfig::class)->getValue('installed', false)) {
838
+                $config = $c->get(\OCP\IConfig::class);
839
+                $appConfig = $c->get(\OCP\IAppConfig::class);
840
+            } else {
841
+                $config = null;
842
+                $appConfig = null;
843
+            }
844
+
845
+            return new Checker(
846
+                $c->get(ServerVersion::class),
847
+                $c->get(EnvironmentHelper::class),
848
+                new FileAccessHelper(),
849
+                $config,
850
+                $appConfig,
851
+                $c->get(ICacheFactory::class),
852
+                $c->get(IAppManager::class),
853
+                $c->get(IMimeTypeDetector::class)
854
+            );
855
+        });
856
+        $this->registerService(Request::class, function (ContainerInterface $c) {
857
+            if (isset($this['urlParams'])) {
858
+                $urlParams = $this['urlParams'];
859
+            } else {
860
+                $urlParams = [];
861
+            }
862
+
863
+            if (defined('PHPUNIT_RUN') && PHPUNIT_RUN
864
+                && in_array('fakeinput', stream_get_wrappers())
865
+            ) {
866
+                $stream = 'fakeinput://data';
867
+            } else {
868
+                $stream = 'php://input';
869
+            }
870
+
871
+            return new Request(
872
+                [
873
+                    'get' => $_GET,
874
+                    'post' => $_POST,
875
+                    'files' => $_FILES,
876
+                    'server' => $_SERVER,
877
+                    'env' => $_ENV,
878
+                    'cookies' => $_COOKIE,
879
+                    'method' => (isset($_SERVER) && isset($_SERVER['REQUEST_METHOD']))
880
+                        ? $_SERVER['REQUEST_METHOD']
881
+                        : '',
882
+                    'urlParams' => $urlParams,
883
+                ],
884
+                $this->get(IRequestId::class),
885
+                $this->get(\OCP\IConfig::class),
886
+                $this->get(CsrfTokenManager::class),
887
+                $stream
888
+            );
889
+        });
890
+        $this->registerAlias(\OCP\IRequest::class, Request::class);
891
+
892
+        $this->registerService(IRequestId::class, function (ContainerInterface $c): IRequestId {
893
+            return new RequestId(
894
+                $_SERVER['UNIQUE_ID'] ?? '',
895
+                $this->get(ISecureRandom::class)
896
+            );
897
+        });
898
+
899
+        /** @since 32.0.0 */
900
+        $this->registerAlias(IEmailValidator::class, EmailValidator::class);
901
+
902
+        $this->registerService(IMailer::class, function (Server $c) {
903
+            return new Mailer(
904
+                $c->get(\OCP\IConfig::class),
905
+                $c->get(LoggerInterface::class),
906
+                $c->get(Defaults::class),
907
+                $c->get(IURLGenerator::class),
908
+                $c->getL10N('lib'),
909
+                $c->get(IEventDispatcher::class),
910
+                $c->get(IFactory::class),
911
+                $c->get(IEmailValidator::class),
912
+            );
913
+        });
914
+
915
+        /** @since 30.0.0 */
916
+        $this->registerAlias(\OCP\Mail\Provider\IManager::class, \OC\Mail\Provider\Manager::class);
917
+
918
+        $this->registerService(ILDAPProviderFactory::class, function (ContainerInterface $c) {
919
+            $config = $c->get(\OCP\IConfig::class);
920
+            $factoryClass = $config->getSystemValue('ldapProviderFactory', null);
921
+            if (is_null($factoryClass) || !class_exists($factoryClass)) {
922
+                return new NullLDAPProviderFactory($this);
923
+            }
924
+            /** @var \OCP\LDAP\ILDAPProviderFactory $factory */
925
+            return new $factoryClass($this);
926
+        });
927
+        $this->registerService(ILDAPProvider::class, function (ContainerInterface $c) {
928
+            $factory = $c->get(ILDAPProviderFactory::class);
929
+            return $factory->getLDAPProvider();
930
+        });
931
+        $this->registerService(ILockingProvider::class, function (ContainerInterface $c) {
932
+            $ini = $c->get(IniGetWrapper::class);
933
+            $config = $c->get(\OCP\IConfig::class);
934
+            $ttl = $config->getSystemValueInt('filelocking.ttl', max(3600, $ini->getNumeric('max_execution_time')));
935
+            if ($config->getSystemValueBool('filelocking.enabled', true) || (defined('PHPUNIT_RUN') && PHPUNIT_RUN)) {
936
+                /** @var \OC\Memcache\Factory $memcacheFactory */
937
+                $memcacheFactory = $c->get(ICacheFactory::class);
938
+                $memcache = $memcacheFactory->createLocking('lock');
939
+                if (!($memcache instanceof \OC\Memcache\NullCache)) {
940
+                    $timeFactory = $c->get(ITimeFactory::class);
941
+                    return new MemcacheLockingProvider($memcache, $timeFactory, $ttl);
942
+                }
943
+                return new DBLockingProvider(
944
+                    $c->get(IDBConnection::class),
945
+                    new TimeFactory(),
946
+                    $ttl,
947
+                    !\OC::$CLI
948
+                );
949
+            }
950
+            return new NoopLockingProvider();
951
+        });
952
+
953
+        $this->registerService(ILockManager::class, function (Server $c): LockManager {
954
+            return new LockManager();
955
+        });
956
+
957
+        $this->registerAlias(ILockdownManager::class, 'LockdownManager');
958
+        $this->registerService(SetupManager::class, function ($c) {
959
+            // create the setupmanager through the mount manager to resolve the cyclic dependency
960
+            return $c->get(\OC\Files\Mount\Manager::class)->getSetupManager();
961
+        });
962
+        $this->registerAlias(IMountManager::class, \OC\Files\Mount\Manager::class);
963
+
964
+        $this->registerService(IMimeTypeDetector::class, function (ContainerInterface $c) {
965
+            return new \OC\Files\Type\Detection(
966
+                $c->get(IURLGenerator::class),
967
+                $c->get(LoggerInterface::class),
968
+                \OC::$configDir,
969
+                \OC::$SERVERROOT . '/resources/config/'
970
+            );
971
+        });
972
+
973
+        $this->registerAlias(IMimeTypeLoader::class, Loader::class);
974
+        $this->registerService(BundleFetcher::class, function () {
975
+            return new BundleFetcher($this->getL10N('lib'));
976
+        });
977
+        $this->registerAlias(\OCP\Notification\IManager::class, Manager::class);
978
+
979
+        $this->registerService(CapabilitiesManager::class, function (ContainerInterface $c) {
980
+            $manager = new CapabilitiesManager($c->get(LoggerInterface::class));
981
+            $manager->registerCapability(function () use ($c) {
982
+                return new \OC\OCS\CoreCapabilities($c->get(\OCP\IConfig::class));
983
+            });
984
+            $manager->registerCapability(function () use ($c) {
985
+                return $c->get(\OC\Security\Bruteforce\Capabilities::class);
986
+            });
987
+            return $manager;
988
+        });
989
+
990
+        $this->registerService(ICommentsManager::class, function (Server $c) {
991
+            $config = $c->get(\OCP\IConfig::class);
992
+            $factoryClass = $config->getSystemValue('comments.managerFactory', CommentsManagerFactory::class);
993
+            /** @var \OCP\Comments\ICommentsManagerFactory $factory */
994
+            $factory = new $factoryClass($this);
995
+            $manager = $factory->getManager();
996
+
997
+            $manager->registerDisplayNameResolver('user', function ($id) use ($c) {
998
+                $manager = $c->get(IUserManager::class);
999
+                $userDisplayName = $manager->getDisplayName($id);
1000
+                if ($userDisplayName === null) {
1001
+                    $l = $c->get(IFactory::class)->get('core');
1002
+                    return $l->t('Unknown account');
1003
+                }
1004
+                return $userDisplayName;
1005
+            });
1006
+
1007
+            return $manager;
1008
+        });
1009
+
1010
+        $this->registerAlias(\OC_Defaults::class, 'ThemingDefaults');
1011
+        $this->registerService('ThemingDefaults', function (Server $c) {
1012
+            try {
1013
+                $classExists = class_exists('OCA\Theming\ThemingDefaults');
1014
+            } catch (\OCP\AutoloadNotAllowedException $e) {
1015
+                // App disabled or in maintenance mode
1016
+                $classExists = false;
1017
+            }
1018
+
1019
+            if ($classExists && $c->get(\OCP\IConfig::class)->getSystemValueBool('installed', false) && $c->get(IAppManager::class)->isEnabledForAnyone('theming') && $c->get(TrustedDomainHelper::class)->isTrustedDomain($c->getRequest()->getInsecureServerHost())) {
1020
+                $backgroundService = new BackgroundService(
1021
+                    $c->get(IRootFolder::class),
1022
+                    $c->get(IAppDataFactory::class)->get('theming'),
1023
+                    $c->get(IAppConfig::class),
1024
+                    $c->get(\OCP\IConfig::class),
1025
+                    $c->get(ISession::class)->get('user_id'),
1026
+                );
1027
+                $imageManager = new ImageManager(
1028
+                    $c->get(\OCP\IConfig::class),
1029
+                    $c->get(IAppDataFactory::class)->get('theming'),
1030
+                    $c->get(IURLGenerator::class),
1031
+                    $c->get(ICacheFactory::class),
1032
+                    $c->get(LoggerInterface::class),
1033
+                    $c->get(ITempManager::class),
1034
+                    $backgroundService,
1035
+                );
1036
+                return new ThemingDefaults(
1037
+                    new AppConfig(
1038
+                        $c->get(\OCP\IConfig::class),
1039
+                        $c->get(\OCP\IAppConfig::class),
1040
+                        'theming',
1041
+                    ),
1042
+                    $c->get(IUserConfig::class),
1043
+                    $c->get(IFactory::class)->get('theming'),
1044
+                    $c->get(IUserSession::class),
1045
+                    $c->get(IURLGenerator::class),
1046
+                    $c->get(ICacheFactory::class),
1047
+                    new Util(
1048
+                        $c->get(ServerVersion::class),
1049
+                        $c->get(\OCP\IConfig::class),
1050
+                        $this->get(IAppManager::class),
1051
+                        $c->get(IAppDataFactory::class)->get('theming'),
1052
+                        $imageManager,
1053
+                    ),
1054
+                    $imageManager,
1055
+                    $c->get(IAppManager::class),
1056
+                    $c->get(INavigationManager::class),
1057
+                    $backgroundService,
1058
+                );
1059
+            }
1060
+            return new \OC_Defaults();
1061
+        });
1062
+        $this->registerService(JSCombiner::class, function (Server $c) {
1063
+            return new JSCombiner(
1064
+                $c->getAppDataDir('js'),
1065
+                $c->get(IURLGenerator::class),
1066
+                $this->get(ICacheFactory::class),
1067
+                $c->get(\OCP\IConfig::class),
1068
+                $c->get(LoggerInterface::class)
1069
+            );
1070
+        });
1071
+        $this->registerAlias(\OCP\EventDispatcher\IEventDispatcher::class, \OC\EventDispatcher\EventDispatcher::class);
1072
+
1073
+        $this->registerService('CryptoWrapper', function (ContainerInterface $c) {
1074
+            // FIXME: Instantiated here due to cyclic dependency
1075
+            $request = new Request(
1076
+                [
1077
+                    'get' => $_GET,
1078
+                    'post' => $_POST,
1079
+                    'files' => $_FILES,
1080
+                    'server' => $_SERVER,
1081
+                    'env' => $_ENV,
1082
+                    'cookies' => $_COOKIE,
1083
+                    'method' => (isset($_SERVER) && isset($_SERVER['REQUEST_METHOD']))
1084
+                        ? $_SERVER['REQUEST_METHOD']
1085
+                        : null,
1086
+                ],
1087
+                $c->get(IRequestId::class),
1088
+                $c->get(\OCP\IConfig::class)
1089
+            );
1090
+
1091
+            return new CryptoWrapper(
1092
+                $c->get(ICrypto::class),
1093
+                $c->get(ISecureRandom::class),
1094
+                $request
1095
+            );
1096
+        });
1097
+        $this->registerService(SessionStorage::class, function (ContainerInterface $c) {
1098
+            return new SessionStorage($c->get(ISession::class));
1099
+        });
1100
+        $this->registerAlias(\OCP\Security\IContentSecurityPolicyManager::class, ContentSecurityPolicyManager::class);
1101
+
1102
+        $this->registerService(IProviderFactory::class, function (ContainerInterface $c) {
1103
+            $config = $c->get(\OCP\IConfig::class);
1104
+            $factoryClass = $config->getSystemValue('sharing.managerFactory', ProviderFactory::class);
1105
+            /** @var \OCP\Share\IProviderFactory $factory */
1106
+            return $c->get($factoryClass);
1107
+        });
1108
+
1109
+        $this->registerAlias(\OCP\Share\IManager::class, \OC\Share20\Manager::class);
1110
+
1111
+        $this->registerService(\OCP\Collaboration\Collaborators\ISearch::class, function (Server $c) {
1112
+            $instance = new Collaboration\Collaborators\Search($c);
1113
+
1114
+            // register default plugins
1115
+            $instance->registerPlugin(['shareType' => 'SHARE_TYPE_USER', 'class' => UserPlugin::class]);
1116
+            $instance->registerPlugin(['shareType' => 'SHARE_TYPE_USER', 'class' => UserByMailPlugin::class]);
1117
+            $instance->registerPlugin(['shareType' => 'SHARE_TYPE_GROUP', 'class' => GroupPlugin::class]);
1118
+            $instance->registerPlugin(['shareType' => 'SHARE_TYPE_EMAIL', 'class' => MailByMailPlugin::class]);
1119
+            $instance->registerPlugin(['shareType' => 'SHARE_TYPE_REMOTE', 'class' => RemotePlugin::class]);
1120
+            $instance->registerPlugin(['shareType' => 'SHARE_TYPE_REMOTE_GROUP', 'class' => RemoteGroupPlugin::class]);
1121
+
1122
+            return $instance;
1123
+        });
1124
+        $this->registerAlias(\OCP\Collaboration\Collaborators\ISearchResult::class, \OC\Collaboration\Collaborators\SearchResult::class);
1125
+
1126
+        $this->registerAlias(\OCP\Collaboration\AutoComplete\IManager::class, \OC\Collaboration\AutoComplete\Manager::class);
1127
+
1128
+        $this->registerAlias(\OCP\Collaboration\Resources\IProviderManager::class, \OC\Collaboration\Resources\ProviderManager::class);
1129
+        $this->registerAlias(\OCP\Collaboration\Resources\IManager::class, \OC\Collaboration\Resources\Manager::class);
1130
+
1131
+        $this->registerAlias(IReferenceManager::class, ReferenceManager::class);
1132
+        $this->registerAlias(ITeamManager::class, TeamManager::class);
1133
+
1134
+        $this->registerDeprecatedAlias('SettingsManager', \OC\Settings\Manager::class);
1135
+        $this->registerAlias(\OCP\Settings\IManager::class, \OC\Settings\Manager::class);
1136
+        $this->registerService(\OC\Files\AppData\Factory::class, function (ContainerInterface $c) {
1137
+            return new \OC\Files\AppData\Factory(
1138
+                $c->get(IRootFolder::class),
1139
+                $c->get(SystemConfig::class)
1140
+            );
1141
+        });
1142
+
1143
+        $this->registerService('LockdownManager', function (ContainerInterface $c) {
1144
+            return new LockdownManager(function () use ($c) {
1145
+                return $c->get(ISession::class);
1146
+            });
1147
+        });
1148
+
1149
+        $this->registerService(\OCP\OCS\IDiscoveryService::class, function (ContainerInterface $c) {
1150
+            return new DiscoveryService(
1151
+                $c->get(ICacheFactory::class),
1152
+                $c->get(IClientService::class)
1153
+            );
1154
+        });
1155
+        $this->registerAlias(IOCMDiscoveryService::class, OCMDiscoveryService::class);
1156
+
1157
+        $this->registerService(ICloudIdManager::class, function (ContainerInterface $c) {
1158
+            return new CloudIdManager(
1159
+                $c->get(ICacheFactory::class),
1160
+                $c->get(IEventDispatcher::class),
1161
+                $c->get(\OCP\Contacts\IManager::class),
1162
+                $c->get(IURLGenerator::class),
1163
+                $c->get(IUserManager::class),
1164
+            );
1165
+        });
1166 1166
 
1167
-		$this->registerAlias(\OCP\GlobalScale\IConfig::class, \OC\GlobalScale\Config::class);
1168
-		$this->registerAlias(ICloudFederationProviderManager::class, CloudFederationProviderManager::class);
1169
-		$this->registerService(ICloudFederationFactory::class, function (Server $c) {
1170
-			return new CloudFederationFactory();
1171
-		});
1167
+        $this->registerAlias(\OCP\GlobalScale\IConfig::class, \OC\GlobalScale\Config::class);
1168
+        $this->registerAlias(ICloudFederationProviderManager::class, CloudFederationProviderManager::class);
1169
+        $this->registerService(ICloudFederationFactory::class, function (Server $c) {
1170
+            return new CloudFederationFactory();
1171
+        });
1172 1172
 
1173
-		$this->registerAlias(\OCP\AppFramework\Utility\IControllerMethodReflector::class, \OC\AppFramework\Utility\ControllerMethodReflector::class);
1173
+        $this->registerAlias(\OCP\AppFramework\Utility\IControllerMethodReflector::class, \OC\AppFramework\Utility\ControllerMethodReflector::class);
1174 1174
 
1175
-		$this->registerAlias(\OCP\AppFramework\Utility\ITimeFactory::class, \OC\AppFramework\Utility\TimeFactory::class);
1176
-		$this->registerAlias(\Psr\Clock\ClockInterface::class, \OCP\AppFramework\Utility\ITimeFactory::class);
1175
+        $this->registerAlias(\OCP\AppFramework\Utility\ITimeFactory::class, \OC\AppFramework\Utility\TimeFactory::class);
1176
+        $this->registerAlias(\Psr\Clock\ClockInterface::class, \OCP\AppFramework\Utility\ITimeFactory::class);
1177 1177
 
1178
-		$this->registerService(Defaults::class, function (Server $c) {
1179
-			return new Defaults(
1180
-				$c->get('ThemingDefaults')
1181
-			);
1182
-		});
1178
+        $this->registerService(Defaults::class, function (Server $c) {
1179
+            return new Defaults(
1180
+                $c->get('ThemingDefaults')
1181
+            );
1182
+        });
1183 1183
 
1184
-		$this->registerService(\OCP\ISession::class, function (ContainerInterface $c) {
1185
-			return $c->get(\OCP\IUserSession::class)->getSession();
1186
-		}, false);
1184
+        $this->registerService(\OCP\ISession::class, function (ContainerInterface $c) {
1185
+            return $c->get(\OCP\IUserSession::class)->getSession();
1186
+        }, false);
1187 1187
 
1188
-		$this->registerService(IShareHelper::class, function (ContainerInterface $c) {
1189
-			return new ShareHelper(
1190
-				$c->get(\OCP\Share\IManager::class)
1191
-			);
1192
-		});
1188
+        $this->registerService(IShareHelper::class, function (ContainerInterface $c) {
1189
+            return new ShareHelper(
1190
+                $c->get(\OCP\Share\IManager::class)
1191
+            );
1192
+        });
1193 1193
 
1194
-		$this->registerService(IApiFactory::class, function (ContainerInterface $c) {
1195
-			return new ApiFactory($c->get(IClientService::class));
1196
-		});
1194
+        $this->registerService(IApiFactory::class, function (ContainerInterface $c) {
1195
+            return new ApiFactory($c->get(IClientService::class));
1196
+        });
1197 1197
 
1198
-		$this->registerService(IInstanceFactory::class, function (ContainerInterface $c) {
1199
-			$memcacheFactory = $c->get(ICacheFactory::class);
1200
-			return new InstanceFactory($memcacheFactory->createLocal('remoteinstance.'), $c->get(IClientService::class));
1201
-		});
1198
+        $this->registerService(IInstanceFactory::class, function (ContainerInterface $c) {
1199
+            $memcacheFactory = $c->get(ICacheFactory::class);
1200
+            return new InstanceFactory($memcacheFactory->createLocal('remoteinstance.'), $c->get(IClientService::class));
1201
+        });
1202 1202
 
1203
-		$this->registerAlias(IContactsStore::class, ContactsStore::class);
1204
-		$this->registerAlias(IAccountManager::class, AccountManager::class);
1203
+        $this->registerAlias(IContactsStore::class, ContactsStore::class);
1204
+        $this->registerAlias(IAccountManager::class, AccountManager::class);
1205 1205
 
1206
-		$this->registerAlias(IStorageFactory::class, StorageFactory::class);
1206
+        $this->registerAlias(IStorageFactory::class, StorageFactory::class);
1207 1207
 
1208
-		$this->registerAlias(\OCP\Dashboard\IManager::class, \OC\Dashboard\Manager::class);
1208
+        $this->registerAlias(\OCP\Dashboard\IManager::class, \OC\Dashboard\Manager::class);
1209 1209
 
1210
-		$this->registerAlias(IFullTextSearchManager::class, FullTextSearchManager::class);
1211
-		$this->registerAlias(IFilesMetadataManager::class, FilesMetadataManager::class);
1210
+        $this->registerAlias(IFullTextSearchManager::class, FullTextSearchManager::class);
1211
+        $this->registerAlias(IFilesMetadataManager::class, FilesMetadataManager::class);
1212 1212
 
1213
-		$this->registerAlias(ISubAdmin::class, SubAdmin::class);
1213
+        $this->registerAlias(ISubAdmin::class, SubAdmin::class);
1214 1214
 
1215
-		$this->registerAlias(IInitialStateService::class, InitialStateService::class);
1215
+        $this->registerAlias(IInitialStateService::class, InitialStateService::class);
1216 1216
 
1217
-		$this->registerAlias(\OCP\IEmojiHelper::class, \OC\EmojiHelper::class);
1217
+        $this->registerAlias(\OCP\IEmojiHelper::class, \OC\EmojiHelper::class);
1218 1218
 
1219
-		$this->registerAlias(\OCP\UserStatus\IManager::class, \OC\UserStatus\Manager::class);
1219
+        $this->registerAlias(\OCP\UserStatus\IManager::class, \OC\UserStatus\Manager::class);
1220 1220
 
1221
-		$this->registerAlias(IBroker::class, Broker::class);
1221
+        $this->registerAlias(IBroker::class, Broker::class);
1222 1222
 
1223
-		$this->registerAlias(\OCP\Files\AppData\IAppDataFactory::class, \OC\Files\AppData\Factory::class);
1223
+        $this->registerAlias(\OCP\Files\AppData\IAppDataFactory::class, \OC\Files\AppData\Factory::class);
1224 1224
 
1225
-		$this->registerAlias(\OCP\Files\IFilenameValidator::class, \OC\Files\FilenameValidator::class);
1225
+        $this->registerAlias(\OCP\Files\IFilenameValidator::class, \OC\Files\FilenameValidator::class);
1226 1226
 
1227
-		$this->registerAlias(IBinaryFinder::class, BinaryFinder::class);
1227
+        $this->registerAlias(IBinaryFinder::class, BinaryFinder::class);
1228 1228
 
1229
-		$this->registerAlias(\OCP\Share\IPublicShareTemplateFactory::class, \OC\Share20\PublicShareTemplateFactory::class);
1229
+        $this->registerAlias(\OCP\Share\IPublicShareTemplateFactory::class, \OC\Share20\PublicShareTemplateFactory::class);
1230 1230
 
1231
-		$this->registerAlias(ITranslationManager::class, TranslationManager::class);
1231
+        $this->registerAlias(ITranslationManager::class, TranslationManager::class);
1232 1232
 
1233
-		$this->registerAlias(IConversionManager::class, ConversionManager::class);
1233
+        $this->registerAlias(IConversionManager::class, ConversionManager::class);
1234 1234
 
1235
-		$this->registerAlias(ISpeechToTextManager::class, SpeechToTextManager::class);
1235
+        $this->registerAlias(ISpeechToTextManager::class, SpeechToTextManager::class);
1236 1236
 
1237
-		$this->registerAlias(IEventSourceFactory::class, EventSourceFactory::class);
1237
+        $this->registerAlias(IEventSourceFactory::class, EventSourceFactory::class);
1238 1238
 
1239
-		$this->registerAlias(\OCP\TextProcessing\IManager::class, \OC\TextProcessing\Manager::class);
1239
+        $this->registerAlias(\OCP\TextProcessing\IManager::class, \OC\TextProcessing\Manager::class);
1240 1240
 
1241
-		$this->registerAlias(\OCP\TextToImage\IManager::class, \OC\TextToImage\Manager::class);
1241
+        $this->registerAlias(\OCP\TextToImage\IManager::class, \OC\TextToImage\Manager::class);
1242 1242
 
1243
-		$this->registerAlias(ILimiter::class, Limiter::class);
1243
+        $this->registerAlias(ILimiter::class, Limiter::class);
1244 1244
 
1245
-		$this->registerAlias(IPhoneNumberUtil::class, PhoneNumberUtil::class);
1245
+        $this->registerAlias(IPhoneNumberUtil::class, PhoneNumberUtil::class);
1246 1246
 
1247
-		// there is no reason for having OCMProvider as a Service
1248
-		$this->registerDeprecatedAlias(ICapabilityAwareOCMProvider::class, OCMProvider::class);
1249
-		$this->registerDeprecatedAlias(IOCMProvider::class, OCMProvider::class);
1247
+        // there is no reason for having OCMProvider as a Service
1248
+        $this->registerDeprecatedAlias(ICapabilityAwareOCMProvider::class, OCMProvider::class);
1249
+        $this->registerDeprecatedAlias(IOCMProvider::class, OCMProvider::class);
1250 1250
 
1251
-		$this->registerAlias(ISetupCheckManager::class, SetupCheckManager::class);
1251
+        $this->registerAlias(ISetupCheckManager::class, SetupCheckManager::class);
1252 1252
 
1253
-		$this->registerAlias(IProfileManager::class, ProfileManager::class);
1253
+        $this->registerAlias(IProfileManager::class, ProfileManager::class);
1254 1254
 
1255
-		$this->registerAlias(IAvailabilityCoordinator::class, AvailabilityCoordinator::class);
1255
+        $this->registerAlias(IAvailabilityCoordinator::class, AvailabilityCoordinator::class);
1256 1256
 
1257
-		$this->registerAlias(IDeclarativeManager::class, DeclarativeManager::class);
1257
+        $this->registerAlias(IDeclarativeManager::class, DeclarativeManager::class);
1258 1258
 
1259
-		$this->registerAlias(\OCP\TaskProcessing\IManager::class, \OC\TaskProcessing\Manager::class);
1259
+        $this->registerAlias(\OCP\TaskProcessing\IManager::class, \OC\TaskProcessing\Manager::class);
1260 1260
 
1261
-		$this->registerAlias(IRemoteAddress::class, RemoteAddress::class);
1261
+        $this->registerAlias(IRemoteAddress::class, RemoteAddress::class);
1262 1262
 
1263
-		$this->registerAlias(\OCP\Security\Ip\IFactory::class, \OC\Security\Ip\Factory::class);
1263
+        $this->registerAlias(\OCP\Security\Ip\IFactory::class, \OC\Security\Ip\Factory::class);
1264 1264
 
1265
-		$this->registerAlias(IRichTextFormatter::class, \OC\RichObjectStrings\RichTextFormatter::class);
1265
+        $this->registerAlias(IRichTextFormatter::class, \OC\RichObjectStrings\RichTextFormatter::class);
1266 1266
 
1267
-		$this->registerAlias(ISignatureManager::class, SignatureManager::class);
1267
+        $this->registerAlias(ISignatureManager::class, SignatureManager::class);
1268 1268
 
1269
-		$this->registerAlias(IGenerator::class, Generator::class);
1270
-		$this->registerService(ISequence::class, function (ContainerInterface $c): ISequence {
1271
-			if (PHP_SAPI !== 'cli') {
1272
-				$sequence = $c->get(APCuSequence::class);
1273
-				if ($sequence->isAvailable()) {
1274
-					return $sequence;
1275
-				}
1276
-			}
1277
-
1278
-			return $c->get(FileSequence::class);
1279
-		}, false);
1280
-		$this->registerAlias(IDecoder::class, Decoder::class);
1281
-
1282
-		$this->connectDispatcher();
1283
-	}
1284
-
1285
-	public function boot() {
1286
-		/** @var HookConnector $hookConnector */
1287
-		$hookConnector = $this->get(HookConnector::class);
1288
-		$hookConnector->viewToNode();
1289
-	}
1290
-
1291
-	private function connectDispatcher(): void {
1292
-		/** @var IEventDispatcher $eventDispatcher */
1293
-		$eventDispatcher = $this->get(IEventDispatcher::class);
1294
-		$eventDispatcher->addServiceListener(LoginFailed::class, LoginFailedListener::class);
1295
-		$eventDispatcher->addServiceListener(PostLoginEvent::class, UserLoggedInListener::class);
1296
-		$eventDispatcher->addServiceListener(UserChangedEvent::class, UserChangedListener::class);
1297
-		$eventDispatcher->addServiceListener(BeforeUserDeletedEvent::class, BeforeUserDeletedListener::class);
1298
-
1299
-		FilesMetadataManager::loadListeners($eventDispatcher);
1300
-		GenerateBlurhashMetadata::loadListeners($eventDispatcher);
1301
-	}
1302
-
1303
-	/**
1304
-	 * @return \OCP\Contacts\IManager
1305
-	 * @deprecated 20.0.0
1306
-	 */
1307
-	public function getContactsManager() {
1308
-		return $this->get(\OCP\Contacts\IManager::class);
1309
-	}
1310
-
1311
-	/**
1312
-	 * @return \OC\Encryption\Manager
1313
-	 * @deprecated 20.0.0
1314
-	 */
1315
-	public function getEncryptionManager() {
1316
-		return $this->get(\OCP\Encryption\IManager::class);
1317
-	}
1318
-
1319
-	/**
1320
-	 * @return \OC\Encryption\File
1321
-	 * @deprecated 20.0.0
1322
-	 */
1323
-	public function getEncryptionFilesHelper() {
1324
-		return $this->get(IFile::class);
1325
-	}
1326
-
1327
-	/**
1328
-	 * The current request object holding all information about the request
1329
-	 * currently being processed is returned from this method.
1330
-	 * In case the current execution was not initiated by a web request null is returned
1331
-	 *
1332
-	 * @return \OCP\IRequest
1333
-	 * @deprecated 20.0.0
1334
-	 */
1335
-	public function getRequest() {
1336
-		return $this->get(IRequest::class);
1337
-	}
1338
-
1339
-	/**
1340
-	 * Returns the root folder of ownCloud's data directory
1341
-	 *
1342
-	 * @return IRootFolder
1343
-	 * @deprecated 20.0.0
1344
-	 */
1345
-	public function getRootFolder() {
1346
-		return $this->get(IRootFolder::class);
1347
-	}
1348
-
1349
-	/**
1350
-	 * Returns the root folder of ownCloud's data directory
1351
-	 * This is the lazy variant so this gets only initialized once it
1352
-	 * is actually used.
1353
-	 *
1354
-	 * @return IRootFolder
1355
-	 * @deprecated 20.0.0
1356
-	 */
1357
-	public function getLazyRootFolder() {
1358
-		return $this->get(IRootFolder::class);
1359
-	}
1360
-
1361
-	/**
1362
-	 * Returns a view to ownCloud's files folder
1363
-	 *
1364
-	 * @param string $userId user ID
1365
-	 * @return \OCP\Files\Folder|null
1366
-	 * @deprecated 20.0.0
1367
-	 */
1368
-	public function getUserFolder($userId = null) {
1369
-		if ($userId === null) {
1370
-			$user = $this->get(IUserSession::class)->getUser();
1371
-			if (!$user) {
1372
-				return null;
1373
-			}
1374
-			$userId = $user->getUID();
1375
-		}
1376
-		$root = $this->get(IRootFolder::class);
1377
-		return $root->getUserFolder($userId);
1378
-	}
1379
-
1380
-	/**
1381
-	 * @return \OC\User\Manager
1382
-	 * @deprecated 20.0.0
1383
-	 */
1384
-	public function getUserManager() {
1385
-		return $this->get(IUserManager::class);
1386
-	}
1387
-
1388
-	/**
1389
-	 * @return \OC\Group\Manager
1390
-	 * @deprecated 20.0.0
1391
-	 */
1392
-	public function getGroupManager() {
1393
-		return $this->get(IGroupManager::class);
1394
-	}
1395
-
1396
-	/**
1397
-	 * @return \OC\User\Session
1398
-	 * @deprecated 20.0.0
1399
-	 */
1400
-	public function getUserSession() {
1401
-		return $this->get(IUserSession::class);
1402
-	}
1403
-
1404
-	/**
1405
-	 * @return \OCP\ISession
1406
-	 * @deprecated 20.0.0
1407
-	 */
1408
-	public function getSession() {
1409
-		return $this->get(Session::class)->getSession();
1410
-	}
1411
-
1412
-	/**
1413
-	 * @param \OCP\ISession $session
1414
-	 * @return void
1415
-	 */
1416
-	public function setSession(\OCP\ISession $session) {
1417
-		$this->get(SessionStorage::class)->setSession($session);
1418
-		$this->get(Session::class)->setSession($session);
1419
-		$this->get(Store::class)->setSession($session);
1420
-	}
1421
-
1422
-	/**
1423
-	 * @return \OCP\IConfig
1424
-	 * @deprecated 20.0.0
1425
-	 */
1426
-	public function getConfig() {
1427
-		return $this->get(AllConfig::class);
1428
-	}
1429
-
1430
-	/**
1431
-	 * @return \OC\SystemConfig
1432
-	 * @deprecated 20.0.0
1433
-	 */
1434
-	public function getSystemConfig() {
1435
-		return $this->get(SystemConfig::class);
1436
-	}
1437
-
1438
-	/**
1439
-	 * @return IFactory
1440
-	 * @deprecated 20.0.0
1441
-	 */
1442
-	public function getL10NFactory() {
1443
-		return $this->get(IFactory::class);
1444
-	}
1445
-
1446
-	/**
1447
-	 * get an L10N instance
1448
-	 *
1449
-	 * @param string $app appid
1450
-	 * @param string $lang
1451
-	 * @return IL10N
1452
-	 * @deprecated 20.0.0 use DI of {@see IL10N} or {@see IFactory} instead, or {@see \OCP\Util::getL10N()} as a last resort
1453
-	 */
1454
-	public function getL10N($app, $lang = null) {
1455
-		return $this->get(IFactory::class)->get($app, $lang);
1456
-	}
1457
-
1458
-	/**
1459
-	 * @return IURLGenerator
1460
-	 * @deprecated 20.0.0
1461
-	 */
1462
-	public function getURLGenerator() {
1463
-		return $this->get(IURLGenerator::class);
1464
-	}
1465
-
1466
-	/**
1467
-	 * Returns an ICache instance. Since 8.1.0 it returns a fake cache. Use
1468
-	 * getMemCacheFactory() instead.
1469
-	 *
1470
-	 * @return ICache
1471
-	 * @deprecated 8.1.0 use getMemCacheFactory to obtain a proper cache
1472
-	 */
1473
-	public function getCache() {
1474
-		return $this->get(ICache::class);
1475
-	}
1476
-
1477
-	/**
1478
-	 * Returns an \OCP\CacheFactory instance
1479
-	 *
1480
-	 * @return \OCP\ICacheFactory
1481
-	 * @deprecated 20.0.0
1482
-	 */
1483
-	public function getMemCacheFactory() {
1484
-		return $this->get(ICacheFactory::class);
1485
-	}
1486
-
1487
-	/**
1488
-	 * Returns the current session
1489
-	 *
1490
-	 * @return \OCP\IDBConnection
1491
-	 * @deprecated 20.0.0
1492
-	 */
1493
-	public function getDatabaseConnection() {
1494
-		return $this->get(IDBConnection::class);
1495
-	}
1496
-
1497
-	/**
1498
-	 * Returns the activity manager
1499
-	 *
1500
-	 * @return \OCP\Activity\IManager
1501
-	 * @deprecated 20.0.0
1502
-	 */
1503
-	public function getActivityManager() {
1504
-		return $this->get(\OCP\Activity\IManager::class);
1505
-	}
1506
-
1507
-	/**
1508
-	 * Returns an job list for controlling background jobs
1509
-	 *
1510
-	 * @return IJobList
1511
-	 * @deprecated 20.0.0
1512
-	 */
1513
-	public function getJobList() {
1514
-		return $this->get(IJobList::class);
1515
-	}
1516
-
1517
-	/**
1518
-	 * Returns a SecureRandom instance
1519
-	 *
1520
-	 * @return \OCP\Security\ISecureRandom
1521
-	 * @deprecated 20.0.0
1522
-	 */
1523
-	public function getSecureRandom() {
1524
-		return $this->get(ISecureRandom::class);
1525
-	}
1526
-
1527
-	/**
1528
-	 * Returns a Crypto instance
1529
-	 *
1530
-	 * @return ICrypto
1531
-	 * @deprecated 20.0.0
1532
-	 */
1533
-	public function getCrypto() {
1534
-		return $this->get(ICrypto::class);
1535
-	}
1536
-
1537
-	/**
1538
-	 * Returns a Hasher instance
1539
-	 *
1540
-	 * @return IHasher
1541
-	 * @deprecated 20.0.0
1542
-	 */
1543
-	public function getHasher() {
1544
-		return $this->get(IHasher::class);
1545
-	}
1546
-
1547
-	/**
1548
-	 * Get the certificate manager
1549
-	 *
1550
-	 * @return \OCP\ICertificateManager
1551
-	 */
1552
-	public function getCertificateManager() {
1553
-		return $this->get(ICertificateManager::class);
1554
-	}
1555
-
1556
-	/**
1557
-	 * Get the manager for temporary files and folders
1558
-	 *
1559
-	 * @return \OCP\ITempManager
1560
-	 * @deprecated 20.0.0
1561
-	 */
1562
-	public function getTempManager() {
1563
-		return $this->get(ITempManager::class);
1564
-	}
1565
-
1566
-	/**
1567
-	 * Get the app manager
1568
-	 *
1569
-	 * @return \OCP\App\IAppManager
1570
-	 * @deprecated 20.0.0
1571
-	 */
1572
-	public function getAppManager() {
1573
-		return $this->get(IAppManager::class);
1574
-	}
1575
-
1576
-	/**
1577
-	 * Creates a new mailer
1578
-	 *
1579
-	 * @return IMailer
1580
-	 * @deprecated 20.0.0
1581
-	 */
1582
-	public function getMailer() {
1583
-		return $this->get(IMailer::class);
1584
-	}
1585
-
1586
-	/**
1587
-	 * Get the webroot
1588
-	 *
1589
-	 * @return string
1590
-	 * @deprecated 20.0.0
1591
-	 */
1592
-	public function getWebRoot() {
1593
-		return $this->webRoot;
1594
-	}
1595
-
1596
-	/**
1597
-	 * Get the locking provider
1598
-	 *
1599
-	 * @return ILockingProvider
1600
-	 * @since 8.1.0
1601
-	 * @deprecated 20.0.0
1602
-	 */
1603
-	public function getLockingProvider() {
1604
-		return $this->get(ILockingProvider::class);
1605
-	}
1606
-
1607
-	/**
1608
-	 * Get the MimeTypeDetector
1609
-	 *
1610
-	 * @return IMimeTypeDetector
1611
-	 * @deprecated 20.0.0
1612
-	 */
1613
-	public function getMimeTypeDetector() {
1614
-		return $this->get(IMimeTypeDetector::class);
1615
-	}
1616
-
1617
-	/**
1618
-	 * Get the MimeTypeLoader
1619
-	 *
1620
-	 * @return IMimeTypeLoader
1621
-	 * @deprecated 20.0.0
1622
-	 */
1623
-	public function getMimeTypeLoader() {
1624
-		return $this->get(IMimeTypeLoader::class);
1625
-	}
1626
-
1627
-	/**
1628
-	 * Get the Notification Manager
1629
-	 *
1630
-	 * @return \OCP\Notification\IManager
1631
-	 * @since 8.2.0
1632
-	 * @deprecated 20.0.0
1633
-	 */
1634
-	public function getNotificationManager() {
1635
-		return $this->get(\OCP\Notification\IManager::class);
1636
-	}
1637
-
1638
-	/**
1639
-	 * @return \OCA\Theming\ThemingDefaults
1640
-	 * @deprecated 20.0.0
1641
-	 */
1642
-	public function getThemingDefaults() {
1643
-		return $this->get('ThemingDefaults');
1644
-	}
1645
-
1646
-	/**
1647
-	 * @return \OC\IntegrityCheck\Checker
1648
-	 * @deprecated 20.0.0
1649
-	 */
1650
-	public function getIntegrityCodeChecker() {
1651
-		return $this->get('IntegrityCodeChecker');
1652
-	}
1653
-
1654
-	/**
1655
-	 * @return CsrfTokenManager
1656
-	 * @deprecated 20.0.0
1657
-	 */
1658
-	public function getCsrfTokenManager() {
1659
-		return $this->get(CsrfTokenManager::class);
1660
-	}
1661
-
1662
-	/**
1663
-	 * @return ContentSecurityPolicyNonceManager
1664
-	 * @deprecated 20.0.0
1665
-	 */
1666
-	public function getContentSecurityPolicyNonceManager() {
1667
-		return $this->get(ContentSecurityPolicyNonceManager::class);
1668
-	}
1669
-
1670
-	/**
1671
-	 * @return \OCP\Settings\IManager
1672
-	 * @deprecated 20.0.0
1673
-	 */
1674
-	public function getSettingsManager() {
1675
-		return $this->get(\OC\Settings\Manager::class);
1676
-	}
1677
-
1678
-	/**
1679
-	 * @return \OCP\Files\IAppData
1680
-	 * @deprecated 20.0.0 Use get(\OCP\Files\AppData\IAppDataFactory::class)->get($app) instead
1681
-	 */
1682
-	public function getAppDataDir($app) {
1683
-		$factory = $this->get(\OC\Files\AppData\Factory::class);
1684
-		return $factory->get($app);
1685
-	}
1686
-
1687
-	/**
1688
-	 * @return \OCP\Federation\ICloudIdManager
1689
-	 * @deprecated 20.0.0
1690
-	 */
1691
-	public function getCloudIdManager() {
1692
-		return $this->get(ICloudIdManager::class);
1693
-	}
1269
+        $this->registerAlias(IGenerator::class, Generator::class);
1270
+        $this->registerService(ISequence::class, function (ContainerInterface $c): ISequence {
1271
+            if (PHP_SAPI !== 'cli') {
1272
+                $sequence = $c->get(APCuSequence::class);
1273
+                if ($sequence->isAvailable()) {
1274
+                    return $sequence;
1275
+                }
1276
+            }
1277
+
1278
+            return $c->get(FileSequence::class);
1279
+        }, false);
1280
+        $this->registerAlias(IDecoder::class, Decoder::class);
1281
+
1282
+        $this->connectDispatcher();
1283
+    }
1284
+
1285
+    public function boot() {
1286
+        /** @var HookConnector $hookConnector */
1287
+        $hookConnector = $this->get(HookConnector::class);
1288
+        $hookConnector->viewToNode();
1289
+    }
1290
+
1291
+    private function connectDispatcher(): void {
1292
+        /** @var IEventDispatcher $eventDispatcher */
1293
+        $eventDispatcher = $this->get(IEventDispatcher::class);
1294
+        $eventDispatcher->addServiceListener(LoginFailed::class, LoginFailedListener::class);
1295
+        $eventDispatcher->addServiceListener(PostLoginEvent::class, UserLoggedInListener::class);
1296
+        $eventDispatcher->addServiceListener(UserChangedEvent::class, UserChangedListener::class);
1297
+        $eventDispatcher->addServiceListener(BeforeUserDeletedEvent::class, BeforeUserDeletedListener::class);
1298
+
1299
+        FilesMetadataManager::loadListeners($eventDispatcher);
1300
+        GenerateBlurhashMetadata::loadListeners($eventDispatcher);
1301
+    }
1302
+
1303
+    /**
1304
+     * @return \OCP\Contacts\IManager
1305
+     * @deprecated 20.0.0
1306
+     */
1307
+    public function getContactsManager() {
1308
+        return $this->get(\OCP\Contacts\IManager::class);
1309
+    }
1310
+
1311
+    /**
1312
+     * @return \OC\Encryption\Manager
1313
+     * @deprecated 20.0.0
1314
+     */
1315
+    public function getEncryptionManager() {
1316
+        return $this->get(\OCP\Encryption\IManager::class);
1317
+    }
1318
+
1319
+    /**
1320
+     * @return \OC\Encryption\File
1321
+     * @deprecated 20.0.0
1322
+     */
1323
+    public function getEncryptionFilesHelper() {
1324
+        return $this->get(IFile::class);
1325
+    }
1326
+
1327
+    /**
1328
+     * The current request object holding all information about the request
1329
+     * currently being processed is returned from this method.
1330
+     * In case the current execution was not initiated by a web request null is returned
1331
+     *
1332
+     * @return \OCP\IRequest
1333
+     * @deprecated 20.0.0
1334
+     */
1335
+    public function getRequest() {
1336
+        return $this->get(IRequest::class);
1337
+    }
1338
+
1339
+    /**
1340
+     * Returns the root folder of ownCloud's data directory
1341
+     *
1342
+     * @return IRootFolder
1343
+     * @deprecated 20.0.0
1344
+     */
1345
+    public function getRootFolder() {
1346
+        return $this->get(IRootFolder::class);
1347
+    }
1348
+
1349
+    /**
1350
+     * Returns the root folder of ownCloud's data directory
1351
+     * This is the lazy variant so this gets only initialized once it
1352
+     * is actually used.
1353
+     *
1354
+     * @return IRootFolder
1355
+     * @deprecated 20.0.0
1356
+     */
1357
+    public function getLazyRootFolder() {
1358
+        return $this->get(IRootFolder::class);
1359
+    }
1360
+
1361
+    /**
1362
+     * Returns a view to ownCloud's files folder
1363
+     *
1364
+     * @param string $userId user ID
1365
+     * @return \OCP\Files\Folder|null
1366
+     * @deprecated 20.0.0
1367
+     */
1368
+    public function getUserFolder($userId = null) {
1369
+        if ($userId === null) {
1370
+            $user = $this->get(IUserSession::class)->getUser();
1371
+            if (!$user) {
1372
+                return null;
1373
+            }
1374
+            $userId = $user->getUID();
1375
+        }
1376
+        $root = $this->get(IRootFolder::class);
1377
+        return $root->getUserFolder($userId);
1378
+    }
1379
+
1380
+    /**
1381
+     * @return \OC\User\Manager
1382
+     * @deprecated 20.0.0
1383
+     */
1384
+    public function getUserManager() {
1385
+        return $this->get(IUserManager::class);
1386
+    }
1387
+
1388
+    /**
1389
+     * @return \OC\Group\Manager
1390
+     * @deprecated 20.0.0
1391
+     */
1392
+    public function getGroupManager() {
1393
+        return $this->get(IGroupManager::class);
1394
+    }
1395
+
1396
+    /**
1397
+     * @return \OC\User\Session
1398
+     * @deprecated 20.0.0
1399
+     */
1400
+    public function getUserSession() {
1401
+        return $this->get(IUserSession::class);
1402
+    }
1403
+
1404
+    /**
1405
+     * @return \OCP\ISession
1406
+     * @deprecated 20.0.0
1407
+     */
1408
+    public function getSession() {
1409
+        return $this->get(Session::class)->getSession();
1410
+    }
1411
+
1412
+    /**
1413
+     * @param \OCP\ISession $session
1414
+     * @return void
1415
+     */
1416
+    public function setSession(\OCP\ISession $session) {
1417
+        $this->get(SessionStorage::class)->setSession($session);
1418
+        $this->get(Session::class)->setSession($session);
1419
+        $this->get(Store::class)->setSession($session);
1420
+    }
1421
+
1422
+    /**
1423
+     * @return \OCP\IConfig
1424
+     * @deprecated 20.0.0
1425
+     */
1426
+    public function getConfig() {
1427
+        return $this->get(AllConfig::class);
1428
+    }
1429
+
1430
+    /**
1431
+     * @return \OC\SystemConfig
1432
+     * @deprecated 20.0.0
1433
+     */
1434
+    public function getSystemConfig() {
1435
+        return $this->get(SystemConfig::class);
1436
+    }
1437
+
1438
+    /**
1439
+     * @return IFactory
1440
+     * @deprecated 20.0.0
1441
+     */
1442
+    public function getL10NFactory() {
1443
+        return $this->get(IFactory::class);
1444
+    }
1445
+
1446
+    /**
1447
+     * get an L10N instance
1448
+     *
1449
+     * @param string $app appid
1450
+     * @param string $lang
1451
+     * @return IL10N
1452
+     * @deprecated 20.0.0 use DI of {@see IL10N} or {@see IFactory} instead, or {@see \OCP\Util::getL10N()} as a last resort
1453
+     */
1454
+    public function getL10N($app, $lang = null) {
1455
+        return $this->get(IFactory::class)->get($app, $lang);
1456
+    }
1457
+
1458
+    /**
1459
+     * @return IURLGenerator
1460
+     * @deprecated 20.0.0
1461
+     */
1462
+    public function getURLGenerator() {
1463
+        return $this->get(IURLGenerator::class);
1464
+    }
1465
+
1466
+    /**
1467
+     * Returns an ICache instance. Since 8.1.0 it returns a fake cache. Use
1468
+     * getMemCacheFactory() instead.
1469
+     *
1470
+     * @return ICache
1471
+     * @deprecated 8.1.0 use getMemCacheFactory to obtain a proper cache
1472
+     */
1473
+    public function getCache() {
1474
+        return $this->get(ICache::class);
1475
+    }
1476
+
1477
+    /**
1478
+     * Returns an \OCP\CacheFactory instance
1479
+     *
1480
+     * @return \OCP\ICacheFactory
1481
+     * @deprecated 20.0.0
1482
+     */
1483
+    public function getMemCacheFactory() {
1484
+        return $this->get(ICacheFactory::class);
1485
+    }
1486
+
1487
+    /**
1488
+     * Returns the current session
1489
+     *
1490
+     * @return \OCP\IDBConnection
1491
+     * @deprecated 20.0.0
1492
+     */
1493
+    public function getDatabaseConnection() {
1494
+        return $this->get(IDBConnection::class);
1495
+    }
1496
+
1497
+    /**
1498
+     * Returns the activity manager
1499
+     *
1500
+     * @return \OCP\Activity\IManager
1501
+     * @deprecated 20.0.0
1502
+     */
1503
+    public function getActivityManager() {
1504
+        return $this->get(\OCP\Activity\IManager::class);
1505
+    }
1506
+
1507
+    /**
1508
+     * Returns an job list for controlling background jobs
1509
+     *
1510
+     * @return IJobList
1511
+     * @deprecated 20.0.0
1512
+     */
1513
+    public function getJobList() {
1514
+        return $this->get(IJobList::class);
1515
+    }
1516
+
1517
+    /**
1518
+     * Returns a SecureRandom instance
1519
+     *
1520
+     * @return \OCP\Security\ISecureRandom
1521
+     * @deprecated 20.0.0
1522
+     */
1523
+    public function getSecureRandom() {
1524
+        return $this->get(ISecureRandom::class);
1525
+    }
1526
+
1527
+    /**
1528
+     * Returns a Crypto instance
1529
+     *
1530
+     * @return ICrypto
1531
+     * @deprecated 20.0.0
1532
+     */
1533
+    public function getCrypto() {
1534
+        return $this->get(ICrypto::class);
1535
+    }
1536
+
1537
+    /**
1538
+     * Returns a Hasher instance
1539
+     *
1540
+     * @return IHasher
1541
+     * @deprecated 20.0.0
1542
+     */
1543
+    public function getHasher() {
1544
+        return $this->get(IHasher::class);
1545
+    }
1546
+
1547
+    /**
1548
+     * Get the certificate manager
1549
+     *
1550
+     * @return \OCP\ICertificateManager
1551
+     */
1552
+    public function getCertificateManager() {
1553
+        return $this->get(ICertificateManager::class);
1554
+    }
1555
+
1556
+    /**
1557
+     * Get the manager for temporary files and folders
1558
+     *
1559
+     * @return \OCP\ITempManager
1560
+     * @deprecated 20.0.0
1561
+     */
1562
+    public function getTempManager() {
1563
+        return $this->get(ITempManager::class);
1564
+    }
1565
+
1566
+    /**
1567
+     * Get the app manager
1568
+     *
1569
+     * @return \OCP\App\IAppManager
1570
+     * @deprecated 20.0.0
1571
+     */
1572
+    public function getAppManager() {
1573
+        return $this->get(IAppManager::class);
1574
+    }
1575
+
1576
+    /**
1577
+     * Creates a new mailer
1578
+     *
1579
+     * @return IMailer
1580
+     * @deprecated 20.0.0
1581
+     */
1582
+    public function getMailer() {
1583
+        return $this->get(IMailer::class);
1584
+    }
1585
+
1586
+    /**
1587
+     * Get the webroot
1588
+     *
1589
+     * @return string
1590
+     * @deprecated 20.0.0
1591
+     */
1592
+    public function getWebRoot() {
1593
+        return $this->webRoot;
1594
+    }
1595
+
1596
+    /**
1597
+     * Get the locking provider
1598
+     *
1599
+     * @return ILockingProvider
1600
+     * @since 8.1.0
1601
+     * @deprecated 20.0.0
1602
+     */
1603
+    public function getLockingProvider() {
1604
+        return $this->get(ILockingProvider::class);
1605
+    }
1606
+
1607
+    /**
1608
+     * Get the MimeTypeDetector
1609
+     *
1610
+     * @return IMimeTypeDetector
1611
+     * @deprecated 20.0.0
1612
+     */
1613
+    public function getMimeTypeDetector() {
1614
+        return $this->get(IMimeTypeDetector::class);
1615
+    }
1616
+
1617
+    /**
1618
+     * Get the MimeTypeLoader
1619
+     *
1620
+     * @return IMimeTypeLoader
1621
+     * @deprecated 20.0.0
1622
+     */
1623
+    public function getMimeTypeLoader() {
1624
+        return $this->get(IMimeTypeLoader::class);
1625
+    }
1626
+
1627
+    /**
1628
+     * Get the Notification Manager
1629
+     *
1630
+     * @return \OCP\Notification\IManager
1631
+     * @since 8.2.0
1632
+     * @deprecated 20.0.0
1633
+     */
1634
+    public function getNotificationManager() {
1635
+        return $this->get(\OCP\Notification\IManager::class);
1636
+    }
1637
+
1638
+    /**
1639
+     * @return \OCA\Theming\ThemingDefaults
1640
+     * @deprecated 20.0.0
1641
+     */
1642
+    public function getThemingDefaults() {
1643
+        return $this->get('ThemingDefaults');
1644
+    }
1645
+
1646
+    /**
1647
+     * @return \OC\IntegrityCheck\Checker
1648
+     * @deprecated 20.0.0
1649
+     */
1650
+    public function getIntegrityCodeChecker() {
1651
+        return $this->get('IntegrityCodeChecker');
1652
+    }
1653
+
1654
+    /**
1655
+     * @return CsrfTokenManager
1656
+     * @deprecated 20.0.0
1657
+     */
1658
+    public function getCsrfTokenManager() {
1659
+        return $this->get(CsrfTokenManager::class);
1660
+    }
1661
+
1662
+    /**
1663
+     * @return ContentSecurityPolicyNonceManager
1664
+     * @deprecated 20.0.0
1665
+     */
1666
+    public function getContentSecurityPolicyNonceManager() {
1667
+        return $this->get(ContentSecurityPolicyNonceManager::class);
1668
+    }
1669
+
1670
+    /**
1671
+     * @return \OCP\Settings\IManager
1672
+     * @deprecated 20.0.0
1673
+     */
1674
+    public function getSettingsManager() {
1675
+        return $this->get(\OC\Settings\Manager::class);
1676
+    }
1677
+
1678
+    /**
1679
+     * @return \OCP\Files\IAppData
1680
+     * @deprecated 20.0.0 Use get(\OCP\Files\AppData\IAppDataFactory::class)->get($app) instead
1681
+     */
1682
+    public function getAppDataDir($app) {
1683
+        $factory = $this->get(\OC\Files\AppData\Factory::class);
1684
+        return $factory->get($app);
1685
+    }
1686
+
1687
+    /**
1688
+     * @return \OCP\Federation\ICloudIdManager
1689
+     * @deprecated 20.0.0
1690
+     */
1691
+    public function getCloudIdManager() {
1692
+        return $this->get(ICloudIdManager::class);
1693
+    }
1694 1694
 }
Please login to merge, or discard this patch.
lib/private/User/Manager.php 2 patches
Indentation   +777 added lines, -777 removed lines patch added patch discarded remove patch
@@ -54,781 +54,781 @@
 block discarded – undo
54 54
  * @package OC\User
55 55
  */
56 56
 class Manager extends PublicEmitter implements IUserManager {
57
-	/**
58
-	 * @var UserInterface[] $backends
59
-	 */
60
-	private array $backends = [];
61
-
62
-	/**
63
-	 * @var array<string,\OC\User\User> $cachedUsers
64
-	 */
65
-	private array $cachedUsers = [];
66
-
67
-	private ICache $cache;
68
-
69
-	private DisplayNameCache $displayNameCache;
70
-
71
-	// This constructor can't autoload any class requiring a DB connection.
72
-	public function __construct(
73
-		private IConfig $config,
74
-		ICacheFactory $cacheFactory,
75
-		private IEventDispatcher $eventDispatcher,
76
-		private LoggerInterface $logger,
77
-	) {
78
-		$this->cache = new WithLocalCache($cacheFactory->createDistributed('user_backend_map'));
79
-		$this->listen('\OC\User', 'postDelete', function (IUser $user): void {
80
-			unset($this->cachedUsers[$user->getUID()]);
81
-		});
82
-		$this->displayNameCache = new DisplayNameCache($cacheFactory, $this);
83
-	}
84
-
85
-	/**
86
-	 * Get the active backends
87
-	 * @return UserInterface[]
88
-	 */
89
-	public function getBackends(): array {
90
-		return $this->backends;
91
-	}
92
-
93
-	public function registerBackend(UserInterface $backend): void {
94
-		$this->backends[] = $backend;
95
-	}
96
-
97
-	public function removeBackend(UserInterface $backend): void {
98
-		$this->cachedUsers = [];
99
-		if (($i = array_search($backend, $this->backends)) !== false) {
100
-			unset($this->backends[$i]);
101
-		}
102
-	}
103
-
104
-	public function clearBackends(): void {
105
-		$this->cachedUsers = [];
106
-		$this->backends = [];
107
-	}
108
-
109
-	/**
110
-	 * get a user by user id
111
-	 *
112
-	 * @param string $uid
113
-	 * @return \OC\User\User|null Either the user or null if the specified user does not exist
114
-	 */
115
-	public function get($uid) {
116
-		if (is_null($uid) || $uid === '' || $uid === false) {
117
-			return null;
118
-		}
119
-		if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
120
-			return $this->cachedUsers[$uid];
121
-		}
122
-
123
-		if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
124
-			return null;
125
-		}
126
-
127
-		$cachedBackend = $this->cache->get(sha1($uid));
128
-		if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
129
-			// Cache has the info of the user backend already, so ask that one directly
130
-			$backend = $this->backends[$cachedBackend];
131
-			if ($backend->userExists($uid)) {
132
-				return $this->getUserObject($uid, $backend);
133
-			}
134
-		}
135
-
136
-		foreach ($this->backends as $i => $backend) {
137
-			if ($i === $cachedBackend) {
138
-				// Tried that one already
139
-				continue;
140
-			}
141
-
142
-			if ($backend->userExists($uid)) {
143
-				// Hash $uid to ensure that only valid characters are used for the cache key
144
-				$this->cache->set(sha1($uid), $i, 300);
145
-				return $this->getUserObject($uid, $backend);
146
-			}
147
-		}
148
-		return null;
149
-	}
150
-
151
-	public function getDisplayName(string $uid): ?string {
152
-		return $this->displayNameCache->getDisplayName($uid);
153
-	}
154
-
155
-	/**
156
-	 * get or construct the user object
157
-	 *
158
-	 * @param string $uid
159
-	 * @param \OCP\UserInterface $backend
160
-	 * @param bool $cacheUser If false the newly created user object will not be cached
161
-	 * @return \OC\User\User
162
-	 */
163
-	public function getUserObject($uid, $backend, $cacheUser = true) {
164
-		if ($backend instanceof IGetRealUIDBackend) {
165
-			$uid = $backend->getRealUID($uid);
166
-		}
167
-
168
-		if (isset($this->cachedUsers[$uid])) {
169
-			return $this->cachedUsers[$uid];
170
-		}
171
-
172
-		$user = new User($uid, $backend, $this->eventDispatcher, $this, $this->config);
173
-		if ($cacheUser) {
174
-			$this->cachedUsers[$uid] = $user;
175
-		}
176
-		return $user;
177
-	}
178
-
179
-	/**
180
-	 * check if a user exists
181
-	 *
182
-	 * @param string $uid
183
-	 * @return bool
184
-	 */
185
-	public function userExists($uid) {
186
-		if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
187
-			return false;
188
-		}
189
-
190
-		$user = $this->get($uid);
191
-		return ($user !== null);
192
-	}
193
-
194
-	/**
195
-	 * Check if the password is valid for the user
196
-	 *
197
-	 * @param string $loginName
198
-	 * @param string $password
199
-	 * @return IUser|false the User object on success, false otherwise
200
-	 */
201
-	public function checkPassword($loginName, $password) {
202
-		$result = $this->checkPasswordNoLogging($loginName, $password);
203
-
204
-		if ($result === false) {
205
-			$this->logger->warning('Login failed: \'' . $loginName . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']);
206
-		}
207
-
208
-		return $result;
209
-	}
210
-
211
-	/**
212
-	 * Check if the password is valid for the user
213
-	 *
214
-	 * @internal
215
-	 * @param string $loginName
216
-	 * @param string $password
217
-	 * @return IUser|false the User object on success, false otherwise
218
-	 */
219
-	public function checkPasswordNoLogging($loginName, $password) {
220
-		$loginName = str_replace("\0", '', $loginName);
221
-		$password = str_replace("\0", '', $password);
222
-
223
-		$cachedBackend = $this->cache->get($loginName);
224
-		if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
225
-			$backends = [$this->backends[$cachedBackend]];
226
-		} else {
227
-			$backends = $this->backends;
228
-		}
229
-		foreach ($backends as $backend) {
230
-			if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
231
-				/** @var ICheckPasswordBackend $backend */
232
-				$uid = $backend->checkPassword($loginName, $password);
233
-				if ($uid !== false) {
234
-					return $this->getUserObject($uid, $backend);
235
-				}
236
-			}
237
-		}
238
-
239
-		// since http basic auth doesn't provide a standard way of handling non ascii password we allow password to be urlencoded
240
-		// we only do this decoding after using the plain password fails to maintain compatibility with any password that happens
241
-		// to contain urlencoded patterns by "accident".
242
-		$password = urldecode($password);
243
-
244
-		foreach ($backends as $backend) {
245
-			if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
246
-				/** @var ICheckPasswordBackend|UserInterface $backend */
247
-				$uid = $backend->checkPassword($loginName, $password);
248
-				if ($uid !== false) {
249
-					return $this->getUserObject($uid, $backend);
250
-				}
251
-			}
252
-		}
253
-
254
-		return false;
255
-	}
256
-
257
-	public function search($pattern, $limit = null, $offset = null) {
258
-		$users = [];
259
-		foreach ($this->backends as $backend) {
260
-			$backendUsers = $backend->getUsers($pattern, $limit, $offset);
261
-			if (is_array($backendUsers)) {
262
-				foreach ($backendUsers as $uid) {
263
-					$users[$uid] = new LazyUser($uid, $this, null, $backend);
264
-				}
265
-			}
266
-		}
267
-
268
-		uasort($users, function (IUser $a, IUser $b) {
269
-			return strcasecmp($a->getUID(), $b->getUID());
270
-		});
271
-		return $users;
272
-	}
273
-
274
-	public function searchDisplayName($pattern, $limit = null, $offset = null) {
275
-		$users = [];
276
-		foreach ($this->backends as $backend) {
277
-			$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
278
-			if (is_array($backendUsers)) {
279
-				foreach ($backendUsers as $uid => $displayName) {
280
-					$users[] = new LazyUser($uid, $this, $displayName, $backend);
281
-				}
282
-			}
283
-		}
284
-
285
-		usort($users, function (IUser $a, IUser $b) {
286
-			return strcasecmp($a->getDisplayName(), $b->getDisplayName());
287
-		});
288
-		return $users;
289
-	}
290
-
291
-	/**
292
-	 * @return IUser[]
293
-	 */
294
-	public function getDisabledUsers(?int $limit = null, int $offset = 0, string $search = ''): array {
295
-		$users = $this->config->getUsersForUserValue('core', 'enabled', 'false');
296
-		$users = array_combine(
297
-			$users,
298
-			array_map(
299
-				fn (string $uid): IUser => new LazyUser($uid, $this),
300
-				$users
301
-			)
302
-		);
303
-		if ($search !== '') {
304
-			$users = array_filter(
305
-				$users,
306
-				function (IUser $user) use ($search): bool {
307
-					try {
308
-						return mb_stripos($user->getUID(), $search) !== false
309
-						|| mb_stripos($user->getDisplayName(), $search) !== false
310
-						|| mb_stripos($user->getEMailAddress() ?? '', $search) !== false;
311
-					} catch (NoUserException $ex) {
312
-						$this->logger->error('Error while filtering disabled users', ['exception' => $ex, 'userUID' => $user->getUID()]);
313
-						return false;
314
-					}
315
-				});
316
-		}
317
-
318
-		$tempLimit = ($limit === null ? null : $limit + $offset);
319
-		foreach ($this->backends as $backend) {
320
-			if (($tempLimit !== null) && (count($users) >= $tempLimit)) {
321
-				break;
322
-			}
323
-			if ($backend instanceof IProvideEnabledStateBackend) {
324
-				$backendUsers = $backend->getDisabledUserList(($tempLimit === null ? null : $tempLimit - count($users)), 0, $search);
325
-				foreach ($backendUsers as $uid) {
326
-					$users[$uid] = new LazyUser($uid, $this, null, $backend);
327
-				}
328
-			}
329
-		}
330
-
331
-		return array_slice($users, $offset, $limit);
332
-	}
333
-
334
-	/**
335
-	 * Search known users (from phonebook sync) by displayName
336
-	 *
337
-	 * @param string $searcher
338
-	 * @param string $pattern
339
-	 * @param int|null $limit
340
-	 * @param int|null $offset
341
-	 * @return IUser[]
342
-	 */
343
-	public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array {
344
-		$users = [];
345
-		foreach ($this->backends as $backend) {
346
-			if ($backend instanceof ISearchKnownUsersBackend) {
347
-				$backendUsers = $backend->searchKnownUsersByDisplayName($searcher, $pattern, $limit, $offset);
348
-			} else {
349
-				// Better than nothing, but filtering after pagination can remove lots of results.
350
-				$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
351
-			}
352
-			if (is_array($backendUsers)) {
353
-				foreach ($backendUsers as $uid => $displayName) {
354
-					$users[] = $this->getUserObject($uid, $backend);
355
-				}
356
-			}
357
-		}
358
-
359
-		usort($users, function ($a, $b) {
360
-			/**
361
-			 * @var IUser $a
362
-			 * @var IUser $b
363
-			 */
364
-			return strcasecmp($a->getDisplayName(), $b->getDisplayName());
365
-		});
366
-		return $users;
367
-	}
368
-
369
-	/**
370
-	 * @param string $uid
371
-	 * @param string $password
372
-	 * @return false|IUser the created user or false
373
-	 * @throws \InvalidArgumentException
374
-	 * @throws HintException
375
-	 */
376
-	public function createUser($uid, $password) {
377
-		// DI injection is not used here as IRegistry needs the user manager itself for user count and thus it would create a cyclic dependency
378
-		/** @var IAssertion $assertion */
379
-		$assertion = \OC::$server->get(IAssertion::class);
380
-		$assertion->createUserIsLegit();
381
-
382
-		$localBackends = [];
383
-		foreach ($this->backends as $backend) {
384
-			if ($backend instanceof Database) {
385
-				// First check if there is another user backend
386
-				$localBackends[] = $backend;
387
-				continue;
388
-			}
389
-
390
-			if ($backend->implementsActions(Backend::CREATE_USER)) {
391
-				return $this->createUserFromBackend($uid, $password, $backend);
392
-			}
393
-		}
394
-
395
-		foreach ($localBackends as $backend) {
396
-			if ($backend->implementsActions(Backend::CREATE_USER)) {
397
-				return $this->createUserFromBackend($uid, $password, $backend);
398
-			}
399
-		}
400
-
401
-		return false;
402
-	}
403
-
404
-	/**
405
-	 * @param string $uid
406
-	 * @param string $password
407
-	 * @param UserInterface $backend
408
-	 * @return IUser|false
409
-	 * @throws \InvalidArgumentException
410
-	 */
411
-	public function createUserFromBackend($uid, $password, UserInterface $backend) {
412
-		$l = \OCP\Util::getL10N('lib');
413
-
414
-		$this->validateUserId($uid, true);
415
-
416
-		// No empty password
417
-		if (trim($password) === '') {
418
-			throw new \InvalidArgumentException($l->t('A valid password must be provided'));
419
-		}
420
-
421
-		// Check if user already exists
422
-		if ($this->userExists($uid)) {
423
-			throw new \InvalidArgumentException($l->t('The Login is already being used'));
424
-		}
425
-
426
-		/** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */
427
-		$this->emit('\OC\User', 'preCreateUser', [$uid, $password]);
428
-		$this->eventDispatcher->dispatchTyped(new BeforeUserCreatedEvent($uid, $password));
429
-		$state = $backend->createUser($uid, $password);
430
-		if ($state === false) {
431
-			throw new \InvalidArgumentException($l->t('Could not create account'));
432
-		}
433
-		$user = $this->getUserObject($uid, $backend);
434
-		if ($user instanceof IUser) {
435
-			/** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */
436
-			$this->emit('\OC\User', 'postCreateUser', [$user, $password]);
437
-			$this->eventDispatcher->dispatchTyped(new UserCreatedEvent($user, $password));
438
-			return $user;
439
-		}
440
-		return false;
441
-	}
442
-
443
-	/**
444
-	 * returns how many users per backend exist (if supported by backend)
445
-	 *
446
-	 * @param boolean $hasLoggedIn when true only users that have a lastLogin
447
-	 *                             entry in the preferences table will be affected
448
-	 * @return array<string, int> an array of backend class as key and count number as value
449
-	 */
450
-	public function countUsers() {
451
-		$userCountStatistics = [];
452
-		foreach ($this->backends as $backend) {
453
-			if ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) {
454
-				/** @var ICountUsersBackend|IUserBackend $backend */
455
-				$backendUsers = $backend->countUsers();
456
-				if ($backendUsers !== false) {
457
-					if ($backend instanceof IUserBackend) {
458
-						$name = $backend->getBackendName();
459
-					} else {
460
-						$name = get_class($backend);
461
-					}
462
-					if (isset($userCountStatistics[$name])) {
463
-						$userCountStatistics[$name] += $backendUsers;
464
-					} else {
465
-						$userCountStatistics[$name] = $backendUsers;
466
-					}
467
-				}
468
-			}
469
-		}
470
-		return $userCountStatistics;
471
-	}
472
-
473
-	public function countUsersTotal(int $limit = 0, bool $onlyMappedUsers = false): int|false {
474
-		$userCount = false;
475
-
476
-		foreach ($this->backends as $backend) {
477
-			if ($onlyMappedUsers && $backend instanceof ICountMappedUsersBackend) {
478
-				$backendUsers = $backend->countMappedUsers();
479
-			} elseif ($backend instanceof ILimitAwareCountUsersBackend) {
480
-				$backendUsers = $backend->countUsers($limit);
481
-			} elseif ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) {
482
-				/** @var ICountUsersBackend $backend */
483
-				$backendUsers = $backend->countUsers();
484
-			} else {
485
-				$this->logger->debug('Skip backend for user count: ' . get_class($backend));
486
-				continue;
487
-			}
488
-			if ($backendUsers !== false) {
489
-				$userCount = (int)$userCount + $backendUsers;
490
-				if ($limit > 0) {
491
-					if ($userCount >= $limit) {
492
-						break;
493
-					}
494
-					$limit -= $userCount;
495
-				}
496
-			} else {
497
-				$this->logger->warning('Can not determine user count for ' . get_class($backend));
498
-			}
499
-		}
500
-		return $userCount;
501
-	}
502
-
503
-	/**
504
-	 * returns how many users per backend exist in the requested groups (if supported by backend)
505
-	 *
506
-	 * @param IGroup[] $groups an array of groups to search in
507
-	 * @param int $limit limit to stop counting
508
-	 * @return array{int,int} total number of users, and number of disabled users in the given groups, below $limit. If limit is reached, -1 is returned for number of disabled users
509
-	 */
510
-	public function countUsersAndDisabledUsersOfGroups(array $groups, int $limit): array {
511
-		$users = [];
512
-		$disabled = [];
513
-		foreach ($groups as $group) {
514
-			foreach ($group->getUsers() as $user) {
515
-				$users[$user->getUID()] = 1;
516
-				if (!$user->isEnabled()) {
517
-					$disabled[$user->getUID()] = 1;
518
-				}
519
-				if (count($users) >= $limit) {
520
-					return [count($users),-1];
521
-				}
522
-			}
523
-		}
524
-		return [count($users),count($disabled)];
525
-	}
526
-
527
-	/**
528
-	 * The callback is executed for each user on each backend.
529
-	 * If the callback returns false no further users will be retrieved.
530
-	 *
531
-	 * @psalm-param \Closure(\OCP\IUser):?bool $callback
532
-	 * @param string $search
533
-	 * @param boolean $onlySeen when true only users that have a lastLogin entry
534
-	 *                          in the preferences table will be affected
535
-	 * @since 9.0.0
536
-	 */
537
-	public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
538
-		if ($onlySeen) {
539
-			$this->callForSeenUsers($callback);
540
-		} else {
541
-			foreach ($this->getBackends() as $backend) {
542
-				$limit = 500;
543
-				$offset = 0;
544
-				do {
545
-					$users = $backend->getUsers($search, $limit, $offset);
546
-					foreach ($users as $uid) {
547
-						if (!$backend->userExists($uid)) {
548
-							continue;
549
-						}
550
-						$user = $this->getUserObject($uid, $backend, false);
551
-						$return = $callback($user);
552
-						if ($return === false) {
553
-							break;
554
-						}
555
-					}
556
-					$offset += $limit;
557
-				} while (count($users) >= $limit);
558
-			}
559
-		}
560
-	}
561
-
562
-	/**
563
-	 * returns how many users are disabled
564
-	 *
565
-	 * @return int
566
-	 * @since 12.0.0
567
-	 */
568
-	public function countDisabledUsers(): int {
569
-		$queryBuilder = Server::get(IDBConnection::class)->getQueryBuilder();
570
-		$queryBuilder->select($queryBuilder->func()->count('*'))
571
-			->from('preferences')
572
-			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
573
-			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
574
-			->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR));
575
-
576
-
577
-		$result = $queryBuilder->executeQuery();
578
-		$count = $result->fetchOne();
579
-		$result->closeCursor();
580
-
581
-		if ($count !== false) {
582
-			$count = (int)$count;
583
-		} else {
584
-			$count = 0;
585
-		}
586
-
587
-		return $count;
588
-	}
589
-
590
-	/**
591
-	 * returns how many users have logged in once
592
-	 *
593
-	 * @return int
594
-	 * @since 11.0.0
595
-	 */
596
-	public function countSeenUsers() {
597
-		$queryBuilder = Server::get(IDBConnection::class)->getQueryBuilder();
598
-		$queryBuilder->select($queryBuilder->func()->count('*'))
599
-			->from('preferences')
600
-			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
601
-			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')));
602
-
603
-		$query = $queryBuilder->executeQuery();
604
-
605
-		$result = (int)$query->fetchOne();
606
-		$query->closeCursor();
607
-
608
-		return $result;
609
-	}
610
-
611
-	public function callForSeenUsers(\Closure $callback) {
612
-		$users = $this->getSeenUsers();
613
-		foreach ($users as $user) {
614
-			$return = $callback($user);
615
-			if ($return === false) {
616
-				return;
617
-			}
618
-		}
619
-	}
620
-
621
-	/**
622
-	 * Getting all userIds that have a lastLogin value requires checking the
623
-	 * value in php because on oracle you cannot use a clob in a where clause,
624
-	 * preventing us from doing a not null or length(value) > 0 check.
625
-	 *
626
-	 * @param int $limit
627
-	 * @param int $offset
628
-	 * @return string[] with user ids
629
-	 */
630
-	private function getSeenUserIds($limit = null, $offset = null) {
631
-		$queryBuilder = Server::get(IDBConnection::class)->getQueryBuilder();
632
-		$queryBuilder->select(['userid'])
633
-			->from('preferences')
634
-			->where($queryBuilder->expr()->eq(
635
-				'appid', $queryBuilder->createNamedParameter('login'))
636
-			)
637
-			->andWhere($queryBuilder->expr()->eq(
638
-				'configkey', $queryBuilder->createNamedParameter('lastLogin'))
639
-			)
640
-			->andWhere($queryBuilder->expr()->isNotNull('configvalue')
641
-			);
642
-
643
-		if ($limit !== null) {
644
-			$queryBuilder->setMaxResults($limit);
645
-		}
646
-		if ($offset !== null) {
647
-			$queryBuilder->setFirstResult($offset);
648
-		}
649
-		$query = $queryBuilder->executeQuery();
650
-		$result = [];
651
-
652
-		while ($row = $query->fetch()) {
653
-			$result[] = $row['userid'];
654
-		}
655
-
656
-		$query->closeCursor();
657
-
658
-		return $result;
659
-	}
660
-
661
-	/**
662
-	 * @internal Only for mocks it in unit tests.
663
-	 */
664
-	public function getUserConfig(): IUserConfig {
665
-		return \OCP\Server::get(IUserConfig::class);
666
-	}
667
-
668
-	/**
669
-	 * @param string $email
670
-	 * @return IUser[]
671
-	 * @since 9.1.0
672
-	 */
673
-	public function getByEmail($email): array {
674
-		$users = [];
675
-		$userConfig = $this->getUserConfig();
676
-		// looking for 'email' only (and not primary_mail) is intentional
677
-		$userIds = $userConfig->searchUsersByValueString('settings', 'email', $email, caseInsensitive: true);
678
-		foreach ($userIds as $userId) {
679
-			$user = $this->get($userId);
680
-			if ($user !== null) {
681
-				$users[] = $user;
682
-			}
683
-		}
684
-		return $users;
685
-	}
686
-
687
-	/**
688
-	 * @param string $uid
689
-	 * @param bool $checkDataDirectory
690
-	 * @throws \InvalidArgumentException Message is an already translated string with a reason why the id is not valid
691
-	 * @since 26.0.0
692
-	 */
693
-	public function validateUserId(string $uid, bool $checkDataDirectory = false): void {
694
-		$l = Server::get(IFactory::class)->get('lib');
695
-
696
-		// Check the ID for bad characters
697
-		// Allowed are: "a-z", "A-Z", "0-9", spaces and "_.@-'"
698
-		if (preg_match('/[^a-zA-Z0-9 _.@\-\']/', $uid)) {
699
-			throw new \InvalidArgumentException($l->t('Only the following characters are allowed in an Login:'
700
-				. ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'));
701
-		}
702
-
703
-		// No empty user ID
704
-		if (trim($uid) === '') {
705
-			throw new \InvalidArgumentException($l->t('A valid Login must be provided'));
706
-		}
707
-
708
-		// No whitespace at the beginning or at the end
709
-		if (trim($uid) !== $uid) {
710
-			throw new \InvalidArgumentException($l->t('Login contains whitespace at the beginning or at the end'));
711
-		}
712
-
713
-		// User ID only consists of 1 or 2 dots (directory traversal)
714
-		if ($uid === '.' || $uid === '..') {
715
-			throw new \InvalidArgumentException($l->t('Login must not consist of dots only'));
716
-		}
717
-
718
-		// User ID is too long
719
-		if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
720
-			// TRANSLATORS User ID is too long
721
-			throw new \InvalidArgumentException($l->t('Username is too long'));
722
-		}
723
-
724
-		if (!$this->verifyUid($uid, $checkDataDirectory)) {
725
-			throw new \InvalidArgumentException($l->t('Login is invalid because files already exist for this user'));
726
-		}
727
-	}
728
-
729
-	/**
730
-	 * Gets the list of user ids sorted by lastLogin, from most recent to least recent
731
-	 *
732
-	 * @param int|null $limit how many users to fetch (default: 25, max: 100)
733
-	 * @param int $offset from which offset to fetch
734
-	 * @param string $search search users based on search params
735
-	 * @return list<string> list of user IDs
736
-	 */
737
-	public function getLastLoggedInUsers(?int $limit = null, int $offset = 0, string $search = ''): array {
738
-		// We can't load all users who already logged in
739
-		$limit = min(100, $limit ?: 25);
740
-
741
-		$connection = Server::get(IDBConnection::class);
742
-		$queryBuilder = $connection->getQueryBuilder();
743
-		$queryBuilder->select('pref_login.userid')
744
-			->from('preferences', 'pref_login')
745
-			->where($queryBuilder->expr()->eq('pref_login.appid', $queryBuilder->expr()->literal('login')))
746
-			->andWhere($queryBuilder->expr()->eq('pref_login.configkey', $queryBuilder->expr()->literal('lastLogin')))
747
-			->setFirstResult($offset)
748
-			->setMaxResults($limit)
749
-		;
750
-
751
-		// Oracle don't want to run ORDER BY on CLOB column
752
-		$loginOrder = $connection->getDatabaseProvider() === IDBConnection::PLATFORM_ORACLE
753
-			? $queryBuilder->expr()->castColumn('pref_login.configvalue', IQueryBuilder::PARAM_INT)
754
-			: 'pref_login.configvalue';
755
-		$queryBuilder
756
-			->orderBy($loginOrder, 'DESC')
757
-			->addOrderBy($queryBuilder->func()->lower('pref_login.userid'), 'ASC');
758
-
759
-		if ($search !== '') {
760
-			$displayNameMatches = $this->searchDisplayName($search);
761
-			$matchedUids = array_map(static fn (IUser $u): string => $u->getUID(), $displayNameMatches);
762
-
763
-			$queryBuilder
764
-				->leftJoin('pref_login', 'preferences', 'pref_email', $queryBuilder->expr()->andX(
765
-					$queryBuilder->expr()->eq('pref_login.userid', 'pref_email.userid'),
766
-					$queryBuilder->expr()->eq('pref_email.appid', $queryBuilder->expr()->literal('settings')),
767
-					$queryBuilder->expr()->eq('pref_email.configkey', $queryBuilder->expr()->literal('email')),
768
-				))
769
-				->andWhere($queryBuilder->expr()->orX(
770
-					$queryBuilder->expr()->in('pref_login.userid', $queryBuilder->createNamedParameter($matchedUids, IQueryBuilder::PARAM_STR_ARRAY)),
771
-				));
772
-		}
773
-
774
-		/** @var list<string> */
775
-		$list = $queryBuilder->executeQuery()->fetchAll(\PDO::FETCH_COLUMN);
776
-
777
-		return $list;
778
-	}
779
-
780
-	private function verifyUid(string $uid, bool $checkDataDirectory = false): bool {
781
-		$appdata = 'appdata_' . $this->config->getSystemValueString('instanceid');
782
-
783
-		if (\in_array($uid, [
784
-			'.htaccess',
785
-			'files_external',
786
-			'__groupfolders',
787
-			'.ncdata',
788
-			'owncloud.log',
789
-			'nextcloud.log',
790
-			'updater.log',
791
-			'audit.log',
792
-			$appdata], true)) {
793
-			return false;
794
-		}
795
-
796
-		if (!$checkDataDirectory) {
797
-			return true;
798
-		}
799
-
800
-		$dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data');
801
-
802
-		return !file_exists(rtrim($dataDirectory, '/') . '/' . $uid);
803
-	}
804
-
805
-	public function getDisplayNameCache(): DisplayNameCache {
806
-		return $this->displayNameCache;
807
-	}
808
-
809
-	public function getSeenUsers(int $offset = 0, ?int $limit = null): \Iterator {
810
-		$maxBatchSize = 1000;
811
-
812
-		do {
813
-			if ($limit !== null) {
814
-				$batchSize = min($limit, $maxBatchSize);
815
-				$limit -= $batchSize;
816
-			} else {
817
-				$batchSize = $maxBatchSize;
818
-			}
819
-
820
-			$userIds = $this->getSeenUserIds($batchSize, $offset);
821
-			$offset += $batchSize;
822
-
823
-			foreach ($userIds as $userId) {
824
-				foreach ($this->backends as $backend) {
825
-					if ($backend->userExists($userId)) {
826
-						$user = new LazyUser($userId, $this, null, $backend);
827
-						yield $user;
828
-						break;
829
-					}
830
-				}
831
-			}
832
-		} while (count($userIds) === $batchSize && $limit !== 0);
833
-	}
57
+    /**
58
+     * @var UserInterface[] $backends
59
+     */
60
+    private array $backends = [];
61
+
62
+    /**
63
+     * @var array<string,\OC\User\User> $cachedUsers
64
+     */
65
+    private array $cachedUsers = [];
66
+
67
+    private ICache $cache;
68
+
69
+    private DisplayNameCache $displayNameCache;
70
+
71
+    // This constructor can't autoload any class requiring a DB connection.
72
+    public function __construct(
73
+        private IConfig $config,
74
+        ICacheFactory $cacheFactory,
75
+        private IEventDispatcher $eventDispatcher,
76
+        private LoggerInterface $logger,
77
+    ) {
78
+        $this->cache = new WithLocalCache($cacheFactory->createDistributed('user_backend_map'));
79
+        $this->listen('\OC\User', 'postDelete', function (IUser $user): void {
80
+            unset($this->cachedUsers[$user->getUID()]);
81
+        });
82
+        $this->displayNameCache = new DisplayNameCache($cacheFactory, $this);
83
+    }
84
+
85
+    /**
86
+     * Get the active backends
87
+     * @return UserInterface[]
88
+     */
89
+    public function getBackends(): array {
90
+        return $this->backends;
91
+    }
92
+
93
+    public function registerBackend(UserInterface $backend): void {
94
+        $this->backends[] = $backend;
95
+    }
96
+
97
+    public function removeBackend(UserInterface $backend): void {
98
+        $this->cachedUsers = [];
99
+        if (($i = array_search($backend, $this->backends)) !== false) {
100
+            unset($this->backends[$i]);
101
+        }
102
+    }
103
+
104
+    public function clearBackends(): void {
105
+        $this->cachedUsers = [];
106
+        $this->backends = [];
107
+    }
108
+
109
+    /**
110
+     * get a user by user id
111
+     *
112
+     * @param string $uid
113
+     * @return \OC\User\User|null Either the user or null if the specified user does not exist
114
+     */
115
+    public function get($uid) {
116
+        if (is_null($uid) || $uid === '' || $uid === false) {
117
+            return null;
118
+        }
119
+        if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
120
+            return $this->cachedUsers[$uid];
121
+        }
122
+
123
+        if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
124
+            return null;
125
+        }
126
+
127
+        $cachedBackend = $this->cache->get(sha1($uid));
128
+        if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
129
+            // Cache has the info of the user backend already, so ask that one directly
130
+            $backend = $this->backends[$cachedBackend];
131
+            if ($backend->userExists($uid)) {
132
+                return $this->getUserObject($uid, $backend);
133
+            }
134
+        }
135
+
136
+        foreach ($this->backends as $i => $backend) {
137
+            if ($i === $cachedBackend) {
138
+                // Tried that one already
139
+                continue;
140
+            }
141
+
142
+            if ($backend->userExists($uid)) {
143
+                // Hash $uid to ensure that only valid characters are used for the cache key
144
+                $this->cache->set(sha1($uid), $i, 300);
145
+                return $this->getUserObject($uid, $backend);
146
+            }
147
+        }
148
+        return null;
149
+    }
150
+
151
+    public function getDisplayName(string $uid): ?string {
152
+        return $this->displayNameCache->getDisplayName($uid);
153
+    }
154
+
155
+    /**
156
+     * get or construct the user object
157
+     *
158
+     * @param string $uid
159
+     * @param \OCP\UserInterface $backend
160
+     * @param bool $cacheUser If false the newly created user object will not be cached
161
+     * @return \OC\User\User
162
+     */
163
+    public function getUserObject($uid, $backend, $cacheUser = true) {
164
+        if ($backend instanceof IGetRealUIDBackend) {
165
+            $uid = $backend->getRealUID($uid);
166
+        }
167
+
168
+        if (isset($this->cachedUsers[$uid])) {
169
+            return $this->cachedUsers[$uid];
170
+        }
171
+
172
+        $user = new User($uid, $backend, $this->eventDispatcher, $this, $this->config);
173
+        if ($cacheUser) {
174
+            $this->cachedUsers[$uid] = $user;
175
+        }
176
+        return $user;
177
+    }
178
+
179
+    /**
180
+     * check if a user exists
181
+     *
182
+     * @param string $uid
183
+     * @return bool
184
+     */
185
+    public function userExists($uid) {
186
+        if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
187
+            return false;
188
+        }
189
+
190
+        $user = $this->get($uid);
191
+        return ($user !== null);
192
+    }
193
+
194
+    /**
195
+     * Check if the password is valid for the user
196
+     *
197
+     * @param string $loginName
198
+     * @param string $password
199
+     * @return IUser|false the User object on success, false otherwise
200
+     */
201
+    public function checkPassword($loginName, $password) {
202
+        $result = $this->checkPasswordNoLogging($loginName, $password);
203
+
204
+        if ($result === false) {
205
+            $this->logger->warning('Login failed: \'' . $loginName . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']);
206
+        }
207
+
208
+        return $result;
209
+    }
210
+
211
+    /**
212
+     * Check if the password is valid for the user
213
+     *
214
+     * @internal
215
+     * @param string $loginName
216
+     * @param string $password
217
+     * @return IUser|false the User object on success, false otherwise
218
+     */
219
+    public function checkPasswordNoLogging($loginName, $password) {
220
+        $loginName = str_replace("\0", '', $loginName);
221
+        $password = str_replace("\0", '', $password);
222
+
223
+        $cachedBackend = $this->cache->get($loginName);
224
+        if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
225
+            $backends = [$this->backends[$cachedBackend]];
226
+        } else {
227
+            $backends = $this->backends;
228
+        }
229
+        foreach ($backends as $backend) {
230
+            if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
231
+                /** @var ICheckPasswordBackend $backend */
232
+                $uid = $backend->checkPassword($loginName, $password);
233
+                if ($uid !== false) {
234
+                    return $this->getUserObject($uid, $backend);
235
+                }
236
+            }
237
+        }
238
+
239
+        // since http basic auth doesn't provide a standard way of handling non ascii password we allow password to be urlencoded
240
+        // we only do this decoding after using the plain password fails to maintain compatibility with any password that happens
241
+        // to contain urlencoded patterns by "accident".
242
+        $password = urldecode($password);
243
+
244
+        foreach ($backends as $backend) {
245
+            if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
246
+                /** @var ICheckPasswordBackend|UserInterface $backend */
247
+                $uid = $backend->checkPassword($loginName, $password);
248
+                if ($uid !== false) {
249
+                    return $this->getUserObject($uid, $backend);
250
+                }
251
+            }
252
+        }
253
+
254
+        return false;
255
+    }
256
+
257
+    public function search($pattern, $limit = null, $offset = null) {
258
+        $users = [];
259
+        foreach ($this->backends as $backend) {
260
+            $backendUsers = $backend->getUsers($pattern, $limit, $offset);
261
+            if (is_array($backendUsers)) {
262
+                foreach ($backendUsers as $uid) {
263
+                    $users[$uid] = new LazyUser($uid, $this, null, $backend);
264
+                }
265
+            }
266
+        }
267
+
268
+        uasort($users, function (IUser $a, IUser $b) {
269
+            return strcasecmp($a->getUID(), $b->getUID());
270
+        });
271
+        return $users;
272
+    }
273
+
274
+    public function searchDisplayName($pattern, $limit = null, $offset = null) {
275
+        $users = [];
276
+        foreach ($this->backends as $backend) {
277
+            $backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
278
+            if (is_array($backendUsers)) {
279
+                foreach ($backendUsers as $uid => $displayName) {
280
+                    $users[] = new LazyUser($uid, $this, $displayName, $backend);
281
+                }
282
+            }
283
+        }
284
+
285
+        usort($users, function (IUser $a, IUser $b) {
286
+            return strcasecmp($a->getDisplayName(), $b->getDisplayName());
287
+        });
288
+        return $users;
289
+    }
290
+
291
+    /**
292
+     * @return IUser[]
293
+     */
294
+    public function getDisabledUsers(?int $limit = null, int $offset = 0, string $search = ''): array {
295
+        $users = $this->config->getUsersForUserValue('core', 'enabled', 'false');
296
+        $users = array_combine(
297
+            $users,
298
+            array_map(
299
+                fn (string $uid): IUser => new LazyUser($uid, $this),
300
+                $users
301
+            )
302
+        );
303
+        if ($search !== '') {
304
+            $users = array_filter(
305
+                $users,
306
+                function (IUser $user) use ($search): bool {
307
+                    try {
308
+                        return mb_stripos($user->getUID(), $search) !== false
309
+                        || mb_stripos($user->getDisplayName(), $search) !== false
310
+                        || mb_stripos($user->getEMailAddress() ?? '', $search) !== false;
311
+                    } catch (NoUserException $ex) {
312
+                        $this->logger->error('Error while filtering disabled users', ['exception' => $ex, 'userUID' => $user->getUID()]);
313
+                        return false;
314
+                    }
315
+                });
316
+        }
317
+
318
+        $tempLimit = ($limit === null ? null : $limit + $offset);
319
+        foreach ($this->backends as $backend) {
320
+            if (($tempLimit !== null) && (count($users) >= $tempLimit)) {
321
+                break;
322
+            }
323
+            if ($backend instanceof IProvideEnabledStateBackend) {
324
+                $backendUsers = $backend->getDisabledUserList(($tempLimit === null ? null : $tempLimit - count($users)), 0, $search);
325
+                foreach ($backendUsers as $uid) {
326
+                    $users[$uid] = new LazyUser($uid, $this, null, $backend);
327
+                }
328
+            }
329
+        }
330
+
331
+        return array_slice($users, $offset, $limit);
332
+    }
333
+
334
+    /**
335
+     * Search known users (from phonebook sync) by displayName
336
+     *
337
+     * @param string $searcher
338
+     * @param string $pattern
339
+     * @param int|null $limit
340
+     * @param int|null $offset
341
+     * @return IUser[]
342
+     */
343
+    public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array {
344
+        $users = [];
345
+        foreach ($this->backends as $backend) {
346
+            if ($backend instanceof ISearchKnownUsersBackend) {
347
+                $backendUsers = $backend->searchKnownUsersByDisplayName($searcher, $pattern, $limit, $offset);
348
+            } else {
349
+                // Better than nothing, but filtering after pagination can remove lots of results.
350
+                $backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
351
+            }
352
+            if (is_array($backendUsers)) {
353
+                foreach ($backendUsers as $uid => $displayName) {
354
+                    $users[] = $this->getUserObject($uid, $backend);
355
+                }
356
+            }
357
+        }
358
+
359
+        usort($users, function ($a, $b) {
360
+            /**
361
+             * @var IUser $a
362
+             * @var IUser $b
363
+             */
364
+            return strcasecmp($a->getDisplayName(), $b->getDisplayName());
365
+        });
366
+        return $users;
367
+    }
368
+
369
+    /**
370
+     * @param string $uid
371
+     * @param string $password
372
+     * @return false|IUser the created user or false
373
+     * @throws \InvalidArgumentException
374
+     * @throws HintException
375
+     */
376
+    public function createUser($uid, $password) {
377
+        // DI injection is not used here as IRegistry needs the user manager itself for user count and thus it would create a cyclic dependency
378
+        /** @var IAssertion $assertion */
379
+        $assertion = \OC::$server->get(IAssertion::class);
380
+        $assertion->createUserIsLegit();
381
+
382
+        $localBackends = [];
383
+        foreach ($this->backends as $backend) {
384
+            if ($backend instanceof Database) {
385
+                // First check if there is another user backend
386
+                $localBackends[] = $backend;
387
+                continue;
388
+            }
389
+
390
+            if ($backend->implementsActions(Backend::CREATE_USER)) {
391
+                return $this->createUserFromBackend($uid, $password, $backend);
392
+            }
393
+        }
394
+
395
+        foreach ($localBackends as $backend) {
396
+            if ($backend->implementsActions(Backend::CREATE_USER)) {
397
+                return $this->createUserFromBackend($uid, $password, $backend);
398
+            }
399
+        }
400
+
401
+        return false;
402
+    }
403
+
404
+    /**
405
+     * @param string $uid
406
+     * @param string $password
407
+     * @param UserInterface $backend
408
+     * @return IUser|false
409
+     * @throws \InvalidArgumentException
410
+     */
411
+    public function createUserFromBackend($uid, $password, UserInterface $backend) {
412
+        $l = \OCP\Util::getL10N('lib');
413
+
414
+        $this->validateUserId($uid, true);
415
+
416
+        // No empty password
417
+        if (trim($password) === '') {
418
+            throw new \InvalidArgumentException($l->t('A valid password must be provided'));
419
+        }
420
+
421
+        // Check if user already exists
422
+        if ($this->userExists($uid)) {
423
+            throw new \InvalidArgumentException($l->t('The Login is already being used'));
424
+        }
425
+
426
+        /** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */
427
+        $this->emit('\OC\User', 'preCreateUser', [$uid, $password]);
428
+        $this->eventDispatcher->dispatchTyped(new BeforeUserCreatedEvent($uid, $password));
429
+        $state = $backend->createUser($uid, $password);
430
+        if ($state === false) {
431
+            throw new \InvalidArgumentException($l->t('Could not create account'));
432
+        }
433
+        $user = $this->getUserObject($uid, $backend);
434
+        if ($user instanceof IUser) {
435
+            /** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */
436
+            $this->emit('\OC\User', 'postCreateUser', [$user, $password]);
437
+            $this->eventDispatcher->dispatchTyped(new UserCreatedEvent($user, $password));
438
+            return $user;
439
+        }
440
+        return false;
441
+    }
442
+
443
+    /**
444
+     * returns how many users per backend exist (if supported by backend)
445
+     *
446
+     * @param boolean $hasLoggedIn when true only users that have a lastLogin
447
+     *                             entry in the preferences table will be affected
448
+     * @return array<string, int> an array of backend class as key and count number as value
449
+     */
450
+    public function countUsers() {
451
+        $userCountStatistics = [];
452
+        foreach ($this->backends as $backend) {
453
+            if ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) {
454
+                /** @var ICountUsersBackend|IUserBackend $backend */
455
+                $backendUsers = $backend->countUsers();
456
+                if ($backendUsers !== false) {
457
+                    if ($backend instanceof IUserBackend) {
458
+                        $name = $backend->getBackendName();
459
+                    } else {
460
+                        $name = get_class($backend);
461
+                    }
462
+                    if (isset($userCountStatistics[$name])) {
463
+                        $userCountStatistics[$name] += $backendUsers;
464
+                    } else {
465
+                        $userCountStatistics[$name] = $backendUsers;
466
+                    }
467
+                }
468
+            }
469
+        }
470
+        return $userCountStatistics;
471
+    }
472
+
473
+    public function countUsersTotal(int $limit = 0, bool $onlyMappedUsers = false): int|false {
474
+        $userCount = false;
475
+
476
+        foreach ($this->backends as $backend) {
477
+            if ($onlyMappedUsers && $backend instanceof ICountMappedUsersBackend) {
478
+                $backendUsers = $backend->countMappedUsers();
479
+            } elseif ($backend instanceof ILimitAwareCountUsersBackend) {
480
+                $backendUsers = $backend->countUsers($limit);
481
+            } elseif ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) {
482
+                /** @var ICountUsersBackend $backend */
483
+                $backendUsers = $backend->countUsers();
484
+            } else {
485
+                $this->logger->debug('Skip backend for user count: ' . get_class($backend));
486
+                continue;
487
+            }
488
+            if ($backendUsers !== false) {
489
+                $userCount = (int)$userCount + $backendUsers;
490
+                if ($limit > 0) {
491
+                    if ($userCount >= $limit) {
492
+                        break;
493
+                    }
494
+                    $limit -= $userCount;
495
+                }
496
+            } else {
497
+                $this->logger->warning('Can not determine user count for ' . get_class($backend));
498
+            }
499
+        }
500
+        return $userCount;
501
+    }
502
+
503
+    /**
504
+     * returns how many users per backend exist in the requested groups (if supported by backend)
505
+     *
506
+     * @param IGroup[] $groups an array of groups to search in
507
+     * @param int $limit limit to stop counting
508
+     * @return array{int,int} total number of users, and number of disabled users in the given groups, below $limit. If limit is reached, -1 is returned for number of disabled users
509
+     */
510
+    public function countUsersAndDisabledUsersOfGroups(array $groups, int $limit): array {
511
+        $users = [];
512
+        $disabled = [];
513
+        foreach ($groups as $group) {
514
+            foreach ($group->getUsers() as $user) {
515
+                $users[$user->getUID()] = 1;
516
+                if (!$user->isEnabled()) {
517
+                    $disabled[$user->getUID()] = 1;
518
+                }
519
+                if (count($users) >= $limit) {
520
+                    return [count($users),-1];
521
+                }
522
+            }
523
+        }
524
+        return [count($users),count($disabled)];
525
+    }
526
+
527
+    /**
528
+     * The callback is executed for each user on each backend.
529
+     * If the callback returns false no further users will be retrieved.
530
+     *
531
+     * @psalm-param \Closure(\OCP\IUser):?bool $callback
532
+     * @param string $search
533
+     * @param boolean $onlySeen when true only users that have a lastLogin entry
534
+     *                          in the preferences table will be affected
535
+     * @since 9.0.0
536
+     */
537
+    public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
538
+        if ($onlySeen) {
539
+            $this->callForSeenUsers($callback);
540
+        } else {
541
+            foreach ($this->getBackends() as $backend) {
542
+                $limit = 500;
543
+                $offset = 0;
544
+                do {
545
+                    $users = $backend->getUsers($search, $limit, $offset);
546
+                    foreach ($users as $uid) {
547
+                        if (!$backend->userExists($uid)) {
548
+                            continue;
549
+                        }
550
+                        $user = $this->getUserObject($uid, $backend, false);
551
+                        $return = $callback($user);
552
+                        if ($return === false) {
553
+                            break;
554
+                        }
555
+                    }
556
+                    $offset += $limit;
557
+                } while (count($users) >= $limit);
558
+            }
559
+        }
560
+    }
561
+
562
+    /**
563
+     * returns how many users are disabled
564
+     *
565
+     * @return int
566
+     * @since 12.0.0
567
+     */
568
+    public function countDisabledUsers(): int {
569
+        $queryBuilder = Server::get(IDBConnection::class)->getQueryBuilder();
570
+        $queryBuilder->select($queryBuilder->func()->count('*'))
571
+            ->from('preferences')
572
+            ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
573
+            ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
574
+            ->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR));
575
+
576
+
577
+        $result = $queryBuilder->executeQuery();
578
+        $count = $result->fetchOne();
579
+        $result->closeCursor();
580
+
581
+        if ($count !== false) {
582
+            $count = (int)$count;
583
+        } else {
584
+            $count = 0;
585
+        }
586
+
587
+        return $count;
588
+    }
589
+
590
+    /**
591
+     * returns how many users have logged in once
592
+     *
593
+     * @return int
594
+     * @since 11.0.0
595
+     */
596
+    public function countSeenUsers() {
597
+        $queryBuilder = Server::get(IDBConnection::class)->getQueryBuilder();
598
+        $queryBuilder->select($queryBuilder->func()->count('*'))
599
+            ->from('preferences')
600
+            ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
601
+            ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')));
602
+
603
+        $query = $queryBuilder->executeQuery();
604
+
605
+        $result = (int)$query->fetchOne();
606
+        $query->closeCursor();
607
+
608
+        return $result;
609
+    }
610
+
611
+    public function callForSeenUsers(\Closure $callback) {
612
+        $users = $this->getSeenUsers();
613
+        foreach ($users as $user) {
614
+            $return = $callback($user);
615
+            if ($return === false) {
616
+                return;
617
+            }
618
+        }
619
+    }
620
+
621
+    /**
622
+     * Getting all userIds that have a lastLogin value requires checking the
623
+     * value in php because on oracle you cannot use a clob in a where clause,
624
+     * preventing us from doing a not null or length(value) > 0 check.
625
+     *
626
+     * @param int $limit
627
+     * @param int $offset
628
+     * @return string[] with user ids
629
+     */
630
+    private function getSeenUserIds($limit = null, $offset = null) {
631
+        $queryBuilder = Server::get(IDBConnection::class)->getQueryBuilder();
632
+        $queryBuilder->select(['userid'])
633
+            ->from('preferences')
634
+            ->where($queryBuilder->expr()->eq(
635
+                'appid', $queryBuilder->createNamedParameter('login'))
636
+            )
637
+            ->andWhere($queryBuilder->expr()->eq(
638
+                'configkey', $queryBuilder->createNamedParameter('lastLogin'))
639
+            )
640
+            ->andWhere($queryBuilder->expr()->isNotNull('configvalue')
641
+            );
642
+
643
+        if ($limit !== null) {
644
+            $queryBuilder->setMaxResults($limit);
645
+        }
646
+        if ($offset !== null) {
647
+            $queryBuilder->setFirstResult($offset);
648
+        }
649
+        $query = $queryBuilder->executeQuery();
650
+        $result = [];
651
+
652
+        while ($row = $query->fetch()) {
653
+            $result[] = $row['userid'];
654
+        }
655
+
656
+        $query->closeCursor();
657
+
658
+        return $result;
659
+    }
660
+
661
+    /**
662
+     * @internal Only for mocks it in unit tests.
663
+     */
664
+    public function getUserConfig(): IUserConfig {
665
+        return \OCP\Server::get(IUserConfig::class);
666
+    }
667
+
668
+    /**
669
+     * @param string $email
670
+     * @return IUser[]
671
+     * @since 9.1.0
672
+     */
673
+    public function getByEmail($email): array {
674
+        $users = [];
675
+        $userConfig = $this->getUserConfig();
676
+        // looking for 'email' only (and not primary_mail) is intentional
677
+        $userIds = $userConfig->searchUsersByValueString('settings', 'email', $email, caseInsensitive: true);
678
+        foreach ($userIds as $userId) {
679
+            $user = $this->get($userId);
680
+            if ($user !== null) {
681
+                $users[] = $user;
682
+            }
683
+        }
684
+        return $users;
685
+    }
686
+
687
+    /**
688
+     * @param string $uid
689
+     * @param bool $checkDataDirectory
690
+     * @throws \InvalidArgumentException Message is an already translated string with a reason why the id is not valid
691
+     * @since 26.0.0
692
+     */
693
+    public function validateUserId(string $uid, bool $checkDataDirectory = false): void {
694
+        $l = Server::get(IFactory::class)->get('lib');
695
+
696
+        // Check the ID for bad characters
697
+        // Allowed are: "a-z", "A-Z", "0-9", spaces and "_.@-'"
698
+        if (preg_match('/[^a-zA-Z0-9 _.@\-\']/', $uid)) {
699
+            throw new \InvalidArgumentException($l->t('Only the following characters are allowed in an Login:'
700
+                . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'));
701
+        }
702
+
703
+        // No empty user ID
704
+        if (trim($uid) === '') {
705
+            throw new \InvalidArgumentException($l->t('A valid Login must be provided'));
706
+        }
707
+
708
+        // No whitespace at the beginning or at the end
709
+        if (trim($uid) !== $uid) {
710
+            throw new \InvalidArgumentException($l->t('Login contains whitespace at the beginning or at the end'));
711
+        }
712
+
713
+        // User ID only consists of 1 or 2 dots (directory traversal)
714
+        if ($uid === '.' || $uid === '..') {
715
+            throw new \InvalidArgumentException($l->t('Login must not consist of dots only'));
716
+        }
717
+
718
+        // User ID is too long
719
+        if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
720
+            // TRANSLATORS User ID is too long
721
+            throw new \InvalidArgumentException($l->t('Username is too long'));
722
+        }
723
+
724
+        if (!$this->verifyUid($uid, $checkDataDirectory)) {
725
+            throw new \InvalidArgumentException($l->t('Login is invalid because files already exist for this user'));
726
+        }
727
+    }
728
+
729
+    /**
730
+     * Gets the list of user ids sorted by lastLogin, from most recent to least recent
731
+     *
732
+     * @param int|null $limit how many users to fetch (default: 25, max: 100)
733
+     * @param int $offset from which offset to fetch
734
+     * @param string $search search users based on search params
735
+     * @return list<string> list of user IDs
736
+     */
737
+    public function getLastLoggedInUsers(?int $limit = null, int $offset = 0, string $search = ''): array {
738
+        // We can't load all users who already logged in
739
+        $limit = min(100, $limit ?: 25);
740
+
741
+        $connection = Server::get(IDBConnection::class);
742
+        $queryBuilder = $connection->getQueryBuilder();
743
+        $queryBuilder->select('pref_login.userid')
744
+            ->from('preferences', 'pref_login')
745
+            ->where($queryBuilder->expr()->eq('pref_login.appid', $queryBuilder->expr()->literal('login')))
746
+            ->andWhere($queryBuilder->expr()->eq('pref_login.configkey', $queryBuilder->expr()->literal('lastLogin')))
747
+            ->setFirstResult($offset)
748
+            ->setMaxResults($limit)
749
+        ;
750
+
751
+        // Oracle don't want to run ORDER BY on CLOB column
752
+        $loginOrder = $connection->getDatabaseProvider() === IDBConnection::PLATFORM_ORACLE
753
+            ? $queryBuilder->expr()->castColumn('pref_login.configvalue', IQueryBuilder::PARAM_INT)
754
+            : 'pref_login.configvalue';
755
+        $queryBuilder
756
+            ->orderBy($loginOrder, 'DESC')
757
+            ->addOrderBy($queryBuilder->func()->lower('pref_login.userid'), 'ASC');
758
+
759
+        if ($search !== '') {
760
+            $displayNameMatches = $this->searchDisplayName($search);
761
+            $matchedUids = array_map(static fn (IUser $u): string => $u->getUID(), $displayNameMatches);
762
+
763
+            $queryBuilder
764
+                ->leftJoin('pref_login', 'preferences', 'pref_email', $queryBuilder->expr()->andX(
765
+                    $queryBuilder->expr()->eq('pref_login.userid', 'pref_email.userid'),
766
+                    $queryBuilder->expr()->eq('pref_email.appid', $queryBuilder->expr()->literal('settings')),
767
+                    $queryBuilder->expr()->eq('pref_email.configkey', $queryBuilder->expr()->literal('email')),
768
+                ))
769
+                ->andWhere($queryBuilder->expr()->orX(
770
+                    $queryBuilder->expr()->in('pref_login.userid', $queryBuilder->createNamedParameter($matchedUids, IQueryBuilder::PARAM_STR_ARRAY)),
771
+                ));
772
+        }
773
+
774
+        /** @var list<string> */
775
+        $list = $queryBuilder->executeQuery()->fetchAll(\PDO::FETCH_COLUMN);
776
+
777
+        return $list;
778
+    }
779
+
780
+    private function verifyUid(string $uid, bool $checkDataDirectory = false): bool {
781
+        $appdata = 'appdata_' . $this->config->getSystemValueString('instanceid');
782
+
783
+        if (\in_array($uid, [
784
+            '.htaccess',
785
+            'files_external',
786
+            '__groupfolders',
787
+            '.ncdata',
788
+            'owncloud.log',
789
+            'nextcloud.log',
790
+            'updater.log',
791
+            'audit.log',
792
+            $appdata], true)) {
793
+            return false;
794
+        }
795
+
796
+        if (!$checkDataDirectory) {
797
+            return true;
798
+        }
799
+
800
+        $dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data');
801
+
802
+        return !file_exists(rtrim($dataDirectory, '/') . '/' . $uid);
803
+    }
804
+
805
+    public function getDisplayNameCache(): DisplayNameCache {
806
+        return $this->displayNameCache;
807
+    }
808
+
809
+    public function getSeenUsers(int $offset = 0, ?int $limit = null): \Iterator {
810
+        $maxBatchSize = 1000;
811
+
812
+        do {
813
+            if ($limit !== null) {
814
+                $batchSize = min($limit, $maxBatchSize);
815
+                $limit -= $batchSize;
816
+            } else {
817
+                $batchSize = $maxBatchSize;
818
+            }
819
+
820
+            $userIds = $this->getSeenUserIds($batchSize, $offset);
821
+            $offset += $batchSize;
822
+
823
+            foreach ($userIds as $userId) {
824
+                foreach ($this->backends as $backend) {
825
+                    if ($backend->userExists($userId)) {
826
+                        $user = new LazyUser($userId, $this, null, $backend);
827
+                        yield $user;
828
+                        break;
829
+                    }
830
+                }
831
+            }
832
+        } while (count($userIds) === $batchSize && $limit !== 0);
833
+    }
834 834
 }
Please login to merge, or discard this patch.
Spacing   +17 added lines, -17 removed lines patch added patch discarded remove patch
@@ -76,7 +76,7 @@  discard block
 block discarded – undo
76 76
 		private LoggerInterface $logger,
77 77
 	) {
78 78
 		$this->cache = new WithLocalCache($cacheFactory->createDistributed('user_backend_map'));
79
-		$this->listen('\OC\User', 'postDelete', function (IUser $user): void {
79
+		$this->listen('\OC\User', 'postDelete', function(IUser $user): void {
80 80
 			unset($this->cachedUsers[$user->getUID()]);
81 81
 		});
82 82
 		$this->displayNameCache = new DisplayNameCache($cacheFactory, $this);
@@ -202,7 +202,7 @@  discard block
 block discarded – undo
202 202
 		$result = $this->checkPasswordNoLogging($loginName, $password);
203 203
 
204 204
 		if ($result === false) {
205
-			$this->logger->warning('Login failed: \'' . $loginName . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']);
205
+			$this->logger->warning('Login failed: \''.$loginName.'\' (Remote IP: \''.\OC::$server->getRequest()->getRemoteAddress().'\')', ['app' => 'core']);
206 206
 		}
207 207
 
208 208
 		return $result;
@@ -265,7 +265,7 @@  discard block
 block discarded – undo
265 265
 			}
266 266
 		}
267 267
 
268
-		uasort($users, function (IUser $a, IUser $b) {
268
+		uasort($users, function(IUser $a, IUser $b) {
269 269
 			return strcasecmp($a->getUID(), $b->getUID());
270 270
 		});
271 271
 		return $users;
@@ -282,7 +282,7 @@  discard block
 block discarded – undo
282 282
 			}
283 283
 		}
284 284
 
285
-		usort($users, function (IUser $a, IUser $b) {
285
+		usort($users, function(IUser $a, IUser $b) {
286 286
 			return strcasecmp($a->getDisplayName(), $b->getDisplayName());
287 287
 		});
288 288
 		return $users;
@@ -303,7 +303,7 @@  discard block
 block discarded – undo
303 303
 		if ($search !== '') {
304 304
 			$users = array_filter(
305 305
 				$users,
306
-				function (IUser $user) use ($search): bool {
306
+				function(IUser $user) use ($search): bool {
307 307
 					try {
308 308
 						return mb_stripos($user->getUID(), $search) !== false
309 309
 						|| mb_stripos($user->getDisplayName(), $search) !== false
@@ -356,7 +356,7 @@  discard block
 block discarded – undo
356 356
 			}
357 357
 		}
358 358
 
359
-		usort($users, function ($a, $b) {
359
+		usort($users, function($a, $b) {
360 360
 			/**
361 361
 			 * @var IUser $a
362 362
 			 * @var IUser $b
@@ -470,7 +470,7 @@  discard block
 block discarded – undo
470 470
 		return $userCountStatistics;
471 471
 	}
472 472
 
473
-	public function countUsersTotal(int $limit = 0, bool $onlyMappedUsers = false): int|false {
473
+	public function countUsersTotal(int $limit = 0, bool $onlyMappedUsers = false): int | false {
474 474
 		$userCount = false;
475 475
 
476 476
 		foreach ($this->backends as $backend) {
@@ -482,11 +482,11 @@  discard block
 block discarded – undo
482 482
 				/** @var ICountUsersBackend $backend */
483 483
 				$backendUsers = $backend->countUsers();
484 484
 			} else {
485
-				$this->logger->debug('Skip backend for user count: ' . get_class($backend));
485
+				$this->logger->debug('Skip backend for user count: '.get_class($backend));
486 486
 				continue;
487 487
 			}
488 488
 			if ($backendUsers !== false) {
489
-				$userCount = (int)$userCount + $backendUsers;
489
+				$userCount = (int) $userCount + $backendUsers;
490 490
 				if ($limit > 0) {
491 491
 					if ($userCount >= $limit) {
492 492
 						break;
@@ -494,7 +494,7 @@  discard block
 block discarded – undo
494 494
 					$limit -= $userCount;
495 495
 				}
496 496
 			} else {
497
-				$this->logger->warning('Can not determine user count for ' . get_class($backend));
497
+				$this->logger->warning('Can not determine user count for '.get_class($backend));
498 498
 			}
499 499
 		}
500 500
 		return $userCount;
@@ -517,11 +517,11 @@  discard block
 block discarded – undo
517 517
 					$disabled[$user->getUID()] = 1;
518 518
 				}
519 519
 				if (count($users) >= $limit) {
520
-					return [count($users),-1];
520
+					return [count($users), -1];
521 521
 				}
522 522
 			}
523 523
 		}
524
-		return [count($users),count($disabled)];
524
+		return [count($users), count($disabled)];
525 525
 	}
526 526
 
527 527
 	/**
@@ -579,7 +579,7 @@  discard block
 block discarded – undo
579 579
 		$result->closeCursor();
580 580
 
581 581
 		if ($count !== false) {
582
-			$count = (int)$count;
582
+			$count = (int) $count;
583 583
 		} else {
584 584
 			$count = 0;
585 585
 		}
@@ -602,7 +602,7 @@  discard block
 block discarded – undo
602 602
 
603 603
 		$query = $queryBuilder->executeQuery();
604 604
 
605
-		$result = (int)$query->fetchOne();
605
+		$result = (int) $query->fetchOne();
606 606
 		$query->closeCursor();
607 607
 
608 608
 		return $result;
@@ -778,7 +778,7 @@  discard block
 block discarded – undo
778 778
 	}
779 779
 
780 780
 	private function verifyUid(string $uid, bool $checkDataDirectory = false): bool {
781
-		$appdata = 'appdata_' . $this->config->getSystemValueString('instanceid');
781
+		$appdata = 'appdata_'.$this->config->getSystemValueString('instanceid');
782 782
 
783 783
 		if (\in_array($uid, [
784 784
 			'.htaccess',
@@ -797,9 +797,9 @@  discard block
 block discarded – undo
797 797
 			return true;
798 798
 		}
799 799
 
800
-		$dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data');
800
+		$dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT.'/data');
801 801
 
802
-		return !file_exists(rtrim($dataDirectory, '/') . '/' . $uid);
802
+		return !file_exists(rtrim($dataDirectory, '/').'/'.$uid);
803 803
 	}
804 804
 
805 805
 	public function getDisplayNameCache(): DisplayNameCache {
Please login to merge, or discard this patch.
lib/base.php 1 patch
Indentation   +1182 added lines, -1182 removed lines patch added patch discarded remove patch
@@ -40,1188 +40,1188 @@
 block discarded – undo
40 40
  * OC_autoload!
41 41
  */
42 42
 class OC {
43
-	/**
44
-	 * The installation path for Nextcloud  on the server (e.g. /srv/http/nextcloud)
45
-	 */
46
-	public static string $SERVERROOT = '';
47
-	/**
48
-	 * the current request path relative to the Nextcloud root (e.g. files/index.php)
49
-	 */
50
-	private static string $SUBURI = '';
51
-	/**
52
-	 * the Nextcloud root path for http requests (e.g. /nextcloud)
53
-	 */
54
-	public static string $WEBROOT = '';
55
-	/**
56
-	 * The installation path array of the apps folder on the server (e.g. /srv/http/nextcloud) 'path' and
57
-	 * web path in 'url'
58
-	 */
59
-	public static array $APPSROOTS = [];
60
-
61
-	public static string $configDir;
62
-
63
-	/**
64
-	 * requested app
65
-	 */
66
-	public static string $REQUESTEDAPP = '';
67
-
68
-	/**
69
-	 * check if Nextcloud runs in cli mode
70
-	 */
71
-	public static bool $CLI = false;
72
-
73
-	public static \Composer\Autoload\ClassLoader $composerAutoloader;
74
-
75
-	public static \OC\Server $server;
76
-
77
-	private static \OC\Config $config;
78
-
79
-	/**
80
-	 * @throws \RuntimeException when the 3rdparty directory is missing or
81
-	 *                           the app path list is empty or contains an invalid path
82
-	 */
83
-	public static function initPaths(): void {
84
-		if (defined('PHPUNIT_CONFIG_DIR')) {
85
-			self::$configDir = OC::$SERVERROOT . '/' . PHPUNIT_CONFIG_DIR . '/';
86
-		} elseif (defined('PHPUNIT_RUN') && PHPUNIT_RUN && is_dir(OC::$SERVERROOT . '/tests/config/')) {
87
-			self::$configDir = OC::$SERVERROOT . '/tests/config/';
88
-		} elseif ($dir = getenv('NEXTCLOUD_CONFIG_DIR')) {
89
-			self::$configDir = rtrim($dir, '/') . '/';
90
-		} else {
91
-			self::$configDir = OC::$SERVERROOT . '/config/';
92
-		}
93
-		self::$config = new \OC\Config(self::$configDir);
94
-
95
-		OC::$SUBURI = str_replace('\\', '/', substr(realpath($_SERVER['SCRIPT_FILENAME'] ?? ''), strlen(OC::$SERVERROOT)));
96
-		/**
97
-		 * FIXME: The following lines are required because we can't yet instantiate
98
-		 *        Server::get(\OCP\IRequest::class) since \OC::$server does not yet exist.
99
-		 */
100
-		$params = [
101
-			'server' => [
102
-				'SCRIPT_NAME' => $_SERVER['SCRIPT_NAME'] ?? null,
103
-				'SCRIPT_FILENAME' => $_SERVER['SCRIPT_FILENAME'] ?? null,
104
-			],
105
-		];
106
-		if (isset($_SERVER['REMOTE_ADDR'])) {
107
-			$params['server']['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
108
-		}
109
-		$fakeRequest = new \OC\AppFramework\Http\Request(
110
-			$params,
111
-			new \OC\AppFramework\Http\RequestId($_SERVER['UNIQUE_ID'] ?? '', new \OC\Security\SecureRandom()),
112
-			new \OC\AllConfig(new \OC\SystemConfig(self::$config))
113
-		);
114
-		$scriptName = $fakeRequest->getScriptName();
115
-		if (substr($scriptName, -1) == '/') {
116
-			$scriptName .= 'index.php';
117
-			//make sure suburi follows the same rules as scriptName
118
-			if (substr(OC::$SUBURI, -9) != 'index.php') {
119
-				if (substr(OC::$SUBURI, -1) != '/') {
120
-					OC::$SUBURI = OC::$SUBURI . '/';
121
-				}
122
-				OC::$SUBURI = OC::$SUBURI . 'index.php';
123
-			}
124
-		}
125
-
126
-		if (OC::$CLI) {
127
-			OC::$WEBROOT = self::$config->getValue('overwritewebroot', '');
128
-		} else {
129
-			if (substr($scriptName, 0 - strlen(OC::$SUBURI)) === OC::$SUBURI) {
130
-				OC::$WEBROOT = substr($scriptName, 0, 0 - strlen(OC::$SUBURI));
131
-
132
-				if (OC::$WEBROOT != '' && OC::$WEBROOT[0] !== '/') {
133
-					OC::$WEBROOT = '/' . OC::$WEBROOT;
134
-				}
135
-			} else {
136
-				// The scriptName is not ending with OC::$SUBURI
137
-				// This most likely means that we are calling from CLI.
138
-				// However some cron jobs still need to generate
139
-				// a web URL, so we use overwritewebroot as a fallback.
140
-				OC::$WEBROOT = self::$config->getValue('overwritewebroot', '');
141
-			}
142
-
143
-			// Resolve /nextcloud to /nextcloud/ to ensure to always have a trailing
144
-			// slash which is required by URL generation.
145
-			if (isset($_SERVER['REQUEST_URI']) && $_SERVER['REQUEST_URI'] === \OC::$WEBROOT
146
-					&& substr($_SERVER['REQUEST_URI'], -1) !== '/') {
147
-				header('Location: ' . \OC::$WEBROOT . '/');
148
-				exit();
149
-			}
150
-		}
151
-
152
-		// search the apps folder
153
-		$config_paths = self::$config->getValue('apps_paths', []);
154
-		if (!empty($config_paths)) {
155
-			foreach ($config_paths as $paths) {
156
-				if (isset($paths['url']) && isset($paths['path'])) {
157
-					$paths['url'] = rtrim($paths['url'], '/');
158
-					$paths['path'] = rtrim($paths['path'], '/');
159
-					OC::$APPSROOTS[] = $paths;
160
-				}
161
-			}
162
-		} elseif (file_exists(OC::$SERVERROOT . '/apps')) {
163
-			OC::$APPSROOTS[] = ['path' => OC::$SERVERROOT . '/apps', 'url' => '/apps', 'writable' => true];
164
-		}
165
-
166
-		if (empty(OC::$APPSROOTS)) {
167
-			throw new \RuntimeException('apps directory not found! Please put the Nextcloud apps folder in the Nextcloud folder'
168
-				. '. You can also configure the location in the config.php file.');
169
-		}
170
-		$paths = [];
171
-		foreach (OC::$APPSROOTS as $path) {
172
-			$paths[] = $path['path'];
173
-			if (!is_dir($path['path'])) {
174
-				throw new \RuntimeException(sprintf('App directory "%s" not found! Please put the Nextcloud apps folder in the'
175
-					. ' Nextcloud folder. You can also configure the location in the config.php file.', $path['path']));
176
-			}
177
-		}
178
-
179
-		// set the right include path
180
-		set_include_path(
181
-			implode(PATH_SEPARATOR, $paths)
182
-		);
183
-	}
184
-
185
-	public static function checkConfig(): void {
186
-		// Create config if it does not already exist
187
-		$configFilePath = self::$configDir . '/config.php';
188
-		if (!file_exists($configFilePath)) {
189
-			@touch($configFilePath);
190
-		}
191
-
192
-		// Check if config is writable
193
-		$configFileWritable = is_writable($configFilePath);
194
-		$configReadOnly = Server::get(IConfig::class)->getSystemValueBool('config_is_read_only');
195
-		if (!$configFileWritable && !$configReadOnly
196
-			|| !$configFileWritable && \OCP\Util::needUpgrade()) {
197
-			$urlGenerator = Server::get(IURLGenerator::class);
198
-			$l = Server::get(\OCP\L10N\IFactory::class)->get('lib');
199
-
200
-			if (self::$CLI) {
201
-				echo $l->t('Cannot write into "config" directory!') . "\n";
202
-				echo $l->t('This can usually be fixed by giving the web server write access to the config directory.') . "\n";
203
-				echo "\n";
204
-				echo $l->t('But, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it.') . "\n";
205
-				echo $l->t('See %s', [ $urlGenerator->linkToDocs('admin-config') ]) . "\n";
206
-				exit;
207
-			} else {
208
-				Server::get(ITemplateManager::class)->printErrorPage(
209
-					$l->t('Cannot write into "config" directory!'),
210
-					$l->t('This can usually be fixed by giving the web server write access to the config directory.') . ' '
211
-					. $l->t('But, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it.') . ' '
212
-					. $l->t('See %s', [ $urlGenerator->linkToDocs('admin-config') ]),
213
-					503
214
-				);
215
-			}
216
-		}
217
-	}
218
-
219
-	public static function checkInstalled(\OC\SystemConfig $systemConfig): void {
220
-		if (defined('OC_CONSOLE')) {
221
-			return;
222
-		}
223
-		// Redirect to installer if not installed
224
-		if (!$systemConfig->getValue('installed', false) && OC::$SUBURI !== '/index.php' && OC::$SUBURI !== '/status.php') {
225
-			if (OC::$CLI) {
226
-				throw new Exception('Not installed');
227
-			} else {
228
-				$url = OC::$WEBROOT . '/index.php';
229
-				header('Location: ' . $url);
230
-			}
231
-			exit();
232
-		}
233
-	}
234
-
235
-	public static function checkMaintenanceMode(\OC\SystemConfig $systemConfig): void {
236
-		// Allow ajax update script to execute without being stopped
237
-		if (((bool)$systemConfig->getValue('maintenance', false)) && OC::$SUBURI != '/core/ajax/update.php') {
238
-			// send http status 503
239
-			http_response_code(503);
240
-			header('X-Nextcloud-Maintenance-Mode: 1');
241
-			header('Retry-After: 120');
242
-
243
-			// render error page
244
-			$template = Server::get(ITemplateManager::class)->getTemplate('', 'update.user', 'guest');
245
-			\OCP\Util::addScript('core', 'maintenance');
246
-			\OCP\Util::addScript('core', 'common');
247
-			\OCP\Util::addStyle('core', 'guest');
248
-			$template->printPage();
249
-			die();
250
-		}
251
-	}
252
-
253
-	/**
254
-	 * Prints the upgrade page
255
-	 */
256
-	private static function printUpgradePage(\OC\SystemConfig $systemConfig): void {
257
-		$cliUpgradeLink = $systemConfig->getValue('upgrade.cli-upgrade-link', '');
258
-		$disableWebUpdater = $systemConfig->getValue('upgrade.disable-web', false);
259
-		$tooBig = false;
260
-		if (!$disableWebUpdater) {
261
-			$apps = Server::get(\OCP\App\IAppManager::class);
262
-			if ($apps->isEnabledForAnyone('user_ldap')) {
263
-				$qb = Server::get(\OCP\IDBConnection::class)->getQueryBuilder();
264
-
265
-				$result = $qb->select($qb->func()->count('*', 'user_count'))
266
-					->from('ldap_user_mapping')
267
-					->executeQuery();
268
-				$row = $result->fetch();
269
-				$result->closeCursor();
270
-
271
-				$tooBig = ($row['user_count'] > 50);
272
-			}
273
-			if (!$tooBig && $apps->isEnabledForAnyone('user_saml')) {
274
-				$qb = Server::get(\OCP\IDBConnection::class)->getQueryBuilder();
275
-
276
-				$result = $qb->select($qb->func()->count('*', 'user_count'))
277
-					->from('user_saml_users')
278
-					->executeQuery();
279
-				$row = $result->fetch();
280
-				$result->closeCursor();
281
-
282
-				$tooBig = ($row['user_count'] > 50);
283
-			}
284
-			if (!$tooBig) {
285
-				// count users
286
-				$totalUsers = Server::get(\OCP\IUserManager::class)->countUsersTotal(51);
287
-				$tooBig = ($totalUsers > 50);
288
-			}
289
-		}
290
-		$ignoreTooBigWarning = isset($_GET['IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup'])
291
-			&& $_GET['IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup'] === 'IAmSuperSureToDoThis';
292
-
293
-		if ($disableWebUpdater || ($tooBig && !$ignoreTooBigWarning)) {
294
-			// send http status 503
295
-			http_response_code(503);
296
-			header('Retry-After: 120');
297
-
298
-			$serverVersion = \OCP\Server::get(\OCP\ServerVersion::class);
299
-
300
-			// render error page
301
-			$template = Server::get(ITemplateManager::class)->getTemplate('', 'update.use-cli', 'guest');
302
-			$template->assign('productName', 'nextcloud'); // for now
303
-			$template->assign('version', $serverVersion->getVersionString());
304
-			$template->assign('tooBig', $tooBig);
305
-			$template->assign('cliUpgradeLink', $cliUpgradeLink);
306
-
307
-			$template->printPage();
308
-			die();
309
-		}
310
-
311
-		// check whether this is a core update or apps update
312
-		$installedVersion = $systemConfig->getValue('version', '0.0.0');
313
-		$currentVersion = implode('.', \OCP\Util::getVersion());
314
-
315
-		// if not a core upgrade, then it's apps upgrade
316
-		$isAppsOnlyUpgrade = version_compare($currentVersion, $installedVersion, '=');
317
-
318
-		$oldTheme = $systemConfig->getValue('theme');
319
-		$systemConfig->setValue('theme', '');
320
-		\OCP\Util::addScript('core', 'common');
321
-		\OCP\Util::addScript('core', 'main');
322
-		\OCP\Util::addTranslations('core');
323
-		\OCP\Util::addScript('core', 'update');
324
-
325
-		/** @var \OC\App\AppManager $appManager */
326
-		$appManager = Server::get(\OCP\App\IAppManager::class);
327
-
328
-		$tmpl = Server::get(ITemplateManager::class)->getTemplate('', 'update.admin', 'guest');
329
-		$tmpl->assign('version', \OCP\Server::get(\OCP\ServerVersion::class)->getVersionString());
330
-		$tmpl->assign('isAppsOnlyUpgrade', $isAppsOnlyUpgrade);
331
-
332
-		// get third party apps
333
-		$ocVersion = \OCP\Util::getVersion();
334
-		$ocVersion = implode('.', $ocVersion);
335
-		$incompatibleApps = $appManager->getIncompatibleApps($ocVersion);
336
-		$incompatibleOverwrites = $systemConfig->getValue('app_install_overwrite', []);
337
-		$incompatibleShippedApps = [];
338
-		$incompatibleDisabledApps = [];
339
-		foreach ($incompatibleApps as $appInfo) {
340
-			if ($appManager->isShipped($appInfo['id'])) {
341
-				$incompatibleShippedApps[] = $appInfo['name'] . ' (' . $appInfo['id'] . ')';
342
-			}
343
-			if (!in_array($appInfo['id'], $incompatibleOverwrites)) {
344
-				$incompatibleDisabledApps[] = $appInfo;
345
-			}
346
-		}
347
-
348
-		if (!empty($incompatibleShippedApps)) {
349
-			$l = Server::get(\OCP\L10N\IFactory::class)->get('core');
350
-			$hint = $l->t('Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory.', [implode(', ', $incompatibleShippedApps)]);
351
-			throw new \OCP\HintException('Application ' . implode(', ', $incompatibleShippedApps) . ' is not present or has a non-compatible version with this server. Please check the apps directory.', $hint);
352
-		}
353
-
354
-		$tmpl->assign('appsToUpgrade', $appManager->getAppsNeedingUpgrade($ocVersion));
355
-		$tmpl->assign('incompatibleAppsList', $incompatibleDisabledApps);
356
-		try {
357
-			$defaults = new \OC_Defaults();
358
-			$tmpl->assign('productName', $defaults->getName());
359
-		} catch (Throwable $error) {
360
-			$tmpl->assign('productName', 'Nextcloud');
361
-		}
362
-		$tmpl->assign('oldTheme', $oldTheme);
363
-		$tmpl->printPage();
364
-	}
365
-
366
-	public static function initSession(): void {
367
-		$request = Server::get(IRequest::class);
368
-
369
-		// TODO: Temporary disabled again to solve issues with CalDAV/CardDAV clients like DAVx5 that use cookies
370
-		// TODO: See https://github.com/nextcloud/server/issues/37277#issuecomment-1476366147 and the other comments
371
-		// TODO: for further information.
372
-		// $isDavRequest = strpos($request->getRequestUri(), '/remote.php/dav') === 0 || strpos($request->getRequestUri(), '/remote.php/webdav') === 0;
373
-		// if ($request->getHeader('Authorization') !== '' && is_null($request->getCookie('cookie_test')) && $isDavRequest && !isset($_COOKIE['nc_session_id'])) {
374
-		// setcookie('cookie_test', 'test', time() + 3600);
375
-		// // Do not initialize the session if a request is authenticated directly
376
-		// // unless there is a session cookie already sent along
377
-		// return;
378
-		// }
379
-
380
-		if ($request->getServerProtocol() === 'https') {
381
-			ini_set('session.cookie_secure', 'true');
382
-		}
383
-
384
-		// prevents javascript from accessing php session cookies
385
-		ini_set('session.cookie_httponly', 'true');
386
-
387
-		// set the cookie path to the Nextcloud directory
388
-		$cookie_path = OC::$WEBROOT ? : '/';
389
-		ini_set('session.cookie_path', $cookie_path);
390
-
391
-		// set the cookie domain to the Nextcloud domain
392
-		$cookie_domain = self::$config->getValue('cookie_domain', '');
393
-		if ($cookie_domain) {
394
-			ini_set('session.cookie_domain', $cookie_domain);
395
-		}
396
-
397
-		// Do not initialize sessions for 'status.php' requests
398
-		// Monitoring endpoints can quickly flood session handlers
399
-		// and 'status.php' doesn't require sessions anyway
400
-		// We still need to run the ini_set above so that same-site cookies use the correct configuration.
401
-		if (str_ends_with($request->getScriptName(), '/status.php')) {
402
-			return;
403
-		}
404
-
405
-		// Let the session name be changed in the initSession Hook
406
-		$sessionName = OC_Util::getInstanceId();
407
-
408
-		try {
409
-			$logger = null;
410
-			if (Server::get(\OC\SystemConfig::class)->getValue('installed', false)) {
411
-				$logger = logger('core');
412
-			}
413
-
414
-			// set the session name to the instance id - which is unique
415
-			$session = new \OC\Session\Internal(
416
-				$sessionName,
417
-				$logger,
418
-			);
419
-
420
-			$cryptoWrapper = Server::get(\OC\Session\CryptoWrapper::class);
421
-			$session = $cryptoWrapper->wrapSession($session);
422
-			self::$server->setSession($session);
423
-
424
-			// if session can't be started break with http 500 error
425
-		} catch (Exception $e) {
426
-			Server::get(LoggerInterface::class)->error($e->getMessage(), ['app' => 'base','exception' => $e]);
427
-			//show the user a detailed error page
428
-			Server::get(ITemplateManager::class)->printExceptionErrorPage($e, 500);
429
-			die();
430
-		}
431
-
432
-		//try to set the session lifetime
433
-		$sessionLifeTime = self::getSessionLifeTime();
434
-
435
-		// session timeout
436
-		if ($session->exists('LAST_ACTIVITY') && (time() - $session->get('LAST_ACTIVITY') > $sessionLifeTime)) {
437
-			if (isset($_COOKIE[session_name()])) {
438
-				setcookie(session_name(), '', -1, self::$WEBROOT ? : '/');
439
-			}
440
-			Server::get(IUserSession::class)->logout();
441
-		}
442
-
443
-		if (!self::hasSessionRelaxedExpiry()) {
444
-			$session->set('LAST_ACTIVITY', time());
445
-		}
446
-		$session->close();
447
-	}
448
-
449
-	private static function getSessionLifeTime(): int {
450
-		return Server::get(IConfig::class)->getSystemValueInt('session_lifetime', 60 * 60 * 24);
451
-	}
452
-
453
-	/**
454
-	 * @return bool true if the session expiry should only be done by gc instead of an explicit timeout
455
-	 */
456
-	public static function hasSessionRelaxedExpiry(): bool {
457
-		return Server::get(IConfig::class)->getSystemValueBool('session_relaxed_expiry', false);
458
-	}
459
-
460
-	/**
461
-	 * Try to set some values to the required Nextcloud default
462
-	 */
463
-	public static function setRequiredIniValues(): void {
464
-		// Don't display errors and log them
465
-		@ini_set('display_errors', '0');
466
-		@ini_set('log_errors', '1');
467
-
468
-		// Try to configure php to enable big file uploads.
469
-		// This doesn't work always depending on the webserver and php configuration.
470
-		// Let's try to overwrite some defaults if they are smaller than 1 hour
471
-
472
-		if (intval(@ini_get('max_execution_time') ?: 0) < 3600) {
473
-			@ini_set('max_execution_time', strval(3600));
474
-		}
475
-
476
-		if (intval(@ini_get('max_input_time') ?: 0) < 3600) {
477
-			@ini_set('max_input_time', strval(3600));
478
-		}
479
-
480
-		// Try to set the maximum execution time to the largest time limit we have
481
-		if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
482
-			@set_time_limit(max(intval(@ini_get('max_execution_time')), intval(@ini_get('max_input_time'))));
483
-		}
484
-
485
-		@ini_set('default_charset', 'UTF-8');
486
-		@ini_set('gd.jpeg_ignore_warning', '1');
487
-	}
488
-
489
-	/**
490
-	 * Send the same site cookies
491
-	 */
492
-	private static function sendSameSiteCookies(): void {
493
-		$cookieParams = session_get_cookie_params();
494
-		$secureCookie = ($cookieParams['secure'] === true) ? 'secure; ' : '';
495
-		$policies = [
496
-			'lax',
497
-			'strict',
498
-		];
499
-
500
-		// Append __Host to the cookie if it meets the requirements
501
-		$cookiePrefix = '';
502
-		if ($cookieParams['secure'] === true && $cookieParams['path'] === '/') {
503
-			$cookiePrefix = '__Host-';
504
-		}
505
-
506
-		foreach ($policies as $policy) {
507
-			header(
508
-				sprintf(
509
-					'Set-Cookie: %snc_sameSiteCookie%s=true; path=%s; httponly;' . $secureCookie . 'expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=%s',
510
-					$cookiePrefix,
511
-					$policy,
512
-					$cookieParams['path'],
513
-					$policy
514
-				),
515
-				false
516
-			);
517
-		}
518
-	}
519
-
520
-	/**
521
-	 * Same Site cookie to further mitigate CSRF attacks. This cookie has to
522
-	 * be set in every request if cookies are sent to add a second level of
523
-	 * defense against CSRF.
524
-	 *
525
-	 * If the cookie is not sent this will set the cookie and reload the page.
526
-	 * We use an additional cookie since we want to protect logout CSRF and
527
-	 * also we can't directly interfere with PHP's session mechanism.
528
-	 */
529
-	private static function performSameSiteCookieProtection(IConfig $config): void {
530
-		$request = Server::get(IRequest::class);
531
-
532
-		// Some user agents are notorious and don't really properly follow HTTP
533
-		// specifications. For those, have an automated opt-out. Since the protection
534
-		// for remote.php is applied in base.php as starting point we need to opt out
535
-		// here.
536
-		$incompatibleUserAgents = $config->getSystemValue('csrf.optout');
537
-
538
-		// Fallback, if csrf.optout is unset
539
-		if (!is_array($incompatibleUserAgents)) {
540
-			$incompatibleUserAgents = [
541
-				// OS X Finder
542
-				'/^WebDAVFS/',
543
-				// Windows webdav drive
544
-				'/^Microsoft-WebDAV-MiniRedir/',
545
-			];
546
-		}
547
-
548
-		if ($request->isUserAgent($incompatibleUserAgents)) {
549
-			return;
550
-		}
551
-
552
-		if (count($_COOKIE) > 0) {
553
-			$requestUri = $request->getScriptName();
554
-			$processingScript = explode('/', $requestUri);
555
-			$processingScript = $processingScript[count($processingScript) - 1];
556
-
557
-			if ($processingScript === 'index.php' // index.php routes are handled in the middleware
558
-				|| $processingScript === 'cron.php' // and cron.php does not need any authentication at all
559
-				|| $processingScript === 'public.php' // For public.php, auth for password protected shares is done in the PublicAuth plugin
560
-			) {
561
-				return;
562
-			}
563
-
564
-			// All other endpoints require the lax and the strict cookie
565
-			if (!$request->passesStrictCookieCheck()) {
566
-				logger('core')->warning('Request does not pass strict cookie check');
567
-				self::sendSameSiteCookies();
568
-				// Debug mode gets access to the resources without strict cookie
569
-				// due to the fact that the SabreDAV browser also lives there.
570
-				if (!$config->getSystemValueBool('debug', false)) {
571
-					http_response_code(\OCP\AppFramework\Http::STATUS_PRECONDITION_FAILED);
572
-					header('Content-Type: application/json');
573
-					echo json_encode(['error' => 'Strict Cookie has not been found in request']);
574
-					exit();
575
-				}
576
-			}
577
-		} elseif (!isset($_COOKIE['nc_sameSiteCookielax']) || !isset($_COOKIE['nc_sameSiteCookiestrict'])) {
578
-			self::sendSameSiteCookies();
579
-		}
580
-	}
581
-
582
-	/**
583
-	 * This function adds some security related headers to all requests served via base.php
584
-	 * The implementation of this function has to happen here to ensure that all third-party
585
-	 * components (e.g. SabreDAV) also benefit from this headers.
586
-	 */
587
-	private static function addSecurityHeaders(): void {
588
-		/**
589
-		 * FIXME: Content Security Policy for legacy components. This
590
-		 * can be removed once \OCP\AppFramework\Http\Response from the AppFramework
591
-		 * is used everywhere.
592
-		 * @see \OCP\AppFramework\Http\Response::getHeaders
593
-		 */
594
-		$policy = 'default-src \'self\'; '
595
-			. 'script-src \'self\' \'nonce-' . \OC::$server->getContentSecurityPolicyNonceManager()->getNonce() . '\'; '
596
-			. 'style-src \'self\' \'unsafe-inline\'; '
597
-			. 'frame-src *; '
598
-			. 'img-src * data: blob:; '
599
-			. 'font-src \'self\' data:; '
600
-			. 'media-src *; '
601
-			. 'connect-src *; '
602
-			. 'object-src \'none\'; '
603
-			. 'base-uri \'self\'; ';
604
-		header('Content-Security-Policy:' . $policy);
605
-
606
-		// Send fallback headers for installations that don't have the possibility to send
607
-		// custom headers on the webserver side
608
-		if (getenv('modHeadersAvailable') !== 'true') {
609
-			header('Referrer-Policy: no-referrer'); // https://www.w3.org/TR/referrer-policy/
610
-			header('X-Content-Type-Options: nosniff'); // Disable sniffing the content type for IE
611
-			header('X-Frame-Options: SAMEORIGIN'); // Disallow iFraming from other domains
612
-			header('X-Permitted-Cross-Domain-Policies: none'); // https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html
613
-			header('X-Robots-Tag: noindex, nofollow'); // https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag
614
-		}
615
-	}
616
-
617
-	public static function init(): void {
618
-		// First handle PHP configuration and copy auth headers to the expected
619
-		// $_SERVER variable before doing anything Server object related
620
-		self::setRequiredIniValues();
621
-		self::handleAuthHeaders();
622
-
623
-		// prevent any XML processing from loading external entities
624
-		libxml_set_external_entity_loader(static function () {
625
-			return null;
626
-		});
627
-
628
-		// Set default timezone before the Server object is booted
629
-		if (!date_default_timezone_set('UTC')) {
630
-			throw new \RuntimeException('Could not set timezone to UTC');
631
-		}
632
-
633
-		// calculate the root directories
634
-		OC::$SERVERROOT = str_replace('\\', '/', substr(__DIR__, 0, -4));
635
-
636
-		// register autoloader
637
-		$loaderStart = microtime(true);
638
-
639
-		self::$CLI = (php_sapi_name() == 'cli');
640
-
641
-		// Add default composer PSR-4 autoloader, ensure apcu to be disabled
642
-		self::$composerAutoloader = require_once OC::$SERVERROOT . '/lib/composer/autoload.php';
643
-		self::$composerAutoloader->setApcuPrefix(null);
644
-
645
-
646
-		try {
647
-			self::initPaths();
648
-			// setup 3rdparty autoloader
649
-			$vendorAutoLoad = OC::$SERVERROOT . '/3rdparty/autoload.php';
650
-			if (!file_exists($vendorAutoLoad)) {
651
-				throw new \RuntimeException('Composer autoloader not found, unable to continue. Check the folder "3rdparty". Running "git submodule update --init" will initialize the git submodule that handles the subfolder "3rdparty".');
652
-			}
653
-			require_once $vendorAutoLoad;
654
-		} catch (\RuntimeException $e) {
655
-			if (!self::$CLI) {
656
-				http_response_code(503);
657
-			}
658
-			// we can't use the template error page here, because this needs the
659
-			// DI container which isn't available yet
660
-			print($e->getMessage());
661
-			exit();
662
-		}
663
-		$loaderEnd = microtime(true);
664
-
665
-		// Enable lazy loading if activated
666
-		\OC\AppFramework\Utility\SimpleContainer::$useLazyObjects = (bool)self::$config->getValue('enable_lazy_objects', true);
667
-
668
-		// setup the basic server
669
-		self::$server = new \OC\Server(\OC::$WEBROOT, self::$config);
670
-		self::$server->boot();
671
-
672
-		try {
673
-			$profiler = new BuiltInProfiler(
674
-				Server::get(IConfig::class),
675
-				Server::get(IRequest::class),
676
-			);
677
-			$profiler->start();
678
-		} catch (\Throwable $e) {
679
-			logger('core')->error('Failed to start profiler: ' . $e->getMessage(), ['app' => 'base']);
680
-		}
681
-
682
-		if (self::$CLI && in_array('--' . \OCP\Console\ReservedOptions::DEBUG_LOG, $_SERVER['argv'])) {
683
-			\OC\Core\Listener\BeforeMessageLoggedEventListener::setup();
684
-		}
685
-
686
-		$eventLogger = Server::get(\OCP\Diagnostics\IEventLogger::class);
687
-		$eventLogger->log('autoloader', 'Autoloader', $loaderStart, $loaderEnd);
688
-		$eventLogger->start('boot', 'Initialize');
689
-
690
-		// Override php.ini and log everything if we're troubleshooting
691
-		if (self::$config->getValue('loglevel') === ILogger::DEBUG) {
692
-			error_reporting(E_ALL);
693
-		}
694
-
695
-		// initialize intl fallback if necessary
696
-		OC_Util::isSetLocaleWorking();
697
-
698
-		$config = Server::get(IConfig::class);
699
-		if (!defined('PHPUNIT_RUN')) {
700
-			$errorHandler = new OC\Log\ErrorHandler(
701
-				\OCP\Server::get(\Psr\Log\LoggerInterface::class),
702
-			);
703
-			$exceptionHandler = [$errorHandler, 'onException'];
704
-			if ($config->getSystemValueBool('debug', false)) {
705
-				set_error_handler([$errorHandler, 'onAll'], E_ALL);
706
-				if (\OC::$CLI) {
707
-					$exceptionHandler = [Server::get(ITemplateManager::class), 'printExceptionErrorPage'];
708
-				}
709
-			} else {
710
-				set_error_handler([$errorHandler, 'onError']);
711
-			}
712
-			register_shutdown_function([$errorHandler, 'onShutdown']);
713
-			set_exception_handler($exceptionHandler);
714
-		}
715
-
716
-		/** @var \OC\AppFramework\Bootstrap\Coordinator $bootstrapCoordinator */
717
-		$bootstrapCoordinator = Server::get(\OC\AppFramework\Bootstrap\Coordinator::class);
718
-		$bootstrapCoordinator->runInitialRegistration();
719
-
720
-		$eventLogger->start('init_session', 'Initialize session');
721
-
722
-		// Check for PHP SimpleXML extension earlier since we need it before our other checks and want to provide a useful hint for web users
723
-		// see https://github.com/nextcloud/server/pull/2619
724
-		if (!function_exists('simplexml_load_file')) {
725
-			throw new \OCP\HintException('The PHP SimpleXML/PHP-XML extension is not installed.', 'Install the extension or make sure it is enabled.');
726
-		}
727
-
728
-		$systemConfig = Server::get(\OC\SystemConfig::class);
729
-		$appManager = Server::get(\OCP\App\IAppManager::class);
730
-		if ($systemConfig->getValue('installed', false)) {
731
-			$appManager->loadApps(['session']);
732
-		}
733
-		if (!self::$CLI) {
734
-			self::initSession();
735
-		}
736
-		$eventLogger->end('init_session');
737
-		self::checkConfig();
738
-		self::checkInstalled($systemConfig);
739
-
740
-		self::addSecurityHeaders();
741
-
742
-		self::performSameSiteCookieProtection($config);
743
-
744
-		if (!defined('OC_CONSOLE')) {
745
-			$eventLogger->start('check_server', 'Run a few configuration checks');
746
-			$errors = OC_Util::checkServer($systemConfig);
747
-			if (count($errors) > 0) {
748
-				if (!self::$CLI) {
749
-					http_response_code(503);
750
-					Util::addStyle('guest');
751
-					try {
752
-						Server::get(ITemplateManager::class)->printGuestPage('', 'error', ['errors' => $errors]);
753
-						exit;
754
-					} catch (\Exception $e) {
755
-						// In case any error happens when showing the error page, we simply fall back to posting the text.
756
-						// This might be the case when e.g. the data directory is broken and we can not load/write SCSS to/from it.
757
-					}
758
-				}
759
-
760
-				// Convert l10n string into regular string for usage in database
761
-				$staticErrors = [];
762
-				foreach ($errors as $error) {
763
-					echo $error['error'] . "\n";
764
-					echo $error['hint'] . "\n\n";
765
-					$staticErrors[] = [
766
-						'error' => (string)$error['error'],
767
-						'hint' => (string)$error['hint'],
768
-					];
769
-				}
770
-
771
-				try {
772
-					$config->setAppValue('core', 'cronErrors', json_encode($staticErrors));
773
-				} catch (\Exception $e) {
774
-					echo('Writing to database failed');
775
-				}
776
-				exit(1);
777
-			} elseif (self::$CLI && $config->getSystemValueBool('installed', false)) {
778
-				$config->deleteAppValue('core', 'cronErrors');
779
-			}
780
-			$eventLogger->end('check_server');
781
-		}
782
-
783
-		// User and Groups
784
-		if (!$systemConfig->getValue('installed', false)) {
785
-			self::$server->getSession()->set('user_id', '');
786
-		}
787
-
788
-		$eventLogger->start('setup_backends', 'Setup group and user backends');
789
-		Server::get(\OCP\IUserManager::class)->registerBackend(new \OC\User\Database());
790
-		Server::get(\OCP\IGroupManager::class)->addBackend(new \OC\Group\Database());
791
-
792
-		// Subscribe to the hook
793
-		\OCP\Util::connectHook(
794
-			'\OCA\Files_Sharing\API\Server2Server',
795
-			'preLoginNameUsedAsUserName',
796
-			'\OC\User\Database',
797
-			'preLoginNameUsedAsUserName'
798
-		);
799
-
800
-		//setup extra user backends
801
-		if (!\OCP\Util::needUpgrade()) {
802
-			OC_User::setupBackends();
803
-		} else {
804
-			// Run upgrades in incognito mode
805
-			OC_User::setIncognitoMode(true);
806
-		}
807
-		$eventLogger->end('setup_backends');
808
-
809
-		self::registerCleanupHooks($systemConfig);
810
-		self::registerShareHooks($systemConfig);
811
-		self::registerEncryptionWrapperAndHooks();
812
-		self::registerAccountHooks();
813
-		self::registerResourceCollectionHooks();
814
-		self::registerFileReferenceEventListener();
815
-		self::registerRenderReferenceEventListener();
816
-		self::registerAppRestrictionsHooks();
817
-
818
-		// Make sure that the application class is not loaded before the database is setup
819
-		if ($systemConfig->getValue('installed', false)) {
820
-			$appManager->loadApp('settings');
821
-		}
822
-
823
-		//make sure temporary files are cleaned up
824
-		$tmpManager = Server::get(\OCP\ITempManager::class);
825
-		register_shutdown_function([$tmpManager, 'clean']);
826
-		$lockProvider = Server::get(\OCP\Lock\ILockingProvider::class);
827
-		register_shutdown_function([$lockProvider, 'releaseAll']);
828
-
829
-		// Check whether the sample configuration has been copied
830
-		if ($systemConfig->getValue('copied_sample_config', false)) {
831
-			$l = Server::get(\OCP\L10N\IFactory::class)->get('lib');
832
-			Server::get(ITemplateManager::class)->printErrorPage(
833
-				$l->t('Sample configuration detected'),
834
-				$l->t('It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php'),
835
-				503
836
-			);
837
-			return;
838
-		}
839
-
840
-		$request = Server::get(IRequest::class);
841
-		$host = $request->getInsecureServerHost();
842
-		/**
843
-		 * if the host passed in headers isn't trusted
844
-		 * FIXME: Should not be in here at all :see_no_evil:
845
-		 */
846
-		if (!OC::$CLI
847
-			&& !Server::get(\OC\Security\TrustedDomainHelper::class)->isTrustedDomain($host)
848
-			&& $config->getSystemValueBool('installed', false)
849
-		) {
850
-			// Allow access to CSS resources
851
-			$isScssRequest = false;
852
-			if (strpos($request->getPathInfo() ?: '', '/css/') === 0) {
853
-				$isScssRequest = true;
854
-			}
855
-
856
-			if (substr($request->getRequestUri(), -11) === '/status.php') {
857
-				http_response_code(400);
858
-				header('Content-Type: application/json');
859
-				echo '{"error": "Trusted domain error.", "code": 15}';
860
-				exit();
861
-			}
862
-
863
-			if (!$isScssRequest) {
864
-				http_response_code(400);
865
-				Server::get(LoggerInterface::class)->info(
866
-					'Trusted domain error. "{remoteAddress}" tried to access using "{host}" as host.',
867
-					[
868
-						'app' => 'core',
869
-						'remoteAddress' => $request->getRemoteAddress(),
870
-						'host' => $host,
871
-					]
872
-				);
873
-
874
-				$tmpl = Server::get(ITemplateManager::class)->getTemplate('core', 'untrustedDomain', 'guest');
875
-				$tmpl->assign('docUrl', Server::get(IURLGenerator::class)->linkToDocs('admin-trusted-domains'));
876
-				$tmpl->printPage();
877
-
878
-				exit();
879
-			}
880
-		}
881
-		$eventLogger->end('boot');
882
-		$eventLogger->log('init', 'OC::init', $loaderStart, microtime(true));
883
-		$eventLogger->start('runtime', 'Runtime');
884
-		$eventLogger->start('request', 'Full request after boot');
885
-		register_shutdown_function(function () use ($eventLogger) {
886
-			$eventLogger->end('request');
887
-		});
888
-
889
-		register_shutdown_function(function () {
890
-			$memoryPeak = memory_get_peak_usage();
891
-			$logLevel = match (true) {
892
-				$memoryPeak > 500_000_000 => ILogger::FATAL,
893
-				$memoryPeak > 400_000_000 => ILogger::ERROR,
894
-				$memoryPeak > 300_000_000 => ILogger::WARN,
895
-				default => null,
896
-			};
897
-			if ($logLevel !== null) {
898
-				$message = 'Request used more than 300 MB of RAM: ' . Util::humanFileSize($memoryPeak);
899
-				$logger = Server::get(LoggerInterface::class);
900
-				$logger->log($logLevel, $message, ['app' => 'core']);
901
-			}
902
-		});
903
-	}
904
-
905
-	/**
906
-	 * register hooks for the cleanup of cache and bruteforce protection
907
-	 */
908
-	public static function registerCleanupHooks(\OC\SystemConfig $systemConfig): void {
909
-		//don't try to do this before we are properly setup
910
-		if ($systemConfig->getValue('installed', false) && !\OCP\Util::needUpgrade()) {
911
-			// NOTE: This will be replaced to use OCP
912
-			$userSession = Server::get(\OC\User\Session::class);
913
-			$userSession->listen('\OC\User', 'postLogin', function () use ($userSession) {
914
-				if (!defined('PHPUNIT_RUN') && $userSession->isLoggedIn()) {
915
-					// reset brute force delay for this IP address and username
916
-					$uid = $userSession->getUser()->getUID();
917
-					$request = Server::get(IRequest::class);
918
-					$throttler = Server::get(IThrottler::class);
919
-					$throttler->resetDelay($request->getRemoteAddress(), 'login', ['user' => $uid]);
920
-				}
921
-
922
-				try {
923
-					$cache = new \OC\Cache\File();
924
-					$cache->gc();
925
-				} catch (\OC\ServerNotAvailableException $e) {
926
-					// not a GC exception, pass it on
927
-					throw $e;
928
-				} catch (\OC\ForbiddenException $e) {
929
-					// filesystem blocked for this request, ignore
930
-				} catch (\Exception $e) {
931
-					// a GC exception should not prevent users from using OC,
932
-					// so log the exception
933
-					Server::get(LoggerInterface::class)->warning('Exception when running cache gc.', [
934
-						'app' => 'core',
935
-						'exception' => $e,
936
-					]);
937
-				}
938
-			});
939
-		}
940
-	}
941
-
942
-	private static function registerEncryptionWrapperAndHooks(): void {
943
-		/** @var \OC\Encryption\Manager */
944
-		$manager = Server::get(\OCP\Encryption\IManager::class);
945
-		Server::get(IEventDispatcher::class)->addListener(
946
-			BeforeFileSystemSetupEvent::class,
947
-			$manager->setupStorage(...),
948
-		);
949
-
950
-		$enabled = $manager->isEnabled();
951
-		if ($enabled) {
952
-			\OC\Encryption\EncryptionEventListener::register(Server::get(IEventDispatcher::class));
953
-		}
954
-	}
955
-
956
-	private static function registerAccountHooks(): void {
957
-		/** @var IEventDispatcher $dispatcher */
958
-		$dispatcher = Server::get(IEventDispatcher::class);
959
-		$dispatcher->addServiceListener(UserChangedEvent::class, \OC\Accounts\Hooks::class);
960
-	}
961
-
962
-	private static function registerAppRestrictionsHooks(): void {
963
-		/** @var \OC\Group\Manager $groupManager */
964
-		$groupManager = Server::get(\OCP\IGroupManager::class);
965
-		$groupManager->listen('\OC\Group', 'postDelete', function (\OCP\IGroup $group) {
966
-			$appManager = Server::get(\OCP\App\IAppManager::class);
967
-			$apps = $appManager->getEnabledAppsForGroup($group);
968
-			foreach ($apps as $appId) {
969
-				$restrictions = $appManager->getAppRestriction($appId);
970
-				if (empty($restrictions)) {
971
-					continue;
972
-				}
973
-				$key = array_search($group->getGID(), $restrictions);
974
-				unset($restrictions[$key]);
975
-				$restrictions = array_values($restrictions);
976
-				if (empty($restrictions)) {
977
-					$appManager->disableApp($appId);
978
-				} else {
979
-					$appManager->enableAppForGroups($appId, $restrictions);
980
-				}
981
-			}
982
-		});
983
-	}
984
-
985
-	private static function registerResourceCollectionHooks(): void {
986
-		\OC\Collaboration\Resources\Listener::register(Server::get(IEventDispatcher::class));
987
-	}
988
-
989
-	private static function registerFileReferenceEventListener(): void {
990
-		\OC\Collaboration\Reference\File\FileReferenceEventListener::register(Server::get(IEventDispatcher::class));
991
-	}
992
-
993
-	private static function registerRenderReferenceEventListener() {
994
-		\OC\Collaboration\Reference\RenderReferenceEventListener::register(Server::get(IEventDispatcher::class));
995
-	}
996
-
997
-	/**
998
-	 * register hooks for sharing
999
-	 */
1000
-	public static function registerShareHooks(\OC\SystemConfig $systemConfig): void {
1001
-		if ($systemConfig->getValue('installed')) {
1002
-
1003
-			$dispatcher = Server::get(IEventDispatcher::class);
1004
-			$dispatcher->addServiceListener(UserRemovedEvent::class, UserRemovedListener::class);
1005
-			$dispatcher->addServiceListener(GroupDeletedEvent::class, GroupDeletedListener::class);
1006
-			$dispatcher->addServiceListener(UserDeletedEvent::class, UserDeletedListener::class);
1007
-		}
1008
-	}
1009
-
1010
-	/**
1011
-	 * Handle the request
1012
-	 */
1013
-	public static function handleRequest(): void {
1014
-		Server::get(\OCP\Diagnostics\IEventLogger::class)->start('handle_request', 'Handle request');
1015
-		$systemConfig = Server::get(\OC\SystemConfig::class);
1016
-
1017
-		// Check if Nextcloud is installed or in maintenance (update) mode
1018
-		if (!$systemConfig->getValue('installed', false)) {
1019
-			\OC::$server->getSession()->clear();
1020
-			$controller = Server::get(\OC\Core\Controller\SetupController::class);
1021
-			$controller->run($_POST);
1022
-			exit();
1023
-		}
1024
-
1025
-		$request = Server::get(IRequest::class);
1026
-		$request->throwDecodingExceptionIfAny();
1027
-		$requestPath = $request->getRawPathInfo();
1028
-		if ($requestPath === '/heartbeat') {
1029
-			return;
1030
-		}
1031
-		if (substr($requestPath, -3) !== '.js') { // we need these files during the upgrade
1032
-			self::checkMaintenanceMode($systemConfig);
1033
-
1034
-			if (\OCP\Util::needUpgrade()) {
1035
-				if (function_exists('opcache_reset')) {
1036
-					opcache_reset();
1037
-				}
1038
-				if (!((bool)$systemConfig->getValue('maintenance', false))) {
1039
-					self::printUpgradePage($systemConfig);
1040
-					exit();
1041
-				}
1042
-			}
1043
-		}
1044
-
1045
-		$appManager = Server::get(\OCP\App\IAppManager::class);
1046
-
1047
-		// Always load authentication apps
1048
-		$appManager->loadApps(['authentication']);
1049
-		$appManager->loadApps(['extended_authentication']);
1050
-
1051
-		// Load minimum set of apps
1052
-		if (!\OCP\Util::needUpgrade()
1053
-			&& !((bool)$systemConfig->getValue('maintenance', false))) {
1054
-			// For logged-in users: Load everything
1055
-			if (Server::get(IUserSession::class)->isLoggedIn()) {
1056
-				$appManager->loadApps();
1057
-			} else {
1058
-				// For guests: Load only filesystem and logging
1059
-				$appManager->loadApps(['filesystem', 'logging']);
1060
-
1061
-				// Don't try to login when a client is trying to get a OAuth token.
1062
-				// OAuth needs to support basic auth too, so the login is not valid
1063
-				// inside Nextcloud and the Login exception would ruin it.
1064
-				if ($request->getRawPathInfo() !== '/apps/oauth2/api/v1/token') {
1065
-					try {
1066
-						self::handleLogin($request);
1067
-					} catch (DisabledUserException $e) {
1068
-						// Disabled users would not be seen as logged in and
1069
-						// trying to log them in would fail, so the login
1070
-						// exception is ignored for the themed stylesheets and
1071
-						// images.
1072
-						if ($request->getRawPathInfo() !== '/apps/theming/theme/default.css'
1073
-							&& $request->getRawPathInfo() !== '/apps/theming/theme/light.css'
1074
-							&& $request->getRawPathInfo() !== '/apps/theming/theme/dark.css'
1075
-							&& $request->getRawPathInfo() !== '/apps/theming/theme/light-highcontrast.css'
1076
-							&& $request->getRawPathInfo() !== '/apps/theming/theme/dark-highcontrast.css'
1077
-							&& $request->getRawPathInfo() !== '/apps/theming/theme/opendyslexic.css'
1078
-							&& $request->getRawPathInfo() !== '/apps/theming/image/background'
1079
-							&& $request->getRawPathInfo() !== '/apps/theming/image/logo'
1080
-							&& $request->getRawPathInfo() !== '/apps/theming/image/logoheader'
1081
-							&& !str_starts_with($request->getRawPathInfo(), '/apps/theming/favicon')
1082
-							&& !str_starts_with($request->getRawPathInfo(), '/apps/theming/icon')) {
1083
-							throw $e;
1084
-						}
1085
-					}
1086
-				}
1087
-			}
1088
-		}
1089
-
1090
-		if (!self::$CLI) {
1091
-			try {
1092
-				if (!\OCP\Util::needUpgrade()) {
1093
-					$appManager->loadApps(['filesystem', 'logging']);
1094
-					$appManager->loadApps();
1095
-				}
1096
-				Server::get(\OC\Route\Router::class)->match($request->getRawPathInfo());
1097
-				return;
1098
-			} catch (Symfony\Component\Routing\Exception\ResourceNotFoundException $e) {
1099
-				//header('HTTP/1.0 404 Not Found');
1100
-			} catch (Symfony\Component\Routing\Exception\MethodNotAllowedException $e) {
1101
-				http_response_code(405);
1102
-				return;
1103
-			}
1104
-		}
1105
-
1106
-		// Handle WebDAV
1107
-		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
1108
-			// not allowed any more to prevent people
1109
-			// mounting this root directly.
1110
-			// Users need to mount remote.php/webdav instead.
1111
-			http_response_code(405);
1112
-			return;
1113
-		}
1114
-
1115
-		// Handle requests for JSON or XML
1116
-		$acceptHeader = $request->getHeader('Accept');
1117
-		if (in_array($acceptHeader, ['application/json', 'application/xml'], true)) {
1118
-			http_response_code(404);
1119
-			return;
1120
-		}
1121
-
1122
-		// Handle resources that can't be found
1123
-		// This prevents browsers from redirecting to the default page and then
1124
-		// attempting to parse HTML as CSS and similar.
1125
-		$destinationHeader = $request->getHeader('Sec-Fetch-Dest');
1126
-		if (in_array($destinationHeader, ['font', 'script', 'style'])) {
1127
-			http_response_code(404);
1128
-			return;
1129
-		}
1130
-
1131
-		// Redirect to the default app or login only as an entry point
1132
-		if ($requestPath === '') {
1133
-			// Someone is logged in
1134
-			if (Server::get(IUserSession::class)->isLoggedIn()) {
1135
-				header('Location: ' . Server::get(IURLGenerator::class)->linkToDefaultPageUrl());
1136
-			} else {
1137
-				// Not handled and not logged in
1138
-				header('Location: ' . Server::get(IURLGenerator::class)->linkToRouteAbsolute('core.login.showLoginForm'));
1139
-			}
1140
-			return;
1141
-		}
1142
-
1143
-		try {
1144
-			Server::get(\OC\Route\Router::class)->match('/error/404');
1145
-		} catch (\Exception $e) {
1146
-			if (!$e instanceof MethodNotAllowedException) {
1147
-				logger('core')->emergency($e->getMessage(), ['exception' => $e]);
1148
-			}
1149
-			$l = Server::get(\OCP\L10N\IFactory::class)->get('lib');
1150
-			Server::get(ITemplateManager::class)->printErrorPage(
1151
-				'404',
1152
-				$l->t('The page could not be found on the server.'),
1153
-				404
1154
-			);
1155
-		}
1156
-	}
1157
-
1158
-	/**
1159
-	 * Check login: apache auth, auth token, basic auth
1160
-	 */
1161
-	public static function handleLogin(OCP\IRequest $request): bool {
1162
-		if ($request->getHeader('X-Nextcloud-Federation')) {
1163
-			return false;
1164
-		}
1165
-		$userSession = Server::get(\OC\User\Session::class);
1166
-		if (OC_User::handleApacheAuth()) {
1167
-			return true;
1168
-		}
1169
-		if (self::tryAppAPILogin($request)) {
1170
-			return true;
1171
-		}
1172
-		if ($userSession->tryTokenLogin($request)) {
1173
-			return true;
1174
-		}
1175
-		if (isset($_COOKIE['nc_username'])
1176
-			&& isset($_COOKIE['nc_token'])
1177
-			&& isset($_COOKIE['nc_session_id'])
1178
-			&& $userSession->loginWithCookie($_COOKIE['nc_username'], $_COOKIE['nc_token'], $_COOKIE['nc_session_id'])) {
1179
-			return true;
1180
-		}
1181
-		if ($userSession->tryBasicAuthLogin($request, Server::get(IThrottler::class))) {
1182
-			return true;
1183
-		}
1184
-		return false;
1185
-	}
1186
-
1187
-	protected static function handleAuthHeaders(): void {
1188
-		//copy http auth headers for apache+php-fcgid work around
1189
-		if (isset($_SERVER['HTTP_XAUTHORIZATION']) && !isset($_SERVER['HTTP_AUTHORIZATION'])) {
1190
-			$_SERVER['HTTP_AUTHORIZATION'] = $_SERVER['HTTP_XAUTHORIZATION'];
1191
-		}
1192
-
1193
-		// Extract PHP_AUTH_USER/PHP_AUTH_PW from other headers if necessary.
1194
-		$vars = [
1195
-			'HTTP_AUTHORIZATION', // apache+php-cgi work around
1196
-			'REDIRECT_HTTP_AUTHORIZATION', // apache+php-cgi alternative
1197
-		];
1198
-		foreach ($vars as $var) {
1199
-			if (isset($_SERVER[$var]) && is_string($_SERVER[$var]) && preg_match('/Basic\s+(.*)$/i', $_SERVER[$var], $matches)) {
1200
-				$credentials = explode(':', base64_decode($matches[1]), 2);
1201
-				if (count($credentials) === 2) {
1202
-					$_SERVER['PHP_AUTH_USER'] = $credentials[0];
1203
-					$_SERVER['PHP_AUTH_PW'] = $credentials[1];
1204
-					break;
1205
-				}
1206
-			}
1207
-		}
1208
-	}
1209
-
1210
-	protected static function tryAppAPILogin(OCP\IRequest $request): bool {
1211
-		if (!$request->getHeader('AUTHORIZATION-APP-API')) {
1212
-			return false;
1213
-		}
1214
-		$appManager = Server::get(OCP\App\IAppManager::class);
1215
-		if (!$appManager->isEnabledForAnyone('app_api')) {
1216
-			return false;
1217
-		}
1218
-		try {
1219
-			$appAPIService = Server::get(OCA\AppAPI\Service\AppAPIService::class);
1220
-			return $appAPIService->validateExAppRequestToNC($request);
1221
-		} catch (\Psr\Container\NotFoundExceptionInterface|\Psr\Container\ContainerExceptionInterface $e) {
1222
-			return false;
1223
-		}
1224
-	}
43
+    /**
44
+     * The installation path for Nextcloud  on the server (e.g. /srv/http/nextcloud)
45
+     */
46
+    public static string $SERVERROOT = '';
47
+    /**
48
+     * the current request path relative to the Nextcloud root (e.g. files/index.php)
49
+     */
50
+    private static string $SUBURI = '';
51
+    /**
52
+     * the Nextcloud root path for http requests (e.g. /nextcloud)
53
+     */
54
+    public static string $WEBROOT = '';
55
+    /**
56
+     * The installation path array of the apps folder on the server (e.g. /srv/http/nextcloud) 'path' and
57
+     * web path in 'url'
58
+     */
59
+    public static array $APPSROOTS = [];
60
+
61
+    public static string $configDir;
62
+
63
+    /**
64
+     * requested app
65
+     */
66
+    public static string $REQUESTEDAPP = '';
67
+
68
+    /**
69
+     * check if Nextcloud runs in cli mode
70
+     */
71
+    public static bool $CLI = false;
72
+
73
+    public static \Composer\Autoload\ClassLoader $composerAutoloader;
74
+
75
+    public static \OC\Server $server;
76
+
77
+    private static \OC\Config $config;
78
+
79
+    /**
80
+     * @throws \RuntimeException when the 3rdparty directory is missing or
81
+     *                           the app path list is empty or contains an invalid path
82
+     */
83
+    public static function initPaths(): void {
84
+        if (defined('PHPUNIT_CONFIG_DIR')) {
85
+            self::$configDir = OC::$SERVERROOT . '/' . PHPUNIT_CONFIG_DIR . '/';
86
+        } elseif (defined('PHPUNIT_RUN') && PHPUNIT_RUN && is_dir(OC::$SERVERROOT . '/tests/config/')) {
87
+            self::$configDir = OC::$SERVERROOT . '/tests/config/';
88
+        } elseif ($dir = getenv('NEXTCLOUD_CONFIG_DIR')) {
89
+            self::$configDir = rtrim($dir, '/') . '/';
90
+        } else {
91
+            self::$configDir = OC::$SERVERROOT . '/config/';
92
+        }
93
+        self::$config = new \OC\Config(self::$configDir);
94
+
95
+        OC::$SUBURI = str_replace('\\', '/', substr(realpath($_SERVER['SCRIPT_FILENAME'] ?? ''), strlen(OC::$SERVERROOT)));
96
+        /**
97
+         * FIXME: The following lines are required because we can't yet instantiate
98
+         *        Server::get(\OCP\IRequest::class) since \OC::$server does not yet exist.
99
+         */
100
+        $params = [
101
+            'server' => [
102
+                'SCRIPT_NAME' => $_SERVER['SCRIPT_NAME'] ?? null,
103
+                'SCRIPT_FILENAME' => $_SERVER['SCRIPT_FILENAME'] ?? null,
104
+            ],
105
+        ];
106
+        if (isset($_SERVER['REMOTE_ADDR'])) {
107
+            $params['server']['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
108
+        }
109
+        $fakeRequest = new \OC\AppFramework\Http\Request(
110
+            $params,
111
+            new \OC\AppFramework\Http\RequestId($_SERVER['UNIQUE_ID'] ?? '', new \OC\Security\SecureRandom()),
112
+            new \OC\AllConfig(new \OC\SystemConfig(self::$config))
113
+        );
114
+        $scriptName = $fakeRequest->getScriptName();
115
+        if (substr($scriptName, -1) == '/') {
116
+            $scriptName .= 'index.php';
117
+            //make sure suburi follows the same rules as scriptName
118
+            if (substr(OC::$SUBURI, -9) != 'index.php') {
119
+                if (substr(OC::$SUBURI, -1) != '/') {
120
+                    OC::$SUBURI = OC::$SUBURI . '/';
121
+                }
122
+                OC::$SUBURI = OC::$SUBURI . 'index.php';
123
+            }
124
+        }
125
+
126
+        if (OC::$CLI) {
127
+            OC::$WEBROOT = self::$config->getValue('overwritewebroot', '');
128
+        } else {
129
+            if (substr($scriptName, 0 - strlen(OC::$SUBURI)) === OC::$SUBURI) {
130
+                OC::$WEBROOT = substr($scriptName, 0, 0 - strlen(OC::$SUBURI));
131
+
132
+                if (OC::$WEBROOT != '' && OC::$WEBROOT[0] !== '/') {
133
+                    OC::$WEBROOT = '/' . OC::$WEBROOT;
134
+                }
135
+            } else {
136
+                // The scriptName is not ending with OC::$SUBURI
137
+                // This most likely means that we are calling from CLI.
138
+                // However some cron jobs still need to generate
139
+                // a web URL, so we use overwritewebroot as a fallback.
140
+                OC::$WEBROOT = self::$config->getValue('overwritewebroot', '');
141
+            }
142
+
143
+            // Resolve /nextcloud to /nextcloud/ to ensure to always have a trailing
144
+            // slash which is required by URL generation.
145
+            if (isset($_SERVER['REQUEST_URI']) && $_SERVER['REQUEST_URI'] === \OC::$WEBROOT
146
+                    && substr($_SERVER['REQUEST_URI'], -1) !== '/') {
147
+                header('Location: ' . \OC::$WEBROOT . '/');
148
+                exit();
149
+            }
150
+        }
151
+
152
+        // search the apps folder
153
+        $config_paths = self::$config->getValue('apps_paths', []);
154
+        if (!empty($config_paths)) {
155
+            foreach ($config_paths as $paths) {
156
+                if (isset($paths['url']) && isset($paths['path'])) {
157
+                    $paths['url'] = rtrim($paths['url'], '/');
158
+                    $paths['path'] = rtrim($paths['path'], '/');
159
+                    OC::$APPSROOTS[] = $paths;
160
+                }
161
+            }
162
+        } elseif (file_exists(OC::$SERVERROOT . '/apps')) {
163
+            OC::$APPSROOTS[] = ['path' => OC::$SERVERROOT . '/apps', 'url' => '/apps', 'writable' => true];
164
+        }
165
+
166
+        if (empty(OC::$APPSROOTS)) {
167
+            throw new \RuntimeException('apps directory not found! Please put the Nextcloud apps folder in the Nextcloud folder'
168
+                . '. You can also configure the location in the config.php file.');
169
+        }
170
+        $paths = [];
171
+        foreach (OC::$APPSROOTS as $path) {
172
+            $paths[] = $path['path'];
173
+            if (!is_dir($path['path'])) {
174
+                throw new \RuntimeException(sprintf('App directory "%s" not found! Please put the Nextcloud apps folder in the'
175
+                    . ' Nextcloud folder. You can also configure the location in the config.php file.', $path['path']));
176
+            }
177
+        }
178
+
179
+        // set the right include path
180
+        set_include_path(
181
+            implode(PATH_SEPARATOR, $paths)
182
+        );
183
+    }
184
+
185
+    public static function checkConfig(): void {
186
+        // Create config if it does not already exist
187
+        $configFilePath = self::$configDir . '/config.php';
188
+        if (!file_exists($configFilePath)) {
189
+            @touch($configFilePath);
190
+        }
191
+
192
+        // Check if config is writable
193
+        $configFileWritable = is_writable($configFilePath);
194
+        $configReadOnly = Server::get(IConfig::class)->getSystemValueBool('config_is_read_only');
195
+        if (!$configFileWritable && !$configReadOnly
196
+            || !$configFileWritable && \OCP\Util::needUpgrade()) {
197
+            $urlGenerator = Server::get(IURLGenerator::class);
198
+            $l = Server::get(\OCP\L10N\IFactory::class)->get('lib');
199
+
200
+            if (self::$CLI) {
201
+                echo $l->t('Cannot write into "config" directory!') . "\n";
202
+                echo $l->t('This can usually be fixed by giving the web server write access to the config directory.') . "\n";
203
+                echo "\n";
204
+                echo $l->t('But, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it.') . "\n";
205
+                echo $l->t('See %s', [ $urlGenerator->linkToDocs('admin-config') ]) . "\n";
206
+                exit;
207
+            } else {
208
+                Server::get(ITemplateManager::class)->printErrorPage(
209
+                    $l->t('Cannot write into "config" directory!'),
210
+                    $l->t('This can usually be fixed by giving the web server write access to the config directory.') . ' '
211
+                    . $l->t('But, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it.') . ' '
212
+                    . $l->t('See %s', [ $urlGenerator->linkToDocs('admin-config') ]),
213
+                    503
214
+                );
215
+            }
216
+        }
217
+    }
218
+
219
+    public static function checkInstalled(\OC\SystemConfig $systemConfig): void {
220
+        if (defined('OC_CONSOLE')) {
221
+            return;
222
+        }
223
+        // Redirect to installer if not installed
224
+        if (!$systemConfig->getValue('installed', false) && OC::$SUBURI !== '/index.php' && OC::$SUBURI !== '/status.php') {
225
+            if (OC::$CLI) {
226
+                throw new Exception('Not installed');
227
+            } else {
228
+                $url = OC::$WEBROOT . '/index.php';
229
+                header('Location: ' . $url);
230
+            }
231
+            exit();
232
+        }
233
+    }
234
+
235
+    public static function checkMaintenanceMode(\OC\SystemConfig $systemConfig): void {
236
+        // Allow ajax update script to execute without being stopped
237
+        if (((bool)$systemConfig->getValue('maintenance', false)) && OC::$SUBURI != '/core/ajax/update.php') {
238
+            // send http status 503
239
+            http_response_code(503);
240
+            header('X-Nextcloud-Maintenance-Mode: 1');
241
+            header('Retry-After: 120');
242
+
243
+            // render error page
244
+            $template = Server::get(ITemplateManager::class)->getTemplate('', 'update.user', 'guest');
245
+            \OCP\Util::addScript('core', 'maintenance');
246
+            \OCP\Util::addScript('core', 'common');
247
+            \OCP\Util::addStyle('core', 'guest');
248
+            $template->printPage();
249
+            die();
250
+        }
251
+    }
252
+
253
+    /**
254
+     * Prints the upgrade page
255
+     */
256
+    private static function printUpgradePage(\OC\SystemConfig $systemConfig): void {
257
+        $cliUpgradeLink = $systemConfig->getValue('upgrade.cli-upgrade-link', '');
258
+        $disableWebUpdater = $systemConfig->getValue('upgrade.disable-web', false);
259
+        $tooBig = false;
260
+        if (!$disableWebUpdater) {
261
+            $apps = Server::get(\OCP\App\IAppManager::class);
262
+            if ($apps->isEnabledForAnyone('user_ldap')) {
263
+                $qb = Server::get(\OCP\IDBConnection::class)->getQueryBuilder();
264
+
265
+                $result = $qb->select($qb->func()->count('*', 'user_count'))
266
+                    ->from('ldap_user_mapping')
267
+                    ->executeQuery();
268
+                $row = $result->fetch();
269
+                $result->closeCursor();
270
+
271
+                $tooBig = ($row['user_count'] > 50);
272
+            }
273
+            if (!$tooBig && $apps->isEnabledForAnyone('user_saml')) {
274
+                $qb = Server::get(\OCP\IDBConnection::class)->getQueryBuilder();
275
+
276
+                $result = $qb->select($qb->func()->count('*', 'user_count'))
277
+                    ->from('user_saml_users')
278
+                    ->executeQuery();
279
+                $row = $result->fetch();
280
+                $result->closeCursor();
281
+
282
+                $tooBig = ($row['user_count'] > 50);
283
+            }
284
+            if (!$tooBig) {
285
+                // count users
286
+                $totalUsers = Server::get(\OCP\IUserManager::class)->countUsersTotal(51);
287
+                $tooBig = ($totalUsers > 50);
288
+            }
289
+        }
290
+        $ignoreTooBigWarning = isset($_GET['IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup'])
291
+            && $_GET['IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup'] === 'IAmSuperSureToDoThis';
292
+
293
+        if ($disableWebUpdater || ($tooBig && !$ignoreTooBigWarning)) {
294
+            // send http status 503
295
+            http_response_code(503);
296
+            header('Retry-After: 120');
297
+
298
+            $serverVersion = \OCP\Server::get(\OCP\ServerVersion::class);
299
+
300
+            // render error page
301
+            $template = Server::get(ITemplateManager::class)->getTemplate('', 'update.use-cli', 'guest');
302
+            $template->assign('productName', 'nextcloud'); // for now
303
+            $template->assign('version', $serverVersion->getVersionString());
304
+            $template->assign('tooBig', $tooBig);
305
+            $template->assign('cliUpgradeLink', $cliUpgradeLink);
306
+
307
+            $template->printPage();
308
+            die();
309
+        }
310
+
311
+        // check whether this is a core update or apps update
312
+        $installedVersion = $systemConfig->getValue('version', '0.0.0');
313
+        $currentVersion = implode('.', \OCP\Util::getVersion());
314
+
315
+        // if not a core upgrade, then it's apps upgrade
316
+        $isAppsOnlyUpgrade = version_compare($currentVersion, $installedVersion, '=');
317
+
318
+        $oldTheme = $systemConfig->getValue('theme');
319
+        $systemConfig->setValue('theme', '');
320
+        \OCP\Util::addScript('core', 'common');
321
+        \OCP\Util::addScript('core', 'main');
322
+        \OCP\Util::addTranslations('core');
323
+        \OCP\Util::addScript('core', 'update');
324
+
325
+        /** @var \OC\App\AppManager $appManager */
326
+        $appManager = Server::get(\OCP\App\IAppManager::class);
327
+
328
+        $tmpl = Server::get(ITemplateManager::class)->getTemplate('', 'update.admin', 'guest');
329
+        $tmpl->assign('version', \OCP\Server::get(\OCP\ServerVersion::class)->getVersionString());
330
+        $tmpl->assign('isAppsOnlyUpgrade', $isAppsOnlyUpgrade);
331
+
332
+        // get third party apps
333
+        $ocVersion = \OCP\Util::getVersion();
334
+        $ocVersion = implode('.', $ocVersion);
335
+        $incompatibleApps = $appManager->getIncompatibleApps($ocVersion);
336
+        $incompatibleOverwrites = $systemConfig->getValue('app_install_overwrite', []);
337
+        $incompatibleShippedApps = [];
338
+        $incompatibleDisabledApps = [];
339
+        foreach ($incompatibleApps as $appInfo) {
340
+            if ($appManager->isShipped($appInfo['id'])) {
341
+                $incompatibleShippedApps[] = $appInfo['name'] . ' (' . $appInfo['id'] . ')';
342
+            }
343
+            if (!in_array($appInfo['id'], $incompatibleOverwrites)) {
344
+                $incompatibleDisabledApps[] = $appInfo;
345
+            }
346
+        }
347
+
348
+        if (!empty($incompatibleShippedApps)) {
349
+            $l = Server::get(\OCP\L10N\IFactory::class)->get('core');
350
+            $hint = $l->t('Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory.', [implode(', ', $incompatibleShippedApps)]);
351
+            throw new \OCP\HintException('Application ' . implode(', ', $incompatibleShippedApps) . ' is not present or has a non-compatible version with this server. Please check the apps directory.', $hint);
352
+        }
353
+
354
+        $tmpl->assign('appsToUpgrade', $appManager->getAppsNeedingUpgrade($ocVersion));
355
+        $tmpl->assign('incompatibleAppsList', $incompatibleDisabledApps);
356
+        try {
357
+            $defaults = new \OC_Defaults();
358
+            $tmpl->assign('productName', $defaults->getName());
359
+        } catch (Throwable $error) {
360
+            $tmpl->assign('productName', 'Nextcloud');
361
+        }
362
+        $tmpl->assign('oldTheme', $oldTheme);
363
+        $tmpl->printPage();
364
+    }
365
+
366
+    public static function initSession(): void {
367
+        $request = Server::get(IRequest::class);
368
+
369
+        // TODO: Temporary disabled again to solve issues with CalDAV/CardDAV clients like DAVx5 that use cookies
370
+        // TODO: See https://github.com/nextcloud/server/issues/37277#issuecomment-1476366147 and the other comments
371
+        // TODO: for further information.
372
+        // $isDavRequest = strpos($request->getRequestUri(), '/remote.php/dav') === 0 || strpos($request->getRequestUri(), '/remote.php/webdav') === 0;
373
+        // if ($request->getHeader('Authorization') !== '' && is_null($request->getCookie('cookie_test')) && $isDavRequest && !isset($_COOKIE['nc_session_id'])) {
374
+        // setcookie('cookie_test', 'test', time() + 3600);
375
+        // // Do not initialize the session if a request is authenticated directly
376
+        // // unless there is a session cookie already sent along
377
+        // return;
378
+        // }
379
+
380
+        if ($request->getServerProtocol() === 'https') {
381
+            ini_set('session.cookie_secure', 'true');
382
+        }
383
+
384
+        // prevents javascript from accessing php session cookies
385
+        ini_set('session.cookie_httponly', 'true');
386
+
387
+        // set the cookie path to the Nextcloud directory
388
+        $cookie_path = OC::$WEBROOT ? : '/';
389
+        ini_set('session.cookie_path', $cookie_path);
390
+
391
+        // set the cookie domain to the Nextcloud domain
392
+        $cookie_domain = self::$config->getValue('cookie_domain', '');
393
+        if ($cookie_domain) {
394
+            ini_set('session.cookie_domain', $cookie_domain);
395
+        }
396
+
397
+        // Do not initialize sessions for 'status.php' requests
398
+        // Monitoring endpoints can quickly flood session handlers
399
+        // and 'status.php' doesn't require sessions anyway
400
+        // We still need to run the ini_set above so that same-site cookies use the correct configuration.
401
+        if (str_ends_with($request->getScriptName(), '/status.php')) {
402
+            return;
403
+        }
404
+
405
+        // Let the session name be changed in the initSession Hook
406
+        $sessionName = OC_Util::getInstanceId();
407
+
408
+        try {
409
+            $logger = null;
410
+            if (Server::get(\OC\SystemConfig::class)->getValue('installed', false)) {
411
+                $logger = logger('core');
412
+            }
413
+
414
+            // set the session name to the instance id - which is unique
415
+            $session = new \OC\Session\Internal(
416
+                $sessionName,
417
+                $logger,
418
+            );
419
+
420
+            $cryptoWrapper = Server::get(\OC\Session\CryptoWrapper::class);
421
+            $session = $cryptoWrapper->wrapSession($session);
422
+            self::$server->setSession($session);
423
+
424
+            // if session can't be started break with http 500 error
425
+        } catch (Exception $e) {
426
+            Server::get(LoggerInterface::class)->error($e->getMessage(), ['app' => 'base','exception' => $e]);
427
+            //show the user a detailed error page
428
+            Server::get(ITemplateManager::class)->printExceptionErrorPage($e, 500);
429
+            die();
430
+        }
431
+
432
+        //try to set the session lifetime
433
+        $sessionLifeTime = self::getSessionLifeTime();
434
+
435
+        // session timeout
436
+        if ($session->exists('LAST_ACTIVITY') && (time() - $session->get('LAST_ACTIVITY') > $sessionLifeTime)) {
437
+            if (isset($_COOKIE[session_name()])) {
438
+                setcookie(session_name(), '', -1, self::$WEBROOT ? : '/');
439
+            }
440
+            Server::get(IUserSession::class)->logout();
441
+        }
442
+
443
+        if (!self::hasSessionRelaxedExpiry()) {
444
+            $session->set('LAST_ACTIVITY', time());
445
+        }
446
+        $session->close();
447
+    }
448
+
449
+    private static function getSessionLifeTime(): int {
450
+        return Server::get(IConfig::class)->getSystemValueInt('session_lifetime', 60 * 60 * 24);
451
+    }
452
+
453
+    /**
454
+     * @return bool true if the session expiry should only be done by gc instead of an explicit timeout
455
+     */
456
+    public static function hasSessionRelaxedExpiry(): bool {
457
+        return Server::get(IConfig::class)->getSystemValueBool('session_relaxed_expiry', false);
458
+    }
459
+
460
+    /**
461
+     * Try to set some values to the required Nextcloud default
462
+     */
463
+    public static function setRequiredIniValues(): void {
464
+        // Don't display errors and log them
465
+        @ini_set('display_errors', '0');
466
+        @ini_set('log_errors', '1');
467
+
468
+        // Try to configure php to enable big file uploads.
469
+        // This doesn't work always depending on the webserver and php configuration.
470
+        // Let's try to overwrite some defaults if they are smaller than 1 hour
471
+
472
+        if (intval(@ini_get('max_execution_time') ?: 0) < 3600) {
473
+            @ini_set('max_execution_time', strval(3600));
474
+        }
475
+
476
+        if (intval(@ini_get('max_input_time') ?: 0) < 3600) {
477
+            @ini_set('max_input_time', strval(3600));
478
+        }
479
+
480
+        // Try to set the maximum execution time to the largest time limit we have
481
+        if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
482
+            @set_time_limit(max(intval(@ini_get('max_execution_time')), intval(@ini_get('max_input_time'))));
483
+        }
484
+
485
+        @ini_set('default_charset', 'UTF-8');
486
+        @ini_set('gd.jpeg_ignore_warning', '1');
487
+    }
488
+
489
+    /**
490
+     * Send the same site cookies
491
+     */
492
+    private static function sendSameSiteCookies(): void {
493
+        $cookieParams = session_get_cookie_params();
494
+        $secureCookie = ($cookieParams['secure'] === true) ? 'secure; ' : '';
495
+        $policies = [
496
+            'lax',
497
+            'strict',
498
+        ];
499
+
500
+        // Append __Host to the cookie if it meets the requirements
501
+        $cookiePrefix = '';
502
+        if ($cookieParams['secure'] === true && $cookieParams['path'] === '/') {
503
+            $cookiePrefix = '__Host-';
504
+        }
505
+
506
+        foreach ($policies as $policy) {
507
+            header(
508
+                sprintf(
509
+                    'Set-Cookie: %snc_sameSiteCookie%s=true; path=%s; httponly;' . $secureCookie . 'expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=%s',
510
+                    $cookiePrefix,
511
+                    $policy,
512
+                    $cookieParams['path'],
513
+                    $policy
514
+                ),
515
+                false
516
+            );
517
+        }
518
+    }
519
+
520
+    /**
521
+     * Same Site cookie to further mitigate CSRF attacks. This cookie has to
522
+     * be set in every request if cookies are sent to add a second level of
523
+     * defense against CSRF.
524
+     *
525
+     * If the cookie is not sent this will set the cookie and reload the page.
526
+     * We use an additional cookie since we want to protect logout CSRF and
527
+     * also we can't directly interfere with PHP's session mechanism.
528
+     */
529
+    private static function performSameSiteCookieProtection(IConfig $config): void {
530
+        $request = Server::get(IRequest::class);
531
+
532
+        // Some user agents are notorious and don't really properly follow HTTP
533
+        // specifications. For those, have an automated opt-out. Since the protection
534
+        // for remote.php is applied in base.php as starting point we need to opt out
535
+        // here.
536
+        $incompatibleUserAgents = $config->getSystemValue('csrf.optout');
537
+
538
+        // Fallback, if csrf.optout is unset
539
+        if (!is_array($incompatibleUserAgents)) {
540
+            $incompatibleUserAgents = [
541
+                // OS X Finder
542
+                '/^WebDAVFS/',
543
+                // Windows webdav drive
544
+                '/^Microsoft-WebDAV-MiniRedir/',
545
+            ];
546
+        }
547
+
548
+        if ($request->isUserAgent($incompatibleUserAgents)) {
549
+            return;
550
+        }
551
+
552
+        if (count($_COOKIE) > 0) {
553
+            $requestUri = $request->getScriptName();
554
+            $processingScript = explode('/', $requestUri);
555
+            $processingScript = $processingScript[count($processingScript) - 1];
556
+
557
+            if ($processingScript === 'index.php' // index.php routes are handled in the middleware
558
+                || $processingScript === 'cron.php' // and cron.php does not need any authentication at all
559
+                || $processingScript === 'public.php' // For public.php, auth for password protected shares is done in the PublicAuth plugin
560
+            ) {
561
+                return;
562
+            }
563
+
564
+            // All other endpoints require the lax and the strict cookie
565
+            if (!$request->passesStrictCookieCheck()) {
566
+                logger('core')->warning('Request does not pass strict cookie check');
567
+                self::sendSameSiteCookies();
568
+                // Debug mode gets access to the resources without strict cookie
569
+                // due to the fact that the SabreDAV browser also lives there.
570
+                if (!$config->getSystemValueBool('debug', false)) {
571
+                    http_response_code(\OCP\AppFramework\Http::STATUS_PRECONDITION_FAILED);
572
+                    header('Content-Type: application/json');
573
+                    echo json_encode(['error' => 'Strict Cookie has not been found in request']);
574
+                    exit();
575
+                }
576
+            }
577
+        } elseif (!isset($_COOKIE['nc_sameSiteCookielax']) || !isset($_COOKIE['nc_sameSiteCookiestrict'])) {
578
+            self::sendSameSiteCookies();
579
+        }
580
+    }
581
+
582
+    /**
583
+     * This function adds some security related headers to all requests served via base.php
584
+     * The implementation of this function has to happen here to ensure that all third-party
585
+     * components (e.g. SabreDAV) also benefit from this headers.
586
+     */
587
+    private static function addSecurityHeaders(): void {
588
+        /**
589
+         * FIXME: Content Security Policy for legacy components. This
590
+         * can be removed once \OCP\AppFramework\Http\Response from the AppFramework
591
+         * is used everywhere.
592
+         * @see \OCP\AppFramework\Http\Response::getHeaders
593
+         */
594
+        $policy = 'default-src \'self\'; '
595
+            . 'script-src \'self\' \'nonce-' . \OC::$server->getContentSecurityPolicyNonceManager()->getNonce() . '\'; '
596
+            . 'style-src \'self\' \'unsafe-inline\'; '
597
+            . 'frame-src *; '
598
+            . 'img-src * data: blob:; '
599
+            . 'font-src \'self\' data:; '
600
+            . 'media-src *; '
601
+            . 'connect-src *; '
602
+            . 'object-src \'none\'; '
603
+            . 'base-uri \'self\'; ';
604
+        header('Content-Security-Policy:' . $policy);
605
+
606
+        // Send fallback headers for installations that don't have the possibility to send
607
+        // custom headers on the webserver side
608
+        if (getenv('modHeadersAvailable') !== 'true') {
609
+            header('Referrer-Policy: no-referrer'); // https://www.w3.org/TR/referrer-policy/
610
+            header('X-Content-Type-Options: nosniff'); // Disable sniffing the content type for IE
611
+            header('X-Frame-Options: SAMEORIGIN'); // Disallow iFraming from other domains
612
+            header('X-Permitted-Cross-Domain-Policies: none'); // https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html
613
+            header('X-Robots-Tag: noindex, nofollow'); // https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag
614
+        }
615
+    }
616
+
617
+    public static function init(): void {
618
+        // First handle PHP configuration and copy auth headers to the expected
619
+        // $_SERVER variable before doing anything Server object related
620
+        self::setRequiredIniValues();
621
+        self::handleAuthHeaders();
622
+
623
+        // prevent any XML processing from loading external entities
624
+        libxml_set_external_entity_loader(static function () {
625
+            return null;
626
+        });
627
+
628
+        // Set default timezone before the Server object is booted
629
+        if (!date_default_timezone_set('UTC')) {
630
+            throw new \RuntimeException('Could not set timezone to UTC');
631
+        }
632
+
633
+        // calculate the root directories
634
+        OC::$SERVERROOT = str_replace('\\', '/', substr(__DIR__, 0, -4));
635
+
636
+        // register autoloader
637
+        $loaderStart = microtime(true);
638
+
639
+        self::$CLI = (php_sapi_name() == 'cli');
640
+
641
+        // Add default composer PSR-4 autoloader, ensure apcu to be disabled
642
+        self::$composerAutoloader = require_once OC::$SERVERROOT . '/lib/composer/autoload.php';
643
+        self::$composerAutoloader->setApcuPrefix(null);
644
+
645
+
646
+        try {
647
+            self::initPaths();
648
+            // setup 3rdparty autoloader
649
+            $vendorAutoLoad = OC::$SERVERROOT . '/3rdparty/autoload.php';
650
+            if (!file_exists($vendorAutoLoad)) {
651
+                throw new \RuntimeException('Composer autoloader not found, unable to continue. Check the folder "3rdparty". Running "git submodule update --init" will initialize the git submodule that handles the subfolder "3rdparty".');
652
+            }
653
+            require_once $vendorAutoLoad;
654
+        } catch (\RuntimeException $e) {
655
+            if (!self::$CLI) {
656
+                http_response_code(503);
657
+            }
658
+            // we can't use the template error page here, because this needs the
659
+            // DI container which isn't available yet
660
+            print($e->getMessage());
661
+            exit();
662
+        }
663
+        $loaderEnd = microtime(true);
664
+
665
+        // Enable lazy loading if activated
666
+        \OC\AppFramework\Utility\SimpleContainer::$useLazyObjects = (bool)self::$config->getValue('enable_lazy_objects', true);
667
+
668
+        // setup the basic server
669
+        self::$server = new \OC\Server(\OC::$WEBROOT, self::$config);
670
+        self::$server->boot();
671
+
672
+        try {
673
+            $profiler = new BuiltInProfiler(
674
+                Server::get(IConfig::class),
675
+                Server::get(IRequest::class),
676
+            );
677
+            $profiler->start();
678
+        } catch (\Throwable $e) {
679
+            logger('core')->error('Failed to start profiler: ' . $e->getMessage(), ['app' => 'base']);
680
+        }
681
+
682
+        if (self::$CLI && in_array('--' . \OCP\Console\ReservedOptions::DEBUG_LOG, $_SERVER['argv'])) {
683
+            \OC\Core\Listener\BeforeMessageLoggedEventListener::setup();
684
+        }
685
+
686
+        $eventLogger = Server::get(\OCP\Diagnostics\IEventLogger::class);
687
+        $eventLogger->log('autoloader', 'Autoloader', $loaderStart, $loaderEnd);
688
+        $eventLogger->start('boot', 'Initialize');
689
+
690
+        // Override php.ini and log everything if we're troubleshooting
691
+        if (self::$config->getValue('loglevel') === ILogger::DEBUG) {
692
+            error_reporting(E_ALL);
693
+        }
694
+
695
+        // initialize intl fallback if necessary
696
+        OC_Util::isSetLocaleWorking();
697
+
698
+        $config = Server::get(IConfig::class);
699
+        if (!defined('PHPUNIT_RUN')) {
700
+            $errorHandler = new OC\Log\ErrorHandler(
701
+                \OCP\Server::get(\Psr\Log\LoggerInterface::class),
702
+            );
703
+            $exceptionHandler = [$errorHandler, 'onException'];
704
+            if ($config->getSystemValueBool('debug', false)) {
705
+                set_error_handler([$errorHandler, 'onAll'], E_ALL);
706
+                if (\OC::$CLI) {
707
+                    $exceptionHandler = [Server::get(ITemplateManager::class), 'printExceptionErrorPage'];
708
+                }
709
+            } else {
710
+                set_error_handler([$errorHandler, 'onError']);
711
+            }
712
+            register_shutdown_function([$errorHandler, 'onShutdown']);
713
+            set_exception_handler($exceptionHandler);
714
+        }
715
+
716
+        /** @var \OC\AppFramework\Bootstrap\Coordinator $bootstrapCoordinator */
717
+        $bootstrapCoordinator = Server::get(\OC\AppFramework\Bootstrap\Coordinator::class);
718
+        $bootstrapCoordinator->runInitialRegistration();
719
+
720
+        $eventLogger->start('init_session', 'Initialize session');
721
+
722
+        // Check for PHP SimpleXML extension earlier since we need it before our other checks and want to provide a useful hint for web users
723
+        // see https://github.com/nextcloud/server/pull/2619
724
+        if (!function_exists('simplexml_load_file')) {
725
+            throw new \OCP\HintException('The PHP SimpleXML/PHP-XML extension is not installed.', 'Install the extension or make sure it is enabled.');
726
+        }
727
+
728
+        $systemConfig = Server::get(\OC\SystemConfig::class);
729
+        $appManager = Server::get(\OCP\App\IAppManager::class);
730
+        if ($systemConfig->getValue('installed', false)) {
731
+            $appManager->loadApps(['session']);
732
+        }
733
+        if (!self::$CLI) {
734
+            self::initSession();
735
+        }
736
+        $eventLogger->end('init_session');
737
+        self::checkConfig();
738
+        self::checkInstalled($systemConfig);
739
+
740
+        self::addSecurityHeaders();
741
+
742
+        self::performSameSiteCookieProtection($config);
743
+
744
+        if (!defined('OC_CONSOLE')) {
745
+            $eventLogger->start('check_server', 'Run a few configuration checks');
746
+            $errors = OC_Util::checkServer($systemConfig);
747
+            if (count($errors) > 0) {
748
+                if (!self::$CLI) {
749
+                    http_response_code(503);
750
+                    Util::addStyle('guest');
751
+                    try {
752
+                        Server::get(ITemplateManager::class)->printGuestPage('', 'error', ['errors' => $errors]);
753
+                        exit;
754
+                    } catch (\Exception $e) {
755
+                        // In case any error happens when showing the error page, we simply fall back to posting the text.
756
+                        // This might be the case when e.g. the data directory is broken and we can not load/write SCSS to/from it.
757
+                    }
758
+                }
759
+
760
+                // Convert l10n string into regular string for usage in database
761
+                $staticErrors = [];
762
+                foreach ($errors as $error) {
763
+                    echo $error['error'] . "\n";
764
+                    echo $error['hint'] . "\n\n";
765
+                    $staticErrors[] = [
766
+                        'error' => (string)$error['error'],
767
+                        'hint' => (string)$error['hint'],
768
+                    ];
769
+                }
770
+
771
+                try {
772
+                    $config->setAppValue('core', 'cronErrors', json_encode($staticErrors));
773
+                } catch (\Exception $e) {
774
+                    echo('Writing to database failed');
775
+                }
776
+                exit(1);
777
+            } elseif (self::$CLI && $config->getSystemValueBool('installed', false)) {
778
+                $config->deleteAppValue('core', 'cronErrors');
779
+            }
780
+            $eventLogger->end('check_server');
781
+        }
782
+
783
+        // User and Groups
784
+        if (!$systemConfig->getValue('installed', false)) {
785
+            self::$server->getSession()->set('user_id', '');
786
+        }
787
+
788
+        $eventLogger->start('setup_backends', 'Setup group and user backends');
789
+        Server::get(\OCP\IUserManager::class)->registerBackend(new \OC\User\Database());
790
+        Server::get(\OCP\IGroupManager::class)->addBackend(new \OC\Group\Database());
791
+
792
+        // Subscribe to the hook
793
+        \OCP\Util::connectHook(
794
+            '\OCA\Files_Sharing\API\Server2Server',
795
+            'preLoginNameUsedAsUserName',
796
+            '\OC\User\Database',
797
+            'preLoginNameUsedAsUserName'
798
+        );
799
+
800
+        //setup extra user backends
801
+        if (!\OCP\Util::needUpgrade()) {
802
+            OC_User::setupBackends();
803
+        } else {
804
+            // Run upgrades in incognito mode
805
+            OC_User::setIncognitoMode(true);
806
+        }
807
+        $eventLogger->end('setup_backends');
808
+
809
+        self::registerCleanupHooks($systemConfig);
810
+        self::registerShareHooks($systemConfig);
811
+        self::registerEncryptionWrapperAndHooks();
812
+        self::registerAccountHooks();
813
+        self::registerResourceCollectionHooks();
814
+        self::registerFileReferenceEventListener();
815
+        self::registerRenderReferenceEventListener();
816
+        self::registerAppRestrictionsHooks();
817
+
818
+        // Make sure that the application class is not loaded before the database is setup
819
+        if ($systemConfig->getValue('installed', false)) {
820
+            $appManager->loadApp('settings');
821
+        }
822
+
823
+        //make sure temporary files are cleaned up
824
+        $tmpManager = Server::get(\OCP\ITempManager::class);
825
+        register_shutdown_function([$tmpManager, 'clean']);
826
+        $lockProvider = Server::get(\OCP\Lock\ILockingProvider::class);
827
+        register_shutdown_function([$lockProvider, 'releaseAll']);
828
+
829
+        // Check whether the sample configuration has been copied
830
+        if ($systemConfig->getValue('copied_sample_config', false)) {
831
+            $l = Server::get(\OCP\L10N\IFactory::class)->get('lib');
832
+            Server::get(ITemplateManager::class)->printErrorPage(
833
+                $l->t('Sample configuration detected'),
834
+                $l->t('It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php'),
835
+                503
836
+            );
837
+            return;
838
+        }
839
+
840
+        $request = Server::get(IRequest::class);
841
+        $host = $request->getInsecureServerHost();
842
+        /**
843
+         * if the host passed in headers isn't trusted
844
+         * FIXME: Should not be in here at all :see_no_evil:
845
+         */
846
+        if (!OC::$CLI
847
+            && !Server::get(\OC\Security\TrustedDomainHelper::class)->isTrustedDomain($host)
848
+            && $config->getSystemValueBool('installed', false)
849
+        ) {
850
+            // Allow access to CSS resources
851
+            $isScssRequest = false;
852
+            if (strpos($request->getPathInfo() ?: '', '/css/') === 0) {
853
+                $isScssRequest = true;
854
+            }
855
+
856
+            if (substr($request->getRequestUri(), -11) === '/status.php') {
857
+                http_response_code(400);
858
+                header('Content-Type: application/json');
859
+                echo '{"error": "Trusted domain error.", "code": 15}';
860
+                exit();
861
+            }
862
+
863
+            if (!$isScssRequest) {
864
+                http_response_code(400);
865
+                Server::get(LoggerInterface::class)->info(
866
+                    'Trusted domain error. "{remoteAddress}" tried to access using "{host}" as host.',
867
+                    [
868
+                        'app' => 'core',
869
+                        'remoteAddress' => $request->getRemoteAddress(),
870
+                        'host' => $host,
871
+                    ]
872
+                );
873
+
874
+                $tmpl = Server::get(ITemplateManager::class)->getTemplate('core', 'untrustedDomain', 'guest');
875
+                $tmpl->assign('docUrl', Server::get(IURLGenerator::class)->linkToDocs('admin-trusted-domains'));
876
+                $tmpl->printPage();
877
+
878
+                exit();
879
+            }
880
+        }
881
+        $eventLogger->end('boot');
882
+        $eventLogger->log('init', 'OC::init', $loaderStart, microtime(true));
883
+        $eventLogger->start('runtime', 'Runtime');
884
+        $eventLogger->start('request', 'Full request after boot');
885
+        register_shutdown_function(function () use ($eventLogger) {
886
+            $eventLogger->end('request');
887
+        });
888
+
889
+        register_shutdown_function(function () {
890
+            $memoryPeak = memory_get_peak_usage();
891
+            $logLevel = match (true) {
892
+                $memoryPeak > 500_000_000 => ILogger::FATAL,
893
+                $memoryPeak > 400_000_000 => ILogger::ERROR,
894
+                $memoryPeak > 300_000_000 => ILogger::WARN,
895
+                default => null,
896
+            };
897
+            if ($logLevel !== null) {
898
+                $message = 'Request used more than 300 MB of RAM: ' . Util::humanFileSize($memoryPeak);
899
+                $logger = Server::get(LoggerInterface::class);
900
+                $logger->log($logLevel, $message, ['app' => 'core']);
901
+            }
902
+        });
903
+    }
904
+
905
+    /**
906
+     * register hooks for the cleanup of cache and bruteforce protection
907
+     */
908
+    public static function registerCleanupHooks(\OC\SystemConfig $systemConfig): void {
909
+        //don't try to do this before we are properly setup
910
+        if ($systemConfig->getValue('installed', false) && !\OCP\Util::needUpgrade()) {
911
+            // NOTE: This will be replaced to use OCP
912
+            $userSession = Server::get(\OC\User\Session::class);
913
+            $userSession->listen('\OC\User', 'postLogin', function () use ($userSession) {
914
+                if (!defined('PHPUNIT_RUN') && $userSession->isLoggedIn()) {
915
+                    // reset brute force delay for this IP address and username
916
+                    $uid = $userSession->getUser()->getUID();
917
+                    $request = Server::get(IRequest::class);
918
+                    $throttler = Server::get(IThrottler::class);
919
+                    $throttler->resetDelay($request->getRemoteAddress(), 'login', ['user' => $uid]);
920
+                }
921
+
922
+                try {
923
+                    $cache = new \OC\Cache\File();
924
+                    $cache->gc();
925
+                } catch (\OC\ServerNotAvailableException $e) {
926
+                    // not a GC exception, pass it on
927
+                    throw $e;
928
+                } catch (\OC\ForbiddenException $e) {
929
+                    // filesystem blocked for this request, ignore
930
+                } catch (\Exception $e) {
931
+                    // a GC exception should not prevent users from using OC,
932
+                    // so log the exception
933
+                    Server::get(LoggerInterface::class)->warning('Exception when running cache gc.', [
934
+                        'app' => 'core',
935
+                        'exception' => $e,
936
+                    ]);
937
+                }
938
+            });
939
+        }
940
+    }
941
+
942
+    private static function registerEncryptionWrapperAndHooks(): void {
943
+        /** @var \OC\Encryption\Manager */
944
+        $manager = Server::get(\OCP\Encryption\IManager::class);
945
+        Server::get(IEventDispatcher::class)->addListener(
946
+            BeforeFileSystemSetupEvent::class,
947
+            $manager->setupStorage(...),
948
+        );
949
+
950
+        $enabled = $manager->isEnabled();
951
+        if ($enabled) {
952
+            \OC\Encryption\EncryptionEventListener::register(Server::get(IEventDispatcher::class));
953
+        }
954
+    }
955
+
956
+    private static function registerAccountHooks(): void {
957
+        /** @var IEventDispatcher $dispatcher */
958
+        $dispatcher = Server::get(IEventDispatcher::class);
959
+        $dispatcher->addServiceListener(UserChangedEvent::class, \OC\Accounts\Hooks::class);
960
+    }
961
+
962
+    private static function registerAppRestrictionsHooks(): void {
963
+        /** @var \OC\Group\Manager $groupManager */
964
+        $groupManager = Server::get(\OCP\IGroupManager::class);
965
+        $groupManager->listen('\OC\Group', 'postDelete', function (\OCP\IGroup $group) {
966
+            $appManager = Server::get(\OCP\App\IAppManager::class);
967
+            $apps = $appManager->getEnabledAppsForGroup($group);
968
+            foreach ($apps as $appId) {
969
+                $restrictions = $appManager->getAppRestriction($appId);
970
+                if (empty($restrictions)) {
971
+                    continue;
972
+                }
973
+                $key = array_search($group->getGID(), $restrictions);
974
+                unset($restrictions[$key]);
975
+                $restrictions = array_values($restrictions);
976
+                if (empty($restrictions)) {
977
+                    $appManager->disableApp($appId);
978
+                } else {
979
+                    $appManager->enableAppForGroups($appId, $restrictions);
980
+                }
981
+            }
982
+        });
983
+    }
984
+
985
+    private static function registerResourceCollectionHooks(): void {
986
+        \OC\Collaboration\Resources\Listener::register(Server::get(IEventDispatcher::class));
987
+    }
988
+
989
+    private static function registerFileReferenceEventListener(): void {
990
+        \OC\Collaboration\Reference\File\FileReferenceEventListener::register(Server::get(IEventDispatcher::class));
991
+    }
992
+
993
+    private static function registerRenderReferenceEventListener() {
994
+        \OC\Collaboration\Reference\RenderReferenceEventListener::register(Server::get(IEventDispatcher::class));
995
+    }
996
+
997
+    /**
998
+     * register hooks for sharing
999
+     */
1000
+    public static function registerShareHooks(\OC\SystemConfig $systemConfig): void {
1001
+        if ($systemConfig->getValue('installed')) {
1002
+
1003
+            $dispatcher = Server::get(IEventDispatcher::class);
1004
+            $dispatcher->addServiceListener(UserRemovedEvent::class, UserRemovedListener::class);
1005
+            $dispatcher->addServiceListener(GroupDeletedEvent::class, GroupDeletedListener::class);
1006
+            $dispatcher->addServiceListener(UserDeletedEvent::class, UserDeletedListener::class);
1007
+        }
1008
+    }
1009
+
1010
+    /**
1011
+     * Handle the request
1012
+     */
1013
+    public static function handleRequest(): void {
1014
+        Server::get(\OCP\Diagnostics\IEventLogger::class)->start('handle_request', 'Handle request');
1015
+        $systemConfig = Server::get(\OC\SystemConfig::class);
1016
+
1017
+        // Check if Nextcloud is installed or in maintenance (update) mode
1018
+        if (!$systemConfig->getValue('installed', false)) {
1019
+            \OC::$server->getSession()->clear();
1020
+            $controller = Server::get(\OC\Core\Controller\SetupController::class);
1021
+            $controller->run($_POST);
1022
+            exit();
1023
+        }
1024
+
1025
+        $request = Server::get(IRequest::class);
1026
+        $request->throwDecodingExceptionIfAny();
1027
+        $requestPath = $request->getRawPathInfo();
1028
+        if ($requestPath === '/heartbeat') {
1029
+            return;
1030
+        }
1031
+        if (substr($requestPath, -3) !== '.js') { // we need these files during the upgrade
1032
+            self::checkMaintenanceMode($systemConfig);
1033
+
1034
+            if (\OCP\Util::needUpgrade()) {
1035
+                if (function_exists('opcache_reset')) {
1036
+                    opcache_reset();
1037
+                }
1038
+                if (!((bool)$systemConfig->getValue('maintenance', false))) {
1039
+                    self::printUpgradePage($systemConfig);
1040
+                    exit();
1041
+                }
1042
+            }
1043
+        }
1044
+
1045
+        $appManager = Server::get(\OCP\App\IAppManager::class);
1046
+
1047
+        // Always load authentication apps
1048
+        $appManager->loadApps(['authentication']);
1049
+        $appManager->loadApps(['extended_authentication']);
1050
+
1051
+        // Load minimum set of apps
1052
+        if (!\OCP\Util::needUpgrade()
1053
+            && !((bool)$systemConfig->getValue('maintenance', false))) {
1054
+            // For logged-in users: Load everything
1055
+            if (Server::get(IUserSession::class)->isLoggedIn()) {
1056
+                $appManager->loadApps();
1057
+            } else {
1058
+                // For guests: Load only filesystem and logging
1059
+                $appManager->loadApps(['filesystem', 'logging']);
1060
+
1061
+                // Don't try to login when a client is trying to get a OAuth token.
1062
+                // OAuth needs to support basic auth too, so the login is not valid
1063
+                // inside Nextcloud and the Login exception would ruin it.
1064
+                if ($request->getRawPathInfo() !== '/apps/oauth2/api/v1/token') {
1065
+                    try {
1066
+                        self::handleLogin($request);
1067
+                    } catch (DisabledUserException $e) {
1068
+                        // Disabled users would not be seen as logged in and
1069
+                        // trying to log them in would fail, so the login
1070
+                        // exception is ignored for the themed stylesheets and
1071
+                        // images.
1072
+                        if ($request->getRawPathInfo() !== '/apps/theming/theme/default.css'
1073
+                            && $request->getRawPathInfo() !== '/apps/theming/theme/light.css'
1074
+                            && $request->getRawPathInfo() !== '/apps/theming/theme/dark.css'
1075
+                            && $request->getRawPathInfo() !== '/apps/theming/theme/light-highcontrast.css'
1076
+                            && $request->getRawPathInfo() !== '/apps/theming/theme/dark-highcontrast.css'
1077
+                            && $request->getRawPathInfo() !== '/apps/theming/theme/opendyslexic.css'
1078
+                            && $request->getRawPathInfo() !== '/apps/theming/image/background'
1079
+                            && $request->getRawPathInfo() !== '/apps/theming/image/logo'
1080
+                            && $request->getRawPathInfo() !== '/apps/theming/image/logoheader'
1081
+                            && !str_starts_with($request->getRawPathInfo(), '/apps/theming/favicon')
1082
+                            && !str_starts_with($request->getRawPathInfo(), '/apps/theming/icon')) {
1083
+                            throw $e;
1084
+                        }
1085
+                    }
1086
+                }
1087
+            }
1088
+        }
1089
+
1090
+        if (!self::$CLI) {
1091
+            try {
1092
+                if (!\OCP\Util::needUpgrade()) {
1093
+                    $appManager->loadApps(['filesystem', 'logging']);
1094
+                    $appManager->loadApps();
1095
+                }
1096
+                Server::get(\OC\Route\Router::class)->match($request->getRawPathInfo());
1097
+                return;
1098
+            } catch (Symfony\Component\Routing\Exception\ResourceNotFoundException $e) {
1099
+                //header('HTTP/1.0 404 Not Found');
1100
+            } catch (Symfony\Component\Routing\Exception\MethodNotAllowedException $e) {
1101
+                http_response_code(405);
1102
+                return;
1103
+            }
1104
+        }
1105
+
1106
+        // Handle WebDAV
1107
+        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
1108
+            // not allowed any more to prevent people
1109
+            // mounting this root directly.
1110
+            // Users need to mount remote.php/webdav instead.
1111
+            http_response_code(405);
1112
+            return;
1113
+        }
1114
+
1115
+        // Handle requests for JSON or XML
1116
+        $acceptHeader = $request->getHeader('Accept');
1117
+        if (in_array($acceptHeader, ['application/json', 'application/xml'], true)) {
1118
+            http_response_code(404);
1119
+            return;
1120
+        }
1121
+
1122
+        // Handle resources that can't be found
1123
+        // This prevents browsers from redirecting to the default page and then
1124
+        // attempting to parse HTML as CSS and similar.
1125
+        $destinationHeader = $request->getHeader('Sec-Fetch-Dest');
1126
+        if (in_array($destinationHeader, ['font', 'script', 'style'])) {
1127
+            http_response_code(404);
1128
+            return;
1129
+        }
1130
+
1131
+        // Redirect to the default app or login only as an entry point
1132
+        if ($requestPath === '') {
1133
+            // Someone is logged in
1134
+            if (Server::get(IUserSession::class)->isLoggedIn()) {
1135
+                header('Location: ' . Server::get(IURLGenerator::class)->linkToDefaultPageUrl());
1136
+            } else {
1137
+                // Not handled and not logged in
1138
+                header('Location: ' . Server::get(IURLGenerator::class)->linkToRouteAbsolute('core.login.showLoginForm'));
1139
+            }
1140
+            return;
1141
+        }
1142
+
1143
+        try {
1144
+            Server::get(\OC\Route\Router::class)->match('/error/404');
1145
+        } catch (\Exception $e) {
1146
+            if (!$e instanceof MethodNotAllowedException) {
1147
+                logger('core')->emergency($e->getMessage(), ['exception' => $e]);
1148
+            }
1149
+            $l = Server::get(\OCP\L10N\IFactory::class)->get('lib');
1150
+            Server::get(ITemplateManager::class)->printErrorPage(
1151
+                '404',
1152
+                $l->t('The page could not be found on the server.'),
1153
+                404
1154
+            );
1155
+        }
1156
+    }
1157
+
1158
+    /**
1159
+     * Check login: apache auth, auth token, basic auth
1160
+     */
1161
+    public static function handleLogin(OCP\IRequest $request): bool {
1162
+        if ($request->getHeader('X-Nextcloud-Federation')) {
1163
+            return false;
1164
+        }
1165
+        $userSession = Server::get(\OC\User\Session::class);
1166
+        if (OC_User::handleApacheAuth()) {
1167
+            return true;
1168
+        }
1169
+        if (self::tryAppAPILogin($request)) {
1170
+            return true;
1171
+        }
1172
+        if ($userSession->tryTokenLogin($request)) {
1173
+            return true;
1174
+        }
1175
+        if (isset($_COOKIE['nc_username'])
1176
+            && isset($_COOKIE['nc_token'])
1177
+            && isset($_COOKIE['nc_session_id'])
1178
+            && $userSession->loginWithCookie($_COOKIE['nc_username'], $_COOKIE['nc_token'], $_COOKIE['nc_session_id'])) {
1179
+            return true;
1180
+        }
1181
+        if ($userSession->tryBasicAuthLogin($request, Server::get(IThrottler::class))) {
1182
+            return true;
1183
+        }
1184
+        return false;
1185
+    }
1186
+
1187
+    protected static function handleAuthHeaders(): void {
1188
+        //copy http auth headers for apache+php-fcgid work around
1189
+        if (isset($_SERVER['HTTP_XAUTHORIZATION']) && !isset($_SERVER['HTTP_AUTHORIZATION'])) {
1190
+            $_SERVER['HTTP_AUTHORIZATION'] = $_SERVER['HTTP_XAUTHORIZATION'];
1191
+        }
1192
+
1193
+        // Extract PHP_AUTH_USER/PHP_AUTH_PW from other headers if necessary.
1194
+        $vars = [
1195
+            'HTTP_AUTHORIZATION', // apache+php-cgi work around
1196
+            'REDIRECT_HTTP_AUTHORIZATION', // apache+php-cgi alternative
1197
+        ];
1198
+        foreach ($vars as $var) {
1199
+            if (isset($_SERVER[$var]) && is_string($_SERVER[$var]) && preg_match('/Basic\s+(.*)$/i', $_SERVER[$var], $matches)) {
1200
+                $credentials = explode(':', base64_decode($matches[1]), 2);
1201
+                if (count($credentials) === 2) {
1202
+                    $_SERVER['PHP_AUTH_USER'] = $credentials[0];
1203
+                    $_SERVER['PHP_AUTH_PW'] = $credentials[1];
1204
+                    break;
1205
+                }
1206
+            }
1207
+        }
1208
+    }
1209
+
1210
+    protected static function tryAppAPILogin(OCP\IRequest $request): bool {
1211
+        if (!$request->getHeader('AUTHORIZATION-APP-API')) {
1212
+            return false;
1213
+        }
1214
+        $appManager = Server::get(OCP\App\IAppManager::class);
1215
+        if (!$appManager->isEnabledForAnyone('app_api')) {
1216
+            return false;
1217
+        }
1218
+        try {
1219
+            $appAPIService = Server::get(OCA\AppAPI\Service\AppAPIService::class);
1220
+            return $appAPIService->validateExAppRequestToNC($request);
1221
+        } catch (\Psr\Container\NotFoundExceptionInterface|\Psr\Container\ContainerExceptionInterface $e) {
1222
+            return false;
1223
+        }
1224
+    }
1225 1225
 }
1226 1226
 
1227 1227
 OC::init();
Please login to merge, or discard this patch.
apps/files_trashbin/lib/Trashbin.php 2 patches
Indentation   +1165 added lines, -1165 removed lines patch added patch discarded remove patch
@@ -56,1169 +56,1169 @@
 block discarded – undo
56 56
 
57 57
 /** @template-implements IEventListener<BeforeNodeDeletedEvent> */
58 58
 class Trashbin implements IEventListener {
59
-	// unit: percentage; 50% of available disk space/quota
60
-	public const DEFAULTMAXSIZE = 50;
61
-
62
-	/**
63
-	 * Ensure we don't need to scan the file during the move to trash
64
-	 * by triggering the scan in the pre-hook
65
-	 */
66
-	public static function ensureFileScannedHook(Node $node): void {
67
-		try {
68
-			self::getUidAndFilename($node->getPath());
69
-		} catch (NotFoundException $e) {
70
-			// Nothing to scan for non existing files
71
-		}
72
-	}
73
-
74
-	/**
75
-	 * get the UID of the owner of the file and the path to the file relative to
76
-	 * owners files folder
77
-	 *
78
-	 * @param string $filename
79
-	 * @return array
80
-	 * @throws NoUserException
81
-	 */
82
-	public static function getUidAndFilename($filename) {
83
-		$uid = Filesystem::getOwner($filename);
84
-		$userManager = Server::get(IUserManager::class);
85
-		// if the user with the UID doesn't exists, e.g. because the UID points
86
-		// to a remote user with a federated cloud ID we use the current logged-in
87
-		// user. We need a valid local user to move the file to the right trash bin
88
-		if (!$userManager->userExists($uid)) {
89
-			$uid = OC_User::getUser();
90
-		}
91
-		if (!$uid) {
92
-			// no owner, usually because of share link from ext storage
93
-			return [null, null];
94
-		}
95
-		Filesystem::initMountPoints($uid);
96
-		if ($uid !== OC_User::getUser()) {
97
-			$info = Filesystem::getFileInfo($filename);
98
-			$ownerView = new View('/' . $uid . '/files');
99
-			try {
100
-				$filename = $ownerView->getPath($info['fileid']);
101
-			} catch (NotFoundException $e) {
102
-				$filename = null;
103
-			}
104
-		}
105
-		return [$uid, $filename];
106
-	}
107
-
108
-	/**
109
-	 * get original location and deleted by of files for user
110
-	 *
111
-	 * @param string $user
112
-	 * @return array<string, array<string, array{location: string, deletedBy: string}>>
113
-	 */
114
-	public static function getExtraData($user) {
115
-		$query = Server::get(IDBConnection::class)->getQueryBuilder();
116
-		$query->select('id', 'timestamp', 'location', 'deleted_by')
117
-			->from('files_trash')
118
-			->where($query->expr()->eq('user', $query->createNamedParameter($user)));
119
-		$result = $query->executeQuery();
120
-		$array = [];
121
-		foreach ($result->iterateAssociative() as $row) {
122
-			$array[$row['id']][$row['timestamp']] = [
123
-				'location' => (string)$row['location'],
124
-				'deletedBy' => (string)$row['deleted_by'],
125
-			];
126
-		}
127
-		$result->closeCursor();
128
-		return $array;
129
-	}
130
-
131
-	/**
132
-	 * get original location of file
133
-	 *
134
-	 * @param string $user
135
-	 * @param string $filename
136
-	 * @param string $timestamp
137
-	 * @return string|false original location
138
-	 */
139
-	public static function getLocation($user, $filename, $timestamp) {
140
-		$query = Server::get(IDBConnection::class)->getQueryBuilder();
141
-		$query->select('location')
142
-			->from('files_trash')
143
-			->where($query->expr()->eq('user', $query->createNamedParameter($user)))
144
-			->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
145
-			->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
146
-
147
-		$result = $query->executeQuery();
148
-		$row = $result->fetchAssociative();
149
-		$result->closeCursor();
150
-
151
-		if (isset($row['location'])) {
152
-			return $row['location'];
153
-		} else {
154
-			return false;
155
-		}
156
-	}
157
-
158
-	/** @param string $user */
159
-	private static function setUpTrash($user): void {
160
-		$view = new View('/' . $user);
161
-		if (!$view->is_dir('files_trashbin')) {
162
-			$view->mkdir('files_trashbin');
163
-		}
164
-		if (!$view->is_dir('files_trashbin/files')) {
165
-			$view->mkdir('files_trashbin/files');
166
-		}
167
-		if (!$view->is_dir('files_trashbin/versions')) {
168
-			$view->mkdir('files_trashbin/versions');
169
-		}
170
-		if (!$view->is_dir('files_trashbin/keys')) {
171
-			$view->mkdir('files_trashbin/keys');
172
-		}
173
-	}
174
-
175
-
176
-	/**
177
-	 * copy file to owners trash
178
-	 *
179
-	 * @param string $sourcePath
180
-	 * @param string $owner
181
-	 * @param string $targetPath
182
-	 * @param string $user
183
-	 * @param int $timestamp
184
-	 */
185
-	private static function copyFilesToUser($sourcePath, $owner, $targetPath, $user, $timestamp): void {
186
-		self::setUpTrash($owner);
187
-
188
-		$targetFilename = basename($targetPath);
189
-		$targetLocation = dirname($targetPath);
190
-
191
-		$sourceFilename = basename($sourcePath);
192
-
193
-		$view = new View('/');
194
-
195
-		$target = $user . '/files_trashbin/files/' . static::getTrashFilename($targetFilename, $timestamp);
196
-		$source = $owner . '/files_trashbin/files/' . static::getTrashFilename($sourceFilename, $timestamp);
197
-		$free = $view->free_space($target);
198
-		$isUnknownOrUnlimitedFreeSpace = $free < 0;
199
-		$isEnoughFreeSpaceLeft = $view->filesize($source) < $free;
200
-		if ($isUnknownOrUnlimitedFreeSpace || $isEnoughFreeSpaceLeft) {
201
-			self::copy_recursive($source, $target, $view);
202
-		}
203
-
204
-
205
-		if ($view->file_exists($target)) {
206
-			$query = Server::get(IDBConnection::class)->getQueryBuilder();
207
-			$query->insert('files_trash')
208
-				->setValue('id', $query->createNamedParameter($targetFilename))
209
-				->setValue('timestamp', $query->createNamedParameter($timestamp))
210
-				->setValue('location', $query->createNamedParameter($targetLocation))
211
-				->setValue('user', $query->createNamedParameter($user))
212
-				->setValue('deleted_by', $query->createNamedParameter($user));
213
-			$result = $query->executeStatement();
214
-			if (!$result) {
215
-				Server::get(LoggerInterface::class)->error('trash bin database couldn\'t be updated for the files owner', ['app' => 'files_trashbin']);
216
-			}
217
-		}
218
-	}
219
-
220
-
221
-	/**
222
-	 * move file to the trash bin
223
-	 *
224
-	 * @param string $file_path path to the deleted file/directory relative to the files root directory
225
-	 * @param bool $ownerOnly delete for owner only (if file gets moved out of a shared folder)
226
-	 *
227
-	 * @return bool
228
-	 */
229
-	public static function move2trash($file_path, $ownerOnly = false) {
230
-		// get the user for which the filesystem is setup
231
-		$root = Filesystem::getRoot();
232
-		[, $user] = explode('/', $root);
233
-		[$owner, $ownerPath] = self::getUidAndFilename($file_path);
234
-
235
-		// if no owner found (ex: ext storage + share link), will use the current user's trashbin then
236
-		if (is_null($owner)) {
237
-			$owner = $user;
238
-			$ownerPath = $file_path;
239
-		}
240
-
241
-		$ownerView = new View('/' . $owner);
242
-
243
-		// file has been deleted in between
244
-		if (is_null($ownerPath) || $ownerPath === '') {
245
-			return true;
246
-		}
247
-
248
-		$sourceInfo = $ownerView->getFileInfo('/files/' . $ownerPath);
249
-
250
-		if ($sourceInfo === false) {
251
-			return true;
252
-		}
253
-
254
-		self::setUpTrash($user);
255
-		if ($owner !== $user) {
256
-			// also setup for owner
257
-			self::setUpTrash($owner);
258
-		}
259
-
260
-		$path_parts = pathinfo($ownerPath);
261
-
262
-		$filename = $path_parts['basename'];
263
-		$location = $path_parts['dirname'];
264
-		/** @var ITimeFactory $timeFactory */
265
-		$timeFactory = Server::get(ITimeFactory::class);
266
-		$timestamp = $timeFactory->getTime();
267
-
268
-		$lockingProvider = Server::get(ILockingProvider::class);
269
-
270
-		// disable proxy to prevent recursive calls
271
-		$trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp);
272
-		$gotLock = false;
273
-
274
-		do {
275
-			/** @var ILockingStorage & IStorage $trashStorage */
276
-			[$trashStorage, $trashInternalPath] = $ownerView->resolvePath($trashPath);
277
-			try {
278
-				$trashStorage->acquireLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
279
-				$gotLock = true;
280
-			} catch (LockedException $e) {
281
-				// a file with the same name is being deleted concurrently
282
-				// nudge the timestamp a bit to resolve the conflict
283
-
284
-				$timestamp = $timestamp + 1;
285
-
286
-				$trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp);
287
-			}
288
-		} while (!$gotLock);
289
-
290
-		$sourceStorage = $sourceInfo->getStorage();
291
-		$sourceInternalPath = $sourceInfo->getInternalPath();
292
-
293
-		if ($trashStorage->file_exists($trashInternalPath)) {
294
-			$trashStorage->unlink($trashInternalPath);
295
-		}
296
-
297
-		$configuredTrashbinSize = static::getConfiguredTrashbinSize($owner);
298
-		if ($configuredTrashbinSize >= 0 && $sourceInfo->getSize() >= $configuredTrashbinSize) {
299
-			return false;
300
-		}
301
-
302
-		try {
303
-			$moveSuccessful = true;
304
-
305
-			$inCache = $sourceStorage->getCache()->inCache($sourceInternalPath);
306
-			$trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
307
-			if ($inCache) {
308
-				$trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
309
-			}
310
-		} catch (CopyRecursiveException $e) {
311
-			$moveSuccessful = false;
312
-			if ($trashStorage->file_exists($trashInternalPath)) {
313
-				$trashStorage->unlink($trashInternalPath);
314
-			}
315
-			Server::get(LoggerInterface::class)->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']);
316
-		}
317
-
318
-		if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
319
-			if ($sourceStorage->is_dir($sourceInternalPath)) {
320
-				$sourceStorage->rmdir($sourceInternalPath);
321
-			} else {
322
-				$sourceStorage->unlink($sourceInternalPath);
323
-			}
324
-
325
-			if ($sourceStorage->file_exists($sourceInternalPath)) {
326
-				// undo the cache move
327
-				$sourceStorage->getUpdater()->renameFromStorage($trashStorage, $trashInternalPath, $sourceInternalPath);
328
-			} else {
329
-				$trashStorage->getUpdater()->remove($trashInternalPath);
330
-			}
331
-			return false;
332
-		}
333
-
334
-		if ($moveSuccessful) {
335
-			// there is still a possibility that the file has been deleted by a remote user
336
-			$deletedBy = self::overwriteDeletedBy($user);
337
-
338
-			$query = Server::get(IDBConnection::class)->getQueryBuilder();
339
-			$query->insert('files_trash')
340
-				->setValue('id', $query->createNamedParameter($filename))
341
-				->setValue('timestamp', $query->createNamedParameter($timestamp))
342
-				->setValue('location', $query->createNamedParameter($location))
343
-				->setValue('user', $query->createNamedParameter($owner))
344
-				->setValue('deleted_by', $query->createNamedParameter($deletedBy));
345
-			$result = $query->executeStatement();
346
-			if (!$result) {
347
-				Server::get(LoggerInterface::class)->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']);
348
-			}
349
-			Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path),
350
-				'trashPath' => Filesystem::normalizePath(static::getTrashFilename($filename, $timestamp))]);
351
-
352
-			self::retainVersions($filename, $owner, $ownerPath, $timestamp);
353
-
354
-			// if owner !== user we need to also add a copy to the users trash
355
-			if ($user !== $owner && $ownerOnly === false) {
356
-				self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp);
357
-			}
358
-		}
359
-
360
-		$trashStorage->releaseLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
361
-
362
-		self::scheduleExpire($user);
363
-
364
-		// if owner !== user we also need to update the owners trash size
365
-		if ($owner !== $user) {
366
-			self::scheduleExpire($owner);
367
-		}
368
-
369
-		return $moveSuccessful;
370
-	}
371
-
372
-	private static function getConfiguredTrashbinSize(string $user): int|float {
373
-		$userConfig = Server::get(IUserConfig::class);
374
-		$userTrashbinSize = $userConfig->getValueString($user, 'files_trashbin', 'trashbin_size', '-1');
375
-		if (is_numeric($userTrashbinSize) && ($userTrashbinSize > -1)) {
376
-			return Util::numericToNumber($userTrashbinSize);
377
-		}
378
-
379
-		$appConfig = Server::get(IAppConfig::class);
380
-		$systemTrashbinSize = $appConfig->getValueString('files_trashbin', 'trashbin_size', '-1');
381
-		if (is_numeric($systemTrashbinSize)) {
382
-			return Util::numericToNumber($systemTrashbinSize);
383
-		}
384
-		return -1;
385
-	}
386
-
387
-	/**
388
-	 * Move file versions to trash so that they can be restored later
389
-	 *
390
-	 * @param string $filename of deleted file
391
-	 * @param string $owner owner user id
392
-	 * @param string $ownerPath path relative to the owner's home storage
393
-	 * @param int $timestamp when the file was deleted
394
-	 */
395
-	private static function retainVersions($filename, $owner, $ownerPath, $timestamp) {
396
-		if (Server::get(IAppManager::class)->isEnabledForUser('files_versions') && !empty($ownerPath)) {
397
-			$user = OC_User::getUser();
398
-			$rootView = new View('/');
399
-
400
-			if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
401
-				if ($owner !== $user) {
402
-					self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . static::getTrashFilename(basename($ownerPath), $timestamp), $rootView);
403
-				}
404
-				self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . static::getTrashFilename($filename, $timestamp));
405
-			} elseif ($versions = Storage::getVersions($owner, $ownerPath)) {
406
-				foreach ($versions as $v) {
407
-					if ($owner !== $user) {
408
-						self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . static::getTrashFilename($v['name'] . '.v' . $v['version'], $timestamp));
409
-					}
410
-					self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v['version'], $timestamp));
411
-				}
412
-			}
413
-		}
414
-	}
415
-
416
-	/**
417
-	 * Move a file or folder on storage level
418
-	 *
419
-	 * @param View $view
420
-	 * @param string $source
421
-	 * @param string $target
422
-	 * @return bool
423
-	 */
424
-	private static function move(View $view, $source, $target) {
425
-		/** @var \OC\Files\Storage\Storage $sourceStorage */
426
-		[$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
427
-		/** @var \OC\Files\Storage\Storage $targetStorage */
428
-		[$targetStorage, $targetInternalPath] = $view->resolvePath($target);
429
-		/** @var \OC\Files\Storage\Storage $ownerTrashStorage */
430
-
431
-		$result = $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
432
-		if ($result) {
433
-			$targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
434
-		}
435
-		return $result;
436
-	}
437
-
438
-	/**
439
-	 * Copy a file or folder on storage level
440
-	 *
441
-	 * @param View $view
442
-	 * @param string $source
443
-	 * @param string $target
444
-	 * @return bool
445
-	 */
446
-	private static function copy(View $view, $source, $target) {
447
-		/** @var \OC\Files\Storage\Storage $sourceStorage */
448
-		[$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
449
-		/** @var \OC\Files\Storage\Storage $targetStorage */
450
-		[$targetStorage, $targetInternalPath] = $view->resolvePath($target);
451
-		/** @var \OC\Files\Storage\Storage $ownerTrashStorage */
452
-
453
-		$result = $targetStorage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
454
-		if ($result) {
455
-			$targetStorage->getUpdater()->update($targetInternalPath);
456
-		}
457
-		return $result;
458
-	}
459
-
460
-	/**
461
-	 * Restore a file or folder from trash bin
462
-	 *
463
-	 * @param string $file path to the deleted file/folder relative to "files_trashbin/files/",
464
-	 *                     including the timestamp suffix ".d12345678"
465
-	 * @param string $filename name of the file/folder
466
-	 * @param int $timestamp time when the file/folder was deleted
467
-	 *
468
-	 * @return bool true on success, false otherwise
469
-	 */
470
-	public static function restore($file, $filename, $timestamp) {
471
-		$user = OC_User::getUser();
472
-		if (!$user) {
473
-			throw new \Exception('Tried to restore a file while not logged in');
474
-		}
475
-		$view = new View('/' . $user);
476
-
477
-		$location = '';
478
-		if ($timestamp) {
479
-			$location = self::getLocation($user, $filename, $timestamp);
480
-			if ($location === false) {
481
-				Server::get(LoggerInterface::class)->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']);
482
-			} else {
483
-				// if location no longer exists, restore file in the root directory
484
-				if ($location !== '/'
485
-					&& (!$view->is_dir('files/' . $location)
486
-						|| !$view->isCreatable('files/' . $location))
487
-				) {
488
-					$location = '';
489
-				}
490
-			}
491
-		}
492
-
493
-		// we need a  extension in case a file/dir with the same name already exists
494
-		$uniqueFilename = self::getUniqueFilename($location, $filename, $view);
495
-
496
-		$source = Filesystem::normalizePath('files_trashbin/files/' . $file);
497
-		$target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
498
-		if (!$view->file_exists($source)) {
499
-			return false;
500
-		}
501
-		$mtime = $view->filemtime($source);
502
-
503
-		// restore file
504
-		if (!$view->isCreatable(dirname($target))) {
505
-			throw new NotPermittedException("Can't restore trash item because the target folder is not writable");
506
-		}
507
-
508
-		$sourcePath = Filesystem::normalizePath($file);
509
-		$targetPath = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
510
-
511
-		$sourceNode = self::getNodeForPath($user, $sourcePath);
512
-		$targetNode = self::getNodeForPath($user, $targetPath, 'files');
513
-		$run = true;
514
-		$event = new BeforeNodeRestoredEvent($sourceNode, $targetNode, $run);
515
-		$dispatcher = Server::get(IEventDispatcher::class);
516
-		$dispatcher->dispatchTyped($event);
517
-
518
-		if (!$run) {
519
-			return false;
520
-		}
521
-
522
-		$restoreResult = $view->rename($source, $target);
523
-
524
-		// handle the restore result
525
-		if ($restoreResult) {
526
-			$fakeRoot = $view->getRoot();
527
-			$view->chroot('/' . $user . '/files');
528
-			$view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
529
-			$view->chroot($fakeRoot);
530
-			Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => $targetPath, 'trashPath' => $sourcePath]);
531
-
532
-			$sourceNode = self::getNodeForPath($user, $sourcePath);
533
-			$targetNode = self::getNodeForPath($user, $targetPath, 'files');
534
-			$event = new NodeRestoredEvent($sourceNode, $targetNode);
535
-			$dispatcher = Server::get(IEventDispatcher::class);
536
-			$dispatcher->dispatchTyped($event);
537
-
538
-			self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
539
-
540
-			if ($timestamp) {
541
-				$query = Server::get(IDBConnection::class)->getQueryBuilder();
542
-				$query->delete('files_trash')
543
-					->where($query->expr()->eq('user', $query->createNamedParameter($user)))
544
-					->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
545
-					->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
546
-				$query->executeStatement();
547
-			}
548
-
549
-			return true;
550
-		}
551
-
552
-		return false;
553
-	}
554
-
555
-	/**
556
-	 * restore versions from trash bin
557
-	 *
558
-	 * @param View $view file view
559
-	 * @param string $file complete path to file
560
-	 * @param string $filename name of file once it was deleted
561
-	 * @param string $uniqueFilename new file name to restore the file without overwriting existing files
562
-	 * @param string $location location if file
563
-	 * @param int $timestamp deletion time
564
-	 * @return false|null
565
-	 */
566
-	private static function restoreVersions(View $view, $file, $filename, $uniqueFilename, $location, $timestamp) {
567
-		if (Server::get(IAppManager::class)->isEnabledForUser('files_versions')) {
568
-			$user = OC_User::getUser();
569
-			$rootView = new View('/');
570
-
571
-			$target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
572
-
573
-			[$owner, $ownerPath] = self::getUidAndFilename($target);
574
-
575
-			// file has been deleted in between
576
-			if (empty($ownerPath)) {
577
-				return false;
578
-			}
579
-
580
-			if ($timestamp) {
581
-				$versionedFile = $filename;
582
-			} else {
583
-				$versionedFile = $file;
584
-			}
585
-
586
-			if ($view->is_dir('/files_trashbin/versions/' . $file)) {
587
-				$rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
588
-			} elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
589
-				foreach ($versions as $v) {
590
-					if ($timestamp) {
591
-						$rootView->rename($user . '/files_trashbin/versions/' . static::getTrashFilename($versionedFile . '.v' . $v, $timestamp), $owner . '/files_versions/' . $ownerPath . '.v' . $v);
592
-					} else {
593
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
594
-					}
595
-				}
596
-			}
597
-		}
598
-	}
599
-
600
-	/**
601
-	 * delete all files from the trash
602
-	 */
603
-	public static function deleteAll() {
604
-		$user = OC_User::getUser();
605
-		$userRoot = \OC::$server->getUserFolder($user)->getParent();
606
-		$view = new View('/' . $user);
607
-		$fileInfos = $view->getDirectoryContent('files_trashbin/files');
608
-
609
-		try {
610
-			$trash = $userRoot->get('files_trashbin');
611
-		} catch (NotFoundException $e) {
612
-			return false;
613
-		}
614
-
615
-		// Array to store the relative path in (after the file is deleted, the view won't be able to relativise the path anymore)
616
-		$filePaths = [];
617
-		foreach ($fileInfos as $fileInfo) {
618
-			$filePaths[] = $view->getRelativePath($fileInfo->getPath());
619
-		}
620
-		unset($fileInfos); // save memory
621
-
622
-		// Bulk PreDelete-Hook
623
-		\OC_Hook::emit('\OCP\Trashbin', 'preDeleteAll', ['paths' => $filePaths]);
624
-
625
-		// Single-File Hooks
626
-		foreach ($filePaths as $path) {
627
-			self::emitTrashbinPreDelete($path);
628
-		}
629
-
630
-		// actual file deletion
631
-		$trash->delete();
632
-
633
-		$query = Server::get(IDBConnection::class)->getQueryBuilder();
634
-		$query->delete('files_trash')
635
-			->where($query->expr()->eq('user', $query->createNamedParameter($user)));
636
-		$query->executeStatement();
637
-
638
-		// Bulk PostDelete-Hook
639
-		\OC_Hook::emit('\OCP\Trashbin', 'deleteAll', ['paths' => $filePaths]);
640
-
641
-		// Single-File Hooks
642
-		foreach ($filePaths as $path) {
643
-			self::emitTrashbinPostDelete($path);
644
-		}
645
-
646
-		$trash = $userRoot->newFolder('files_trashbin');
647
-		$trash->newFolder('files');
648
-
649
-		return true;
650
-	}
651
-
652
-	/**
653
-	 * wrapper function to emit the 'preDelete' hook of \OCP\Trashbin before a file is deleted
654
-	 *
655
-	 * @param string $path
656
-	 */
657
-	protected static function emitTrashbinPreDelete($path) {
658
-		\OC_Hook::emit('\OCP\Trashbin', 'preDelete', ['path' => $path]);
659
-	}
660
-
661
-	/**
662
-	 * wrapper function to emit the 'delete' hook of \OCP\Trashbin after a file has been deleted
663
-	 *
664
-	 * @param string $path
665
-	 */
666
-	protected static function emitTrashbinPostDelete($path) {
667
-		\OC_Hook::emit('\OCP\Trashbin', 'delete', ['path' => $path]);
668
-	}
669
-
670
-	/**
671
-	 * delete file from trash bin permanently
672
-	 *
673
-	 * @param string $filename path to the file
674
-	 * @param string $user
675
-	 * @param int $timestamp of deletion time
676
-	 *
677
-	 * @return int|float size of deleted files
678
-	 */
679
-	public static function delete($filename, $user, $timestamp = null) {
680
-		$userRoot = \OC::$server->getUserFolder($user)->getParent();
681
-		$view = new View('/' . $user);
682
-		$size = 0;
683
-
684
-		if ($timestamp) {
685
-			$query = Server::get(IDBConnection::class)->getQueryBuilder();
686
-			$query->delete('files_trash')
687
-				->where($query->expr()->eq('user', $query->createNamedParameter($user)))
688
-				->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
689
-				->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
690
-			$query->executeStatement();
691
-
692
-			$file = static::getTrashFilename($filename, $timestamp);
693
-		} else {
694
-			$file = $filename;
695
-		}
696
-
697
-		$size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
698
-
699
-		try {
700
-			$node = $userRoot->get('/files_trashbin/files/' . $file);
701
-		} catch (NotFoundException $e) {
702
-			return $size;
703
-		}
704
-
705
-		if ($node instanceof Folder) {
706
-			$size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
707
-		} elseif ($node instanceof File) {
708
-			$size += $view->filesize('/files_trashbin/files/' . $file);
709
-		}
710
-
711
-		self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
712
-		$node->delete();
713
-		self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
714
-
715
-		return $size;
716
-	}
717
-
718
-	/**
719
-	 * @param string $file
720
-	 * @param string $filename
721
-	 * @param ?int $timestamp
722
-	 */
723
-	private static function deleteVersions(View $view, $file, $filename, $timestamp, string $user): int|float {
724
-		$size = 0;
725
-		if (Server::get(IAppManager::class)->isEnabledForUser('files_versions')) {
726
-			if ($view->is_dir('files_trashbin/versions/' . $file)) {
727
-				$size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
728
-				$view->unlink('files_trashbin/versions/' . $file);
729
-			} elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
730
-				foreach ($versions as $v) {
731
-					if ($timestamp) {
732
-						$size += $view->filesize('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp));
733
-						$view->unlink('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp));
734
-					} else {
735
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
736
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
737
-					}
738
-				}
739
-			}
740
-		}
741
-		return $size;
742
-	}
743
-
744
-	/**
745
-	 * check to see whether a file exists in trashbin
746
-	 *
747
-	 * @param string $filename path to the file
748
-	 * @param int $timestamp of deletion time
749
-	 * @return bool true if file exists, otherwise false
750
-	 */
751
-	public static function file_exists($filename, $timestamp = null) {
752
-		$user = OC_User::getUser();
753
-		$view = new View('/' . $user);
754
-
755
-		if ($timestamp) {
756
-			$filename = static::getTrashFilename($filename, $timestamp);
757
-		}
758
-
759
-		$target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
760
-		return $view->file_exists($target);
761
-	}
762
-
763
-	/**
764
-	 * deletes used space for trash bin in db if user was deleted
765
-	 *
766
-	 * @param string $uid id of deleted user
767
-	 * @return bool result of db delete operation
768
-	 */
769
-	public static function deleteUser($uid) {
770
-		$query = Server::get(IDBConnection::class)->getQueryBuilder();
771
-		$query->delete('files_trash')
772
-			->where($query->expr()->eq('user', $query->createNamedParameter($uid)));
773
-		return (bool)$query->executeStatement();
774
-	}
775
-
776
-	/**
777
-	 * calculate remaining free space for trash bin
778
-	 *
779
-	 * @param int|float $trashbinSize current size of the trash bin
780
-	 * @param string $user
781
-	 * @return int|float available free space for trash bin
782
-	 */
783
-	private static function calculateFreeSpace(int|float $trashbinSize, string $user): int|float {
784
-		$configuredTrashbinSize = static::getConfiguredTrashbinSize($user);
785
-		if ($configuredTrashbinSize > -1) {
786
-			return $configuredTrashbinSize - $trashbinSize;
787
-		}
788
-
789
-		$userObject = Server::get(IUserManager::class)->get($user);
790
-		if (is_null($userObject)) {
791
-			return 0;
792
-		}
793
-		$softQuota = true;
794
-		$quota = $userObject->getQuota();
795
-		if ($quota === null || $quota === 'none') {
796
-			$quota = Filesystem::free_space('/');
797
-			$softQuota = false;
798
-			// inf or unknown free space
799
-			if ($quota < 0) {
800
-				$quota = PHP_INT_MAX;
801
-			}
802
-		} else {
803
-			$quota = Util::computerFileSize($quota);
804
-			// invalid quota
805
-			if ($quota === false) {
806
-				$quota = PHP_INT_MAX;
807
-			}
808
-		}
809
-
810
-		// calculate available space for trash bin
811
-		// subtract size of files and current trash bin size from quota
812
-		if ($softQuota) {
813
-			$userFolder = \OC::$server->getUserFolder($user);
814
-			if (is_null($userFolder)) {
815
-				return 0;
816
-			}
817
-			$free = $quota - $userFolder->getSize(false); // remaining free space for user
818
-			if ($free > 0) {
819
-				$availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $trashbinSize; // how much space can be used for versions
820
-			} else {
821
-				$availableSpace = $free - $trashbinSize;
822
-			}
823
-		} else {
824
-			$availableSpace = $quota;
825
-		}
826
-
827
-		return Util::numericToNumber($availableSpace);
828
-	}
829
-
830
-	/**
831
-	 * resize trash bin if necessary after a new file was added to Nextcloud
832
-	 *
833
-	 * @param string $user user id
834
-	 */
835
-	public static function resizeTrash($user) {
836
-		$size = self::getTrashbinSize($user);
837
-
838
-		$freeSpace = self::calculateFreeSpace($size, $user);
839
-
840
-		if ($freeSpace < 0) {
841
-			self::scheduleExpire($user);
842
-		}
843
-	}
844
-
845
-	/**
846
-	 * clean up the trash bin
847
-	 *
848
-	 * @param string $user
849
-	 */
850
-	public static function expire($user) {
851
-		$trashBinSize = self::getTrashbinSize($user);
852
-		$availableSpace = self::calculateFreeSpace($trashBinSize, $user);
853
-
854
-		$dirContent = Helper::getTrashFiles('/', $user, 'mtime');
855
-
856
-		// delete all files older then $retention_obligation
857
-		[$delSize, $count] = self::deleteExpiredFiles($dirContent, $user);
858
-
859
-		$availableSpace += $delSize;
860
-
861
-		// delete files from trash until we meet the trash bin size limit again
862
-		self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace);
863
-	}
864
-
865
-	/**
866
-	 * @param string $user
867
-	 */
868
-	private static function scheduleExpire($user) {
869
-		// let the admin disable auto expire
870
-		$expiration = Server::get(Expiration::class);
871
-		if ($expiration->isEnabled()) {
872
-			Server::get(IBus::class)->push(new Expire($user));
873
-		}
874
-	}
875
-
876
-	/**
877
-	 * if the size limit for the trash bin is reached, we delete the oldest
878
-	 * files in the trash bin until we meet the limit again
879
-	 *
880
-	 * @param array $files
881
-	 * @param string $user
882
-	 * @param int|float $availableSpace available disc space
883
-	 * @return int|float size of deleted files
884
-	 */
885
-	protected static function deleteFiles(array $files, string $user, int|float $availableSpace): int|float {
886
-		$expiration = Server::get(Expiration::class);
887
-		$size = 0;
888
-
889
-		if ($availableSpace <= 0) {
890
-			foreach ($files as $file) {
891
-				if ($availableSpace <= 0 && $expiration->isExpired($file['mtime'], true)) {
892
-					$tmp = self::delete($file['name'], $user, $file['mtime']);
893
-					Server::get(LoggerInterface::class)->info(
894
-						'remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota) for user "{user}"',
895
-						[
896
-							'app' => 'files_trashbin',
897
-							'user' => $user,
898
-						]
899
-					);
900
-					$availableSpace += $tmp;
901
-					$size += $tmp;
902
-				} else {
903
-					break;
904
-				}
905
-			}
906
-		}
907
-		return $size;
908
-	}
909
-
910
-	/**
911
-	 * delete files older then max storage time
912
-	 *
913
-	 * @param array $files list of files sorted by mtime
914
-	 * @param string $user
915
-	 * @return array{int|float, int} size of deleted files and number of deleted files
916
-	 */
917
-	public static function deleteExpiredFiles($files, $user) {
918
-		$expiration = Server::get(Expiration::class);
919
-		$size = 0;
920
-		$count = 0;
921
-		foreach ($files as $file) {
922
-			$timestamp = $file['mtime'];
923
-			$filename = $file['name'];
924
-			if ($expiration->isExpired($timestamp)) {
925
-				try {
926
-					$size += self::delete($filename, $user, $timestamp);
927
-					$count++;
928
-				} catch (NotPermittedException $e) {
929
-					Server::get(LoggerInterface::class)->warning('Removing "' . $filename . '" from trashbin failed for user "{user}"',
930
-						[
931
-							'exception' => $e,
932
-							'app' => 'files_trashbin',
933
-							'user' => $user,
934
-						]
935
-					);
936
-				}
937
-				Server::get(LoggerInterface::class)->info(
938
-					'Remove "' . $filename . '" from trashbin for user "{user}" because it exceeds max retention obligation term.',
939
-					[
940
-						'app' => 'files_trashbin',
941
-						'user' => $user,
942
-					],
943
-				);
944
-			} else {
945
-				break;
946
-			}
947
-		}
948
-
949
-		return [$size, $count];
950
-	}
951
-
952
-	/**
953
-	 * recursive copy to copy a whole directory
954
-	 *
955
-	 * @param string $source source path, relative to the users files directory
956
-	 * @param string $destination destination path relative to the users root directory
957
-	 * @param View $view file view for the users root directory
958
-	 * @return int|float
959
-	 * @throws Exceptions\CopyRecursiveException
960
-	 */
961
-	private static function copy_recursive($source, $destination, View $view): int|float {
962
-		$size = 0;
963
-		if ($view->is_dir($source)) {
964
-			$view->mkdir($destination);
965
-			$view->touch($destination, $view->filemtime($source));
966
-			foreach ($view->getDirectoryContent($source) as $i) {
967
-				$pathDir = $source . '/' . $i['name'];
968
-				if ($view->is_dir($pathDir)) {
969
-					$size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
970
-				} else {
971
-					$size += $view->filesize($pathDir);
972
-					$result = $view->copy($pathDir, $destination . '/' . $i['name']);
973
-					if (!$result) {
974
-						throw new CopyRecursiveException();
975
-					}
976
-					$view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
977
-				}
978
-			}
979
-		} else {
980
-			$size += $view->filesize($source);
981
-			$result = $view->copy($source, $destination);
982
-			if (!$result) {
983
-				throw new CopyRecursiveException();
984
-			}
985
-			$view->touch($destination, $view->filemtime($source));
986
-		}
987
-		return $size;
988
-	}
989
-
990
-	/**
991
-	 * find all versions which belong to the file we want to restore
992
-	 *
993
-	 * @param string $filename name of the file which should be restored
994
-	 * @param int $timestamp timestamp when the file was deleted
995
-	 */
996
-	private static function getVersionsFromTrash($filename, $timestamp, string $user): array {
997
-		$view = new View('/' . $user . '/files_trashbin/versions');
998
-		$versions = [];
999
-
1000
-		/** @var \OC\Files\Storage\Storage $storage */
1001
-		[$storage,] = $view->resolvePath('/');
1002
-
1003
-		$pattern = Server::get(IDBConnection::class)->escapeLikeParameter(basename($filename));
1004
-		if ($timestamp) {
1005
-			// fetch for old versions
1006
-			$escapedTimestamp = Server::get(IDBConnection::class)->escapeLikeParameter((string)$timestamp);
1007
-			$pattern .= '.v%.d' . $escapedTimestamp;
1008
-			$offset = -strlen($escapedTimestamp) - 2;
1009
-		} else {
1010
-			$pattern .= '.v%';
1011
-		}
1012
-
1013
-		// Manually fetch all versions from the file cache to be able to filter them by their parent
1014
-		$cache = $storage->getCache('');
1015
-		$query = new CacheQueryBuilder(
1016
-			Server::get(IDBConnection::class)->getQueryBuilder(),
1017
-			Server::get(IFilesMetadataManager::class),
1018
-		);
1019
-		$normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/' . $filename)), '/');
1020
-		$parentId = $cache->getId($normalizedParentPath);
1021
-		if ($parentId === -1) {
1022
-			return [];
1023
-		}
1024
-
1025
-		$query->selectFileCache()
1026
-			->whereStorageId($cache->getNumericStorageId())
1027
-			->andWhere($query->expr()->eq('parent', $query->createNamedParameter($parentId)))
1028
-			->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern)));
1029
-
1030
-		$result = $query->executeQuery();
1031
-		$entries = $result->fetchAllAssociative();
1032
-		$result->closeCursor();
1033
-
1034
-		/** @var CacheEntry[] $matches */
1035
-		$matches = array_map(function (array $data) {
1036
-			return Cache::cacheEntryFromData($data, Server::get(IMimeTypeLoader::class));
1037
-		}, $entries);
1038
-
1039
-		foreach ($matches as $ma) {
1040
-			if ($timestamp) {
1041
-				$parts = explode('.v', substr($ma['path'], 0, $offset));
1042
-				$versions[] = end($parts);
1043
-			} else {
1044
-				$parts = explode('.v', $ma['path']);
1045
-				$versions[] = end($parts);
1046
-			}
1047
-		}
1048
-
1049
-		return $versions;
1050
-	}
1051
-
1052
-	/**
1053
-	 * find unique extension for restored file if a file with the same name already exists
1054
-	 *
1055
-	 * @param string $location where the file should be restored
1056
-	 * @param string $filename name of the file
1057
-	 * @param View $view filesystem view relative to users root directory
1058
-	 * @return string with unique extension
1059
-	 */
1060
-	private static function getUniqueFilename($location, $filename, View $view) {
1061
-		$ext = pathinfo($filename, PATHINFO_EXTENSION);
1062
-		$name = pathinfo($filename, PATHINFO_FILENAME);
1063
-		$l = Util::getL10N('files_trashbin');
1064
-
1065
-		$location = '/' . trim($location, '/');
1066
-
1067
-		// if extension is not empty we set a dot in front of it
1068
-		if ($ext !== '') {
1069
-			$ext = '.' . $ext;
1070
-		}
1071
-
1072
-		if ($view->file_exists('files' . $location . '/' . $filename)) {
1073
-			$i = 2;
1074
-			$uniqueName = $name . ' (' . $l->t('restored') . ')' . $ext;
1075
-			while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
1076
-				$uniqueName = $name . ' (' . $l->t('restored') . ' ' . $i . ')' . $ext;
1077
-				$i++;
1078
-			}
1079
-
1080
-			return $uniqueName;
1081
-		}
1082
-
1083
-		return $filename;
1084
-	}
1085
-
1086
-	/**
1087
-	 * get the size from a given root folder
1088
-	 *
1089
-	 * @param View $view file view on the root folder
1090
-	 * @return int|float size of the folder
1091
-	 */
1092
-	private static function calculateSize(View $view): int|float {
1093
-		$root = Server::get(IConfig::class)->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
1094
-		if (!file_exists($root)) {
1095
-			return 0;
1096
-		}
1097
-		$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), \RecursiveIteratorIterator::CHILD_FIRST);
1098
-		$size = 0;
1099
-
1100
-		/**
1101
-		 * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
1102
-		 * This bug is fixed in PHP 5.5.9 or before
1103
-		 * See #8376
1104
-		 */
1105
-		$iterator->rewind();
1106
-		while ($iterator->valid()) {
1107
-			$path = $iterator->current();
1108
-			$relpath = substr($path, strlen($root) - 1);
1109
-			if (!$view->is_dir($relpath)) {
1110
-				$size += $view->filesize($relpath);
1111
-			}
1112
-			$iterator->next();
1113
-		}
1114
-		return $size;
1115
-	}
1116
-
1117
-	/**
1118
-	 * get current size of trash bin from a given user
1119
-	 *
1120
-	 * @param string $user user who owns the trash bin
1121
-	 * @return int|float trash bin size
1122
-	 */
1123
-	private static function getTrashbinSize(string $user): int|float {
1124
-		$view = new View('/' . $user);
1125
-		$fileInfo = $view->getFileInfo('/files_trashbin');
1126
-		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
1127
-	}
1128
-
1129
-	/**
1130
-	 * check if trash bin is empty for a given user
1131
-	 *
1132
-	 * @param string $user
1133
-	 * @return bool
1134
-	 */
1135
-	public static function isEmpty($user) {
1136
-		$view = new View('/' . $user . '/files_trashbin');
1137
-		if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
1138
-			while (($file = readdir($dh)) !== false) {
1139
-				if (!Filesystem::isIgnoredDir($file)) {
1140
-					return false;
1141
-				}
1142
-			}
1143
-		}
1144
-		return true;
1145
-	}
1146
-
1147
-	/**
1148
-	 * @param $path
1149
-	 * @return string
1150
-	 */
1151
-	public static function preview_icon($path) {
1152
-		return Server::get(IURLGenerator::class)->linkToRoute('core_ajax_trashbin_preview', ['x' => 32, 'y' => 32, 'file' => $path]);
1153
-	}
1154
-
1155
-	/**
1156
-	 * Return the filename used in the trash bin
1157
-	 */
1158
-	public static function getTrashFilename(string $filename, int $timestamp): string {
1159
-		$trashFilename = $filename . '.d' . $timestamp;
1160
-		$length = strlen($trashFilename);
1161
-		// oc_filecache `name` column has a limit of 250 chars
1162
-		$maxLength = 250;
1163
-		if ($length > $maxLength) {
1164
-			$trashFilename = substr_replace(
1165
-				$trashFilename,
1166
-				'',
1167
-				$maxLength / 2,
1168
-				$length - $maxLength
1169
-			);
1170
-		}
1171
-		return $trashFilename;
1172
-	}
1173
-
1174
-	private static function getNodeForPath(string $user, string $path, string $baseDir = 'files_trashbin/files'): Node {
1175
-		$rootFolder = Server::get(IRootFolder::class);
1176
-		$path = ltrim($path, '/');
1177
-
1178
-		$userFolder = $rootFolder->getUserFolder($user);
1179
-		/** @var Folder $trashFolder */
1180
-		$trashFolder = $userFolder->getParent()->get($baseDir);
1181
-		try {
1182
-			return $trashFolder->get($path);
1183
-		} catch (NotFoundException $ex) {
1184
-		}
1185
-
1186
-		$view = Server::get(View::class);
1187
-		$fullPath = '/' . $user . '/' . $baseDir . '/' . $path;
1188
-
1189
-		if (Filesystem::is_dir($path)) {
1190
-			return new NonExistingFolder($rootFolder, $view, $fullPath);
1191
-		} else {
1192
-			return new NonExistingFile($rootFolder, $view, $fullPath);
1193
-		}
1194
-	}
1195
-
1196
-	/**
1197
-	 * in case the request is authed, and user token is from a federated share
1198
-	 * we use shared_with as initiator of the deletion
1199
-	 */
1200
-	private static function overwriteDeletedBy(string $user) {
1201
-		try {
1202
-			$request = Server::get(IRequest::class);
1203
-			/** @psalm-suppress NoInterfaceProperties */
1204
-			$token = $request->server['PHP_AUTH_USER'] ?? '';
1205
-			if ($token === '') {
1206
-				return $user;
1207
-			}
1208
-
1209
-			$federatedShareProvider = Server::get(\OCA\FederatedFileSharing\FederatedShareProvider::class);
1210
-			$share = $federatedShareProvider->getShareByToken($token);
1211
-
1212
-			return $share->getSharedWith();
1213
-		} catch (NotFoundExceptionInterface|ContainerExceptionInterface|ShareNotFound) {
1214
-		}
1215
-
1216
-		return $user;
1217
-	}
1218
-
1219
-	public function handle(Event $event): void {
1220
-		if ($event instanceof BeforeNodeDeletedEvent) {
1221
-			self::ensureFileScannedHook($event->getNode());
1222
-		}
1223
-	}
59
+    // unit: percentage; 50% of available disk space/quota
60
+    public const DEFAULTMAXSIZE = 50;
61
+
62
+    /**
63
+     * Ensure we don't need to scan the file during the move to trash
64
+     * by triggering the scan in the pre-hook
65
+     */
66
+    public static function ensureFileScannedHook(Node $node): void {
67
+        try {
68
+            self::getUidAndFilename($node->getPath());
69
+        } catch (NotFoundException $e) {
70
+            // Nothing to scan for non existing files
71
+        }
72
+    }
73
+
74
+    /**
75
+     * get the UID of the owner of the file and the path to the file relative to
76
+     * owners files folder
77
+     *
78
+     * @param string $filename
79
+     * @return array
80
+     * @throws NoUserException
81
+     */
82
+    public static function getUidAndFilename($filename) {
83
+        $uid = Filesystem::getOwner($filename);
84
+        $userManager = Server::get(IUserManager::class);
85
+        // if the user with the UID doesn't exists, e.g. because the UID points
86
+        // to a remote user with a federated cloud ID we use the current logged-in
87
+        // user. We need a valid local user to move the file to the right trash bin
88
+        if (!$userManager->userExists($uid)) {
89
+            $uid = OC_User::getUser();
90
+        }
91
+        if (!$uid) {
92
+            // no owner, usually because of share link from ext storage
93
+            return [null, null];
94
+        }
95
+        Filesystem::initMountPoints($uid);
96
+        if ($uid !== OC_User::getUser()) {
97
+            $info = Filesystem::getFileInfo($filename);
98
+            $ownerView = new View('/' . $uid . '/files');
99
+            try {
100
+                $filename = $ownerView->getPath($info['fileid']);
101
+            } catch (NotFoundException $e) {
102
+                $filename = null;
103
+            }
104
+        }
105
+        return [$uid, $filename];
106
+    }
107
+
108
+    /**
109
+     * get original location and deleted by of files for user
110
+     *
111
+     * @param string $user
112
+     * @return array<string, array<string, array{location: string, deletedBy: string}>>
113
+     */
114
+    public static function getExtraData($user) {
115
+        $query = Server::get(IDBConnection::class)->getQueryBuilder();
116
+        $query->select('id', 'timestamp', 'location', 'deleted_by')
117
+            ->from('files_trash')
118
+            ->where($query->expr()->eq('user', $query->createNamedParameter($user)));
119
+        $result = $query->executeQuery();
120
+        $array = [];
121
+        foreach ($result->iterateAssociative() as $row) {
122
+            $array[$row['id']][$row['timestamp']] = [
123
+                'location' => (string)$row['location'],
124
+                'deletedBy' => (string)$row['deleted_by'],
125
+            ];
126
+        }
127
+        $result->closeCursor();
128
+        return $array;
129
+    }
130
+
131
+    /**
132
+     * get original location of file
133
+     *
134
+     * @param string $user
135
+     * @param string $filename
136
+     * @param string $timestamp
137
+     * @return string|false original location
138
+     */
139
+    public static function getLocation($user, $filename, $timestamp) {
140
+        $query = Server::get(IDBConnection::class)->getQueryBuilder();
141
+        $query->select('location')
142
+            ->from('files_trash')
143
+            ->where($query->expr()->eq('user', $query->createNamedParameter($user)))
144
+            ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
145
+            ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
146
+
147
+        $result = $query->executeQuery();
148
+        $row = $result->fetchAssociative();
149
+        $result->closeCursor();
150
+
151
+        if (isset($row['location'])) {
152
+            return $row['location'];
153
+        } else {
154
+            return false;
155
+        }
156
+    }
157
+
158
+    /** @param string $user */
159
+    private static function setUpTrash($user): void {
160
+        $view = new View('/' . $user);
161
+        if (!$view->is_dir('files_trashbin')) {
162
+            $view->mkdir('files_trashbin');
163
+        }
164
+        if (!$view->is_dir('files_trashbin/files')) {
165
+            $view->mkdir('files_trashbin/files');
166
+        }
167
+        if (!$view->is_dir('files_trashbin/versions')) {
168
+            $view->mkdir('files_trashbin/versions');
169
+        }
170
+        if (!$view->is_dir('files_trashbin/keys')) {
171
+            $view->mkdir('files_trashbin/keys');
172
+        }
173
+    }
174
+
175
+
176
+    /**
177
+     * copy file to owners trash
178
+     *
179
+     * @param string $sourcePath
180
+     * @param string $owner
181
+     * @param string $targetPath
182
+     * @param string $user
183
+     * @param int $timestamp
184
+     */
185
+    private static function copyFilesToUser($sourcePath, $owner, $targetPath, $user, $timestamp): void {
186
+        self::setUpTrash($owner);
187
+
188
+        $targetFilename = basename($targetPath);
189
+        $targetLocation = dirname($targetPath);
190
+
191
+        $sourceFilename = basename($sourcePath);
192
+
193
+        $view = new View('/');
194
+
195
+        $target = $user . '/files_trashbin/files/' . static::getTrashFilename($targetFilename, $timestamp);
196
+        $source = $owner . '/files_trashbin/files/' . static::getTrashFilename($sourceFilename, $timestamp);
197
+        $free = $view->free_space($target);
198
+        $isUnknownOrUnlimitedFreeSpace = $free < 0;
199
+        $isEnoughFreeSpaceLeft = $view->filesize($source) < $free;
200
+        if ($isUnknownOrUnlimitedFreeSpace || $isEnoughFreeSpaceLeft) {
201
+            self::copy_recursive($source, $target, $view);
202
+        }
203
+
204
+
205
+        if ($view->file_exists($target)) {
206
+            $query = Server::get(IDBConnection::class)->getQueryBuilder();
207
+            $query->insert('files_trash')
208
+                ->setValue('id', $query->createNamedParameter($targetFilename))
209
+                ->setValue('timestamp', $query->createNamedParameter($timestamp))
210
+                ->setValue('location', $query->createNamedParameter($targetLocation))
211
+                ->setValue('user', $query->createNamedParameter($user))
212
+                ->setValue('deleted_by', $query->createNamedParameter($user));
213
+            $result = $query->executeStatement();
214
+            if (!$result) {
215
+                Server::get(LoggerInterface::class)->error('trash bin database couldn\'t be updated for the files owner', ['app' => 'files_trashbin']);
216
+            }
217
+        }
218
+    }
219
+
220
+
221
+    /**
222
+     * move file to the trash bin
223
+     *
224
+     * @param string $file_path path to the deleted file/directory relative to the files root directory
225
+     * @param bool $ownerOnly delete for owner only (if file gets moved out of a shared folder)
226
+     *
227
+     * @return bool
228
+     */
229
+    public static function move2trash($file_path, $ownerOnly = false) {
230
+        // get the user for which the filesystem is setup
231
+        $root = Filesystem::getRoot();
232
+        [, $user] = explode('/', $root);
233
+        [$owner, $ownerPath] = self::getUidAndFilename($file_path);
234
+
235
+        // if no owner found (ex: ext storage + share link), will use the current user's trashbin then
236
+        if (is_null($owner)) {
237
+            $owner = $user;
238
+            $ownerPath = $file_path;
239
+        }
240
+
241
+        $ownerView = new View('/' . $owner);
242
+
243
+        // file has been deleted in between
244
+        if (is_null($ownerPath) || $ownerPath === '') {
245
+            return true;
246
+        }
247
+
248
+        $sourceInfo = $ownerView->getFileInfo('/files/' . $ownerPath);
249
+
250
+        if ($sourceInfo === false) {
251
+            return true;
252
+        }
253
+
254
+        self::setUpTrash($user);
255
+        if ($owner !== $user) {
256
+            // also setup for owner
257
+            self::setUpTrash($owner);
258
+        }
259
+
260
+        $path_parts = pathinfo($ownerPath);
261
+
262
+        $filename = $path_parts['basename'];
263
+        $location = $path_parts['dirname'];
264
+        /** @var ITimeFactory $timeFactory */
265
+        $timeFactory = Server::get(ITimeFactory::class);
266
+        $timestamp = $timeFactory->getTime();
267
+
268
+        $lockingProvider = Server::get(ILockingProvider::class);
269
+
270
+        // disable proxy to prevent recursive calls
271
+        $trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp);
272
+        $gotLock = false;
273
+
274
+        do {
275
+            /** @var ILockingStorage & IStorage $trashStorage */
276
+            [$trashStorage, $trashInternalPath] = $ownerView->resolvePath($trashPath);
277
+            try {
278
+                $trashStorage->acquireLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
279
+                $gotLock = true;
280
+            } catch (LockedException $e) {
281
+                // a file with the same name is being deleted concurrently
282
+                // nudge the timestamp a bit to resolve the conflict
283
+
284
+                $timestamp = $timestamp + 1;
285
+
286
+                $trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp);
287
+            }
288
+        } while (!$gotLock);
289
+
290
+        $sourceStorage = $sourceInfo->getStorage();
291
+        $sourceInternalPath = $sourceInfo->getInternalPath();
292
+
293
+        if ($trashStorage->file_exists($trashInternalPath)) {
294
+            $trashStorage->unlink($trashInternalPath);
295
+        }
296
+
297
+        $configuredTrashbinSize = static::getConfiguredTrashbinSize($owner);
298
+        if ($configuredTrashbinSize >= 0 && $sourceInfo->getSize() >= $configuredTrashbinSize) {
299
+            return false;
300
+        }
301
+
302
+        try {
303
+            $moveSuccessful = true;
304
+
305
+            $inCache = $sourceStorage->getCache()->inCache($sourceInternalPath);
306
+            $trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
307
+            if ($inCache) {
308
+                $trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
309
+            }
310
+        } catch (CopyRecursiveException $e) {
311
+            $moveSuccessful = false;
312
+            if ($trashStorage->file_exists($trashInternalPath)) {
313
+                $trashStorage->unlink($trashInternalPath);
314
+            }
315
+            Server::get(LoggerInterface::class)->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']);
316
+        }
317
+
318
+        if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
319
+            if ($sourceStorage->is_dir($sourceInternalPath)) {
320
+                $sourceStorage->rmdir($sourceInternalPath);
321
+            } else {
322
+                $sourceStorage->unlink($sourceInternalPath);
323
+            }
324
+
325
+            if ($sourceStorage->file_exists($sourceInternalPath)) {
326
+                // undo the cache move
327
+                $sourceStorage->getUpdater()->renameFromStorage($trashStorage, $trashInternalPath, $sourceInternalPath);
328
+            } else {
329
+                $trashStorage->getUpdater()->remove($trashInternalPath);
330
+            }
331
+            return false;
332
+        }
333
+
334
+        if ($moveSuccessful) {
335
+            // there is still a possibility that the file has been deleted by a remote user
336
+            $deletedBy = self::overwriteDeletedBy($user);
337
+
338
+            $query = Server::get(IDBConnection::class)->getQueryBuilder();
339
+            $query->insert('files_trash')
340
+                ->setValue('id', $query->createNamedParameter($filename))
341
+                ->setValue('timestamp', $query->createNamedParameter($timestamp))
342
+                ->setValue('location', $query->createNamedParameter($location))
343
+                ->setValue('user', $query->createNamedParameter($owner))
344
+                ->setValue('deleted_by', $query->createNamedParameter($deletedBy));
345
+            $result = $query->executeStatement();
346
+            if (!$result) {
347
+                Server::get(LoggerInterface::class)->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']);
348
+            }
349
+            Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path),
350
+                'trashPath' => Filesystem::normalizePath(static::getTrashFilename($filename, $timestamp))]);
351
+
352
+            self::retainVersions($filename, $owner, $ownerPath, $timestamp);
353
+
354
+            // if owner !== user we need to also add a copy to the users trash
355
+            if ($user !== $owner && $ownerOnly === false) {
356
+                self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp);
357
+            }
358
+        }
359
+
360
+        $trashStorage->releaseLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
361
+
362
+        self::scheduleExpire($user);
363
+
364
+        // if owner !== user we also need to update the owners trash size
365
+        if ($owner !== $user) {
366
+            self::scheduleExpire($owner);
367
+        }
368
+
369
+        return $moveSuccessful;
370
+    }
371
+
372
+    private static function getConfiguredTrashbinSize(string $user): int|float {
373
+        $userConfig = Server::get(IUserConfig::class);
374
+        $userTrashbinSize = $userConfig->getValueString($user, 'files_trashbin', 'trashbin_size', '-1');
375
+        if (is_numeric($userTrashbinSize) && ($userTrashbinSize > -1)) {
376
+            return Util::numericToNumber($userTrashbinSize);
377
+        }
378
+
379
+        $appConfig = Server::get(IAppConfig::class);
380
+        $systemTrashbinSize = $appConfig->getValueString('files_trashbin', 'trashbin_size', '-1');
381
+        if (is_numeric($systemTrashbinSize)) {
382
+            return Util::numericToNumber($systemTrashbinSize);
383
+        }
384
+        return -1;
385
+    }
386
+
387
+    /**
388
+     * Move file versions to trash so that they can be restored later
389
+     *
390
+     * @param string $filename of deleted file
391
+     * @param string $owner owner user id
392
+     * @param string $ownerPath path relative to the owner's home storage
393
+     * @param int $timestamp when the file was deleted
394
+     */
395
+    private static function retainVersions($filename, $owner, $ownerPath, $timestamp) {
396
+        if (Server::get(IAppManager::class)->isEnabledForUser('files_versions') && !empty($ownerPath)) {
397
+            $user = OC_User::getUser();
398
+            $rootView = new View('/');
399
+
400
+            if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
401
+                if ($owner !== $user) {
402
+                    self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . static::getTrashFilename(basename($ownerPath), $timestamp), $rootView);
403
+                }
404
+                self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . static::getTrashFilename($filename, $timestamp));
405
+            } elseif ($versions = Storage::getVersions($owner, $ownerPath)) {
406
+                foreach ($versions as $v) {
407
+                    if ($owner !== $user) {
408
+                        self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . static::getTrashFilename($v['name'] . '.v' . $v['version'], $timestamp));
409
+                    }
410
+                    self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v['version'], $timestamp));
411
+                }
412
+            }
413
+        }
414
+    }
415
+
416
+    /**
417
+     * Move a file or folder on storage level
418
+     *
419
+     * @param View $view
420
+     * @param string $source
421
+     * @param string $target
422
+     * @return bool
423
+     */
424
+    private static function move(View $view, $source, $target) {
425
+        /** @var \OC\Files\Storage\Storage $sourceStorage */
426
+        [$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
427
+        /** @var \OC\Files\Storage\Storage $targetStorage */
428
+        [$targetStorage, $targetInternalPath] = $view->resolvePath($target);
429
+        /** @var \OC\Files\Storage\Storage $ownerTrashStorage */
430
+
431
+        $result = $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
432
+        if ($result) {
433
+            $targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
434
+        }
435
+        return $result;
436
+    }
437
+
438
+    /**
439
+     * Copy a file or folder on storage level
440
+     *
441
+     * @param View $view
442
+     * @param string $source
443
+     * @param string $target
444
+     * @return bool
445
+     */
446
+    private static function copy(View $view, $source, $target) {
447
+        /** @var \OC\Files\Storage\Storage $sourceStorage */
448
+        [$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
449
+        /** @var \OC\Files\Storage\Storage $targetStorage */
450
+        [$targetStorage, $targetInternalPath] = $view->resolvePath($target);
451
+        /** @var \OC\Files\Storage\Storage $ownerTrashStorage */
452
+
453
+        $result = $targetStorage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
454
+        if ($result) {
455
+            $targetStorage->getUpdater()->update($targetInternalPath);
456
+        }
457
+        return $result;
458
+    }
459
+
460
+    /**
461
+     * Restore a file or folder from trash bin
462
+     *
463
+     * @param string $file path to the deleted file/folder relative to "files_trashbin/files/",
464
+     *                     including the timestamp suffix ".d12345678"
465
+     * @param string $filename name of the file/folder
466
+     * @param int $timestamp time when the file/folder was deleted
467
+     *
468
+     * @return bool true on success, false otherwise
469
+     */
470
+    public static function restore($file, $filename, $timestamp) {
471
+        $user = OC_User::getUser();
472
+        if (!$user) {
473
+            throw new \Exception('Tried to restore a file while not logged in');
474
+        }
475
+        $view = new View('/' . $user);
476
+
477
+        $location = '';
478
+        if ($timestamp) {
479
+            $location = self::getLocation($user, $filename, $timestamp);
480
+            if ($location === false) {
481
+                Server::get(LoggerInterface::class)->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']);
482
+            } else {
483
+                // if location no longer exists, restore file in the root directory
484
+                if ($location !== '/'
485
+                    && (!$view->is_dir('files/' . $location)
486
+                        || !$view->isCreatable('files/' . $location))
487
+                ) {
488
+                    $location = '';
489
+                }
490
+            }
491
+        }
492
+
493
+        // we need a  extension in case a file/dir with the same name already exists
494
+        $uniqueFilename = self::getUniqueFilename($location, $filename, $view);
495
+
496
+        $source = Filesystem::normalizePath('files_trashbin/files/' . $file);
497
+        $target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
498
+        if (!$view->file_exists($source)) {
499
+            return false;
500
+        }
501
+        $mtime = $view->filemtime($source);
502
+
503
+        // restore file
504
+        if (!$view->isCreatable(dirname($target))) {
505
+            throw new NotPermittedException("Can't restore trash item because the target folder is not writable");
506
+        }
507
+
508
+        $sourcePath = Filesystem::normalizePath($file);
509
+        $targetPath = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
510
+
511
+        $sourceNode = self::getNodeForPath($user, $sourcePath);
512
+        $targetNode = self::getNodeForPath($user, $targetPath, 'files');
513
+        $run = true;
514
+        $event = new BeforeNodeRestoredEvent($sourceNode, $targetNode, $run);
515
+        $dispatcher = Server::get(IEventDispatcher::class);
516
+        $dispatcher->dispatchTyped($event);
517
+
518
+        if (!$run) {
519
+            return false;
520
+        }
521
+
522
+        $restoreResult = $view->rename($source, $target);
523
+
524
+        // handle the restore result
525
+        if ($restoreResult) {
526
+            $fakeRoot = $view->getRoot();
527
+            $view->chroot('/' . $user . '/files');
528
+            $view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
529
+            $view->chroot($fakeRoot);
530
+            Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => $targetPath, 'trashPath' => $sourcePath]);
531
+
532
+            $sourceNode = self::getNodeForPath($user, $sourcePath);
533
+            $targetNode = self::getNodeForPath($user, $targetPath, 'files');
534
+            $event = new NodeRestoredEvent($sourceNode, $targetNode);
535
+            $dispatcher = Server::get(IEventDispatcher::class);
536
+            $dispatcher->dispatchTyped($event);
537
+
538
+            self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
539
+
540
+            if ($timestamp) {
541
+                $query = Server::get(IDBConnection::class)->getQueryBuilder();
542
+                $query->delete('files_trash')
543
+                    ->where($query->expr()->eq('user', $query->createNamedParameter($user)))
544
+                    ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
545
+                    ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
546
+                $query->executeStatement();
547
+            }
548
+
549
+            return true;
550
+        }
551
+
552
+        return false;
553
+    }
554
+
555
+    /**
556
+     * restore versions from trash bin
557
+     *
558
+     * @param View $view file view
559
+     * @param string $file complete path to file
560
+     * @param string $filename name of file once it was deleted
561
+     * @param string $uniqueFilename new file name to restore the file without overwriting existing files
562
+     * @param string $location location if file
563
+     * @param int $timestamp deletion time
564
+     * @return false|null
565
+     */
566
+    private static function restoreVersions(View $view, $file, $filename, $uniqueFilename, $location, $timestamp) {
567
+        if (Server::get(IAppManager::class)->isEnabledForUser('files_versions')) {
568
+            $user = OC_User::getUser();
569
+            $rootView = new View('/');
570
+
571
+            $target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
572
+
573
+            [$owner, $ownerPath] = self::getUidAndFilename($target);
574
+
575
+            // file has been deleted in between
576
+            if (empty($ownerPath)) {
577
+                return false;
578
+            }
579
+
580
+            if ($timestamp) {
581
+                $versionedFile = $filename;
582
+            } else {
583
+                $versionedFile = $file;
584
+            }
585
+
586
+            if ($view->is_dir('/files_trashbin/versions/' . $file)) {
587
+                $rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
588
+            } elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
589
+                foreach ($versions as $v) {
590
+                    if ($timestamp) {
591
+                        $rootView->rename($user . '/files_trashbin/versions/' . static::getTrashFilename($versionedFile . '.v' . $v, $timestamp), $owner . '/files_versions/' . $ownerPath . '.v' . $v);
592
+                    } else {
593
+                        $rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
594
+                    }
595
+                }
596
+            }
597
+        }
598
+    }
599
+
600
+    /**
601
+     * delete all files from the trash
602
+     */
603
+    public static function deleteAll() {
604
+        $user = OC_User::getUser();
605
+        $userRoot = \OC::$server->getUserFolder($user)->getParent();
606
+        $view = new View('/' . $user);
607
+        $fileInfos = $view->getDirectoryContent('files_trashbin/files');
608
+
609
+        try {
610
+            $trash = $userRoot->get('files_trashbin');
611
+        } catch (NotFoundException $e) {
612
+            return false;
613
+        }
614
+
615
+        // Array to store the relative path in (after the file is deleted, the view won't be able to relativise the path anymore)
616
+        $filePaths = [];
617
+        foreach ($fileInfos as $fileInfo) {
618
+            $filePaths[] = $view->getRelativePath($fileInfo->getPath());
619
+        }
620
+        unset($fileInfos); // save memory
621
+
622
+        // Bulk PreDelete-Hook
623
+        \OC_Hook::emit('\OCP\Trashbin', 'preDeleteAll', ['paths' => $filePaths]);
624
+
625
+        // Single-File Hooks
626
+        foreach ($filePaths as $path) {
627
+            self::emitTrashbinPreDelete($path);
628
+        }
629
+
630
+        // actual file deletion
631
+        $trash->delete();
632
+
633
+        $query = Server::get(IDBConnection::class)->getQueryBuilder();
634
+        $query->delete('files_trash')
635
+            ->where($query->expr()->eq('user', $query->createNamedParameter($user)));
636
+        $query->executeStatement();
637
+
638
+        // Bulk PostDelete-Hook
639
+        \OC_Hook::emit('\OCP\Trashbin', 'deleteAll', ['paths' => $filePaths]);
640
+
641
+        // Single-File Hooks
642
+        foreach ($filePaths as $path) {
643
+            self::emitTrashbinPostDelete($path);
644
+        }
645
+
646
+        $trash = $userRoot->newFolder('files_trashbin');
647
+        $trash->newFolder('files');
648
+
649
+        return true;
650
+    }
651
+
652
+    /**
653
+     * wrapper function to emit the 'preDelete' hook of \OCP\Trashbin before a file is deleted
654
+     *
655
+     * @param string $path
656
+     */
657
+    protected static function emitTrashbinPreDelete($path) {
658
+        \OC_Hook::emit('\OCP\Trashbin', 'preDelete', ['path' => $path]);
659
+    }
660
+
661
+    /**
662
+     * wrapper function to emit the 'delete' hook of \OCP\Trashbin after a file has been deleted
663
+     *
664
+     * @param string $path
665
+     */
666
+    protected static function emitTrashbinPostDelete($path) {
667
+        \OC_Hook::emit('\OCP\Trashbin', 'delete', ['path' => $path]);
668
+    }
669
+
670
+    /**
671
+     * delete file from trash bin permanently
672
+     *
673
+     * @param string $filename path to the file
674
+     * @param string $user
675
+     * @param int $timestamp of deletion time
676
+     *
677
+     * @return int|float size of deleted files
678
+     */
679
+    public static function delete($filename, $user, $timestamp = null) {
680
+        $userRoot = \OC::$server->getUserFolder($user)->getParent();
681
+        $view = new View('/' . $user);
682
+        $size = 0;
683
+
684
+        if ($timestamp) {
685
+            $query = Server::get(IDBConnection::class)->getQueryBuilder();
686
+            $query->delete('files_trash')
687
+                ->where($query->expr()->eq('user', $query->createNamedParameter($user)))
688
+                ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
689
+                ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
690
+            $query->executeStatement();
691
+
692
+            $file = static::getTrashFilename($filename, $timestamp);
693
+        } else {
694
+            $file = $filename;
695
+        }
696
+
697
+        $size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
698
+
699
+        try {
700
+            $node = $userRoot->get('/files_trashbin/files/' . $file);
701
+        } catch (NotFoundException $e) {
702
+            return $size;
703
+        }
704
+
705
+        if ($node instanceof Folder) {
706
+            $size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
707
+        } elseif ($node instanceof File) {
708
+            $size += $view->filesize('/files_trashbin/files/' . $file);
709
+        }
710
+
711
+        self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
712
+        $node->delete();
713
+        self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
714
+
715
+        return $size;
716
+    }
717
+
718
+    /**
719
+     * @param string $file
720
+     * @param string $filename
721
+     * @param ?int $timestamp
722
+     */
723
+    private static function deleteVersions(View $view, $file, $filename, $timestamp, string $user): int|float {
724
+        $size = 0;
725
+        if (Server::get(IAppManager::class)->isEnabledForUser('files_versions')) {
726
+            if ($view->is_dir('files_trashbin/versions/' . $file)) {
727
+                $size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
728
+                $view->unlink('files_trashbin/versions/' . $file);
729
+            } elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
730
+                foreach ($versions as $v) {
731
+                    if ($timestamp) {
732
+                        $size += $view->filesize('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp));
733
+                        $view->unlink('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp));
734
+                    } else {
735
+                        $size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
736
+                        $view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
737
+                    }
738
+                }
739
+            }
740
+        }
741
+        return $size;
742
+    }
743
+
744
+    /**
745
+     * check to see whether a file exists in trashbin
746
+     *
747
+     * @param string $filename path to the file
748
+     * @param int $timestamp of deletion time
749
+     * @return bool true if file exists, otherwise false
750
+     */
751
+    public static function file_exists($filename, $timestamp = null) {
752
+        $user = OC_User::getUser();
753
+        $view = new View('/' . $user);
754
+
755
+        if ($timestamp) {
756
+            $filename = static::getTrashFilename($filename, $timestamp);
757
+        }
758
+
759
+        $target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
760
+        return $view->file_exists($target);
761
+    }
762
+
763
+    /**
764
+     * deletes used space for trash bin in db if user was deleted
765
+     *
766
+     * @param string $uid id of deleted user
767
+     * @return bool result of db delete operation
768
+     */
769
+    public static function deleteUser($uid) {
770
+        $query = Server::get(IDBConnection::class)->getQueryBuilder();
771
+        $query->delete('files_trash')
772
+            ->where($query->expr()->eq('user', $query->createNamedParameter($uid)));
773
+        return (bool)$query->executeStatement();
774
+    }
775
+
776
+    /**
777
+     * calculate remaining free space for trash bin
778
+     *
779
+     * @param int|float $trashbinSize current size of the trash bin
780
+     * @param string $user
781
+     * @return int|float available free space for trash bin
782
+     */
783
+    private static function calculateFreeSpace(int|float $trashbinSize, string $user): int|float {
784
+        $configuredTrashbinSize = static::getConfiguredTrashbinSize($user);
785
+        if ($configuredTrashbinSize > -1) {
786
+            return $configuredTrashbinSize - $trashbinSize;
787
+        }
788
+
789
+        $userObject = Server::get(IUserManager::class)->get($user);
790
+        if (is_null($userObject)) {
791
+            return 0;
792
+        }
793
+        $softQuota = true;
794
+        $quota = $userObject->getQuota();
795
+        if ($quota === null || $quota === 'none') {
796
+            $quota = Filesystem::free_space('/');
797
+            $softQuota = false;
798
+            // inf or unknown free space
799
+            if ($quota < 0) {
800
+                $quota = PHP_INT_MAX;
801
+            }
802
+        } else {
803
+            $quota = Util::computerFileSize($quota);
804
+            // invalid quota
805
+            if ($quota === false) {
806
+                $quota = PHP_INT_MAX;
807
+            }
808
+        }
809
+
810
+        // calculate available space for trash bin
811
+        // subtract size of files and current trash bin size from quota
812
+        if ($softQuota) {
813
+            $userFolder = \OC::$server->getUserFolder($user);
814
+            if (is_null($userFolder)) {
815
+                return 0;
816
+            }
817
+            $free = $quota - $userFolder->getSize(false); // remaining free space for user
818
+            if ($free > 0) {
819
+                $availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $trashbinSize; // how much space can be used for versions
820
+            } else {
821
+                $availableSpace = $free - $trashbinSize;
822
+            }
823
+        } else {
824
+            $availableSpace = $quota;
825
+        }
826
+
827
+        return Util::numericToNumber($availableSpace);
828
+    }
829
+
830
+    /**
831
+     * resize trash bin if necessary after a new file was added to Nextcloud
832
+     *
833
+     * @param string $user user id
834
+     */
835
+    public static function resizeTrash($user) {
836
+        $size = self::getTrashbinSize($user);
837
+
838
+        $freeSpace = self::calculateFreeSpace($size, $user);
839
+
840
+        if ($freeSpace < 0) {
841
+            self::scheduleExpire($user);
842
+        }
843
+    }
844
+
845
+    /**
846
+     * clean up the trash bin
847
+     *
848
+     * @param string $user
849
+     */
850
+    public static function expire($user) {
851
+        $trashBinSize = self::getTrashbinSize($user);
852
+        $availableSpace = self::calculateFreeSpace($trashBinSize, $user);
853
+
854
+        $dirContent = Helper::getTrashFiles('/', $user, 'mtime');
855
+
856
+        // delete all files older then $retention_obligation
857
+        [$delSize, $count] = self::deleteExpiredFiles($dirContent, $user);
858
+
859
+        $availableSpace += $delSize;
860
+
861
+        // delete files from trash until we meet the trash bin size limit again
862
+        self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace);
863
+    }
864
+
865
+    /**
866
+     * @param string $user
867
+     */
868
+    private static function scheduleExpire($user) {
869
+        // let the admin disable auto expire
870
+        $expiration = Server::get(Expiration::class);
871
+        if ($expiration->isEnabled()) {
872
+            Server::get(IBus::class)->push(new Expire($user));
873
+        }
874
+    }
875
+
876
+    /**
877
+     * if the size limit for the trash bin is reached, we delete the oldest
878
+     * files in the trash bin until we meet the limit again
879
+     *
880
+     * @param array $files
881
+     * @param string $user
882
+     * @param int|float $availableSpace available disc space
883
+     * @return int|float size of deleted files
884
+     */
885
+    protected static function deleteFiles(array $files, string $user, int|float $availableSpace): int|float {
886
+        $expiration = Server::get(Expiration::class);
887
+        $size = 0;
888
+
889
+        if ($availableSpace <= 0) {
890
+            foreach ($files as $file) {
891
+                if ($availableSpace <= 0 && $expiration->isExpired($file['mtime'], true)) {
892
+                    $tmp = self::delete($file['name'], $user, $file['mtime']);
893
+                    Server::get(LoggerInterface::class)->info(
894
+                        'remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota) for user "{user}"',
895
+                        [
896
+                            'app' => 'files_trashbin',
897
+                            'user' => $user,
898
+                        ]
899
+                    );
900
+                    $availableSpace += $tmp;
901
+                    $size += $tmp;
902
+                } else {
903
+                    break;
904
+                }
905
+            }
906
+        }
907
+        return $size;
908
+    }
909
+
910
+    /**
911
+     * delete files older then max storage time
912
+     *
913
+     * @param array $files list of files sorted by mtime
914
+     * @param string $user
915
+     * @return array{int|float, int} size of deleted files and number of deleted files
916
+     */
917
+    public static function deleteExpiredFiles($files, $user) {
918
+        $expiration = Server::get(Expiration::class);
919
+        $size = 0;
920
+        $count = 0;
921
+        foreach ($files as $file) {
922
+            $timestamp = $file['mtime'];
923
+            $filename = $file['name'];
924
+            if ($expiration->isExpired($timestamp)) {
925
+                try {
926
+                    $size += self::delete($filename, $user, $timestamp);
927
+                    $count++;
928
+                } catch (NotPermittedException $e) {
929
+                    Server::get(LoggerInterface::class)->warning('Removing "' . $filename . '" from trashbin failed for user "{user}"',
930
+                        [
931
+                            'exception' => $e,
932
+                            'app' => 'files_trashbin',
933
+                            'user' => $user,
934
+                        ]
935
+                    );
936
+                }
937
+                Server::get(LoggerInterface::class)->info(
938
+                    'Remove "' . $filename . '" from trashbin for user "{user}" because it exceeds max retention obligation term.',
939
+                    [
940
+                        'app' => 'files_trashbin',
941
+                        'user' => $user,
942
+                    ],
943
+                );
944
+            } else {
945
+                break;
946
+            }
947
+        }
948
+
949
+        return [$size, $count];
950
+    }
951
+
952
+    /**
953
+     * recursive copy to copy a whole directory
954
+     *
955
+     * @param string $source source path, relative to the users files directory
956
+     * @param string $destination destination path relative to the users root directory
957
+     * @param View $view file view for the users root directory
958
+     * @return int|float
959
+     * @throws Exceptions\CopyRecursiveException
960
+     */
961
+    private static function copy_recursive($source, $destination, View $view): int|float {
962
+        $size = 0;
963
+        if ($view->is_dir($source)) {
964
+            $view->mkdir($destination);
965
+            $view->touch($destination, $view->filemtime($source));
966
+            foreach ($view->getDirectoryContent($source) as $i) {
967
+                $pathDir = $source . '/' . $i['name'];
968
+                if ($view->is_dir($pathDir)) {
969
+                    $size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
970
+                } else {
971
+                    $size += $view->filesize($pathDir);
972
+                    $result = $view->copy($pathDir, $destination . '/' . $i['name']);
973
+                    if (!$result) {
974
+                        throw new CopyRecursiveException();
975
+                    }
976
+                    $view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
977
+                }
978
+            }
979
+        } else {
980
+            $size += $view->filesize($source);
981
+            $result = $view->copy($source, $destination);
982
+            if (!$result) {
983
+                throw new CopyRecursiveException();
984
+            }
985
+            $view->touch($destination, $view->filemtime($source));
986
+        }
987
+        return $size;
988
+    }
989
+
990
+    /**
991
+     * find all versions which belong to the file we want to restore
992
+     *
993
+     * @param string $filename name of the file which should be restored
994
+     * @param int $timestamp timestamp when the file was deleted
995
+     */
996
+    private static function getVersionsFromTrash($filename, $timestamp, string $user): array {
997
+        $view = new View('/' . $user . '/files_trashbin/versions');
998
+        $versions = [];
999
+
1000
+        /** @var \OC\Files\Storage\Storage $storage */
1001
+        [$storage,] = $view->resolvePath('/');
1002
+
1003
+        $pattern = Server::get(IDBConnection::class)->escapeLikeParameter(basename($filename));
1004
+        if ($timestamp) {
1005
+            // fetch for old versions
1006
+            $escapedTimestamp = Server::get(IDBConnection::class)->escapeLikeParameter((string)$timestamp);
1007
+            $pattern .= '.v%.d' . $escapedTimestamp;
1008
+            $offset = -strlen($escapedTimestamp) - 2;
1009
+        } else {
1010
+            $pattern .= '.v%';
1011
+        }
1012
+
1013
+        // Manually fetch all versions from the file cache to be able to filter them by their parent
1014
+        $cache = $storage->getCache('');
1015
+        $query = new CacheQueryBuilder(
1016
+            Server::get(IDBConnection::class)->getQueryBuilder(),
1017
+            Server::get(IFilesMetadataManager::class),
1018
+        );
1019
+        $normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/' . $filename)), '/');
1020
+        $parentId = $cache->getId($normalizedParentPath);
1021
+        if ($parentId === -1) {
1022
+            return [];
1023
+        }
1024
+
1025
+        $query->selectFileCache()
1026
+            ->whereStorageId($cache->getNumericStorageId())
1027
+            ->andWhere($query->expr()->eq('parent', $query->createNamedParameter($parentId)))
1028
+            ->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern)));
1029
+
1030
+        $result = $query->executeQuery();
1031
+        $entries = $result->fetchAllAssociative();
1032
+        $result->closeCursor();
1033
+
1034
+        /** @var CacheEntry[] $matches */
1035
+        $matches = array_map(function (array $data) {
1036
+            return Cache::cacheEntryFromData($data, Server::get(IMimeTypeLoader::class));
1037
+        }, $entries);
1038
+
1039
+        foreach ($matches as $ma) {
1040
+            if ($timestamp) {
1041
+                $parts = explode('.v', substr($ma['path'], 0, $offset));
1042
+                $versions[] = end($parts);
1043
+            } else {
1044
+                $parts = explode('.v', $ma['path']);
1045
+                $versions[] = end($parts);
1046
+            }
1047
+        }
1048
+
1049
+        return $versions;
1050
+    }
1051
+
1052
+    /**
1053
+     * find unique extension for restored file if a file with the same name already exists
1054
+     *
1055
+     * @param string $location where the file should be restored
1056
+     * @param string $filename name of the file
1057
+     * @param View $view filesystem view relative to users root directory
1058
+     * @return string with unique extension
1059
+     */
1060
+    private static function getUniqueFilename($location, $filename, View $view) {
1061
+        $ext = pathinfo($filename, PATHINFO_EXTENSION);
1062
+        $name = pathinfo($filename, PATHINFO_FILENAME);
1063
+        $l = Util::getL10N('files_trashbin');
1064
+
1065
+        $location = '/' . trim($location, '/');
1066
+
1067
+        // if extension is not empty we set a dot in front of it
1068
+        if ($ext !== '') {
1069
+            $ext = '.' . $ext;
1070
+        }
1071
+
1072
+        if ($view->file_exists('files' . $location . '/' . $filename)) {
1073
+            $i = 2;
1074
+            $uniqueName = $name . ' (' . $l->t('restored') . ')' . $ext;
1075
+            while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
1076
+                $uniqueName = $name . ' (' . $l->t('restored') . ' ' . $i . ')' . $ext;
1077
+                $i++;
1078
+            }
1079
+
1080
+            return $uniqueName;
1081
+        }
1082
+
1083
+        return $filename;
1084
+    }
1085
+
1086
+    /**
1087
+     * get the size from a given root folder
1088
+     *
1089
+     * @param View $view file view on the root folder
1090
+     * @return int|float size of the folder
1091
+     */
1092
+    private static function calculateSize(View $view): int|float {
1093
+        $root = Server::get(IConfig::class)->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
1094
+        if (!file_exists($root)) {
1095
+            return 0;
1096
+        }
1097
+        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), \RecursiveIteratorIterator::CHILD_FIRST);
1098
+        $size = 0;
1099
+
1100
+        /**
1101
+         * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
1102
+         * This bug is fixed in PHP 5.5.9 or before
1103
+         * See #8376
1104
+         */
1105
+        $iterator->rewind();
1106
+        while ($iterator->valid()) {
1107
+            $path = $iterator->current();
1108
+            $relpath = substr($path, strlen($root) - 1);
1109
+            if (!$view->is_dir($relpath)) {
1110
+                $size += $view->filesize($relpath);
1111
+            }
1112
+            $iterator->next();
1113
+        }
1114
+        return $size;
1115
+    }
1116
+
1117
+    /**
1118
+     * get current size of trash bin from a given user
1119
+     *
1120
+     * @param string $user user who owns the trash bin
1121
+     * @return int|float trash bin size
1122
+     */
1123
+    private static function getTrashbinSize(string $user): int|float {
1124
+        $view = new View('/' . $user);
1125
+        $fileInfo = $view->getFileInfo('/files_trashbin');
1126
+        return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
1127
+    }
1128
+
1129
+    /**
1130
+     * check if trash bin is empty for a given user
1131
+     *
1132
+     * @param string $user
1133
+     * @return bool
1134
+     */
1135
+    public static function isEmpty($user) {
1136
+        $view = new View('/' . $user . '/files_trashbin');
1137
+        if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
1138
+            while (($file = readdir($dh)) !== false) {
1139
+                if (!Filesystem::isIgnoredDir($file)) {
1140
+                    return false;
1141
+                }
1142
+            }
1143
+        }
1144
+        return true;
1145
+    }
1146
+
1147
+    /**
1148
+     * @param $path
1149
+     * @return string
1150
+     */
1151
+    public static function preview_icon($path) {
1152
+        return Server::get(IURLGenerator::class)->linkToRoute('core_ajax_trashbin_preview', ['x' => 32, 'y' => 32, 'file' => $path]);
1153
+    }
1154
+
1155
+    /**
1156
+     * Return the filename used in the trash bin
1157
+     */
1158
+    public static function getTrashFilename(string $filename, int $timestamp): string {
1159
+        $trashFilename = $filename . '.d' . $timestamp;
1160
+        $length = strlen($trashFilename);
1161
+        // oc_filecache `name` column has a limit of 250 chars
1162
+        $maxLength = 250;
1163
+        if ($length > $maxLength) {
1164
+            $trashFilename = substr_replace(
1165
+                $trashFilename,
1166
+                '',
1167
+                $maxLength / 2,
1168
+                $length - $maxLength
1169
+            );
1170
+        }
1171
+        return $trashFilename;
1172
+    }
1173
+
1174
+    private static function getNodeForPath(string $user, string $path, string $baseDir = 'files_trashbin/files'): Node {
1175
+        $rootFolder = Server::get(IRootFolder::class);
1176
+        $path = ltrim($path, '/');
1177
+
1178
+        $userFolder = $rootFolder->getUserFolder($user);
1179
+        /** @var Folder $trashFolder */
1180
+        $trashFolder = $userFolder->getParent()->get($baseDir);
1181
+        try {
1182
+            return $trashFolder->get($path);
1183
+        } catch (NotFoundException $ex) {
1184
+        }
1185
+
1186
+        $view = Server::get(View::class);
1187
+        $fullPath = '/' . $user . '/' . $baseDir . '/' . $path;
1188
+
1189
+        if (Filesystem::is_dir($path)) {
1190
+            return new NonExistingFolder($rootFolder, $view, $fullPath);
1191
+        } else {
1192
+            return new NonExistingFile($rootFolder, $view, $fullPath);
1193
+        }
1194
+    }
1195
+
1196
+    /**
1197
+     * in case the request is authed, and user token is from a federated share
1198
+     * we use shared_with as initiator of the deletion
1199
+     */
1200
+    private static function overwriteDeletedBy(string $user) {
1201
+        try {
1202
+            $request = Server::get(IRequest::class);
1203
+            /** @psalm-suppress NoInterfaceProperties */
1204
+            $token = $request->server['PHP_AUTH_USER'] ?? '';
1205
+            if ($token === '') {
1206
+                return $user;
1207
+            }
1208
+
1209
+            $federatedShareProvider = Server::get(\OCA\FederatedFileSharing\FederatedShareProvider::class);
1210
+            $share = $federatedShareProvider->getShareByToken($token);
1211
+
1212
+            return $share->getSharedWith();
1213
+        } catch (NotFoundExceptionInterface|ContainerExceptionInterface|ShareNotFound) {
1214
+        }
1215
+
1216
+        return $user;
1217
+    }
1218
+
1219
+    public function handle(Event $event): void {
1220
+        if ($event instanceof BeforeNodeDeletedEvent) {
1221
+            self::ensureFileScannedHook($event->getNode());
1222
+        }
1223
+    }
1224 1224
 }
Please login to merge, or discard this patch.
Spacing   +79 added lines, -79 removed lines patch added patch discarded remove patch
@@ -95,7 +95,7 @@  discard block
 block discarded – undo
95 95
 		Filesystem::initMountPoints($uid);
96 96
 		if ($uid !== OC_User::getUser()) {
97 97
 			$info = Filesystem::getFileInfo($filename);
98
-			$ownerView = new View('/' . $uid . '/files');
98
+			$ownerView = new View('/'.$uid.'/files');
99 99
 			try {
100 100
 				$filename = $ownerView->getPath($info['fileid']);
101 101
 			} catch (NotFoundException $e) {
@@ -120,8 +120,8 @@  discard block
 block discarded – undo
120 120
 		$array = [];
121 121
 		foreach ($result->iterateAssociative() as $row) {
122 122
 			$array[$row['id']][$row['timestamp']] = [
123
-				'location' => (string)$row['location'],
124
-				'deletedBy' => (string)$row['deleted_by'],
123
+				'location' => (string) $row['location'],
124
+				'deletedBy' => (string) $row['deleted_by'],
125 125
 			];
126 126
 		}
127 127
 		$result->closeCursor();
@@ -157,7 +157,7 @@  discard block
 block discarded – undo
157 157
 
158 158
 	/** @param string $user */
159 159
 	private static function setUpTrash($user): void {
160
-		$view = new View('/' . $user);
160
+		$view = new View('/'.$user);
161 161
 		if (!$view->is_dir('files_trashbin')) {
162 162
 			$view->mkdir('files_trashbin');
163 163
 		}
@@ -192,8 +192,8 @@  discard block
 block discarded – undo
192 192
 
193 193
 		$view = new View('/');
194 194
 
195
-		$target = $user . '/files_trashbin/files/' . static::getTrashFilename($targetFilename, $timestamp);
196
-		$source = $owner . '/files_trashbin/files/' . static::getTrashFilename($sourceFilename, $timestamp);
195
+		$target = $user.'/files_trashbin/files/'.static::getTrashFilename($targetFilename, $timestamp);
196
+		$source = $owner.'/files_trashbin/files/'.static::getTrashFilename($sourceFilename, $timestamp);
197 197
 		$free = $view->free_space($target);
198 198
 		$isUnknownOrUnlimitedFreeSpace = $free < 0;
199 199
 		$isEnoughFreeSpaceLeft = $view->filesize($source) < $free;
@@ -238,14 +238,14 @@  discard block
 block discarded – undo
238 238
 			$ownerPath = $file_path;
239 239
 		}
240 240
 
241
-		$ownerView = new View('/' . $owner);
241
+		$ownerView = new View('/'.$owner);
242 242
 
243 243
 		// file has been deleted in between
244 244
 		if (is_null($ownerPath) || $ownerPath === '') {
245 245
 			return true;
246 246
 		}
247 247
 
248
-		$sourceInfo = $ownerView->getFileInfo('/files/' . $ownerPath);
248
+		$sourceInfo = $ownerView->getFileInfo('/files/'.$ownerPath);
249 249
 
250 250
 		if ($sourceInfo === false) {
251 251
 			return true;
@@ -268,7 +268,7 @@  discard block
 block discarded – undo
268 268
 		$lockingProvider = Server::get(ILockingProvider::class);
269 269
 
270 270
 		// disable proxy to prevent recursive calls
271
-		$trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp);
271
+		$trashPath = '/files_trashbin/files/'.static::getTrashFilename($filename, $timestamp);
272 272
 		$gotLock = false;
273 273
 
274 274
 		do {
@@ -283,7 +283,7 @@  discard block
 block discarded – undo
283 283
 
284 284
 				$timestamp = $timestamp + 1;
285 285
 
286
-				$trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp);
286
+				$trashPath = '/files_trashbin/files/'.static::getTrashFilename($filename, $timestamp);
287 287
 			}
288 288
 		} while (!$gotLock);
289 289
 
@@ -312,7 +312,7 @@  discard block
 block discarded – undo
312 312
 			if ($trashStorage->file_exists($trashInternalPath)) {
313 313
 				$trashStorage->unlink($trashInternalPath);
314 314
 			}
315
-			Server::get(LoggerInterface::class)->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']);
315
+			Server::get(LoggerInterface::class)->error('Couldn\'t move '.$file_path.' to the trash bin', ['app' => 'files_trashbin']);
316 316
 		}
317 317
 
318 318
 		if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
@@ -369,7 +369,7 @@  discard block
 block discarded – undo
369 369
 		return $moveSuccessful;
370 370
 	}
371 371
 
372
-	private static function getConfiguredTrashbinSize(string $user): int|float {
372
+	private static function getConfiguredTrashbinSize(string $user): int | float {
373 373
 		$userConfig = Server::get(IUserConfig::class);
374 374
 		$userTrashbinSize = $userConfig->getValueString($user, 'files_trashbin', 'trashbin_size', '-1');
375 375
 		if (is_numeric($userTrashbinSize) && ($userTrashbinSize > -1)) {
@@ -397,17 +397,17 @@  discard block
 block discarded – undo
397 397
 			$user = OC_User::getUser();
398 398
 			$rootView = new View('/');
399 399
 
400
-			if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
400
+			if ($rootView->is_dir($owner.'/files_versions/'.$ownerPath)) {
401 401
 				if ($owner !== $user) {
402
-					self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . static::getTrashFilename(basename($ownerPath), $timestamp), $rootView);
402
+					self::copy_recursive($owner.'/files_versions/'.$ownerPath, $owner.'/files_trashbin/versions/'.static::getTrashFilename(basename($ownerPath), $timestamp), $rootView);
403 403
 				}
404
-				self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . static::getTrashFilename($filename, $timestamp));
404
+				self::move($rootView, $owner.'/files_versions/'.$ownerPath, $user.'/files_trashbin/versions/'.static::getTrashFilename($filename, $timestamp));
405 405
 			} elseif ($versions = Storage::getVersions($owner, $ownerPath)) {
406 406
 				foreach ($versions as $v) {
407 407
 					if ($owner !== $user) {
408
-						self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . static::getTrashFilename($v['name'] . '.v' . $v['version'], $timestamp));
408
+						self::copy($rootView, $owner.'/files_versions'.$v['path'].'.v'.$v['version'], $owner.'/files_trashbin/versions/'.static::getTrashFilename($v['name'].'.v'.$v['version'], $timestamp));
409 409
 					}
410
-					self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v['version'], $timestamp));
410
+					self::move($rootView, $owner.'/files_versions'.$v['path'].'.v'.$v['version'], $user.'/files_trashbin/versions/'.static::getTrashFilename($filename.'.v'.$v['version'], $timestamp));
411 411
 				}
412 412
 			}
413 413
 		}
@@ -472,18 +472,18 @@  discard block
 block discarded – undo
472 472
 		if (!$user) {
473 473
 			throw new \Exception('Tried to restore a file while not logged in');
474 474
 		}
475
-		$view = new View('/' . $user);
475
+		$view = new View('/'.$user);
476 476
 
477 477
 		$location = '';
478 478
 		if ($timestamp) {
479 479
 			$location = self::getLocation($user, $filename, $timestamp);
480 480
 			if ($location === false) {
481
-				Server::get(LoggerInterface::class)->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']);
481
+				Server::get(LoggerInterface::class)->error('trash bin database inconsistent! ($user: '.$user.' $filename: '.$filename.', $timestamp: '.$timestamp.')', ['app' => 'files_trashbin']);
482 482
 			} else {
483 483
 				// if location no longer exists, restore file in the root directory
484 484
 				if ($location !== '/'
485
-					&& (!$view->is_dir('files/' . $location)
486
-						|| !$view->isCreatable('files/' . $location))
485
+					&& (!$view->is_dir('files/'.$location)
486
+						|| !$view->isCreatable('files/'.$location))
487 487
 				) {
488 488
 					$location = '';
489 489
 				}
@@ -493,8 +493,8 @@  discard block
 block discarded – undo
493 493
 		// we need a  extension in case a file/dir with the same name already exists
494 494
 		$uniqueFilename = self::getUniqueFilename($location, $filename, $view);
495 495
 
496
-		$source = Filesystem::normalizePath('files_trashbin/files/' . $file);
497
-		$target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
496
+		$source = Filesystem::normalizePath('files_trashbin/files/'.$file);
497
+		$target = Filesystem::normalizePath('files/'.$location.'/'.$uniqueFilename);
498 498
 		if (!$view->file_exists($source)) {
499 499
 			return false;
500 500
 		}
@@ -506,7 +506,7 @@  discard block
 block discarded – undo
506 506
 		}
507 507
 
508 508
 		$sourcePath = Filesystem::normalizePath($file);
509
-		$targetPath = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
509
+		$targetPath = Filesystem::normalizePath('/'.$location.'/'.$uniqueFilename);
510 510
 
511 511
 		$sourceNode = self::getNodeForPath($user, $sourcePath);
512 512
 		$targetNode = self::getNodeForPath($user, $targetPath, 'files');
@@ -524,8 +524,8 @@  discard block
 block discarded – undo
524 524
 		// handle the restore result
525 525
 		if ($restoreResult) {
526 526
 			$fakeRoot = $view->getRoot();
527
-			$view->chroot('/' . $user . '/files');
528
-			$view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
527
+			$view->chroot('/'.$user.'/files');
528
+			$view->touch('/'.$location.'/'.$uniqueFilename, $mtime);
529 529
 			$view->chroot($fakeRoot);
530 530
 			Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => $targetPath, 'trashPath' => $sourcePath]);
531 531
 
@@ -568,7 +568,7 @@  discard block
 block discarded – undo
568 568
 			$user = OC_User::getUser();
569 569
 			$rootView = new View('/');
570 570
 
571
-			$target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
571
+			$target = Filesystem::normalizePath('/'.$location.'/'.$uniqueFilename);
572 572
 
573 573
 			[$owner, $ownerPath] = self::getUidAndFilename($target);
574 574
 
@@ -583,14 +583,14 @@  discard block
 block discarded – undo
583 583
 				$versionedFile = $file;
584 584
 			}
585 585
 
586
-			if ($view->is_dir('/files_trashbin/versions/' . $file)) {
587
-				$rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
586
+			if ($view->is_dir('/files_trashbin/versions/'.$file)) {
587
+				$rootView->rename(Filesystem::normalizePath($user.'/files_trashbin/versions/'.$file), Filesystem::normalizePath($owner.'/files_versions/'.$ownerPath));
588 588
 			} elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
589 589
 				foreach ($versions as $v) {
590 590
 					if ($timestamp) {
591
-						$rootView->rename($user . '/files_trashbin/versions/' . static::getTrashFilename($versionedFile . '.v' . $v, $timestamp), $owner . '/files_versions/' . $ownerPath . '.v' . $v);
591
+						$rootView->rename($user.'/files_trashbin/versions/'.static::getTrashFilename($versionedFile.'.v'.$v, $timestamp), $owner.'/files_versions/'.$ownerPath.'.v'.$v);
592 592
 					} else {
593
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
593
+						$rootView->rename($user.'/files_trashbin/versions/'.$versionedFile.'.v'.$v, $owner.'/files_versions/'.$ownerPath.'.v'.$v);
594 594
 					}
595 595
 				}
596 596
 			}
@@ -603,7 +603,7 @@  discard block
 block discarded – undo
603 603
 	public static function deleteAll() {
604 604
 		$user = OC_User::getUser();
605 605
 		$userRoot = \OC::$server->getUserFolder($user)->getParent();
606
-		$view = new View('/' . $user);
606
+		$view = new View('/'.$user);
607 607
 		$fileInfos = $view->getDirectoryContent('files_trashbin/files');
608 608
 
609 609
 		try {
@@ -678,7 +678,7 @@  discard block
 block discarded – undo
678 678
 	 */
679 679
 	public static function delete($filename, $user, $timestamp = null) {
680 680
 		$userRoot = \OC::$server->getUserFolder($user)->getParent();
681
-		$view = new View('/' . $user);
681
+		$view = new View('/'.$user);
682 682
 		$size = 0;
683 683
 
684 684
 		if ($timestamp) {
@@ -697,20 +697,20 @@  discard block
 block discarded – undo
697 697
 		$size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
698 698
 
699 699
 		try {
700
-			$node = $userRoot->get('/files_trashbin/files/' . $file);
700
+			$node = $userRoot->get('/files_trashbin/files/'.$file);
701 701
 		} catch (NotFoundException $e) {
702 702
 			return $size;
703 703
 		}
704 704
 
705 705
 		if ($node instanceof Folder) {
706
-			$size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
706
+			$size += self::calculateSize(new View('/'.$user.'/files_trashbin/files/'.$file));
707 707
 		} elseif ($node instanceof File) {
708
-			$size += $view->filesize('/files_trashbin/files/' . $file);
708
+			$size += $view->filesize('/files_trashbin/files/'.$file);
709 709
 		}
710 710
 
711
-		self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
711
+		self::emitTrashbinPreDelete('/files_trashbin/files/'.$file);
712 712
 		$node->delete();
713
-		self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
713
+		self::emitTrashbinPostDelete('/files_trashbin/files/'.$file);
714 714
 
715 715
 		return $size;
716 716
 	}
@@ -720,20 +720,20 @@  discard block
 block discarded – undo
720 720
 	 * @param string $filename
721 721
 	 * @param ?int $timestamp
722 722
 	 */
723
-	private static function deleteVersions(View $view, $file, $filename, $timestamp, string $user): int|float {
723
+	private static function deleteVersions(View $view, $file, $filename, $timestamp, string $user): int | float {
724 724
 		$size = 0;
725 725
 		if (Server::get(IAppManager::class)->isEnabledForUser('files_versions')) {
726
-			if ($view->is_dir('files_trashbin/versions/' . $file)) {
727
-				$size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
728
-				$view->unlink('files_trashbin/versions/' . $file);
726
+			if ($view->is_dir('files_trashbin/versions/'.$file)) {
727
+				$size += self::calculateSize(new View('/'.$user.'/files_trashbin/versions/'.$file));
728
+				$view->unlink('files_trashbin/versions/'.$file);
729 729
 			} elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
730 730
 				foreach ($versions as $v) {
731 731
 					if ($timestamp) {
732
-						$size += $view->filesize('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp));
733
-						$view->unlink('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp));
732
+						$size += $view->filesize('/files_trashbin/versions/'.static::getTrashFilename($filename.'.v'.$v, $timestamp));
733
+						$view->unlink('/files_trashbin/versions/'.static::getTrashFilename($filename.'.v'.$v, $timestamp));
734 734
 					} else {
735
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
736
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
735
+						$size += $view->filesize('/files_trashbin/versions/'.$filename.'.v'.$v);
736
+						$view->unlink('/files_trashbin/versions/'.$filename.'.v'.$v);
737 737
 					}
738 738
 				}
739 739
 			}
@@ -750,13 +750,13 @@  discard block
 block discarded – undo
750 750
 	 */
751 751
 	public static function file_exists($filename, $timestamp = null) {
752 752
 		$user = OC_User::getUser();
753
-		$view = new View('/' . $user);
753
+		$view = new View('/'.$user);
754 754
 
755 755
 		if ($timestamp) {
756 756
 			$filename = static::getTrashFilename($filename, $timestamp);
757 757
 		}
758 758
 
759
-		$target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
759
+		$target = Filesystem::normalizePath('files_trashbin/files/'.$filename);
760 760
 		return $view->file_exists($target);
761 761
 	}
762 762
 
@@ -770,7 +770,7 @@  discard block
 block discarded – undo
770 770
 		$query = Server::get(IDBConnection::class)->getQueryBuilder();
771 771
 		$query->delete('files_trash')
772 772
 			->where($query->expr()->eq('user', $query->createNamedParameter($uid)));
773
-		return (bool)$query->executeStatement();
773
+		return (bool) $query->executeStatement();
774 774
 	}
775 775
 
776 776
 	/**
@@ -780,7 +780,7 @@  discard block
 block discarded – undo
780 780
 	 * @param string $user
781 781
 	 * @return int|float available free space for trash bin
782 782
 	 */
783
-	private static function calculateFreeSpace(int|float $trashbinSize, string $user): int|float {
783
+	private static function calculateFreeSpace(int | float $trashbinSize, string $user): int | float {
784 784
 		$configuredTrashbinSize = static::getConfiguredTrashbinSize($user);
785 785
 		if ($configuredTrashbinSize > -1) {
786 786
 			return $configuredTrashbinSize - $trashbinSize;
@@ -882,7 +882,7 @@  discard block
 block discarded – undo
882 882
 	 * @param int|float $availableSpace available disc space
883 883
 	 * @return int|float size of deleted files
884 884
 	 */
885
-	protected static function deleteFiles(array $files, string $user, int|float $availableSpace): int|float {
885
+	protected static function deleteFiles(array $files, string $user, int | float $availableSpace): int | float {
886 886
 		$expiration = Server::get(Expiration::class);
887 887
 		$size = 0;
888 888
 
@@ -891,7 +891,7 @@  discard block
 block discarded – undo
891 891
 				if ($availableSpace <= 0 && $expiration->isExpired($file['mtime'], true)) {
892 892
 					$tmp = self::delete($file['name'], $user, $file['mtime']);
893 893
 					Server::get(LoggerInterface::class)->info(
894
-						'remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota) for user "{user}"',
894
+						'remove "'.$file['name'].'" ('.$tmp.'B) to meet the limit of trash bin size (50% of available quota) for user "{user}"',
895 895
 						[
896 896
 							'app' => 'files_trashbin',
897 897
 							'user' => $user,
@@ -926,7 +926,7 @@  discard block
 block discarded – undo
926 926
 					$size += self::delete($filename, $user, $timestamp);
927 927
 					$count++;
928 928
 				} catch (NotPermittedException $e) {
929
-					Server::get(LoggerInterface::class)->warning('Removing "' . $filename . '" from trashbin failed for user "{user}"',
929
+					Server::get(LoggerInterface::class)->warning('Removing "'.$filename.'" from trashbin failed for user "{user}"',
930 930
 						[
931 931
 							'exception' => $e,
932 932
 							'app' => 'files_trashbin',
@@ -935,7 +935,7 @@  discard block
 block discarded – undo
935 935
 					);
936 936
 				}
937 937
 				Server::get(LoggerInterface::class)->info(
938
-					'Remove "' . $filename . '" from trashbin for user "{user}" because it exceeds max retention obligation term.',
938
+					'Remove "'.$filename.'" from trashbin for user "{user}" because it exceeds max retention obligation term.',
939 939
 					[
940 940
 						'app' => 'files_trashbin',
941 941
 						'user' => $user,
@@ -958,22 +958,22 @@  discard block
 block discarded – undo
958 958
 	 * @return int|float
959 959
 	 * @throws Exceptions\CopyRecursiveException
960 960
 	 */
961
-	private static function copy_recursive($source, $destination, View $view): int|float {
961
+	private static function copy_recursive($source, $destination, View $view): int | float {
962 962
 		$size = 0;
963 963
 		if ($view->is_dir($source)) {
964 964
 			$view->mkdir($destination);
965 965
 			$view->touch($destination, $view->filemtime($source));
966 966
 			foreach ($view->getDirectoryContent($source) as $i) {
967
-				$pathDir = $source . '/' . $i['name'];
967
+				$pathDir = $source.'/'.$i['name'];
968 968
 				if ($view->is_dir($pathDir)) {
969
-					$size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
969
+					$size += self::copy_recursive($pathDir, $destination.'/'.$i['name'], $view);
970 970
 				} else {
971 971
 					$size += $view->filesize($pathDir);
972
-					$result = $view->copy($pathDir, $destination . '/' . $i['name']);
972
+					$result = $view->copy($pathDir, $destination.'/'.$i['name']);
973 973
 					if (!$result) {
974 974
 						throw new CopyRecursiveException();
975 975
 					}
976
-					$view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
976
+					$view->touch($destination.'/'.$i['name'], $view->filemtime($pathDir));
977 977
 				}
978 978
 			}
979 979
 		} else {
@@ -994,17 +994,17 @@  discard block
 block discarded – undo
994 994
 	 * @param int $timestamp timestamp when the file was deleted
995 995
 	 */
996 996
 	private static function getVersionsFromTrash($filename, $timestamp, string $user): array {
997
-		$view = new View('/' . $user . '/files_trashbin/versions');
997
+		$view = new View('/'.$user.'/files_trashbin/versions');
998 998
 		$versions = [];
999 999
 
1000 1000
 		/** @var \OC\Files\Storage\Storage $storage */
1001
-		[$storage,] = $view->resolvePath('/');
1001
+		[$storage, ] = $view->resolvePath('/');
1002 1002
 
1003 1003
 		$pattern = Server::get(IDBConnection::class)->escapeLikeParameter(basename($filename));
1004 1004
 		if ($timestamp) {
1005 1005
 			// fetch for old versions
1006
-			$escapedTimestamp = Server::get(IDBConnection::class)->escapeLikeParameter((string)$timestamp);
1007
-			$pattern .= '.v%.d' . $escapedTimestamp;
1006
+			$escapedTimestamp = Server::get(IDBConnection::class)->escapeLikeParameter((string) $timestamp);
1007
+			$pattern .= '.v%.d'.$escapedTimestamp;
1008 1008
 			$offset = -strlen($escapedTimestamp) - 2;
1009 1009
 		} else {
1010 1010
 			$pattern .= '.v%';
@@ -1016,7 +1016,7 @@  discard block
 block discarded – undo
1016 1016
 			Server::get(IDBConnection::class)->getQueryBuilder(),
1017 1017
 			Server::get(IFilesMetadataManager::class),
1018 1018
 		);
1019
-		$normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/' . $filename)), '/');
1019
+		$normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/'.$filename)), '/');
1020 1020
 		$parentId = $cache->getId($normalizedParentPath);
1021 1021
 		if ($parentId === -1) {
1022 1022
 			return [];
@@ -1032,7 +1032,7 @@  discard block
 block discarded – undo
1032 1032
 		$result->closeCursor();
1033 1033
 
1034 1034
 		/** @var CacheEntry[] $matches */
1035
-		$matches = array_map(function (array $data) {
1035
+		$matches = array_map(function(array $data) {
1036 1036
 			return Cache::cacheEntryFromData($data, Server::get(IMimeTypeLoader::class));
1037 1037
 		}, $entries);
1038 1038
 
@@ -1062,18 +1062,18 @@  discard block
 block discarded – undo
1062 1062
 		$name = pathinfo($filename, PATHINFO_FILENAME);
1063 1063
 		$l = Util::getL10N('files_trashbin');
1064 1064
 
1065
-		$location = '/' . trim($location, '/');
1065
+		$location = '/'.trim($location, '/');
1066 1066
 
1067 1067
 		// if extension is not empty we set a dot in front of it
1068 1068
 		if ($ext !== '') {
1069
-			$ext = '.' . $ext;
1069
+			$ext = '.'.$ext;
1070 1070
 		}
1071 1071
 
1072
-		if ($view->file_exists('files' . $location . '/' . $filename)) {
1072
+		if ($view->file_exists('files'.$location.'/'.$filename)) {
1073 1073
 			$i = 2;
1074
-			$uniqueName = $name . ' (' . $l->t('restored') . ')' . $ext;
1075
-			while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
1076
-				$uniqueName = $name . ' (' . $l->t('restored') . ' ' . $i . ')' . $ext;
1074
+			$uniqueName = $name.' ('.$l->t('restored').')'.$ext;
1075
+			while ($view->file_exists('files'.$location.'/'.$uniqueName)) {
1076
+				$uniqueName = $name.' ('.$l->t('restored').' '.$i.')'.$ext;
1077 1077
 				$i++;
1078 1078
 			}
1079 1079
 
@@ -1089,8 +1089,8 @@  discard block
 block discarded – undo
1089 1089
 	 * @param View $view file view on the root folder
1090 1090
 	 * @return int|float size of the folder
1091 1091
 	 */
1092
-	private static function calculateSize(View $view): int|float {
1093
-		$root = Server::get(IConfig::class)->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
1092
+	private static function calculateSize(View $view): int | float {
1093
+		$root = Server::get(IConfig::class)->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data').$view->getAbsolutePath('');
1094 1094
 		if (!file_exists($root)) {
1095 1095
 			return 0;
1096 1096
 		}
@@ -1120,8 +1120,8 @@  discard block
 block discarded – undo
1120 1120
 	 * @param string $user user who owns the trash bin
1121 1121
 	 * @return int|float trash bin size
1122 1122
 	 */
1123
-	private static function getTrashbinSize(string $user): int|float {
1124
-		$view = new View('/' . $user);
1123
+	private static function getTrashbinSize(string $user): int | float {
1124
+		$view = new View('/'.$user);
1125 1125
 		$fileInfo = $view->getFileInfo('/files_trashbin');
1126 1126
 		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
1127 1127
 	}
@@ -1133,7 +1133,7 @@  discard block
 block discarded – undo
1133 1133
 	 * @return bool
1134 1134
 	 */
1135 1135
 	public static function isEmpty($user) {
1136
-		$view = new View('/' . $user . '/files_trashbin');
1136
+		$view = new View('/'.$user.'/files_trashbin');
1137 1137
 		if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
1138 1138
 			while (($file = readdir($dh)) !== false) {
1139 1139
 				if (!Filesystem::isIgnoredDir($file)) {
@@ -1156,7 +1156,7 @@  discard block
 block discarded – undo
1156 1156
 	 * Return the filename used in the trash bin
1157 1157
 	 */
1158 1158
 	public static function getTrashFilename(string $filename, int $timestamp): string {
1159
-		$trashFilename = $filename . '.d' . $timestamp;
1159
+		$trashFilename = $filename.'.d'.$timestamp;
1160 1160
 		$length = strlen($trashFilename);
1161 1161
 		// oc_filecache `name` column has a limit of 250 chars
1162 1162
 		$maxLength = 250;
@@ -1184,7 +1184,7 @@  discard block
 block discarded – undo
1184 1184
 		}
1185 1185
 
1186 1186
 		$view = Server::get(View::class);
1187
-		$fullPath = '/' . $user . '/' . $baseDir . '/' . $path;
1187
+		$fullPath = '/'.$user.'/'.$baseDir.'/'.$path;
1188 1188
 
1189 1189
 		if (Filesystem::is_dir($path)) {
1190 1190
 			return new NonExistingFolder($rootFolder, $view, $fullPath);
@@ -1210,7 +1210,7 @@  discard block
 block discarded – undo
1210 1210
 			$share = $federatedShareProvider->getShareByToken($token);
1211 1211
 
1212 1212
 			return $share->getSharedWith();
1213
-		} catch (NotFoundExceptionInterface|ContainerExceptionInterface|ShareNotFound) {
1213
+		} catch (NotFoundExceptionInterface | ContainerExceptionInterface | ShareNotFound) {
1214 1214
 		}
1215 1215
 
1216 1216
 		return $user;
Please login to merge, or discard this patch.
apps/dav/lib/CalDAV/TimezoneService.php 2 patches
Indentation   +64 added lines, -64 removed lines patch added patch discarded remove patch
@@ -21,75 +21,75 @@
 block discarded – undo
21 21
 
22 22
 class TimezoneService {
23 23
 
24
-	public function __construct(
25
-		private IConfig $config,
26
-		private IUserConfig $userConfig,
27
-		private PropertyMapper $propertyMapper,
28
-		private IManager $calendarManager,
29
-	) {
30
-	}
24
+    public function __construct(
25
+        private IConfig $config,
26
+        private IUserConfig $userConfig,
27
+        private PropertyMapper $propertyMapper,
28
+        private IManager $calendarManager,
29
+    ) {
30
+    }
31 31
 
32
-	public function getUserTimezone(string $userId): ?string {
33
-		$fromConfig = $this->userConfig->getValueString(
34
-			$userId,
35
-			'core',
36
-			'timezone',
37
-		);
38
-		if ($fromConfig !== '') {
39
-			return $fromConfig;
40
-		}
32
+    public function getUserTimezone(string $userId): ?string {
33
+        $fromConfig = $this->userConfig->getValueString(
34
+            $userId,
35
+            'core',
36
+            'timezone',
37
+        );
38
+        if ($fromConfig !== '') {
39
+            return $fromConfig;
40
+        }
41 41
 
42
-		$availabilityPropPath = 'calendars/' . $userId . '/inbox';
43
-		$availabilityProp = '{' . Plugin::NS_CALDAV . '}calendar-availability';
44
-		$availabilities = $this->propertyMapper->findPropertyByPathAndName($userId, $availabilityPropPath, $availabilityProp);
45
-		if (!empty($availabilities)) {
46
-			$availability = $availabilities[0]->getPropertyvalue();
47
-			/** @var VCalendar $vCalendar */
48
-			$vCalendar = Reader::read($availability);
49
-			/** @var VTimeZone $vTimezone */
50
-			$vTimezone = $vCalendar->VTIMEZONE;
51
-			// Sabre has a fallback to date_default_timezone_get
52
-			return $vTimezone->getTimeZone()->getName();
53
-		}
42
+        $availabilityPropPath = 'calendars/' . $userId . '/inbox';
43
+        $availabilityProp = '{' . Plugin::NS_CALDAV . '}calendar-availability';
44
+        $availabilities = $this->propertyMapper->findPropertyByPathAndName($userId, $availabilityPropPath, $availabilityProp);
45
+        if (!empty($availabilities)) {
46
+            $availability = $availabilities[0]->getPropertyvalue();
47
+            /** @var VCalendar $vCalendar */
48
+            $vCalendar = Reader::read($availability);
49
+            /** @var VTimeZone $vTimezone */
50
+            $vTimezone = $vCalendar->VTIMEZONE;
51
+            // Sabre has a fallback to date_default_timezone_get
52
+            return $vTimezone->getTimeZone()->getName();
53
+        }
54 54
 
55
-		$principal = 'principals/users/' . $userId;
56
-		$uri = $this->userConfig->getValueString($userId, 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI);
57
-		$calendars = $this->calendarManager->getCalendarsForPrincipal($principal);
55
+        $principal = 'principals/users/' . $userId;
56
+        $uri = $this->userConfig->getValueString($userId, 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI);
57
+        $calendars = $this->calendarManager->getCalendarsForPrincipal($principal);
58 58
 
59
-		/** @var ?VTimeZone $personalCalendarTimezone */
60
-		$personalCalendarTimezone = array_reduce($calendars, function (?VTimeZone $acc, ICalendar $calendar) use ($uri) {
61
-			if ($acc !== null) {
62
-				return $acc;
63
-			}
64
-			if ($calendar->getUri() === $uri && !$calendar->isDeleted() && $calendar instanceof CalendarImpl) {
65
-				return $calendar->getSchedulingTimezone();
66
-			}
67
-			return null;
68
-		});
69
-		if ($personalCalendarTimezone !== null) {
70
-			return $personalCalendarTimezone->getTimeZone()->getName();
71
-		}
59
+        /** @var ?VTimeZone $personalCalendarTimezone */
60
+        $personalCalendarTimezone = array_reduce($calendars, function (?VTimeZone $acc, ICalendar $calendar) use ($uri) {
61
+            if ($acc !== null) {
62
+                return $acc;
63
+            }
64
+            if ($calendar->getUri() === $uri && !$calendar->isDeleted() && $calendar instanceof CalendarImpl) {
65
+                return $calendar->getSchedulingTimezone();
66
+            }
67
+            return null;
68
+        });
69
+        if ($personalCalendarTimezone !== null) {
70
+            return $personalCalendarTimezone->getTimeZone()->getName();
71
+        }
72 72
 
73
-		// No timezone in the personalCalendarTimezone calendar or no personalCalendarTimezone calendar
74
-		// Loop through all calendars until we find a timezone.
75
-		/** @var ?VTimeZone $firstTimezone */
76
-		$firstTimezone = array_reduce($calendars, function (?VTimeZone $acc, ICalendar $calendar) {
77
-			if ($acc !== null) {
78
-				return $acc;
79
-			}
80
-			if (!$calendar->isDeleted() && $calendar instanceof CalendarImpl) {
81
-				return $calendar->getSchedulingTimezone();
82
-			}
83
-			return null;
84
-		});
85
-		if ($firstTimezone !== null) {
86
-			return $firstTimezone->getTimeZone()->getName();
87
-		}
88
-		return null;
89
-	}
73
+        // No timezone in the personalCalendarTimezone calendar or no personalCalendarTimezone calendar
74
+        // Loop through all calendars until we find a timezone.
75
+        /** @var ?VTimeZone $firstTimezone */
76
+        $firstTimezone = array_reduce($calendars, function (?VTimeZone $acc, ICalendar $calendar) {
77
+            if ($acc !== null) {
78
+                return $acc;
79
+            }
80
+            if (!$calendar->isDeleted() && $calendar instanceof CalendarImpl) {
81
+                return $calendar->getSchedulingTimezone();
82
+            }
83
+            return null;
84
+        });
85
+        if ($firstTimezone !== null) {
86
+            return $firstTimezone->getTimeZone()->getName();
87
+        }
88
+        return null;
89
+    }
90 90
 
91
-	public function getDefaultTimezone(): string {
92
-		return $this->config->getSystemValueString('default_timezone', 'UTC');
93
-	}
91
+    public function getDefaultTimezone(): string {
92
+        return $this->config->getSystemValueString('default_timezone', 'UTC');
93
+    }
94 94
 
95 95
 }
Please login to merge, or discard this patch.
Spacing   +5 added lines, -5 removed lines patch added patch discarded remove patch
@@ -39,8 +39,8 @@  discard block
 block discarded – undo
39 39
 			return $fromConfig;
40 40
 		}
41 41
 
42
-		$availabilityPropPath = 'calendars/' . $userId . '/inbox';
43
-		$availabilityProp = '{' . Plugin::NS_CALDAV . '}calendar-availability';
42
+		$availabilityPropPath = 'calendars/'.$userId.'/inbox';
43
+		$availabilityProp = '{'.Plugin::NS_CALDAV.'}calendar-availability';
44 44
 		$availabilities = $this->propertyMapper->findPropertyByPathAndName($userId, $availabilityPropPath, $availabilityProp);
45 45
 		if (!empty($availabilities)) {
46 46
 			$availability = $availabilities[0]->getPropertyvalue();
@@ -52,12 +52,12 @@  discard block
 block discarded – undo
52 52
 			return $vTimezone->getTimeZone()->getName();
53 53
 		}
54 54
 
55
-		$principal = 'principals/users/' . $userId;
55
+		$principal = 'principals/users/'.$userId;
56 56
 		$uri = $this->userConfig->getValueString($userId, 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI);
57 57
 		$calendars = $this->calendarManager->getCalendarsForPrincipal($principal);
58 58
 
59 59
 		/** @var ?VTimeZone $personalCalendarTimezone */
60
-		$personalCalendarTimezone = array_reduce($calendars, function (?VTimeZone $acc, ICalendar $calendar) use ($uri) {
60
+		$personalCalendarTimezone = array_reduce($calendars, function(?VTimeZone $acc, ICalendar $calendar) use ($uri) {
61 61
 			if ($acc !== null) {
62 62
 				return $acc;
63 63
 			}
@@ -73,7 +73,7 @@  discard block
 block discarded – undo
73 73
 		// No timezone in the personalCalendarTimezone calendar or no personalCalendarTimezone calendar
74 74
 		// Loop through all calendars until we find a timezone.
75 75
 		/** @var ?VTimeZone $firstTimezone */
76
-		$firstTimezone = array_reduce($calendars, function (?VTimeZone $acc, ICalendar $calendar) {
76
+		$firstTimezone = array_reduce($calendars, function(?VTimeZone $acc, ICalendar $calendar) {
77 77
 			if ($acc !== null) {
78 78
 				return $acc;
79 79
 			}
Please login to merge, or discard this patch.
apps/dav/lib/CalDAV/Schedule/IMipService.php 1 patch
Indentation   +1290 added lines, -1290 removed lines patch added patch discarded remove patch
@@ -30,1294 +30,1294 @@
 block discarded – undo
30 30
 
31 31
 class IMipService {
32 32
 
33
-	private IL10N $l10n;
34
-
35
-	/** @var string[] */
36
-	private const STRING_DIFF = [
37
-		'meeting_title' => 'SUMMARY',
38
-		'meeting_description' => 'DESCRIPTION',
39
-		'meeting_url' => 'URL',
40
-		'meeting_location' => 'LOCATION'
41
-	];
42
-
43
-	public function __construct(
44
-		private URLGenerator $urlGenerator,
45
-		private IDBConnection $db,
46
-		private ISecureRandom $random,
47
-		private L10NFactory $l10nFactory,
48
-		private ITimeFactory $timeFactory,
49
-		private readonly IUserManager $userManager,
50
-		private readonly IUserConfig $userConfig,
51
-		private readonly IAppConfig $appConfig,
52
-	) {
53
-		$language = $this->l10nFactory->findGenericLanguage();
54
-		$locale = $this->l10nFactory->findLocale($language);
55
-		$this->l10n = $this->l10nFactory->get('dav', $language, $locale);
56
-	}
57
-
58
-	/**
59
-	 * @param string|null $senderName
60
-	 * @param string $default
61
-	 * @return string
62
-	 */
63
-	public function getFrom(?string $senderName, string $default): string {
64
-		if ($senderName === null) {
65
-			return $default;
66
-		}
67
-
68
-		return $this->l10n->t('%1$s via %2$s', [$senderName, $default]);
69
-	}
70
-
71
-	public static function readPropertyWithDefault(VEvent $vevent, string $property, string $default) {
72
-		if (isset($vevent->$property)) {
73
-			$value = $vevent->$property->getValue();
74
-			if (!empty($value)) {
75
-				return $value;
76
-			}
77
-		}
78
-		return $default;
79
-	}
80
-
81
-	private function generateDiffString(VEvent $vevent, VEvent $oldVEvent, string $property, string $default): ?string {
82
-		$strikethrough = "<span style='text-decoration: line-through'>%s</span><br />%s";
83
-		if (!isset($vevent->$property)) {
84
-			return $default;
85
-		}
86
-		$value = $vevent->$property->getValue();
87
-		$newstring = $value === null ? null : htmlspecialchars($value);
88
-		if (isset($oldVEvent->$property) && $oldVEvent->$property->getValue() !== $newstring) {
89
-			$oldstring = $oldVEvent->$property->getValue();
90
-			return sprintf($strikethrough, htmlspecialchars($oldstring), $newstring);
91
-		}
92
-		return $newstring;
93
-	}
94
-
95
-	/**
96
-	 * Like generateDiffString() but linkifies the property values if they are urls.
97
-	 */
98
-	private function generateLinkifiedDiffString(VEvent $vevent, VEvent $oldVEvent, string $property, string $default): ?string {
99
-		if (!isset($vevent->$property)) {
100
-			return $default;
101
-		}
102
-		/** @var string|null $newString */
103
-		$newString = htmlspecialchars($vevent->$property->getValue());
104
-		$oldString = isset($oldVEvent->$property) ? htmlspecialchars($oldVEvent->$property->getValue()) : null;
105
-		if ($oldString !== $newString) {
106
-			return sprintf(
107
-				"<span style='text-decoration: line-through'>%s</span><br />%s",
108
-				$this->linkify($oldString) ?? $oldString ?? '',
109
-				$this->linkify($newString) ?? $newString ?? ''
110
-			);
111
-		}
112
-		return $this->linkify($newString) ?? $newString;
113
-	}
114
-
115
-	/**
116
-	 * Convert a given url to a html link element or return null otherwise.
117
-	 */
118
-	private function linkify(?string $url): ?string {
119
-		if ($url === null) {
120
-			return null;
121
-		}
122
-		if (!str_starts_with($url, 'http://') && !str_starts_with($url, 'https://')) {
123
-			return null;
124
-		}
125
-
126
-		return sprintf('<a href="%1$s">%1$s</a>', htmlspecialchars($url));
127
-	}
128
-
129
-	/**
130
-	 * @param VEvent $vEvent
131
-	 * @param VEvent|null $oldVEvent
132
-	 * @return array
133
-	 */
134
-	public function buildBodyData(VEvent $vEvent, ?VEvent $oldVEvent): array {
135
-
136
-		// construct event reader
137
-		$eventReaderCurrent = new EventReader($vEvent);
138
-		$eventReaderPrevious = !empty($oldVEvent) ? new EventReader($oldVEvent) : null;
139
-		$defaultVal = '';
140
-		$data = [];
141
-		$data['meeting_when'] = $this->generateWhenString($eventReaderCurrent);
142
-
143
-		foreach (self::STRING_DIFF as $key => $property) {
144
-			$data[$key] = self::readPropertyWithDefault($vEvent, $property, $defaultVal);
145
-		}
146
-
147
-		$data['meeting_url_html'] = self::readPropertyWithDefault($vEvent, 'URL', $defaultVal);
148
-
149
-		if (($locationHtml = $this->linkify($data['meeting_location'])) !== null) {
150
-			$data['meeting_location_html'] = $locationHtml;
151
-		}
152
-
153
-		if (!empty($oldVEvent)) {
154
-			$oldMeetingWhen = $this->generateWhenString($eventReaderPrevious);
155
-			$data['meeting_title_html'] = $this->generateDiffString($vEvent, $oldVEvent, 'SUMMARY', $data['meeting_title']);
156
-			$data['meeting_description_html'] = $this->generateDiffString($vEvent, $oldVEvent, 'DESCRIPTION', $data['meeting_description']);
157
-			$data['meeting_location_html'] = $this->generateLinkifiedDiffString($vEvent, $oldVEvent, 'LOCATION', $data['meeting_location']);
158
-
159
-			$oldUrl = self::readPropertyWithDefault($oldVEvent, 'URL', $defaultVal);
160
-			$data['meeting_url_html'] = !empty($oldUrl) && $oldUrl !== $data['meeting_url'] ? sprintf('<a href="%1$s">%1$s</a>', $oldUrl) : $data['meeting_url'];
161
-
162
-			$data['meeting_when_html'] = $oldMeetingWhen !== $data['meeting_when'] ? sprintf("<span style='text-decoration: line-through'>%s</span><br />%s", $oldMeetingWhen, $data['meeting_when']) : $data['meeting_when'];
163
-		}
164
-		// generate occurring next string
165
-		if ($eventReaderCurrent->recurs()) {
166
-			$data['meeting_occurring'] = $this->generateOccurringString($eventReaderCurrent);
167
-		}
168
-		return $data;
169
-	}
170
-
171
-	/**
172
-	 * @param VEvent $vEvent
173
-	 * @return array
174
-	 */
175
-	public function buildReplyBodyData(VEvent $vEvent): array {
176
-		// construct event reader
177
-		$eventReader = new EventReader($vEvent);
178
-		$defaultVal = '';
179
-		$data = [];
180
-		$data['meeting_when'] = $this->generateWhenString($eventReader);
181
-
182
-		foreach (self::STRING_DIFF as $key => $property) {
183
-			$data[$key] = self::readPropertyWithDefault($vEvent, $property, $defaultVal);
184
-		}
185
-
186
-		if (($locationHtml = $this->linkify($data['meeting_location'])) !== null) {
187
-			$data['meeting_location_html'] = $locationHtml;
188
-		}
189
-
190
-		$data['meeting_url_html'] = $data['meeting_url'] ? sprintf('<a href="%1$s">%1$s</a>', $data['meeting_url']) : '';
191
-
192
-		// generate occurring next string
193
-		if ($eventReader->recurs()) {
194
-			$data['meeting_occurring'] = $this->generateOccurringString($eventReader);
195
-		}
196
-
197
-		return $data;
198
-	}
199
-
200
-	/**
201
-	 * generates a when string based on if a event has an recurrence or not
202
-	 *
203
-	 * @since 30.0.0
204
-	 *
205
-	 * @param EventReader $er
206
-	 *
207
-	 * @return string
208
-	 */
209
-	public function generateWhenString(EventReader $er): string {
210
-		return match ($er->recurs()) {
211
-			true => $this->generateWhenStringRecurring($er),
212
-			false => $this->generateWhenStringSingular($er)
213
-		};
214
-	}
215
-
216
-	/**
217
-	 * generates a when string for a non recurring event
218
-	 *
219
-	 * @since 30.0.0
220
-	 *
221
-	 * @param EventReader $er
222
-	 *
223
-	 * @return string
224
-	 */
225
-	public function generateWhenStringSingular(EventReader $er): string {
226
-		// initialize
227
-		$startTime = null;
228
-		$endTime = null;
229
-		// calculate time difference from now to start of event
230
-		$occurring = $this->minimizeInterval($this->timeFactory->getDateTime()->diff($er->recurrenceDate()));
231
-		// extract start date
232
-		$startDate = $this->l10n->l('date', $er->startDateTime(), ['width' => 'full']);
233
-		// time of the day
234
-		if (!$er->entireDay()) {
235
-			$startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
236
-			$startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
237
-			$endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
238
-		}
239
-		// generate localized when string
240
-		// TRANSLATORS
241
-		// Indicates when a calendar event will happen, shown on invitation emails
242
-		// Output produced in order:
243
-		// In 1 minute/hour/day/week/month/year on July 1, 2024 for the entire day
244
-		// In 1 minute/hour/day/week/month/year on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)
245
-		// In 2 minutes/hours/days/weeks/months/years on July 1, 2024 for the entire day
246
-		// In 2 minutes/hours/days/weeks/months/years on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)
247
-		return match ([$occurring['scale'], $endTime !== null]) {
248
-			['past', false] => $this->l10n->t(
249
-				'In the past on %1$s for the entire day',
250
-				[$startDate]
251
-			),
252
-			['minute', false] => $this->l10n->n(
253
-				'In %n minute on %1$s for the entire day',
254
-				'In %n minutes on %1$s for the entire day',
255
-				$occurring['interval'],
256
-				[$startDate]
257
-			),
258
-			['hour', false] => $this->l10n->n(
259
-				'In %n hour on %1$s for the entire day',
260
-				'In %n hours on %1$s for the entire day',
261
-				$occurring['interval'],
262
-				[$startDate]
263
-			),
264
-			['day', false] => $this->l10n->n(
265
-				'In %n day on %1$s for the entire day',
266
-				'In %n days on %1$s for the entire day',
267
-				$occurring['interval'],
268
-				[$startDate]
269
-			),
270
-			['week', false] => $this->l10n->n(
271
-				'In %n week on %1$s for the entire day',
272
-				'In %n weeks on %1$s for the entire day',
273
-				$occurring['interval'],
274
-				[$startDate]
275
-			),
276
-			['month', false] => $this->l10n->n(
277
-				'In %n month on %1$s for the entire day',
278
-				'In %n months on %1$s for the entire day',
279
-				$occurring['interval'],
280
-				[$startDate]
281
-			),
282
-			['year', false] => $this->l10n->n(
283
-				'In %n year on %1$s for the entire day',
284
-				'In %n years on %1$s for the entire day',
285
-				$occurring['interval'],
286
-				[$startDate]
287
-			),
288
-			['past', true] => $this->l10n->t(
289
-				'In the past on %1$s between %2$s - %3$s',
290
-				[$startDate, $startTime, $endTime]
291
-			),
292
-			['minute', true] => $this->l10n->n(
293
-				'In %n minute on %1$s between %2$s - %3$s',
294
-				'In %n minutes on %1$s between %2$s - %3$s',
295
-				$occurring['interval'],
296
-				[$startDate, $startTime, $endTime]
297
-			),
298
-			['hour', true] => $this->l10n->n(
299
-				'In %n hour on %1$s between %2$s - %3$s',
300
-				'In %n hours on %1$s between %2$s - %3$s',
301
-				$occurring['interval'],
302
-				[$startDate, $startTime, $endTime]
303
-			),
304
-			['day', true] => $this->l10n->n(
305
-				'In %n day on %1$s between %2$s - %3$s',
306
-				'In %n days on %1$s between %2$s - %3$s',
307
-				$occurring['interval'],
308
-				[$startDate, $startTime, $endTime]
309
-			),
310
-			['week', true] => $this->l10n->n(
311
-				'In %n week on %1$s between %2$s - %3$s',
312
-				'In %n weeks on %1$s between %2$s - %3$s',
313
-				$occurring['interval'],
314
-				[$startDate, $startTime, $endTime]
315
-			),
316
-			['month', true] => $this->l10n->n(
317
-				'In %n month on %1$s between %2$s - %3$s',
318
-				'In %n months on %1$s between %2$s - %3$s',
319
-				$occurring['interval'],
320
-				[$startDate, $startTime, $endTime]
321
-			),
322
-			['year', true] => $this->l10n->n(
323
-				'In %n year on %1$s between %2$s - %3$s',
324
-				'In %n years on %1$s between %2$s - %3$s',
325
-				$occurring['interval'],
326
-				[$startDate, $startTime, $endTime]
327
-			),
328
-			default => $this->l10n->t('Could not generate when statement')
329
-		};
330
-	}
331
-
332
-	/**
333
-	 * generates a when string based on recurrence precision/frequency
334
-	 *
335
-	 * @since 30.0.0
336
-	 *
337
-	 * @param EventReader $er
338
-	 *
339
-	 * @return string
340
-	 */
341
-	public function generateWhenStringRecurring(EventReader $er): string {
342
-		return match ($er->recurringPrecision()) {
343
-			'daily' => $this->generateWhenStringRecurringDaily($er),
344
-			'weekly' => $this->generateWhenStringRecurringWeekly($er),
345
-			'monthly' => $this->generateWhenStringRecurringMonthly($er),
346
-			'yearly' => $this->generateWhenStringRecurringYearly($er),
347
-			'fixed' => $this->generateWhenStringRecurringFixed($er),
348
-		};
349
-	}
350
-
351
-	/**
352
-	 * generates a when string for a daily precision/frequency
353
-	 *
354
-	 * @since 30.0.0
355
-	 *
356
-	 * @param EventReader $er
357
-	 *
358
-	 * @return string
359
-	 */
360
-	public function generateWhenStringRecurringDaily(EventReader $er): string {
361
-
362
-		// initialize
363
-		$interval = (int)$er->recurringInterval();
364
-		$startTime = null;
365
-		$conclusion = null;
366
-		// time of the day
367
-		if (!$er->entireDay()) {
368
-			$startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
369
-			$startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
370
-			$endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
371
-		}
372
-		// conclusion
373
-		if ($er->recurringConcludes()) {
374
-			$conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
375
-		}
376
-		// generate localized when string
377
-		// TRANSLATORS
378
-		// Indicates when a calendar event will happen, shown on invitation emails
379
-		// Output produced in order:
380
-		// Every Day for the entire day
381
-		// Every Day for the entire day until July 13, 2024
382
-		// Every Day between 8:00 AM - 9:00 AM (America/Toronto)
383
-		// Every Day between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024
384
-		// Every 3 Days for the entire day
385
-		// Every 3 Days for the entire day until July 13, 2024
386
-		// Every 3 Days between 8:00 AM - 9:00 AM (America/Toronto)
387
-		// Every 3 Days between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024
388
-		return match ([($interval > 1), $startTime !== null, $conclusion !== null]) {
389
-			[false, false, false] => $this->l10n->t('Every Day for the entire day'),
390
-			[false, false, true] => $this->l10n->t('Every Day for the entire day until %1$s', [$conclusion]),
391
-			[false, true, false] => $this->l10n->t('Every Day between %1$s - %2$s', [$startTime, $endTime]),
392
-			[false, true, true] => $this->l10n->t('Every Day between %1$s - %2$s until %3$s', [$startTime, $endTime, $conclusion]),
393
-			[true, false, false] => $this->l10n->t('Every %1$d Days for the entire day', [$interval]),
394
-			[true, false, true] => $this->l10n->t('Every %1$d Days for the entire day until %2$s', [$interval, $conclusion]),
395
-			[true, true, false] => $this->l10n->t('Every %1$d Days between %2$s - %3$s', [$interval, $startTime, $endTime]),
396
-			[true, true, true] => $this->l10n->t('Every %1$d Days between %2$s - %3$s until %4$s', [$interval, $startTime, $endTime, $conclusion]),
397
-			default => $this->l10n->t('Could not generate event recurrence statement')
398
-		};
399
-
400
-	}
401
-
402
-	/**
403
-	 * generates a when string for a weekly precision/frequency
404
-	 *
405
-	 * @since 30.0.0
406
-	 *
407
-	 * @param EventReader $er
408
-	 *
409
-	 * @return string
410
-	 */
411
-	public function generateWhenStringRecurringWeekly(EventReader $er): string {
412
-
413
-		// initialize
414
-		$interval = (int)$er->recurringInterval();
415
-		$startTime = null;
416
-		$conclusion = null;
417
-		// days of the week
418
-		$days = implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed()));
419
-		// time of the day
420
-		if (!$er->entireDay()) {
421
-			$startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
422
-			$startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
423
-			$endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
424
-		}
425
-		// conclusion
426
-		if ($er->recurringConcludes()) {
427
-			$conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
428
-		}
429
-		// generate localized when string
430
-		// TRANSLATORS
431
-		// Indicates when a calendar event will happen, shown on invitation emails
432
-		// Output produced in order:
433
-		// Every Week on Monday, Wednesday, Friday for the entire day
434
-		// Every Week on Monday, Wednesday, Friday for the entire day until July 13, 2024
435
-		// Every Week on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto)
436
-		// Every Week on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024
437
-		// Every 2 Weeks on Monday, Wednesday, Friday for the entire day
438
-		// Every 2 Weeks on Monday, Wednesday, Friday for the entire day until July 13, 2024
439
-		// Every 2 Weeks on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto)
440
-		// Every 2 Weeks on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024
441
-		return match ([($interval > 1), $startTime !== null, $conclusion !== null]) {
442
-			[false, false, false] => $this->l10n->t('Every Week on %1$s for the entire day', [$days]),
443
-			[false, false, true] => $this->l10n->t('Every Week on %1$s for the entire day until %2$s', [$days, $conclusion]),
444
-			[false, true, false] => $this->l10n->t('Every Week on %1$s between %2$s - %3$s', [$days, $startTime, $endTime]),
445
-			[false, true, true] => $this->l10n->t('Every Week on %1$s between %2$s - %3$s until %4$s', [$days, $startTime, $endTime, $conclusion]),
446
-			[true, false, false] => $this->l10n->t('Every %1$d Weeks on %2$s for the entire day', [$interval, $days]),
447
-			[true, false, true] => $this->l10n->t('Every %1$d Weeks on %2$s for the entire day until %3$s', [$interval, $days, $conclusion]),
448
-			[true, true, false] => $this->l10n->t('Every %1$d Weeks on %2$s between %3$s - %4$s', [$interval, $days, $startTime, $endTime]),
449
-			[true, true, true] => $this->l10n->t('Every %1$d Weeks on %2$s between %3$s - %4$s until %5$s', [$interval, $days, $startTime, $endTime, $conclusion]),
450
-			default => $this->l10n->t('Could not generate event recurrence statement')
451
-		};
452
-
453
-	}
454
-
455
-	/**
456
-	 * generates a when string for a monthly precision/frequency
457
-	 *
458
-	 * @since 30.0.0
459
-	 *
460
-	 * @param EventReader $er
461
-	 *
462
-	 * @return string
463
-	 */
464
-	public function generateWhenStringRecurringMonthly(EventReader $er): string {
465
-
466
-		// initialize
467
-		$interval = (int)$er->recurringInterval();
468
-		$startTime = null;
469
-		$conclusion = null;
470
-		// days of month
471
-		if ($er->recurringPattern() === 'R') {
472
-			$days = implode(', ', array_map(function ($value) { return $this->localizeRelativePositionName($value); }, $er->recurringRelativePositionNamed())) . ' '
473
-					. implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed()));
474
-		} else {
475
-			$days = implode(', ', $er->recurringDaysOfMonth());
476
-		}
477
-		// time of the day
478
-		if (!$er->entireDay()) {
479
-			$startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
480
-			$startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
481
-			$endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
482
-		}
483
-		// conclusion
484
-		if ($er->recurringConcludes()) {
485
-			$conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
486
-		}
487
-		// generate localized when string
488
-		// TRANSLATORS
489
-		// Indicates when a calendar event will happen, shown on invitation emails
490
-		// Output produced in order, output varies depending on if the event is absolute or releative:
491
-		// Absolute: Every Month on the 1, 8 for the entire day
492
-		// Relative: Every Month on the First Sunday, Saturday for the entire day
493
-		// Absolute: Every Month on the 1, 8 for the entire day until December 31, 2024
494
-		// Relative: Every Month on the First Sunday, Saturday for the entire day until December 31, 2024
495
-		// Absolute: Every Month on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto)
496
-		// Relative: Every Month on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)
497
-		// Absolute: Every Month on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024
498
-		// Relative: Every Month on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024
499
-		// Absolute: Every 2 Months on the 1, 8 for the entire day
500
-		// Relative: Every 2 Months on the First Sunday, Saturday for the entire day
501
-		// Absolute: Every 2 Months on the 1, 8 for the entire day until December 31, 2024
502
-		// Relative: Every 2 Months on the First Sunday, Saturday for the entire day until December 31, 2024
503
-		// Absolute: Every 2 Months on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto)
504
-		// Relative: Every 2 Months on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)
505
-		// Absolute: Every 2 Months on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024
506
-		// Relative: Every 2 Months on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024
507
-		return match ([($interval > 1), $startTime !== null, $conclusion !== null]) {
508
-			[false, false, false] => $this->l10n->t('Every Month on the %1$s for the entire day', [$days]),
509
-			[false, false, true] => $this->l10n->t('Every Month on the %1$s for the entire day until %2$s', [$days, $conclusion]),
510
-			[false, true, false] => $this->l10n->t('Every Month on the %1$s between %2$s - %3$s', [$days, $startTime, $endTime]),
511
-			[false, true, true] => $this->l10n->t('Every Month on the %1$s between %2$s - %3$s until %4$s', [$days, $startTime, $endTime, $conclusion]),
512
-			[true, false, false] => $this->l10n->t('Every %1$d Months on the %2$s for the entire day', [$interval, $days]),
513
-			[true, false, true] => $this->l10n->t('Every %1$d Months on the %2$s for the entire day until %3$s', [$interval, $days, $conclusion]),
514
-			[true, true, false] => $this->l10n->t('Every %1$d Months on the %2$s between %3$s - %4$s', [$interval, $days, $startTime, $endTime]),
515
-			[true, true, true] => $this->l10n->t('Every %1$d Months on the %2$s between %3$s - %4$s until %5$s', [$interval, $days, $startTime, $endTime, $conclusion]),
516
-			default => $this->l10n->t('Could not generate event recurrence statement')
517
-		};
518
-	}
519
-
520
-	/**
521
-	 * generates a when string for a yearly precision/frequency
522
-	 *
523
-	 * @since 30.0.0
524
-	 *
525
-	 * @param EventReader $er
526
-	 *
527
-	 * @return string
528
-	 */
529
-	public function generateWhenStringRecurringYearly(EventReader $er): string {
530
-
531
-		// initialize
532
-		$interval = (int)$er->recurringInterval();
533
-		$startTime = null;
534
-		$conclusion = null;
535
-		// months of year
536
-		$months = implode(', ', array_map(function ($value) { return $this->localizeMonthName($value); }, $er->recurringMonthsOfYearNamed()));
537
-		// days of month
538
-		if ($er->recurringPattern() === 'R') {
539
-			$days = implode(', ', array_map(function ($value) { return $this->localizeRelativePositionName($value); }, $er->recurringRelativePositionNamed())) . ' '
540
-					. implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed()));
541
-		} else {
542
-			$days = $er->startDateTime()->format('jS');
543
-		}
544
-		// time of the day
545
-		if (!$er->entireDay()) {
546
-			$startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
547
-			$startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
548
-			$endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
549
-		}
550
-		// conclusion
551
-		if ($er->recurringConcludes()) {
552
-			$conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
553
-		}
554
-		// generate localized when string
555
-		// TRANSLATORS
556
-		// Indicates when a calendar event will happen, shown on invitation emails
557
-		// Output produced in order, output varies depending on if the event is absolute or releative:
558
-		// Absolute: Every Year in July on the 1st for the entire day
559
-		// Relative: Every Year in July on the First Sunday, Saturday for the entire day
560
-		// Absolute: Every Year in July on the 1st for the entire day until July 31, 2026
561
-		// Relative: Every Year in July on the First Sunday, Saturday for the entire day until July 31, 2026
562
-		// Absolute: Every Year in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto)
563
-		// Relative: Every Year in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)
564
-		// Absolute: Every Year in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026
565
-		// Relative: Every Year in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026
566
-		// Absolute: Every 2 Years in July on the 1st for the entire day
567
-		// Relative: Every 2 Years in July on the First Sunday, Saturday for the entire day
568
-		// Absolute: Every 2 Years in July on the 1st for the entire day until July 31, 2026
569
-		// Relative: Every 2 Years in July on the First Sunday, Saturday for the entire day until July 31, 2026
570
-		// Absolute: Every 2 Years in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto)
571
-		// Relative: Every 2 Years in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)
572
-		// Absolute: Every 2 Years in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026
573
-		// Relative: Every 2 Years in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026
574
-		return match ([($interval > 1), $startTime !== null, $conclusion !== null]) {
575
-			[false, false, false] => $this->l10n->t('Every Year in %1$s on the %2$s for the entire day', [$months, $days]),
576
-			[false, false, true] => $this->l10n->t('Every Year in %1$s on the %2$s for the entire day until %3$s', [$months, $days, $conclusion]),
577
-			[false, true, false] => $this->l10n->t('Every Year in %1$s on the %2$s between %3$s - %4$s', [$months, $days, $startTime, $endTime]),
578
-			[false, true, true] => $this->l10n->t('Every Year in %1$s on the %2$s between %3$s - %4$s until %5$s', [$months, $days, $startTime, $endTime, $conclusion]),
579
-			[true, false, false] => $this->l10n->t('Every %1$d Years in %2$s on the %3$s for the entire day', [$interval, $months, $days]),
580
-			[true, false, true] => $this->l10n->t('Every %1$d Years in %2$s on the %3$s for the entire day until %4$s', [$interval, $months,  $days, $conclusion]),
581
-			[true, true, false] => $this->l10n->t('Every %1$d Years in %2$s on the %3$s between %4$s - %5$s', [$interval, $months, $days, $startTime, $endTime]),
582
-			[true, true, true] => $this->l10n->t('Every %1$d Years in %2$s on the %3$s between %4$s - %5$s until %6$s', [$interval, $months, $days, $startTime, $endTime, $conclusion]),
583
-			default => $this->l10n->t('Could not generate event recurrence statement')
584
-		};
585
-	}
586
-
587
-	/**
588
-	 * generates a when string for a fixed precision/frequency
589
-	 *
590
-	 * @since 30.0.0
591
-	 *
592
-	 * @param EventReader $er
593
-	 *
594
-	 * @return string
595
-	 */
596
-	public function generateWhenStringRecurringFixed(EventReader $er): string {
597
-		// initialize
598
-		$startTime = null;
599
-		$conclusion = null;
600
-		// time of the day
601
-		if (!$er->entireDay()) {
602
-			$startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
603
-			$startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
604
-			$endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
605
-		}
606
-		// conclusion
607
-		$conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
608
-		// generate localized when string
609
-		// TRANSLATORS
610
-		// Indicates when a calendar event will happen, shown on invitation emails
611
-		// Output produced in order:
612
-		// On specific dates for the entire day until July 13, 2024
613
-		// On specific dates between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024
614
-		return match ($startTime !== null) {
615
-			false => $this->l10n->t('On specific dates for the entire day until %1$s', [$conclusion]),
616
-			true => $this->l10n->t('On specific dates between %1$s - %2$s until %3$s', [$startTime, $endTime, $conclusion]),
617
-		};
618
-	}
619
-
620
-	/**
621
-	 * generates a occurring next string for a recurring event
622
-	 *
623
-	 * @since 30.0.0
624
-	 *
625
-	 * @param EventReader $er
626
-	 *
627
-	 * @return string
628
-	 */
629
-	public function generateOccurringString(EventReader $er): string {
630
-
631
-		// initialize
632
-		$occurrence = null;
633
-		$occurrence2 = null;
634
-		$occurrence3 = null;
635
-		// reset to initial occurrence
636
-		$er->recurrenceRewind();
637
-		// forward to current date
638
-		$er->recurrenceAdvanceTo($this->timeFactory->getDateTime());
639
-		// calculate time difference from now to start of next event occurrence and minimize it
640
-		$occurrenceIn = $this->minimizeInterval($this->timeFactory->getDateTime()->diff($er->recurrenceDate()));
641
-		// store next occurrence value
642
-		$occurrence = $this->l10n->l('date', $er->recurrenceDate(), ['width' => 'long']);
643
-		// forward one occurrence
644
-		$er->recurrenceAdvance();
645
-		// evaluate if occurrence is valid
646
-		if ($er->recurrenceDate() !== null) {
647
-			// store following occurrence value
648
-			$occurrence2 = $this->l10n->l('date', $er->recurrenceDate(), ['width' => 'long']);
649
-			// forward one occurrence
650
-			$er->recurrenceAdvance();
651
-			// evaluate if occurrence is valid
652
-			if ($er->recurrenceDate()) {
653
-				// store following occurrence value
654
-				$occurrence3 = $this->l10n->l('date', $er->recurrenceDate(), ['width' => 'long']);
655
-			}
656
-		}
657
-		// generate localized when string
658
-		// TRANSLATORS
659
-		// Indicates when a calendar event will happen, shown on invitation emails
660
-		// Output produced in order:
661
-		// In 1 minute/hour/day/week/month/year on July 1, 2024
662
-		// In 1 minute/hour/day/week/month/year on July 1, 2024 then on July 3, 2024
663
-		// In 1 minute/hour/day/week/month/year on July 1, 2024 then on July 3, 2024 and July 5, 2024
664
-		// In 2 minutes/hours/days/weeks/months/years on July 1, 2024
665
-		// In 2 minutes/hours/days/weeks/months/years on July 1, 2024 then on July 3, 2024
666
-		// In 2 minutes/hours/days/weeks/months/years on July 1, 2024 then on July 3, 2024 and July 5, 2024
667
-		return match ([$occurrenceIn['scale'], $occurrence2 !== null, $occurrence3 !== null]) {
668
-			['past', false, false] => $this->l10n->t(
669
-				'In the past on %1$s',
670
-				[$occurrence]
671
-			),
672
-			['minute', false, false] => $this->l10n->n(
673
-				'In %n minute on %1$s',
674
-				'In %n minutes on %1$s',
675
-				$occurrenceIn['interval'],
676
-				[$occurrence]
677
-			),
678
-			['hour', false, false] => $this->l10n->n(
679
-				'In %n hour on %1$s',
680
-				'In %n hours on %1$s',
681
-				$occurrenceIn['interval'],
682
-				[$occurrence]
683
-			),
684
-			['day', false, false] => $this->l10n->n(
685
-				'In %n day on %1$s',
686
-				'In %n days on %1$s',
687
-				$occurrenceIn['interval'],
688
-				[$occurrence]
689
-			),
690
-			['week', false, false] => $this->l10n->n(
691
-				'In %n week on %1$s',
692
-				'In %n weeks on %1$s',
693
-				$occurrenceIn['interval'],
694
-				[$occurrence]
695
-			),
696
-			['month', false, false] => $this->l10n->n(
697
-				'In %n month on %1$s',
698
-				'In %n months on %1$s',
699
-				$occurrenceIn['interval'],
700
-				[$occurrence]
701
-			),
702
-			['year', false, false] => $this->l10n->n(
703
-				'In %n year on %1$s',
704
-				'In %n years on %1$s',
705
-				$occurrenceIn['interval'],
706
-				[$occurrence]
707
-			),
708
-			['past', true, false] => $this->l10n->t(
709
-				'In the past on %1$s then on %2$s',
710
-				[$occurrence, $occurrence2]
711
-			),
712
-			['minute', true, false] => $this->l10n->n(
713
-				'In %n minute on %1$s then on %2$s',
714
-				'In %n minutes on %1$s then on %2$s',
715
-				$occurrenceIn['interval'],
716
-				[$occurrence, $occurrence2]
717
-			),
718
-			['hour', true, false] => $this->l10n->n(
719
-				'In %n hour on %1$s then on %2$s',
720
-				'In %n hours on %1$s then on %2$s',
721
-				$occurrenceIn['interval'],
722
-				[$occurrence, $occurrence2]
723
-			),
724
-			['day', true, false] => $this->l10n->n(
725
-				'In %n day on %1$s then on %2$s',
726
-				'In %n days on %1$s then on %2$s',
727
-				$occurrenceIn['interval'],
728
-				[$occurrence, $occurrence2]
729
-			),
730
-			['week', true, false] => $this->l10n->n(
731
-				'In %n week on %1$s then on %2$s',
732
-				'In %n weeks on %1$s then on %2$s',
733
-				$occurrenceIn['interval'],
734
-				[$occurrence, $occurrence2]
735
-			),
736
-			['month', true, false] => $this->l10n->n(
737
-				'In %n month on %1$s then on %2$s',
738
-				'In %n months on %1$s then on %2$s',
739
-				$occurrenceIn['interval'],
740
-				[$occurrence, $occurrence2]
741
-			),
742
-			['year', true, false] => $this->l10n->n(
743
-				'In %n year on %1$s then on %2$s',
744
-				'In %n years on %1$s then on %2$s',
745
-				$occurrenceIn['interval'],
746
-				[$occurrence, $occurrence2]
747
-			),
748
-			['past', true, true] => $this->l10n->t(
749
-				'In the past on %1$s then on %2$s and %3$s',
750
-				[$occurrence, $occurrence2, $occurrence3]
751
-			),
752
-			['minute', true, true] => $this->l10n->n(
753
-				'In %n minute on %1$s then on %2$s and %3$s',
754
-				'In %n minutes on %1$s then on %2$s and %3$s',
755
-				$occurrenceIn['interval'],
756
-				[$occurrence, $occurrence2, $occurrence3]
757
-			),
758
-			['hour', true, true] => $this->l10n->n(
759
-				'In %n hour on %1$s then on %2$s and %3$s',
760
-				'In %n hours on %1$s then on %2$s and %3$s',
761
-				$occurrenceIn['interval'],
762
-				[$occurrence, $occurrence2, $occurrence3]
763
-			),
764
-			['day', true, true] => $this->l10n->n(
765
-				'In %n day on %1$s then on %2$s and %3$s',
766
-				'In %n days on %1$s then on %2$s and %3$s',
767
-				$occurrenceIn['interval'],
768
-				[$occurrence, $occurrence2, $occurrence3]
769
-			),
770
-			['week', true, true] => $this->l10n->n(
771
-				'In %n week on %1$s then on %2$s and %3$s',
772
-				'In %n weeks on %1$s then on %2$s and %3$s',
773
-				$occurrenceIn['interval'],
774
-				[$occurrence, $occurrence2, $occurrence3]
775
-			),
776
-			['month', true, true] => $this->l10n->n(
777
-				'In %n month on %1$s then on %2$s and %3$s',
778
-				'In %n months on %1$s then on %2$s and %3$s',
779
-				$occurrenceIn['interval'],
780
-				[$occurrence, $occurrence2, $occurrence3]
781
-			),
782
-			['year', true, true] => $this->l10n->n(
783
-				'In %n year on %1$s then on %2$s and %3$s',
784
-				'In %n years on %1$s then on %2$s and %3$s',
785
-				$occurrenceIn['interval'],
786
-				[$occurrence, $occurrence2, $occurrence3]
787
-			),
788
-			default => $this->l10n->t('Could not generate next recurrence statement')
789
-		};
790
-
791
-	}
792
-
793
-	/**
794
-	 * @param VEvent $vEvent
795
-	 * @return array
796
-	 */
797
-	public function buildCancelledBodyData(VEvent $vEvent): array {
798
-		// construct event reader
799
-		$eventReaderCurrent = new EventReader($vEvent);
800
-		$defaultVal = '';
801
-		$strikethrough = "<span style='text-decoration: line-through'>%s</span>";
802
-
803
-		$newMeetingWhen = $this->generateWhenString($eventReaderCurrent);
804
-		$newSummary = htmlspecialchars(isset($vEvent->SUMMARY) && (string)$vEvent->SUMMARY !== '' ? (string)$vEvent->SUMMARY : $this->l10n->t('Untitled event'));
805
-		$newDescription = htmlspecialchars(isset($vEvent->DESCRIPTION) && (string)$vEvent->DESCRIPTION !== '' ? (string)$vEvent->DESCRIPTION : $defaultVal);
806
-		$newUrl = isset($vEvent->URL) && (string)$vEvent->URL !== '' ? sprintf('<a href="%1$s">%1$s</a>', $vEvent->URL) : $defaultVal;
807
-		$newLocation = htmlspecialchars(isset($vEvent->LOCATION) && (string)$vEvent->LOCATION !== '' ? (string)$vEvent->LOCATION : $defaultVal);
808
-		$newLocationHtml = $this->linkify($newLocation) ?? $newLocation;
809
-
810
-		$data = [];
811
-		$data['meeting_when_html'] = $newMeetingWhen === '' ?: sprintf($strikethrough, $newMeetingWhen);
812
-		$data['meeting_when'] = $newMeetingWhen;
813
-		$data['meeting_title_html'] = sprintf($strikethrough, $newSummary);
814
-		$data['meeting_title'] = $newSummary !== '' ? $newSummary: $this->l10n->t('Untitled event');
815
-		$data['meeting_description_html'] = $newDescription !== '' ? sprintf($strikethrough, $newDescription) : '';
816
-		$data['meeting_description'] = $newDescription;
817
-		$data['meeting_url_html'] = $newUrl !== '' ? sprintf($strikethrough, $newUrl) : '';
818
-		$data['meeting_url'] = isset($vEvent->URL) ? (string)$vEvent->URL : '';
819
-		$data['meeting_location_html'] = $newLocationHtml !== '' ? sprintf($strikethrough, $newLocationHtml) : '';
820
-		$data['meeting_location'] = $newLocation;
821
-		return $data;
822
-	}
823
-
824
-	/**
825
-	 * Check if event took place in the past
826
-	 *
827
-	 * @param VCalendar $vObject
828
-	 * @return int
829
-	 */
830
-	public function getLastOccurrence(VCalendar $vObject) {
831
-		/** @var VEvent $component */
832
-		$component = $vObject->VEVENT;
833
-
834
-		if (isset($component->RRULE)) {
835
-			$it = new EventIterator($vObject, (string)$component->UID);
836
-			$maxDate = new \DateTime(IMipPlugin::MAX_DATE);
837
-			if ($it->isInfinite()) {
838
-				return $maxDate->getTimestamp();
839
-			}
840
-
841
-			$end = $it->getDtEnd();
842
-			while ($it->valid() && $end < $maxDate) {
843
-				$end = $it->getDtEnd();
844
-				$it->next();
845
-			}
846
-			return $end->getTimestamp();
847
-		}
848
-
849
-		/** @var Property\ICalendar\DateTime $dtStart */
850
-		$dtStart = $component->DTSTART;
851
-
852
-		if (isset($component->DTEND)) {
853
-			/** @var Property\ICalendar\DateTime $dtEnd */
854
-			$dtEnd = $component->DTEND;
855
-			return $dtEnd->getDateTime()->getTimeStamp();
856
-		}
857
-
858
-		if (isset($component->DURATION)) {
859
-			/** @var \DateTime $endDate */
860
-			$endDate = clone $dtStart->getDateTime();
861
-			// $component->DTEND->getDateTime() returns DateTimeImmutable
862
-			$endDate = $endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
863
-			return $endDate->getTimestamp();
864
-		}
865
-
866
-		if (!$dtStart->hasTime()) {
867
-			/** @var \DateTime $endDate */
868
-			// $component->DTSTART->getDateTime() returns DateTimeImmutable
869
-			$endDate = clone $dtStart->getDateTime();
870
-			$endDate = $endDate->modify('+1 day');
871
-			return $endDate->getTimestamp();
872
-		}
873
-
874
-		// No computation of end time possible - return start date
875
-		return $dtStart->getDateTime()->getTimeStamp();
876
-	}
877
-
878
-	/**
879
-	 * @param Property $attendee
880
-	 */
881
-	public function setL10nFromAttendee(Property $attendee) {
882
-		$language = null;
883
-		$locale = null;
884
-		// check if the attendee is a system user
885
-		$userAddress = $attendee->getValue();
886
-		if (str_starts_with($userAddress, 'mailto:')) {
887
-			$userAddress = substr($userAddress, 7);
888
-		}
889
-		$users = $this->userManager->getByEmail($userAddress);
890
-		if ($users !== []) {
891
-			$user = array_shift($users);
892
-			$language = $this->userConfig->getValueString($user->getUID(), 'core', 'lang', '') ?: null;
893
-			$locale = $this->userConfig->getValueString($user->getUID(), 'core', 'locale', '') ?: null;
894
-		}
895
-		// fallback to attendee LANGUAGE parameter if language not set
896
-		if ($language === null && isset($attendee['LANGUAGE']) && $attendee['LANGUAGE'] instanceof Parameter) {
897
-			$language = $attendee['LANGUAGE']->getValue();
898
-		}
899
-		// fallback to system language if language not set
900
-		if ($language === null) {
901
-			$language = $this->l10nFactory->findGenericLanguage();
902
-		}
903
-		// fallback to system locale if locale not set
904
-		if ($locale === null) {
905
-			$locale = $this->l10nFactory->findLocale($language);
906
-		}
907
-		$this->l10n = $this->l10nFactory->get('dav', $language, $locale);
908
-	}
909
-
910
-	/**
911
-	 * @param Property|null $attendee
912
-	 * @return bool
913
-	 */
914
-	public function getAttendeeRsvpOrReqForParticipant(?Property $attendee = null) {
915
-		if ($attendee === null) {
916
-			return false;
917
-		}
918
-
919
-		$rsvp = $attendee->offsetGet('RSVP');
920
-		if (($rsvp instanceof Parameter) && (strcasecmp($rsvp->getValue(), 'TRUE') === 0)) {
921
-			return true;
922
-		}
923
-		$role = $attendee->offsetGet('ROLE');
924
-		// @see https://datatracker.ietf.org/doc/html/rfc5545#section-3.2.16
925
-		// Attendees without a role are assumed required and should receive an invitation link even if they have no RSVP set
926
-		if ($role === null
927
-			|| (($role instanceof Parameter) && (strcasecmp($role->getValue(), 'REQ-PARTICIPANT') === 0))
928
-			|| (($role instanceof Parameter) && (strcasecmp($role->getValue(), 'OPT-PARTICIPANT') === 0))
929
-		) {
930
-			return true;
931
-		}
932
-
933
-		// RFC 5545 3.2.17: default RSVP is false
934
-		return false;
935
-	}
936
-
937
-	/**
938
-	 * @param IEMailTemplate $template
939
-	 * @param string $method
940
-	 * @param string $sender
941
-	 * @param string $summary
942
-	 * @param string|null $partstat
943
-	 * @param bool $isModified
944
-	 */
945
-	public function addSubjectAndHeading(IEMailTemplate $template,
946
-		string $method, string $sender, string $summary, bool $isModified, ?Property $replyingAttendee = null): void {
947
-		if ($method === IMipPlugin::METHOD_CANCEL) {
948
-			// TRANSLATORS Subject for email, when an invitation is cancelled. Ex: "Cancelled: {{Event Name}}"
949
-			$template->setSubject($this->l10n->t('Cancelled: %1$s', [$summary]));
950
-			$template->addHeading($this->l10n->t('"%1$s" has been canceled', [$summary]));
951
-		} elseif ($method === IMipPlugin::METHOD_REPLY) {
952
-			// TRANSLATORS Subject for email, when an invitation is replied to. Ex: "Re: {{Event Name}}"
953
-			$template->setSubject($this->l10n->t('Re: %1$s', [$summary]));
954
-			// Build the strings
955
-			$partstat = (isset($replyingAttendee)) ? $replyingAttendee->offsetGet('PARTSTAT') : null;
956
-			$partstat = ($partstat instanceof Parameter) ? $partstat->getValue() : null;
957
-			switch ($partstat) {
958
-				case 'ACCEPTED':
959
-					$template->addHeading($this->l10n->t('%1$s has accepted your invitation', [$sender]));
960
-					break;
961
-				case 'TENTATIVE':
962
-					$template->addHeading($this->l10n->t('%1$s has tentatively accepted your invitation', [$sender]));
963
-					break;
964
-				case 'DECLINED':
965
-					$template->addHeading($this->l10n->t('%1$s has declined your invitation', [$sender]));
966
-					break;
967
-				case null:
968
-				default:
969
-					$template->addHeading($this->l10n->t('%1$s has responded to your invitation', [$sender]));
970
-					break;
971
-			}
972
-		} elseif ($method === IMipPlugin::METHOD_REQUEST && $isModified) {
973
-			// TRANSLATORS Subject for email, when an invitation is updated. Ex: "Invitation updated: {{Event Name}}"
974
-			$template->setSubject($this->l10n->t('Invitation updated: %1$s', [$summary]));
975
-			$template->addHeading($this->l10n->t('%1$s updated the event "%2$s"', [$sender, $summary]));
976
-		} else {
977
-			// TRANSLATORS Subject for email, when an invitation is sent. Ex: "Invitation: {{Event Name}}"
978
-			$template->setSubject($this->l10n->t('Invitation: %1$s', [$summary]));
979
-			$template->addHeading($this->l10n->t('%1$s would like to invite you to "%2$s"', [$sender, $summary]));
980
-		}
981
-	}
982
-
983
-	/**
984
-	 * @param string $path
985
-	 * @return string
986
-	 */
987
-	public function getAbsoluteImagePath($path): string {
988
-		return $this->urlGenerator->getAbsoluteURL(
989
-			$this->urlGenerator->imagePath('core', $path)
990
-		);
991
-	}
992
-
993
-	/**
994
-	 * addAttendees: add organizer and attendee names/emails to iMip mail.
995
-	 *
996
-	 * Enable with DAV setting: invitation_list_attendees (default: no)
997
-	 *
998
-	 * The default is 'no', which matches old behavior, and is privacy preserving.
999
-	 *
1000
-	 * To enable including attendees in invitation emails:
1001
-	 *   % php occ config:app:set dav invitation_list_attendees --value yes --type bool
1002
-	 *
1003
-	 * @param IEMailTemplate $template
1004
-	 * @param IL10N $this->l10n
1005
-	 * @param VEvent $vevent
1006
-	 * @author brad2014 on github.com
1007
-	 */
1008
-	public function addAttendees(IEMailTemplate $template, VEvent $vevent) {
1009
-		if (!$this->appConfig->getValueBool('dav', 'invitation_list_attendees')) {
1010
-			return;
1011
-		}
1012
-
1013
-		if (isset($vevent->ORGANIZER)) {
1014
-			/** @var Property&Property\ICalendar\CalAddress $organizer */
1015
-			$organizer = $vevent->ORGANIZER;
1016
-			$organizerEmail = substr($organizer->getNormalizedValue(), 7);
1017
-			/** @var string|null $organizerName */
1018
-			$organizerName = isset($organizer->CN) ? $organizer->CN->getValue() : null;
1019
-			$organizerHTML = sprintf('<a href="%s">%s</a>',
1020
-				htmlspecialchars($organizer->getNormalizedValue()),
1021
-				htmlspecialchars($organizerName ?: $organizerEmail));
1022
-			$organizerText = sprintf('%s <%s>', $organizerName, $organizerEmail);
1023
-			if (isset($organizer['PARTSTAT'])) {
1024
-				/** @var Parameter $partstat */
1025
-				$partstat = $organizer['PARTSTAT'];
1026
-				if (strcasecmp($partstat->getValue(), 'ACCEPTED') === 0) {
1027
-					$organizerHTML .= ' ✔︎';
1028
-					$organizerText .= ' ✔︎';
1029
-				}
1030
-			}
1031
-			$template->addBodyListItem($organizerHTML, $this->l10n->t('Organizer:'),
1032
-				$this->getAbsoluteImagePath('caldav/organizer.png'),
1033
-				$organizerText, '', IMipPlugin::IMIP_INDENT);
1034
-		}
1035
-
1036
-		$attendees = $vevent->select('ATTENDEE');
1037
-		if (count($attendees) === 0) {
1038
-			return;
1039
-		}
1040
-
1041
-		$attendeesHTML = [];
1042
-		$attendeesText = [];
1043
-		foreach ($attendees as $attendee) {
1044
-			/** @var Property&Property\ICalendar\CalAddress $attendee */
1045
-			$attendeeEmail = substr($attendee->getNormalizedValue(), 7);
1046
-			$attendeeName = null;
1047
-			if (isset($attendee['CN'])) {
1048
-				/** @var Parameter $cn */
1049
-				$cn = $attendee['CN'];
1050
-				$attendeeName = $cn->getValue();
1051
-			}
1052
-			$attendeeHTML = sprintf('<a href="%s">%s</a>',
1053
-				htmlspecialchars($attendee->getNormalizedValue()),
1054
-				htmlspecialchars($attendeeName ?: $attendeeEmail));
1055
-			$attendeeText = sprintf('%s <%s>', $attendeeName, $attendeeEmail);
1056
-			if (isset($attendee['PARTSTAT'])) {
1057
-				/** @var Parameter $partstat */
1058
-				$partstat = $attendee['PARTSTAT'];
1059
-				if (strcasecmp($partstat->getValue(), 'ACCEPTED') === 0) {
1060
-					$attendeeHTML .= ' ✔︎';
1061
-					$attendeeText .= ' ✔︎';
1062
-				}
1063
-			}
1064
-			$attendeesHTML[] = $attendeeHTML;
1065
-			$attendeesText[] = $attendeeText;
1066
-		}
1067
-
1068
-		$template->addBodyListItem(implode('<br/>', $attendeesHTML), $this->l10n->t('Attendees:'),
1069
-			$this->getAbsoluteImagePath('caldav/attendees.png'),
1070
-			implode("\n", $attendeesText), '', IMipPlugin::IMIP_INDENT);
1071
-	}
1072
-
1073
-	/**
1074
-	 * @param IEMailTemplate $template
1075
-	 * @param VEVENT $vevent
1076
-	 * @param $data
1077
-	 */
1078
-	public function addBulletList(IEMailTemplate $template, VEvent $vevent, $data) {
1079
-		$template->addBodyListItem(
1080
-			$data['meeting_title_html'] ?? htmlspecialchars($data['meeting_title']), $this->l10n->t('Title:'),
1081
-			$this->getAbsoluteImagePath('caldav/title.png'), $data['meeting_title'], '', IMipPlugin::IMIP_INDENT);
1082
-		if ($data['meeting_when'] !== '') {
1083
-			$template->addBodyListItem($data['meeting_when_html'] ?? htmlspecialchars($data['meeting_when']), $this->l10n->t('When:'),
1084
-				$this->getAbsoluteImagePath('caldav/time.png'), $data['meeting_when'], '', IMipPlugin::IMIP_INDENT);
1085
-		}
1086
-		if ($data['meeting_location'] !== '') {
1087
-			$template->addBodyListItem($data['meeting_location_html'] ?? htmlspecialchars($data['meeting_location']), $this->l10n->t('Location:'),
1088
-				$this->getAbsoluteImagePath('caldav/location.png'), $data['meeting_location'], '', IMipPlugin::IMIP_INDENT);
1089
-		}
1090
-		if ($data['meeting_url'] !== '') {
1091
-			$template->addBodyListItem($data['meeting_url_html'] ?? htmlspecialchars($data['meeting_url']), $this->l10n->t('Link:'),
1092
-				$this->getAbsoluteImagePath('caldav/link.png'), $data['meeting_url'], '', IMipPlugin::IMIP_INDENT);
1093
-		}
1094
-		if (isset($data['meeting_occurring'])) {
1095
-			$template->addBodyListItem($data['meeting_occurring_html'] ?? htmlspecialchars($data['meeting_occurring']), $this->l10n->t('Occurring:'),
1096
-				$this->getAbsoluteImagePath('caldav/time.png'), $data['meeting_occurring'], '', IMipPlugin::IMIP_INDENT);
1097
-		}
1098
-
1099
-		$this->addAttendees($template, $vevent);
1100
-
1101
-		/* Put description last, like an email body, since it can be arbitrarily long */
1102
-		if ($data['meeting_description']) {
1103
-			$template->addBodyListItem($data['meeting_description_html'] ?? htmlspecialchars($data['meeting_description']), $this->l10n->t('Description:'),
1104
-				$this->getAbsoluteImagePath('caldav/description.png'), $data['meeting_description'], '', IMipPlugin::IMIP_INDENT);
1105
-		}
1106
-	}
1107
-
1108
-	/**
1109
-	 * @param Message $iTipMessage
1110
-	 * @return null|Property
1111
-	 */
1112
-	public function getCurrentAttendee(Message $iTipMessage): ?Property {
1113
-		/** @var VEvent $vevent */
1114
-		$vevent = $iTipMessage->message->VEVENT;
1115
-		$attendees = $vevent->select('ATTENDEE');
1116
-		foreach ($attendees as $attendee) {
1117
-			if ($iTipMessage->method === 'REPLY' && strcasecmp($attendee->getValue(), $iTipMessage->sender) === 0) {
1118
-				/** @var Property $attendee */
1119
-				return $attendee;
1120
-			} elseif (strcasecmp($attendee->getValue(), $iTipMessage->recipient) === 0) {
1121
-				/** @var Property $attendee */
1122
-				return $attendee;
1123
-			}
1124
-		}
1125
-		return null;
1126
-	}
1127
-
1128
-	/**
1129
-	 * @param Message $iTipMessage
1130
-	 * @param VEvent $vevent
1131
-	 * @param int $lastOccurrence
1132
-	 * @return string
1133
-	 */
1134
-	public function createInvitationToken(Message $iTipMessage, VEvent $vevent, int $lastOccurrence): string {
1135
-		$token = $this->random->generate(60, ISecureRandom::CHAR_ALPHANUMERIC);
1136
-
1137
-		$attendee = $iTipMessage->recipient;
1138
-		$organizer = $iTipMessage->sender;
1139
-		$sequence = $iTipMessage->sequence;
1140
-		$recurrenceId = isset($vevent->{'RECURRENCE-ID'})
1141
-			? $vevent->{'RECURRENCE-ID'}->serialize() : null;
1142
-		$uid = $vevent->{'UID'}?->getValue();
1143
-
1144
-		$query = $this->db->getQueryBuilder();
1145
-		$query->insert('calendar_invitations')
1146
-			->values([
1147
-				'token' => $query->createNamedParameter($token),
1148
-				'attendee' => $query->createNamedParameter($attendee),
1149
-				'organizer' => $query->createNamedParameter($organizer),
1150
-				'sequence' => $query->createNamedParameter($sequence),
1151
-				'recurrenceid' => $query->createNamedParameter($recurrenceId),
1152
-				'expiration' => $query->createNamedParameter($lastOccurrence),
1153
-				'uid' => $query->createNamedParameter($uid)
1154
-			])
1155
-			->executeStatement();
1156
-
1157
-		return $token;
1158
-	}
1159
-
1160
-	/**
1161
-	 * @param IEMailTemplate $template
1162
-	 * @param $token
1163
-	 */
1164
-	public function addResponseButtons(IEMailTemplate $template, $token) {
1165
-		$template->addBodyButtonGroup(
1166
-			$this->l10n->t('Accept'),
1167
-			$this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.accept', [
1168
-				'token' => $token,
1169
-			]),
1170
-			$this->l10n->t('Decline'),
1171
-			$this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.decline', [
1172
-				'token' => $token,
1173
-			])
1174
-		);
1175
-	}
1176
-
1177
-	public function addMoreOptionsButton(IEMailTemplate $template, $token) {
1178
-		$moreOptionsURL = $this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.options', [
1179
-			'token' => $token,
1180
-		]);
1181
-		$html = vsprintf('<small><a href="%s">%s</a></small>', [
1182
-			$moreOptionsURL, $this->l10n->t('More options …')
1183
-		]);
1184
-		$text = $this->l10n->t('More options at %s', [$moreOptionsURL]);
1185
-
1186
-		$template->addBodyText($html, $text);
1187
-	}
1188
-
1189
-	public function getReplyingAttendee(Message $iTipMessage): ?Property {
1190
-		/** @var VEvent $vevent */
1191
-		$vevent = $iTipMessage->message->VEVENT;
1192
-		$attendees = $vevent->select('ATTENDEE');
1193
-		foreach ($attendees as $attendee) {
1194
-			/** @var Property $attendee */
1195
-			if (strcasecmp($attendee->getValue(), $iTipMessage->sender) === 0) {
1196
-				return $attendee;
1197
-			}
1198
-		}
1199
-		return null;
1200
-	}
1201
-
1202
-	public function isRoomOrResource(Property $attendee): bool {
1203
-		$cuType = $attendee->offsetGet('CUTYPE');
1204
-		if (!$cuType instanceof Parameter) {
1205
-			return false;
1206
-		}
1207
-		$type = $cuType->getValue() ?? 'INDIVIDUAL';
1208
-		if (\in_array(strtoupper($type), ['RESOURCE', 'ROOM'], true)) {
1209
-			// Don't send emails to things
1210
-			return true;
1211
-		}
1212
-		return false;
1213
-	}
1214
-
1215
-	public function isCircle(Property $attendee): bool {
1216
-		$cuType = $attendee->offsetGet('CUTYPE');
1217
-		if (!$cuType instanceof Parameter) {
1218
-			return false;
1219
-		}
1220
-
1221
-		$uri = $attendee->getValue();
1222
-		if (!$uri) {
1223
-			return false;
1224
-		}
1225
-
1226
-		$cuTypeValue = $cuType->getValue();
1227
-		return $cuTypeValue === 'GROUP' && str_starts_with($uri, 'mailto:circle+');
1228
-	}
1229
-
1230
-	public function minimizeInterval(\DateInterval $dateInterval): array {
1231
-		// evaluate if time interval is in the past
1232
-		if ($dateInterval->invert == 1) {
1233
-			return ['interval' => 1, 'scale' => 'past'];
1234
-		}
1235
-		// evaluate interval parts and return smallest time period
1236
-		if ($dateInterval->y > 0) {
1237
-			$interval = $dateInterval->y;
1238
-			$scale = 'year';
1239
-		} elseif ($dateInterval->m > 0) {
1240
-			$interval = $dateInterval->m;
1241
-			$scale = 'month';
1242
-		} elseif ($dateInterval->d >= 7) {
1243
-			$interval = (int)($dateInterval->d / 7);
1244
-			$scale = 'week';
1245
-		} elseif ($dateInterval->d > 0) {
1246
-			$interval = $dateInterval->d;
1247
-			$scale = 'day';
1248
-		} elseif ($dateInterval->h > 0) {
1249
-			$interval = $dateInterval->h;
1250
-			$scale = 'hour';
1251
-		} else {
1252
-			$interval = $dateInterval->i;
1253
-			$scale = 'minute';
1254
-		}
1255
-
1256
-		return ['interval' => $interval, 'scale' => $scale];
1257
-	}
1258
-
1259
-	/**
1260
-	 * Localizes week day names to another language
1261
-	 *
1262
-	 * @param string $value
1263
-	 *
1264
-	 * @return string
1265
-	 */
1266
-	public function localizeDayName(string $value): string {
1267
-		return match ($value) {
1268
-			'Monday' => $this->l10n->t('Monday'),
1269
-			'Tuesday' => $this->l10n->t('Tuesday'),
1270
-			'Wednesday' => $this->l10n->t('Wednesday'),
1271
-			'Thursday' => $this->l10n->t('Thursday'),
1272
-			'Friday' => $this->l10n->t('Friday'),
1273
-			'Saturday' => $this->l10n->t('Saturday'),
1274
-			'Sunday' => $this->l10n->t('Sunday'),
1275
-		};
1276
-	}
1277
-
1278
-	/**
1279
-	 * Localizes month names to another language
1280
-	 *
1281
-	 * @param string $value
1282
-	 *
1283
-	 * @return string
1284
-	 */
1285
-	public function localizeMonthName(string $value): string {
1286
-		return match ($value) {
1287
-			'January' => $this->l10n->t('January'),
1288
-			'February' => $this->l10n->t('February'),
1289
-			'March' => $this->l10n->t('March'),
1290
-			'April' => $this->l10n->t('April'),
1291
-			'May' => $this->l10n->t('May'),
1292
-			'June' => $this->l10n->t('June'),
1293
-			'July' => $this->l10n->t('July'),
1294
-			'August' => $this->l10n->t('August'),
1295
-			'September' => $this->l10n->t('September'),
1296
-			'October' => $this->l10n->t('October'),
1297
-			'November' => $this->l10n->t('November'),
1298
-			'December' => $this->l10n->t('December'),
1299
-		};
1300
-	}
1301
-
1302
-	/**
1303
-	 * Localizes relative position names to another language
1304
-	 *
1305
-	 * @param string $value
1306
-	 *
1307
-	 * @return string
1308
-	 */
1309
-	public function localizeRelativePositionName(string $value): string {
1310
-		return match ($value) {
1311
-			'First' => $this->l10n->t('First'),
1312
-			'Second' => $this->l10n->t('Second'),
1313
-			'Third' => $this->l10n->t('Third'),
1314
-			'Fourth' => $this->l10n->t('Fourth'),
1315
-			'Fifth' => $this->l10n->t('Fifth'),
1316
-			'Last' => $this->l10n->t('Last'),
1317
-			'Second Last' => $this->l10n->t('Second Last'),
1318
-			'Third Last' => $this->l10n->t('Third Last'),
1319
-			'Fourth Last' => $this->l10n->t('Fourth Last'),
1320
-			'Fifth Last' => $this->l10n->t('Fifth Last'),
1321
-		};
1322
-	}
33
+    private IL10N $l10n;
34
+
35
+    /** @var string[] */
36
+    private const STRING_DIFF = [
37
+        'meeting_title' => 'SUMMARY',
38
+        'meeting_description' => 'DESCRIPTION',
39
+        'meeting_url' => 'URL',
40
+        'meeting_location' => 'LOCATION'
41
+    ];
42
+
43
+    public function __construct(
44
+        private URLGenerator $urlGenerator,
45
+        private IDBConnection $db,
46
+        private ISecureRandom $random,
47
+        private L10NFactory $l10nFactory,
48
+        private ITimeFactory $timeFactory,
49
+        private readonly IUserManager $userManager,
50
+        private readonly IUserConfig $userConfig,
51
+        private readonly IAppConfig $appConfig,
52
+    ) {
53
+        $language = $this->l10nFactory->findGenericLanguage();
54
+        $locale = $this->l10nFactory->findLocale($language);
55
+        $this->l10n = $this->l10nFactory->get('dav', $language, $locale);
56
+    }
57
+
58
+    /**
59
+     * @param string|null $senderName
60
+     * @param string $default
61
+     * @return string
62
+     */
63
+    public function getFrom(?string $senderName, string $default): string {
64
+        if ($senderName === null) {
65
+            return $default;
66
+        }
67
+
68
+        return $this->l10n->t('%1$s via %2$s', [$senderName, $default]);
69
+    }
70
+
71
+    public static function readPropertyWithDefault(VEvent $vevent, string $property, string $default) {
72
+        if (isset($vevent->$property)) {
73
+            $value = $vevent->$property->getValue();
74
+            if (!empty($value)) {
75
+                return $value;
76
+            }
77
+        }
78
+        return $default;
79
+    }
80
+
81
+    private function generateDiffString(VEvent $vevent, VEvent $oldVEvent, string $property, string $default): ?string {
82
+        $strikethrough = "<span style='text-decoration: line-through'>%s</span><br />%s";
83
+        if (!isset($vevent->$property)) {
84
+            return $default;
85
+        }
86
+        $value = $vevent->$property->getValue();
87
+        $newstring = $value === null ? null : htmlspecialchars($value);
88
+        if (isset($oldVEvent->$property) && $oldVEvent->$property->getValue() !== $newstring) {
89
+            $oldstring = $oldVEvent->$property->getValue();
90
+            return sprintf($strikethrough, htmlspecialchars($oldstring), $newstring);
91
+        }
92
+        return $newstring;
93
+    }
94
+
95
+    /**
96
+     * Like generateDiffString() but linkifies the property values if they are urls.
97
+     */
98
+    private function generateLinkifiedDiffString(VEvent $vevent, VEvent $oldVEvent, string $property, string $default): ?string {
99
+        if (!isset($vevent->$property)) {
100
+            return $default;
101
+        }
102
+        /** @var string|null $newString */
103
+        $newString = htmlspecialchars($vevent->$property->getValue());
104
+        $oldString = isset($oldVEvent->$property) ? htmlspecialchars($oldVEvent->$property->getValue()) : null;
105
+        if ($oldString !== $newString) {
106
+            return sprintf(
107
+                "<span style='text-decoration: line-through'>%s</span><br />%s",
108
+                $this->linkify($oldString) ?? $oldString ?? '',
109
+                $this->linkify($newString) ?? $newString ?? ''
110
+            );
111
+        }
112
+        return $this->linkify($newString) ?? $newString;
113
+    }
114
+
115
+    /**
116
+     * Convert a given url to a html link element or return null otherwise.
117
+     */
118
+    private function linkify(?string $url): ?string {
119
+        if ($url === null) {
120
+            return null;
121
+        }
122
+        if (!str_starts_with($url, 'http://') && !str_starts_with($url, 'https://')) {
123
+            return null;
124
+        }
125
+
126
+        return sprintf('<a href="%1$s">%1$s</a>', htmlspecialchars($url));
127
+    }
128
+
129
+    /**
130
+     * @param VEvent $vEvent
131
+     * @param VEvent|null $oldVEvent
132
+     * @return array
133
+     */
134
+    public function buildBodyData(VEvent $vEvent, ?VEvent $oldVEvent): array {
135
+
136
+        // construct event reader
137
+        $eventReaderCurrent = new EventReader($vEvent);
138
+        $eventReaderPrevious = !empty($oldVEvent) ? new EventReader($oldVEvent) : null;
139
+        $defaultVal = '';
140
+        $data = [];
141
+        $data['meeting_when'] = $this->generateWhenString($eventReaderCurrent);
142
+
143
+        foreach (self::STRING_DIFF as $key => $property) {
144
+            $data[$key] = self::readPropertyWithDefault($vEvent, $property, $defaultVal);
145
+        }
146
+
147
+        $data['meeting_url_html'] = self::readPropertyWithDefault($vEvent, 'URL', $defaultVal);
148
+
149
+        if (($locationHtml = $this->linkify($data['meeting_location'])) !== null) {
150
+            $data['meeting_location_html'] = $locationHtml;
151
+        }
152
+
153
+        if (!empty($oldVEvent)) {
154
+            $oldMeetingWhen = $this->generateWhenString($eventReaderPrevious);
155
+            $data['meeting_title_html'] = $this->generateDiffString($vEvent, $oldVEvent, 'SUMMARY', $data['meeting_title']);
156
+            $data['meeting_description_html'] = $this->generateDiffString($vEvent, $oldVEvent, 'DESCRIPTION', $data['meeting_description']);
157
+            $data['meeting_location_html'] = $this->generateLinkifiedDiffString($vEvent, $oldVEvent, 'LOCATION', $data['meeting_location']);
158
+
159
+            $oldUrl = self::readPropertyWithDefault($oldVEvent, 'URL', $defaultVal);
160
+            $data['meeting_url_html'] = !empty($oldUrl) && $oldUrl !== $data['meeting_url'] ? sprintf('<a href="%1$s">%1$s</a>', $oldUrl) : $data['meeting_url'];
161
+
162
+            $data['meeting_when_html'] = $oldMeetingWhen !== $data['meeting_when'] ? sprintf("<span style='text-decoration: line-through'>%s</span><br />%s", $oldMeetingWhen, $data['meeting_when']) : $data['meeting_when'];
163
+        }
164
+        // generate occurring next string
165
+        if ($eventReaderCurrent->recurs()) {
166
+            $data['meeting_occurring'] = $this->generateOccurringString($eventReaderCurrent);
167
+        }
168
+        return $data;
169
+    }
170
+
171
+    /**
172
+     * @param VEvent $vEvent
173
+     * @return array
174
+     */
175
+    public function buildReplyBodyData(VEvent $vEvent): array {
176
+        // construct event reader
177
+        $eventReader = new EventReader($vEvent);
178
+        $defaultVal = '';
179
+        $data = [];
180
+        $data['meeting_when'] = $this->generateWhenString($eventReader);
181
+
182
+        foreach (self::STRING_DIFF as $key => $property) {
183
+            $data[$key] = self::readPropertyWithDefault($vEvent, $property, $defaultVal);
184
+        }
185
+
186
+        if (($locationHtml = $this->linkify($data['meeting_location'])) !== null) {
187
+            $data['meeting_location_html'] = $locationHtml;
188
+        }
189
+
190
+        $data['meeting_url_html'] = $data['meeting_url'] ? sprintf('<a href="%1$s">%1$s</a>', $data['meeting_url']) : '';
191
+
192
+        // generate occurring next string
193
+        if ($eventReader->recurs()) {
194
+            $data['meeting_occurring'] = $this->generateOccurringString($eventReader);
195
+        }
196
+
197
+        return $data;
198
+    }
199
+
200
+    /**
201
+     * generates a when string based on if a event has an recurrence or not
202
+     *
203
+     * @since 30.0.0
204
+     *
205
+     * @param EventReader $er
206
+     *
207
+     * @return string
208
+     */
209
+    public function generateWhenString(EventReader $er): string {
210
+        return match ($er->recurs()) {
211
+            true => $this->generateWhenStringRecurring($er),
212
+            false => $this->generateWhenStringSingular($er)
213
+        };
214
+    }
215
+
216
+    /**
217
+     * generates a when string for a non recurring event
218
+     *
219
+     * @since 30.0.0
220
+     *
221
+     * @param EventReader $er
222
+     *
223
+     * @return string
224
+     */
225
+    public function generateWhenStringSingular(EventReader $er): string {
226
+        // initialize
227
+        $startTime = null;
228
+        $endTime = null;
229
+        // calculate time difference from now to start of event
230
+        $occurring = $this->minimizeInterval($this->timeFactory->getDateTime()->diff($er->recurrenceDate()));
231
+        // extract start date
232
+        $startDate = $this->l10n->l('date', $er->startDateTime(), ['width' => 'full']);
233
+        // time of the day
234
+        if (!$er->entireDay()) {
235
+            $startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
236
+            $startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
237
+            $endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
238
+        }
239
+        // generate localized when string
240
+        // TRANSLATORS
241
+        // Indicates when a calendar event will happen, shown on invitation emails
242
+        // Output produced in order:
243
+        // In 1 minute/hour/day/week/month/year on July 1, 2024 for the entire day
244
+        // In 1 minute/hour/day/week/month/year on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)
245
+        // In 2 minutes/hours/days/weeks/months/years on July 1, 2024 for the entire day
246
+        // In 2 minutes/hours/days/weeks/months/years on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)
247
+        return match ([$occurring['scale'], $endTime !== null]) {
248
+            ['past', false] => $this->l10n->t(
249
+                'In the past on %1$s for the entire day',
250
+                [$startDate]
251
+            ),
252
+            ['minute', false] => $this->l10n->n(
253
+                'In %n minute on %1$s for the entire day',
254
+                'In %n minutes on %1$s for the entire day',
255
+                $occurring['interval'],
256
+                [$startDate]
257
+            ),
258
+            ['hour', false] => $this->l10n->n(
259
+                'In %n hour on %1$s for the entire day',
260
+                'In %n hours on %1$s for the entire day',
261
+                $occurring['interval'],
262
+                [$startDate]
263
+            ),
264
+            ['day', false] => $this->l10n->n(
265
+                'In %n day on %1$s for the entire day',
266
+                'In %n days on %1$s for the entire day',
267
+                $occurring['interval'],
268
+                [$startDate]
269
+            ),
270
+            ['week', false] => $this->l10n->n(
271
+                'In %n week on %1$s for the entire day',
272
+                'In %n weeks on %1$s for the entire day',
273
+                $occurring['interval'],
274
+                [$startDate]
275
+            ),
276
+            ['month', false] => $this->l10n->n(
277
+                'In %n month on %1$s for the entire day',
278
+                'In %n months on %1$s for the entire day',
279
+                $occurring['interval'],
280
+                [$startDate]
281
+            ),
282
+            ['year', false] => $this->l10n->n(
283
+                'In %n year on %1$s for the entire day',
284
+                'In %n years on %1$s for the entire day',
285
+                $occurring['interval'],
286
+                [$startDate]
287
+            ),
288
+            ['past', true] => $this->l10n->t(
289
+                'In the past on %1$s between %2$s - %3$s',
290
+                [$startDate, $startTime, $endTime]
291
+            ),
292
+            ['minute', true] => $this->l10n->n(
293
+                'In %n minute on %1$s between %2$s - %3$s',
294
+                'In %n minutes on %1$s between %2$s - %3$s',
295
+                $occurring['interval'],
296
+                [$startDate, $startTime, $endTime]
297
+            ),
298
+            ['hour', true] => $this->l10n->n(
299
+                'In %n hour on %1$s between %2$s - %3$s',
300
+                'In %n hours on %1$s between %2$s - %3$s',
301
+                $occurring['interval'],
302
+                [$startDate, $startTime, $endTime]
303
+            ),
304
+            ['day', true] => $this->l10n->n(
305
+                'In %n day on %1$s between %2$s - %3$s',
306
+                'In %n days on %1$s between %2$s - %3$s',
307
+                $occurring['interval'],
308
+                [$startDate, $startTime, $endTime]
309
+            ),
310
+            ['week', true] => $this->l10n->n(
311
+                'In %n week on %1$s between %2$s - %3$s',
312
+                'In %n weeks on %1$s between %2$s - %3$s',
313
+                $occurring['interval'],
314
+                [$startDate, $startTime, $endTime]
315
+            ),
316
+            ['month', true] => $this->l10n->n(
317
+                'In %n month on %1$s between %2$s - %3$s',
318
+                'In %n months on %1$s between %2$s - %3$s',
319
+                $occurring['interval'],
320
+                [$startDate, $startTime, $endTime]
321
+            ),
322
+            ['year', true] => $this->l10n->n(
323
+                'In %n year on %1$s between %2$s - %3$s',
324
+                'In %n years on %1$s between %2$s - %3$s',
325
+                $occurring['interval'],
326
+                [$startDate, $startTime, $endTime]
327
+            ),
328
+            default => $this->l10n->t('Could not generate when statement')
329
+        };
330
+    }
331
+
332
+    /**
333
+     * generates a when string based on recurrence precision/frequency
334
+     *
335
+     * @since 30.0.0
336
+     *
337
+     * @param EventReader $er
338
+     *
339
+     * @return string
340
+     */
341
+    public function generateWhenStringRecurring(EventReader $er): string {
342
+        return match ($er->recurringPrecision()) {
343
+            'daily' => $this->generateWhenStringRecurringDaily($er),
344
+            'weekly' => $this->generateWhenStringRecurringWeekly($er),
345
+            'monthly' => $this->generateWhenStringRecurringMonthly($er),
346
+            'yearly' => $this->generateWhenStringRecurringYearly($er),
347
+            'fixed' => $this->generateWhenStringRecurringFixed($er),
348
+        };
349
+    }
350
+
351
+    /**
352
+     * generates a when string for a daily precision/frequency
353
+     *
354
+     * @since 30.0.0
355
+     *
356
+     * @param EventReader $er
357
+     *
358
+     * @return string
359
+     */
360
+    public function generateWhenStringRecurringDaily(EventReader $er): string {
361
+
362
+        // initialize
363
+        $interval = (int)$er->recurringInterval();
364
+        $startTime = null;
365
+        $conclusion = null;
366
+        // time of the day
367
+        if (!$er->entireDay()) {
368
+            $startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
369
+            $startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
370
+            $endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
371
+        }
372
+        // conclusion
373
+        if ($er->recurringConcludes()) {
374
+            $conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
375
+        }
376
+        // generate localized when string
377
+        // TRANSLATORS
378
+        // Indicates when a calendar event will happen, shown on invitation emails
379
+        // Output produced in order:
380
+        // Every Day for the entire day
381
+        // Every Day for the entire day until July 13, 2024
382
+        // Every Day between 8:00 AM - 9:00 AM (America/Toronto)
383
+        // Every Day between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024
384
+        // Every 3 Days for the entire day
385
+        // Every 3 Days for the entire day until July 13, 2024
386
+        // Every 3 Days between 8:00 AM - 9:00 AM (America/Toronto)
387
+        // Every 3 Days between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024
388
+        return match ([($interval > 1), $startTime !== null, $conclusion !== null]) {
389
+            [false, false, false] => $this->l10n->t('Every Day for the entire day'),
390
+            [false, false, true] => $this->l10n->t('Every Day for the entire day until %1$s', [$conclusion]),
391
+            [false, true, false] => $this->l10n->t('Every Day between %1$s - %2$s', [$startTime, $endTime]),
392
+            [false, true, true] => $this->l10n->t('Every Day between %1$s - %2$s until %3$s', [$startTime, $endTime, $conclusion]),
393
+            [true, false, false] => $this->l10n->t('Every %1$d Days for the entire day', [$interval]),
394
+            [true, false, true] => $this->l10n->t('Every %1$d Days for the entire day until %2$s', [$interval, $conclusion]),
395
+            [true, true, false] => $this->l10n->t('Every %1$d Days between %2$s - %3$s', [$interval, $startTime, $endTime]),
396
+            [true, true, true] => $this->l10n->t('Every %1$d Days between %2$s - %3$s until %4$s', [$interval, $startTime, $endTime, $conclusion]),
397
+            default => $this->l10n->t('Could not generate event recurrence statement')
398
+        };
399
+
400
+    }
401
+
402
+    /**
403
+     * generates a when string for a weekly precision/frequency
404
+     *
405
+     * @since 30.0.0
406
+     *
407
+     * @param EventReader $er
408
+     *
409
+     * @return string
410
+     */
411
+    public function generateWhenStringRecurringWeekly(EventReader $er): string {
412
+
413
+        // initialize
414
+        $interval = (int)$er->recurringInterval();
415
+        $startTime = null;
416
+        $conclusion = null;
417
+        // days of the week
418
+        $days = implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed()));
419
+        // time of the day
420
+        if (!$er->entireDay()) {
421
+            $startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
422
+            $startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
423
+            $endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
424
+        }
425
+        // conclusion
426
+        if ($er->recurringConcludes()) {
427
+            $conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
428
+        }
429
+        // generate localized when string
430
+        // TRANSLATORS
431
+        // Indicates when a calendar event will happen, shown on invitation emails
432
+        // Output produced in order:
433
+        // Every Week on Monday, Wednesday, Friday for the entire day
434
+        // Every Week on Monday, Wednesday, Friday for the entire day until July 13, 2024
435
+        // Every Week on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto)
436
+        // Every Week on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024
437
+        // Every 2 Weeks on Monday, Wednesday, Friday for the entire day
438
+        // Every 2 Weeks on Monday, Wednesday, Friday for the entire day until July 13, 2024
439
+        // Every 2 Weeks on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto)
440
+        // Every 2 Weeks on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024
441
+        return match ([($interval > 1), $startTime !== null, $conclusion !== null]) {
442
+            [false, false, false] => $this->l10n->t('Every Week on %1$s for the entire day', [$days]),
443
+            [false, false, true] => $this->l10n->t('Every Week on %1$s for the entire day until %2$s', [$days, $conclusion]),
444
+            [false, true, false] => $this->l10n->t('Every Week on %1$s between %2$s - %3$s', [$days, $startTime, $endTime]),
445
+            [false, true, true] => $this->l10n->t('Every Week on %1$s between %2$s - %3$s until %4$s', [$days, $startTime, $endTime, $conclusion]),
446
+            [true, false, false] => $this->l10n->t('Every %1$d Weeks on %2$s for the entire day', [$interval, $days]),
447
+            [true, false, true] => $this->l10n->t('Every %1$d Weeks on %2$s for the entire day until %3$s', [$interval, $days, $conclusion]),
448
+            [true, true, false] => $this->l10n->t('Every %1$d Weeks on %2$s between %3$s - %4$s', [$interval, $days, $startTime, $endTime]),
449
+            [true, true, true] => $this->l10n->t('Every %1$d Weeks on %2$s between %3$s - %4$s until %5$s', [$interval, $days, $startTime, $endTime, $conclusion]),
450
+            default => $this->l10n->t('Could not generate event recurrence statement')
451
+        };
452
+
453
+    }
454
+
455
+    /**
456
+     * generates a when string for a monthly precision/frequency
457
+     *
458
+     * @since 30.0.0
459
+     *
460
+     * @param EventReader $er
461
+     *
462
+     * @return string
463
+     */
464
+    public function generateWhenStringRecurringMonthly(EventReader $er): string {
465
+
466
+        // initialize
467
+        $interval = (int)$er->recurringInterval();
468
+        $startTime = null;
469
+        $conclusion = null;
470
+        // days of month
471
+        if ($er->recurringPattern() === 'R') {
472
+            $days = implode(', ', array_map(function ($value) { return $this->localizeRelativePositionName($value); }, $er->recurringRelativePositionNamed())) . ' '
473
+                    . implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed()));
474
+        } else {
475
+            $days = implode(', ', $er->recurringDaysOfMonth());
476
+        }
477
+        // time of the day
478
+        if (!$er->entireDay()) {
479
+            $startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
480
+            $startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
481
+            $endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
482
+        }
483
+        // conclusion
484
+        if ($er->recurringConcludes()) {
485
+            $conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
486
+        }
487
+        // generate localized when string
488
+        // TRANSLATORS
489
+        // Indicates when a calendar event will happen, shown on invitation emails
490
+        // Output produced in order, output varies depending on if the event is absolute or releative:
491
+        // Absolute: Every Month on the 1, 8 for the entire day
492
+        // Relative: Every Month on the First Sunday, Saturday for the entire day
493
+        // Absolute: Every Month on the 1, 8 for the entire day until December 31, 2024
494
+        // Relative: Every Month on the First Sunday, Saturday for the entire day until December 31, 2024
495
+        // Absolute: Every Month on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto)
496
+        // Relative: Every Month on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)
497
+        // Absolute: Every Month on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024
498
+        // Relative: Every Month on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024
499
+        // Absolute: Every 2 Months on the 1, 8 for the entire day
500
+        // Relative: Every 2 Months on the First Sunday, Saturday for the entire day
501
+        // Absolute: Every 2 Months on the 1, 8 for the entire day until December 31, 2024
502
+        // Relative: Every 2 Months on the First Sunday, Saturday for the entire day until December 31, 2024
503
+        // Absolute: Every 2 Months on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto)
504
+        // Relative: Every 2 Months on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)
505
+        // Absolute: Every 2 Months on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024
506
+        // Relative: Every 2 Months on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024
507
+        return match ([($interval > 1), $startTime !== null, $conclusion !== null]) {
508
+            [false, false, false] => $this->l10n->t('Every Month on the %1$s for the entire day', [$days]),
509
+            [false, false, true] => $this->l10n->t('Every Month on the %1$s for the entire day until %2$s', [$days, $conclusion]),
510
+            [false, true, false] => $this->l10n->t('Every Month on the %1$s between %2$s - %3$s', [$days, $startTime, $endTime]),
511
+            [false, true, true] => $this->l10n->t('Every Month on the %1$s between %2$s - %3$s until %4$s', [$days, $startTime, $endTime, $conclusion]),
512
+            [true, false, false] => $this->l10n->t('Every %1$d Months on the %2$s for the entire day', [$interval, $days]),
513
+            [true, false, true] => $this->l10n->t('Every %1$d Months on the %2$s for the entire day until %3$s', [$interval, $days, $conclusion]),
514
+            [true, true, false] => $this->l10n->t('Every %1$d Months on the %2$s between %3$s - %4$s', [$interval, $days, $startTime, $endTime]),
515
+            [true, true, true] => $this->l10n->t('Every %1$d Months on the %2$s between %3$s - %4$s until %5$s', [$interval, $days, $startTime, $endTime, $conclusion]),
516
+            default => $this->l10n->t('Could not generate event recurrence statement')
517
+        };
518
+    }
519
+
520
+    /**
521
+     * generates a when string for a yearly precision/frequency
522
+     *
523
+     * @since 30.0.0
524
+     *
525
+     * @param EventReader $er
526
+     *
527
+     * @return string
528
+     */
529
+    public function generateWhenStringRecurringYearly(EventReader $er): string {
530
+
531
+        // initialize
532
+        $interval = (int)$er->recurringInterval();
533
+        $startTime = null;
534
+        $conclusion = null;
535
+        // months of year
536
+        $months = implode(', ', array_map(function ($value) { return $this->localizeMonthName($value); }, $er->recurringMonthsOfYearNamed()));
537
+        // days of month
538
+        if ($er->recurringPattern() === 'R') {
539
+            $days = implode(', ', array_map(function ($value) { return $this->localizeRelativePositionName($value); }, $er->recurringRelativePositionNamed())) . ' '
540
+                    . implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed()));
541
+        } else {
542
+            $days = $er->startDateTime()->format('jS');
543
+        }
544
+        // time of the day
545
+        if (!$er->entireDay()) {
546
+            $startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
547
+            $startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
548
+            $endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
549
+        }
550
+        // conclusion
551
+        if ($er->recurringConcludes()) {
552
+            $conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
553
+        }
554
+        // generate localized when string
555
+        // TRANSLATORS
556
+        // Indicates when a calendar event will happen, shown on invitation emails
557
+        // Output produced in order, output varies depending on if the event is absolute or releative:
558
+        // Absolute: Every Year in July on the 1st for the entire day
559
+        // Relative: Every Year in July on the First Sunday, Saturday for the entire day
560
+        // Absolute: Every Year in July on the 1st for the entire day until July 31, 2026
561
+        // Relative: Every Year in July on the First Sunday, Saturday for the entire day until July 31, 2026
562
+        // Absolute: Every Year in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto)
563
+        // Relative: Every Year in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)
564
+        // Absolute: Every Year in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026
565
+        // Relative: Every Year in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026
566
+        // Absolute: Every 2 Years in July on the 1st for the entire day
567
+        // Relative: Every 2 Years in July on the First Sunday, Saturday for the entire day
568
+        // Absolute: Every 2 Years in July on the 1st for the entire day until July 31, 2026
569
+        // Relative: Every 2 Years in July on the First Sunday, Saturday for the entire day until July 31, 2026
570
+        // Absolute: Every 2 Years in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto)
571
+        // Relative: Every 2 Years in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)
572
+        // Absolute: Every 2 Years in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026
573
+        // Relative: Every 2 Years in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026
574
+        return match ([($interval > 1), $startTime !== null, $conclusion !== null]) {
575
+            [false, false, false] => $this->l10n->t('Every Year in %1$s on the %2$s for the entire day', [$months, $days]),
576
+            [false, false, true] => $this->l10n->t('Every Year in %1$s on the %2$s for the entire day until %3$s', [$months, $days, $conclusion]),
577
+            [false, true, false] => $this->l10n->t('Every Year in %1$s on the %2$s between %3$s - %4$s', [$months, $days, $startTime, $endTime]),
578
+            [false, true, true] => $this->l10n->t('Every Year in %1$s on the %2$s between %3$s - %4$s until %5$s', [$months, $days, $startTime, $endTime, $conclusion]),
579
+            [true, false, false] => $this->l10n->t('Every %1$d Years in %2$s on the %3$s for the entire day', [$interval, $months, $days]),
580
+            [true, false, true] => $this->l10n->t('Every %1$d Years in %2$s on the %3$s for the entire day until %4$s', [$interval, $months,  $days, $conclusion]),
581
+            [true, true, false] => $this->l10n->t('Every %1$d Years in %2$s on the %3$s between %4$s - %5$s', [$interval, $months, $days, $startTime, $endTime]),
582
+            [true, true, true] => $this->l10n->t('Every %1$d Years in %2$s on the %3$s between %4$s - %5$s until %6$s', [$interval, $months, $days, $startTime, $endTime, $conclusion]),
583
+            default => $this->l10n->t('Could not generate event recurrence statement')
584
+        };
585
+    }
586
+
587
+    /**
588
+     * generates a when string for a fixed precision/frequency
589
+     *
590
+     * @since 30.0.0
591
+     *
592
+     * @param EventReader $er
593
+     *
594
+     * @return string
595
+     */
596
+    public function generateWhenStringRecurringFixed(EventReader $er): string {
597
+        // initialize
598
+        $startTime = null;
599
+        $conclusion = null;
600
+        // time of the day
601
+        if (!$er->entireDay()) {
602
+            $startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
603
+            $startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
604
+            $endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
605
+        }
606
+        // conclusion
607
+        $conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
608
+        // generate localized when string
609
+        // TRANSLATORS
610
+        // Indicates when a calendar event will happen, shown on invitation emails
611
+        // Output produced in order:
612
+        // On specific dates for the entire day until July 13, 2024
613
+        // On specific dates between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024
614
+        return match ($startTime !== null) {
615
+            false => $this->l10n->t('On specific dates for the entire day until %1$s', [$conclusion]),
616
+            true => $this->l10n->t('On specific dates between %1$s - %2$s until %3$s', [$startTime, $endTime, $conclusion]),
617
+        };
618
+    }
619
+
620
+    /**
621
+     * generates a occurring next string for a recurring event
622
+     *
623
+     * @since 30.0.0
624
+     *
625
+     * @param EventReader $er
626
+     *
627
+     * @return string
628
+     */
629
+    public function generateOccurringString(EventReader $er): string {
630
+
631
+        // initialize
632
+        $occurrence = null;
633
+        $occurrence2 = null;
634
+        $occurrence3 = null;
635
+        // reset to initial occurrence
636
+        $er->recurrenceRewind();
637
+        // forward to current date
638
+        $er->recurrenceAdvanceTo($this->timeFactory->getDateTime());
639
+        // calculate time difference from now to start of next event occurrence and minimize it
640
+        $occurrenceIn = $this->minimizeInterval($this->timeFactory->getDateTime()->diff($er->recurrenceDate()));
641
+        // store next occurrence value
642
+        $occurrence = $this->l10n->l('date', $er->recurrenceDate(), ['width' => 'long']);
643
+        // forward one occurrence
644
+        $er->recurrenceAdvance();
645
+        // evaluate if occurrence is valid
646
+        if ($er->recurrenceDate() !== null) {
647
+            // store following occurrence value
648
+            $occurrence2 = $this->l10n->l('date', $er->recurrenceDate(), ['width' => 'long']);
649
+            // forward one occurrence
650
+            $er->recurrenceAdvance();
651
+            // evaluate if occurrence is valid
652
+            if ($er->recurrenceDate()) {
653
+                // store following occurrence value
654
+                $occurrence3 = $this->l10n->l('date', $er->recurrenceDate(), ['width' => 'long']);
655
+            }
656
+        }
657
+        // generate localized when string
658
+        // TRANSLATORS
659
+        // Indicates when a calendar event will happen, shown on invitation emails
660
+        // Output produced in order:
661
+        // In 1 minute/hour/day/week/month/year on July 1, 2024
662
+        // In 1 minute/hour/day/week/month/year on July 1, 2024 then on July 3, 2024
663
+        // In 1 minute/hour/day/week/month/year on July 1, 2024 then on July 3, 2024 and July 5, 2024
664
+        // In 2 minutes/hours/days/weeks/months/years on July 1, 2024
665
+        // In 2 minutes/hours/days/weeks/months/years on July 1, 2024 then on July 3, 2024
666
+        // In 2 minutes/hours/days/weeks/months/years on July 1, 2024 then on July 3, 2024 and July 5, 2024
667
+        return match ([$occurrenceIn['scale'], $occurrence2 !== null, $occurrence3 !== null]) {
668
+            ['past', false, false] => $this->l10n->t(
669
+                'In the past on %1$s',
670
+                [$occurrence]
671
+            ),
672
+            ['minute', false, false] => $this->l10n->n(
673
+                'In %n minute on %1$s',
674
+                'In %n minutes on %1$s',
675
+                $occurrenceIn['interval'],
676
+                [$occurrence]
677
+            ),
678
+            ['hour', false, false] => $this->l10n->n(
679
+                'In %n hour on %1$s',
680
+                'In %n hours on %1$s',
681
+                $occurrenceIn['interval'],
682
+                [$occurrence]
683
+            ),
684
+            ['day', false, false] => $this->l10n->n(
685
+                'In %n day on %1$s',
686
+                'In %n days on %1$s',
687
+                $occurrenceIn['interval'],
688
+                [$occurrence]
689
+            ),
690
+            ['week', false, false] => $this->l10n->n(
691
+                'In %n week on %1$s',
692
+                'In %n weeks on %1$s',
693
+                $occurrenceIn['interval'],
694
+                [$occurrence]
695
+            ),
696
+            ['month', false, false] => $this->l10n->n(
697
+                'In %n month on %1$s',
698
+                'In %n months on %1$s',
699
+                $occurrenceIn['interval'],
700
+                [$occurrence]
701
+            ),
702
+            ['year', false, false] => $this->l10n->n(
703
+                'In %n year on %1$s',
704
+                'In %n years on %1$s',
705
+                $occurrenceIn['interval'],
706
+                [$occurrence]
707
+            ),
708
+            ['past', true, false] => $this->l10n->t(
709
+                'In the past on %1$s then on %2$s',
710
+                [$occurrence, $occurrence2]
711
+            ),
712
+            ['minute', true, false] => $this->l10n->n(
713
+                'In %n minute on %1$s then on %2$s',
714
+                'In %n minutes on %1$s then on %2$s',
715
+                $occurrenceIn['interval'],
716
+                [$occurrence, $occurrence2]
717
+            ),
718
+            ['hour', true, false] => $this->l10n->n(
719
+                'In %n hour on %1$s then on %2$s',
720
+                'In %n hours on %1$s then on %2$s',
721
+                $occurrenceIn['interval'],
722
+                [$occurrence, $occurrence2]
723
+            ),
724
+            ['day', true, false] => $this->l10n->n(
725
+                'In %n day on %1$s then on %2$s',
726
+                'In %n days on %1$s then on %2$s',
727
+                $occurrenceIn['interval'],
728
+                [$occurrence, $occurrence2]
729
+            ),
730
+            ['week', true, false] => $this->l10n->n(
731
+                'In %n week on %1$s then on %2$s',
732
+                'In %n weeks on %1$s then on %2$s',
733
+                $occurrenceIn['interval'],
734
+                [$occurrence, $occurrence2]
735
+            ),
736
+            ['month', true, false] => $this->l10n->n(
737
+                'In %n month on %1$s then on %2$s',
738
+                'In %n months on %1$s then on %2$s',
739
+                $occurrenceIn['interval'],
740
+                [$occurrence, $occurrence2]
741
+            ),
742
+            ['year', true, false] => $this->l10n->n(
743
+                'In %n year on %1$s then on %2$s',
744
+                'In %n years on %1$s then on %2$s',
745
+                $occurrenceIn['interval'],
746
+                [$occurrence, $occurrence2]
747
+            ),
748
+            ['past', true, true] => $this->l10n->t(
749
+                'In the past on %1$s then on %2$s and %3$s',
750
+                [$occurrence, $occurrence2, $occurrence3]
751
+            ),
752
+            ['minute', true, true] => $this->l10n->n(
753
+                'In %n minute on %1$s then on %2$s and %3$s',
754
+                'In %n minutes on %1$s then on %2$s and %3$s',
755
+                $occurrenceIn['interval'],
756
+                [$occurrence, $occurrence2, $occurrence3]
757
+            ),
758
+            ['hour', true, true] => $this->l10n->n(
759
+                'In %n hour on %1$s then on %2$s and %3$s',
760
+                'In %n hours on %1$s then on %2$s and %3$s',
761
+                $occurrenceIn['interval'],
762
+                [$occurrence, $occurrence2, $occurrence3]
763
+            ),
764
+            ['day', true, true] => $this->l10n->n(
765
+                'In %n day on %1$s then on %2$s and %3$s',
766
+                'In %n days on %1$s then on %2$s and %3$s',
767
+                $occurrenceIn['interval'],
768
+                [$occurrence, $occurrence2, $occurrence3]
769
+            ),
770
+            ['week', true, true] => $this->l10n->n(
771
+                'In %n week on %1$s then on %2$s and %3$s',
772
+                'In %n weeks on %1$s then on %2$s and %3$s',
773
+                $occurrenceIn['interval'],
774
+                [$occurrence, $occurrence2, $occurrence3]
775
+            ),
776
+            ['month', true, true] => $this->l10n->n(
777
+                'In %n month on %1$s then on %2$s and %3$s',
778
+                'In %n months on %1$s then on %2$s and %3$s',
779
+                $occurrenceIn['interval'],
780
+                [$occurrence, $occurrence2, $occurrence3]
781
+            ),
782
+            ['year', true, true] => $this->l10n->n(
783
+                'In %n year on %1$s then on %2$s and %3$s',
784
+                'In %n years on %1$s then on %2$s and %3$s',
785
+                $occurrenceIn['interval'],
786
+                [$occurrence, $occurrence2, $occurrence3]
787
+            ),
788
+            default => $this->l10n->t('Could not generate next recurrence statement')
789
+        };
790
+
791
+    }
792
+
793
+    /**
794
+     * @param VEvent $vEvent
795
+     * @return array
796
+     */
797
+    public function buildCancelledBodyData(VEvent $vEvent): array {
798
+        // construct event reader
799
+        $eventReaderCurrent = new EventReader($vEvent);
800
+        $defaultVal = '';
801
+        $strikethrough = "<span style='text-decoration: line-through'>%s</span>";
802
+
803
+        $newMeetingWhen = $this->generateWhenString($eventReaderCurrent);
804
+        $newSummary = htmlspecialchars(isset($vEvent->SUMMARY) && (string)$vEvent->SUMMARY !== '' ? (string)$vEvent->SUMMARY : $this->l10n->t('Untitled event'));
805
+        $newDescription = htmlspecialchars(isset($vEvent->DESCRIPTION) && (string)$vEvent->DESCRIPTION !== '' ? (string)$vEvent->DESCRIPTION : $defaultVal);
806
+        $newUrl = isset($vEvent->URL) && (string)$vEvent->URL !== '' ? sprintf('<a href="%1$s">%1$s</a>', $vEvent->URL) : $defaultVal;
807
+        $newLocation = htmlspecialchars(isset($vEvent->LOCATION) && (string)$vEvent->LOCATION !== '' ? (string)$vEvent->LOCATION : $defaultVal);
808
+        $newLocationHtml = $this->linkify($newLocation) ?? $newLocation;
809
+
810
+        $data = [];
811
+        $data['meeting_when_html'] = $newMeetingWhen === '' ?: sprintf($strikethrough, $newMeetingWhen);
812
+        $data['meeting_when'] = $newMeetingWhen;
813
+        $data['meeting_title_html'] = sprintf($strikethrough, $newSummary);
814
+        $data['meeting_title'] = $newSummary !== '' ? $newSummary: $this->l10n->t('Untitled event');
815
+        $data['meeting_description_html'] = $newDescription !== '' ? sprintf($strikethrough, $newDescription) : '';
816
+        $data['meeting_description'] = $newDescription;
817
+        $data['meeting_url_html'] = $newUrl !== '' ? sprintf($strikethrough, $newUrl) : '';
818
+        $data['meeting_url'] = isset($vEvent->URL) ? (string)$vEvent->URL : '';
819
+        $data['meeting_location_html'] = $newLocationHtml !== '' ? sprintf($strikethrough, $newLocationHtml) : '';
820
+        $data['meeting_location'] = $newLocation;
821
+        return $data;
822
+    }
823
+
824
+    /**
825
+     * Check if event took place in the past
826
+     *
827
+     * @param VCalendar $vObject
828
+     * @return int
829
+     */
830
+    public function getLastOccurrence(VCalendar $vObject) {
831
+        /** @var VEvent $component */
832
+        $component = $vObject->VEVENT;
833
+
834
+        if (isset($component->RRULE)) {
835
+            $it = new EventIterator($vObject, (string)$component->UID);
836
+            $maxDate = new \DateTime(IMipPlugin::MAX_DATE);
837
+            if ($it->isInfinite()) {
838
+                return $maxDate->getTimestamp();
839
+            }
840
+
841
+            $end = $it->getDtEnd();
842
+            while ($it->valid() && $end < $maxDate) {
843
+                $end = $it->getDtEnd();
844
+                $it->next();
845
+            }
846
+            return $end->getTimestamp();
847
+        }
848
+
849
+        /** @var Property\ICalendar\DateTime $dtStart */
850
+        $dtStart = $component->DTSTART;
851
+
852
+        if (isset($component->DTEND)) {
853
+            /** @var Property\ICalendar\DateTime $dtEnd */
854
+            $dtEnd = $component->DTEND;
855
+            return $dtEnd->getDateTime()->getTimeStamp();
856
+        }
857
+
858
+        if (isset($component->DURATION)) {
859
+            /** @var \DateTime $endDate */
860
+            $endDate = clone $dtStart->getDateTime();
861
+            // $component->DTEND->getDateTime() returns DateTimeImmutable
862
+            $endDate = $endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
863
+            return $endDate->getTimestamp();
864
+        }
865
+
866
+        if (!$dtStart->hasTime()) {
867
+            /** @var \DateTime $endDate */
868
+            // $component->DTSTART->getDateTime() returns DateTimeImmutable
869
+            $endDate = clone $dtStart->getDateTime();
870
+            $endDate = $endDate->modify('+1 day');
871
+            return $endDate->getTimestamp();
872
+        }
873
+
874
+        // No computation of end time possible - return start date
875
+        return $dtStart->getDateTime()->getTimeStamp();
876
+    }
877
+
878
+    /**
879
+     * @param Property $attendee
880
+     */
881
+    public function setL10nFromAttendee(Property $attendee) {
882
+        $language = null;
883
+        $locale = null;
884
+        // check if the attendee is a system user
885
+        $userAddress = $attendee->getValue();
886
+        if (str_starts_with($userAddress, 'mailto:')) {
887
+            $userAddress = substr($userAddress, 7);
888
+        }
889
+        $users = $this->userManager->getByEmail($userAddress);
890
+        if ($users !== []) {
891
+            $user = array_shift($users);
892
+            $language = $this->userConfig->getValueString($user->getUID(), 'core', 'lang', '') ?: null;
893
+            $locale = $this->userConfig->getValueString($user->getUID(), 'core', 'locale', '') ?: null;
894
+        }
895
+        // fallback to attendee LANGUAGE parameter if language not set
896
+        if ($language === null && isset($attendee['LANGUAGE']) && $attendee['LANGUAGE'] instanceof Parameter) {
897
+            $language = $attendee['LANGUAGE']->getValue();
898
+        }
899
+        // fallback to system language if language not set
900
+        if ($language === null) {
901
+            $language = $this->l10nFactory->findGenericLanguage();
902
+        }
903
+        // fallback to system locale if locale not set
904
+        if ($locale === null) {
905
+            $locale = $this->l10nFactory->findLocale($language);
906
+        }
907
+        $this->l10n = $this->l10nFactory->get('dav', $language, $locale);
908
+    }
909
+
910
+    /**
911
+     * @param Property|null $attendee
912
+     * @return bool
913
+     */
914
+    public function getAttendeeRsvpOrReqForParticipant(?Property $attendee = null) {
915
+        if ($attendee === null) {
916
+            return false;
917
+        }
918
+
919
+        $rsvp = $attendee->offsetGet('RSVP');
920
+        if (($rsvp instanceof Parameter) && (strcasecmp($rsvp->getValue(), 'TRUE') === 0)) {
921
+            return true;
922
+        }
923
+        $role = $attendee->offsetGet('ROLE');
924
+        // @see https://datatracker.ietf.org/doc/html/rfc5545#section-3.2.16
925
+        // Attendees without a role are assumed required and should receive an invitation link even if they have no RSVP set
926
+        if ($role === null
927
+            || (($role instanceof Parameter) && (strcasecmp($role->getValue(), 'REQ-PARTICIPANT') === 0))
928
+            || (($role instanceof Parameter) && (strcasecmp($role->getValue(), 'OPT-PARTICIPANT') === 0))
929
+        ) {
930
+            return true;
931
+        }
932
+
933
+        // RFC 5545 3.2.17: default RSVP is false
934
+        return false;
935
+    }
936
+
937
+    /**
938
+     * @param IEMailTemplate $template
939
+     * @param string $method
940
+     * @param string $sender
941
+     * @param string $summary
942
+     * @param string|null $partstat
943
+     * @param bool $isModified
944
+     */
945
+    public function addSubjectAndHeading(IEMailTemplate $template,
946
+        string $method, string $sender, string $summary, bool $isModified, ?Property $replyingAttendee = null): void {
947
+        if ($method === IMipPlugin::METHOD_CANCEL) {
948
+            // TRANSLATORS Subject for email, when an invitation is cancelled. Ex: "Cancelled: {{Event Name}}"
949
+            $template->setSubject($this->l10n->t('Cancelled: %1$s', [$summary]));
950
+            $template->addHeading($this->l10n->t('"%1$s" has been canceled', [$summary]));
951
+        } elseif ($method === IMipPlugin::METHOD_REPLY) {
952
+            // TRANSLATORS Subject for email, when an invitation is replied to. Ex: "Re: {{Event Name}}"
953
+            $template->setSubject($this->l10n->t('Re: %1$s', [$summary]));
954
+            // Build the strings
955
+            $partstat = (isset($replyingAttendee)) ? $replyingAttendee->offsetGet('PARTSTAT') : null;
956
+            $partstat = ($partstat instanceof Parameter) ? $partstat->getValue() : null;
957
+            switch ($partstat) {
958
+                case 'ACCEPTED':
959
+                    $template->addHeading($this->l10n->t('%1$s has accepted your invitation', [$sender]));
960
+                    break;
961
+                case 'TENTATIVE':
962
+                    $template->addHeading($this->l10n->t('%1$s has tentatively accepted your invitation', [$sender]));
963
+                    break;
964
+                case 'DECLINED':
965
+                    $template->addHeading($this->l10n->t('%1$s has declined your invitation', [$sender]));
966
+                    break;
967
+                case null:
968
+                default:
969
+                    $template->addHeading($this->l10n->t('%1$s has responded to your invitation', [$sender]));
970
+                    break;
971
+            }
972
+        } elseif ($method === IMipPlugin::METHOD_REQUEST && $isModified) {
973
+            // TRANSLATORS Subject for email, when an invitation is updated. Ex: "Invitation updated: {{Event Name}}"
974
+            $template->setSubject($this->l10n->t('Invitation updated: %1$s', [$summary]));
975
+            $template->addHeading($this->l10n->t('%1$s updated the event "%2$s"', [$sender, $summary]));
976
+        } else {
977
+            // TRANSLATORS Subject for email, when an invitation is sent. Ex: "Invitation: {{Event Name}}"
978
+            $template->setSubject($this->l10n->t('Invitation: %1$s', [$summary]));
979
+            $template->addHeading($this->l10n->t('%1$s would like to invite you to "%2$s"', [$sender, $summary]));
980
+        }
981
+    }
982
+
983
+    /**
984
+     * @param string $path
985
+     * @return string
986
+     */
987
+    public function getAbsoluteImagePath($path): string {
988
+        return $this->urlGenerator->getAbsoluteURL(
989
+            $this->urlGenerator->imagePath('core', $path)
990
+        );
991
+    }
992
+
993
+    /**
994
+     * addAttendees: add organizer and attendee names/emails to iMip mail.
995
+     *
996
+     * Enable with DAV setting: invitation_list_attendees (default: no)
997
+     *
998
+     * The default is 'no', which matches old behavior, and is privacy preserving.
999
+     *
1000
+     * To enable including attendees in invitation emails:
1001
+     *   % php occ config:app:set dav invitation_list_attendees --value yes --type bool
1002
+     *
1003
+     * @param IEMailTemplate $template
1004
+     * @param IL10N $this->l10n
1005
+     * @param VEvent $vevent
1006
+     * @author brad2014 on github.com
1007
+     */
1008
+    public function addAttendees(IEMailTemplate $template, VEvent $vevent) {
1009
+        if (!$this->appConfig->getValueBool('dav', 'invitation_list_attendees')) {
1010
+            return;
1011
+        }
1012
+
1013
+        if (isset($vevent->ORGANIZER)) {
1014
+            /** @var Property&Property\ICalendar\CalAddress $organizer */
1015
+            $organizer = $vevent->ORGANIZER;
1016
+            $organizerEmail = substr($organizer->getNormalizedValue(), 7);
1017
+            /** @var string|null $organizerName */
1018
+            $organizerName = isset($organizer->CN) ? $organizer->CN->getValue() : null;
1019
+            $organizerHTML = sprintf('<a href="%s">%s</a>',
1020
+                htmlspecialchars($organizer->getNormalizedValue()),
1021
+                htmlspecialchars($organizerName ?: $organizerEmail));
1022
+            $organizerText = sprintf('%s <%s>', $organizerName, $organizerEmail);
1023
+            if (isset($organizer['PARTSTAT'])) {
1024
+                /** @var Parameter $partstat */
1025
+                $partstat = $organizer['PARTSTAT'];
1026
+                if (strcasecmp($partstat->getValue(), 'ACCEPTED') === 0) {
1027
+                    $organizerHTML .= ' ✔︎';
1028
+                    $organizerText .= ' ✔︎';
1029
+                }
1030
+            }
1031
+            $template->addBodyListItem($organizerHTML, $this->l10n->t('Organizer:'),
1032
+                $this->getAbsoluteImagePath('caldav/organizer.png'),
1033
+                $organizerText, '', IMipPlugin::IMIP_INDENT);
1034
+        }
1035
+
1036
+        $attendees = $vevent->select('ATTENDEE');
1037
+        if (count($attendees) === 0) {
1038
+            return;
1039
+        }
1040
+
1041
+        $attendeesHTML = [];
1042
+        $attendeesText = [];
1043
+        foreach ($attendees as $attendee) {
1044
+            /** @var Property&Property\ICalendar\CalAddress $attendee */
1045
+            $attendeeEmail = substr($attendee->getNormalizedValue(), 7);
1046
+            $attendeeName = null;
1047
+            if (isset($attendee['CN'])) {
1048
+                /** @var Parameter $cn */
1049
+                $cn = $attendee['CN'];
1050
+                $attendeeName = $cn->getValue();
1051
+            }
1052
+            $attendeeHTML = sprintf('<a href="%s">%s</a>',
1053
+                htmlspecialchars($attendee->getNormalizedValue()),
1054
+                htmlspecialchars($attendeeName ?: $attendeeEmail));
1055
+            $attendeeText = sprintf('%s <%s>', $attendeeName, $attendeeEmail);
1056
+            if (isset($attendee['PARTSTAT'])) {
1057
+                /** @var Parameter $partstat */
1058
+                $partstat = $attendee['PARTSTAT'];
1059
+                if (strcasecmp($partstat->getValue(), 'ACCEPTED') === 0) {
1060
+                    $attendeeHTML .= ' ✔︎';
1061
+                    $attendeeText .= ' ✔︎';
1062
+                }
1063
+            }
1064
+            $attendeesHTML[] = $attendeeHTML;
1065
+            $attendeesText[] = $attendeeText;
1066
+        }
1067
+
1068
+        $template->addBodyListItem(implode('<br/>', $attendeesHTML), $this->l10n->t('Attendees:'),
1069
+            $this->getAbsoluteImagePath('caldav/attendees.png'),
1070
+            implode("\n", $attendeesText), '', IMipPlugin::IMIP_INDENT);
1071
+    }
1072
+
1073
+    /**
1074
+     * @param IEMailTemplate $template
1075
+     * @param VEVENT $vevent
1076
+     * @param $data
1077
+     */
1078
+    public function addBulletList(IEMailTemplate $template, VEvent $vevent, $data) {
1079
+        $template->addBodyListItem(
1080
+            $data['meeting_title_html'] ?? htmlspecialchars($data['meeting_title']), $this->l10n->t('Title:'),
1081
+            $this->getAbsoluteImagePath('caldav/title.png'), $data['meeting_title'], '', IMipPlugin::IMIP_INDENT);
1082
+        if ($data['meeting_when'] !== '') {
1083
+            $template->addBodyListItem($data['meeting_when_html'] ?? htmlspecialchars($data['meeting_when']), $this->l10n->t('When:'),
1084
+                $this->getAbsoluteImagePath('caldav/time.png'), $data['meeting_when'], '', IMipPlugin::IMIP_INDENT);
1085
+        }
1086
+        if ($data['meeting_location'] !== '') {
1087
+            $template->addBodyListItem($data['meeting_location_html'] ?? htmlspecialchars($data['meeting_location']), $this->l10n->t('Location:'),
1088
+                $this->getAbsoluteImagePath('caldav/location.png'), $data['meeting_location'], '', IMipPlugin::IMIP_INDENT);
1089
+        }
1090
+        if ($data['meeting_url'] !== '') {
1091
+            $template->addBodyListItem($data['meeting_url_html'] ?? htmlspecialchars($data['meeting_url']), $this->l10n->t('Link:'),
1092
+                $this->getAbsoluteImagePath('caldav/link.png'), $data['meeting_url'], '', IMipPlugin::IMIP_INDENT);
1093
+        }
1094
+        if (isset($data['meeting_occurring'])) {
1095
+            $template->addBodyListItem($data['meeting_occurring_html'] ?? htmlspecialchars($data['meeting_occurring']), $this->l10n->t('Occurring:'),
1096
+                $this->getAbsoluteImagePath('caldav/time.png'), $data['meeting_occurring'], '', IMipPlugin::IMIP_INDENT);
1097
+        }
1098
+
1099
+        $this->addAttendees($template, $vevent);
1100
+
1101
+        /* Put description last, like an email body, since it can be arbitrarily long */
1102
+        if ($data['meeting_description']) {
1103
+            $template->addBodyListItem($data['meeting_description_html'] ?? htmlspecialchars($data['meeting_description']), $this->l10n->t('Description:'),
1104
+                $this->getAbsoluteImagePath('caldav/description.png'), $data['meeting_description'], '', IMipPlugin::IMIP_INDENT);
1105
+        }
1106
+    }
1107
+
1108
+    /**
1109
+     * @param Message $iTipMessage
1110
+     * @return null|Property
1111
+     */
1112
+    public function getCurrentAttendee(Message $iTipMessage): ?Property {
1113
+        /** @var VEvent $vevent */
1114
+        $vevent = $iTipMessage->message->VEVENT;
1115
+        $attendees = $vevent->select('ATTENDEE');
1116
+        foreach ($attendees as $attendee) {
1117
+            if ($iTipMessage->method === 'REPLY' && strcasecmp($attendee->getValue(), $iTipMessage->sender) === 0) {
1118
+                /** @var Property $attendee */
1119
+                return $attendee;
1120
+            } elseif (strcasecmp($attendee->getValue(), $iTipMessage->recipient) === 0) {
1121
+                /** @var Property $attendee */
1122
+                return $attendee;
1123
+            }
1124
+        }
1125
+        return null;
1126
+    }
1127
+
1128
+    /**
1129
+     * @param Message $iTipMessage
1130
+     * @param VEvent $vevent
1131
+     * @param int $lastOccurrence
1132
+     * @return string
1133
+     */
1134
+    public function createInvitationToken(Message $iTipMessage, VEvent $vevent, int $lastOccurrence): string {
1135
+        $token = $this->random->generate(60, ISecureRandom::CHAR_ALPHANUMERIC);
1136
+
1137
+        $attendee = $iTipMessage->recipient;
1138
+        $organizer = $iTipMessage->sender;
1139
+        $sequence = $iTipMessage->sequence;
1140
+        $recurrenceId = isset($vevent->{'RECURRENCE-ID'})
1141
+            ? $vevent->{'RECURRENCE-ID'}->serialize() : null;
1142
+        $uid = $vevent->{'UID'}?->getValue();
1143
+
1144
+        $query = $this->db->getQueryBuilder();
1145
+        $query->insert('calendar_invitations')
1146
+            ->values([
1147
+                'token' => $query->createNamedParameter($token),
1148
+                'attendee' => $query->createNamedParameter($attendee),
1149
+                'organizer' => $query->createNamedParameter($organizer),
1150
+                'sequence' => $query->createNamedParameter($sequence),
1151
+                'recurrenceid' => $query->createNamedParameter($recurrenceId),
1152
+                'expiration' => $query->createNamedParameter($lastOccurrence),
1153
+                'uid' => $query->createNamedParameter($uid)
1154
+            ])
1155
+            ->executeStatement();
1156
+
1157
+        return $token;
1158
+    }
1159
+
1160
+    /**
1161
+     * @param IEMailTemplate $template
1162
+     * @param $token
1163
+     */
1164
+    public function addResponseButtons(IEMailTemplate $template, $token) {
1165
+        $template->addBodyButtonGroup(
1166
+            $this->l10n->t('Accept'),
1167
+            $this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.accept', [
1168
+                'token' => $token,
1169
+            ]),
1170
+            $this->l10n->t('Decline'),
1171
+            $this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.decline', [
1172
+                'token' => $token,
1173
+            ])
1174
+        );
1175
+    }
1176
+
1177
+    public function addMoreOptionsButton(IEMailTemplate $template, $token) {
1178
+        $moreOptionsURL = $this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.options', [
1179
+            'token' => $token,
1180
+        ]);
1181
+        $html = vsprintf('<small><a href="%s">%s</a></small>', [
1182
+            $moreOptionsURL, $this->l10n->t('More options …')
1183
+        ]);
1184
+        $text = $this->l10n->t('More options at %s', [$moreOptionsURL]);
1185
+
1186
+        $template->addBodyText($html, $text);
1187
+    }
1188
+
1189
+    public function getReplyingAttendee(Message $iTipMessage): ?Property {
1190
+        /** @var VEvent $vevent */
1191
+        $vevent = $iTipMessage->message->VEVENT;
1192
+        $attendees = $vevent->select('ATTENDEE');
1193
+        foreach ($attendees as $attendee) {
1194
+            /** @var Property $attendee */
1195
+            if (strcasecmp($attendee->getValue(), $iTipMessage->sender) === 0) {
1196
+                return $attendee;
1197
+            }
1198
+        }
1199
+        return null;
1200
+    }
1201
+
1202
+    public function isRoomOrResource(Property $attendee): bool {
1203
+        $cuType = $attendee->offsetGet('CUTYPE');
1204
+        if (!$cuType instanceof Parameter) {
1205
+            return false;
1206
+        }
1207
+        $type = $cuType->getValue() ?? 'INDIVIDUAL';
1208
+        if (\in_array(strtoupper($type), ['RESOURCE', 'ROOM'], true)) {
1209
+            // Don't send emails to things
1210
+            return true;
1211
+        }
1212
+        return false;
1213
+    }
1214
+
1215
+    public function isCircle(Property $attendee): bool {
1216
+        $cuType = $attendee->offsetGet('CUTYPE');
1217
+        if (!$cuType instanceof Parameter) {
1218
+            return false;
1219
+        }
1220
+
1221
+        $uri = $attendee->getValue();
1222
+        if (!$uri) {
1223
+            return false;
1224
+        }
1225
+
1226
+        $cuTypeValue = $cuType->getValue();
1227
+        return $cuTypeValue === 'GROUP' && str_starts_with($uri, 'mailto:circle+');
1228
+    }
1229
+
1230
+    public function minimizeInterval(\DateInterval $dateInterval): array {
1231
+        // evaluate if time interval is in the past
1232
+        if ($dateInterval->invert == 1) {
1233
+            return ['interval' => 1, 'scale' => 'past'];
1234
+        }
1235
+        // evaluate interval parts and return smallest time period
1236
+        if ($dateInterval->y > 0) {
1237
+            $interval = $dateInterval->y;
1238
+            $scale = 'year';
1239
+        } elseif ($dateInterval->m > 0) {
1240
+            $interval = $dateInterval->m;
1241
+            $scale = 'month';
1242
+        } elseif ($dateInterval->d >= 7) {
1243
+            $interval = (int)($dateInterval->d / 7);
1244
+            $scale = 'week';
1245
+        } elseif ($dateInterval->d > 0) {
1246
+            $interval = $dateInterval->d;
1247
+            $scale = 'day';
1248
+        } elseif ($dateInterval->h > 0) {
1249
+            $interval = $dateInterval->h;
1250
+            $scale = 'hour';
1251
+        } else {
1252
+            $interval = $dateInterval->i;
1253
+            $scale = 'minute';
1254
+        }
1255
+
1256
+        return ['interval' => $interval, 'scale' => $scale];
1257
+    }
1258
+
1259
+    /**
1260
+     * Localizes week day names to another language
1261
+     *
1262
+     * @param string $value
1263
+     *
1264
+     * @return string
1265
+     */
1266
+    public function localizeDayName(string $value): string {
1267
+        return match ($value) {
1268
+            'Monday' => $this->l10n->t('Monday'),
1269
+            'Tuesday' => $this->l10n->t('Tuesday'),
1270
+            'Wednesday' => $this->l10n->t('Wednesday'),
1271
+            'Thursday' => $this->l10n->t('Thursday'),
1272
+            'Friday' => $this->l10n->t('Friday'),
1273
+            'Saturday' => $this->l10n->t('Saturday'),
1274
+            'Sunday' => $this->l10n->t('Sunday'),
1275
+        };
1276
+    }
1277
+
1278
+    /**
1279
+     * Localizes month names to another language
1280
+     *
1281
+     * @param string $value
1282
+     *
1283
+     * @return string
1284
+     */
1285
+    public function localizeMonthName(string $value): string {
1286
+        return match ($value) {
1287
+            'January' => $this->l10n->t('January'),
1288
+            'February' => $this->l10n->t('February'),
1289
+            'March' => $this->l10n->t('March'),
1290
+            'April' => $this->l10n->t('April'),
1291
+            'May' => $this->l10n->t('May'),
1292
+            'June' => $this->l10n->t('June'),
1293
+            'July' => $this->l10n->t('July'),
1294
+            'August' => $this->l10n->t('August'),
1295
+            'September' => $this->l10n->t('September'),
1296
+            'October' => $this->l10n->t('October'),
1297
+            'November' => $this->l10n->t('November'),
1298
+            'December' => $this->l10n->t('December'),
1299
+        };
1300
+    }
1301
+
1302
+    /**
1303
+     * Localizes relative position names to another language
1304
+     *
1305
+     * @param string $value
1306
+     *
1307
+     * @return string
1308
+     */
1309
+    public function localizeRelativePositionName(string $value): string {
1310
+        return match ($value) {
1311
+            'First' => $this->l10n->t('First'),
1312
+            'Second' => $this->l10n->t('Second'),
1313
+            'Third' => $this->l10n->t('Third'),
1314
+            'Fourth' => $this->l10n->t('Fourth'),
1315
+            'Fifth' => $this->l10n->t('Fifth'),
1316
+            'Last' => $this->l10n->t('Last'),
1317
+            'Second Last' => $this->l10n->t('Second Last'),
1318
+            'Third Last' => $this->l10n->t('Third Last'),
1319
+            'Fourth Last' => $this->l10n->t('Fourth Last'),
1320
+            'Fifth Last' => $this->l10n->t('Fifth Last'),
1321
+        };
1322
+    }
1323 1323
 }
Please login to merge, or discard this patch.
apps/dav/tests/unit/CalDAV/TimezoneServiceTest.php 1 patch
Indentation   +115 added lines, -115 removed lines patch added patch discarded remove patch
@@ -23,125 +23,125 @@
 block discarded – undo
23 23
 use Test\TestCase;
24 24
 
25 25
 class TimezoneServiceTest extends TestCase {
26
-	private IConfig&MockObject $config;
27
-	private IUserConfig&MockObject $userConfig;
28
-	private PropertyMapper&MockObject $propertyMapper;
29
-	private IManager&MockObject $calendarManager;
30
-	private TimezoneService $service;
31
-
32
-	protected function setUp(): void {
33
-		parent::setUp();
34
-
35
-		$this->config = $this->createMock(IConfig::class);
36
-		$this->userConfig = $this->createMock(IUserConfig::class);
37
-		$this->propertyMapper = $this->createMock(PropertyMapper::class);
38
-		$this->calendarManager = $this->createMock(IManager::class);
39
-
40
-		$this->service = new TimezoneService(
41
-			$this->config,
42
-			$this->userConfig,
43
-			$this->propertyMapper,
44
-			$this->calendarManager,
45
-		);
46
-	}
47
-
48
-	public function testGetUserTimezoneFromSettings(): void {
49
-		$this->userConfig->expects(self::once())
50
-			->method('getValueString')
51
-			->with('test123', 'core', 'timezone', '')
52
-			->willReturn('Europe/Warsaw');
53
-
54
-		$timezone = $this->service->getUserTimezone('test123');
55
-
56
-		self::assertSame('Europe/Warsaw', $timezone);
57
-	}
58
-
59
-	public function testGetUserTimezoneFromAvailability(): void {
60
-		$this->userConfig->expects(self::once())
61
-			->method('getValueString')
62
-			->with('test123', 'core', 'timezone', '')
63
-			->willReturn('');
64
-		$property = new Property();
65
-		$property->setPropertyvalue('BEGIN:VCALENDAR
26
+    private IConfig&MockObject $config;
27
+    private IUserConfig&MockObject $userConfig;
28
+    private PropertyMapper&MockObject $propertyMapper;
29
+    private IManager&MockObject $calendarManager;
30
+    private TimezoneService $service;
31
+
32
+    protected function setUp(): void {
33
+        parent::setUp();
34
+
35
+        $this->config = $this->createMock(IConfig::class);
36
+        $this->userConfig = $this->createMock(IUserConfig::class);
37
+        $this->propertyMapper = $this->createMock(PropertyMapper::class);
38
+        $this->calendarManager = $this->createMock(IManager::class);
39
+
40
+        $this->service = new TimezoneService(
41
+            $this->config,
42
+            $this->userConfig,
43
+            $this->propertyMapper,
44
+            $this->calendarManager,
45
+        );
46
+    }
47
+
48
+    public function testGetUserTimezoneFromSettings(): void {
49
+        $this->userConfig->expects(self::once())
50
+            ->method('getValueString')
51
+            ->with('test123', 'core', 'timezone', '')
52
+            ->willReturn('Europe/Warsaw');
53
+
54
+        $timezone = $this->service->getUserTimezone('test123');
55
+
56
+        self::assertSame('Europe/Warsaw', $timezone);
57
+    }
58
+
59
+    public function testGetUserTimezoneFromAvailability(): void {
60
+        $this->userConfig->expects(self::once())
61
+            ->method('getValueString')
62
+            ->with('test123', 'core', 'timezone', '')
63
+            ->willReturn('');
64
+        $property = new Property();
65
+        $property->setPropertyvalue('BEGIN:VCALENDAR
66 66
 PRODID:Nextcloud DAV app
67 67
 BEGIN:VTIMEZONE
68 68
 TZID:Europe/Vienna
69 69
 END:VTIMEZONE
70 70
 END:VCALENDAR');
71
-		$this->propertyMapper->expects(self::once())
72
-			->method('findPropertyByPathAndName')
73
-			->willReturn([
74
-				$property,
75
-			]);
76
-
77
-		$timezone = $this->service->getUserTimezone('test123');
78
-
79
-		self::assertNotNull($timezone);
80
-		self::assertEquals('Europe/Vienna', $timezone);
81
-	}
82
-
83
-	public function testGetUserTimezoneFromPersonalCalendar(): void {
84
-		$this->userConfig->expects(self::exactly(2))
85
-			->method('getValueString')
86
-			->willReturnMap([
87
-				['test123', 'core', 'timezone', '', false, ''],
88
-				['test123', 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI, false, 'personal-1'],
89
-			]);
90
-		$other = $this->createMock(ICalendar::class);
91
-		$other->method('getUri')->willReturn('other');
92
-		$personal = $this->createMock(CalendarImpl::class);
93
-		$personal->method('getUri')->willReturn('personal-1');
94
-		$tz = new DateTimeZone('Europe/Berlin');
95
-		$vtz = $this->createMock(VTimeZone::class);
96
-		$vtz->method('getTimeZone')->willReturn($tz);
97
-		$personal->method('getSchedulingTimezone')->willReturn($vtz);
98
-		$this->calendarManager->expects(self::once())
99
-			->method('getCalendarsForPrincipal')
100
-			->with('principals/users/test123')
101
-			->willReturn([
102
-				$other,
103
-				$personal,
104
-			]);
105
-
106
-		$timezone = $this->service->getUserTimezone('test123');
107
-
108
-		self::assertNotNull($timezone);
109
-		self::assertEquals('Europe/Berlin', $timezone);
110
-	}
111
-
112
-	public function testGetUserTimezoneFromAny(): void {
113
-		$this->userConfig->expects(self::exactly(2))
114
-			->method('getValueString')
115
-			->willReturnMap([
116
-				['test123', 'core', 'timezone', '', false, ''],
117
-				['test123', 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI, false, 'personal-1'],
118
-			]);
119
-		$other = $this->createMock(ICalendar::class);
120
-		$other->method('getUri')->willReturn('other');
121
-		$personal = $this->createMock(CalendarImpl::class);
122
-		$personal->method('getUri')->willReturn('personal-2');
123
-		$tz = new DateTimeZone('Europe/Prague');
124
-		$vtz = $this->createMock(VTimeZone::class);
125
-		$vtz->method('getTimeZone')->willReturn($tz);
126
-		$personal->method('getSchedulingTimezone')->willReturn($vtz);
127
-		$this->calendarManager->expects(self::once())
128
-			->method('getCalendarsForPrincipal')
129
-			->with('principals/users/test123')
130
-			->willReturn([
131
-				$other,
132
-				$personal,
133
-			]);
134
-
135
-		$timezone = $this->service->getUserTimezone('test123');
136
-
137
-		self::assertNotNull($timezone);
138
-		self::assertEquals('Europe/Prague', $timezone);
139
-	}
140
-
141
-	public function testGetUserTimezoneNoneFound(): void {
142
-		$timezone = $this->service->getUserTimezone('test123');
143
-
144
-		self::assertNull($timezone);
145
-	}
71
+        $this->propertyMapper->expects(self::once())
72
+            ->method('findPropertyByPathAndName')
73
+            ->willReturn([
74
+                $property,
75
+            ]);
76
+
77
+        $timezone = $this->service->getUserTimezone('test123');
78
+
79
+        self::assertNotNull($timezone);
80
+        self::assertEquals('Europe/Vienna', $timezone);
81
+    }
82
+
83
+    public function testGetUserTimezoneFromPersonalCalendar(): void {
84
+        $this->userConfig->expects(self::exactly(2))
85
+            ->method('getValueString')
86
+            ->willReturnMap([
87
+                ['test123', 'core', 'timezone', '', false, ''],
88
+                ['test123', 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI, false, 'personal-1'],
89
+            ]);
90
+        $other = $this->createMock(ICalendar::class);
91
+        $other->method('getUri')->willReturn('other');
92
+        $personal = $this->createMock(CalendarImpl::class);
93
+        $personal->method('getUri')->willReturn('personal-1');
94
+        $tz = new DateTimeZone('Europe/Berlin');
95
+        $vtz = $this->createMock(VTimeZone::class);
96
+        $vtz->method('getTimeZone')->willReturn($tz);
97
+        $personal->method('getSchedulingTimezone')->willReturn($vtz);
98
+        $this->calendarManager->expects(self::once())
99
+            ->method('getCalendarsForPrincipal')
100
+            ->with('principals/users/test123')
101
+            ->willReturn([
102
+                $other,
103
+                $personal,
104
+            ]);
105
+
106
+        $timezone = $this->service->getUserTimezone('test123');
107
+
108
+        self::assertNotNull($timezone);
109
+        self::assertEquals('Europe/Berlin', $timezone);
110
+    }
111
+
112
+    public function testGetUserTimezoneFromAny(): void {
113
+        $this->userConfig->expects(self::exactly(2))
114
+            ->method('getValueString')
115
+            ->willReturnMap([
116
+                ['test123', 'core', 'timezone', '', false, ''],
117
+                ['test123', 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI, false, 'personal-1'],
118
+            ]);
119
+        $other = $this->createMock(ICalendar::class);
120
+        $other->method('getUri')->willReturn('other');
121
+        $personal = $this->createMock(CalendarImpl::class);
122
+        $personal->method('getUri')->willReturn('personal-2');
123
+        $tz = new DateTimeZone('Europe/Prague');
124
+        $vtz = $this->createMock(VTimeZone::class);
125
+        $vtz->method('getTimeZone')->willReturn($tz);
126
+        $personal->method('getSchedulingTimezone')->willReturn($vtz);
127
+        $this->calendarManager->expects(self::once())
128
+            ->method('getCalendarsForPrincipal')
129
+            ->with('principals/users/test123')
130
+            ->willReturn([
131
+                $other,
132
+                $personal,
133
+            ]);
134
+
135
+        $timezone = $this->service->getUserTimezone('test123');
136
+
137
+        self::assertNotNull($timezone);
138
+        self::assertEquals('Europe/Prague', $timezone);
139
+    }
140
+
141
+    public function testGetUserTimezoneNoneFound(): void {
142
+        $timezone = $this->service->getUserTimezone('test123');
143
+
144
+        self::assertNull($timezone);
145
+    }
146 146
 
147 147
 }
Please login to merge, or discard this patch.