Completed
Push — master ( 79155a...8d0855 )
by
unknown
57:03 queued 15:47
created
apps/user_ldap/lib/User/Manager.php 1 patch
Indentation   +213 added lines, -213 removed lines patch added patch discarded remove patch
@@ -27,237 +27,237 @@
 block discarded – undo
27 27
  * cache
28 28
  */
29 29
 class Manager {
30
-	protected ?Access $access = null;
31
-	protected IDBConnection $db;
32
-	/** @var CappedMemoryCache<User> $usersByDN */
33
-	protected CappedMemoryCache $usersByDN;
34
-	/** @var CappedMemoryCache<User> $usersByUid */
35
-	protected CappedMemoryCache $usersByUid;
30
+    protected ?Access $access = null;
31
+    protected IDBConnection $db;
32
+    /** @var CappedMemoryCache<User> $usersByDN */
33
+    protected CappedMemoryCache $usersByDN;
34
+    /** @var CappedMemoryCache<User> $usersByUid */
35
+    protected CappedMemoryCache $usersByUid;
36 36
 
37
-	public function __construct(
38
-		protected IConfig $ocConfig,
39
-		protected IUserConfig $userConfig,
40
-		protected IAppConfig $appConfig,
41
-		protected LoggerInterface $logger,
42
-		protected IAvatarManager $avatarManager,
43
-		protected Image $image,
44
-		protected IUserManager $userManager,
45
-		protected INotificationManager $notificationManager,
46
-		private IManager $shareManager,
47
-	) {
48
-		$this->usersByDN = new CappedMemoryCache();
49
-		$this->usersByUid = new CappedMemoryCache();
50
-	}
37
+    public function __construct(
38
+        protected IConfig $ocConfig,
39
+        protected IUserConfig $userConfig,
40
+        protected IAppConfig $appConfig,
41
+        protected LoggerInterface $logger,
42
+        protected IAvatarManager $avatarManager,
43
+        protected Image $image,
44
+        protected IUserManager $userManager,
45
+        protected INotificationManager $notificationManager,
46
+        private IManager $shareManager,
47
+    ) {
48
+        $this->usersByDN = new CappedMemoryCache();
49
+        $this->usersByUid = new CappedMemoryCache();
50
+    }
51 51
 
52
-	/**
53
-	 * Binds manager to an instance of Access.
54
-	 * It needs to be assigned first before the manager can be used.
55
-	 * @param Access
56
-	 */
57
-	public function setLdapAccess(Access $access) {
58
-		$this->access = $access;
59
-	}
52
+    /**
53
+     * Binds manager to an instance of Access.
54
+     * It needs to be assigned first before the manager can be used.
55
+     * @param Access
56
+     */
57
+    public function setLdapAccess(Access $access) {
58
+        $this->access = $access;
59
+    }
60 60
 
61
-	/**
62
-	 * @brief creates an instance of User and caches (just runtime) it in the
63
-	 * property array
64
-	 * @param string $dn the DN of the user
65
-	 * @param string $uid the internal (owncloud) username
66
-	 * @return User
67
-	 */
68
-	private function createAndCache($dn, $uid) {
69
-		$this->checkAccess();
70
-		$user = new User($uid, $dn, $this->access, $this->ocConfig, $this->userConfig, $this->appConfig,
71
-			clone $this->image, $this->logger,
72
-			$this->avatarManager, $this->userManager,
73
-			$this->notificationManager);
74
-		$this->usersByDN[$dn] = $user;
75
-		$this->usersByUid[$uid] = $user;
76
-		return $user;
77
-	}
61
+    /**
62
+     * @brief creates an instance of User and caches (just runtime) it in the
63
+     * property array
64
+     * @param string $dn the DN of the user
65
+     * @param string $uid the internal (owncloud) username
66
+     * @return User
67
+     */
68
+    private function createAndCache($dn, $uid) {
69
+        $this->checkAccess();
70
+        $user = new User($uid, $dn, $this->access, $this->ocConfig, $this->userConfig, $this->appConfig,
71
+            clone $this->image, $this->logger,
72
+            $this->avatarManager, $this->userManager,
73
+            $this->notificationManager);
74
+        $this->usersByDN[$dn] = $user;
75
+        $this->usersByUid[$uid] = $user;
76
+        return $user;
77
+    }
78 78
 
79
-	/**
80
-	 * removes a user entry from the cache
81
-	 * @param $uid
82
-	 */
83
-	public function invalidate($uid) {
84
-		if (!isset($this->usersByUid[$uid])) {
85
-			return;
86
-		}
87
-		$dn = $this->usersByUid[$uid]->getDN();
88
-		unset($this->usersByUid[$uid]);
89
-		unset($this->usersByDN[$dn]);
90
-	}
79
+    /**
80
+     * removes a user entry from the cache
81
+     * @param $uid
82
+     */
83
+    public function invalidate($uid) {
84
+        if (!isset($this->usersByUid[$uid])) {
85
+            return;
86
+        }
87
+        $dn = $this->usersByUid[$uid]->getDN();
88
+        unset($this->usersByUid[$uid]);
89
+        unset($this->usersByDN[$dn]);
90
+    }
91 91
 
92
-	/**
93
-	 * @brief checks whether the Access instance has been set
94
-	 * @throws \Exception if Access has not been set
95
-	 * @psalm-assert !null $this->access
96
-	 * @return null
97
-	 */
98
-	private function checkAccess() {
99
-		if (is_null($this->access)) {
100
-			throw new \Exception('LDAP Access instance must be set first');
101
-		}
102
-	}
92
+    /**
93
+     * @brief checks whether the Access instance has been set
94
+     * @throws \Exception if Access has not been set
95
+     * @psalm-assert !null $this->access
96
+     * @return null
97
+     */
98
+    private function checkAccess() {
99
+        if (is_null($this->access)) {
100
+            throw new \Exception('LDAP Access instance must be set first');
101
+        }
102
+    }
103 103
 
104
-	/**
105
-	 * returns a list of attributes that will be processed further, e.g. quota,
106
-	 * email, displayname, or others.
107
-	 *
108
-	 * @param bool $minimal - optional, set to true to skip attributes with big
109
-	 *                      payload
110
-	 * @return string[]
111
-	 */
112
-	public function getAttributes($minimal = false) {
113
-		$baseAttributes = array_merge(Access::UUID_ATTRIBUTES, ['dn', 'uid', 'samaccountname', 'memberof']);
114
-		$attributes = [
115
-			$this->access->getConnection()->ldapExpertUUIDUserAttr,
116
-			$this->access->getConnection()->ldapExpertUsernameAttr,
117
-			$this->access->getConnection()->ldapQuotaAttribute,
118
-			$this->access->getConnection()->ldapEmailAttribute,
119
-			$this->access->getConnection()->ldapUserDisplayName,
120
-			$this->access->getConnection()->ldapUserDisplayName2,
121
-			$this->access->getConnection()->ldapExtStorageHomeAttribute,
122
-			$this->access->getConnection()->ldapAttributePhone,
123
-			$this->access->getConnection()->ldapAttributeWebsite,
124
-			$this->access->getConnection()->ldapAttributeAddress,
125
-			$this->access->getConnection()->ldapAttributeTwitter,
126
-			$this->access->getConnection()->ldapAttributeFediverse,
127
-			$this->access->getConnection()->ldapAttributeOrganisation,
128
-			$this->access->getConnection()->ldapAttributeRole,
129
-			$this->access->getConnection()->ldapAttributeHeadline,
130
-			$this->access->getConnection()->ldapAttributeBiography,
131
-			$this->access->getConnection()->ldapAttributeBirthDate,
132
-			$this->access->getConnection()->ldapAttributePronouns,
133
-		];
104
+    /**
105
+     * returns a list of attributes that will be processed further, e.g. quota,
106
+     * email, displayname, or others.
107
+     *
108
+     * @param bool $minimal - optional, set to true to skip attributes with big
109
+     *                      payload
110
+     * @return string[]
111
+     */
112
+    public function getAttributes($minimal = false) {
113
+        $baseAttributes = array_merge(Access::UUID_ATTRIBUTES, ['dn', 'uid', 'samaccountname', 'memberof']);
114
+        $attributes = [
115
+            $this->access->getConnection()->ldapExpertUUIDUserAttr,
116
+            $this->access->getConnection()->ldapExpertUsernameAttr,
117
+            $this->access->getConnection()->ldapQuotaAttribute,
118
+            $this->access->getConnection()->ldapEmailAttribute,
119
+            $this->access->getConnection()->ldapUserDisplayName,
120
+            $this->access->getConnection()->ldapUserDisplayName2,
121
+            $this->access->getConnection()->ldapExtStorageHomeAttribute,
122
+            $this->access->getConnection()->ldapAttributePhone,
123
+            $this->access->getConnection()->ldapAttributeWebsite,
124
+            $this->access->getConnection()->ldapAttributeAddress,
125
+            $this->access->getConnection()->ldapAttributeTwitter,
126
+            $this->access->getConnection()->ldapAttributeFediverse,
127
+            $this->access->getConnection()->ldapAttributeOrganisation,
128
+            $this->access->getConnection()->ldapAttributeRole,
129
+            $this->access->getConnection()->ldapAttributeHeadline,
130
+            $this->access->getConnection()->ldapAttributeBiography,
131
+            $this->access->getConnection()->ldapAttributeBirthDate,
132
+            $this->access->getConnection()->ldapAttributePronouns,
133
+        ];
134 134
 
135
-		$homeRule = (string)$this->access->getConnection()->homeFolderNamingRule;
136
-		if (str_starts_with($homeRule, 'attr:')) {
137
-			$attributes[] = substr($homeRule, strlen('attr:'));
138
-		}
135
+        $homeRule = (string)$this->access->getConnection()->homeFolderNamingRule;
136
+        if (str_starts_with($homeRule, 'attr:')) {
137
+            $attributes[] = substr($homeRule, strlen('attr:'));
138
+        }
139 139
 
140
-		if (!$minimal) {
141
-			// attributes that are not really important but may come with big
142
-			// payload.
143
-			$attributes = array_merge(
144
-				$attributes,
145
-				$this->access->getConnection()->resolveRule('avatar')
146
-			);
147
-		}
140
+        if (!$minimal) {
141
+            // attributes that are not really important but may come with big
142
+            // payload.
143
+            $attributes = array_merge(
144
+                $attributes,
145
+                $this->access->getConnection()->resolveRule('avatar')
146
+            );
147
+        }
148 148
 
149
-		$attributes = array_reduce($attributes,
150
-			function ($list, $attribute) {
151
-				$attribute = strtolower(trim((string)$attribute));
152
-				if (!empty($attribute) && !in_array($attribute, $list)) {
153
-					$list[] = $attribute;
154
-				}
149
+        $attributes = array_reduce($attributes,
150
+            function ($list, $attribute) {
151
+                $attribute = strtolower(trim((string)$attribute));
152
+                if (!empty($attribute) && !in_array($attribute, $list)) {
153
+                    $list[] = $attribute;
154
+                }
155 155
 
156
-				return $list;
157
-			},
158
-			$baseAttributes // hard-coded, lower-case, non-empty attributes
159
-		);
156
+                return $list;
157
+            },
158
+            $baseAttributes // hard-coded, lower-case, non-empty attributes
159
+        );
160 160
 
161
-		return $attributes;
162
-	}
161
+        return $attributes;
162
+    }
163 163
 
164
-	/**
165
-	 * Checks whether the specified user is marked as deleted.
166
-	 * @param string $id the Nextcloud username
167
-	 */
168
-	public function isDeletedUser(string $id): bool {
169
-		try {
170
-			return $this->userConfig->getValueBool($id, 'user_ldap', 'isDeleted');
171
-		} catch (\InvalidArgumentException $e) {
172
-			// Most likely the string is too long to be a valid user id
173
-			$this->logger->debug('Invalid id given to isDeletedUser', ['exception' => $e]);
174
-			return false;
175
-		}
176
-	}
164
+    /**
165
+     * Checks whether the specified user is marked as deleted.
166
+     * @param string $id the Nextcloud username
167
+     */
168
+    public function isDeletedUser(string $id): bool {
169
+        try {
170
+            return $this->userConfig->getValueBool($id, 'user_ldap', 'isDeleted');
171
+        } catch (\InvalidArgumentException $e) {
172
+            // Most likely the string is too long to be a valid user id
173
+            $this->logger->debug('Invalid id given to isDeletedUser', ['exception' => $e]);
174
+            return false;
175
+        }
176
+    }
177 177
 
178
-	/**
179
-	 * Creates and returns an instance of OfflineUser for the specified user.
180
-	 */
181
-	public function getDeletedUser(string $id): OfflineUser {
182
-		return new OfflineUser(
183
-			$id,
184
-			$this->userConfig,
185
-			$this->access->getUserMapper(),
186
-			$this->shareManager
187
-		);
188
-	}
178
+    /**
179
+     * Creates and returns an instance of OfflineUser for the specified user.
180
+     */
181
+    public function getDeletedUser(string $id): OfflineUser {
182
+        return new OfflineUser(
183
+            $id,
184
+            $this->userConfig,
185
+            $this->access->getUserMapper(),
186
+            $this->shareManager
187
+        );
188
+    }
189 189
 
190
-	/**
191
-	 * @brief returns a User object by its Nextcloud username
192
-	 * @param string $id the DN or username of the user
193
-	 * @return User|OfflineUser|null
194
-	 */
195
-	protected function createInstancyByUserName($id) {
196
-		//most likely a uid. Check whether it is a deleted user
197
-		if ($this->isDeletedUser($id)) {
198
-			return $this->getDeletedUser($id);
199
-		}
200
-		$dn = $this->access->username2dn($id);
201
-		if ($dn !== false) {
202
-			return $this->createAndCache($dn, $id);
203
-		}
204
-		return null;
205
-	}
190
+    /**
191
+     * @brief returns a User object by its Nextcloud username
192
+     * @param string $id the DN or username of the user
193
+     * @return User|OfflineUser|null
194
+     */
195
+    protected function createInstancyByUserName($id) {
196
+        //most likely a uid. Check whether it is a deleted user
197
+        if ($this->isDeletedUser($id)) {
198
+            return $this->getDeletedUser($id);
199
+        }
200
+        $dn = $this->access->username2dn($id);
201
+        if ($dn !== false) {
202
+            return $this->createAndCache($dn, $id);
203
+        }
204
+        return null;
205
+    }
206 206
 
207
-	/**
208
-	 * @brief returns a User object by its DN or Nextcloud username
209
-	 * @param string $id the DN or username of the user
210
-	 * @return User|OfflineUser|null
211
-	 * @throws \Exception when connection could not be established
212
-	 */
213
-	public function get($id) {
214
-		$this->checkAccess();
215
-		if (isset($this->usersByDN[$id])) {
216
-			return $this->usersByDN[$id];
217
-		} elseif (isset($this->usersByUid[$id])) {
218
-			return $this->usersByUid[$id];
219
-		}
207
+    /**
208
+     * @brief returns a User object by its DN or Nextcloud username
209
+     * @param string $id the DN or username of the user
210
+     * @return User|OfflineUser|null
211
+     * @throws \Exception when connection could not be established
212
+     */
213
+    public function get($id) {
214
+        $this->checkAccess();
215
+        if (isset($this->usersByDN[$id])) {
216
+            return $this->usersByDN[$id];
217
+        } elseif (isset($this->usersByUid[$id])) {
218
+            return $this->usersByUid[$id];
219
+        }
220 220
 
221
-		if ($this->access->stringResemblesDN($id)) {
222
-			$uid = $this->access->dn2username($id);
223
-			if ($uid !== false) {
224
-				return $this->createAndCache($id, $uid);
225
-			}
226
-		}
221
+        if ($this->access->stringResemblesDN($id)) {
222
+            $uid = $this->access->dn2username($id);
223
+            if ($uid !== false) {
224
+                return $this->createAndCache($id, $uid);
225
+            }
226
+        }
227 227
 
228
-		return $this->createInstancyByUserName($id);
229
-	}
228
+        return $this->createInstancyByUserName($id);
229
+    }
230 230
 
231
-	/**
232
-	 * @brief Checks whether a User object by its DN or Nextcloud username exists
233
-	 * @param string $id the DN or username of the user
234
-	 * @throws \Exception when connection could not be established
235
-	 */
236
-	public function exists($id): bool {
237
-		$this->checkAccess();
238
-		$this->logger->debug('Checking if {id} exists', ['id' => $id]);
239
-		if (isset($this->usersByDN[$id])) {
240
-			return true;
241
-		} elseif (isset($this->usersByUid[$id])) {
242
-			return true;
243
-		}
231
+    /**
232
+     * @brief Checks whether a User object by its DN or Nextcloud username exists
233
+     * @param string $id the DN or username of the user
234
+     * @throws \Exception when connection could not be established
235
+     */
236
+    public function exists($id): bool {
237
+        $this->checkAccess();
238
+        $this->logger->debug('Checking if {id} exists', ['id' => $id]);
239
+        if (isset($this->usersByDN[$id])) {
240
+            return true;
241
+        } elseif (isset($this->usersByUid[$id])) {
242
+            return true;
243
+        }
244 244
 
245
-		if ($this->access->stringResemblesDN($id)) {
246
-			$this->logger->debug('{id} looks like a dn', ['id' => $id]);
247
-			$uid = $this->access->dn2username($id);
248
-			if ($uid !== false) {
249
-				return true;
250
-			}
251
-		}
245
+        if ($this->access->stringResemblesDN($id)) {
246
+            $this->logger->debug('{id} looks like a dn', ['id' => $id]);
247
+            $uid = $this->access->dn2username($id);
248
+            if ($uid !== false) {
249
+                return true;
250
+            }
251
+        }
252 252
 
253
-		// Most likely a uid. Check whether it is a deleted user
254
-		if ($this->isDeletedUser($id)) {
255
-			return true;
256
-		}
257
-		$dn = $this->access->username2dn($id);
258
-		if ($dn !== false) {
259
-			return true;
260
-		}
261
-		return false;
262
-	}
253
+        // Most likely a uid. Check whether it is a deleted user
254
+        if ($this->isDeletedUser($id)) {
255
+            return true;
256
+        }
257
+        $dn = $this->access->username2dn($id);
258
+        if ($dn !== false) {
259
+            return true;
260
+        }
261
+        return false;
262
+    }
263 263
 }
Please login to merge, or discard this patch.
lib/private/Config/UserConfig.php 1 patch
Indentation   +2043 added lines, -2043 removed lines patch added patch discarded remove patch
@@ -48,2047 +48,2047 @@
 block discarded – undo
48 48
  * @since 31.0.0
49 49
  */
50 50
 class UserConfig implements IUserConfig {
51
-	private const USER_MAX_LENGTH = 64;
52
-	private const APP_MAX_LENGTH = 32;
53
-	private const KEY_MAX_LENGTH = 64;
54
-	private const INDEX_MAX_LENGTH = 64;
55
-	private const ENCRYPTION_PREFIX = '$UserConfigEncryption$';
56
-	private const ENCRYPTION_PREFIX_LENGTH = 22; // strlen(self::ENCRYPTION_PREFIX)
57
-
58
-	/** @var array<string, array<string, array<string, mixed>>> [ass'user_id' => ['app_id' => ['key' => 'value']]] */
59
-	private array $fastCache = [];   // cache for normal config keys
60
-	/** @var array<string, array<string, array<string, mixed>>> ['user_id' => ['app_id' => ['key' => 'value']]] */
61
-	private array $lazyCache = [];   // cache for lazy config keys
62
-	/** @var array<string, array<string, array<string, array<string, mixed>>>> ['user_id' => ['app_id' => ['key' => ['type' => ValueType, 'flags' => bitflag]]]] */
63
-	private array $valueDetails = [];  // type for all config values
64
-	/** @var array<string, boolean> ['user_id' => bool] */
65
-	private array $fastLoaded = [];
66
-	/** @var array<string, boolean> ['user_id' => bool] */
67
-	private array $lazyLoaded = [];
68
-	/** @var array<string, array{entries: array<string, Entry>, aliases: array<string, string>, strictness: Strictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */
69
-	private array $configLexiconDetails = [];
70
-	private bool $ignoreLexiconAliases = false;
71
-	private array $strictnessApplied = [];
72
-
73
-	public function __construct(
74
-		protected IDBConnection $connection,
75
-		protected IConfig $config,
76
-		private readonly ConfigManager $configManager,
77
-		private readonly PresetManager $presetManager,
78
-		protected LoggerInterface $logger,
79
-		protected ICrypto $crypto,
80
-		protected IEventDispatcher $dispatcher,
81
-	) {
82
-	}
83
-
84
-	/**
85
-	 * @inheritDoc
86
-	 *
87
-	 * @param string $appId optional id of app
88
-	 *
89
-	 * @return list<string> list of userIds
90
-	 * @since 31.0.0
91
-	 */
92
-	public function getUserIds(string $appId = ''): array {
93
-		$this->assertParams(app: $appId, allowEmptyUser: true, allowEmptyApp: true);
94
-
95
-		$qb = $this->connection->getQueryBuilder();
96
-		$qb->from('preferences');
97
-		$qb->select('userid');
98
-		$qb->groupBy('userid');
99
-		if ($appId !== '') {
100
-			$qb->where($qb->expr()->eq('appid', $qb->createNamedParameter($appId)));
101
-		}
102
-
103
-		$result = $qb->executeQuery();
104
-		$rows = $result->fetchAll();
105
-		$userIds = [];
106
-		foreach ($rows as $row) {
107
-			$userIds[] = $row['userid'];
108
-		}
109
-
110
-		return $userIds;
111
-	}
112
-
113
-	/**
114
-	 * @inheritDoc
115
-	 *
116
-	 * @return list<string> list of app ids
117
-	 * @since 31.0.0
118
-	 */
119
-	public function getApps(string $userId): array {
120
-		$this->assertParams($userId, allowEmptyApp: true);
121
-		$this->loadConfigAll($userId);
122
-		$apps = array_merge(array_keys($this->fastCache[$userId] ?? []), array_keys($this->lazyCache[$userId] ?? []));
123
-		sort($apps);
124
-
125
-		return array_values(array_unique($apps));
126
-	}
127
-
128
-	/**
129
-	 * @inheritDoc
130
-	 *
131
-	 * @param string $userId id of the user
132
-	 * @param string $app id of the app
133
-	 *
134
-	 * @return list<string> list of stored config keys
135
-	 * @since 31.0.0
136
-	 */
137
-	public function getKeys(string $userId, string $app): array {
138
-		$this->assertParams($userId, $app);
139
-		$this->loadConfigAll($userId);
140
-		// array_merge() will remove numeric keys (here config keys), so addition arrays instead
141
-		$keys = array_map('strval', array_keys(($this->fastCache[$userId][$app] ?? []) + ($this->lazyCache[$userId][$app] ?? [])));
142
-		sort($keys);
143
-
144
-		return array_values(array_unique($keys));
145
-	}
146
-
147
-	/**
148
-	 * @inheritDoc
149
-	 *
150
-	 * @param string $userId id of the user
151
-	 * @param string $app id of the app
152
-	 * @param string $key config key
153
-	 * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
154
-	 *
155
-	 * @return bool TRUE if key exists
156
-	 * @since 31.0.0
157
-	 */
158
-	public function hasKey(string $userId, string $app, string $key, ?bool $lazy = false): bool {
159
-		$this->assertParams($userId, $app, $key);
160
-		$this->loadConfig($userId, $lazy);
161
-		$this->matchAndApplyLexiconDefinition($userId, $app, $key);
162
-
163
-		if ($lazy === null) {
164
-			$appCache = $this->getValues($userId, $app);
165
-			return isset($appCache[$key]);
166
-		}
167
-
168
-		if ($lazy) {
169
-			return isset($this->lazyCache[$userId][$app][$key]);
170
-		}
171
-
172
-		return isset($this->fastCache[$userId][$app][$key]);
173
-	}
174
-
175
-	/**
176
-	 * @inheritDoc
177
-	 *
178
-	 * @param string $userId id of the user
179
-	 * @param string $app id of the app
180
-	 * @param string $key config key
181
-	 * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
182
-	 *
183
-	 * @return bool
184
-	 * @throws UnknownKeyException if config key is not known
185
-	 * @since 31.0.0
186
-	 */
187
-	public function isSensitive(string $userId, string $app, string $key, ?bool $lazy = false): bool {
188
-		$this->assertParams($userId, $app, $key);
189
-		$this->loadConfig($userId, $lazy);
190
-		$this->matchAndApplyLexiconDefinition($userId, $app, $key);
191
-
192
-		if (!isset($this->valueDetails[$userId][$app][$key])) {
193
-			throw new UnknownKeyException('unknown config key');
194
-		}
195
-
196
-		return $this->isFlagged(self::FLAG_SENSITIVE, $this->valueDetails[$userId][$app][$key]['flags']);
197
-	}
198
-
199
-	/**
200
-	 * @inheritDoc
201
-	 *
202
-	 * @param string $userId id of the user
203
-	 * @param string $app id of the app
204
-	 * @param string $key config key
205
-	 * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
206
-	 *
207
-	 * @return bool
208
-	 * @throws UnknownKeyException if config key is not known
209
-	 * @since 31.0.0
210
-	 */
211
-	public function isIndexed(string $userId, string $app, string $key, ?bool $lazy = false): bool {
212
-		$this->assertParams($userId, $app, $key);
213
-		$this->loadConfig($userId, $lazy);
214
-		$this->matchAndApplyLexiconDefinition($userId, $app, $key);
215
-
216
-		if (!isset($this->valueDetails[$userId][$app][$key])) {
217
-			throw new UnknownKeyException('unknown config key');
218
-		}
219
-
220
-		return $this->isFlagged(self::FLAG_INDEXED, $this->valueDetails[$userId][$app][$key]['flags']);
221
-	}
222
-
223
-	/**
224
-	 * @inheritDoc
225
-	 *
226
-	 * @param string $userId id of the user
227
-	 * @param string $app if of the app
228
-	 * @param string $key config key
229
-	 *
230
-	 * @return bool TRUE if config is lazy loaded
231
-	 * @throws UnknownKeyException if config key is not known
232
-	 * @see IUserConfig for details about lazy loading
233
-	 * @since 31.0.0
234
-	 */
235
-	public function isLazy(string $userId, string $app, string $key): bool {
236
-		$this->matchAndApplyLexiconDefinition($userId, $app, $key);
237
-
238
-		// there is a huge probability the non-lazy config are already loaded
239
-		// meaning that we can start by only checking if a current non-lazy key exists
240
-		if ($this->hasKey($userId, $app, $key, false)) {
241
-			// meaning key is not lazy.
242
-			return false;
243
-		}
244
-
245
-		// as key is not found as non-lazy, we load and search in the lazy config
246
-		if ($this->hasKey($userId, $app, $key, true)) {
247
-			return true;
248
-		}
249
-
250
-		throw new UnknownKeyException('unknown config key');
251
-	}
252
-
253
-	/**
254
-	 * @inheritDoc
255
-	 *
256
-	 * @param string $userId id of the user
257
-	 * @param string $app id of the app
258
-	 * @param string $prefix config keys prefix to search
259
-	 * @param bool $filtered TRUE to hide sensitive config values. Value are replaced by {@see IConfig::SENSITIVE_VALUE}
260
-	 *
261
-	 * @return array<string, string|int|float|bool|array> [key => value]
262
-	 * @since 31.0.0
263
-	 */
264
-	public function getValues(
265
-		string $userId,
266
-		string $app,
267
-		string $prefix = '',
268
-		bool $filtered = false,
269
-	): array {
270
-		$this->assertParams($userId, $app, $prefix);
271
-		// if we want to filter values, we need to get sensitivity
272
-		$this->loadConfigAll($userId);
273
-		// array_merge() will remove numeric keys (here config keys), so addition arrays instead
274
-		$values = array_filter(
275
-			$this->formatAppValues($userId, $app, ($this->fastCache[$userId][$app] ?? []) + ($this->lazyCache[$userId][$app] ?? []), $filtered),
276
-			function (string $key) use ($prefix): bool {
277
-				// filter values based on $prefix
278
-				return str_starts_with($key, $prefix);
279
-			}, ARRAY_FILTER_USE_KEY
280
-		);
281
-
282
-		return $values;
283
-	}
284
-
285
-	/**
286
-	 * @inheritDoc
287
-	 *
288
-	 * @param string $userId id of the user
289
-	 * @param bool $filtered TRUE to hide sensitive config values. Value are replaced by {@see IConfig::SENSITIVE_VALUE}
290
-	 *
291
-	 * @return array<string, array<string, string|int|float|bool|array>> [appId => [key => value]]
292
-	 * @since 31.0.0
293
-	 */
294
-	public function getAllValues(string $userId, bool $filtered = false): array {
295
-		$this->assertParams($userId, allowEmptyApp: true);
296
-		$this->loadConfigAll($userId);
297
-
298
-		$result = [];
299
-		foreach ($this->getApps($userId) as $app) {
300
-			// array_merge() will remove numeric keys (here config keys), so addition arrays instead
301
-			$cached = ($this->fastCache[$userId][$app] ?? []) + ($this->lazyCache[$userId][$app] ?? []);
302
-			$result[$app] = $this->formatAppValues($userId, $app, $cached, $filtered);
303
-		}
304
-
305
-		return $result;
306
-	}
307
-
308
-	/**
309
-	 * @inheritDoc
310
-	 *
311
-	 * @param string $userId id of the user
312
-	 * @param string $key config key
313
-	 * @param bool $lazy search within lazy loaded config
314
-	 * @param ValueType|null $typedAs enforce type for the returned values
315
-	 *
316
-	 * @return array<string, string|int|float|bool|array> [appId => value]
317
-	 * @since 31.0.0
318
-	 */
319
-	public function getValuesByApps(string $userId, string $key, bool $lazy = false, ?ValueType $typedAs = null): array {
320
-		$this->assertParams($userId, '', $key, allowEmptyApp: true);
321
-		$this->loadConfig($userId, $lazy);
322
-
323
-		/** @var array<array-key, array<array-key, mixed>> $cache */
324
-		if ($lazy) {
325
-			$cache = $this->lazyCache[$userId];
326
-		} else {
327
-			$cache = $this->fastCache[$userId];
328
-		}
329
-
330
-		$values = [];
331
-		foreach (array_keys($cache) as $app) {
332
-			if (isset($cache[$app][$key])) {
333
-				$value = $cache[$app][$key];
334
-				try {
335
-					$this->decryptSensitiveValue($userId, $app, $key, $value);
336
-					$value = $this->convertTypedValue($value, $typedAs ?? $this->getValueType($userId, $app, $key, $lazy));
337
-				} catch (IncorrectTypeException|UnknownKeyException) {
338
-				}
339
-				$values[$app] = $value;
340
-			}
341
-		}
342
-
343
-		return $values;
344
-	}
345
-
346
-
347
-	/**
348
-	 * @inheritDoc
349
-	 *
350
-	 * @param string $app id of the app
351
-	 * @param string $key config key
352
-	 * @param ValueType|null $typedAs enforce type for the returned values
353
-	 * @param array|null $userIds limit to a list of user ids
354
-	 *
355
-	 * @return array<string, string|int|float|bool|array> [userId => value]
356
-	 * @since 31.0.0
357
-	 */
358
-	public function getValuesByUsers(
359
-		string $app,
360
-		string $key,
361
-		?ValueType $typedAs = null,
362
-		?array $userIds = null,
363
-	): array {
364
-		$this->assertParams('', $app, $key, allowEmptyUser: true);
365
-		$this->matchAndApplyLexiconDefinition('', $app, $key);
366
-
367
-		$qb = $this->connection->getQueryBuilder();
368
-		$qb->select('userid', 'configvalue', 'type')
369
-			->from('preferences')
370
-			->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)))
371
-			->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)));
372
-
373
-		$values = [];
374
-		// this nested function will execute current Query and store result within $values.
375
-		$executeAndStoreValue = function (IQueryBuilder $qb) use (&$values, $typedAs): IResult {
376
-			$result = $qb->executeQuery();
377
-			while ($row = $result->fetch()) {
378
-				$value = $row['configvalue'];
379
-				try {
380
-					$value = $this->convertTypedValue($value, $typedAs ?? ValueType::from((int)$row['type']));
381
-				} catch (IncorrectTypeException) {
382
-				}
383
-				$values[$row['userid']] = $value;
384
-			}
385
-			return $result;
386
-		};
387
-
388
-		// if no userIds to filter, we execute query as it is and returns all values ...
389
-		if ($userIds === null) {
390
-			$result = $executeAndStoreValue($qb);
391
-			$result->closeCursor();
392
-			return $values;
393
-		}
394
-
395
-		// if userIds to filter, we chunk the list and execute the same query multiple times until we get all values
396
-		$result = null;
397
-		$qb->andWhere($qb->expr()->in('userid', $qb->createParameter('userIds')));
398
-		foreach (array_chunk($userIds, 50, true) as $chunk) {
399
-			$qb->setParameter('userIds', $chunk, IQueryBuilder::PARAM_STR_ARRAY);
400
-			$result = $executeAndStoreValue($qb);
401
-		}
402
-		$result?->closeCursor();
403
-
404
-		return $values;
405
-	}
406
-
407
-	/**
408
-	 * @inheritDoc
409
-	 *
410
-	 * @param string $app id of the app
411
-	 * @param string $key config key
412
-	 * @param string $value config value
413
-	 * @param bool $caseInsensitive non-case-sensitive search, only works if $value is a string
414
-	 *
415
-	 * @return Generator<string>
416
-	 * @since 31.0.0
417
-	 */
418
-	public function searchUsersByValueString(string $app, string $key, string $value, bool $caseInsensitive = false): Generator {
419
-		return $this->searchUsersByTypedValue($app, $key, $value, $caseInsensitive);
420
-	}
421
-
422
-	/**
423
-	 * @inheritDoc
424
-	 *
425
-	 * @param string $app id of the app
426
-	 * @param string $key config key
427
-	 * @param int $value config value
428
-	 *
429
-	 * @return Generator<string>
430
-	 * @since 31.0.0
431
-	 */
432
-	public function searchUsersByValueInt(string $app, string $key, int $value): Generator {
433
-		return $this->searchUsersByValueString($app, $key, (string)$value);
434
-	}
435
-
436
-	/**
437
-	 * @inheritDoc
438
-	 *
439
-	 * @param string $app id of the app
440
-	 * @param string $key config key
441
-	 * @param array $values list of config values
442
-	 *
443
-	 * @return Generator<string>
444
-	 * @since 31.0.0
445
-	 */
446
-	public function searchUsersByValues(string $app, string $key, array $values): Generator {
447
-		return $this->searchUsersByTypedValue($app, $key, $values);
448
-	}
449
-
450
-	/**
451
-	 * @inheritDoc
452
-	 *
453
-	 * @param string $app id of the app
454
-	 * @param string $key config key
455
-	 * @param bool $value config value
456
-	 *
457
-	 * @return Generator<string>
458
-	 * @since 31.0.0
459
-	 */
460
-	public function searchUsersByValueBool(string $app, string $key, bool $value): Generator {
461
-		$values = ['0', 'off', 'false', 'no'];
462
-		if ($value) {
463
-			$values = ['1', 'on', 'true', 'yes'];
464
-		}
465
-		return $this->searchUsersByValues($app, $key, $values);
466
-	}
467
-
468
-	/**
469
-	 * returns a list of users with config key set to a specific value, or within the list of
470
-	 * possible values
471
-	 *
472
-	 * @param string $app
473
-	 * @param string $key
474
-	 * @param string|array $value
475
-	 * @param bool $caseInsensitive
476
-	 *
477
-	 * @return Generator<string>
478
-	 */
479
-	private function searchUsersByTypedValue(string $app, string $key, string|array $value, bool $caseInsensitive = false): Generator {
480
-		$this->assertParams('', $app, $key, allowEmptyUser: true);
481
-		$this->matchAndApplyLexiconDefinition('', $app, $key);
482
-
483
-		$lexiconEntry = $this->getLexiconEntry($app, $key);
484
-		if ($lexiconEntry?->isFlagged(self::FLAG_INDEXED) === false) {
485
-			$this->logger->notice('UserConfig+Lexicon: using searchUsersByTypedValue on config key ' . $app . '/' . $key . ' which is not set as indexed');
486
-		}
487
-
488
-		$qb = $this->connection->getQueryBuilder();
489
-		$qb->from('preferences');
490
-		$qb->select('userid');
491
-		$qb->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)));
492
-		$qb->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)));
493
-
494
-		$configValueColumn = ($this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_ORACLE) ? $qb->expr()->castColumn('configvalue', IQueryBuilder::PARAM_STR) : 'configvalue';
495
-		if (is_array($value)) {
496
-			$where = $qb->expr()->in('indexed', $qb->createNamedParameter($value, IQueryBuilder::PARAM_STR_ARRAY));
497
-			// in case lexicon does not exist for this key - or is not set as indexed - we keep searching for non-index entries if 'flags' is set as not indexed
498
-			if ($lexiconEntry?->isFlagged(self::FLAG_INDEXED) !== true) {
499
-				$where = $qb->expr()->orX(
500
-					$where,
501
-					$qb->expr()->andX(
502
-						$qb->expr()->neq($qb->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), $qb->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)),
503
-						$qb->expr()->in($configValueColumn, $qb->createNamedParameter($value, IQueryBuilder::PARAM_STR_ARRAY))
504
-					)
505
-				);
506
-			}
507
-		} else {
508
-			if ($caseInsensitive) {
509
-				$where = $qb->expr()->eq($qb->func()->lower('indexed'), $qb->createNamedParameter(strtolower($value)));
510
-				// in case lexicon does not exist for this key - or is not set as indexed - we keep searching for non-index entries if 'flags' is set as not indexed
511
-				if ($lexiconEntry?->isFlagged(self::FLAG_INDEXED) !== true) {
512
-					$where = $qb->expr()->orX(
513
-						$where,
514
-						$qb->expr()->andX(
515
-							$qb->expr()->neq($qb->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), $qb->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)),
516
-							$qb->expr()->eq($qb->func()->lower($configValueColumn), $qb->createNamedParameter(strtolower($value)))
517
-						)
518
-					);
519
-				}
520
-			} else {
521
-				$where = $qb->expr()->eq('indexed', $qb->createNamedParameter($value));
522
-				// in case lexicon does not exist for this key - or is not set as indexed - we keep searching for non-index entries if 'flags' is set as not indexed
523
-				if ($lexiconEntry?->isFlagged(self::FLAG_INDEXED) !== true) {
524
-					$where = $qb->expr()->orX(
525
-						$where,
526
-						$qb->expr()->andX(
527
-							$qb->expr()->neq($qb->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), $qb->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)),
528
-							$qb->expr()->eq($configValueColumn, $qb->createNamedParameter($value))
529
-						)
530
-					);
531
-				}
532
-			}
533
-		}
534
-
535
-		$qb->andWhere($where);
536
-		$result = $qb->executeQuery();
537
-		while ($row = $result->fetch()) {
538
-			yield $row['userid'];
539
-		}
540
-	}
541
-
542
-	/**
543
-	 * Get the config value as string.
544
-	 * If the value does not exist the given default will be returned.
545
-	 *
546
-	 * Set lazy to `null` to ignore it and get the value from either source.
547
-	 *
548
-	 * **WARNING:** Method is internal and **SHOULD** not be used, as it is better to get the value with a type.
549
-	 *
550
-	 * @param string $userId id of the user
551
-	 * @param string $app id of the app
552
-	 * @param string $key config key
553
-	 * @param string $default config value
554
-	 * @param null|bool $lazy get config as lazy loaded or not. can be NULL
555
-	 *
556
-	 * @return string the value or $default
557
-	 * @throws TypeConflictException
558
-	 * @internal
559
-	 * @since 31.0.0
560
-	 * @see IUserConfig for explanation about lazy loading
561
-	 * @see getValueString()
562
-	 * @see getValueInt()
563
-	 * @see getValueFloat()
564
-	 * @see getValueBool()
565
-	 * @see getValueArray()
566
-	 */
567
-	public function getValueMixed(
568
-		string $userId,
569
-		string $app,
570
-		string $key,
571
-		string $default = '',
572
-		?bool $lazy = false,
573
-	): string {
574
-		$this->matchAndApplyLexiconDefinition($userId, $app, $key);
575
-		try {
576
-			$lazy ??= $this->isLazy($userId, $app, $key);
577
-		} catch (UnknownKeyException) {
578
-			return $default;
579
-		}
580
-
581
-		return $this->getTypedValue(
582
-			$userId,
583
-			$app,
584
-			$key,
585
-			$default,
586
-			$lazy,
587
-			ValueType::MIXED
588
-		);
589
-	}
590
-
591
-	/**
592
-	 * @inheritDoc
593
-	 *
594
-	 * @param string $userId id of the user
595
-	 * @param string $app id of the app
596
-	 * @param string $key config key
597
-	 * @param string $default default value
598
-	 * @param bool $lazy search within lazy loaded config
599
-	 *
600
-	 * @return string stored config value or $default if not set in database
601
-	 * @throws InvalidArgumentException if one of the argument format is invalid
602
-	 * @throws TypeConflictException in case of conflict with the value type set in database
603
-	 * @since 31.0.0
604
-	 * @see IUserConfig for explanation about lazy loading
605
-	 */
606
-	public function getValueString(
607
-		string $userId,
608
-		string $app,
609
-		string $key,
610
-		string $default = '',
611
-		bool $lazy = false,
612
-	): string {
613
-		return $this->getTypedValue($userId, $app, $key, $default, $lazy, ValueType::STRING);
614
-	}
615
-
616
-	/**
617
-	 * @inheritDoc
618
-	 *
619
-	 * @param string $userId id of the user
620
-	 * @param string $app id of the app
621
-	 * @param string $key config key
622
-	 * @param int $default default value
623
-	 * @param bool $lazy search within lazy loaded config
624
-	 *
625
-	 * @return int stored config value or $default if not set in database
626
-	 * @throws InvalidArgumentException if one of the argument format is invalid
627
-	 * @throws TypeConflictException in case of conflict with the value type set in database
628
-	 * @since 31.0.0
629
-	 * @see IUserConfig for explanation about lazy loading
630
-	 */
631
-	public function getValueInt(
632
-		string $userId,
633
-		string $app,
634
-		string $key,
635
-		int $default = 0,
636
-		bool $lazy = false,
637
-	): int {
638
-		return (int)$this->getTypedValue($userId, $app, $key, (string)$default, $lazy, ValueType::INT);
639
-	}
640
-
641
-	/**
642
-	 * @inheritDoc
643
-	 *
644
-	 * @param string $userId id of the user
645
-	 * @param string $app id of the app
646
-	 * @param string $key config key
647
-	 * @param float $default default value
648
-	 * @param bool $lazy search within lazy loaded config
649
-	 *
650
-	 * @return float stored config value or $default if not set in database
651
-	 * @throws InvalidArgumentException if one of the argument format is invalid
652
-	 * @throws TypeConflictException in case of conflict with the value type set in database
653
-	 * @since 31.0.0
654
-	 * @see IUserConfig for explanation about lazy loading
655
-	 */
656
-	public function getValueFloat(
657
-		string $userId,
658
-		string $app,
659
-		string $key,
660
-		float $default = 0,
661
-		bool $lazy = false,
662
-	): float {
663
-		return (float)$this->getTypedValue($userId, $app, $key, (string)$default, $lazy, ValueType::FLOAT);
664
-	}
665
-
666
-	/**
667
-	 * @inheritDoc
668
-	 *
669
-	 * @param string $userId id of the user
670
-	 * @param string $app id of the app
671
-	 * @param string $key config key
672
-	 * @param bool $default default value
673
-	 * @param bool $lazy search within lazy loaded config
674
-	 *
675
-	 * @return bool stored config value or $default if not set in database
676
-	 * @throws InvalidArgumentException if one of the argument format is invalid
677
-	 * @throws TypeConflictException in case of conflict with the value type set in database
678
-	 * @since 31.0.0
679
-	 * @see IUserConfig for explanation about lazy loading
680
-	 */
681
-	public function getValueBool(
682
-		string $userId,
683
-		string $app,
684
-		string $key,
685
-		bool $default = false,
686
-		bool $lazy = false,
687
-	): bool {
688
-		$b = strtolower($this->getTypedValue($userId, $app, $key, $default ? 'true' : 'false', $lazy, ValueType::BOOL));
689
-		return in_array($b, ['1', 'true', 'yes', 'on']);
690
-	}
691
-
692
-	/**
693
-	 * @inheritDoc
694
-	 *
695
-	 * @param string $userId id of the user
696
-	 * @param string $app id of the app
697
-	 * @param string $key config key
698
-	 * @param array $default default value
699
-	 * @param bool $lazy search within lazy loaded config
700
-	 *
701
-	 * @return array stored config value or $default if not set in database
702
-	 * @throws InvalidArgumentException if one of the argument format is invalid
703
-	 * @throws TypeConflictException in case of conflict with the value type set in database
704
-	 * @since 31.0.0
705
-	 * @see IUserConfig for explanation about lazy loading
706
-	 */
707
-	public function getValueArray(
708
-		string $userId,
709
-		string $app,
710
-		string $key,
711
-		array $default = [],
712
-		bool $lazy = false,
713
-	): array {
714
-		try {
715
-			$defaultJson = json_encode($default, JSON_THROW_ON_ERROR);
716
-			$value = json_decode($this->getTypedValue($userId, $app, $key, $defaultJson, $lazy, ValueType::ARRAY), true, flags: JSON_THROW_ON_ERROR);
717
-
718
-			return is_array($value) ? $value : [];
719
-		} catch (JsonException) {
720
-			return [];
721
-		}
722
-	}
723
-
724
-	/**
725
-	 * @param string $userId
726
-	 * @param string $app id of the app
727
-	 * @param string $key config key
728
-	 * @param string $default default value
729
-	 * @param bool $lazy search within lazy loaded config
730
-	 * @param ValueType $type value type
731
-	 *
732
-	 * @return string
733
-	 * @throws TypeConflictException if type from database is not VALUE_MIXED and different from the requested one
734
-	 */
735
-	private function getTypedValue(
736
-		string $userId,
737
-		string $app,
738
-		string $key,
739
-		string $default,
740
-		bool $lazy,
741
-		ValueType $type,
742
-	): string {
743
-		$this->assertParams($userId, $app, $key);
744
-		$origKey = $key;
745
-		$matched = $this->matchAndApplyLexiconDefinition($userId, $app, $key, $lazy, $type, default: $default);
746
-		if ($default === null) {
747
-			// there is no logical reason for it to be null
748
-			throw new \Exception('default cannot be null');
749
-		}
750
-
751
-		// returns default if strictness of lexicon is set to WARNING (block and report)
752
-		if (!$matched) {
753
-			return $default;
754
-		}
755
-
756
-		$this->loadConfig($userId, $lazy);
757
-
758
-		/**
759
-		 * We ignore check if mixed type is requested.
760
-		 * If type of stored value is set as mixed, we don't filter.
761
-		 * If type of stored value is defined, we compare with the one requested.
762
-		 */
763
-		$knownType = $this->valueDetails[$userId][$app][$key]['type'] ?? null;
764
-		if ($type !== ValueType::MIXED
765
-			&& $knownType !== null
766
-			&& $knownType !== ValueType::MIXED
767
-			&& $type !== $knownType) {
768
-			$this->logger->warning('conflict with value type from database', ['app' => $app, 'key' => $key, 'type' => $type, 'knownType' => $knownType]);
769
-			throw new TypeConflictException('conflict with value type from database');
770
-		}
771
-
772
-		/**
773
-		 * - the pair $app/$key cannot exist in both array,
774
-		 * - we should still return an existing non-lazy value even if current method
775
-		 *   is called with $lazy is true
776
-		 *
777
-		 * This way, lazyCache will be empty until the load for lazy config value is requested.
778
-		 */
779
-		if (isset($this->lazyCache[$userId][$app][$key])) {
780
-			$value = $this->lazyCache[$userId][$app][$key];
781
-		} elseif (isset($this->fastCache[$userId][$app][$key])) {
782
-			$value = $this->fastCache[$userId][$app][$key];
783
-		} else {
784
-			return $default;
785
-		}
786
-
787
-		$this->decryptSensitiveValue($userId, $app, $key, $value);
788
-
789
-		// in case the key was modified while running matchAndApplyLexiconDefinition() we are
790
-		// interested to check options in case a modification of the value is needed
791
-		// ie inverting value from previous key when using lexicon option RENAME_INVERT_BOOLEAN
792
-		if ($origKey !== $key && $type === ValueType::BOOL) {
793
-			$value = ($this->configManager->convertToBool($value, $this->getLexiconEntry($app, $key))) ? '1' : '0';
794
-		}
795
-
796
-		return $value;
797
-	}
798
-
799
-	/**
800
-	 * @inheritDoc
801
-	 *
802
-	 * @param string $userId id of the user
803
-	 * @param string $app id of the app
804
-	 * @param string $key config key
805
-	 *
806
-	 * @return ValueType type of the value
807
-	 * @throws UnknownKeyException if config key is not known
808
-	 * @throws IncorrectTypeException if config value type is not known
809
-	 * @since 31.0.0
810
-	 */
811
-	public function getValueType(string $userId, string $app, string $key, ?bool $lazy = null): ValueType {
812
-		$this->assertParams($userId, $app, $key);
813
-		$this->loadConfig($userId, $lazy);
814
-		$this->matchAndApplyLexiconDefinition($userId, $app, $key);
815
-
816
-		if (!isset($this->valueDetails[$userId][$app][$key]['type'])) {
817
-			throw new UnknownKeyException('unknown config key');
818
-		}
819
-
820
-		return $this->valueDetails[$userId][$app][$key]['type'];
821
-	}
822
-
823
-	/**
824
-	 * @inheritDoc
825
-	 *
826
-	 * @param string $userId id of the user
827
-	 * @param string $app id of the app
828
-	 * @param string $key config key
829
-	 * @param bool $lazy lazy loading
830
-	 *
831
-	 * @return int flags applied to value
832
-	 * @throws UnknownKeyException if config key is not known
833
-	 * @throws IncorrectTypeException if config value type is not known
834
-	 * @since 31.0.0
835
-	 */
836
-	public function getValueFlags(string $userId, string $app, string $key, bool $lazy = false): int {
837
-		$this->assertParams($userId, $app, $key);
838
-		$this->loadConfig($userId, $lazy);
839
-		$this->matchAndApplyLexiconDefinition($userId, $app, $key);
840
-
841
-		if (!isset($this->valueDetails[$userId][$app][$key])) {
842
-			throw new UnknownKeyException('unknown config key');
843
-		}
844
-
845
-		return $this->valueDetails[$userId][$app][$key]['flags'];
846
-	}
847
-
848
-	/**
849
-	 * Store a config key and its value in database as VALUE_MIXED
850
-	 *
851
-	 * **WARNING:** Method is internal and **MUST** not be used as it is best to set a real value type
852
-	 *
853
-	 * @param string $userId id of the user
854
-	 * @param string $app id of the app
855
-	 * @param string $key config key
856
-	 * @param string $value config value
857
-	 * @param bool $lazy set config as lazy loaded
858
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
859
-	 *
860
-	 * @return bool TRUE if value was different, therefor updated in database
861
-	 * @throws TypeConflictException if type from database is not VALUE_MIXED
862
-	 * @internal
863
-	 * @since 31.0.0
864
-	 * @see IUserConfig for explanation about lazy loading
865
-	 * @see setValueString()
866
-	 * @see setValueInt()
867
-	 * @see setValueFloat()
868
-	 * @see setValueBool()
869
-	 * @see setValueArray()
870
-	 */
871
-	public function setValueMixed(
872
-		string $userId,
873
-		string $app,
874
-		string $key,
875
-		string $value,
876
-		bool $lazy = false,
877
-		int $flags = 0,
878
-	): bool {
879
-		return $this->setTypedValue(
880
-			$userId,
881
-			$app,
882
-			$key,
883
-			$value,
884
-			$lazy,
885
-			$flags,
886
-			ValueType::MIXED
887
-		);
888
-	}
889
-
890
-
891
-	/**
892
-	 * @inheritDoc
893
-	 *
894
-	 * @param string $userId id of the user
895
-	 * @param string $app id of the app
896
-	 * @param string $key config key
897
-	 * @param string $value config value
898
-	 * @param bool $lazy set config as lazy loaded
899
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
900
-	 *
901
-	 * @return bool TRUE if value was different, therefor updated in database
902
-	 * @throws TypeConflictException if type from database is not VALUE_MIXED and different from the requested one
903
-	 * @since 31.0.0
904
-	 * @see IUserConfig for explanation about lazy loading
905
-	 */
906
-	public function setValueString(
907
-		string $userId,
908
-		string $app,
909
-		string $key,
910
-		string $value,
911
-		bool $lazy = false,
912
-		int $flags = 0,
913
-	): bool {
914
-		return $this->setTypedValue(
915
-			$userId,
916
-			$app,
917
-			$key,
918
-			$value,
919
-			$lazy,
920
-			$flags,
921
-			ValueType::STRING
922
-		);
923
-	}
924
-
925
-	/**
926
-	 * @inheritDoc
927
-	 *
928
-	 * @param string $userId id of the user
929
-	 * @param string $app id of the app
930
-	 * @param string $key config key
931
-	 * @param int $value config value
932
-	 * @param bool $lazy set config as lazy loaded
933
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
934
-	 *
935
-	 * @return bool TRUE if value was different, therefor updated in database
936
-	 * @throws TypeConflictException if type from database is not VALUE_MIXED and different from the requested one
937
-	 * @since 31.0.0
938
-	 * @see IUserConfig for explanation about lazy loading
939
-	 */
940
-	public function setValueInt(
941
-		string $userId,
942
-		string $app,
943
-		string $key,
944
-		int $value,
945
-		bool $lazy = false,
946
-		int $flags = 0,
947
-	): bool {
948
-		if ($value > 2000000000) {
949
-			$this->logger->debug('You are trying to store an integer value around/above 2,147,483,647. This is a reminder that reaching this theoretical limit on 32 bits system will throw an exception.');
950
-		}
951
-
952
-		return $this->setTypedValue(
953
-			$userId,
954
-			$app,
955
-			$key,
956
-			(string)$value,
957
-			$lazy,
958
-			$flags,
959
-			ValueType::INT
960
-		);
961
-	}
962
-
963
-	/**
964
-	 * @inheritDoc
965
-	 *
966
-	 * @param string $userId id of the user
967
-	 * @param string $app id of the app
968
-	 * @param string $key config key
969
-	 * @param float $value config value
970
-	 * @param bool $lazy set config as lazy loaded
971
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
972
-	 *
973
-	 * @return bool TRUE if value was different, therefor updated in database
974
-	 * @throws TypeConflictException if type from database is not VALUE_MIXED and different from the requested one
975
-	 * @since 31.0.0
976
-	 * @see IUserConfig for explanation about lazy loading
977
-	 */
978
-	public function setValueFloat(
979
-		string $userId,
980
-		string $app,
981
-		string $key,
982
-		float $value,
983
-		bool $lazy = false,
984
-		int $flags = 0,
985
-	): bool {
986
-		return $this->setTypedValue(
987
-			$userId,
988
-			$app,
989
-			$key,
990
-			(string)$value,
991
-			$lazy,
992
-			$flags,
993
-			ValueType::FLOAT
994
-		);
995
-	}
996
-
997
-	/**
998
-	 * @inheritDoc
999
-	 *
1000
-	 * @param string $userId id of the user
1001
-	 * @param string $app id of the app
1002
-	 * @param string $key config key
1003
-	 * @param bool $value config value
1004
-	 * @param bool $lazy set config as lazy loaded
1005
-	 *
1006
-	 * @return bool TRUE if value was different, therefor updated in database
1007
-	 * @throws TypeConflictException if type from database is not VALUE_MIXED and different from the requested one
1008
-	 * @since 31.0.0
1009
-	 * @see IUserConfig for explanation about lazy loading
1010
-	 */
1011
-	public function setValueBool(
1012
-		string $userId,
1013
-		string $app,
1014
-		string $key,
1015
-		bool $value,
1016
-		bool $lazy = false,
1017
-		int $flags = 0,
1018
-	): bool {
1019
-		return $this->setTypedValue(
1020
-			$userId,
1021
-			$app,
1022
-			$key,
1023
-			($value) ? '1' : '0',
1024
-			$lazy,
1025
-			$flags,
1026
-			ValueType::BOOL
1027
-		);
1028
-	}
1029
-
1030
-	/**
1031
-	 * @inheritDoc
1032
-	 *
1033
-	 * @param string $userId id of the user
1034
-	 * @param string $app id of the app
1035
-	 * @param string $key config key
1036
-	 * @param array $value config value
1037
-	 * @param bool $lazy set config as lazy loaded
1038
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
1039
-	 *
1040
-	 * @return bool TRUE if value was different, therefor updated in database
1041
-	 * @throws TypeConflictException if type from database is not VALUE_MIXED and different from the requested one
1042
-	 * @throws JsonException
1043
-	 * @since 31.0.0
1044
-	 * @see IUserConfig for explanation about lazy loading
1045
-	 */
1046
-	public function setValueArray(
1047
-		string $userId,
1048
-		string $app,
1049
-		string $key,
1050
-		array $value,
1051
-		bool $lazy = false,
1052
-		int $flags = 0,
1053
-	): bool {
1054
-		try {
1055
-			return $this->setTypedValue(
1056
-				$userId,
1057
-				$app,
1058
-				$key,
1059
-				json_encode($value, JSON_THROW_ON_ERROR),
1060
-				$lazy,
1061
-				$flags,
1062
-				ValueType::ARRAY
1063
-			);
1064
-		} catch (JsonException $e) {
1065
-			$this->logger->warning('could not setValueArray', ['app' => $app, 'key' => $key, 'exception' => $e]);
1066
-			throw $e;
1067
-		}
1068
-	}
1069
-
1070
-	/**
1071
-	 * Store a config key and its value in database
1072
-	 *
1073
-	 * If config key is already known with the exact same config value and same sensitive/lazy status, the
1074
-	 * database is not updated. If config value was previously stored as sensitive, status will not be
1075
-	 * altered.
1076
-	 *
1077
-	 * @param string $userId id of the user
1078
-	 * @param string $app id of the app
1079
-	 * @param string $key config key
1080
-	 * @param string $value config value
1081
-	 * @param bool $lazy config set as lazy loaded
1082
-	 * @param ValueType $type value type
1083
-	 *
1084
-	 * @return bool TRUE if value was updated in database
1085
-	 * @throws TypeConflictException if type from database is not VALUE_MIXED and different from the requested one
1086
-	 * @see IUserConfig for explanation about lazy loading
1087
-	 */
1088
-	private function setTypedValue(
1089
-		string $userId,
1090
-		string $app,
1091
-		string $key,
1092
-		string $value,
1093
-		bool $lazy,
1094
-		int $flags,
1095
-		ValueType $type,
1096
-	): bool {
1097
-		// Primary email addresses are always(!) expected to be lowercase
1098
-		if ($app === 'settings' && $key === 'email') {
1099
-			$value = strtolower($value);
1100
-		}
1101
-
1102
-		$this->assertParams($userId, $app, $key);
1103
-		if (!$this->matchAndApplyLexiconDefinition($userId, $app, $key, $lazy, $type, $flags)) {
1104
-			// returns false as database is not updated
1105
-			return false;
1106
-		}
1107
-		$this->loadConfig($userId, $lazy);
1108
-
1109
-		$inserted = $refreshCache = false;
1110
-		$origValue = $value;
1111
-		$sensitive = $this->isFlagged(self::FLAG_SENSITIVE, $flags);
1112
-		if ($sensitive || ($this->hasKey($userId, $app, $key, $lazy) && $this->isSensitive($userId, $app, $key, $lazy))) {
1113
-			$value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
1114
-			$flags |= self::FLAG_SENSITIVE;
1115
-		}
1116
-
1117
-		// if requested, we fill the 'indexed' field with current value
1118
-		$indexed = '';
1119
-		if ($type !== ValueType::ARRAY && $this->isFlagged(self::FLAG_INDEXED, $flags)) {
1120
-			if ($this->isFlagged(self::FLAG_SENSITIVE, $flags)) {
1121
-				$this->logger->warning('sensitive value are not to be indexed');
1122
-			} elseif (strlen($value) > self::USER_MAX_LENGTH) {
1123
-				$this->logger->warning('value is too lengthy to be indexed');
1124
-			} else {
1125
-				$indexed = $value;
1126
-			}
1127
-		}
1128
-
1129
-		$oldValue = null;
1130
-		if ($this->hasKey($userId, $app, $key, $lazy)) {
1131
-			/**
1132
-			 * no update if key is already known with set lazy status and value is
1133
-			 * not different, unless sensitivity is switched from false to true.
1134
-			 */
1135
-			$oldValue = $this->getTypedValue($userId, $app, $key, $value, $lazy, $type);
1136
-			if ($origValue === $oldValue
1137
-				&& (!$sensitive || $this->isSensitive($userId, $app, $key, $lazy))) {
1138
-				return false;
1139
-			}
1140
-		} else {
1141
-			/**
1142
-			 * if key is not known yet, we try to insert.
1143
-			 * It might fail if the key exists with a different lazy flag.
1144
-			 */
1145
-			try {
1146
-				$insert = $this->connection->getQueryBuilder();
1147
-				$insert->insert('preferences')
1148
-					->setValue('userid', $insert->createNamedParameter($userId))
1149
-					->setValue('appid', $insert->createNamedParameter($app))
1150
-					->setValue('lazy', $insert->createNamedParameter(($lazy) ? 1 : 0, IQueryBuilder::PARAM_INT))
1151
-					->setValue('type', $insert->createNamedParameter($type->value, IQueryBuilder::PARAM_INT))
1152
-					->setValue('flags', $insert->createNamedParameter($flags, IQueryBuilder::PARAM_INT))
1153
-					->setValue('indexed', $insert->createNamedParameter($indexed))
1154
-					->setValue('configkey', $insert->createNamedParameter($key))
1155
-					->setValue('configvalue', $insert->createNamedParameter($value));
1156
-				$insert->executeStatement();
1157
-				$inserted = true;
1158
-			} catch (DBException $e) {
1159
-				if ($e->getReason() !== DBException::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
1160
-					// TODO: throw exception or just log and returns false !?
1161
-					throw $e;
1162
-				}
1163
-			}
1164
-		}
1165
-
1166
-		/**
1167
-		 * We cannot insert a new row, meaning we need to update an already existing one
1168
-		 */
1169
-		if (!$inserted) {
1170
-			$currType = $this->valueDetails[$userId][$app][$key]['type'] ?? null;
1171
-			if ($currType === null) { // this might happen when switching lazy loading status
1172
-				$this->loadConfigAll($userId);
1173
-				$currType = $this->valueDetails[$userId][$app][$key]['type'];
1174
-			}
1175
-
1176
-			/**
1177
-			 * We only log a warning and set it to VALUE_MIXED.
1178
-			 */
1179
-			if ($currType === null) {
1180
-				$this->logger->warning('Value type is set to zero (0) in database. This is not supposed to happens', ['app' => $app, 'key' => $key]);
1181
-				$currType = ValueType::MIXED;
1182
-			}
1183
-
1184
-			/**
1185
-			 * we only accept a different type from the one stored in database
1186
-			 * if the one stored in database is not-defined (VALUE_MIXED)
1187
-			 */
1188
-			if ($currType !== ValueType::MIXED
1189
-				&& $currType !== $type) {
1190
-				try {
1191
-					$currTypeDef = $currType->getDefinition();
1192
-					$typeDef = $type->getDefinition();
1193
-				} catch (IncorrectTypeException) {
1194
-					$currTypeDef = $currType->value;
1195
-					$typeDef = $type->value;
1196
-				}
1197
-				throw new TypeConflictException('conflict between new type (' . $typeDef . ') and old type (' . $currTypeDef . ')');
1198
-			}
1199
-
1200
-			if ($lazy !== $this->isLazy($userId, $app, $key)) {
1201
-				$refreshCache = true;
1202
-			}
1203
-
1204
-			$update = $this->connection->getQueryBuilder();
1205
-			$update->update('preferences')
1206
-				->set('configvalue', $update->createNamedParameter($value))
1207
-				->set('lazy', $update->createNamedParameter(($lazy) ? 1 : 0, IQueryBuilder::PARAM_INT))
1208
-				->set('type', $update->createNamedParameter($type->value, IQueryBuilder::PARAM_INT))
1209
-				->set('flags', $update->createNamedParameter($flags, IQueryBuilder::PARAM_INT))
1210
-				->set('indexed', $update->createNamedParameter($indexed))
1211
-				->where($update->expr()->eq('userid', $update->createNamedParameter($userId)))
1212
-				->andWhere($update->expr()->eq('appid', $update->createNamedParameter($app)))
1213
-				->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
1214
-
1215
-			$update->executeStatement();
1216
-		}
1217
-
1218
-		$this->dispatcher->dispatchTyped(new UserConfigChangedEvent($userId, $app, $key, $value, $oldValue));
1219
-
1220
-		if ($refreshCache) {
1221
-			$this->clearCache($userId);
1222
-			return true;
1223
-		}
1224
-
1225
-		// update local cache
1226
-		if ($lazy) {
1227
-			$this->lazyCache[$userId][$app][$key] = $value;
1228
-		} else {
1229
-			$this->fastCache[$userId][$app][$key] = $value;
1230
-		}
1231
-		$this->valueDetails[$userId][$app][$key] = [
1232
-			'type' => $type,
1233
-			'flags' => $flags
1234
-		];
1235
-
1236
-		return true;
1237
-	}
1238
-
1239
-	/**
1240
-	 * Change the type of config value.
1241
-	 *
1242
-	 * **WARNING:** Method is internal and **MUST** not be used as it may break things.
1243
-	 *
1244
-	 * @param string $userId id of the user
1245
-	 * @param string $app id of the app
1246
-	 * @param string $key config key
1247
-	 * @param ValueType $type value type
1248
-	 *
1249
-	 * @return bool TRUE if database update were necessary
1250
-	 * @throws UnknownKeyException if $key is now known in database
1251
-	 * @throws IncorrectTypeException if $type is not valid
1252
-	 * @internal
1253
-	 * @since 31.0.0
1254
-	 */
1255
-	public function updateType(string $userId, string $app, string $key, ValueType $type = ValueType::MIXED): bool {
1256
-		$this->assertParams($userId, $app, $key);
1257
-		$this->loadConfigAll($userId);
1258
-		$this->matchAndApplyLexiconDefinition($userId, $app, $key);
1259
-		$this->isLazy($userId, $app, $key); // confirm key exists
1260
-
1261
-		$update = $this->connection->getQueryBuilder();
1262
-		$update->update('preferences')
1263
-			->set('type', $update->createNamedParameter($type->value, IQueryBuilder::PARAM_INT))
1264
-			->where($update->expr()->eq('userid', $update->createNamedParameter($userId)))
1265
-			->andWhere($update->expr()->eq('appid', $update->createNamedParameter($app)))
1266
-			->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
1267
-		$update->executeStatement();
1268
-
1269
-		$this->valueDetails[$userId][$app][$key]['type'] = $type;
1270
-
1271
-		return true;
1272
-	}
1273
-
1274
-	/**
1275
-	 * @inheritDoc
1276
-	 *
1277
-	 * @param string $userId id of the user
1278
-	 * @param string $app id of the app
1279
-	 * @param string $key config key
1280
-	 * @param bool $sensitive TRUE to set as sensitive, FALSE to unset
1281
-	 *
1282
-	 * @return bool TRUE if entry was found in database and an update was necessary
1283
-	 * @since 31.0.0
1284
-	 */
1285
-	public function updateSensitive(string $userId, string $app, string $key, bool $sensitive): bool {
1286
-		$this->assertParams($userId, $app, $key);
1287
-		$this->loadConfigAll($userId);
1288
-		$this->matchAndApplyLexiconDefinition($userId, $app, $key);
1289
-
1290
-		try {
1291
-			if ($sensitive === $this->isSensitive($userId, $app, $key, null)) {
1292
-				return false;
1293
-			}
1294
-		} catch (UnknownKeyException) {
1295
-			return false;
1296
-		}
1297
-
1298
-		$lazy = $this->isLazy($userId, $app, $key);
1299
-		if ($lazy) {
1300
-			$cache = $this->lazyCache;
1301
-		} else {
1302
-			$cache = $this->fastCache;
1303
-		}
1304
-
1305
-		if (!isset($cache[$userId][$app][$key])) {
1306
-			throw new UnknownKeyException('unknown config key');
1307
-		}
1308
-
1309
-		$value = $cache[$userId][$app][$key];
1310
-		$flags = $this->getValueFlags($userId, $app, $key);
1311
-		if ($sensitive) {
1312
-			$flags |= self::FLAG_SENSITIVE;
1313
-			$value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
1314
-		} else {
1315
-			$flags &= ~self::FLAG_SENSITIVE;
1316
-			$this->decryptSensitiveValue($userId, $app, $key, $value);
1317
-		}
1318
-
1319
-		$update = $this->connection->getQueryBuilder();
1320
-		$update->update('preferences')
1321
-			->set('flags', $update->createNamedParameter($flags, IQueryBuilder::PARAM_INT))
1322
-			->set('configvalue', $update->createNamedParameter($value))
1323
-			->where($update->expr()->eq('userid', $update->createNamedParameter($userId)))
1324
-			->andWhere($update->expr()->eq('appid', $update->createNamedParameter($app)))
1325
-			->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
1326
-		$update->executeStatement();
1327
-
1328
-		$this->valueDetails[$userId][$app][$key]['flags'] = $flags;
1329
-
1330
-		return true;
1331
-	}
1332
-
1333
-	/**
1334
-	 * @inheritDoc
1335
-	 *
1336
-	 * @param string $app
1337
-	 * @param string $key
1338
-	 * @param bool $sensitive
1339
-	 *
1340
-	 * @since 31.0.0
1341
-	 */
1342
-	public function updateGlobalSensitive(string $app, string $key, bool $sensitive): void {
1343
-		$this->assertParams('', $app, $key, allowEmptyUser: true);
1344
-		$this->matchAndApplyLexiconDefinition('', $app, $key);
1345
-
1346
-		foreach (array_keys($this->getValuesByUsers($app, $key)) as $userId) {
1347
-			try {
1348
-				$this->updateSensitive($userId, $app, $key, $sensitive);
1349
-			} catch (UnknownKeyException) {
1350
-				// should not happen and can be ignored
1351
-			}
1352
-		}
1353
-
1354
-		// we clear all cache
1355
-		$this->clearCacheAll();
1356
-	}
1357
-
1358
-	/**
1359
-	 * @inheritDoc
1360
-	 *
1361
-	 * @param string $userId
1362
-	 * @param string $app
1363
-	 * @param string $key
1364
-	 * @param bool $indexed
1365
-	 *
1366
-	 * @return bool
1367
-	 * @throws DBException
1368
-	 * @throws IncorrectTypeException
1369
-	 * @throws UnknownKeyException
1370
-	 * @since 31.0.0
1371
-	 */
1372
-	public function updateIndexed(string $userId, string $app, string $key, bool $indexed): bool {
1373
-		$this->assertParams($userId, $app, $key);
1374
-		$this->loadConfigAll($userId);
1375
-		$this->matchAndApplyLexiconDefinition($userId, $app, $key);
1376
-
1377
-		try {
1378
-			if ($indexed === $this->isIndexed($userId, $app, $key, null)) {
1379
-				return false;
1380
-			}
1381
-		} catch (UnknownKeyException) {
1382
-			return false;
1383
-		}
1384
-
1385
-		$lazy = $this->isLazy($userId, $app, $key);
1386
-		if ($lazy) {
1387
-			$cache = $this->lazyCache;
1388
-		} else {
1389
-			$cache = $this->fastCache;
1390
-		}
1391
-
1392
-		if (!isset($cache[$userId][$app][$key])) {
1393
-			throw new UnknownKeyException('unknown config key');
1394
-		}
1395
-
1396
-		$value = $cache[$userId][$app][$key];
1397
-		$flags = $this->getValueFlags($userId, $app, $key);
1398
-		if ($indexed) {
1399
-			$indexed = $value;
1400
-		} else {
1401
-			$flags &= ~self::FLAG_INDEXED;
1402
-			$indexed = '';
1403
-		}
1404
-
1405
-		$update = $this->connection->getQueryBuilder();
1406
-		$update->update('preferences')
1407
-			->set('flags', $update->createNamedParameter($flags, IQueryBuilder::PARAM_INT))
1408
-			->set('indexed', $update->createNamedParameter($indexed))
1409
-			->where($update->expr()->eq('userid', $update->createNamedParameter($userId)))
1410
-			->andWhere($update->expr()->eq('appid', $update->createNamedParameter($app)))
1411
-			->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
1412
-		$update->executeStatement();
1413
-
1414
-		$this->valueDetails[$userId][$app][$key]['flags'] = $flags;
1415
-
1416
-		return true;
1417
-	}
1418
-
1419
-
1420
-	/**
1421
-	 * @inheritDoc
1422
-	 *
1423
-	 * @param string $app
1424
-	 * @param string $key
1425
-	 * @param bool $indexed
1426
-	 *
1427
-	 * @since 31.0.0
1428
-	 */
1429
-	public function updateGlobalIndexed(string $app, string $key, bool $indexed): void {
1430
-		$this->assertParams('', $app, $key, allowEmptyUser: true);
1431
-		$this->matchAndApplyLexiconDefinition('', $app, $key);
1432
-
1433
-		$update = $this->connection->getQueryBuilder();
1434
-		$update->update('preferences')
1435
-			->where(
1436
-				$update->expr()->eq('appid', $update->createNamedParameter($app)),
1437
-				$update->expr()->eq('configkey', $update->createNamedParameter($key))
1438
-			);
1439
-
1440
-		// switching flags 'indexed' on and off is about adding/removing the bit value on the correct entries
1441
-		if ($indexed) {
1442
-			$update->set('indexed', $update->func()->substring('configvalue', $update->createNamedParameter(1, IQueryBuilder::PARAM_INT), $update->createNamedParameter(64, IQueryBuilder::PARAM_INT)));
1443
-			$update->set('flags', $update->func()->add('flags', $update->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)));
1444
-			$update->andWhere(
1445
-				$update->expr()->neq($update->expr()->castColumn(
1446
-					$update->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), IQueryBuilder::PARAM_INT), $update->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)
1447
-				));
1448
-		} else {
1449
-			// emptying field 'indexed' if key is not set as indexed anymore
1450
-			$update->set('indexed', $update->createNamedParameter(''));
1451
-			$update->set('flags', $update->func()->subtract('flags', $update->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)));
1452
-			$update->andWhere(
1453
-				$update->expr()->eq($update->expr()->castColumn(
1454
-					$update->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), IQueryBuilder::PARAM_INT), $update->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)
1455
-				));
1456
-		}
1457
-
1458
-		$update->executeStatement();
1459
-
1460
-		// we clear all cache
1461
-		$this->clearCacheAll();
1462
-	}
1463
-
1464
-	/**
1465
-	 * @inheritDoc
1466
-	 *
1467
-	 * @param string $userId id of the user
1468
-	 * @param string $app id of the app
1469
-	 * @param string $key config key
1470
-	 * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset
1471
-	 *
1472
-	 * @return bool TRUE if entry was found in database and an update was necessary
1473
-	 * @since 31.0.0
1474
-	 */
1475
-	public function updateLazy(string $userId, string $app, string $key, bool $lazy): bool {
1476
-		$this->assertParams($userId, $app, $key);
1477
-		$this->loadConfigAll($userId);
1478
-		$this->matchAndApplyLexiconDefinition($userId, $app, $key);
1479
-
1480
-		try {
1481
-			if ($lazy === $this->isLazy($userId, $app, $key)) {
1482
-				return false;
1483
-			}
1484
-		} catch (UnknownKeyException) {
1485
-			return false;
1486
-		}
1487
-
1488
-		$update = $this->connection->getQueryBuilder();
1489
-		$update->update('preferences')
1490
-			->set('lazy', $update->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT))
1491
-			->where($update->expr()->eq('userid', $update->createNamedParameter($userId)))
1492
-			->andWhere($update->expr()->eq('appid', $update->createNamedParameter($app)))
1493
-			->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
1494
-		$update->executeStatement();
1495
-
1496
-		// At this point, it is a lot safer to clean cache
1497
-		$this->clearCache($userId);
1498
-
1499
-		return true;
1500
-	}
1501
-
1502
-	/**
1503
-	 * @inheritDoc
1504
-	 *
1505
-	 * @param string $app id of the app
1506
-	 * @param string $key config key
1507
-	 * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset
1508
-	 *
1509
-	 * @since 31.0.0
1510
-	 */
1511
-	public function updateGlobalLazy(string $app, string $key, bool $lazy): void {
1512
-		$this->assertParams('', $app, $key, allowEmptyUser: true);
1513
-		$this->matchAndApplyLexiconDefinition('', $app, $key);
1514
-
1515
-		$update = $this->connection->getQueryBuilder();
1516
-		$update->update('preferences')
1517
-			->set('lazy', $update->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT))
1518
-			->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
1519
-			->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
1520
-		$update->executeStatement();
1521
-
1522
-		$this->clearCacheAll();
1523
-	}
1524
-
1525
-	/**
1526
-	 * @inheritDoc
1527
-	 *
1528
-	 * @param string $userId id of the user
1529
-	 * @param string $app id of the app
1530
-	 * @param string $key config key
1531
-	 *
1532
-	 * @return array
1533
-	 * @throws UnknownKeyException if config key is not known in database
1534
-	 * @since 31.0.0
1535
-	 */
1536
-	public function getDetails(string $userId, string $app, string $key): array {
1537
-		$this->assertParams($userId, $app, $key);
1538
-		$this->loadConfigAll($userId);
1539
-		$this->matchAndApplyLexiconDefinition($userId, $app, $key);
1540
-
1541
-		$lazy = $this->isLazy($userId, $app, $key);
1542
-
1543
-		if ($lazy) {
1544
-			$cache = $this->lazyCache[$userId];
1545
-		} else {
1546
-			$cache = $this->fastCache[$userId];
1547
-		}
1548
-
1549
-		$type = $this->getValueType($userId, $app, $key);
1550
-		try {
1551
-			$typeString = $type->getDefinition();
1552
-		} catch (IncorrectTypeException $e) {
1553
-			$this->logger->warning('type stored in database is not correct', ['exception' => $e, 'type' => $type]);
1554
-			$typeString = (string)$type->value;
1555
-		}
1556
-
1557
-		if (!isset($cache[$app][$key])) {
1558
-			throw new UnknownKeyException('unknown config key');
1559
-		}
1560
-
1561
-		$value = $cache[$app][$key];
1562
-		$sensitive = $this->isSensitive($userId, $app, $key, null);
1563
-		$this->decryptSensitiveValue($userId, $app, $key, $value);
1564
-
1565
-		return [
1566
-			'userId' => $userId,
1567
-			'app' => $app,
1568
-			'key' => $key,
1569
-			'value' => $value,
1570
-			'type' => $type->value,
1571
-			'lazy' => $lazy,
1572
-			'typeString' => $typeString,
1573
-			'sensitive' => $sensitive
1574
-		];
1575
-	}
1576
-
1577
-	/**
1578
-	 * @inheritDoc
1579
-	 *
1580
-	 * @param string $userId id of the user
1581
-	 * @param string $app id of the app
1582
-	 * @param string $key config key
1583
-	 *
1584
-	 * @since 31.0.0
1585
-	 */
1586
-	public function deleteUserConfig(string $userId, string $app, string $key): void {
1587
-		$this->assertParams($userId, $app, $key);
1588
-		$this->matchAndApplyLexiconDefinition($userId, $app, $key);
1589
-
1590
-		$qb = $this->connection->getQueryBuilder();
1591
-		$qb->delete('preferences')
1592
-			->where($qb->expr()->eq('userid', $qb->createNamedParameter($userId)))
1593
-			->andWhere($qb->expr()->eq('appid', $qb->createNamedParameter($app)))
1594
-			->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)));
1595
-		$qb->executeStatement();
1596
-
1597
-		unset($this->lazyCache[$userId][$app][$key]);
1598
-		unset($this->fastCache[$userId][$app][$key]);
1599
-		unset($this->valueDetails[$userId][$app][$key]);
1600
-	}
1601
-
1602
-	/**
1603
-	 * @inheritDoc
1604
-	 *
1605
-	 * @param string $app id of the app
1606
-	 * @param string $key config key
1607
-	 *
1608
-	 * @since 31.0.0
1609
-	 */
1610
-	public function deleteKey(string $app, string $key): void {
1611
-		$this->assertParams('', $app, $key, allowEmptyUser: true);
1612
-		$this->matchAndApplyLexiconDefinition('', $app, $key);
1613
-
1614
-		$qb = $this->connection->getQueryBuilder();
1615
-		$qb->delete('preferences')
1616
-			->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)))
1617
-			->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)));
1618
-		$qb->executeStatement();
1619
-
1620
-		$this->clearCacheAll();
1621
-	}
1622
-
1623
-	/**
1624
-	 * @inheritDoc
1625
-	 *
1626
-	 * @param string $app id of the app
1627
-	 *
1628
-	 * @since 31.0.0
1629
-	 */
1630
-	public function deleteApp(string $app): void {
1631
-		$this->assertParams('', $app, allowEmptyUser: true);
1632
-
1633
-		$qb = $this->connection->getQueryBuilder();
1634
-		$qb->delete('preferences')
1635
-			->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)));
1636
-		$qb->executeStatement();
1637
-
1638
-		$this->clearCacheAll();
1639
-	}
1640
-
1641
-	public function deleteAllUserConfig(string $userId): void {
1642
-		$this->assertParams($userId, '', allowEmptyApp: true);
1643
-		$qb = $this->connection->getQueryBuilder();
1644
-		$qb->delete('preferences')
1645
-			->where($qb->expr()->eq('userid', $qb->createNamedParameter($userId)));
1646
-		$qb->executeStatement();
1647
-
1648
-		$this->clearCache($userId);
1649
-	}
1650
-
1651
-	/**
1652
-	 * @inheritDoc
1653
-	 *
1654
-	 * @param string $userId id of the user
1655
-	 * @param bool $reload set to TRUE to refill cache instantly after clearing it.
1656
-	 *
1657
-	 * @since 31.0.0
1658
-	 */
1659
-	public function clearCache(string $userId, bool $reload = false): void {
1660
-		$this->assertParams($userId, allowEmptyApp: true);
1661
-		$this->lazyLoaded[$userId] = $this->fastLoaded[$userId] = false;
1662
-		$this->lazyCache[$userId] = $this->fastCache[$userId] = $this->valueDetails[$userId] = [];
1663
-
1664
-		if (!$reload) {
1665
-			return;
1666
-		}
1667
-
1668
-		$this->loadConfigAll($userId);
1669
-	}
1670
-
1671
-	/**
1672
-	 * @inheritDoc
1673
-	 *
1674
-	 * @since 31.0.0
1675
-	 */
1676
-	public function clearCacheAll(): void {
1677
-		$this->lazyLoaded = $this->fastLoaded = [];
1678
-		$this->lazyCache = $this->fastCache = $this->valueDetails = $this->configLexiconDetails = [];
1679
-	}
1680
-
1681
-	/**
1682
-	 * For debug purpose.
1683
-	 * Returns the cached data.
1684
-	 *
1685
-	 * @return array
1686
-	 * @since 31.0.0
1687
-	 * @internal
1688
-	 */
1689
-	public function statusCache(): array {
1690
-		return [
1691
-			'fastLoaded' => $this->fastLoaded,
1692
-			'fastCache' => $this->fastCache,
1693
-			'lazyLoaded' => $this->lazyLoaded,
1694
-			'lazyCache' => $this->lazyCache,
1695
-			'valueDetails' => $this->valueDetails,
1696
-		];
1697
-	}
1698
-
1699
-	/**
1700
-	 * @param int $needle bitflag to search
1701
-	 * @param int $flags all flags
1702
-	 *
1703
-	 * @return bool TRUE if bitflag $needle is set in $flags
1704
-	 */
1705
-	private function isFlagged(int $needle, int $flags): bool {
1706
-		return (($needle & $flags) !== 0);
1707
-	}
1708
-
1709
-	/**
1710
-	 * Confirm the string set for app and key fit the database description
1711
-	 *
1712
-	 * @param string $userId
1713
-	 * @param string $app assert $app fit in database
1714
-	 * @param string $prefKey assert config key fit in database
1715
-	 * @param bool $allowEmptyUser
1716
-	 * @param bool $allowEmptyApp $app can be empty string
1717
-	 * @param ValueType|null $valueType assert value type is only one type
1718
-	 * @throws InvalidArgumentException if userId, app, or prefKey is invalid (too long, or empty string)
1719
-	 */
1720
-	private function assertParams(
1721
-		string $userId = '',
1722
-		string $app = '',
1723
-		string $prefKey = '',
1724
-		bool $allowEmptyUser = false,
1725
-		bool $allowEmptyApp = false,
1726
-	): void {
1727
-		if (!$allowEmptyUser && $userId === '') {
1728
-			throw new InvalidArgumentException('userId cannot be an empty string');
1729
-		}
1730
-		if (!$allowEmptyApp && $app === '') {
1731
-			throw new InvalidArgumentException('app cannot be an empty string');
1732
-		}
1733
-		if (strlen($userId) > self::USER_MAX_LENGTH) {
1734
-			throw new InvalidArgumentException('Value (' . $userId . ') for userId is too long (' . self::USER_MAX_LENGTH . ')');
1735
-		}
1736
-		if (strlen($app) > self::APP_MAX_LENGTH) {
1737
-			throw new InvalidArgumentException('Value (' . $app . ') for app is too long (' . self::APP_MAX_LENGTH . ')');
1738
-		}
1739
-		if (strlen($prefKey) > self::KEY_MAX_LENGTH) {
1740
-			throw new InvalidArgumentException('Value (' . $prefKey . ') for key is too long (' . self::KEY_MAX_LENGTH . ')');
1741
-		}
1742
-	}
1743
-
1744
-	private function loadConfigAll(string $userId): void {
1745
-		$this->loadConfig($userId, null);
1746
-	}
1747
-
1748
-	/**
1749
-	 * Load normal config or config set as lazy loaded
1750
-	 *
1751
-	 * @param bool|null $lazy set to TRUE to load config set as lazy loaded, set to NULL to load all config
1752
-	 */
1753
-	private function loadConfig(string $userId, ?bool $lazy = false): void {
1754
-		if ($this->isLoaded($userId, $lazy)) {
1755
-			return;
1756
-		}
1757
-
1758
-		if (($lazy ?? true) !== false) { // if lazy is null or true, we debug log
1759
-			$this->logger->debug('The loading of lazy UserConfig values have been requested', ['exception' => new \RuntimeException('ignorable exception')]);
1760
-		}
1761
-
1762
-		$qb = $this->connection->getQueryBuilder();
1763
-		$qb->from('preferences');
1764
-		$qb->select('appid', 'configkey', 'configvalue', 'type', 'flags');
1765
-		$qb->where($qb->expr()->eq('userid', $qb->createNamedParameter($userId)));
1766
-
1767
-		// we only need value from lazy when loadConfig does not specify it
1768
-		if ($lazy !== null) {
1769
-			$qb->andWhere($qb->expr()->eq('lazy', $qb->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT)));
1770
-		} else {
1771
-			$qb->addSelect('lazy');
1772
-		}
1773
-
1774
-		$result = $qb->executeQuery();
1775
-		$rows = $result->fetchAll();
1776
-		foreach ($rows as $row) {
1777
-			if (($row['lazy'] ?? ($lazy ?? 0) ? 1 : 0) === 1) {
1778
-				$this->lazyCache[$userId][$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
1779
-			} else {
1780
-				$this->fastCache[$userId][$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
1781
-			}
1782
-			$this->valueDetails[$userId][$row['appid']][$row['configkey']] = ['type' => ValueType::from((int)($row['type'] ?? 0)), 'flags' => (int)$row['flags']];
1783
-		}
1784
-		$result->closeCursor();
1785
-		$this->setAsLoaded($userId, $lazy);
1786
-	}
1787
-
1788
-	/**
1789
-	 * if $lazy is:
1790
-	 *  - false: will returns true if fast config are loaded
1791
-	 *  - true : will returns true if lazy config are loaded
1792
-	 *  - null : will returns true if both config are loaded
1793
-	 *
1794
-	 * @param string $userId
1795
-	 * @param bool $lazy
1796
-	 *
1797
-	 * @return bool
1798
-	 */
1799
-	private function isLoaded(string $userId, ?bool $lazy): bool {
1800
-		if ($lazy === null) {
1801
-			return ($this->lazyLoaded[$userId] ?? false) && ($this->fastLoaded[$userId] ?? false);
1802
-		}
1803
-
1804
-		return $lazy ? $this->lazyLoaded[$userId] ?? false : $this->fastLoaded[$userId] ?? false;
1805
-	}
1806
-
1807
-	/**
1808
-	 * if $lazy is:
1809
-	 * - false: set fast config as loaded
1810
-	 * - true : set lazy config as loaded
1811
-	 * - null : set both config as loaded
1812
-	 *
1813
-	 * @param string $userId
1814
-	 * @param bool $lazy
1815
-	 */
1816
-	private function setAsLoaded(string $userId, ?bool $lazy): void {
1817
-		if ($lazy === null) {
1818
-			$this->fastLoaded[$userId] = $this->lazyLoaded[$userId] = true;
1819
-			return;
1820
-		}
1821
-
1822
-		// We also create empty entry to keep both fastLoaded/lazyLoaded synced
1823
-		if ($lazy) {
1824
-			$this->lazyLoaded[$userId] = true;
1825
-			$this->fastLoaded[$userId] = $this->fastLoaded[$userId] ?? false;
1826
-			$this->fastCache[$userId] = $this->fastCache[$userId] ?? [];
1827
-		} else {
1828
-			$this->fastLoaded[$userId] = true;
1829
-			$this->lazyLoaded[$userId] = $this->lazyLoaded[$userId] ?? false;
1830
-			$this->lazyCache[$userId] = $this->lazyCache[$userId] ?? [];
1831
-		}
1832
-	}
1833
-
1834
-	/**
1835
-	 * **Warning:** this will load all lazy values from the database
1836
-	 *
1837
-	 * @param string $userId id of the user
1838
-	 * @param string $app id of the app
1839
-	 * @param bool $filtered TRUE to hide sensitive config values. Value are replaced by {@see IConfig::SENSITIVE_VALUE}
1840
-	 *
1841
-	 * @return array<string, string|int|float|bool|array>
1842
-	 */
1843
-	private function formatAppValues(string $userId, string $app, array $values, bool $filtered = false): array {
1844
-		foreach ($values as $key => $value) {
1845
-			//$key = (string)$key;
1846
-			try {
1847
-				$type = $this->getValueType($userId, $app, (string)$key);
1848
-			} catch (UnknownKeyException) {
1849
-				continue;
1850
-			}
1851
-
1852
-			if ($this->isFlagged(self::FLAG_SENSITIVE, $this->valueDetails[$userId][$app][$key]['flags'] ?? 0)) {
1853
-				if ($filtered) {
1854
-					$value = IConfig::SENSITIVE_VALUE;
1855
-					$type = ValueType::STRING;
1856
-				} else {
1857
-					$this->decryptSensitiveValue($userId, $app, (string)$key, $value);
1858
-				}
1859
-			}
1860
-
1861
-			$values[$key] = $this->convertTypedValue($value, $type);
1862
-		}
1863
-
1864
-		return $values;
1865
-	}
1866
-
1867
-	/**
1868
-	 * convert string value to the expected type
1869
-	 *
1870
-	 * @param string $value
1871
-	 * @param ValueType $type
1872
-	 *
1873
-	 * @return string|int|float|bool|array
1874
-	 */
1875
-	private function convertTypedValue(string $value, ValueType $type): string|int|float|bool|array {
1876
-		switch ($type) {
1877
-			case ValueType::INT:
1878
-				return (int)$value;
1879
-			case ValueType::FLOAT:
1880
-				return (float)$value;
1881
-			case ValueType::BOOL:
1882
-				return in_array(strtolower($value), ['1', 'true', 'yes', 'on']);
1883
-			case ValueType::ARRAY:
1884
-				try {
1885
-					return json_decode($value, true, flags: JSON_THROW_ON_ERROR);
1886
-				} catch (JsonException) {
1887
-					// ignoreable
1888
-				}
1889
-				break;
1890
-		}
1891
-		return $value;
1892
-	}
1893
-
1894
-
1895
-	/**
1896
-	 * will change referenced $value with the decrypted value in case of encrypted (sensitive value)
1897
-	 *
1898
-	 * @param string $userId
1899
-	 * @param string $app
1900
-	 * @param string $key
1901
-	 * @param string $value
1902
-	 */
1903
-	private function decryptSensitiveValue(string $userId, string $app, string $key, string &$value): void {
1904
-		if (!$this->isFlagged(self::FLAG_SENSITIVE, $this->valueDetails[$userId][$app][$key]['flags'] ?? 0)) {
1905
-			return;
1906
-		}
1907
-
1908
-		if (!str_starts_with($value, self::ENCRYPTION_PREFIX)) {
1909
-			return;
1910
-		}
1911
-
1912
-		try {
1913
-			$value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
1914
-		} catch (\Exception $e) {
1915
-			$this->logger->warning('could not decrypt sensitive value', [
1916
-				'userId' => $userId,
1917
-				'app' => $app,
1918
-				'key' => $key,
1919
-				'value' => $value,
1920
-				'exception' => $e
1921
-			]);
1922
-		}
1923
-	}
1924
-
1925
-	/**
1926
-	 * Match and apply current use of config values with defined lexicon.
1927
-	 * Set $lazy to NULL only if only interested into checking that $key is alias.
1928
-	 *
1929
-	 * @throws UnknownKeyException
1930
-	 * @throws TypeConflictException
1931
-	 * @return bool FALSE if conflict with defined lexicon were observed in the process
1932
-	 */
1933
-	private function matchAndApplyLexiconDefinition(
1934
-		string $userId,
1935
-		string $app,
1936
-		string &$key,
1937
-		?bool &$lazy = null,
1938
-		ValueType &$type = ValueType::MIXED,
1939
-		int &$flags = 0,
1940
-		?string &$default = null,
1941
-	): bool {
1942
-		$configDetails = $this->getConfigDetailsFromLexicon($app);
1943
-		if (array_key_exists($key, $configDetails['aliases']) && !$this->ignoreLexiconAliases) {
1944
-			// in case '$rename' is set in ConfigLexiconEntry, we use the new config key
1945
-			$key = $configDetails['aliases'][$key];
1946
-		}
1947
-
1948
-		if (!array_key_exists($key, $configDetails['entries'])) {
1949
-			return $this->applyLexiconStrictness($configDetails['strictness'], $app . '/' . $key);
1950
-		}
1951
-
1952
-		// if lazy is NULL, we ignore all check on the type/lazyness/default from Lexicon
1953
-		if ($lazy === null) {
1954
-			return true;
1955
-		}
1956
-
1957
-		/** @var Entry $configValue */
1958
-		$configValue = $configDetails['entries'][$key];
1959
-		if ($type === ValueType::MIXED) {
1960
-			// we overwrite if value was requested as mixed
1961
-			$type = $configValue->getValueType();
1962
-		} elseif ($configValue->getValueType() !== $type) {
1963
-			throw new TypeConflictException('The user config key ' . $app . '/' . $key . ' is typed incorrectly in relation to the config lexicon');
1964
-		}
1965
-
1966
-		$lazy = $configValue->isLazy();
1967
-		$flags = $configValue->getFlags();
1968
-		if ($configValue->isDeprecated()) {
1969
-			$this->logger->notice('User config key ' . $app . '/' . $key . ' is set as deprecated.');
1970
-		}
1971
-
1972
-		$enforcedValue = $this->config->getSystemValue('lexicon.default.userconfig.enforced', [])[$app][$key] ?? false;
1973
-		if (!$enforcedValue && $this->hasKey($userId, $app, $key, $lazy)) {
1974
-			// if key exists there should be no need to extract default
1975
-			return true;
1976
-		}
1977
-
1978
-		// only look for default if needed, default from Lexicon got priority if not overwritten by admin
1979
-		if ($default !== null) {
1980
-			$default = $this->getSystemDefault($app, $configValue) ?? $configValue->getDefault($this->presetManager->getLexiconPreset()) ?? $default;
1981
-		}
1982
-
1983
-		// returning false will make get() returning $default and set() not changing value in database
1984
-		return !$enforcedValue;
1985
-	}
1986
-
1987
-	/**
1988
-	 * get default value set in config/config.php if stored in key:
1989
-	 *
1990
-	 * 'lexicon.default.userconfig' => [
1991
-	 *        <appId> => [
1992
-	 *           <configKey> => 'my value',
1993
-	 *        ]
1994
-	 *     ],
1995
-	 *
1996
-	 * The entry is converted to string to fit the expected type when managing default value
1997
-	 */
1998
-	private function getSystemDefault(string $appId, Entry $configValue): ?string {
1999
-		$default = $this->config->getSystemValue('lexicon.default.userconfig', [])[$appId][$configValue->getKey()] ?? null;
2000
-		if ($default === null) {
2001
-			// no system default, using default default.
2002
-			return null;
2003
-		}
2004
-
2005
-		return $configValue->convertToString($default);
2006
-	}
2007
-
2008
-	/**
2009
-	 * manage ConfigLexicon behavior based on strictness set in IConfigLexicon
2010
-	 *
2011
-	 * @param Strictness|null $strictness
2012
-	 * @param string $line
2013
-	 *
2014
-	 * @return bool TRUE if conflict can be fully ignored
2015
-	 * @throws UnknownKeyException
2016
-	 * @see ILexicon::getStrictness()
2017
-	 */
2018
-	private function applyLexiconStrictness(?Strictness $strictness, string $configAppKey): bool {
2019
-		if ($strictness === null) {
2020
-			return true;
2021
-		}
2022
-
2023
-		$line = 'The user config key ' . $configAppKey . ' is not defined in the config lexicon';
2024
-		switch ($strictness) {
2025
-			case Strictness::IGNORE:
2026
-				return true;
2027
-			case Strictness::NOTICE:
2028
-				if (!in_array($configAppKey, $this->strictnessApplied, true)) {
2029
-					$this->strictnessApplied[] = $configAppKey;
2030
-					$this->logger->notice($line);
2031
-				}
2032
-				return true;
2033
-			case Strictness::WARNING:
2034
-				if (!in_array($configAppKey, $this->strictnessApplied, true)) {
2035
-					$this->strictnessApplied[] = $configAppKey;
2036
-					$this->logger->warning($line);
2037
-				}
2038
-				return false;
2039
-			case Strictness::EXCEPTION:
2040
-				throw new UnknownKeyException($line);
2041
-		}
2042
-
2043
-		throw new UnknownKeyException($line);
2044
-	}
2045
-
2046
-	/**
2047
-	 * extract details from registered $appId's config lexicon
2048
-	 *
2049
-	 * @param string $appId
2050
-	 *
2051
-	 * @return array{entries: array<string, Entry>, aliases: array<string, string>, strictness: Strictness}
2052
-	 * @internal
2053
-	 */
2054
-	public function getConfigDetailsFromLexicon(string $appId): array {
2055
-		if (!array_key_exists($appId, $this->configLexiconDetails)) {
2056
-			$entries = $aliases = [];
2057
-			$bootstrapCoordinator = \OCP\Server::get(Coordinator::class);
2058
-			$configLexicon = $bootstrapCoordinator->getRegistrationContext()?->getConfigLexicon($appId);
2059
-			foreach ($configLexicon?->getUserConfigs() ?? [] as $configEntry) {
2060
-				$entries[$configEntry->getKey()] = $configEntry;
2061
-				if ($configEntry->getRename() !== null) {
2062
-					$aliases[$configEntry->getRename()] = $configEntry->getKey();
2063
-				}
2064
-			}
2065
-
2066
-			$this->configLexiconDetails[$appId] = [
2067
-				'entries' => $entries,
2068
-				'aliases' => $aliases,
2069
-				'strictness' => $configLexicon?->getStrictness() ?? Strictness::IGNORE
2070
-			];
2071
-		}
2072
-
2073
-		return $this->configLexiconDetails[$appId];
2074
-	}
2075
-
2076
-	/**
2077
-	 * get Lexicon Entry using appId and config key entry
2078
-	 *
2079
-	 * @return Entry|null NULL if entry does not exist in user's Lexicon
2080
-	 * @internal
2081
-	 */
2082
-	public function getLexiconEntry(string $appId, string $key): ?Entry {
2083
-		return $this->getConfigDetailsFromLexicon($appId)['entries'][$key] ?? null;
2084
-	}
2085
-
2086
-	/**
2087
-	 * if set to TRUE, ignore aliases defined in Config Lexicon during the use of the methods of this class
2088
-	 *
2089
-	 * @internal
2090
-	 */
2091
-	public function ignoreLexiconAliases(bool $ignore): void {
2092
-		$this->ignoreLexiconAliases = $ignore;
2093
-	}
51
+    private const USER_MAX_LENGTH = 64;
52
+    private const APP_MAX_LENGTH = 32;
53
+    private const KEY_MAX_LENGTH = 64;
54
+    private const INDEX_MAX_LENGTH = 64;
55
+    private const ENCRYPTION_PREFIX = '$UserConfigEncryption$';
56
+    private const ENCRYPTION_PREFIX_LENGTH = 22; // strlen(self::ENCRYPTION_PREFIX)
57
+
58
+    /** @var array<string, array<string, array<string, mixed>>> [ass'user_id' => ['app_id' => ['key' => 'value']]] */
59
+    private array $fastCache = [];   // cache for normal config keys
60
+    /** @var array<string, array<string, array<string, mixed>>> ['user_id' => ['app_id' => ['key' => 'value']]] */
61
+    private array $lazyCache = [];   // cache for lazy config keys
62
+    /** @var array<string, array<string, array<string, array<string, mixed>>>> ['user_id' => ['app_id' => ['key' => ['type' => ValueType, 'flags' => bitflag]]]] */
63
+    private array $valueDetails = [];  // type for all config values
64
+    /** @var array<string, boolean> ['user_id' => bool] */
65
+    private array $fastLoaded = [];
66
+    /** @var array<string, boolean> ['user_id' => bool] */
67
+    private array $lazyLoaded = [];
68
+    /** @var array<string, array{entries: array<string, Entry>, aliases: array<string, string>, strictness: Strictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */
69
+    private array $configLexiconDetails = [];
70
+    private bool $ignoreLexiconAliases = false;
71
+    private array $strictnessApplied = [];
72
+
73
+    public function __construct(
74
+        protected IDBConnection $connection,
75
+        protected IConfig $config,
76
+        private readonly ConfigManager $configManager,
77
+        private readonly PresetManager $presetManager,
78
+        protected LoggerInterface $logger,
79
+        protected ICrypto $crypto,
80
+        protected IEventDispatcher $dispatcher,
81
+    ) {
82
+    }
83
+
84
+    /**
85
+     * @inheritDoc
86
+     *
87
+     * @param string $appId optional id of app
88
+     *
89
+     * @return list<string> list of userIds
90
+     * @since 31.0.0
91
+     */
92
+    public function getUserIds(string $appId = ''): array {
93
+        $this->assertParams(app: $appId, allowEmptyUser: true, allowEmptyApp: true);
94
+
95
+        $qb = $this->connection->getQueryBuilder();
96
+        $qb->from('preferences');
97
+        $qb->select('userid');
98
+        $qb->groupBy('userid');
99
+        if ($appId !== '') {
100
+            $qb->where($qb->expr()->eq('appid', $qb->createNamedParameter($appId)));
101
+        }
102
+
103
+        $result = $qb->executeQuery();
104
+        $rows = $result->fetchAll();
105
+        $userIds = [];
106
+        foreach ($rows as $row) {
107
+            $userIds[] = $row['userid'];
108
+        }
109
+
110
+        return $userIds;
111
+    }
112
+
113
+    /**
114
+     * @inheritDoc
115
+     *
116
+     * @return list<string> list of app ids
117
+     * @since 31.0.0
118
+     */
119
+    public function getApps(string $userId): array {
120
+        $this->assertParams($userId, allowEmptyApp: true);
121
+        $this->loadConfigAll($userId);
122
+        $apps = array_merge(array_keys($this->fastCache[$userId] ?? []), array_keys($this->lazyCache[$userId] ?? []));
123
+        sort($apps);
124
+
125
+        return array_values(array_unique($apps));
126
+    }
127
+
128
+    /**
129
+     * @inheritDoc
130
+     *
131
+     * @param string $userId id of the user
132
+     * @param string $app id of the app
133
+     *
134
+     * @return list<string> list of stored config keys
135
+     * @since 31.0.0
136
+     */
137
+    public function getKeys(string $userId, string $app): array {
138
+        $this->assertParams($userId, $app);
139
+        $this->loadConfigAll($userId);
140
+        // array_merge() will remove numeric keys (here config keys), so addition arrays instead
141
+        $keys = array_map('strval', array_keys(($this->fastCache[$userId][$app] ?? []) + ($this->lazyCache[$userId][$app] ?? [])));
142
+        sort($keys);
143
+
144
+        return array_values(array_unique($keys));
145
+    }
146
+
147
+    /**
148
+     * @inheritDoc
149
+     *
150
+     * @param string $userId id of the user
151
+     * @param string $app id of the app
152
+     * @param string $key config key
153
+     * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
154
+     *
155
+     * @return bool TRUE if key exists
156
+     * @since 31.0.0
157
+     */
158
+    public function hasKey(string $userId, string $app, string $key, ?bool $lazy = false): bool {
159
+        $this->assertParams($userId, $app, $key);
160
+        $this->loadConfig($userId, $lazy);
161
+        $this->matchAndApplyLexiconDefinition($userId, $app, $key);
162
+
163
+        if ($lazy === null) {
164
+            $appCache = $this->getValues($userId, $app);
165
+            return isset($appCache[$key]);
166
+        }
167
+
168
+        if ($lazy) {
169
+            return isset($this->lazyCache[$userId][$app][$key]);
170
+        }
171
+
172
+        return isset($this->fastCache[$userId][$app][$key]);
173
+    }
174
+
175
+    /**
176
+     * @inheritDoc
177
+     *
178
+     * @param string $userId id of the user
179
+     * @param string $app id of the app
180
+     * @param string $key config key
181
+     * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
182
+     *
183
+     * @return bool
184
+     * @throws UnknownKeyException if config key is not known
185
+     * @since 31.0.0
186
+     */
187
+    public function isSensitive(string $userId, string $app, string $key, ?bool $lazy = false): bool {
188
+        $this->assertParams($userId, $app, $key);
189
+        $this->loadConfig($userId, $lazy);
190
+        $this->matchAndApplyLexiconDefinition($userId, $app, $key);
191
+
192
+        if (!isset($this->valueDetails[$userId][$app][$key])) {
193
+            throw new UnknownKeyException('unknown config key');
194
+        }
195
+
196
+        return $this->isFlagged(self::FLAG_SENSITIVE, $this->valueDetails[$userId][$app][$key]['flags']);
197
+    }
198
+
199
+    /**
200
+     * @inheritDoc
201
+     *
202
+     * @param string $userId id of the user
203
+     * @param string $app id of the app
204
+     * @param string $key config key
205
+     * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
206
+     *
207
+     * @return bool
208
+     * @throws UnknownKeyException if config key is not known
209
+     * @since 31.0.0
210
+     */
211
+    public function isIndexed(string $userId, string $app, string $key, ?bool $lazy = false): bool {
212
+        $this->assertParams($userId, $app, $key);
213
+        $this->loadConfig($userId, $lazy);
214
+        $this->matchAndApplyLexiconDefinition($userId, $app, $key);
215
+
216
+        if (!isset($this->valueDetails[$userId][$app][$key])) {
217
+            throw new UnknownKeyException('unknown config key');
218
+        }
219
+
220
+        return $this->isFlagged(self::FLAG_INDEXED, $this->valueDetails[$userId][$app][$key]['flags']);
221
+    }
222
+
223
+    /**
224
+     * @inheritDoc
225
+     *
226
+     * @param string $userId id of the user
227
+     * @param string $app if of the app
228
+     * @param string $key config key
229
+     *
230
+     * @return bool TRUE if config is lazy loaded
231
+     * @throws UnknownKeyException if config key is not known
232
+     * @see IUserConfig for details about lazy loading
233
+     * @since 31.0.0
234
+     */
235
+    public function isLazy(string $userId, string $app, string $key): bool {
236
+        $this->matchAndApplyLexiconDefinition($userId, $app, $key);
237
+
238
+        // there is a huge probability the non-lazy config are already loaded
239
+        // meaning that we can start by only checking if a current non-lazy key exists
240
+        if ($this->hasKey($userId, $app, $key, false)) {
241
+            // meaning key is not lazy.
242
+            return false;
243
+        }
244
+
245
+        // as key is not found as non-lazy, we load and search in the lazy config
246
+        if ($this->hasKey($userId, $app, $key, true)) {
247
+            return true;
248
+        }
249
+
250
+        throw new UnknownKeyException('unknown config key');
251
+    }
252
+
253
+    /**
254
+     * @inheritDoc
255
+     *
256
+     * @param string $userId id of the user
257
+     * @param string $app id of the app
258
+     * @param string $prefix config keys prefix to search
259
+     * @param bool $filtered TRUE to hide sensitive config values. Value are replaced by {@see IConfig::SENSITIVE_VALUE}
260
+     *
261
+     * @return array<string, string|int|float|bool|array> [key => value]
262
+     * @since 31.0.0
263
+     */
264
+    public function getValues(
265
+        string $userId,
266
+        string $app,
267
+        string $prefix = '',
268
+        bool $filtered = false,
269
+    ): array {
270
+        $this->assertParams($userId, $app, $prefix);
271
+        // if we want to filter values, we need to get sensitivity
272
+        $this->loadConfigAll($userId);
273
+        // array_merge() will remove numeric keys (here config keys), so addition arrays instead
274
+        $values = array_filter(
275
+            $this->formatAppValues($userId, $app, ($this->fastCache[$userId][$app] ?? []) + ($this->lazyCache[$userId][$app] ?? []), $filtered),
276
+            function (string $key) use ($prefix): bool {
277
+                // filter values based on $prefix
278
+                return str_starts_with($key, $prefix);
279
+            }, ARRAY_FILTER_USE_KEY
280
+        );
281
+
282
+        return $values;
283
+    }
284
+
285
+    /**
286
+     * @inheritDoc
287
+     *
288
+     * @param string $userId id of the user
289
+     * @param bool $filtered TRUE to hide sensitive config values. Value are replaced by {@see IConfig::SENSITIVE_VALUE}
290
+     *
291
+     * @return array<string, array<string, string|int|float|bool|array>> [appId => [key => value]]
292
+     * @since 31.0.0
293
+     */
294
+    public function getAllValues(string $userId, bool $filtered = false): array {
295
+        $this->assertParams($userId, allowEmptyApp: true);
296
+        $this->loadConfigAll($userId);
297
+
298
+        $result = [];
299
+        foreach ($this->getApps($userId) as $app) {
300
+            // array_merge() will remove numeric keys (here config keys), so addition arrays instead
301
+            $cached = ($this->fastCache[$userId][$app] ?? []) + ($this->lazyCache[$userId][$app] ?? []);
302
+            $result[$app] = $this->formatAppValues($userId, $app, $cached, $filtered);
303
+        }
304
+
305
+        return $result;
306
+    }
307
+
308
+    /**
309
+     * @inheritDoc
310
+     *
311
+     * @param string $userId id of the user
312
+     * @param string $key config key
313
+     * @param bool $lazy search within lazy loaded config
314
+     * @param ValueType|null $typedAs enforce type for the returned values
315
+     *
316
+     * @return array<string, string|int|float|bool|array> [appId => value]
317
+     * @since 31.0.0
318
+     */
319
+    public function getValuesByApps(string $userId, string $key, bool $lazy = false, ?ValueType $typedAs = null): array {
320
+        $this->assertParams($userId, '', $key, allowEmptyApp: true);
321
+        $this->loadConfig($userId, $lazy);
322
+
323
+        /** @var array<array-key, array<array-key, mixed>> $cache */
324
+        if ($lazy) {
325
+            $cache = $this->lazyCache[$userId];
326
+        } else {
327
+            $cache = $this->fastCache[$userId];
328
+        }
329
+
330
+        $values = [];
331
+        foreach (array_keys($cache) as $app) {
332
+            if (isset($cache[$app][$key])) {
333
+                $value = $cache[$app][$key];
334
+                try {
335
+                    $this->decryptSensitiveValue($userId, $app, $key, $value);
336
+                    $value = $this->convertTypedValue($value, $typedAs ?? $this->getValueType($userId, $app, $key, $lazy));
337
+                } catch (IncorrectTypeException|UnknownKeyException) {
338
+                }
339
+                $values[$app] = $value;
340
+            }
341
+        }
342
+
343
+        return $values;
344
+    }
345
+
346
+
347
+    /**
348
+     * @inheritDoc
349
+     *
350
+     * @param string $app id of the app
351
+     * @param string $key config key
352
+     * @param ValueType|null $typedAs enforce type for the returned values
353
+     * @param array|null $userIds limit to a list of user ids
354
+     *
355
+     * @return array<string, string|int|float|bool|array> [userId => value]
356
+     * @since 31.0.0
357
+     */
358
+    public function getValuesByUsers(
359
+        string $app,
360
+        string $key,
361
+        ?ValueType $typedAs = null,
362
+        ?array $userIds = null,
363
+    ): array {
364
+        $this->assertParams('', $app, $key, allowEmptyUser: true);
365
+        $this->matchAndApplyLexiconDefinition('', $app, $key);
366
+
367
+        $qb = $this->connection->getQueryBuilder();
368
+        $qb->select('userid', 'configvalue', 'type')
369
+            ->from('preferences')
370
+            ->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)))
371
+            ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)));
372
+
373
+        $values = [];
374
+        // this nested function will execute current Query and store result within $values.
375
+        $executeAndStoreValue = function (IQueryBuilder $qb) use (&$values, $typedAs): IResult {
376
+            $result = $qb->executeQuery();
377
+            while ($row = $result->fetch()) {
378
+                $value = $row['configvalue'];
379
+                try {
380
+                    $value = $this->convertTypedValue($value, $typedAs ?? ValueType::from((int)$row['type']));
381
+                } catch (IncorrectTypeException) {
382
+                }
383
+                $values[$row['userid']] = $value;
384
+            }
385
+            return $result;
386
+        };
387
+
388
+        // if no userIds to filter, we execute query as it is and returns all values ...
389
+        if ($userIds === null) {
390
+            $result = $executeAndStoreValue($qb);
391
+            $result->closeCursor();
392
+            return $values;
393
+        }
394
+
395
+        // if userIds to filter, we chunk the list and execute the same query multiple times until we get all values
396
+        $result = null;
397
+        $qb->andWhere($qb->expr()->in('userid', $qb->createParameter('userIds')));
398
+        foreach (array_chunk($userIds, 50, true) as $chunk) {
399
+            $qb->setParameter('userIds', $chunk, IQueryBuilder::PARAM_STR_ARRAY);
400
+            $result = $executeAndStoreValue($qb);
401
+        }
402
+        $result?->closeCursor();
403
+
404
+        return $values;
405
+    }
406
+
407
+    /**
408
+     * @inheritDoc
409
+     *
410
+     * @param string $app id of the app
411
+     * @param string $key config key
412
+     * @param string $value config value
413
+     * @param bool $caseInsensitive non-case-sensitive search, only works if $value is a string
414
+     *
415
+     * @return Generator<string>
416
+     * @since 31.0.0
417
+     */
418
+    public function searchUsersByValueString(string $app, string $key, string $value, bool $caseInsensitive = false): Generator {
419
+        return $this->searchUsersByTypedValue($app, $key, $value, $caseInsensitive);
420
+    }
421
+
422
+    /**
423
+     * @inheritDoc
424
+     *
425
+     * @param string $app id of the app
426
+     * @param string $key config key
427
+     * @param int $value config value
428
+     *
429
+     * @return Generator<string>
430
+     * @since 31.0.0
431
+     */
432
+    public function searchUsersByValueInt(string $app, string $key, int $value): Generator {
433
+        return $this->searchUsersByValueString($app, $key, (string)$value);
434
+    }
435
+
436
+    /**
437
+     * @inheritDoc
438
+     *
439
+     * @param string $app id of the app
440
+     * @param string $key config key
441
+     * @param array $values list of config values
442
+     *
443
+     * @return Generator<string>
444
+     * @since 31.0.0
445
+     */
446
+    public function searchUsersByValues(string $app, string $key, array $values): Generator {
447
+        return $this->searchUsersByTypedValue($app, $key, $values);
448
+    }
449
+
450
+    /**
451
+     * @inheritDoc
452
+     *
453
+     * @param string $app id of the app
454
+     * @param string $key config key
455
+     * @param bool $value config value
456
+     *
457
+     * @return Generator<string>
458
+     * @since 31.0.0
459
+     */
460
+    public function searchUsersByValueBool(string $app, string $key, bool $value): Generator {
461
+        $values = ['0', 'off', 'false', 'no'];
462
+        if ($value) {
463
+            $values = ['1', 'on', 'true', 'yes'];
464
+        }
465
+        return $this->searchUsersByValues($app, $key, $values);
466
+    }
467
+
468
+    /**
469
+     * returns a list of users with config key set to a specific value, or within the list of
470
+     * possible values
471
+     *
472
+     * @param string $app
473
+     * @param string $key
474
+     * @param string|array $value
475
+     * @param bool $caseInsensitive
476
+     *
477
+     * @return Generator<string>
478
+     */
479
+    private function searchUsersByTypedValue(string $app, string $key, string|array $value, bool $caseInsensitive = false): Generator {
480
+        $this->assertParams('', $app, $key, allowEmptyUser: true);
481
+        $this->matchAndApplyLexiconDefinition('', $app, $key);
482
+
483
+        $lexiconEntry = $this->getLexiconEntry($app, $key);
484
+        if ($lexiconEntry?->isFlagged(self::FLAG_INDEXED) === false) {
485
+            $this->logger->notice('UserConfig+Lexicon: using searchUsersByTypedValue on config key ' . $app . '/' . $key . ' which is not set as indexed');
486
+        }
487
+
488
+        $qb = $this->connection->getQueryBuilder();
489
+        $qb->from('preferences');
490
+        $qb->select('userid');
491
+        $qb->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)));
492
+        $qb->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)));
493
+
494
+        $configValueColumn = ($this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_ORACLE) ? $qb->expr()->castColumn('configvalue', IQueryBuilder::PARAM_STR) : 'configvalue';
495
+        if (is_array($value)) {
496
+            $where = $qb->expr()->in('indexed', $qb->createNamedParameter($value, IQueryBuilder::PARAM_STR_ARRAY));
497
+            // in case lexicon does not exist for this key - or is not set as indexed - we keep searching for non-index entries if 'flags' is set as not indexed
498
+            if ($lexiconEntry?->isFlagged(self::FLAG_INDEXED) !== true) {
499
+                $where = $qb->expr()->orX(
500
+                    $where,
501
+                    $qb->expr()->andX(
502
+                        $qb->expr()->neq($qb->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), $qb->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)),
503
+                        $qb->expr()->in($configValueColumn, $qb->createNamedParameter($value, IQueryBuilder::PARAM_STR_ARRAY))
504
+                    )
505
+                );
506
+            }
507
+        } else {
508
+            if ($caseInsensitive) {
509
+                $where = $qb->expr()->eq($qb->func()->lower('indexed'), $qb->createNamedParameter(strtolower($value)));
510
+                // in case lexicon does not exist for this key - or is not set as indexed - we keep searching for non-index entries if 'flags' is set as not indexed
511
+                if ($lexiconEntry?->isFlagged(self::FLAG_INDEXED) !== true) {
512
+                    $where = $qb->expr()->orX(
513
+                        $where,
514
+                        $qb->expr()->andX(
515
+                            $qb->expr()->neq($qb->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), $qb->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)),
516
+                            $qb->expr()->eq($qb->func()->lower($configValueColumn), $qb->createNamedParameter(strtolower($value)))
517
+                        )
518
+                    );
519
+                }
520
+            } else {
521
+                $where = $qb->expr()->eq('indexed', $qb->createNamedParameter($value));
522
+                // in case lexicon does not exist for this key - or is not set as indexed - we keep searching for non-index entries if 'flags' is set as not indexed
523
+                if ($lexiconEntry?->isFlagged(self::FLAG_INDEXED) !== true) {
524
+                    $where = $qb->expr()->orX(
525
+                        $where,
526
+                        $qb->expr()->andX(
527
+                            $qb->expr()->neq($qb->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), $qb->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)),
528
+                            $qb->expr()->eq($configValueColumn, $qb->createNamedParameter($value))
529
+                        )
530
+                    );
531
+                }
532
+            }
533
+        }
534
+
535
+        $qb->andWhere($where);
536
+        $result = $qb->executeQuery();
537
+        while ($row = $result->fetch()) {
538
+            yield $row['userid'];
539
+        }
540
+    }
541
+
542
+    /**
543
+     * Get the config value as string.
544
+     * If the value does not exist the given default will be returned.
545
+     *
546
+     * Set lazy to `null` to ignore it and get the value from either source.
547
+     *
548
+     * **WARNING:** Method is internal and **SHOULD** not be used, as it is better to get the value with a type.
549
+     *
550
+     * @param string $userId id of the user
551
+     * @param string $app id of the app
552
+     * @param string $key config key
553
+     * @param string $default config value
554
+     * @param null|bool $lazy get config as lazy loaded or not. can be NULL
555
+     *
556
+     * @return string the value or $default
557
+     * @throws TypeConflictException
558
+     * @internal
559
+     * @since 31.0.0
560
+     * @see IUserConfig for explanation about lazy loading
561
+     * @see getValueString()
562
+     * @see getValueInt()
563
+     * @see getValueFloat()
564
+     * @see getValueBool()
565
+     * @see getValueArray()
566
+     */
567
+    public function getValueMixed(
568
+        string $userId,
569
+        string $app,
570
+        string $key,
571
+        string $default = '',
572
+        ?bool $lazy = false,
573
+    ): string {
574
+        $this->matchAndApplyLexiconDefinition($userId, $app, $key);
575
+        try {
576
+            $lazy ??= $this->isLazy($userId, $app, $key);
577
+        } catch (UnknownKeyException) {
578
+            return $default;
579
+        }
580
+
581
+        return $this->getTypedValue(
582
+            $userId,
583
+            $app,
584
+            $key,
585
+            $default,
586
+            $lazy,
587
+            ValueType::MIXED
588
+        );
589
+    }
590
+
591
+    /**
592
+     * @inheritDoc
593
+     *
594
+     * @param string $userId id of the user
595
+     * @param string $app id of the app
596
+     * @param string $key config key
597
+     * @param string $default default value
598
+     * @param bool $lazy search within lazy loaded config
599
+     *
600
+     * @return string stored config value or $default if not set in database
601
+     * @throws InvalidArgumentException if one of the argument format is invalid
602
+     * @throws TypeConflictException in case of conflict with the value type set in database
603
+     * @since 31.0.0
604
+     * @see IUserConfig for explanation about lazy loading
605
+     */
606
+    public function getValueString(
607
+        string $userId,
608
+        string $app,
609
+        string $key,
610
+        string $default = '',
611
+        bool $lazy = false,
612
+    ): string {
613
+        return $this->getTypedValue($userId, $app, $key, $default, $lazy, ValueType::STRING);
614
+    }
615
+
616
+    /**
617
+     * @inheritDoc
618
+     *
619
+     * @param string $userId id of the user
620
+     * @param string $app id of the app
621
+     * @param string $key config key
622
+     * @param int $default default value
623
+     * @param bool $lazy search within lazy loaded config
624
+     *
625
+     * @return int stored config value or $default if not set in database
626
+     * @throws InvalidArgumentException if one of the argument format is invalid
627
+     * @throws TypeConflictException in case of conflict with the value type set in database
628
+     * @since 31.0.0
629
+     * @see IUserConfig for explanation about lazy loading
630
+     */
631
+    public function getValueInt(
632
+        string $userId,
633
+        string $app,
634
+        string $key,
635
+        int $default = 0,
636
+        bool $lazy = false,
637
+    ): int {
638
+        return (int)$this->getTypedValue($userId, $app, $key, (string)$default, $lazy, ValueType::INT);
639
+    }
640
+
641
+    /**
642
+     * @inheritDoc
643
+     *
644
+     * @param string $userId id of the user
645
+     * @param string $app id of the app
646
+     * @param string $key config key
647
+     * @param float $default default value
648
+     * @param bool $lazy search within lazy loaded config
649
+     *
650
+     * @return float stored config value or $default if not set in database
651
+     * @throws InvalidArgumentException if one of the argument format is invalid
652
+     * @throws TypeConflictException in case of conflict with the value type set in database
653
+     * @since 31.0.0
654
+     * @see IUserConfig for explanation about lazy loading
655
+     */
656
+    public function getValueFloat(
657
+        string $userId,
658
+        string $app,
659
+        string $key,
660
+        float $default = 0,
661
+        bool $lazy = false,
662
+    ): float {
663
+        return (float)$this->getTypedValue($userId, $app, $key, (string)$default, $lazy, ValueType::FLOAT);
664
+    }
665
+
666
+    /**
667
+     * @inheritDoc
668
+     *
669
+     * @param string $userId id of the user
670
+     * @param string $app id of the app
671
+     * @param string $key config key
672
+     * @param bool $default default value
673
+     * @param bool $lazy search within lazy loaded config
674
+     *
675
+     * @return bool stored config value or $default if not set in database
676
+     * @throws InvalidArgumentException if one of the argument format is invalid
677
+     * @throws TypeConflictException in case of conflict with the value type set in database
678
+     * @since 31.0.0
679
+     * @see IUserConfig for explanation about lazy loading
680
+     */
681
+    public function getValueBool(
682
+        string $userId,
683
+        string $app,
684
+        string $key,
685
+        bool $default = false,
686
+        bool $lazy = false,
687
+    ): bool {
688
+        $b = strtolower($this->getTypedValue($userId, $app, $key, $default ? 'true' : 'false', $lazy, ValueType::BOOL));
689
+        return in_array($b, ['1', 'true', 'yes', 'on']);
690
+    }
691
+
692
+    /**
693
+     * @inheritDoc
694
+     *
695
+     * @param string $userId id of the user
696
+     * @param string $app id of the app
697
+     * @param string $key config key
698
+     * @param array $default default value
699
+     * @param bool $lazy search within lazy loaded config
700
+     *
701
+     * @return array stored config value or $default if not set in database
702
+     * @throws InvalidArgumentException if one of the argument format is invalid
703
+     * @throws TypeConflictException in case of conflict with the value type set in database
704
+     * @since 31.0.0
705
+     * @see IUserConfig for explanation about lazy loading
706
+     */
707
+    public function getValueArray(
708
+        string $userId,
709
+        string $app,
710
+        string $key,
711
+        array $default = [],
712
+        bool $lazy = false,
713
+    ): array {
714
+        try {
715
+            $defaultJson = json_encode($default, JSON_THROW_ON_ERROR);
716
+            $value = json_decode($this->getTypedValue($userId, $app, $key, $defaultJson, $lazy, ValueType::ARRAY), true, flags: JSON_THROW_ON_ERROR);
717
+
718
+            return is_array($value) ? $value : [];
719
+        } catch (JsonException) {
720
+            return [];
721
+        }
722
+    }
723
+
724
+    /**
725
+     * @param string $userId
726
+     * @param string $app id of the app
727
+     * @param string $key config key
728
+     * @param string $default default value
729
+     * @param bool $lazy search within lazy loaded config
730
+     * @param ValueType $type value type
731
+     *
732
+     * @return string
733
+     * @throws TypeConflictException if type from database is not VALUE_MIXED and different from the requested one
734
+     */
735
+    private function getTypedValue(
736
+        string $userId,
737
+        string $app,
738
+        string $key,
739
+        string $default,
740
+        bool $lazy,
741
+        ValueType $type,
742
+    ): string {
743
+        $this->assertParams($userId, $app, $key);
744
+        $origKey = $key;
745
+        $matched = $this->matchAndApplyLexiconDefinition($userId, $app, $key, $lazy, $type, default: $default);
746
+        if ($default === null) {
747
+            // there is no logical reason for it to be null
748
+            throw new \Exception('default cannot be null');
749
+        }
750
+
751
+        // returns default if strictness of lexicon is set to WARNING (block and report)
752
+        if (!$matched) {
753
+            return $default;
754
+        }
755
+
756
+        $this->loadConfig($userId, $lazy);
757
+
758
+        /**
759
+         * We ignore check if mixed type is requested.
760
+         * If type of stored value is set as mixed, we don't filter.
761
+         * If type of stored value is defined, we compare with the one requested.
762
+         */
763
+        $knownType = $this->valueDetails[$userId][$app][$key]['type'] ?? null;
764
+        if ($type !== ValueType::MIXED
765
+            && $knownType !== null
766
+            && $knownType !== ValueType::MIXED
767
+            && $type !== $knownType) {
768
+            $this->logger->warning('conflict with value type from database', ['app' => $app, 'key' => $key, 'type' => $type, 'knownType' => $knownType]);
769
+            throw new TypeConflictException('conflict with value type from database');
770
+        }
771
+
772
+        /**
773
+         * - the pair $app/$key cannot exist in both array,
774
+         * - we should still return an existing non-lazy value even if current method
775
+         *   is called with $lazy is true
776
+         *
777
+         * This way, lazyCache will be empty until the load for lazy config value is requested.
778
+         */
779
+        if (isset($this->lazyCache[$userId][$app][$key])) {
780
+            $value = $this->lazyCache[$userId][$app][$key];
781
+        } elseif (isset($this->fastCache[$userId][$app][$key])) {
782
+            $value = $this->fastCache[$userId][$app][$key];
783
+        } else {
784
+            return $default;
785
+        }
786
+
787
+        $this->decryptSensitiveValue($userId, $app, $key, $value);
788
+
789
+        // in case the key was modified while running matchAndApplyLexiconDefinition() we are
790
+        // interested to check options in case a modification of the value is needed
791
+        // ie inverting value from previous key when using lexicon option RENAME_INVERT_BOOLEAN
792
+        if ($origKey !== $key && $type === ValueType::BOOL) {
793
+            $value = ($this->configManager->convertToBool($value, $this->getLexiconEntry($app, $key))) ? '1' : '0';
794
+        }
795
+
796
+        return $value;
797
+    }
798
+
799
+    /**
800
+     * @inheritDoc
801
+     *
802
+     * @param string $userId id of the user
803
+     * @param string $app id of the app
804
+     * @param string $key config key
805
+     *
806
+     * @return ValueType type of the value
807
+     * @throws UnknownKeyException if config key is not known
808
+     * @throws IncorrectTypeException if config value type is not known
809
+     * @since 31.0.0
810
+     */
811
+    public function getValueType(string $userId, string $app, string $key, ?bool $lazy = null): ValueType {
812
+        $this->assertParams($userId, $app, $key);
813
+        $this->loadConfig($userId, $lazy);
814
+        $this->matchAndApplyLexiconDefinition($userId, $app, $key);
815
+
816
+        if (!isset($this->valueDetails[$userId][$app][$key]['type'])) {
817
+            throw new UnknownKeyException('unknown config key');
818
+        }
819
+
820
+        return $this->valueDetails[$userId][$app][$key]['type'];
821
+    }
822
+
823
+    /**
824
+     * @inheritDoc
825
+     *
826
+     * @param string $userId id of the user
827
+     * @param string $app id of the app
828
+     * @param string $key config key
829
+     * @param bool $lazy lazy loading
830
+     *
831
+     * @return int flags applied to value
832
+     * @throws UnknownKeyException if config key is not known
833
+     * @throws IncorrectTypeException if config value type is not known
834
+     * @since 31.0.0
835
+     */
836
+    public function getValueFlags(string $userId, string $app, string $key, bool $lazy = false): int {
837
+        $this->assertParams($userId, $app, $key);
838
+        $this->loadConfig($userId, $lazy);
839
+        $this->matchAndApplyLexiconDefinition($userId, $app, $key);
840
+
841
+        if (!isset($this->valueDetails[$userId][$app][$key])) {
842
+            throw new UnknownKeyException('unknown config key');
843
+        }
844
+
845
+        return $this->valueDetails[$userId][$app][$key]['flags'];
846
+    }
847
+
848
+    /**
849
+     * Store a config key and its value in database as VALUE_MIXED
850
+     *
851
+     * **WARNING:** Method is internal and **MUST** not be used as it is best to set a real value type
852
+     *
853
+     * @param string $userId id of the user
854
+     * @param string $app id of the app
855
+     * @param string $key config key
856
+     * @param string $value config value
857
+     * @param bool $lazy set config as lazy loaded
858
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
859
+     *
860
+     * @return bool TRUE if value was different, therefor updated in database
861
+     * @throws TypeConflictException if type from database is not VALUE_MIXED
862
+     * @internal
863
+     * @since 31.0.0
864
+     * @see IUserConfig for explanation about lazy loading
865
+     * @see setValueString()
866
+     * @see setValueInt()
867
+     * @see setValueFloat()
868
+     * @see setValueBool()
869
+     * @see setValueArray()
870
+     */
871
+    public function setValueMixed(
872
+        string $userId,
873
+        string $app,
874
+        string $key,
875
+        string $value,
876
+        bool $lazy = false,
877
+        int $flags = 0,
878
+    ): bool {
879
+        return $this->setTypedValue(
880
+            $userId,
881
+            $app,
882
+            $key,
883
+            $value,
884
+            $lazy,
885
+            $flags,
886
+            ValueType::MIXED
887
+        );
888
+    }
889
+
890
+
891
+    /**
892
+     * @inheritDoc
893
+     *
894
+     * @param string $userId id of the user
895
+     * @param string $app id of the app
896
+     * @param string $key config key
897
+     * @param string $value config value
898
+     * @param bool $lazy set config as lazy loaded
899
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
900
+     *
901
+     * @return bool TRUE if value was different, therefor updated in database
902
+     * @throws TypeConflictException if type from database is not VALUE_MIXED and different from the requested one
903
+     * @since 31.0.0
904
+     * @see IUserConfig for explanation about lazy loading
905
+     */
906
+    public function setValueString(
907
+        string $userId,
908
+        string $app,
909
+        string $key,
910
+        string $value,
911
+        bool $lazy = false,
912
+        int $flags = 0,
913
+    ): bool {
914
+        return $this->setTypedValue(
915
+            $userId,
916
+            $app,
917
+            $key,
918
+            $value,
919
+            $lazy,
920
+            $flags,
921
+            ValueType::STRING
922
+        );
923
+    }
924
+
925
+    /**
926
+     * @inheritDoc
927
+     *
928
+     * @param string $userId id of the user
929
+     * @param string $app id of the app
930
+     * @param string $key config key
931
+     * @param int $value config value
932
+     * @param bool $lazy set config as lazy loaded
933
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
934
+     *
935
+     * @return bool TRUE if value was different, therefor updated in database
936
+     * @throws TypeConflictException if type from database is not VALUE_MIXED and different from the requested one
937
+     * @since 31.0.0
938
+     * @see IUserConfig for explanation about lazy loading
939
+     */
940
+    public function setValueInt(
941
+        string $userId,
942
+        string $app,
943
+        string $key,
944
+        int $value,
945
+        bool $lazy = false,
946
+        int $flags = 0,
947
+    ): bool {
948
+        if ($value > 2000000000) {
949
+            $this->logger->debug('You are trying to store an integer value around/above 2,147,483,647. This is a reminder that reaching this theoretical limit on 32 bits system will throw an exception.');
950
+        }
951
+
952
+        return $this->setTypedValue(
953
+            $userId,
954
+            $app,
955
+            $key,
956
+            (string)$value,
957
+            $lazy,
958
+            $flags,
959
+            ValueType::INT
960
+        );
961
+    }
962
+
963
+    /**
964
+     * @inheritDoc
965
+     *
966
+     * @param string $userId id of the user
967
+     * @param string $app id of the app
968
+     * @param string $key config key
969
+     * @param float $value config value
970
+     * @param bool $lazy set config as lazy loaded
971
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
972
+     *
973
+     * @return bool TRUE if value was different, therefor updated in database
974
+     * @throws TypeConflictException if type from database is not VALUE_MIXED and different from the requested one
975
+     * @since 31.0.0
976
+     * @see IUserConfig for explanation about lazy loading
977
+     */
978
+    public function setValueFloat(
979
+        string $userId,
980
+        string $app,
981
+        string $key,
982
+        float $value,
983
+        bool $lazy = false,
984
+        int $flags = 0,
985
+    ): bool {
986
+        return $this->setTypedValue(
987
+            $userId,
988
+            $app,
989
+            $key,
990
+            (string)$value,
991
+            $lazy,
992
+            $flags,
993
+            ValueType::FLOAT
994
+        );
995
+    }
996
+
997
+    /**
998
+     * @inheritDoc
999
+     *
1000
+     * @param string $userId id of the user
1001
+     * @param string $app id of the app
1002
+     * @param string $key config key
1003
+     * @param bool $value config value
1004
+     * @param bool $lazy set config as lazy loaded
1005
+     *
1006
+     * @return bool TRUE if value was different, therefor updated in database
1007
+     * @throws TypeConflictException if type from database is not VALUE_MIXED and different from the requested one
1008
+     * @since 31.0.0
1009
+     * @see IUserConfig for explanation about lazy loading
1010
+     */
1011
+    public function setValueBool(
1012
+        string $userId,
1013
+        string $app,
1014
+        string $key,
1015
+        bool $value,
1016
+        bool $lazy = false,
1017
+        int $flags = 0,
1018
+    ): bool {
1019
+        return $this->setTypedValue(
1020
+            $userId,
1021
+            $app,
1022
+            $key,
1023
+            ($value) ? '1' : '0',
1024
+            $lazy,
1025
+            $flags,
1026
+            ValueType::BOOL
1027
+        );
1028
+    }
1029
+
1030
+    /**
1031
+     * @inheritDoc
1032
+     *
1033
+     * @param string $userId id of the user
1034
+     * @param string $app id of the app
1035
+     * @param string $key config key
1036
+     * @param array $value config value
1037
+     * @param bool $lazy set config as lazy loaded
1038
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
1039
+     *
1040
+     * @return bool TRUE if value was different, therefor updated in database
1041
+     * @throws TypeConflictException if type from database is not VALUE_MIXED and different from the requested one
1042
+     * @throws JsonException
1043
+     * @since 31.0.0
1044
+     * @see IUserConfig for explanation about lazy loading
1045
+     */
1046
+    public function setValueArray(
1047
+        string $userId,
1048
+        string $app,
1049
+        string $key,
1050
+        array $value,
1051
+        bool $lazy = false,
1052
+        int $flags = 0,
1053
+    ): bool {
1054
+        try {
1055
+            return $this->setTypedValue(
1056
+                $userId,
1057
+                $app,
1058
+                $key,
1059
+                json_encode($value, JSON_THROW_ON_ERROR),
1060
+                $lazy,
1061
+                $flags,
1062
+                ValueType::ARRAY
1063
+            );
1064
+        } catch (JsonException $e) {
1065
+            $this->logger->warning('could not setValueArray', ['app' => $app, 'key' => $key, 'exception' => $e]);
1066
+            throw $e;
1067
+        }
1068
+    }
1069
+
1070
+    /**
1071
+     * Store a config key and its value in database
1072
+     *
1073
+     * If config key is already known with the exact same config value and same sensitive/lazy status, the
1074
+     * database is not updated. If config value was previously stored as sensitive, status will not be
1075
+     * altered.
1076
+     *
1077
+     * @param string $userId id of the user
1078
+     * @param string $app id of the app
1079
+     * @param string $key config key
1080
+     * @param string $value config value
1081
+     * @param bool $lazy config set as lazy loaded
1082
+     * @param ValueType $type value type
1083
+     *
1084
+     * @return bool TRUE if value was updated in database
1085
+     * @throws TypeConflictException if type from database is not VALUE_MIXED and different from the requested one
1086
+     * @see IUserConfig for explanation about lazy loading
1087
+     */
1088
+    private function setTypedValue(
1089
+        string $userId,
1090
+        string $app,
1091
+        string $key,
1092
+        string $value,
1093
+        bool $lazy,
1094
+        int $flags,
1095
+        ValueType $type,
1096
+    ): bool {
1097
+        // Primary email addresses are always(!) expected to be lowercase
1098
+        if ($app === 'settings' && $key === 'email') {
1099
+            $value = strtolower($value);
1100
+        }
1101
+
1102
+        $this->assertParams($userId, $app, $key);
1103
+        if (!$this->matchAndApplyLexiconDefinition($userId, $app, $key, $lazy, $type, $flags)) {
1104
+            // returns false as database is not updated
1105
+            return false;
1106
+        }
1107
+        $this->loadConfig($userId, $lazy);
1108
+
1109
+        $inserted = $refreshCache = false;
1110
+        $origValue = $value;
1111
+        $sensitive = $this->isFlagged(self::FLAG_SENSITIVE, $flags);
1112
+        if ($sensitive || ($this->hasKey($userId, $app, $key, $lazy) && $this->isSensitive($userId, $app, $key, $lazy))) {
1113
+            $value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
1114
+            $flags |= self::FLAG_SENSITIVE;
1115
+        }
1116
+
1117
+        // if requested, we fill the 'indexed' field with current value
1118
+        $indexed = '';
1119
+        if ($type !== ValueType::ARRAY && $this->isFlagged(self::FLAG_INDEXED, $flags)) {
1120
+            if ($this->isFlagged(self::FLAG_SENSITIVE, $flags)) {
1121
+                $this->logger->warning('sensitive value are not to be indexed');
1122
+            } elseif (strlen($value) > self::USER_MAX_LENGTH) {
1123
+                $this->logger->warning('value is too lengthy to be indexed');
1124
+            } else {
1125
+                $indexed = $value;
1126
+            }
1127
+        }
1128
+
1129
+        $oldValue = null;
1130
+        if ($this->hasKey($userId, $app, $key, $lazy)) {
1131
+            /**
1132
+             * no update if key is already known with set lazy status and value is
1133
+             * not different, unless sensitivity is switched from false to true.
1134
+             */
1135
+            $oldValue = $this->getTypedValue($userId, $app, $key, $value, $lazy, $type);
1136
+            if ($origValue === $oldValue
1137
+                && (!$sensitive || $this->isSensitive($userId, $app, $key, $lazy))) {
1138
+                return false;
1139
+            }
1140
+        } else {
1141
+            /**
1142
+             * if key is not known yet, we try to insert.
1143
+             * It might fail if the key exists with a different lazy flag.
1144
+             */
1145
+            try {
1146
+                $insert = $this->connection->getQueryBuilder();
1147
+                $insert->insert('preferences')
1148
+                    ->setValue('userid', $insert->createNamedParameter($userId))
1149
+                    ->setValue('appid', $insert->createNamedParameter($app))
1150
+                    ->setValue('lazy', $insert->createNamedParameter(($lazy) ? 1 : 0, IQueryBuilder::PARAM_INT))
1151
+                    ->setValue('type', $insert->createNamedParameter($type->value, IQueryBuilder::PARAM_INT))
1152
+                    ->setValue('flags', $insert->createNamedParameter($flags, IQueryBuilder::PARAM_INT))
1153
+                    ->setValue('indexed', $insert->createNamedParameter($indexed))
1154
+                    ->setValue('configkey', $insert->createNamedParameter($key))
1155
+                    ->setValue('configvalue', $insert->createNamedParameter($value));
1156
+                $insert->executeStatement();
1157
+                $inserted = true;
1158
+            } catch (DBException $e) {
1159
+                if ($e->getReason() !== DBException::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
1160
+                    // TODO: throw exception or just log and returns false !?
1161
+                    throw $e;
1162
+                }
1163
+            }
1164
+        }
1165
+
1166
+        /**
1167
+         * We cannot insert a new row, meaning we need to update an already existing one
1168
+         */
1169
+        if (!$inserted) {
1170
+            $currType = $this->valueDetails[$userId][$app][$key]['type'] ?? null;
1171
+            if ($currType === null) { // this might happen when switching lazy loading status
1172
+                $this->loadConfigAll($userId);
1173
+                $currType = $this->valueDetails[$userId][$app][$key]['type'];
1174
+            }
1175
+
1176
+            /**
1177
+             * We only log a warning and set it to VALUE_MIXED.
1178
+             */
1179
+            if ($currType === null) {
1180
+                $this->logger->warning('Value type is set to zero (0) in database. This is not supposed to happens', ['app' => $app, 'key' => $key]);
1181
+                $currType = ValueType::MIXED;
1182
+            }
1183
+
1184
+            /**
1185
+             * we only accept a different type from the one stored in database
1186
+             * if the one stored in database is not-defined (VALUE_MIXED)
1187
+             */
1188
+            if ($currType !== ValueType::MIXED
1189
+                && $currType !== $type) {
1190
+                try {
1191
+                    $currTypeDef = $currType->getDefinition();
1192
+                    $typeDef = $type->getDefinition();
1193
+                } catch (IncorrectTypeException) {
1194
+                    $currTypeDef = $currType->value;
1195
+                    $typeDef = $type->value;
1196
+                }
1197
+                throw new TypeConflictException('conflict between new type (' . $typeDef . ') and old type (' . $currTypeDef . ')');
1198
+            }
1199
+
1200
+            if ($lazy !== $this->isLazy($userId, $app, $key)) {
1201
+                $refreshCache = true;
1202
+            }
1203
+
1204
+            $update = $this->connection->getQueryBuilder();
1205
+            $update->update('preferences')
1206
+                ->set('configvalue', $update->createNamedParameter($value))
1207
+                ->set('lazy', $update->createNamedParameter(($lazy) ? 1 : 0, IQueryBuilder::PARAM_INT))
1208
+                ->set('type', $update->createNamedParameter($type->value, IQueryBuilder::PARAM_INT))
1209
+                ->set('flags', $update->createNamedParameter($flags, IQueryBuilder::PARAM_INT))
1210
+                ->set('indexed', $update->createNamedParameter($indexed))
1211
+                ->where($update->expr()->eq('userid', $update->createNamedParameter($userId)))
1212
+                ->andWhere($update->expr()->eq('appid', $update->createNamedParameter($app)))
1213
+                ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
1214
+
1215
+            $update->executeStatement();
1216
+        }
1217
+
1218
+        $this->dispatcher->dispatchTyped(new UserConfigChangedEvent($userId, $app, $key, $value, $oldValue));
1219
+
1220
+        if ($refreshCache) {
1221
+            $this->clearCache($userId);
1222
+            return true;
1223
+        }
1224
+
1225
+        // update local cache
1226
+        if ($lazy) {
1227
+            $this->lazyCache[$userId][$app][$key] = $value;
1228
+        } else {
1229
+            $this->fastCache[$userId][$app][$key] = $value;
1230
+        }
1231
+        $this->valueDetails[$userId][$app][$key] = [
1232
+            'type' => $type,
1233
+            'flags' => $flags
1234
+        ];
1235
+
1236
+        return true;
1237
+    }
1238
+
1239
+    /**
1240
+     * Change the type of config value.
1241
+     *
1242
+     * **WARNING:** Method is internal and **MUST** not be used as it may break things.
1243
+     *
1244
+     * @param string $userId id of the user
1245
+     * @param string $app id of the app
1246
+     * @param string $key config key
1247
+     * @param ValueType $type value type
1248
+     *
1249
+     * @return bool TRUE if database update were necessary
1250
+     * @throws UnknownKeyException if $key is now known in database
1251
+     * @throws IncorrectTypeException if $type is not valid
1252
+     * @internal
1253
+     * @since 31.0.0
1254
+     */
1255
+    public function updateType(string $userId, string $app, string $key, ValueType $type = ValueType::MIXED): bool {
1256
+        $this->assertParams($userId, $app, $key);
1257
+        $this->loadConfigAll($userId);
1258
+        $this->matchAndApplyLexiconDefinition($userId, $app, $key);
1259
+        $this->isLazy($userId, $app, $key); // confirm key exists
1260
+
1261
+        $update = $this->connection->getQueryBuilder();
1262
+        $update->update('preferences')
1263
+            ->set('type', $update->createNamedParameter($type->value, IQueryBuilder::PARAM_INT))
1264
+            ->where($update->expr()->eq('userid', $update->createNamedParameter($userId)))
1265
+            ->andWhere($update->expr()->eq('appid', $update->createNamedParameter($app)))
1266
+            ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
1267
+        $update->executeStatement();
1268
+
1269
+        $this->valueDetails[$userId][$app][$key]['type'] = $type;
1270
+
1271
+        return true;
1272
+    }
1273
+
1274
+    /**
1275
+     * @inheritDoc
1276
+     *
1277
+     * @param string $userId id of the user
1278
+     * @param string $app id of the app
1279
+     * @param string $key config key
1280
+     * @param bool $sensitive TRUE to set as sensitive, FALSE to unset
1281
+     *
1282
+     * @return bool TRUE if entry was found in database and an update was necessary
1283
+     * @since 31.0.0
1284
+     */
1285
+    public function updateSensitive(string $userId, string $app, string $key, bool $sensitive): bool {
1286
+        $this->assertParams($userId, $app, $key);
1287
+        $this->loadConfigAll($userId);
1288
+        $this->matchAndApplyLexiconDefinition($userId, $app, $key);
1289
+
1290
+        try {
1291
+            if ($sensitive === $this->isSensitive($userId, $app, $key, null)) {
1292
+                return false;
1293
+            }
1294
+        } catch (UnknownKeyException) {
1295
+            return false;
1296
+        }
1297
+
1298
+        $lazy = $this->isLazy($userId, $app, $key);
1299
+        if ($lazy) {
1300
+            $cache = $this->lazyCache;
1301
+        } else {
1302
+            $cache = $this->fastCache;
1303
+        }
1304
+
1305
+        if (!isset($cache[$userId][$app][$key])) {
1306
+            throw new UnknownKeyException('unknown config key');
1307
+        }
1308
+
1309
+        $value = $cache[$userId][$app][$key];
1310
+        $flags = $this->getValueFlags($userId, $app, $key);
1311
+        if ($sensitive) {
1312
+            $flags |= self::FLAG_SENSITIVE;
1313
+            $value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
1314
+        } else {
1315
+            $flags &= ~self::FLAG_SENSITIVE;
1316
+            $this->decryptSensitiveValue($userId, $app, $key, $value);
1317
+        }
1318
+
1319
+        $update = $this->connection->getQueryBuilder();
1320
+        $update->update('preferences')
1321
+            ->set('flags', $update->createNamedParameter($flags, IQueryBuilder::PARAM_INT))
1322
+            ->set('configvalue', $update->createNamedParameter($value))
1323
+            ->where($update->expr()->eq('userid', $update->createNamedParameter($userId)))
1324
+            ->andWhere($update->expr()->eq('appid', $update->createNamedParameter($app)))
1325
+            ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
1326
+        $update->executeStatement();
1327
+
1328
+        $this->valueDetails[$userId][$app][$key]['flags'] = $flags;
1329
+
1330
+        return true;
1331
+    }
1332
+
1333
+    /**
1334
+     * @inheritDoc
1335
+     *
1336
+     * @param string $app
1337
+     * @param string $key
1338
+     * @param bool $sensitive
1339
+     *
1340
+     * @since 31.0.0
1341
+     */
1342
+    public function updateGlobalSensitive(string $app, string $key, bool $sensitive): void {
1343
+        $this->assertParams('', $app, $key, allowEmptyUser: true);
1344
+        $this->matchAndApplyLexiconDefinition('', $app, $key);
1345
+
1346
+        foreach (array_keys($this->getValuesByUsers($app, $key)) as $userId) {
1347
+            try {
1348
+                $this->updateSensitive($userId, $app, $key, $sensitive);
1349
+            } catch (UnknownKeyException) {
1350
+                // should not happen and can be ignored
1351
+            }
1352
+        }
1353
+
1354
+        // we clear all cache
1355
+        $this->clearCacheAll();
1356
+    }
1357
+
1358
+    /**
1359
+     * @inheritDoc
1360
+     *
1361
+     * @param string $userId
1362
+     * @param string $app
1363
+     * @param string $key
1364
+     * @param bool $indexed
1365
+     *
1366
+     * @return bool
1367
+     * @throws DBException
1368
+     * @throws IncorrectTypeException
1369
+     * @throws UnknownKeyException
1370
+     * @since 31.0.0
1371
+     */
1372
+    public function updateIndexed(string $userId, string $app, string $key, bool $indexed): bool {
1373
+        $this->assertParams($userId, $app, $key);
1374
+        $this->loadConfigAll($userId);
1375
+        $this->matchAndApplyLexiconDefinition($userId, $app, $key);
1376
+
1377
+        try {
1378
+            if ($indexed === $this->isIndexed($userId, $app, $key, null)) {
1379
+                return false;
1380
+            }
1381
+        } catch (UnknownKeyException) {
1382
+            return false;
1383
+        }
1384
+
1385
+        $lazy = $this->isLazy($userId, $app, $key);
1386
+        if ($lazy) {
1387
+            $cache = $this->lazyCache;
1388
+        } else {
1389
+            $cache = $this->fastCache;
1390
+        }
1391
+
1392
+        if (!isset($cache[$userId][$app][$key])) {
1393
+            throw new UnknownKeyException('unknown config key');
1394
+        }
1395
+
1396
+        $value = $cache[$userId][$app][$key];
1397
+        $flags = $this->getValueFlags($userId, $app, $key);
1398
+        if ($indexed) {
1399
+            $indexed = $value;
1400
+        } else {
1401
+            $flags &= ~self::FLAG_INDEXED;
1402
+            $indexed = '';
1403
+        }
1404
+
1405
+        $update = $this->connection->getQueryBuilder();
1406
+        $update->update('preferences')
1407
+            ->set('flags', $update->createNamedParameter($flags, IQueryBuilder::PARAM_INT))
1408
+            ->set('indexed', $update->createNamedParameter($indexed))
1409
+            ->where($update->expr()->eq('userid', $update->createNamedParameter($userId)))
1410
+            ->andWhere($update->expr()->eq('appid', $update->createNamedParameter($app)))
1411
+            ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
1412
+        $update->executeStatement();
1413
+
1414
+        $this->valueDetails[$userId][$app][$key]['flags'] = $flags;
1415
+
1416
+        return true;
1417
+    }
1418
+
1419
+
1420
+    /**
1421
+     * @inheritDoc
1422
+     *
1423
+     * @param string $app
1424
+     * @param string $key
1425
+     * @param bool $indexed
1426
+     *
1427
+     * @since 31.0.0
1428
+     */
1429
+    public function updateGlobalIndexed(string $app, string $key, bool $indexed): void {
1430
+        $this->assertParams('', $app, $key, allowEmptyUser: true);
1431
+        $this->matchAndApplyLexiconDefinition('', $app, $key);
1432
+
1433
+        $update = $this->connection->getQueryBuilder();
1434
+        $update->update('preferences')
1435
+            ->where(
1436
+                $update->expr()->eq('appid', $update->createNamedParameter($app)),
1437
+                $update->expr()->eq('configkey', $update->createNamedParameter($key))
1438
+            );
1439
+
1440
+        // switching flags 'indexed' on and off is about adding/removing the bit value on the correct entries
1441
+        if ($indexed) {
1442
+            $update->set('indexed', $update->func()->substring('configvalue', $update->createNamedParameter(1, IQueryBuilder::PARAM_INT), $update->createNamedParameter(64, IQueryBuilder::PARAM_INT)));
1443
+            $update->set('flags', $update->func()->add('flags', $update->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)));
1444
+            $update->andWhere(
1445
+                $update->expr()->neq($update->expr()->castColumn(
1446
+                    $update->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), IQueryBuilder::PARAM_INT), $update->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)
1447
+                ));
1448
+        } else {
1449
+            // emptying field 'indexed' if key is not set as indexed anymore
1450
+            $update->set('indexed', $update->createNamedParameter(''));
1451
+            $update->set('flags', $update->func()->subtract('flags', $update->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)));
1452
+            $update->andWhere(
1453
+                $update->expr()->eq($update->expr()->castColumn(
1454
+                    $update->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), IQueryBuilder::PARAM_INT), $update->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)
1455
+                ));
1456
+        }
1457
+
1458
+        $update->executeStatement();
1459
+
1460
+        // we clear all cache
1461
+        $this->clearCacheAll();
1462
+    }
1463
+
1464
+    /**
1465
+     * @inheritDoc
1466
+     *
1467
+     * @param string $userId id of the user
1468
+     * @param string $app id of the app
1469
+     * @param string $key config key
1470
+     * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset
1471
+     *
1472
+     * @return bool TRUE if entry was found in database and an update was necessary
1473
+     * @since 31.0.0
1474
+     */
1475
+    public function updateLazy(string $userId, string $app, string $key, bool $lazy): bool {
1476
+        $this->assertParams($userId, $app, $key);
1477
+        $this->loadConfigAll($userId);
1478
+        $this->matchAndApplyLexiconDefinition($userId, $app, $key);
1479
+
1480
+        try {
1481
+            if ($lazy === $this->isLazy($userId, $app, $key)) {
1482
+                return false;
1483
+            }
1484
+        } catch (UnknownKeyException) {
1485
+            return false;
1486
+        }
1487
+
1488
+        $update = $this->connection->getQueryBuilder();
1489
+        $update->update('preferences')
1490
+            ->set('lazy', $update->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT))
1491
+            ->where($update->expr()->eq('userid', $update->createNamedParameter($userId)))
1492
+            ->andWhere($update->expr()->eq('appid', $update->createNamedParameter($app)))
1493
+            ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
1494
+        $update->executeStatement();
1495
+
1496
+        // At this point, it is a lot safer to clean cache
1497
+        $this->clearCache($userId);
1498
+
1499
+        return true;
1500
+    }
1501
+
1502
+    /**
1503
+     * @inheritDoc
1504
+     *
1505
+     * @param string $app id of the app
1506
+     * @param string $key config key
1507
+     * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset
1508
+     *
1509
+     * @since 31.0.0
1510
+     */
1511
+    public function updateGlobalLazy(string $app, string $key, bool $lazy): void {
1512
+        $this->assertParams('', $app, $key, allowEmptyUser: true);
1513
+        $this->matchAndApplyLexiconDefinition('', $app, $key);
1514
+
1515
+        $update = $this->connection->getQueryBuilder();
1516
+        $update->update('preferences')
1517
+            ->set('lazy', $update->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT))
1518
+            ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
1519
+            ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
1520
+        $update->executeStatement();
1521
+
1522
+        $this->clearCacheAll();
1523
+    }
1524
+
1525
+    /**
1526
+     * @inheritDoc
1527
+     *
1528
+     * @param string $userId id of the user
1529
+     * @param string $app id of the app
1530
+     * @param string $key config key
1531
+     *
1532
+     * @return array
1533
+     * @throws UnknownKeyException if config key is not known in database
1534
+     * @since 31.0.0
1535
+     */
1536
+    public function getDetails(string $userId, string $app, string $key): array {
1537
+        $this->assertParams($userId, $app, $key);
1538
+        $this->loadConfigAll($userId);
1539
+        $this->matchAndApplyLexiconDefinition($userId, $app, $key);
1540
+
1541
+        $lazy = $this->isLazy($userId, $app, $key);
1542
+
1543
+        if ($lazy) {
1544
+            $cache = $this->lazyCache[$userId];
1545
+        } else {
1546
+            $cache = $this->fastCache[$userId];
1547
+        }
1548
+
1549
+        $type = $this->getValueType($userId, $app, $key);
1550
+        try {
1551
+            $typeString = $type->getDefinition();
1552
+        } catch (IncorrectTypeException $e) {
1553
+            $this->logger->warning('type stored in database is not correct', ['exception' => $e, 'type' => $type]);
1554
+            $typeString = (string)$type->value;
1555
+        }
1556
+
1557
+        if (!isset($cache[$app][$key])) {
1558
+            throw new UnknownKeyException('unknown config key');
1559
+        }
1560
+
1561
+        $value = $cache[$app][$key];
1562
+        $sensitive = $this->isSensitive($userId, $app, $key, null);
1563
+        $this->decryptSensitiveValue($userId, $app, $key, $value);
1564
+
1565
+        return [
1566
+            'userId' => $userId,
1567
+            'app' => $app,
1568
+            'key' => $key,
1569
+            'value' => $value,
1570
+            'type' => $type->value,
1571
+            'lazy' => $lazy,
1572
+            'typeString' => $typeString,
1573
+            'sensitive' => $sensitive
1574
+        ];
1575
+    }
1576
+
1577
+    /**
1578
+     * @inheritDoc
1579
+     *
1580
+     * @param string $userId id of the user
1581
+     * @param string $app id of the app
1582
+     * @param string $key config key
1583
+     *
1584
+     * @since 31.0.0
1585
+     */
1586
+    public function deleteUserConfig(string $userId, string $app, string $key): void {
1587
+        $this->assertParams($userId, $app, $key);
1588
+        $this->matchAndApplyLexiconDefinition($userId, $app, $key);
1589
+
1590
+        $qb = $this->connection->getQueryBuilder();
1591
+        $qb->delete('preferences')
1592
+            ->where($qb->expr()->eq('userid', $qb->createNamedParameter($userId)))
1593
+            ->andWhere($qb->expr()->eq('appid', $qb->createNamedParameter($app)))
1594
+            ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)));
1595
+        $qb->executeStatement();
1596
+
1597
+        unset($this->lazyCache[$userId][$app][$key]);
1598
+        unset($this->fastCache[$userId][$app][$key]);
1599
+        unset($this->valueDetails[$userId][$app][$key]);
1600
+    }
1601
+
1602
+    /**
1603
+     * @inheritDoc
1604
+     *
1605
+     * @param string $app id of the app
1606
+     * @param string $key config key
1607
+     *
1608
+     * @since 31.0.0
1609
+     */
1610
+    public function deleteKey(string $app, string $key): void {
1611
+        $this->assertParams('', $app, $key, allowEmptyUser: true);
1612
+        $this->matchAndApplyLexiconDefinition('', $app, $key);
1613
+
1614
+        $qb = $this->connection->getQueryBuilder();
1615
+        $qb->delete('preferences')
1616
+            ->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)))
1617
+            ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)));
1618
+        $qb->executeStatement();
1619
+
1620
+        $this->clearCacheAll();
1621
+    }
1622
+
1623
+    /**
1624
+     * @inheritDoc
1625
+     *
1626
+     * @param string $app id of the app
1627
+     *
1628
+     * @since 31.0.0
1629
+     */
1630
+    public function deleteApp(string $app): void {
1631
+        $this->assertParams('', $app, allowEmptyUser: true);
1632
+
1633
+        $qb = $this->connection->getQueryBuilder();
1634
+        $qb->delete('preferences')
1635
+            ->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)));
1636
+        $qb->executeStatement();
1637
+
1638
+        $this->clearCacheAll();
1639
+    }
1640
+
1641
+    public function deleteAllUserConfig(string $userId): void {
1642
+        $this->assertParams($userId, '', allowEmptyApp: true);
1643
+        $qb = $this->connection->getQueryBuilder();
1644
+        $qb->delete('preferences')
1645
+            ->where($qb->expr()->eq('userid', $qb->createNamedParameter($userId)));
1646
+        $qb->executeStatement();
1647
+
1648
+        $this->clearCache($userId);
1649
+    }
1650
+
1651
+    /**
1652
+     * @inheritDoc
1653
+     *
1654
+     * @param string $userId id of the user
1655
+     * @param bool $reload set to TRUE to refill cache instantly after clearing it.
1656
+     *
1657
+     * @since 31.0.0
1658
+     */
1659
+    public function clearCache(string $userId, bool $reload = false): void {
1660
+        $this->assertParams($userId, allowEmptyApp: true);
1661
+        $this->lazyLoaded[$userId] = $this->fastLoaded[$userId] = false;
1662
+        $this->lazyCache[$userId] = $this->fastCache[$userId] = $this->valueDetails[$userId] = [];
1663
+
1664
+        if (!$reload) {
1665
+            return;
1666
+        }
1667
+
1668
+        $this->loadConfigAll($userId);
1669
+    }
1670
+
1671
+    /**
1672
+     * @inheritDoc
1673
+     *
1674
+     * @since 31.0.0
1675
+     */
1676
+    public function clearCacheAll(): void {
1677
+        $this->lazyLoaded = $this->fastLoaded = [];
1678
+        $this->lazyCache = $this->fastCache = $this->valueDetails = $this->configLexiconDetails = [];
1679
+    }
1680
+
1681
+    /**
1682
+     * For debug purpose.
1683
+     * Returns the cached data.
1684
+     *
1685
+     * @return array
1686
+     * @since 31.0.0
1687
+     * @internal
1688
+     */
1689
+    public function statusCache(): array {
1690
+        return [
1691
+            'fastLoaded' => $this->fastLoaded,
1692
+            'fastCache' => $this->fastCache,
1693
+            'lazyLoaded' => $this->lazyLoaded,
1694
+            'lazyCache' => $this->lazyCache,
1695
+            'valueDetails' => $this->valueDetails,
1696
+        ];
1697
+    }
1698
+
1699
+    /**
1700
+     * @param int $needle bitflag to search
1701
+     * @param int $flags all flags
1702
+     *
1703
+     * @return bool TRUE if bitflag $needle is set in $flags
1704
+     */
1705
+    private function isFlagged(int $needle, int $flags): bool {
1706
+        return (($needle & $flags) !== 0);
1707
+    }
1708
+
1709
+    /**
1710
+     * Confirm the string set for app and key fit the database description
1711
+     *
1712
+     * @param string $userId
1713
+     * @param string $app assert $app fit in database
1714
+     * @param string $prefKey assert config key fit in database
1715
+     * @param bool $allowEmptyUser
1716
+     * @param bool $allowEmptyApp $app can be empty string
1717
+     * @param ValueType|null $valueType assert value type is only one type
1718
+     * @throws InvalidArgumentException if userId, app, or prefKey is invalid (too long, or empty string)
1719
+     */
1720
+    private function assertParams(
1721
+        string $userId = '',
1722
+        string $app = '',
1723
+        string $prefKey = '',
1724
+        bool $allowEmptyUser = false,
1725
+        bool $allowEmptyApp = false,
1726
+    ): void {
1727
+        if (!$allowEmptyUser && $userId === '') {
1728
+            throw new InvalidArgumentException('userId cannot be an empty string');
1729
+        }
1730
+        if (!$allowEmptyApp && $app === '') {
1731
+            throw new InvalidArgumentException('app cannot be an empty string');
1732
+        }
1733
+        if (strlen($userId) > self::USER_MAX_LENGTH) {
1734
+            throw new InvalidArgumentException('Value (' . $userId . ') for userId is too long (' . self::USER_MAX_LENGTH . ')');
1735
+        }
1736
+        if (strlen($app) > self::APP_MAX_LENGTH) {
1737
+            throw new InvalidArgumentException('Value (' . $app . ') for app is too long (' . self::APP_MAX_LENGTH . ')');
1738
+        }
1739
+        if (strlen($prefKey) > self::KEY_MAX_LENGTH) {
1740
+            throw new InvalidArgumentException('Value (' . $prefKey . ') for key is too long (' . self::KEY_MAX_LENGTH . ')');
1741
+        }
1742
+    }
1743
+
1744
+    private function loadConfigAll(string $userId): void {
1745
+        $this->loadConfig($userId, null);
1746
+    }
1747
+
1748
+    /**
1749
+     * Load normal config or config set as lazy loaded
1750
+     *
1751
+     * @param bool|null $lazy set to TRUE to load config set as lazy loaded, set to NULL to load all config
1752
+     */
1753
+    private function loadConfig(string $userId, ?bool $lazy = false): void {
1754
+        if ($this->isLoaded($userId, $lazy)) {
1755
+            return;
1756
+        }
1757
+
1758
+        if (($lazy ?? true) !== false) { // if lazy is null or true, we debug log
1759
+            $this->logger->debug('The loading of lazy UserConfig values have been requested', ['exception' => new \RuntimeException('ignorable exception')]);
1760
+        }
1761
+
1762
+        $qb = $this->connection->getQueryBuilder();
1763
+        $qb->from('preferences');
1764
+        $qb->select('appid', 'configkey', 'configvalue', 'type', 'flags');
1765
+        $qb->where($qb->expr()->eq('userid', $qb->createNamedParameter($userId)));
1766
+
1767
+        // we only need value from lazy when loadConfig does not specify it
1768
+        if ($lazy !== null) {
1769
+            $qb->andWhere($qb->expr()->eq('lazy', $qb->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT)));
1770
+        } else {
1771
+            $qb->addSelect('lazy');
1772
+        }
1773
+
1774
+        $result = $qb->executeQuery();
1775
+        $rows = $result->fetchAll();
1776
+        foreach ($rows as $row) {
1777
+            if (($row['lazy'] ?? ($lazy ?? 0) ? 1 : 0) === 1) {
1778
+                $this->lazyCache[$userId][$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
1779
+            } else {
1780
+                $this->fastCache[$userId][$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
1781
+            }
1782
+            $this->valueDetails[$userId][$row['appid']][$row['configkey']] = ['type' => ValueType::from((int)($row['type'] ?? 0)), 'flags' => (int)$row['flags']];
1783
+        }
1784
+        $result->closeCursor();
1785
+        $this->setAsLoaded($userId, $lazy);
1786
+    }
1787
+
1788
+    /**
1789
+     * if $lazy is:
1790
+     *  - false: will returns true if fast config are loaded
1791
+     *  - true : will returns true if lazy config are loaded
1792
+     *  - null : will returns true if both config are loaded
1793
+     *
1794
+     * @param string $userId
1795
+     * @param bool $lazy
1796
+     *
1797
+     * @return bool
1798
+     */
1799
+    private function isLoaded(string $userId, ?bool $lazy): bool {
1800
+        if ($lazy === null) {
1801
+            return ($this->lazyLoaded[$userId] ?? false) && ($this->fastLoaded[$userId] ?? false);
1802
+        }
1803
+
1804
+        return $lazy ? $this->lazyLoaded[$userId] ?? false : $this->fastLoaded[$userId] ?? false;
1805
+    }
1806
+
1807
+    /**
1808
+     * if $lazy is:
1809
+     * - false: set fast config as loaded
1810
+     * - true : set lazy config as loaded
1811
+     * - null : set both config as loaded
1812
+     *
1813
+     * @param string $userId
1814
+     * @param bool $lazy
1815
+     */
1816
+    private function setAsLoaded(string $userId, ?bool $lazy): void {
1817
+        if ($lazy === null) {
1818
+            $this->fastLoaded[$userId] = $this->lazyLoaded[$userId] = true;
1819
+            return;
1820
+        }
1821
+
1822
+        // We also create empty entry to keep both fastLoaded/lazyLoaded synced
1823
+        if ($lazy) {
1824
+            $this->lazyLoaded[$userId] = true;
1825
+            $this->fastLoaded[$userId] = $this->fastLoaded[$userId] ?? false;
1826
+            $this->fastCache[$userId] = $this->fastCache[$userId] ?? [];
1827
+        } else {
1828
+            $this->fastLoaded[$userId] = true;
1829
+            $this->lazyLoaded[$userId] = $this->lazyLoaded[$userId] ?? false;
1830
+            $this->lazyCache[$userId] = $this->lazyCache[$userId] ?? [];
1831
+        }
1832
+    }
1833
+
1834
+    /**
1835
+     * **Warning:** this will load all lazy values from the database
1836
+     *
1837
+     * @param string $userId id of the user
1838
+     * @param string $app id of the app
1839
+     * @param bool $filtered TRUE to hide sensitive config values. Value are replaced by {@see IConfig::SENSITIVE_VALUE}
1840
+     *
1841
+     * @return array<string, string|int|float|bool|array>
1842
+     */
1843
+    private function formatAppValues(string $userId, string $app, array $values, bool $filtered = false): array {
1844
+        foreach ($values as $key => $value) {
1845
+            //$key = (string)$key;
1846
+            try {
1847
+                $type = $this->getValueType($userId, $app, (string)$key);
1848
+            } catch (UnknownKeyException) {
1849
+                continue;
1850
+            }
1851
+
1852
+            if ($this->isFlagged(self::FLAG_SENSITIVE, $this->valueDetails[$userId][$app][$key]['flags'] ?? 0)) {
1853
+                if ($filtered) {
1854
+                    $value = IConfig::SENSITIVE_VALUE;
1855
+                    $type = ValueType::STRING;
1856
+                } else {
1857
+                    $this->decryptSensitiveValue($userId, $app, (string)$key, $value);
1858
+                }
1859
+            }
1860
+
1861
+            $values[$key] = $this->convertTypedValue($value, $type);
1862
+        }
1863
+
1864
+        return $values;
1865
+    }
1866
+
1867
+    /**
1868
+     * convert string value to the expected type
1869
+     *
1870
+     * @param string $value
1871
+     * @param ValueType $type
1872
+     *
1873
+     * @return string|int|float|bool|array
1874
+     */
1875
+    private function convertTypedValue(string $value, ValueType $type): string|int|float|bool|array {
1876
+        switch ($type) {
1877
+            case ValueType::INT:
1878
+                return (int)$value;
1879
+            case ValueType::FLOAT:
1880
+                return (float)$value;
1881
+            case ValueType::BOOL:
1882
+                return in_array(strtolower($value), ['1', 'true', 'yes', 'on']);
1883
+            case ValueType::ARRAY:
1884
+                try {
1885
+                    return json_decode($value, true, flags: JSON_THROW_ON_ERROR);
1886
+                } catch (JsonException) {
1887
+                    // ignoreable
1888
+                }
1889
+                break;
1890
+        }
1891
+        return $value;
1892
+    }
1893
+
1894
+
1895
+    /**
1896
+     * will change referenced $value with the decrypted value in case of encrypted (sensitive value)
1897
+     *
1898
+     * @param string $userId
1899
+     * @param string $app
1900
+     * @param string $key
1901
+     * @param string $value
1902
+     */
1903
+    private function decryptSensitiveValue(string $userId, string $app, string $key, string &$value): void {
1904
+        if (!$this->isFlagged(self::FLAG_SENSITIVE, $this->valueDetails[$userId][$app][$key]['flags'] ?? 0)) {
1905
+            return;
1906
+        }
1907
+
1908
+        if (!str_starts_with($value, self::ENCRYPTION_PREFIX)) {
1909
+            return;
1910
+        }
1911
+
1912
+        try {
1913
+            $value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
1914
+        } catch (\Exception $e) {
1915
+            $this->logger->warning('could not decrypt sensitive value', [
1916
+                'userId' => $userId,
1917
+                'app' => $app,
1918
+                'key' => $key,
1919
+                'value' => $value,
1920
+                'exception' => $e
1921
+            ]);
1922
+        }
1923
+    }
1924
+
1925
+    /**
1926
+     * Match and apply current use of config values with defined lexicon.
1927
+     * Set $lazy to NULL only if only interested into checking that $key is alias.
1928
+     *
1929
+     * @throws UnknownKeyException
1930
+     * @throws TypeConflictException
1931
+     * @return bool FALSE if conflict with defined lexicon were observed in the process
1932
+     */
1933
+    private function matchAndApplyLexiconDefinition(
1934
+        string $userId,
1935
+        string $app,
1936
+        string &$key,
1937
+        ?bool &$lazy = null,
1938
+        ValueType &$type = ValueType::MIXED,
1939
+        int &$flags = 0,
1940
+        ?string &$default = null,
1941
+    ): bool {
1942
+        $configDetails = $this->getConfigDetailsFromLexicon($app);
1943
+        if (array_key_exists($key, $configDetails['aliases']) && !$this->ignoreLexiconAliases) {
1944
+            // in case '$rename' is set in ConfigLexiconEntry, we use the new config key
1945
+            $key = $configDetails['aliases'][$key];
1946
+        }
1947
+
1948
+        if (!array_key_exists($key, $configDetails['entries'])) {
1949
+            return $this->applyLexiconStrictness($configDetails['strictness'], $app . '/' . $key);
1950
+        }
1951
+
1952
+        // if lazy is NULL, we ignore all check on the type/lazyness/default from Lexicon
1953
+        if ($lazy === null) {
1954
+            return true;
1955
+        }
1956
+
1957
+        /** @var Entry $configValue */
1958
+        $configValue = $configDetails['entries'][$key];
1959
+        if ($type === ValueType::MIXED) {
1960
+            // we overwrite if value was requested as mixed
1961
+            $type = $configValue->getValueType();
1962
+        } elseif ($configValue->getValueType() !== $type) {
1963
+            throw new TypeConflictException('The user config key ' . $app . '/' . $key . ' is typed incorrectly in relation to the config lexicon');
1964
+        }
1965
+
1966
+        $lazy = $configValue->isLazy();
1967
+        $flags = $configValue->getFlags();
1968
+        if ($configValue->isDeprecated()) {
1969
+            $this->logger->notice('User config key ' . $app . '/' . $key . ' is set as deprecated.');
1970
+        }
1971
+
1972
+        $enforcedValue = $this->config->getSystemValue('lexicon.default.userconfig.enforced', [])[$app][$key] ?? false;
1973
+        if (!$enforcedValue && $this->hasKey($userId, $app, $key, $lazy)) {
1974
+            // if key exists there should be no need to extract default
1975
+            return true;
1976
+        }
1977
+
1978
+        // only look for default if needed, default from Lexicon got priority if not overwritten by admin
1979
+        if ($default !== null) {
1980
+            $default = $this->getSystemDefault($app, $configValue) ?? $configValue->getDefault($this->presetManager->getLexiconPreset()) ?? $default;
1981
+        }
1982
+
1983
+        // returning false will make get() returning $default and set() not changing value in database
1984
+        return !$enforcedValue;
1985
+    }
1986
+
1987
+    /**
1988
+     * get default value set in config/config.php if stored in key:
1989
+     *
1990
+     * 'lexicon.default.userconfig' => [
1991
+     *        <appId> => [
1992
+     *           <configKey> => 'my value',
1993
+     *        ]
1994
+     *     ],
1995
+     *
1996
+     * The entry is converted to string to fit the expected type when managing default value
1997
+     */
1998
+    private function getSystemDefault(string $appId, Entry $configValue): ?string {
1999
+        $default = $this->config->getSystemValue('lexicon.default.userconfig', [])[$appId][$configValue->getKey()] ?? null;
2000
+        if ($default === null) {
2001
+            // no system default, using default default.
2002
+            return null;
2003
+        }
2004
+
2005
+        return $configValue->convertToString($default);
2006
+    }
2007
+
2008
+    /**
2009
+     * manage ConfigLexicon behavior based on strictness set in IConfigLexicon
2010
+     *
2011
+     * @param Strictness|null $strictness
2012
+     * @param string $line
2013
+     *
2014
+     * @return bool TRUE if conflict can be fully ignored
2015
+     * @throws UnknownKeyException
2016
+     * @see ILexicon::getStrictness()
2017
+     */
2018
+    private function applyLexiconStrictness(?Strictness $strictness, string $configAppKey): bool {
2019
+        if ($strictness === null) {
2020
+            return true;
2021
+        }
2022
+
2023
+        $line = 'The user config key ' . $configAppKey . ' is not defined in the config lexicon';
2024
+        switch ($strictness) {
2025
+            case Strictness::IGNORE:
2026
+                return true;
2027
+            case Strictness::NOTICE:
2028
+                if (!in_array($configAppKey, $this->strictnessApplied, true)) {
2029
+                    $this->strictnessApplied[] = $configAppKey;
2030
+                    $this->logger->notice($line);
2031
+                }
2032
+                return true;
2033
+            case Strictness::WARNING:
2034
+                if (!in_array($configAppKey, $this->strictnessApplied, true)) {
2035
+                    $this->strictnessApplied[] = $configAppKey;
2036
+                    $this->logger->warning($line);
2037
+                }
2038
+                return false;
2039
+            case Strictness::EXCEPTION:
2040
+                throw new UnknownKeyException($line);
2041
+        }
2042
+
2043
+        throw new UnknownKeyException($line);
2044
+    }
2045
+
2046
+    /**
2047
+     * extract details from registered $appId's config lexicon
2048
+     *
2049
+     * @param string $appId
2050
+     *
2051
+     * @return array{entries: array<string, Entry>, aliases: array<string, string>, strictness: Strictness}
2052
+     * @internal
2053
+     */
2054
+    public function getConfigDetailsFromLexicon(string $appId): array {
2055
+        if (!array_key_exists($appId, $this->configLexiconDetails)) {
2056
+            $entries = $aliases = [];
2057
+            $bootstrapCoordinator = \OCP\Server::get(Coordinator::class);
2058
+            $configLexicon = $bootstrapCoordinator->getRegistrationContext()?->getConfigLexicon($appId);
2059
+            foreach ($configLexicon?->getUserConfigs() ?? [] as $configEntry) {
2060
+                $entries[$configEntry->getKey()] = $configEntry;
2061
+                if ($configEntry->getRename() !== null) {
2062
+                    $aliases[$configEntry->getRename()] = $configEntry->getKey();
2063
+                }
2064
+            }
2065
+
2066
+            $this->configLexiconDetails[$appId] = [
2067
+                'entries' => $entries,
2068
+                'aliases' => $aliases,
2069
+                'strictness' => $configLexicon?->getStrictness() ?? Strictness::IGNORE
2070
+            ];
2071
+        }
2072
+
2073
+        return $this->configLexiconDetails[$appId];
2074
+    }
2075
+
2076
+    /**
2077
+     * get Lexicon Entry using appId and config key entry
2078
+     *
2079
+     * @return Entry|null NULL if entry does not exist in user's Lexicon
2080
+     * @internal
2081
+     */
2082
+    public function getLexiconEntry(string $appId, string $key): ?Entry {
2083
+        return $this->getConfigDetailsFromLexicon($appId)['entries'][$key] ?? null;
2084
+    }
2085
+
2086
+    /**
2087
+     * if set to TRUE, ignore aliases defined in Config Lexicon during the use of the methods of this class
2088
+     *
2089
+     * @internal
2090
+     */
2091
+    public function ignoreLexiconAliases(bool $ignore): void {
2092
+        $this->ignoreLexiconAliases = $ignore;
2093
+    }
2094 2094
 }
Please login to merge, or discard this patch.
lib/public/Config/IUserConfig.php 1 patch
Indentation   +752 added lines, -752 removed lines patch added patch discarded remove patch
@@ -30,756 +30,756 @@
 block discarded – undo
30 30
  */
31 31
 #[Consumable(since: '32.0.0')]
32 32
 interface IUserConfig {
33
-	/**
34
-	 * @since 32.0.0
35
-	 */
36
-	public const FLAG_SENSITIVE = 1;   // value is sensitive
37
-	/**
38
-	 * @since 32.0.0
39
-	 */
40
-	public const FLAG_INDEXED = 2;    // value should be indexed
41
-	/**
42
-	 * @since 33.0.0
43
-	 */
44
-	public const FLAG_INTERNAL = 4;   // value is considered internal and can be hidden from listing
45
-
46
-	/**
47
-	 * Get list of all userIds with config stored in database.
48
-	 * If $appId is specified, will only limit the search to this value
49
-	 *
50
-	 * **WARNING:** ignore any cache and get data directly from database.
51
-	 *
52
-	 * @param string $appId optional id of app
53
-	 *
54
-	 * @return list<string> list of userIds
55
-	 * @throws \InvalidArgumentException if $appId is invalid (too long)
56
-	 *
57
-	 * @since 32.0.0
58
-	 */
59
-	public function getUserIds(string $appId = ''): array;
60
-
61
-	/**
62
-	 * Get list of all apps that have at least one config
63
-	 * value related to $userId stored in database
64
-	 *
65
-	 * **WARNING:** ignore lazy filtering, all user config are loaded from database
66
-	 *
67
-	 * @param string $userId id of the user
68
-	 *
69
-	 * @return list<string> list of app ids
70
-	 * @throws \InvalidArgumentException if $userId is invalid (too long, or empty string)
71
-	 *
72
-	 * @since 32.0.0
73
-	 */
74
-	public function getApps(string $userId): array;
75
-
76
-	/**
77
-	 * Returns all keys stored in database, related to user+app.
78
-	 * Please note that the values are not returned.
79
-	 *
80
-	 * **WARNING:** ignore lazy filtering, all user config are loaded from database
81
-	 *
82
-	 * @param string $userId id of the user
83
-	 * @param string $app id of the app
84
-	 *
85
-	 * @return list<string> list of stored config keys
86
-	 * @throws \InvalidArgumentException if $userId or $app is invalid (too long, or empty string)
87
-	 *
88
-	 * @since 32.0.0
89
-	 */
90
-	public function getKeys(string $userId, string $app): array;
91
-
92
-	/**
93
-	 * Check if a key exists in the list of stored config values.
94
-	 *
95
-	 * @param string $userId id of the user
96
-	 * @param string $app id of the app
97
-	 * @param string $key config key
98
-	 * @param bool $lazy search within lazy loaded config
99
-	 *
100
-	 * @return bool TRUE if key exists
101
-	 * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
102
-	 *
103
-	 * @since 32.0.0
104
-	 */
105
-	public function hasKey(string $userId, string $app, string $key, ?bool $lazy = false): bool;
106
-
107
-	/**
108
-	 * best way to see if a value is set as sensitive (not displayed in report)
109
-	 *
110
-	 * @param string $userId id of the user
111
-	 * @param string $app id of the app
112
-	 * @param string $key config key
113
-	 * @param bool|null $lazy search within lazy loaded config
114
-	 *
115
-	 * @return bool TRUE if value is sensitive
116
-	 * @throws UnknownKeyException if config key is not known
117
-	 * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
118
-	 *
119
-	 * @since 32.0.0
120
-	 */
121
-	public function isSensitive(string $userId, string $app, string $key, ?bool $lazy = false): bool;
122
-
123
-	/**
124
-	 * best way to see if a value is set as indexed (so it can be search)
125
-	 *
126
-	 * @see self::searchUsersByValueString()
127
-	 * @see self::searchUsersByValueInt()
128
-	 * @see self::searchUsersByValueBool()
129
-	 * @see self::searchUsersByValues()
130
-	 *
131
-	 * @param string $userId id of the user
132
-	 * @param string $app id of the app
133
-	 * @param string $key config key
134
-	 * @param bool|null $lazy search within lazy loaded config
135
-	 *
136
-	 * @return bool TRUE if value is sensitive
137
-	 * @throws UnknownKeyException if config key is not known
138
-	 * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
139
-	 *
140
-	 * @since 32.0.0
141
-	 */
142
-	public function isIndexed(string $userId, string $app, string $key, ?bool $lazy = false): bool;
143
-
144
-	/**
145
-	 * Returns if the config key stored in database is lazy loaded
146
-	 *
147
-	 * **WARNING:** ignore lazy filtering, all config values are loaded from database
148
-	 *
149
-	 * @param string $userId id of the user
150
-	 * @param string $app id of the app
151
-	 * @param string $key config key
152
-	 *
153
-	 * @return bool TRUE if config is lazy loaded
154
-	 * @throws UnknownKeyException if config key is not known
155
-	 * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
156
-	 * @see IUserConfig for details about lazy loading
157
-	 *
158
-	 * @since 32.0.0
159
-	 */
160
-	public function isLazy(string $userId, string $app, string $key): bool;
161
-
162
-	/**
163
-	 * List all config values from an app with config key starting with $key.
164
-	 * Returns an array with config key as key, stored value as value.
165
-	 *
166
-	 * **WARNING:** ignore lazy filtering, all config values are loaded from database
167
-	 *
168
-	 * @param string $userId id of the user
169
-	 * @param string $app id of the app
170
-	 * @param string $prefix config keys prefix to search, can be empty.
171
-	 * @param bool $filtered filter sensitive config values
172
-	 *
173
-	 * @return array<string, string|int|float|bool|array> [key => value]
174
-	 * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
175
-	 *
176
-	 * @since 32.0.0
177
-	 */
178
-	public function getValues(string $userId, string $app, string $prefix = '', bool $filtered = false): array;
179
-
180
-	/**
181
-	 * List all config values of a user.
182
-	 * Returns an array with config key as key, stored value as value.
183
-	 *
184
-	 * **WARNING:** ignore lazy filtering, all config values are loaded from database
185
-	 *
186
-	 * @param string $userId id of the user
187
-	 * @param bool $filtered filter sensitive config values
188
-	 *
189
-	 * @return array<string, string|int|float|bool|array> [key => value]
190
-	 * @throws \InvalidArgumentException if $userId is invalid (too long, or empty string)
191
-	 *
192
-	 * @since 32.0.0
193
-	 */
194
-	public function getAllValues(string $userId, bool $filtered = false): array;
195
-
196
-	/**
197
-	 * List all apps storing a specific config key and its stored value.
198
-	 * Returns an array with appId as key, stored value as value.
199
-	 *
200
-	 * @param string $userId id of the user
201
-	 * @param string $key config key
202
-	 * @param bool $lazy search within lazy loaded config
203
-	 * @param ValueType|null $typedAs enforce type for the returned values
204
-	 *
205
-	 * @return array<string, string|int|float|bool|array> [appId => value]
206
-	 * @throws \InvalidArgumentException if $userId or $key is invalid (too long, or empty string)
207
-	 *
208
-	 * @since 32.0.0
209
-	 */
210
-	public function getValuesByApps(string $userId, string $key, bool $lazy = false, ?ValueType $typedAs = null): array;
211
-
212
-	/**
213
-	 * List all users storing a specific config key and its stored value.
214
-	 * Returns an array with userId as key, stored value as value.
215
-	 *
216
-	 * **WARNING:** no caching, generate a fresh request
217
-	 *
218
-	 * @param string $app id of the app
219
-	 * @param string $key config key
220
-	 * @param ValueType|null $typedAs enforce type for the returned values
221
-	 * @param array|null $userIds limit the search to a list of user ids
222
-	 *
223
-	 * @return array<string, string|int|float|bool|array> [userId => value]
224
-	 * @throws \InvalidArgumentException if $app or $key is invalid (too long, or empty string)
225
-	 *
226
-	 * @since 32.0.0
227
-	 */
228
-	public function getValuesByUsers(string $app, string $key, ?ValueType $typedAs = null, ?array $userIds = null): array;
229
-
230
-	/**
231
-	 * List all users storing a specific config key/value pair.
232
-	 * Returns a list of user ids.
233
-	 *
234
-	 * **WARNING:** no caching, generate a fresh request
235
-	 *
236
-	 * @param string $app id of the app
237
-	 * @param string $key config key
238
-	 * @param string $value config value
239
-	 * @param bool $caseInsensitive non-case-sensitive search, only works if $value is a string
240
-	 *
241
-	 * @return Generator<string>
242
-	 * @throws \InvalidArgumentException if $app or $key is invalid (too long, or empty string)
243
-	 *
244
-	 * @since 32.0.0
245
-	 */
246
-	public function searchUsersByValueString(string $app, string $key, string $value, bool $caseInsensitive = false): Generator;
247
-
248
-	/**
249
-	 * List all users storing a specific config key/value pair.
250
-	 * Returns a list of user ids.
251
-	 *
252
-	 * **WARNING:** no caching, generate a fresh request
253
-	 *
254
-	 * @param string $app id of the app
255
-	 * @param string $key config key
256
-	 * @param int $value config value
257
-	 *
258
-	 * @return Generator<string>
259
-	 * @throws \InvalidArgumentException if $app or $key is invalid (too long, or empty string)
260
-	 *
261
-	 * @since 32.0.0
262
-	 */
263
-	public function searchUsersByValueInt(string $app, string $key, int $value): Generator;
264
-
265
-	/**
266
-	 * List all users storing a specific config key/value pair.
267
-	 * Returns a list of user ids.
268
-	 *
269
-	 * **WARNING:** no caching, generate a fresh request
270
-	 *
271
-	 * @param string $app id of the app
272
-	 * @param string $key config key
273
-	 * @param array $values list of possible config values
274
-	 *
275
-	 * @return Generator<string>
276
-	 * @throws \InvalidArgumentException if $app or $key is invalid (too long, or empty string)
277
-	 *
278
-	 * @since 32.0.0
279
-	 */
280
-	public function searchUsersByValues(string $app, string $key, array $values): Generator;
281
-
282
-	/**
283
-	 * List all users storing a specific config key/value pair.
284
-	 * Returns a list of user ids.
285
-	 *
286
-	 * **WARNING:** no caching, generate a fresh request
287
-	 *
288
-	 * @param string $app id of the app
289
-	 * @param string $key config key
290
-	 * @param bool $value config value
291
-	 *
292
-	 * @return Generator<string>
293
-	 * @throws \InvalidArgumentException if $app or $key is invalid (too long, or empty string)
294
-	 *
295
-	 * @since 32.0.0
296
-	 */
297
-	public function searchUsersByValueBool(string $app, string $key, bool $value): Generator;
298
-
299
-	/**
300
-	 * Get user config assigned to a config key.
301
-	 * If config key is not found in database, default value is returned.
302
-	 * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
303
-	 *
304
-	 * @param string $userId id of the user
305
-	 * @param string $app id of the app
306
-	 * @param string $key config key
307
-	 * @param string $default default value
308
-	 * @param bool $lazy search within lazy loaded config
309
-	 *
310
-	 * @return string stored config value or $default if not set in database
311
-	 * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
312
-	 *
313
-	 * @since 32.0.0
314
-	 *
315
-	 * @see IUserConfig for explanation about lazy loading
316
-	 * @see getValueInt()
317
-	 * @see getValueFloat()
318
-	 * @see getValueBool()
319
-	 * @see getValueArray()
320
-	 */
321
-	public function getValueString(string $userId, string $app, string $key, string $default = '', bool $lazy = false): string;
322
-
323
-	/**
324
-	 * Get config value assigned to a config key.
325
-	 * If config key is not found in database, default value is returned.
326
-	 * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
327
-	 *
328
-	 * @param string $userId id of the user
329
-	 * @param string $app id of the app
330
-	 * @param string $key config key
331
-	 * @param int $default default value
332
-	 * @param bool $lazy search within lazy loaded config
333
-	 *
334
-	 * @return int stored config value or $default if not set in database
335
-	 * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
336
-	 *
337
-	 * @since 32.0.0
338
-	 *
339
-	 * @see IUserConfig for explanation about lazy loading
340
-	 * @see getValueString()
341
-	 * @see getValueFloat()
342
-	 * @see getValueBool()
343
-	 * @see getValueArray()
344
-	 */
345
-	public function getValueInt(string $userId, string $app, string $key, int $default = 0, bool $lazy = false): int;
346
-
347
-	/**
348
-	 * Get config value assigned to a config key.
349
-	 * If config key is not found in database, default value is returned.
350
-	 * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
351
-	 *
352
-	 * @param string $userId id of the user
353
-	 * @param string $app id of the app
354
-	 * @param string $key config key
355
-	 * @param float $default default value
356
-	 * @param bool $lazy search within lazy loaded config
357
-	 *
358
-	 * @return float stored config value or $default if not set in database
359
-	 * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
360
-	 *
361
-	 * @since 32.0.0
362
-	 *
363
-	 * @see IUserConfig for explanation about lazy loading
364
-	 * @see getValueString()
365
-	 * @see getValueInt()
366
-	 * @see getValueBool()
367
-	 * @see getValueArray()
368
-	 */
369
-	public function getValueFloat(string $userId, string $app, string $key, float $default = 0, bool $lazy = false): float;
370
-
371
-	/**
372
-	 * Get config value assigned to a config key.
373
-	 * If config key is not found in database, default value is returned.
374
-	 * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
375
-	 *
376
-	 * @param string $userId id of the user
377
-	 * @param string $app id of the app
378
-	 * @param string $key config key
379
-	 * @param bool $default default value
380
-	 * @param bool $lazy search within lazy loaded config
381
-	 *
382
-	 * @return bool stored config value or $default if not set in database
383
-	 * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
384
-	 *
385
-	 * @since 32.0.0
386
-	 *
387
-	 * @see IUserPrefences for explanation about lazy loading
388
-	 * @see getValueString()
389
-	 * @see getValueInt()
390
-	 * @see getValueFloat()
391
-	 * @see getValueArray()
392
-	 */
393
-	public function getValueBool(string $userId, string $app, string $key, bool $default = false, bool $lazy = false): bool;
394
-
395
-	/**
396
-	 * Get config value assigned to a config key.
397
-	 * If config key is not found in database, default value is returned.
398
-	 * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
399
-	 *
400
-	 * @param string $userId id of the user
401
-	 * @param string $app id of the app
402
-	 * @param string $key config key
403
-	 * @param array $default default value`
404
-	 * @param bool $lazy search within lazy loaded config
405
-	 *
406
-	 * @return array stored config value or $default if not set in database
407
-	 * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
408
-	 *
409
-	 * @since 32.0.0
410
-	 *
411
-	 * @see IUserConfig for explanation about lazy loading
412
-	 * @see getValueString()
413
-	 * @see getValueInt()
414
-	 * @see getValueFloat()
415
-	 * @see getValueBool()
416
-	 */
417
-	public function getValueArray(string $userId, string $app, string $key, array $default = [], bool $lazy = false): array;
418
-
419
-	/**
420
-	 * returns the type of config value
421
-	 *
422
-	 * **WARNING:** ignore lazy filtering, all config values are loaded from database
423
-	 *              unless lazy is set to false
424
-	 *
425
-	 * @param string $userId id of the user
426
-	 * @param string $app id of the app
427
-	 * @param string $key config key
428
-	 * @param bool|null $lazy
429
-	 *
430
-	 * @return ValueType type of the value
431
-	 * @throws UnknownKeyException if config key is not known
432
-	 * @throws IncorrectTypeException if config value type is not known
433
-	 * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
434
-	 *
435
-	 * @since 32.0.0
436
-	 */
437
-	public function getValueType(string $userId, string $app, string $key, ?bool $lazy = null): ValueType;
438
-
439
-	/**
440
-	 * returns a bitflag related to config value
441
-	 *
442
-	 * **WARNING:** ignore lazy filtering, all config values are loaded from database
443
-	 *              unless lazy is set to false
444
-	 *
445
-	 * @param string $userId id of the user
446
-	 * @param string $app id of the app
447
-	 * @param string $key config key
448
-	 * @param bool $lazy lazy loading
449
-	 *
450
-	 * @return int a bitflag in relation to the config value
451
-	 * @throws UnknownKeyException if config key is not known
452
-	 * @throws IncorrectTypeException if config value type is not known
453
-	 * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
454
-	 *
455
-	 * @since 32.0.0
456
-	 */
457
-	public function getValueFlags(string $userId, string $app, string $key, bool $lazy = false): int;
458
-
459
-	/**
460
-	 * Store a config key and its value in database
461
-	 *
462
-	 * If config key is already known with the exact same config value, the database is not updated.
463
-	 * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
464
-	 *
465
-	 * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
466
-	 *
467
-	 * @param string $userId id of the user
468
-	 * @param string $app id of the app
469
-	 * @param string $key config key
470
-	 * @param string $value config value
471
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
472
-	 * @param bool $lazy set config as lazy loaded
473
-	 *
474
-	 * @return bool TRUE if value was different, therefor updated in database
475
-	 * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
476
-	 *
477
-	 * @since 32.0.0
478
-	 *
479
-	 * @see IUserConfig for explanation about lazy loading
480
-	 * @see setValueInt()
481
-	 * @see setValueFloat()
482
-	 * @see setValueBool()
483
-	 * @see setValueArray()
484
-	 */
485
-	public function setValueString(string $userId, string $app, string $key, string $value, bool $lazy = false, int $flags = 0): bool;
486
-
487
-	/**
488
-	 * Store a config key and its value in database
489
-	 *
490
-	 * When handling huge value around and/or above 2,147,483,647, a debug log will be generated
491
-	 * on 64bits system, as php int type reach its limit (and throw an exception) on 32bits when using huge numbers.
492
-	 *
493
-	 * When using huge numbers, it is advised to use {@see \OCP\Util::numericToNumber()} and {@see setValueString()}
494
-	 *
495
-	 * If config key is already known with the exact same config value, the database is not updated.
496
-	 * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
497
-	 *
498
-	 * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
499
-	 *
500
-	 * @param string $userId id of the user
501
-	 * @param string $app id of the app
502
-	 * @param string $key config key
503
-	 * @param int $value config value
504
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
505
-	 * @param bool $lazy set config as lazy loaded
506
-	 *
507
-	 * @return bool TRUE if value was different, therefor updated in database
508
-	 * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
509
-	 *
510
-	 * @since 32.0.0
511
-	 *
512
-	 * @see IUserConfig for explanation about lazy loading
513
-	 * @see setValueString()
514
-	 * @see setValueFloat()
515
-	 * @see setValueBool()
516
-	 * @see setValueArray()
517
-	 */
518
-	public function setValueInt(string $userId, string $app, string $key, int $value, bool $lazy = false, int $flags = 0): bool;
519
-
520
-	/**
521
-	 * Store a config key and its value in database.
522
-	 *
523
-	 * If config key is already known with the exact same config value, the database is not updated.
524
-	 * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
525
-	 *
526
-	 * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
527
-	 *
528
-	 * @param string $userId id of the user
529
-	 * @param string $app id of the app
530
-	 * @param string $key config key
531
-	 * @param float $value config value
532
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
533
-	 * @param bool $lazy set config as lazy loaded
534
-	 *
535
-	 * @return bool TRUE if value was different, therefor updated in database
536
-	 * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
537
-	 *
538
-	 * @since 32.0.0
539
-	 *
540
-	 * @see IUserConfig for explanation about lazy loading
541
-	 * @see setValueString()
542
-	 * @see setValueInt()
543
-	 * @see setValueBool()
544
-	 * @see setValueArray()
545
-	 */
546
-	public function setValueFloat(string $userId, string $app, string $key, float $value, bool $lazy = false, int $flags = 0): bool;
547
-
548
-	/**
549
-	 * Store a config key and its value in database
550
-	 *
551
-	 * If config key is already known with the exact same config value, the database is not updated.
552
-	 * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
553
-	 *
554
-	 * If config value was previously stored as lazy loaded, status cannot be altered without using {@see deleteKey()} first
555
-	 *
556
-	 * @param string $userId id of the user
557
-	 * @param string $app id of the app
558
-	 * @param string $key config key
559
-	 * @param bool $value config value
560
-	 * @param bool $lazy set config as lazy loaded
561
-	 *
562
-	 * @return bool TRUE if value was different, therefor updated in database
563
-	 * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
564
-	 *
565
-	 * @since 32.0.0
566
-	 *
567
-	 * @see IUserConfig for explanation about lazy loading
568
-	 * @see setValueString()
569
-	 * @see setValueInt()
570
-	 * @see setValueFloat()
571
-	 * @see setValueArray()
572
-	 */
573
-	public function setValueBool(string $userId, string $app, string $key, bool $value, bool $lazy = false): bool;
574
-
575
-	/**
576
-	 * Store a config key and its value in database
577
-	 *
578
-	 * If config key is already known with the exact same config value, the database is not updated.
579
-	 * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
580
-	 *
581
-	 * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
582
-	 *
583
-	 * @param string $userId id of the user
584
-	 * @param string $app id of the app
585
-	 * @param string $key config key
586
-	 * @param array $value config value
587
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
588
-	 * @param bool $lazy set config as lazy loaded
589
-	 *
590
-	 * @return bool TRUE if value was different, therefor updated in database
591
-	 * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
592
-	 *
593
-	 * @since 32.0.0
594
-	 *
595
-	 * @see IUserConfig for explanation about lazy loading
596
-	 * @see setValueString()
597
-	 * @see setValueInt()
598
-	 * @see setValueFloat()
599
-	 * @see setValueBool()
600
-	 */
601
-	public function setValueArray(string $userId, string $app, string $key, array $value, bool $lazy = false, int $flags = 0): bool;
602
-
603
-	/**
604
-	 * switch sensitive status of a config value
605
-	 *
606
-	 * **WARNING:** ignore lazy filtering, all config values are loaded from database
607
-	 *
608
-	 * @param string $userId id of the user
609
-	 * @param string $app id of the app
610
-	 * @param string $key config key
611
-	 * @param bool $sensitive TRUE to set as sensitive, FALSE to unset
612
-	 *
613
-	 * @return bool TRUE if database update were necessary
614
-	 * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
615
-	 *
616
-	 * @since 32.0.0
617
-	 */
618
-	public function updateSensitive(string $userId, string $app, string $key, bool $sensitive): bool;
619
-
620
-	/**
621
-	 * switch sensitive loading status of a config key for all users
622
-	 *
623
-	 * **Warning:** heavy on resources, MUST only be used on occ command or migrations
624
-	 *
625
-	 * @param string $app id of the app
626
-	 * @param string $key config key
627
-	 * @param bool $sensitive TRUE to set as sensitive, FALSE to unset
628
-	 * @throws \InvalidArgumentException if $app or $key is invalid (too long, or empty string)
629
-	 *
630
-	 * @since 32.0.0
631
-	 */
632
-	public function updateGlobalSensitive(string $app, string $key, bool $sensitive): void;
633
-
634
-
635
-	/**
636
-	 * switch indexed status of a config value
637
-	 *
638
-	 *  **WARNING:** ignore lazy filtering, all config values are loaded from database
639
-	 *
640
-	 * @param string $userId id of the user
641
-	 * @param string $app id of the app
642
-	 * @param string $key config key
643
-	 * @param bool $indexed TRUE to set as indexed, FALSE to unset
644
-	 *
645
-	 * @return bool TRUE if database update were necessary
646
-	 * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
647
-	 *
648
-	 * @since 32.0.0
649
-	 */
650
-	public function updateIndexed(string $userId, string $app, string $key, bool $indexed): bool;
651
-
652
-	/**
653
-	 * switch sensitive loading status of a config key for all users
654
-	 *
655
-	 * **Warning:** heavy on resources, MUST only be used on occ command or migrations
656
-	 *
657
-	 * @param string $app id of the app
658
-	 * @param string $key config key
659
-	 * @param bool $indexed TRUE to set as indexed, FALSE to unset
660
-	 * @throws \InvalidArgumentException if $app or $key is invalid (too long, or empty string)
661
-	 *
662
-	 * @since 32.0.0
663
-	 */
664
-	public function updateGlobalIndexed(string $app, string $key, bool $indexed): void;
665
-
666
-	/**
667
-	 * switch lazy loading status of a config value
668
-	 *
669
-	 * @param string $userId id of the user
670
-	 * @param string $app id of the app
671
-	 * @param string $key config key
672
-	 * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset
673
-	 *
674
-	 * @return bool TRUE if database update was necessary
675
-	 * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
676
-	 *
677
-	 * @since 32.0.0
678
-	 */
679
-	public function updateLazy(string $userId, string $app, string $key, bool $lazy): bool;
680
-
681
-	/**
682
-	 * switch lazy loading status of a config key for all users
683
-	 *
684
-	 * **Warning:** heavy on resources, MUST only be used on occ command or migrations
685
-	 *
686
-	 * @param string $app id of the app
687
-	 * @param string $key config key
688
-	 * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset
689
-	 * @throws \InvalidArgumentException if $app or $key is invalid (too long, or empty string)
690
-	 *
691
-	 * @since 32.0.0
692
-	 */
693
-	public function updateGlobalLazy(string $app, string $key, bool $lazy): void;
694
-
695
-	/**
696
-	 * returns an array contains details about a config value
697
-	 *
698
-	 * ```
699
-	 * [
700
-	 *   "app" => "myapp",
701
-	 *   "key" => "mykey",
702
-	 *   "value" => "its_value",
703
-	 *   "lazy" => false,
704
-	 *   "type" => 4,
705
-	 *   "typeString" => "string",
706
-	 *   'sensitive' => true
707
-	 * ]
708
-	 * ```
709
-	 *
710
-	 * @param string $userId id of the user
711
-	 * @param string $app id of the app
712
-	 * @param string $key config key
713
-	 *
714
-	 * @return array
715
-	 * @throws UnknownKeyException if config key is not known in database
716
-	 * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
717
-	 *
718
-	 * @since 32.0.0
719
-	 */
720
-	public function getDetails(string $userId, string $app, string $key): array;
721
-
722
-	/**
723
-	 * Delete single config key from database.
724
-	 *
725
-	 * @param string $userId id of the user
726
-	 * @param string $app id of the app
727
-	 * @param string $key config key
728
-	 * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
729
-	 *
730
-	 * @since 32.0.0
731
-	 */
732
-	public function deleteUserConfig(string $userId, string $app, string $key): void;
733
-
734
-	/**
735
-	 * Delete config values from all users linked to a specific config keys
736
-	 *
737
-	 * @param string $app id of the app
738
-	 * @param string $key config key
739
-	 * @throws \InvalidArgumentException if $app or $key is invalid (too long, or empty string)
740
-	 *
741
-	 * @since 32.0.0
742
-	 */
743
-	public function deleteKey(string $app, string $key): void;
744
-
745
-	/**
746
-	 * delete all config keys linked to an app
747
-	 *
748
-	 * @param string $app id of the app
749
-	 * @throws \InvalidArgumentException if $app is invalid (too long, or empty string)
750
-	 *
751
-	 * @since 32.0.0
752
-	 */
753
-	public function deleteApp(string $app): void;
754
-
755
-	/**
756
-	 * delete all config keys linked to a user
757
-	 *
758
-	 * @param string $userId id of the user
759
-	 * @throws \InvalidArgumentException if $userId is invalid (too long, or empty string)
760
-	 *
761
-	 * @since 32.0.0
762
-	 */
763
-	public function deleteAllUserConfig(string $userId): void;
764
-
765
-	/**
766
-	 * Clear the cache for a single user
767
-	 *
768
-	 * The cache will be rebuilt only the next time a user config is requested.
769
-	 *
770
-	 * @param string $userId id of the user
771
-	 * @param bool $reload set to TRUE to refill cache instantly after clearing it
772
-	 * @throws \InvalidArgumentException if $userId is invalid (too long, or empty string)
773
-	 *
774
-	 * @since 32.0.0
775
-	 */
776
-	public function clearCache(string $userId, bool $reload = false): void;
777
-
778
-	/**
779
-	 * Clear the cache for all users.
780
-	 * The cache will be rebuilt only the next time a user config is requested.
781
-	 *
782
-	 * @since 32.0.0
783
-	 */
784
-	public function clearCacheAll(): void;
33
+    /**
34
+     * @since 32.0.0
35
+     */
36
+    public const FLAG_SENSITIVE = 1;   // value is sensitive
37
+    /**
38
+     * @since 32.0.0
39
+     */
40
+    public const FLAG_INDEXED = 2;    // value should be indexed
41
+    /**
42
+     * @since 33.0.0
43
+     */
44
+    public const FLAG_INTERNAL = 4;   // value is considered internal and can be hidden from listing
45
+
46
+    /**
47
+     * Get list of all userIds with config stored in database.
48
+     * If $appId is specified, will only limit the search to this value
49
+     *
50
+     * **WARNING:** ignore any cache and get data directly from database.
51
+     *
52
+     * @param string $appId optional id of app
53
+     *
54
+     * @return list<string> list of userIds
55
+     * @throws \InvalidArgumentException if $appId is invalid (too long)
56
+     *
57
+     * @since 32.0.0
58
+     */
59
+    public function getUserIds(string $appId = ''): array;
60
+
61
+    /**
62
+     * Get list of all apps that have at least one config
63
+     * value related to $userId stored in database
64
+     *
65
+     * **WARNING:** ignore lazy filtering, all user config are loaded from database
66
+     *
67
+     * @param string $userId id of the user
68
+     *
69
+     * @return list<string> list of app ids
70
+     * @throws \InvalidArgumentException if $userId is invalid (too long, or empty string)
71
+     *
72
+     * @since 32.0.0
73
+     */
74
+    public function getApps(string $userId): array;
75
+
76
+    /**
77
+     * Returns all keys stored in database, related to user+app.
78
+     * Please note that the values are not returned.
79
+     *
80
+     * **WARNING:** ignore lazy filtering, all user config are loaded from database
81
+     *
82
+     * @param string $userId id of the user
83
+     * @param string $app id of the app
84
+     *
85
+     * @return list<string> list of stored config keys
86
+     * @throws \InvalidArgumentException if $userId or $app is invalid (too long, or empty string)
87
+     *
88
+     * @since 32.0.0
89
+     */
90
+    public function getKeys(string $userId, string $app): array;
91
+
92
+    /**
93
+     * Check if a key exists in the list of stored config values.
94
+     *
95
+     * @param string $userId id of the user
96
+     * @param string $app id of the app
97
+     * @param string $key config key
98
+     * @param bool $lazy search within lazy loaded config
99
+     *
100
+     * @return bool TRUE if key exists
101
+     * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
102
+     *
103
+     * @since 32.0.0
104
+     */
105
+    public function hasKey(string $userId, string $app, string $key, ?bool $lazy = false): bool;
106
+
107
+    /**
108
+     * best way to see if a value is set as sensitive (not displayed in report)
109
+     *
110
+     * @param string $userId id of the user
111
+     * @param string $app id of the app
112
+     * @param string $key config key
113
+     * @param bool|null $lazy search within lazy loaded config
114
+     *
115
+     * @return bool TRUE if value is sensitive
116
+     * @throws UnknownKeyException if config key is not known
117
+     * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
118
+     *
119
+     * @since 32.0.0
120
+     */
121
+    public function isSensitive(string $userId, string $app, string $key, ?bool $lazy = false): bool;
122
+
123
+    /**
124
+     * best way to see if a value is set as indexed (so it can be search)
125
+     *
126
+     * @see self::searchUsersByValueString()
127
+     * @see self::searchUsersByValueInt()
128
+     * @see self::searchUsersByValueBool()
129
+     * @see self::searchUsersByValues()
130
+     *
131
+     * @param string $userId id of the user
132
+     * @param string $app id of the app
133
+     * @param string $key config key
134
+     * @param bool|null $lazy search within lazy loaded config
135
+     *
136
+     * @return bool TRUE if value is sensitive
137
+     * @throws UnknownKeyException if config key is not known
138
+     * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
139
+     *
140
+     * @since 32.0.0
141
+     */
142
+    public function isIndexed(string $userId, string $app, string $key, ?bool $lazy = false): bool;
143
+
144
+    /**
145
+     * Returns if the config key stored in database is lazy loaded
146
+     *
147
+     * **WARNING:** ignore lazy filtering, all config values are loaded from database
148
+     *
149
+     * @param string $userId id of the user
150
+     * @param string $app id of the app
151
+     * @param string $key config key
152
+     *
153
+     * @return bool TRUE if config is lazy loaded
154
+     * @throws UnknownKeyException if config key is not known
155
+     * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
156
+     * @see IUserConfig for details about lazy loading
157
+     *
158
+     * @since 32.0.0
159
+     */
160
+    public function isLazy(string $userId, string $app, string $key): bool;
161
+
162
+    /**
163
+     * List all config values from an app with config key starting with $key.
164
+     * Returns an array with config key as key, stored value as value.
165
+     *
166
+     * **WARNING:** ignore lazy filtering, all config values are loaded from database
167
+     *
168
+     * @param string $userId id of the user
169
+     * @param string $app id of the app
170
+     * @param string $prefix config keys prefix to search, can be empty.
171
+     * @param bool $filtered filter sensitive config values
172
+     *
173
+     * @return array<string, string|int|float|bool|array> [key => value]
174
+     * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
175
+     *
176
+     * @since 32.0.0
177
+     */
178
+    public function getValues(string $userId, string $app, string $prefix = '', bool $filtered = false): array;
179
+
180
+    /**
181
+     * List all config values of a user.
182
+     * Returns an array with config key as key, stored value as value.
183
+     *
184
+     * **WARNING:** ignore lazy filtering, all config values are loaded from database
185
+     *
186
+     * @param string $userId id of the user
187
+     * @param bool $filtered filter sensitive config values
188
+     *
189
+     * @return array<string, string|int|float|bool|array> [key => value]
190
+     * @throws \InvalidArgumentException if $userId is invalid (too long, or empty string)
191
+     *
192
+     * @since 32.0.0
193
+     */
194
+    public function getAllValues(string $userId, bool $filtered = false): array;
195
+
196
+    /**
197
+     * List all apps storing a specific config key and its stored value.
198
+     * Returns an array with appId as key, stored value as value.
199
+     *
200
+     * @param string $userId id of the user
201
+     * @param string $key config key
202
+     * @param bool $lazy search within lazy loaded config
203
+     * @param ValueType|null $typedAs enforce type for the returned values
204
+     *
205
+     * @return array<string, string|int|float|bool|array> [appId => value]
206
+     * @throws \InvalidArgumentException if $userId or $key is invalid (too long, or empty string)
207
+     *
208
+     * @since 32.0.0
209
+     */
210
+    public function getValuesByApps(string $userId, string $key, bool $lazy = false, ?ValueType $typedAs = null): array;
211
+
212
+    /**
213
+     * List all users storing a specific config key and its stored value.
214
+     * Returns an array with userId as key, stored value as value.
215
+     *
216
+     * **WARNING:** no caching, generate a fresh request
217
+     *
218
+     * @param string $app id of the app
219
+     * @param string $key config key
220
+     * @param ValueType|null $typedAs enforce type for the returned values
221
+     * @param array|null $userIds limit the search to a list of user ids
222
+     *
223
+     * @return array<string, string|int|float|bool|array> [userId => value]
224
+     * @throws \InvalidArgumentException if $app or $key is invalid (too long, or empty string)
225
+     *
226
+     * @since 32.0.0
227
+     */
228
+    public function getValuesByUsers(string $app, string $key, ?ValueType $typedAs = null, ?array $userIds = null): array;
229
+
230
+    /**
231
+     * List all users storing a specific config key/value pair.
232
+     * Returns a list of user ids.
233
+     *
234
+     * **WARNING:** no caching, generate a fresh request
235
+     *
236
+     * @param string $app id of the app
237
+     * @param string $key config key
238
+     * @param string $value config value
239
+     * @param bool $caseInsensitive non-case-sensitive search, only works if $value is a string
240
+     *
241
+     * @return Generator<string>
242
+     * @throws \InvalidArgumentException if $app or $key is invalid (too long, or empty string)
243
+     *
244
+     * @since 32.0.0
245
+     */
246
+    public function searchUsersByValueString(string $app, string $key, string $value, bool $caseInsensitive = false): Generator;
247
+
248
+    /**
249
+     * List all users storing a specific config key/value pair.
250
+     * Returns a list of user ids.
251
+     *
252
+     * **WARNING:** no caching, generate a fresh request
253
+     *
254
+     * @param string $app id of the app
255
+     * @param string $key config key
256
+     * @param int $value config value
257
+     *
258
+     * @return Generator<string>
259
+     * @throws \InvalidArgumentException if $app or $key is invalid (too long, or empty string)
260
+     *
261
+     * @since 32.0.0
262
+     */
263
+    public function searchUsersByValueInt(string $app, string $key, int $value): Generator;
264
+
265
+    /**
266
+     * List all users storing a specific config key/value pair.
267
+     * Returns a list of user ids.
268
+     *
269
+     * **WARNING:** no caching, generate a fresh request
270
+     *
271
+     * @param string $app id of the app
272
+     * @param string $key config key
273
+     * @param array $values list of possible config values
274
+     *
275
+     * @return Generator<string>
276
+     * @throws \InvalidArgumentException if $app or $key is invalid (too long, or empty string)
277
+     *
278
+     * @since 32.0.0
279
+     */
280
+    public function searchUsersByValues(string $app, string $key, array $values): Generator;
281
+
282
+    /**
283
+     * List all users storing a specific config key/value pair.
284
+     * Returns a list of user ids.
285
+     *
286
+     * **WARNING:** no caching, generate a fresh request
287
+     *
288
+     * @param string $app id of the app
289
+     * @param string $key config key
290
+     * @param bool $value config value
291
+     *
292
+     * @return Generator<string>
293
+     * @throws \InvalidArgumentException if $app or $key is invalid (too long, or empty string)
294
+     *
295
+     * @since 32.0.0
296
+     */
297
+    public function searchUsersByValueBool(string $app, string $key, bool $value): Generator;
298
+
299
+    /**
300
+     * Get user config assigned to a config key.
301
+     * If config key is not found in database, default value is returned.
302
+     * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
303
+     *
304
+     * @param string $userId id of the user
305
+     * @param string $app id of the app
306
+     * @param string $key config key
307
+     * @param string $default default value
308
+     * @param bool $lazy search within lazy loaded config
309
+     *
310
+     * @return string stored config value or $default if not set in database
311
+     * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
312
+     *
313
+     * @since 32.0.0
314
+     *
315
+     * @see IUserConfig for explanation about lazy loading
316
+     * @see getValueInt()
317
+     * @see getValueFloat()
318
+     * @see getValueBool()
319
+     * @see getValueArray()
320
+     */
321
+    public function getValueString(string $userId, string $app, string $key, string $default = '', bool $lazy = false): string;
322
+
323
+    /**
324
+     * Get config value assigned to a config key.
325
+     * If config key is not found in database, default value is returned.
326
+     * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
327
+     *
328
+     * @param string $userId id of the user
329
+     * @param string $app id of the app
330
+     * @param string $key config key
331
+     * @param int $default default value
332
+     * @param bool $lazy search within lazy loaded config
333
+     *
334
+     * @return int stored config value or $default if not set in database
335
+     * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
336
+     *
337
+     * @since 32.0.0
338
+     *
339
+     * @see IUserConfig for explanation about lazy loading
340
+     * @see getValueString()
341
+     * @see getValueFloat()
342
+     * @see getValueBool()
343
+     * @see getValueArray()
344
+     */
345
+    public function getValueInt(string $userId, string $app, string $key, int $default = 0, bool $lazy = false): int;
346
+
347
+    /**
348
+     * Get config value assigned to a config key.
349
+     * If config key is not found in database, default value is returned.
350
+     * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
351
+     *
352
+     * @param string $userId id of the user
353
+     * @param string $app id of the app
354
+     * @param string $key config key
355
+     * @param float $default default value
356
+     * @param bool $lazy search within lazy loaded config
357
+     *
358
+     * @return float stored config value or $default if not set in database
359
+     * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
360
+     *
361
+     * @since 32.0.0
362
+     *
363
+     * @see IUserConfig for explanation about lazy loading
364
+     * @see getValueString()
365
+     * @see getValueInt()
366
+     * @see getValueBool()
367
+     * @see getValueArray()
368
+     */
369
+    public function getValueFloat(string $userId, string $app, string $key, float $default = 0, bool $lazy = false): float;
370
+
371
+    /**
372
+     * Get config value assigned to a config key.
373
+     * If config key is not found in database, default value is returned.
374
+     * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
375
+     *
376
+     * @param string $userId id of the user
377
+     * @param string $app id of the app
378
+     * @param string $key config key
379
+     * @param bool $default default value
380
+     * @param bool $lazy search within lazy loaded config
381
+     *
382
+     * @return bool stored config value or $default if not set in database
383
+     * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
384
+     *
385
+     * @since 32.0.0
386
+     *
387
+     * @see IUserPrefences for explanation about lazy loading
388
+     * @see getValueString()
389
+     * @see getValueInt()
390
+     * @see getValueFloat()
391
+     * @see getValueArray()
392
+     */
393
+    public function getValueBool(string $userId, string $app, string $key, bool $default = false, bool $lazy = false): bool;
394
+
395
+    /**
396
+     * Get config value assigned to a config key.
397
+     * If config key is not found in database, default value is returned.
398
+     * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
399
+     *
400
+     * @param string $userId id of the user
401
+     * @param string $app id of the app
402
+     * @param string $key config key
403
+     * @param array $default default value`
404
+     * @param bool $lazy search within lazy loaded config
405
+     *
406
+     * @return array stored config value or $default if not set in database
407
+     * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
408
+     *
409
+     * @since 32.0.0
410
+     *
411
+     * @see IUserConfig for explanation about lazy loading
412
+     * @see getValueString()
413
+     * @see getValueInt()
414
+     * @see getValueFloat()
415
+     * @see getValueBool()
416
+     */
417
+    public function getValueArray(string $userId, string $app, string $key, array $default = [], bool $lazy = false): array;
418
+
419
+    /**
420
+     * returns the type of config value
421
+     *
422
+     * **WARNING:** ignore lazy filtering, all config values are loaded from database
423
+     *              unless lazy is set to false
424
+     *
425
+     * @param string $userId id of the user
426
+     * @param string $app id of the app
427
+     * @param string $key config key
428
+     * @param bool|null $lazy
429
+     *
430
+     * @return ValueType type of the value
431
+     * @throws UnknownKeyException if config key is not known
432
+     * @throws IncorrectTypeException if config value type is not known
433
+     * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
434
+     *
435
+     * @since 32.0.0
436
+     */
437
+    public function getValueType(string $userId, string $app, string $key, ?bool $lazy = null): ValueType;
438
+
439
+    /**
440
+     * returns a bitflag related to config value
441
+     *
442
+     * **WARNING:** ignore lazy filtering, all config values are loaded from database
443
+     *              unless lazy is set to false
444
+     *
445
+     * @param string $userId id of the user
446
+     * @param string $app id of the app
447
+     * @param string $key config key
448
+     * @param bool $lazy lazy loading
449
+     *
450
+     * @return int a bitflag in relation to the config value
451
+     * @throws UnknownKeyException if config key is not known
452
+     * @throws IncorrectTypeException if config value type is not known
453
+     * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
454
+     *
455
+     * @since 32.0.0
456
+     */
457
+    public function getValueFlags(string $userId, string $app, string $key, bool $lazy = false): int;
458
+
459
+    /**
460
+     * Store a config key and its value in database
461
+     *
462
+     * If config key is already known with the exact same config value, the database is not updated.
463
+     * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
464
+     *
465
+     * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
466
+     *
467
+     * @param string $userId id of the user
468
+     * @param string $app id of the app
469
+     * @param string $key config key
470
+     * @param string $value config value
471
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
472
+     * @param bool $lazy set config as lazy loaded
473
+     *
474
+     * @return bool TRUE if value was different, therefor updated in database
475
+     * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
476
+     *
477
+     * @since 32.0.0
478
+     *
479
+     * @see IUserConfig for explanation about lazy loading
480
+     * @see setValueInt()
481
+     * @see setValueFloat()
482
+     * @see setValueBool()
483
+     * @see setValueArray()
484
+     */
485
+    public function setValueString(string $userId, string $app, string $key, string $value, bool $lazy = false, int $flags = 0): bool;
486
+
487
+    /**
488
+     * Store a config key and its value in database
489
+     *
490
+     * When handling huge value around and/or above 2,147,483,647, a debug log will be generated
491
+     * on 64bits system, as php int type reach its limit (and throw an exception) on 32bits when using huge numbers.
492
+     *
493
+     * When using huge numbers, it is advised to use {@see \OCP\Util::numericToNumber()} and {@see setValueString()}
494
+     *
495
+     * If config key is already known with the exact same config value, the database is not updated.
496
+     * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
497
+     *
498
+     * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
499
+     *
500
+     * @param string $userId id of the user
501
+     * @param string $app id of the app
502
+     * @param string $key config key
503
+     * @param int $value config value
504
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
505
+     * @param bool $lazy set config as lazy loaded
506
+     *
507
+     * @return bool TRUE if value was different, therefor updated in database
508
+     * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
509
+     *
510
+     * @since 32.0.0
511
+     *
512
+     * @see IUserConfig for explanation about lazy loading
513
+     * @see setValueString()
514
+     * @see setValueFloat()
515
+     * @see setValueBool()
516
+     * @see setValueArray()
517
+     */
518
+    public function setValueInt(string $userId, string $app, string $key, int $value, bool $lazy = false, int $flags = 0): bool;
519
+
520
+    /**
521
+     * Store a config key and its value in database.
522
+     *
523
+     * If config key is already known with the exact same config value, the database is not updated.
524
+     * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
525
+     *
526
+     * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
527
+     *
528
+     * @param string $userId id of the user
529
+     * @param string $app id of the app
530
+     * @param string $key config key
531
+     * @param float $value config value
532
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
533
+     * @param bool $lazy set config as lazy loaded
534
+     *
535
+     * @return bool TRUE if value was different, therefor updated in database
536
+     * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
537
+     *
538
+     * @since 32.0.0
539
+     *
540
+     * @see IUserConfig for explanation about lazy loading
541
+     * @see setValueString()
542
+     * @see setValueInt()
543
+     * @see setValueBool()
544
+     * @see setValueArray()
545
+     */
546
+    public function setValueFloat(string $userId, string $app, string $key, float $value, bool $lazy = false, int $flags = 0): bool;
547
+
548
+    /**
549
+     * Store a config key and its value in database
550
+     *
551
+     * If config key is already known with the exact same config value, the database is not updated.
552
+     * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
553
+     *
554
+     * If config value was previously stored as lazy loaded, status cannot be altered without using {@see deleteKey()} first
555
+     *
556
+     * @param string $userId id of the user
557
+     * @param string $app id of the app
558
+     * @param string $key config key
559
+     * @param bool $value config value
560
+     * @param bool $lazy set config as lazy loaded
561
+     *
562
+     * @return bool TRUE if value was different, therefor updated in database
563
+     * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
564
+     *
565
+     * @since 32.0.0
566
+     *
567
+     * @see IUserConfig for explanation about lazy loading
568
+     * @see setValueString()
569
+     * @see setValueInt()
570
+     * @see setValueFloat()
571
+     * @see setValueArray()
572
+     */
573
+    public function setValueBool(string $userId, string $app, string $key, bool $value, bool $lazy = false): bool;
574
+
575
+    /**
576
+     * Store a config key and its value in database
577
+     *
578
+     * If config key is already known with the exact same config value, the database is not updated.
579
+     * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
580
+     *
581
+     * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
582
+     *
583
+     * @param string $userId id of the user
584
+     * @param string $app id of the app
585
+     * @param string $key config key
586
+     * @param array $value config value
587
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
588
+     * @param bool $lazy set config as lazy loaded
589
+     *
590
+     * @return bool TRUE if value was different, therefor updated in database
591
+     * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
592
+     *
593
+     * @since 32.0.0
594
+     *
595
+     * @see IUserConfig for explanation about lazy loading
596
+     * @see setValueString()
597
+     * @see setValueInt()
598
+     * @see setValueFloat()
599
+     * @see setValueBool()
600
+     */
601
+    public function setValueArray(string $userId, string $app, string $key, array $value, bool $lazy = false, int $flags = 0): bool;
602
+
603
+    /**
604
+     * switch sensitive status of a config value
605
+     *
606
+     * **WARNING:** ignore lazy filtering, all config values are loaded from database
607
+     *
608
+     * @param string $userId id of the user
609
+     * @param string $app id of the app
610
+     * @param string $key config key
611
+     * @param bool $sensitive TRUE to set as sensitive, FALSE to unset
612
+     *
613
+     * @return bool TRUE if database update were necessary
614
+     * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
615
+     *
616
+     * @since 32.0.0
617
+     */
618
+    public function updateSensitive(string $userId, string $app, string $key, bool $sensitive): bool;
619
+
620
+    /**
621
+     * switch sensitive loading status of a config key for all users
622
+     *
623
+     * **Warning:** heavy on resources, MUST only be used on occ command or migrations
624
+     *
625
+     * @param string $app id of the app
626
+     * @param string $key config key
627
+     * @param bool $sensitive TRUE to set as sensitive, FALSE to unset
628
+     * @throws \InvalidArgumentException if $app or $key is invalid (too long, or empty string)
629
+     *
630
+     * @since 32.0.0
631
+     */
632
+    public function updateGlobalSensitive(string $app, string $key, bool $sensitive): void;
633
+
634
+
635
+    /**
636
+     * switch indexed status of a config value
637
+     *
638
+     *  **WARNING:** ignore lazy filtering, all config values are loaded from database
639
+     *
640
+     * @param string $userId id of the user
641
+     * @param string $app id of the app
642
+     * @param string $key config key
643
+     * @param bool $indexed TRUE to set as indexed, FALSE to unset
644
+     *
645
+     * @return bool TRUE if database update were necessary
646
+     * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
647
+     *
648
+     * @since 32.0.0
649
+     */
650
+    public function updateIndexed(string $userId, string $app, string $key, bool $indexed): bool;
651
+
652
+    /**
653
+     * switch sensitive loading status of a config key for all users
654
+     *
655
+     * **Warning:** heavy on resources, MUST only be used on occ command or migrations
656
+     *
657
+     * @param string $app id of the app
658
+     * @param string $key config key
659
+     * @param bool $indexed TRUE to set as indexed, FALSE to unset
660
+     * @throws \InvalidArgumentException if $app or $key is invalid (too long, or empty string)
661
+     *
662
+     * @since 32.0.0
663
+     */
664
+    public function updateGlobalIndexed(string $app, string $key, bool $indexed): void;
665
+
666
+    /**
667
+     * switch lazy loading status of a config value
668
+     *
669
+     * @param string $userId id of the user
670
+     * @param string $app id of the app
671
+     * @param string $key config key
672
+     * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset
673
+     *
674
+     * @return bool TRUE if database update was necessary
675
+     * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
676
+     *
677
+     * @since 32.0.0
678
+     */
679
+    public function updateLazy(string $userId, string $app, string $key, bool $lazy): bool;
680
+
681
+    /**
682
+     * switch lazy loading status of a config key for all users
683
+     *
684
+     * **Warning:** heavy on resources, MUST only be used on occ command or migrations
685
+     *
686
+     * @param string $app id of the app
687
+     * @param string $key config key
688
+     * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset
689
+     * @throws \InvalidArgumentException if $app or $key is invalid (too long, or empty string)
690
+     *
691
+     * @since 32.0.0
692
+     */
693
+    public function updateGlobalLazy(string $app, string $key, bool $lazy): void;
694
+
695
+    /**
696
+     * returns an array contains details about a config value
697
+     *
698
+     * ```
699
+     * [
700
+     *   "app" => "myapp",
701
+     *   "key" => "mykey",
702
+     *   "value" => "its_value",
703
+     *   "lazy" => false,
704
+     *   "type" => 4,
705
+     *   "typeString" => "string",
706
+     *   'sensitive' => true
707
+     * ]
708
+     * ```
709
+     *
710
+     * @param string $userId id of the user
711
+     * @param string $app id of the app
712
+     * @param string $key config key
713
+     *
714
+     * @return array
715
+     * @throws UnknownKeyException if config key is not known in database
716
+     * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
717
+     *
718
+     * @since 32.0.0
719
+     */
720
+    public function getDetails(string $userId, string $app, string $key): array;
721
+
722
+    /**
723
+     * Delete single config key from database.
724
+     *
725
+     * @param string $userId id of the user
726
+     * @param string $app id of the app
727
+     * @param string $key config key
728
+     * @throws \InvalidArgumentException if $userId, $app or $key is invalid (too long, or empty string)
729
+     *
730
+     * @since 32.0.0
731
+     */
732
+    public function deleteUserConfig(string $userId, string $app, string $key): void;
733
+
734
+    /**
735
+     * Delete config values from all users linked to a specific config keys
736
+     *
737
+     * @param string $app id of the app
738
+     * @param string $key config key
739
+     * @throws \InvalidArgumentException if $app or $key is invalid (too long, or empty string)
740
+     *
741
+     * @since 32.0.0
742
+     */
743
+    public function deleteKey(string $app, string $key): void;
744
+
745
+    /**
746
+     * delete all config keys linked to an app
747
+     *
748
+     * @param string $app id of the app
749
+     * @throws \InvalidArgumentException if $app is invalid (too long, or empty string)
750
+     *
751
+     * @since 32.0.0
752
+     */
753
+    public function deleteApp(string $app): void;
754
+
755
+    /**
756
+     * delete all config keys linked to a user
757
+     *
758
+     * @param string $userId id of the user
759
+     * @throws \InvalidArgumentException if $userId is invalid (too long, or empty string)
760
+     *
761
+     * @since 32.0.0
762
+     */
763
+    public function deleteAllUserConfig(string $userId): void;
764
+
765
+    /**
766
+     * Clear the cache for a single user
767
+     *
768
+     * The cache will be rebuilt only the next time a user config is requested.
769
+     *
770
+     * @param string $userId id of the user
771
+     * @param bool $reload set to TRUE to refill cache instantly after clearing it
772
+     * @throws \InvalidArgumentException if $userId is invalid (too long, or empty string)
773
+     *
774
+     * @since 32.0.0
775
+     */
776
+    public function clearCache(string $userId, bool $reload = false): void;
777
+
778
+    /**
779
+     * Clear the cache for all users.
780
+     * The cache will be rebuilt only the next time a user config is requested.
781
+     *
782
+     * @since 32.0.0
783
+     */
784
+    public function clearCacheAll(): void;
785 785
 }
Please login to merge, or discard this patch.